AkurAI Build
Menu

Public repository / ecdd82c68dc11022c41dbf1ff22cac68e002a3e1

AkurAI-Mail / docs/framework-migration.md

Latest change ecdd82c68dc11022c41dbf1ff22cac68e002a3e1 - OLI-247: wave 2 — signal-badge view model and signals-based sync state on framework runtime by Ólafur Búi Ólafsson

# Webmail UI → AkurAI-Framework migration plan (OLI-241)

Framework client runtime (AkurAI-Framework `site/frontend`): native ES modules, **no build step, no npm deps**.
`signals.js` (signal/effect + `data-*` directives), `islands.js` (`registerIsland`, hydrate server-rendered markup),
`spa.js` (router), `akurai-client.js` (typed auto-API client), `ai/components` (chat/tool UI), server-side templates.
Source of truth checked: AkurAI-Framework@d506ed5.

## Inventory and mapping

| Current (SvelteKit) | Size | Framework equivalent | Migration step |
|---|---|---|---|
| `src/lib/webmail/shortcuts.ts` + toggle in `+page.svelte` | 36 | `signals.js` + plain ESM | **Done in OLI-241** → `src/framework/shortcuts.js` |
| `filters.ts`, `folders.ts`, `threads.ts`, `virtual.ts`, `recipients.ts`, `compose.ts`, `session.ts` (pure logic + tests) | ~380 | Plain ESM + JSDoc, tests stay on `bun test` | Rename `.ts`→`.js` with JSDoc types; no framework API needed. Wave 1. |
| `sync.ts`, `sync.svelte.ts` (rune store) | 91 | `signals.js` `signal`/`effect` | Wave 1 (replace runes with signals) |
| `SignalBadge`, `SyncBanner`, `SettingsDrawer` | 231 | Islands (`registerIsland`) over server-rendered markup | Wave 2 |
| `RecipientCombobox`, `RichTextEditor`, `ComposeDialog` | 212 | Islands; editor needs a framework rich-text component (gap G4) | Wave 3 |
| `MessageList.svelte` (virtual list, threads, chips) | 605 | Island + `data-for`; virtualization needs gap G3 | Wave 3 |
| `MessageReader.svelte` | 381 | Island; sanitized-HTML render | Wave 3 |
| `src/routes/webmail/+page.svelte` (state, actions, layout, CSS) | 1727 | Server template + `spa.js` route + one top-level island holding a signal store | Wave 4, last (decompose first) |
| `src/routes/webmail/+page.ts` (auth gate + load) | 18 | Framework auth/sessions (`/docs/auth`) + `akurai-client.js` | Wave 4 |
| `src/lib/api.ts` | 23 | `akurai-client.js` | Wave 4; keep `api.test.ts` green by porting 1:1 |

Order rationale: leaf logic first (no UI risk), then components bottom-up, page last. Each wave ships behind green `bun test ./src`
and `scripts/webmail-shortcuts-e2e.ts`-style browser checks.

## Wave 0 (this issue): shortcuts slice

- `src/framework/shortcuts.js`: `resolveKey` keymap, `createShortcuts` (signal-driven listener + persisted toggle), `scrollSelectedIntoView`.
- `src/framework/vendor/signals.js`: vendored framework runtime (see G1/G2).
- `+page.svelte` now only maps actions to its own state; the keymap, toggle state and scroll helper live in the framework module.
- Tests: `src/framework/shortcuts.test.js` (ported from OLI-239, written red first), plus `scripts/webmail-shortcuts-e2e.ts` (headless chromium against a mock API: Enter opens, `#` prompts delete, `j` scrolls selection into view, toggle persists and silences keys).
- Behaviour note: the OLI-239 `shortcuts.ts` was only partially wired (page had its own handler with extra keys R/f/s/u/!/Delete/arrows). The framework module is now the single keymap and covers all of them; behaviour is unchanged.

## Framework gaps (to file as AkurAI-Framework issues)

| # | Gap | Why mail needs it | Proposed owner |
|---|---|---|---|
| G1 | `signals.js` touches `document` at import time → cannot be imported under `bun test`/SSR | Unit-testing any framework module; vendored copy carries a one-line guard | Framework dev (Dev Lead to file) |
| G2 | No installable/versioned client package (files are served from the site only) | We vendor-copy `signals.js`, which can drift; need a pinned import path (npm tarball or `/vendor/akurai@x.y.z/`) | Platform Engineer + Framework |
| G3 | No virtualized list / windowing primitive for `data-for` | Inbox lists reach thousands of rows (`virtual.ts` today) | Framework dev |
| G4 | No rich-text editor component | Compose (`RichTextEditor.svelte`) | Framework dev |
| G5 | No keyboard-shortcut/hotkey helper or focus-scope utility | Generic version of today's `src/framework/shortcuts.js`; upstream it | Mail Dev (extract) → Framework |
| G6 | `data-*` directives use `new Function`/`with`; mail renders untrusted content so only island JS (not directives) may touch message data | CSP and XSS posture for message bodies | SecOps Engineer review |
| G7 | No typed (`.d.ts`) surface for signals/islands/client; we use JSDoc + `checkJs` | TypeScript-checked consumers | Framework dev |
| G8 | Framework SPA router vs. IdP return_to redirect flow (`loginUrl`) undocumented | Page auth gate migration (Wave 4) | Framework dev + IDP owner |

## Risks

- Tooling: `package.json` still pins SvelteKit/Vite; the Svelte page imports the framework module through Vite until Wave 4 removes Svelte.
- `bun` is resolved via mise `latest` — pin before wave 1 (Dev Lead).
- Do not deploy mid-migration waves without the browser check passing.

## Wave 2 progress (OLI-247)

- `src/framework/signal-badge.js`: SignalBadge view model (label/color/icon/age); `SignalBadge.svelte` now uses it.
- `src/framework/sync-state.js`: signals-based sync store with injectable clock/timers (tested); not yet wired — `sync.svelte.ts` stays until the page moves off Svelte (Wave 4 needs a signal→rune bridge).
- Not done: SyncBanner/SettingsDrawer islands (need server-rendered markup + island host from Wave 4), `svelte-check` (no node_modules in worktree).
- Run tests with the mise bun directly: `~/.local/share/mise/installs/bun/1.4.2/bin/bun test ./src` (shim fails in sandbox).