Commit Graph
4 Commits
Author SHA1 Message Date
lukasandClaude Opus 4.8 57a1f1562f M4 Calendar: reminders form, invitations, short publish links, settings UX
Calendar settings (Settings > Calendars):
- Reminders are now {method, minutes}: Add-reminder opens a boxed form with a
  method dropdown (Show an alert / Email me) + time dropdown, bell icons.
  Backend normalizes the legacy list[int] transparently.
- New Invitations section (auto-add emailed events): policy radios, "Add new
  events from" (My contacts / Anyone), "Add to calendar", mark-read+file toggle.
  Persisted to user settings (inbound iMIP processing still deferred).
- Time zone options show GMT codes, e.g. "Central European Summer Time (GMT+2)".
- Default calendar now defaults to "Personal" instead of a generic placeholder.
- Colour picker is a circular-swatch-only dropdown (sized so swatches don't overlap).
- Delete calendar / remove subscription now confirm via a modal.
- Create form: dropped the breadcrumb; heading stays "Create calendar" even after
  a Publish toggle auto-creates the calendar.

Publish links: replaced the 244-char vault-sealed token with a 16-char random
slug (secrets.token_urlsafe). New sdk.public_session() (privileged, non-RLS) lets
the public /public/{token} feed resolve the slug directly with explicit filters.

Settings shell: "<- Mail" is now a context-aware "<- Back" via ?from=; Calendar
opens Settings with from=/p/calendar so Back returns to the calendar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-08-02 08:48:33 +02:00
lukasandClaude Opus 4.8 8c634f7967 M4: Calendar plugin (builtin) + mail HTML/attachment fixes
Calendar plugin (webmail_plugin_calendar, non-removable core plugin):
- Local calendars/events CRUD with RLS isolation; recurrence expanded at
  query time (DST-correct), EXDATE + detached-override merge.
- Calendar app bundle: month/week/day/3-day views, mini-month, event form
  (color, show-as, timezone combobox, repeat, reminders, guests w/ recipient
  autocomplete, Jitsi video call), event popover with RSVP badges, resizable/
  collapsible left panel + theme toggle, overlap layout.
- iMIP invites: sdk.mail sends REQUEST from the organizer's mailbox (HTML +
  text/calendar + invite.ics); public /rsvp endpoint (vault-sealed token)
  records accepted/declined/tentative per attendee.
- "This and following" edit + scoped delete (this / this-and-following), with
  a delete confirmation dialog for recurring events.

Core:
- Settings: time_format (12/24h, default 24h) + jitsi_server; new Integrations
  section; date order follows time format (en-GB vs en-US) across Mail.
- Plugin SDK: mail (send from a user's account), vault (envelope encrypt),
  multi-method SSRF-guarded http; AppSwitcher product switcher; full-bleed
  PluginPage.

Mail HTML rendering:
- Sanitizer allows <style> blocks + class (CSS sanitized: drop script-like,
  strip @import/url() when remote images blocked); SANITIZER_VERSION 3 so
  already-indexed mail re-sanitizes on view.
- Attachments: a Content-Disposition: attachment part is a real attachment
  even when it also carries a Content-ID (fixes hidden invite.ics).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 16:33:34 +02:00
lukasandClaude Opus 4.8 d46ad13b7b M3 plugin platform: frontend host, reference plugins, UI kit + docs
Completes M3 (SPEC §11 + §12 acceptance) on top of the backend platform (4e077f1).

Frontend host (§11.3):
- src/plugins/: manifest loader, runtime ESM bundle loader (framework-agnostic
  mount contract), PluginSlot, PluginPage (app.page at /p/*), plugin context
  (scoped api/events/i18n), capabilities hook. Wired slots into ThreadView
  (message.actions/message.banner), MailLayout sidebar (sidebar.section + page nav)
  and Settings→Accounts (settings.page); ws.ts fans out core + plugin.* events.
- plugin-ui.css: theme-aware .wm-* classes so bundles don't re-derive inline styles.

SDK additions:
- CurrentUserId + require_csrf (plugin-router auth), messages.envelope/header,
  actions.set_flags/trash, sdk.enqueue.

Reference plugins (plugins/, installed via webmail.plugins entry points):
- hello: skeleton page-plugin (app.page + plg_hello_note w/ RLS + cron job) —
  install→run→uninstall --drop-data leaves zero residue.
- labels: message.actions + sidebar.section slots, KV only, zero core diffs.
- filters: message.new hook -> match (subject/from/to) -> Mark read / Pin / Delete
  (delete = move thread to Trash via the core path).

docs/plugins.md: full authoring guide + a complete "Unsubscribe button" worked
example (the §12 "writable from docs alone" bar).

Also: conftest resets the arq pool between tests (loop-bound global, like engine/redis).

Gates: backend ruff + mypy --strict (107) + full suite 337; frontend tsc + eslint
+ vitest (11) + vite build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rcj3TpydMTzTSmU7kunzf8
2026-07-27 08:30:24 +02:00
lukasandClaude Opus 4.8 4e077f1a37 M3 plugin platform: SDK + loader, RLS-isolated tables, migrations, jobs
Backend foundation for the plugin platform (SPEC §11), Phases 1-3.

Phase 1 — SDK + loader:
- app/plugins/types.py: frozen Plugin/UiManifest/Slot/Page/JobSpec contract.
- app/plugins/sdk.py: sdk(name) facade — the only app.* import allowed for plugins
  (scoped session, plugin.{name}.* publish, blob, outbox, settings, SSRF http_get, KV, enqueue).
- app/plugins/loader.py: entry-point discovery (webmail.plugins), hook forwarding
  (API + worker), router mounting at /api/plugins/{name}, ui-manifest aggregation.
- plugin_kv core table (migration 0013) + GET /api/plugins/ui-manifest.

Phase 2 — non-removable core plugins + DB-enforced privacy:
- builtin flag (calendar/contacts will be builtin=True; uninstall refuses them).
- RLS per-user isolation on plg_* tables: least-privilege webmail_plugin role
  (migration 0014), plugin_session(user_id) sets LOCAL ROLE + app.current_user_id,
  rls.enable_rls() (ENABLE+FORCE RLS + USING/WITH CHECK policy). Mail untouched.
- Per-plugin Alembic runner (shared env, dedicated version table) + install/uninstall
  --drop-data CLI; skeleton fixture proves install -> run -> uninstall --drop-data
  leaves zero residue (§12 M3 acceptance).

Phase 3 — plugin jobs (§11.2):
- JobSpec registered as arq function plg.{plugin}.{func} + cron (5-field parser);
  singleton=True guarded by a per-(plugin,user) Redis lock; wired onto the shared
  interactive worker.

Gates: ruff, mypy --strict (94 files + tests), full suite 326 passed. RLS isolation,
migration round-trip, and singleton lock covered by tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rcj3TpydMTzTSmU7kunzf8
2026-07-26 21:42:35 +02:00