diff --git a/docs/architecture-map/01-frontend/README.md b/docs/architecture-map/01-frontend/README.md new file mode 100644 index 0000000..8b9dab3 --- /dev/null +++ b/docs/architecture-map/01-frontend/README.md @@ -0,0 +1,90 @@ +--- +name: Frontend slice (Svelte 5 + SvelteKit + Tauri webview) +type: architecture-map-slice-index +slice: 01-frontend +last_verified: 2026/05/09 +--- + +# Frontend (Slice 01) + +> **Where you are:** [Architecture map](../README.md) → Frontend + +**Plain English summary.** This slice is everything the user sees. It is a Svelte 5 single page app, served by SvelteKit in SPA mode, hosted inside Tauri's webview. It owns four windows (main, tasks float, transcript viewer, transcription preview overlay), seven page modules switched by an in memory router store, around thirty reusable components, ten reactive stores, and the design system tokens. The frontend never touches the filesystem, audio, models or the LLM directly. Everything that crosses the WebView boundary goes through `invoke()` (calling Rust commands) or Tauri events. If you delete this slice, you delete the entire user surface but keep the engine. + +## At a glance + +- **Path:** `src/` +- **Total LOC counted:** about 17 200 (583 `app.css` + 2 740 routes + 6 230 pages + 3 174 components + 4 085 stores/utils/types + ~ 2 360 design system + 130 misc). +- **File counts:** 7 pages, 25 components, 10 stores, 1 action, 16 utils, 1 type module, 3 locales, 4 routes (root + float/viewer/preview), 20 design system preview HTMLs, 3 design system JSX kits. +- **Frameworks:** Svelte 5 (runes mode), SvelteKit 2.58, `@sveltejs/adapter-static` with `index.html` fallback (SPA), Vite 6, Tailwind v4 via `@tailwindcss/vite`, svelte-i18n 4, lucide-svelte for icons. +- **Tauri SDK touchpoints:** `@tauri-apps/api` (core invoke, event, window) plus `plugin-autostart`, `plugin-dialog`, `plugin-global-shortcut`, `plugin-notification`, `plugin-opener`. SSR is disabled (`src/routes/+layout.js`) so the whole tree is client side only. +- **Persistence used directly by frontend:** `localStorage` for `magnotia_settings`, `magnotia_profiles`, `magnotia_task_lists`, `magnotia_templates`, `magnotia_locale`, plus a small handoff key for the viewer window. Preferences additionally persist via Tauri (`save_preferences`). +- **Build commands:** `npm run dev` (`svelte-kit sync && vite dev`), `npm run build`, `npm run check` (svelte-check using `jsconfig.json`). + +## Map of this slice + +- [Windows and routes](windows-and-routes.md). The four Tauri windows, the SvelteKit routes that back them, the `+layout@.svelte` break, and how the shell decides between custom chrome and native decorations. +- [Pages overview](pages-overview.md). Index of the seven pages routed by `page.current`, and the `/float`, `/viewer`, `/preview` route pages. +- [Pages: dictation](pages/dictation.md). The hero recording surface. +- [Pages: settings](pages/settings.md). The 2 484 line settings panel. +- [Pages: history](pages/history.md). FTS5 backed transcript browser. +- [Pages: tasks](pages/tasks.md). Inbox, today, soon, later board. +- [Pages: files](pages/files.md). Drag and drop file transcription. +- [Pages: first run](pages/first-run.md). Hardware probe and model bootstrap. +- [Components](components.md). All 25 reusable components grouped by role. +- [Stores](stores.md). Ten Svelte 5 `$state` stores, plus what each owns and what events they react to. +- [Actions, utils, types](actions-utils-types.md). The single Svelte action, the 16 utility modules, and the `app.ts` type bible. +- [Internationalisation](i18n.md). svelte-i18n setup, locale persistence, current coverage. +- [Design system](design-system.md). Reference material under `src/design-system/` (preview HTML, JSX UI kits). Note: this is reference, not live code. +- [Frontend ↔ Tauri bridge](frontend-tauri-bridge.md). Every `invoke()` command name and every event listened for or emitted, with their callers. +- [App shell and styling](app-shell-and-styling.md). `app.css`, `app.html`, fonts, Tailwind v4 configuration, accessibility CSS variables. + +## How this slice connects to others + +**In (frontend depends on Tauri runtime, slice 02).** + +- Sixty plus distinct `invoke()` commands. Full list with caller in [frontend-tauri-bridge.md](frontend-tauri-bridge.md). +- Tauri events listened to: `model-download-progress`, `parakeet-download-progress`, `magnotia:llm-download-progress`, `magnotia:hotkey-pressed`, `magnotia:open-wind-down`, `magnotia:preferences-changed`, `task-window-focus`, `preview-listening`, `preview-cleanup`, `preview-hide`, plus drag drop (`tauri://drag-drop`, `tauri://drag-enter`, `tauri://drag-leave`). +- Window APIs: `getCurrentWindow().minimize() / toggleMaximize() / setPosition() / label`. +- Plugin imports loaded lazily: `@tauri-apps/plugin-global-shortcut`, `@tauri-apps/plugin-dialog`. + +**Out (frontend triggers behaviour back into the runtime).** + +- DOM `CustomEvent` bus on `window` carries internal traffic (`magnotia:start-timer`, `magnotia:toggle-recording`, `magnotia:task-completed`, etc). The Rust side does not listen to these; they are intra frontend. +- Tauri `emit()` is used for `magnotia:preferences-changed` only, to fan preference updates across windows. +- Multi window orchestration: pages call `open_task_window`, `open_viewer_window`, `open_preview_window` to ask Rust to spawn secondary webviews. + +**Other slices that read frontend conventions.** + +- Slice 02 (Tauri runtime) emits the events listed above and registers commands the frontend invokes. +- Slice 03 (audio + transcription) ships partial results via a `Channel` (Tauri 2 typed channel) opened in `DictationPage`. +- Slice 04 (LLM, formatting, MCP) emits `magnotia:llm-download-progress` and `cleanup_transcript_text_cmd` results consumed by Dictation. +- Slice 05 (storage, hotkey, build) supplies `add_transcript`, `delete_transcript`, profiles, tasks and the evdev hotkey backend. + +## Existing in repo docs (do not duplicate) + +- `docs/brief/` and `docs/whisper-ecosystem/brief.md`. Product brief and feature set. +- `docs/icon-mapping.md`. Lucide icon migration audit. +- `docs/code-review-2026-04-22.md`. Last code review snapshot. +- `docs/handovers/`. Ship logs (e.g. Phase 9c is the most recent settings related one). +- `docs/audit/`. UX and accessibility audits. +- `docs/superpowers/`. Process artefacts. +- `src/design-system/README.md`, `src/design-system/SKILL.md`. Brand and design ground truth. + +This slice index is the navigation hub. The map files referenced above carry the detail. + +## Open questions, debt, drift, dead code + +1. **`src/lib/Sidebar.svelte` lives outside `components/`.** Every other reusable Svelte module is under `src/lib/components/`. The sidebar is the only sibling of those folders. Cosmetic but it surprises contributors. +2. **Two parallel theme systems.** `settings.theme` (string `"Light"`/`"Dark"`/`"System"`) coexists with `preferences.theme` (`"light"`/`"dark"`/`"system"`). `+layout.svelte:61-68` and `float/+layout@.svelte` both run a "legacy → new" migration `$effect` every time. The legacy pathway should be retired. +3. **`@ts-nocheck` is widespread.** `+layout.svelte`, `DictationPage.svelte`, `FilesPage.svelte`, `FirstRunPage.svelte` and the float/viewer/preview layouts all opt out of TypeScript. Type safety stops at the page boundary. +4. **`SettingsPage.svelte` is 2 484 lines.** Phase 9c handover already flagged this. `SettingsGroup.svelte` exists but the deeper restructure into seven progressive disclosure groups plus search has been deferred. +5. **`profiles` redirect is a dead route.** `+page.svelte:13` still rewrites `page.current === "profiles"` to `"settings"`, suggesting the old profiles page was removed but call sites may persist. Worth grepping and deleting the redirect after a release. +6. **Cross window settings sync uses `localStorage` `storage` events.** `float/+layout@.svelte` listens for `storage` to apply settings, while preferences use the dedicated `magnotia:preferences-changed` Tauri event. Inconsistent. Settings sync via `storage` is fragile because it only fires on cross document writes. +7. **`shims.d.ts` next to the lib root.** Single shim file at `src/lib/shims.d.ts`. Worth verifying it is still needed (Svelte 5 + SvelteKit ship most ambient types now). +8. **`design-system/ui_kits/`** contains JSX components and an HTML index. They are reference, not live, and should not import from the runtime tree. Confirm the build excludes them. +9. **`speaker.svelte.ts` is ten lines.** A near empty store. Used by `SpeakerButton.svelte`. Consider folding into a util if it never grows. +10. **CSS variable `--font-size-transcript` is set on `body` from `+layout.svelte` but `preferences` already sets `--font-size-body` and `--transcript-font-size`.** Three knobs for transcript text size in different stores. Drift between `settings.fontSize` and `preferences.accessibility.transcriptSize` is likely. +11. **i18n coverage is thin.** Each locale has 19 lines. svelte-i18n is wired but most strings remain hardcoded. Treat as a stub. +12. **Tailwind v4 has no standalone config file.** All theming lives in `app.css` `@theme`. There is no `tailwind.config.{js,ts}`. Mention this so contributors do not waste time looking. +13. **`docs/architecture-map/01-frontend/` was empty before this pass.** Reciprocal slice indexes (02 to 05) need updating to point here. diff --git a/docs/architecture-map/01-frontend/actions-utils-types.md b/docs/architecture-map/01-frontend/actions-utils-types.md new file mode 100644 index 0000000..299230e --- /dev/null +++ b/docs/architecture-map/01-frontend/actions-utils-types.md @@ -0,0 +1,125 @@ +--- +name: Actions, utils, types +type: architecture-map-page +slice: 01-frontend +last_verified: 2026/05/09 +--- + +# Actions, utils, types + +> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Actions, utils, types + +**Plain English summary.** Helpers that are not components and not state. The single Svelte action (`bionicReading`), 16 utility modules under `src/lib/utils/`, and the `app.ts` type module that everyone imports. + +## Actions + +### `src/lib/actions/bionicReading.ts` (74 LOC) + +Svelte action that toggles "bionic reading" rendering on a node. Walks the text nodes via `TreeWalker`, replaces them with bolded prefix + plain suffix spans. A `MutationObserver` reapplies on text changes (disconnects while mutating to avoid infinite loop). `stripBionic` undoes by unwrapping `` tags and normalising adjacent text nodes. + +Used in DictationPage and the viewer. Wired to `preferences.accessibility.bionicReading`. + +## Utils + +### `runtime.ts` (45 LOC) + +Exports `hasTauriRuntime()`. Probes for `window.__TAURI_INTERNALS__` or `window.isTauri` to short circuit Tauri calls in browser preview mode. Used everywhere as the gate before `invoke()`. + +### `osInfo.ts` (110 LOC) + +Caches OS info via `invoke("os_info")` (or similar; check `lib.rs`). Exposes `loadOsInfo`, `isMac`, `isLinux`, `modKeyLabel` synchronously after `loadOsInfo` resolves. Drives whether the shell renders custom or native chrome. + +### `errors.ts` (8 LOC) + +`errorMessage(e)` returns a string from `Error | unknown`. Tiny. + +### `storage.ts` (8 LOC) + +`parseStoredJson(key, fallback)` wraps `JSON.parse(localStorage.getItem(key))` with try/catch. Used by `settingsMigrations`, `page.svelte.ts`, and the viewer handoff. + +### `settingsMigrations.ts` (134 LOC) + +Versioned migrations for `magnotia_settings`. Holds `CURRENT_SETTINGS_VERSION = 1`. Envelope shape: `{ version: number, data: T }`. Reads bare unversioned blobs as v0. `loadSettingsWithMigration(key, defaults)` walks the chain and toasts on corruption. + +### `frontmatter.ts` (148 LOC) + +Builds YAML frontmatter for transcript markdown export. + +- `deriveAutoTags(text, profile)`. Heuristic auto tags. +- `buildFrontmatter(transcript)`. Returns the YAML block. +- `buildMarkdown(transcript)`. Joins frontmatter + body. +- `normaliseTag(s)`. Lowercase, replace whitespace. + +### `saveMarkdown.ts` (132 LOC) + +Save dialog + write file dance. `suggestedFilename(item)` builds `-.md`. `saveTranscriptAsMarkdown(item)` opens `@tauri-apps/plugin-dialog`, then writes via `invoke`. `exportTranscriptsToDir(items)` for bulk export. + +### `export.ts` (125 LOC) + +Generic export helpers used by DictationPage and FilesPage. Format choice: plain text, markdown, JSON. Falls through to clipboard or save dialog. + +### `taskExtractor.ts` (224 LOC) + +`extractTasks(text)` uses regex heuristics for "TODO:", "Action:", "I need to ...", numbered lists, etc. Falls back when LLM extraction is unavailable. + +### `textMeasure.ts` (166 LOC) + +Canvas-based pre-wrap text measurement. `measurePreWrap(text, font, lineHeight, width)` returns the rendered height. `clampTextLines(text, n)` trims to N lines plus ellipsis. Used by HistoryPage virtual scroll and DictationPage textarea sizing. + +### `accessibilityTypography.ts` (41 LOC) + +Reads `--font-family-body`, `--font-size-body`, `--line-height-body`, `--letter-spacing-body` from `` and returns numeric values. `pretextFontShorthand`, `bodyPretextLineHeight`, `transcriptPretextFont`, `transcriptPretextLineHeight` are the public helpers. + +### `virtualList.ts` (49 LOC) + +`buildCumulativeOffsets(heights)` and `findVisibleRange(offsets, scrollTop, viewportHeight, overscan)`. Pure. Used by HistoryPage and `VirtualSegmentList`. + +### `time.ts` (46 LOC) + +`pad(n)`, `formatTime(seconds)`, `formatDuration(seconds)`, `formatTimestamp(iso)`. Display helpers. + +### `sounds.ts` (101 LOC) + +Loads the start/stop/complete cue sounds via `