Every plugin iframe loaded /plugin-sandbox, a force-dynamic Next page.
Each frame then fetched, parsed and hydrated Next's client router on top
of React and the runtime, and the server rendered the page once per
frame, one after another when a dozen plugins started together.
Sandboxed frames have opaque origins, so the browser's code cache never
helps, and they run on the host page's main thread: with 12 plugins
the frames took 2.5-3 s to come up after the app had loaded.
The sandbox routes now return a small HTML page with the UI kit's
stylesheet and the runtime as an ES module, both built ahead of time:
- `scripts/build-plugin-sandbox.mjs` bundles the runtime with esbuild
and compiles `plugin-kit.css` with the app's Tailwind setup into
`public/plugin-sandbox-assets/`, with a manifest. Named Tabler imports
are pointed at the icons' own files, so the full icon set stays in a
chunk that only loads for an icon name the kit doesn't ship.
- `npm run build`, `npm run dev`, the Dockerfile and the perf-guard
workflow run it before `next build`.
- `lib/plugin-sandbox/sandbox-document.ts` writes the page; proxy.ts
still sets the sandbox CSP and the script carries its nonce.
- The assets are served from public/ with CORS and immutable caching.
A frame on an idle page is ready in 255 ms instead of 362 ms, eleven at
once in 1.0 s instead of 2.1 s.
Typing @ at the start of a word in the rich-text body offers the To and
Cc recipients (contact groups expanded) and inserts the chosen one's
first name as plain text. The name comes from the display name; with a
firstname.lastname@ address, the address tells which word of the name is
the first name, and without a name the address supplies it. A bare
initial keeps the mailbox name (@m.mustermann), and recipients who share
a first name get their full name. Bcc recipients are never offered -
naming one in the text would disclose them to everyone else.
The list only opens while a recipient matches, so Enter and Escape
otherwise keep their usual meaning. While it is open, Escape closes the
list rather than the composer, which handles Escape in the capture phase,
and the list closes when the editor loses focus.
Built on @tiptap/suggestion, at the version of the other TipTap packages.
Remove packages nothing imports any more (`@tiptap/extension-image`,
`asn1js`, `pkijs`, `pvtsutils`, `sonner`, `webcrypto-liner`) and the
`elliptic` override, which only applied through `webcrypto-liner`.
`sharp`, `undici`, `@tiptap/extension-paragraph` and
`@tiptap/extension-heading` were imported directly but only resolved as
transitive dependencies of next, jsdom and starter-kit. List them
explicitly and point the `undici` override at `$undici` so both stay in
step.
Keep the latest Docker tags on the stable release when a GitHub release is marked as a pre-release, so auto-updating deployments on :latest do not pick up beta builds.
`npm run typecheck` failed with 8 TS2307 errors from Next's generated
route validators referencing old page paths (e.g. calendar/page.js) after
routes moved to catch-all segments. Two causes:
- The production validator (.next/types) was stale. Prepend `next typegen`
so `tsc` always runs against freshly generated route types.
- The dev validator (.next/dev/types) is a turbopack-dev-only artifact that
is absent from clean/CI checkouts and goes stale locally. `next typegen`
force-re-adds it to tsconfig `include`, so exclude it instead (exclude
overrides include), leaving route typing to the maintained .next/types.
tsc still catches real type errors; verified `npm run typecheck` is clean
and idempotent.
- tailwind.config.ts is not actually being used so removed
- moved animate-fade-in to globals.css
- added tw-animate-css package to make animate-in work
- added a definition for animate-shake as it doesn't exist in tw-animate-css
Add an end-to-end integration harness that runs the webmail against a real
Stalwart mail server in Docker and drives it with Playwright, focused on the
mail/folder synchronisation behaviour (unread/total counters, folder-list
sync, account-scoped Unified Mailbox) that the unified-mailbox work touches.
- integration/ stack: Stalwart (declarative bootstrap: alice/bob/carol,
submission + IMAP listeners, permissive CORS) + webmail (dev mode, so the
browser's plaintext cross-origin JMAP calls aren't blocked by the prod CSP).
- Helpers: dependency-free SMTP submit client, JMAP client for seeding /
inspecting server state, and page helpers (login, add/switch account,
locale-independent folder-counter reads).
- Specs: login, single-account sync (receive/read/move/delete/folder-create/
burst) and multi-account (per-account isolation + cross-account Unified
Inbox aggregation + background-account delivery). 12 tests, all green.
- Add focused data-testid hooks to the mail UI (folder rows + counters, email
list items, account switcher, composer) for stable selectors.
- Exclude examples/ and integration/ from tsconfig/eslint/.dockerignore.
* feat: add Jalali (Persian/Shamsi) calendar support with Saturday as week start
- Add jalaali-js library for Gregorian ↔ Jalali date conversion
- Create lib/jalali-utils.ts with Jalali calendar utilities
- Create hooks/use-calendar-locale.ts for unified calendar locale handling
- Expand FirstDayOfWeek type to include 6 (Saturday)
- Update all calendar views (month, week, day, mini, toolbar) to support
Jalali calendar display and Saturday-first week ordering
- Add Jalali month names (Farvardin … Esfand) to all locale files
- Add Persian (fa) locale with full translations
- Update settings UI to include Saturday as first day of week option
- Update useFormatEventDate to show Jalali dates when locale is fa
- Auto-detect Jalali calendar when fa locale is active
The calendar system automatically switches to Jalali when the locale is
set to Persian (fa). All internal date handling remains Gregorian (ISO
8601) for JMAP protocol compatibility; Jalali conversion is purely at
the display layer.
* Add PR template for Jalali calendar feature
* chore: remove accidentally added PR template
* fix: add image_too_large key to fa locale for PR #462 compatibility
Next 16 removed the `next lint` subcommand, so `npm run lint` failed with
"Invalid project directory provided, no such directory: .../lint". Point the
lint and lint:fix scripts at ESLint directly, using the existing flat config
(eslint.config.mjs).