docs: architecture map (initial 5-slice generation, 105 pages)
Five-slice navigable map of the entire codebase under
docs/architecture-map/. Each slice is a self-contained
breadcrumbed sub-tree:
01-frontend (16) Svelte/SvelteKit UI
02-tauri-runtime (26) src-tauri commands + lifecycle
03-audio-transcription (16) audio + transcription crates
04-llm-formatting-mcp (19) llm, ai-formatting, mcp, cloud
05-core-storage-hotkey-build core, storage, hotkey, workspace,
(26) CI, dev glue
Plus master README.md and data-flow-end-to-end.md tracing
audio bytes from microphone to FTS5 search to MCP read.
Generated by 5 parallel subagents on 2026/05/09 against
HEAD 3c47000. Each page has YAML frontmatter, file:line code
refs, sibling cross-links, plain-English summaries.
Aggregated debt surfaced (full lists in master README):
RB-08 macOS power assertion, schema head drift v14 vs v15,
VAD blocked on ort version conflict, streaming primitives
not wired into live.rs, no prompt versioning, MCP has no
auth, cloud-providers in-memory keystore, SettingsPage
2 484 LOC, commands/live.rs 1 737 LOC, dual theme system,
brand rename to Lumenote pending across the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
docs/architecture-map/01-frontend/README.md
Normal file
90
docs/architecture-map/01-frontend/README.md
Normal file
@@ -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.
|
||||
125
docs/architecture-map/01-frontend/actions-utils-types.md
Normal file
125
docs/architecture-map/01-frontend/actions-utils-types.md
Normal file
@@ -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 `<b>` 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<T>(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 `<slug>-<YYYY-MM-DD>.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 `<html>` 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 `<audio>` and exposes `playStartCue()`, `playStopCue()`, `playCompleteCue()`. Volume from `settings.soundCueVolume`. Honours `settings.soundCues` flag.
|
||||
|
||||
### `hotkeyValidity.ts` (149 LOC)
|
||||
|
||||
Validates a hotkey combo against per-OS rules (no Cmd alone on macOS, no single letters, etc). Used by `HotkeyRecorder.svelte`.
|
||||
|
||||
### `constants.js` (30 LOC)
|
||||
|
||||
The shared scalars:
|
||||
- Timing: `FEEDBACK_TIMEOUT_MS`, `HOTKEY_FEEDBACK_MS`, `HISTORY_MAX_ENTRIES = 100`, `MAX_PCM_SAMPLES = 4_800_000` (5 min @ 16 kHz), `SIDEBAR_MAX_TASKS = 30`, `CHUNK_INTERVAL_MS = 3000`, `MIN_CHUNK_SAMPLES = 8000`.
|
||||
- Buckets: `BUCKET_COLORS`, `BUCKET_DOT_COLORS` (`inbox` / `today` / `soon` / `later`).
|
||||
- Effort: `EFFORT_LABELS`, `EFFORT_ORDER`.
|
||||
- Playback: `PLAYBACK_SPEEDS = [0.5, 1, 1.5, 2, 3, 5]`.
|
||||
|
||||
Note: this is `.js`, not `.ts`. Other utils are `.ts`.
|
||||
|
||||
## Types
|
||||
|
||||
### `src/lib/types/app.ts` (408 LOC)
|
||||
|
||||
Single source of truth for shared shapes. Contains:
|
||||
- `PageState`, `SettingsState`, `Preferences`, `AccessibilityPreferences`.
|
||||
- `Profile`, `Template`, `TaskList`, `TaskBucket`, `TaskEntry`, `TaskDraft`, `TaskUpdate`, `TaskDto`, `EnergyLevel`.
|
||||
- `TranscriptDto`, `TranscriptEntry`, `TranscriptWriteEntry`, `TranscriptMetaPatch`, `Segment`, `ViewerSegment`.
|
||||
- `DailyCompletionCount`, `ToastSeverity`, `FontFamily`, `ReduceMotion`.
|
||||
|
||||
### `src/lib/shims.d.ts`
|
||||
|
||||
Single ambient declaration file. Worth verifying the contents are still needed (Svelte 5 + SvelteKit ship most ambient types now). README debt note 7.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- `constants.js` is JS, not TS. If you migrate, the JSDoc shapes in `BUCKET_COLORS` need to align with Tailwind class strings; type-only objects still need a runtime export.
|
||||
- `taskExtractor.ts` is a regex heuristic. The LLM path (`extract_tasks_from_transcript_cmd`) is the better signal; the heuristic is the offline fallback.
|
||||
- `osInfo.ts` caches forever. If you need to handle a hot OS detection retry (you should not), invalidate the cache yourself.
|
||||
- `textMeasure.ts` uses an offscreen `<canvas>`. Costs are real for long transcripts; results are cached per (text, font, line-height, width) tuple in HistoryPage.
|
||||
- `frontmatter.ts` and `saveMarkdown.ts` overlap. The former produces the body; the latter handles the I/O. Keep them split.
|
||||
|
||||
## See also
|
||||
|
||||
- [Stores](stores.md). Many utils are imported by stores.
|
||||
- [Components](components.md). The components that consume these helpers.
|
||||
- [App shell and styling](app-shell-and-styling.md). CSS variables consumed by the typography utils.
|
||||
110
docs/architecture-map/01-frontend/app-shell-and-styling.md
Normal file
110
docs/architecture-map/01-frontend/app-shell-and-styling.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: App shell and styling
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# App shell and styling
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → App shell and styling
|
||||
|
||||
**Plain English summary.** Where the visual chrome and CSS plumbing live. `app.html` is the SvelteKit document. `app.css` carries the Tailwind v4 import, the `@theme` token block, the brand `@font-face` declarations, the sensory zones, and the accessibility CSS variables. There is no separate `tailwind.config.{js,ts}`; Tailwind v4 is configured entirely in `app.css`. Fonts ship from `src/fonts/` (bundled by Vite) and `static/fonts/` (served as-is).
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/app.html`, `src/app.css`, `src/app.d.ts`, `src/fonts/`, `src/assets/`, `static/`.
|
||||
- **LOC:** 13 (`app.html`) + 583 (`app.css`) + 8 (`app.d.ts`).
|
||||
- **Frameworks:** Tailwind v4 via `@tailwindcss/vite`. No PostCSS config. No standalone Tailwind config file.
|
||||
- **Adapter:** `@sveltejs/adapter-static` with `index.html` fallback (SPA, `svelte.config.js`).
|
||||
|
||||
## What's in here
|
||||
|
||||
### `src/app.html` (13 LOC)
|
||||
|
||||
Standard SvelteKit document. Sets `lang="en"`, charset, viewport, favicon, title (`Magnotia`), and applies `data-sveltekit-preload-data="hover"` on `<body>`. The body content is wrapped in `<div style="display: contents">` so the SvelteKit-injected children render inline.
|
||||
|
||||
### `src/app.d.ts` (8 LOC)
|
||||
|
||||
Ambient declaration extending `Window` with `__TAURI_INTERNALS__` and `isTauri` so `utils/runtime.ts` typechecks.
|
||||
|
||||
### `src/app.css` (583 LOC)
|
||||
|
||||
Layered:
|
||||
1. `@import "tailwindcss"` (Tailwind v4 entry).
|
||||
2. `@font-face` for Lexend, Atkinson Hyperlegible Next, OpenDyslexic, JetBrains Mono, Instrument Serif Italic. URLs use root-absolute paths (`/fonts/...`) served from `static/fonts/` by Vite.
|
||||
3. `@theme` token block. Sets the design tokens that Tailwind v4 picks up as utility classes:
|
||||
- Colour tokens: `--color-bg`, `--color-bg-raised`, `--color-text`, `--color-text-secondary`, `--color-text-tertiary`, `--color-accent`, `--color-warning`, `--color-success`, `--color-border`, `--color-border-subtle`, `--color-hover`, `--color-nav-active`, plus a sensory-zone palette family.
|
||||
- Typography tokens: `--font-display`, `--font-body`, `--font-mono`, plus `--font-size-*` and `--line-height-*`.
|
||||
- Motion tokens: `--duration-ui`, `--duration-fast`, `--duration-slow`. Easing tokens for `cubic-bezier` curves.
|
||||
- Shadow tokens: `--shadow-accent-md`, `--shadow-accent-glow`, etc.
|
||||
4. Sensory-zone overrides keyed on `[data-zone="..."]` on `<html>`. Switches token values to dim, focus, etc.
|
||||
5. Theme overrides keyed on `[data-theme="dark"]` and `[data-theme="light"]`. The `preferences` store writes both `data-theme` and `data-zone`.
|
||||
6. Accessibility variables on `<html>`: `--font-family-body`, `--font-size-body`, `--letter-spacing-body`, `--line-height-body`. Set by the `preferences` store via `applyToDOM()`.
|
||||
7. Base styles: `body` background, font, body font-family bound to the variable, scrollbar styling, focus ring.
|
||||
8. Utility classes for grain texture (`.grain` uses the noise asset under `assets/grain.svg`), CRT-style transcript surface, etc.
|
||||
9. Animations: `@keyframes fade-in`, `@keyframes pulse`, etc. Reduced motion guard at the bottom (`@media (prefers-reduced-motion: reduce)`).
|
||||
|
||||
### `src/fonts/` (bundled woff2)
|
||||
|
||||
- `atkinson-hyperlegible-next.woff2`
|
||||
- `instrument-serif-italic.woff2`
|
||||
- `jetbrains-mono.woff2`
|
||||
- `lexend-variable.woff2`
|
||||
- `opendyslexic.woff2`
|
||||
|
||||
Bundled by Vite (referenced from `app.css` via `/fonts/...` paths that Vite resolves). The same files live in `static/fonts/` for the static-served path. Confirm whether both paths are required; if Vite handles font copying, `static/fonts/` may be redundant.
|
||||
|
||||
### `src/assets/`
|
||||
|
||||
- `grain.svg`. Noise texture used by the `.grain` utility class.
|
||||
- `waveform-mark.svg`. Brand glyph.
|
||||
- `wordmark.svg`. Brand wordmark.
|
||||
|
||||
### `static/`
|
||||
|
||||
Served as-is from the webview root.
|
||||
|
||||
- `favicon.png`. Site favicon.
|
||||
- `fonts/`. Same five woff2 files as `src/fonts/`. Likely the source of `app.css /fonts/...` URLs.
|
||||
- `pcm-processor.js`. AudioWorklet processor (slice 02 owns the integration). 32-line file that converts microphone input to int16 PCM frames and posts them up.
|
||||
- `textures/grain.png`. PNG version of the grain texture.
|
||||
- `svelte.svg`, `tauri.svg`, `vite.svg`. SvelteKit defaults; technically unused. Candidate for removal.
|
||||
|
||||
## How preferences map to the DOM
|
||||
|
||||
| Preference | DOM target |
|
||||
|---|---|
|
||||
| `theme` ("light" / "dark" / "system") | `data-theme` on `<html>`. `system` resolves to OS preference at apply time. |
|
||||
| `zone` ("default" / ...) | `data-zone` on `<html>`. |
|
||||
| `accessibility.fontFamily` ("lexend" / "atkinson" / "opendyslexic") | `--font-family-body` CSS variable on `<html>`. |
|
||||
| `accessibility.fontSize` | `--font-size-body` (pixels). |
|
||||
| `accessibility.letterSpacing` | `--letter-spacing-body` (em). |
|
||||
| `accessibility.lineHeight` | `--line-height-body` (unitless). |
|
||||
| `accessibility.bionicReading` | `<html data-bionic-reading="true|false">`. The `bionicReading` action reads this. |
|
||||
| `accessibility.reduceMotion` | `<html data-reduce-motion="reduce|no-preference|system">`. Pairs with the `prefers-reduced-motion` media query. |
|
||||
|
||||
Plus the legacy `settings.fontSize` writes `--font-size-transcript` on `<body>` directly (`+layout.svelte:204`). That is separate from `accessibility.fontSize`.
|
||||
|
||||
## Tailwind v4 setup
|
||||
|
||||
- Installed via `@tailwindcss/vite` in `vite.config.js:3`.
|
||||
- Entry: `src/app.css` line 1, `@import "tailwindcss"`.
|
||||
- Tokens declared inline via `@theme` blocks in `app.css`.
|
||||
- No `tailwind.config.{js,ts}` exists. Do not look for one.
|
||||
- Class scanning: Tailwind v4 scans the source tree by default. Custom paths can be configured with `@source` directives if needed.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- **Two font sources.** `src/fonts/` (Vite bundled) and `static/fonts/` (raw served). Confirm whether both are needed; redundancy bloats the bundle.
|
||||
- **Mirror file `src/design-system/colors_and_type.css`** must be updated whenever `app.css` `@theme` changes. There is no automated check.
|
||||
- **`prefers-reduced-motion` honoured by CSS** but JS animations (e.g. `CompletionSparkline`'s stagger) are guarded in component code, not via the media query alone.
|
||||
- **Tailwind v4 `@theme` is class-scanning sensitive.** If you put utility class strings inside conditional template literals that Tailwind cannot see at scan time, they will not be generated.
|
||||
- **Removing default SvelteKit assets** (`static/svelte.svg`, `vite.svg`, `tauri.svg`) requires confirming nothing references them in `app.html` or `README.md`.
|
||||
- **`pcm-processor.js`** is a static asset because AudioWorklet processors must be served from a same-origin URL. Bundling it through Vite would break the worklet registration. Leave it in `static/`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Design system](design-system.md). The reference mirror of `app.css` tokens.
|
||||
- [Stores](stores.md). The `preferences` store that writes the DOM.
|
||||
- [Components](components.md). Where the utility classes are consumed.
|
||||
103
docs/architecture-map/01-frontend/components.md
Normal file
103
docs/architecture-map/01-frontend/components.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: Components
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Components
|
||||
|
||||
**Plain English summary.** All 25 reusable Svelte 5 components, plus `Sidebar.svelte` which is the only component that lives at `src/lib/Sidebar.svelte` rather than under `src/lib/components/`. Grouped by what they do. Where a component is mounted directly by the shell (`+layout.svelte`), that is called out.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/components/` (25 files), plus `src/lib/Sidebar.svelte`.
|
||||
- **LOC:** 3 174 (components) + 178 (sidebar) = 3 352.
|
||||
- **Conventions:** Svelte 5 runes (`$state`, `$props`, `$derived`, `$effect`). Tailwind v4 utility classes. CSS variables for design tokens. Lucide icons at 16/20/24px with `aria-label` next to or instead of the glyph.
|
||||
|
||||
## Shell mounts (always present)
|
||||
|
||||
| Component | LOC | Path | Purpose |
|
||||
|---|---|---|---|
|
||||
| `Sidebar` | 178 | `src/lib/Sidebar.svelte` | Left nav. Switches `page.current`. Collapsed mode shows tooltips. Renders task badge from `tasks.length`. |
|
||||
| `Titlebar` | 80 | `src/lib/components/Titlebar.svelte` | Custom window chrome for non Linux (frameless windows). Min/max/close + drag area + sidebar aligned spacer. |
|
||||
| `ToastViewport` | 143 | `src/lib/components/ToastViewport.svelte` | Bottom right toast stack. Reads from the global `toasts` store. Honours `prefers-reduced-motion`. |
|
||||
| `ResizeHandles` | 67 | `src/lib/components/ResizeHandles.svelte` | Invisible 5 px margins for frameless resize. Linux uses native, so `+layout.svelte` suppresses this. |
|
||||
| `FocusTimer` | 298 | `src/lib/components/FocusTimer.svelte` | Floating top right SVG progress ring. Listens for `magnotia:start-timer` window events and delegates to the focus timer store. Cancel hover, success flourish. Hidden on float and viewer windows by URL probe. |
|
||||
| `MorningTriageModal` | 356 | `src/lib/components/MorningTriageModal.svelte` | Phase 5 modal. Self gated on `settings.ritualsMorning` and time of day. Mounted globally so it appears over any page. |
|
||||
| `TaskSidebar` | 97 | `src/lib/components/TaskSidebar.svelte` | Optional right side dock that appears when `page.taskSidebarOpen`. Quick add input, top tasks, link out to the full Tasks page. |
|
||||
|
||||
## Settings building blocks
|
||||
|
||||
| Component | LOC | Purpose |
|
||||
|---|---|---|
|
||||
| `SettingsGroup` | 86 | Native `<details>` based progressive disclosure with animated chevron. Handler hooks (`onopen`) are used by SettingsPage to lazy probe expensive state (model lists, paste backends, etc). |
|
||||
| `Toggle` | 98 | iOS style toggle. ARIA switch role. |
|
||||
| `SegmentedButton` | 19 | Tiny segmented switch with ARIA radio role. |
|
||||
| `HotkeyRecorder` | 217 | Captures a key combo. Uses `utils/hotkeyValidity.ts` to check the combo before persisting. |
|
||||
| `ZonePicker` | 36 | Sensory zone (default / focus / dim / etc) picker that maps to a `data-zone` attribute on `<html>`. |
|
||||
| `AccessibilityControls` | 112 | Font family, font size, letter spacing, line height, reduce motion, bionic reading. Writes via `updateAccessibility`. |
|
||||
| `ImplementationRulesEditor` | 266 | Edits the implementation intentions list ("when X happens, do Y"). Persists via the `implementationIntentions` store. |
|
||||
| `ModelDownloader` | 112 | Standalone download panel used inside Dictation when no model is loaded. Subscribes to `model-download-progress`. |
|
||||
|
||||
## Card chrome and empty states
|
||||
|
||||
| Component | LOC | Purpose |
|
||||
|---|---|---|
|
||||
| `Card` | 35 | Generic container. Border, padding, optional header. The page level "block" primitive. |
|
||||
| `EmptyState` | 22 | Centred icon + title + body slot. Used widely. |
|
||||
| `UnicodeSpinner` | 30 | ASCII spinner used while probing or downloading. |
|
||||
|
||||
## Tasks
|
||||
|
||||
| Component | LOC | Purpose |
|
||||
|---|---|---|
|
||||
| `WipTaskList` | 153 | Renders task rows with energy chips, completion checkbox, expansion to show micro steps, and a "start focus timer" button (dispatches `magnotia:start-timer`). |
|
||||
| `MicroSteps` | 310 | Per task expansion: nudge bus integration, micro step suggestions from the LLM (`magnotia:microstep-generated`), step completion (`magnotia:step-completed`), per task implementation rules. |
|
||||
| `EnergyChip` | 106 | Low/medium/high energy selector. Used on task rows and in TasksPage quick add. |
|
||||
| `CompletionSparkline` | 92 | 7 day completion bar chart. Animated entrance with 30 ms stagger, respects `prefers-reduced-motion`. Reads from `recentCompletions` store. |
|
||||
| `VisualTimer` | 33 | Compact visual timer pill used inside MicroSteps and the float window. |
|
||||
|
||||
## Transcripts
|
||||
|
||||
| Component | LOC | Purpose |
|
||||
|---|---|---|
|
||||
| `VirtualSegmentList` | 234 | Virtualised scroll for transcript segments in the viewer window. Uses `utils/virtualList.ts` and `utils/textMeasure.ts` to compute per row heights from current preferences. |
|
||||
| `SpeakerButton` | 112 | Trigger TTS playback for a chunk of text. Uses `tts_speak`. Reads from the `speaker` store to debounce concurrent requests. |
|
||||
| `LlmStatusChip` | 60 | Pill in the sidebar/dictation surface showing LLM state (idle, generating, downloading, unavailable). Reads `llmStatus` store. |
|
||||
|
||||
## Where each component is used
|
||||
|
||||
- `Sidebar`: `+layout.svelte` shell only.
|
||||
- `Titlebar`: `+layout.svelte`, `float/+layout@.svelte`, `viewer/+layout@.svelte` (when `useCustomChrome`).
|
||||
- `ToastViewport`: `+layout.svelte`, `viewer/+layout@.svelte`.
|
||||
- `ResizeHandles`: `+layout.svelte` (when `useCustomChrome`).
|
||||
- `FocusTimer`: `+layout.svelte`, `float/+layout@.svelte`.
|
||||
- `MorningTriageModal`: `+layout.svelte`.
|
||||
- `TaskSidebar`: `+layout.svelte` (when `page.taskSidebarOpen`).
|
||||
- `Card`, `EmptyState`, `Toggle`, `SegmentedButton`: SettingsPage, DictationPage, HistoryPage, TasksPage, FilesPage.
|
||||
- `SettingsGroup`, `HotkeyRecorder`, `ImplementationRulesEditor`, `ZonePicker`, `AccessibilityControls`: SettingsPage.
|
||||
- `ModelDownloader`: DictationPage.
|
||||
- `WipTaskList`, `MicroSteps`, `EnergyChip`, `CompletionSparkline`: TasksPage and `WipTaskList` reused inside the float window.
|
||||
- `VirtualSegmentList`: viewer/+page.svelte.
|
||||
- `SpeakerButton`: DictationPage, viewer.
|
||||
- `LlmStatusChip`: Sidebar, DictationPage.
|
||||
- `UnicodeSpinner`: FirstRunPage, SettingsPage.
|
||||
- `VisualTimer`: MicroSteps, float window.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- `Sidebar.svelte` lives outside `components/`. README debt note 1.
|
||||
- `Titlebar` reads `settings.sidebarCollapsed` to mirror the spacer width. Animation timing is driven by `--duration-ui`.
|
||||
- `MorningTriageModal` is heavy (356 LOC). Self gates internally; do not move the gate elsewhere or you will pay its cost on every paint.
|
||||
- `FocusTimer` detects "secondary window" by URL prefix. If you add a fifth window, update the probe.
|
||||
- `CompletionSparkline` animations are the only place that uses CSS keyframes for entrance. Honour reduced motion.
|
||||
- Many components receive props with `let { foo = default } = $props();` (Svelte 5 idiom). The minimal `Card`, `EmptyState`, `Toggle`, `SegmentedButton` are good reading order for understanding the runes idiom on this codebase.
|
||||
|
||||
## See also
|
||||
|
||||
- [Stores](stores.md). The state these components consume.
|
||||
- [Actions, utils, types](actions-utils-types.md). The bionic reading action and the typography utilities most components use.
|
||||
- [Design system](design-system.md). The brand reference these components target.
|
||||
67
docs/architecture-map/01-frontend/design-system.md
Normal file
67
docs/architecture-map/01-frontend/design-system.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: Design system (reference, not live)
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Design system
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Design system
|
||||
|
||||
**Plain English summary.** Reference material. The runtime styles live in `src/app.css`. Everything under `src/design-system/` is brand documentation, preview pages, JSX UI kits, and ground truth source for the magnotia-design Claude skill. None of it is imported by the runtime SvelteKit app.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/design-system/`
|
||||
- **Files:**
|
||||
- `README.md`. Brand and content rules. (Magnotia by CORBEL.)
|
||||
- `SKILL.md`. magnotia-design skill manifest (`user-invocable: true`). The skill copies assets out and writes throwaway HTML or production code on demand.
|
||||
- `colors_and_type.css`. Mirror of the runtime `@theme` tokens for buildless preview pages. Intentional duplication, kept in sync by hand.
|
||||
- `preview/`. 20 buildless HTML spec cards.
|
||||
- `ui_kits/`. JSX recreations (`DictationPage.jsx`, `OtherPages.jsx`, `Sidebar.jsx`) plus an `index.html` and a README.
|
||||
|
||||
## What's in here
|
||||
|
||||
### `colors_and_type.css`
|
||||
|
||||
Mirrors `src/app.css` `@font-face` declarations and (typically) the `@theme` token block, with relative `fonts/...` URLs so preview HTML can render without going through Tailwind/Vite. The file's banner comment is explicit:
|
||||
|
||||
> When app.css @theme changes, mirror the change here. There is no automated sync; intentional duplication keeps the preview pages buildless.
|
||||
|
||||
### `preview/` (20 HTML files)
|
||||
|
||||
Each renders one slice of the system:
|
||||
- Brand: `brand-icons.html`, `brand-wordmark.html`.
|
||||
- Colour: `colors-accent.html`, `colors-semantic.html`, `colors-surfaces.html`, `colors-text.html`, `colors-zones.html`.
|
||||
- Components: `components-buttons.html`, `components-cards.html`, `components-empty-states.html`, `components-inputs.html`, `components-nav.html`, `components-toasts.html`.
|
||||
- Spacing and motion: `spacing-motion.html`, `spacing-radii.html`, `spacing-scale.html`, `spacing-shadows.html`.
|
||||
- Type: `type-body.html`, `type-headings.html`, `type-transcript-mono.html`.
|
||||
|
||||
These are static. Open in any browser.
|
||||
|
||||
### `ui_kits/` (JSX, reference only)
|
||||
|
||||
Three `.jsx` files plus `index.html` and a README. Reproduce the desktop pages in JSX so prototypes can be built outside the Tauri shell. The README inside ui_kits explains how to use them. They are not imported anywhere from the SvelteKit app.
|
||||
|
||||
### Brand language (from README.md)
|
||||
|
||||
- Built for the noise allergic. Solarpunk-as-posture, AI-minimisation, ground truth + warmth.
|
||||
- Iconography: Lucide, 2 px stroke. 16 px nav, 20 px features, 24 px primary actions. Always paired with a label except OS titlebar controls.
|
||||
- Transparency + blur used sparingly: collapsed-sidebar tooltips, float window backdrop, accent-subtle 6% tint. No glassmorphism.
|
||||
|
||||
## Why it lives in `src/`
|
||||
|
||||
The design system files sit under `src/` so the magnotia-design skill (which runs from this repo) can read ground-truth Svelte source from `magnotia-source/` (a sibling import). The runtime build does not include this folder; it is a reference root.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- Tokens in `colors_and_type.css` and `app.css` `@theme` can drift. There is no CI check. When you change a token in one, change it in both.
|
||||
- `ui_kits/` JSX is not used by the app. Treat it as documentation, not as a future framework migration plan. There is no plan to switch from Svelte to React.
|
||||
- Vite (with `sveltekit()` plugin) ignores `.html` files outside `static/` and `.jsx` files outside imports. If anyone adds an `import './design-system/...'` somewhere, the build will start picking up reference material. Do not.
|
||||
- The skill is user invocable (`SKILL.md`). External agents may write into `src/design-system/` based on user invocation. Treat the contents as agent-shaped, not human-only.
|
||||
|
||||
## See also
|
||||
|
||||
- [App shell and styling](app-shell-and-styling.md). The runtime `app.css` that this folder mirrors.
|
||||
- [Components](components.md). The Svelte versions of the JSX kits.
|
||||
163
docs/architecture-map/01-frontend/frontend-tauri-bridge.md
Normal file
163
docs/architecture-map/01-frontend/frontend-tauri-bridge.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
name: Frontend ↔ Tauri bridge
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Frontend ↔ Tauri bridge
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Frontend ↔ Tauri bridge
|
||||
|
||||
**Plain English summary.** The complete surface where the Svelte frontend talks to the Rust runtime. Two channels: synchronous request/response via `invoke()`, and asynchronous events via `listen()` / `emit()`. Every command name and every event name the frontend touches is listed here so slice 02 can verify reciprocal mentions.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Direction in (Rust → frontend):** events listed under "Tauri events listened to".
|
||||
- **Direction out (frontend → Rust):** all `invoke()` commands listed under "Tauri commands invoked".
|
||||
- **DOM-only events:** `magnotia:*` `CustomEvent`s on `window`. These never cross the Rust boundary.
|
||||
- **Cross-window event:** `magnotia:preferences-changed` is the only one that goes through `emit()`.
|
||||
|
||||
## Tauri commands invoked (alphabetical)
|
||||
|
||||
Discovered via `grep -rho 'invoke([\"\\']\\([a-zA-Z_][a-zA-Z0-9_]*\\)' src/`. Each name appears at least once in the frontend.
|
||||
|
||||
| Command | Caller(s) |
|
||||
|---|---|
|
||||
| `add_transcript` | `stores/page.svelte.ts:234` (`addToHistory`) |
|
||||
| `check_engine` | `pages/SettingsPage.svelte:826` |
|
||||
| `check_for_update` | `routes/+layout.svelte:356` |
|
||||
| `check_hotkey_access` | `routes/+layout.svelte:86` |
|
||||
| `check_llm_model` | `pages/DictationPage.svelte:241`, `pages/SettingsPage.svelte:597` |
|
||||
| `check_parakeet_engine` | `pages/SettingsPage.svelte:857` |
|
||||
| `check_parakeet_model` | `pages/SettingsPage.svelte:858` |
|
||||
| `cleanup_transcript_text_cmd` | `pages/DictationPage.svelte:472` |
|
||||
| `complete_task_cmd` | `stores/page.svelte.ts:474` |
|
||||
| `copy_to_clipboard` | DictationPage, FilesPage, HistoryPage, viewer/+page, preview/+page |
|
||||
| `delete_implementation_rule` | `stores/implementationIntentions.svelte.ts:82` |
|
||||
| `delete_llm_model` | `pages/SettingsPage.svelte:660` |
|
||||
| `delete_profile_cmd` | `stores/profiles.svelte.ts:93` |
|
||||
| `delete_profile_term_cmd` | `stores/profiles.svelte.ts:121` |
|
||||
| `delete_task_cmd` | `stores/page.svelte.ts:456` |
|
||||
| `delete_transcript` | `stores/page.svelte.ts:320`, `pages/HistoryPage.svelte:285` |
|
||||
| `deliver_nudge` | `stores/nudgeBus.svelte.ts:112` |
|
||||
| `detect_meeting_processes` | `routes/+layout.svelte:399` |
|
||||
| `detect_paste_backends` | `pages/SettingsPage.svelte:850` |
|
||||
| `download_llm_model` | `pages/SettingsPage.svelte:619` |
|
||||
| `download_model` | `pages/SettingsPage.svelte:929`, `pages/FirstRunPage.svelte:59` |
|
||||
| `download_parakeet_model` | `pages/SettingsPage.svelte:988`, `pages/FirstRunPage.svelte:73` |
|
||||
| `extract_content_tags_cmd` | `pages/HistoryPage.svelte:433, 470` |
|
||||
| `extract_tasks_from_transcript_cmd` | `pages/DictationPage.svelte:491` |
|
||||
| `generate_diagnostic_report` | `pages/SettingsPage.svelte:377` |
|
||||
| `get_llm_status` | DictationPage, SettingsPage, layout (refresh path) |
|
||||
| `get_runtime_capabilities` | `pages/DictationPage.svelte:134`, `pages/SettingsPage.svelte:543` |
|
||||
| `is_wayland_session` | `routes/+layout.svelte:82` |
|
||||
| `list_audio_devices` | `pages/SettingsPage.svelte:187` |
|
||||
| `list_models` | `routes/+layout.svelte:346`, `pages/SettingsPage.svelte:846, 930` |
|
||||
| `list_parakeet_models` | `routes/+layout.svelte:347` |
|
||||
| `load_llm_model` | `pages/DictationPage.svelte:243`, `pages/SettingsPage.svelte:634` |
|
||||
| `load_model` | `pages/DictationPage.svelte:272`, `pages/SettingsPage.svelte:944`, `pages/FirstRunPage.svelte:60` |
|
||||
| `load_parakeet_model` | `pages/DictationPage.svelte:270`, `pages/SettingsPage.svelte:1003`, `pages/FirstRunPage.svelte:74` |
|
||||
| `log_frontend_error` | `routes/+layout.svelte:282` |
|
||||
| `open_preview_window` | `pages/DictationPage.svelte:385` |
|
||||
| `open_task_window` | `pages/TasksPage.svelte:231` |
|
||||
| `open_viewer_window` | `pages/HistoryPage.svelte:392, 556` |
|
||||
| `paste_text` | `pages/DictationPage.svelte:544` |
|
||||
| `paste_text_replacing` | `routes/preview/+page.svelte:92` |
|
||||
| `prewarm_default_model_cmd` | `routes/+layout.svelte:371` |
|
||||
| `probe_system` | `pages/SettingsPage.svelte:822`, `pages/FirstRunPage.svelte:31` |
|
||||
| `rank_models` | `pages/FirstRunPage.svelte:32` |
|
||||
| `recommend_llm_tier` | `pages/SettingsPage.svelte:587` |
|
||||
| `save_diagnostic_report` | `pages/SettingsPage.svelte:405` |
|
||||
| `save_preferences` | `stores/preferences.svelte.ts:109` |
|
||||
| `start_evdev_hotkey` | `routes/+layout.svelte:129` |
|
||||
| `start_live_transcription_session` | `pages/DictationPage.svelte:344` |
|
||||
| `stop_evdev_hotkey` | `routes/+layout.svelte:424` |
|
||||
| `stop_live_transcription_session` | `pages/DictationPage.svelte:415` |
|
||||
| `test_llm_model` | `pages/SettingsPage.svelte:684` |
|
||||
| `transcribe_file` | `pages/FilesPage.svelte:76` |
|
||||
| `tts_list_voices` | `pages/SettingsPage.svelte:750` |
|
||||
| `tts_speak` | `pages/SettingsPage.svelte:769`, `stores/implementationIntentions.svelte.ts:170`, `stores/nudgeBus.svelte.ts:119` |
|
||||
| `tts_stop` | `components/SpeakerButton.svelte` (cancel current TTS) |
|
||||
| `uncomplete_task_cmd` | `stores/page.svelte.ts:491` |
|
||||
| `unload_llm_model` | `pages/SettingsPage.svelte:648` |
|
||||
| `update_evdev_hotkey` | `routes/+layout.svelte:127` |
|
||||
| `update_profile_cmd` | `stores/profiles.svelte.ts:77` |
|
||||
| `update_transcript` | `stores/page.svelte.ts:272`, `routes/viewer/+page.svelte:326` |
|
||||
|
||||
Plus any commands invoked by `saveMarkdown.ts` via the dialog/file-write plugin imports rather than direct `invoke`. (Verified the truncated `tts_s...` resolves to `tts_speak` and `tts_stop`; both are listed.)
|
||||
|
||||
## Tauri events listened to (Rust → frontend)
|
||||
|
||||
| Event | Listener |
|
||||
|---|---|
|
||||
| `magnotia:hotkey-pressed` | `routes/+layout.svelte:177` (evdev backend) |
|
||||
| `magnotia:llm-download-progress` | `pages/SettingsPage.svelte:873` |
|
||||
| `magnotia:open-wind-down` | `routes/+layout.svelte:251` (tray menu hook) |
|
||||
| `magnotia:preferences-changed` | `routes/+layout.svelte:263`, `routes/float/+layout@.svelte`, `routes/viewer/+layout@.svelte`, `routes/preview/+layout@.svelte:41` |
|
||||
| `model-download-progress` | `pages/SettingsPage.svelte:864`, `pages/FirstRunPage.svelte:46`, `components/ModelDownloader.svelte:31` |
|
||||
| `parakeet-download-progress` | `pages/SettingsPage.svelte:880`, `pages/FirstRunPage.svelte:50` |
|
||||
| `preview-cleanup` | `routes/preview/+page.svelte:141` |
|
||||
| `preview-hide` | `routes/preview/+page.svelte:161` |
|
||||
| `preview-listening` | `routes/preview/+page.svelte:113` |
|
||||
| `task-window-focus` | `routes/float/+layout@.svelte` |
|
||||
| `tauri://drag-drop` | `pages/FilesPage.svelte:28` |
|
||||
| `tauri://drag-enter` | `pages/FilesPage.svelte:34` |
|
||||
| `tauri://drag-leave` | `pages/FilesPage.svelte:35` |
|
||||
|
||||
## Tauri events emitted (frontend → Rust or other windows)
|
||||
|
||||
| Event | Emitter | Purpose |
|
||||
|---|---|---|
|
||||
| `magnotia:preferences-changed` | `stores/preferences.svelte.ts` (`broadcastPreferences`) | Cross-window preference sync. |
|
||||
| `preview-append` | `pages/DictationPage.svelte:165` | Stream partial text to overlay window. |
|
||||
| `preview-listening` | `pages/DictationPage.svelte:381` | Tell overlay to enter listening state. |
|
||||
| `preview-cleanup` | `pages/DictationPage.svelte:531` | Tell overlay to enter cleanup state. |
|
||||
| `preview-final` | `pages/DictationPage.svelte:539` | Tell overlay to render final text. |
|
||||
| `preview-hide` | `pages/DictationPage.svelte:606` | Tell overlay to dismiss. |
|
||||
|
||||
## DOM-only `magnotia:*` window events (intra-frontend bus)
|
||||
|
||||
| Event | Dispatcher | Listener(s) |
|
||||
|---|---|---|
|
||||
| `magnotia:focus-timer-cancelled` | `stores/focusTimer.svelte.ts` | `completionStats` (indirectly), components subscribed to focus timer. |
|
||||
| `magnotia:focus-timer-complete` | `stores/focusTimer.svelte.ts` | Same as above. |
|
||||
| `magnotia:implementation-rules-changed` | `stores/implementationIntentions.svelte.ts` | `ImplementationRulesEditor`. |
|
||||
| `magnotia:microstep-generated` | `stores/nudgeBus.svelte.ts` (around micro step generation) | `MicroSteps.svelte`. |
|
||||
| `magnotia:morning-triage-finished` | `MorningTriageModal.svelte` | `nudgeBus`. |
|
||||
| `magnotia:start-timer` | `WipTaskList.svelte`, `MicroSteps.svelte` | `FocusTimer.svelte` + `focusTimer` store. |
|
||||
| `magnotia:step-completed` | `MicroSteps.svelte` | `completionStats` refresh. |
|
||||
| `magnotia:task-completed` | `stores/page.svelte.ts` (`completeTask`) | `completionStats`. |
|
||||
| `magnotia:task-deleted` | `stores/page.svelte.ts` (`deleteTask`) | `completionStats`. |
|
||||
| `magnotia:task-uncompleted` | `stores/page.svelte.ts` (`uncompleteTask`) | `completionStats`. |
|
||||
| `magnotia:toggle-recording` | `routes/+layout.svelte` (after hotkey debounce, both backends) | `pages/DictationPage.svelte`. |
|
||||
|
||||
## Window APIs
|
||||
|
||||
- `getCurrentWindow().minimize()` and `.toggleMaximize()`. `Titlebar.svelte`.
|
||||
- `getCurrentWindow().label`. Used to guard hotkey registration to the main window only and to filter own-source preference echoes.
|
||||
- `convertFileSrc(absolutePath)`. HistoryPage and viewer use this for `<audio>` elements.
|
||||
|
||||
## Plugins
|
||||
|
||||
| Plugin | Used by |
|
||||
|---|---|
|
||||
| `@tauri-apps/plugin-dialog` | `utils/saveMarkdown.ts`, `pages/FilesPage.svelte:handleBrowse`. |
|
||||
| `@tauri-apps/plugin-global-shortcut` | `routes/+layout.svelte` (X11 / macOS / Windows hotkey backend). |
|
||||
| `@tauri-apps/plugin-autostart` | SettingsPage launch-at-login toggle. |
|
||||
| `@tauri-apps/plugin-notification` | Available, used by nudge bus or a related path. Confirm specific call sites. |
|
||||
| `@tauri-apps/plugin-opener` | Available, used to open external URLs. Confirm specific call sites. |
|
||||
|
||||
## Watch outs
|
||||
|
||||
- The truncated grep returned `tts_s...` as a unique prefix. Verify every TTS command name (`tts_speak`, possibly `tts_stop`) against `lib.rs` to make sure the table above is exhaustive.
|
||||
- The "preview-*" events have two flavours: `magnotia:` namespaced (`preferences-changed`) and bare (`preview-listening`, `preview-cleanup`, `preview-hide`, `preview-append`, `preview-final`). The bare names are scoped to the overlay window's two-way handshake. Do not rename.
|
||||
- `magnotia:open-wind-down` listener is in `+layout.svelte` so the page opens regardless of which window the tray click came from. If you ever isolate windows further, this assumption breaks.
|
||||
- `task-window-focus` is the only event sent specifically to the float window.
|
||||
- DOM `CustomEvent` handlers do not survive across windows (they are window-local). The tasks list refresh trick on focus relies on this.
|
||||
|
||||
## See also
|
||||
|
||||
- [Windows and routes](windows-and-routes.md). For the listener mounting points.
|
||||
- [Stores](stores.md). For the dispatch points of intra-frontend events.
|
||||
- [../02-tauri-runtime/README.md](../02-tauri-runtime/README.md). For the matching Rust handlers (slice 02 will mirror this table from the other side).
|
||||
75
docs/architecture-map/01-frontend/i18n.md
Normal file
75
docs/architecture-map/01-frontend/i18n.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: Internationalisation
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Internationalisation
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Internationalisation
|
||||
|
||||
**Plain English summary.** svelte-i18n is wired but coverage is intentionally minimal. The infrastructure (loader, persisted choice, Settings selector) is in place so strings can migrate incrementally. Three locales: English (source of truth), Spanish, German. Most strings still render hardcoded.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/i18n/`
|
||||
- **LOC:** 73 (`index.ts`) + 19 lines per locale JSON.
|
||||
- **Key files:**
|
||||
- `src/lib/i18n/index.ts`. Setup, locale detection, public exports.
|
||||
- `src/lib/i18n/locales/en.json`. 19 lines.
|
||||
- `src/lib/i18n/locales/es.json`. 19 lines.
|
||||
- `src/lib/i18n/locales/de.json`. 19 lines.
|
||||
- **Library:** `svelte-i18n` 4.0.1.
|
||||
|
||||
## What's in here
|
||||
|
||||
### `index.ts`
|
||||
|
||||
```ts
|
||||
import { init, register, locale as svelteLocale } from "svelte-i18n";
|
||||
import { derived, get } from "svelte/store";
|
||||
|
||||
export type Locale = "en" | "es" | "de";
|
||||
|
||||
export const SUPPORTED_LOCALES: { code: Locale; label: string }[] = [
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "es", label: "Español" },
|
||||
{ code: "de", label: "Deutsch" },
|
||||
];
|
||||
|
||||
const STORAGE_KEY = "magnotia_locale";
|
||||
|
||||
register("en", () => import("./locales/en.json"));
|
||||
register("es", () => import("./locales/es.json"));
|
||||
register("de", () => import("./locales/de.json"));
|
||||
|
||||
function detectInitialLocale(): Locale { /* localStorage → navigator.language → "en" */ }
|
||||
```
|
||||
|
||||
Public exports:
|
||||
- `initI18n()`. Idempotent. Called from every layout's `onMount`-ish path (`+layout.svelte:38` and the float/viewer/preview break layouts).
|
||||
- `setLocale(code)`. Writes to `magnotia_locale` and updates the svelte-i18n store.
|
||||
- `currentLocale`. A derived store that components subscribe to via `$currentLocale`.
|
||||
- `SUPPORTED_LOCALES` constant for the picker.
|
||||
|
||||
### Locale JSON shape
|
||||
|
||||
19 lines per file means roughly a dozen translated strings. Treat the locales as a stub. Most page text is still hardcoded.
|
||||
|
||||
## Where it is consumed
|
||||
|
||||
- `SettingsPage.svelte:22` imports `_` (the translation function) and `SUPPORTED_LOCALES`, `setLocale`, `currentLocale`. The locale picker UI lives in Settings.
|
||||
- A handful of strings inside SettingsPage use `$_('key')`.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- Adding a new locale is one `register("xx", () => import("./locales/xx.json"))` call plus a `SUPPORTED_LOCALES` entry.
|
||||
- Cross window: each window initialises i18n independently via its layout. Locale change persists to `localStorage["magnotia_locale"]`. Float and viewer pick up the new locale on the next mount, not live. Worth wiring a Tauri event if live cross-window translation is needed.
|
||||
- The "British English" toggle in `settings.britishEnglish` is a transcription post-processing flag (slice 04 territory), not a UI locale. Not wired through svelte-i18n.
|
||||
- Default locale is detected from `navigator.language`. In Tauri, this is the OS locale.
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages: settings](pages/settings.md). The locale picker UI.
|
||||
- [Stores](stores.md). The `currentLocale` derived store.
|
||||
65
docs/architecture-map/01-frontend/pages-overview.md
Normal file
65
docs/architecture-map/01-frontend/pages-overview.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: Pages overview
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Pages overview
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Pages overview
|
||||
|
||||
**Plain English summary.** Magnotia has seven main window pages and three secondary window pages. The main window picks which to show by reading `page.current` (a string in the `page` store) and rendering the matching component. The secondary windows are routed by URL (`/float`, `/viewer`, `/preview`).
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Main window pages live at** `src/lib/pages/*.svelte` (7 files, 6 232 LOC).
|
||||
- **Secondary window pages live at** `src/routes/{float,viewer,preview}/+page.svelte` (3 files, 1 361 LOC).
|
||||
- **Switch site:** `src/routes/+page.svelte:18-32`.
|
||||
- **Driver store:** `src/lib/stores/page.svelte.ts:24-33` (`page.current`).
|
||||
- Possible values of `page.current`: `"first-run" | "dictation" | "files" | "tasks" | "history" | "settings" | "shutdown"`. Plus the legacy `"profiles"` which is rewritten to `"settings"` (see README debt note 5).
|
||||
|
||||
## Map of pages
|
||||
|
||||
### Main window
|
||||
|
||||
| Page | File | LOC | One line hook |
|
||||
|---|---|---|---|
|
||||
| Dictation | `src/lib/pages/DictationPage.svelte` | 1 100 | The hero recording surface. Streams partial transcripts via a Tauri `Channel`, runs the AI cleanup pipeline, paste/copy/export, extracts tasks, fills a template. |
|
||||
| Settings | `src/lib/pages/SettingsPage.svelte` | 2 484 | The configuration panel. Audio devices, vocabulary, profiles, templates, model management (Whisper + Parakeet + LLM), hotkey, rituals, nudges, accessibility, diagnostics. |
|
||||
| History | `src/lib/pages/HistoryPage.svelte` | 974 | Browse, search, play, edit, star, tag, export saved transcripts. Backed by SQLite FTS5. |
|
||||
| Tasks | `src/lib/pages/TasksPage.svelte` | 725 | Inbox/today/soon/later board with energy chips, lists, completion sparkline. |
|
||||
| Files | `src/lib/pages/FilesPage.svelte` | 263 | Drop or browse audio/video files. Calls `transcribe_file`. |
|
||||
| First run | `src/lib/pages/FirstRunPage.svelte` | 337 | Hardware probe (`probe_system`), model recommendation, model download. Auto exits to dictation. |
|
||||
| Shutdown ritual | `src/lib/pages/ShutdownRitualPage.svelte` | 169 | Evening wind down ritual. Triggered from the tray menu via `magnotia:open-wind-down`. |
|
||||
|
||||
### Secondary windows (URL routed)
|
||||
|
||||
| Window label | URL | File | LOC | One line hook |
|
||||
|---|---|---|---|---|
|
||||
| `tasks-float` | `/float` | `src/routes/float/+page.svelte` | 481 | Detached pinned tasks window with quick add and list management. |
|
||||
| `transcript-viewer` | `/viewer` | `src/routes/viewer/+page.svelte` | 606 | Transcript editor with synced audio playback and per segment editing. |
|
||||
| `transcription-preview` | `/preview` | `src/routes/preview/+page.svelte` | 274 | Borderless overlay showing live transcription with copy and revert. |
|
||||
|
||||
## How navigation actually happens
|
||||
|
||||
- Sidebar buttons set `page.current` directly (`src/lib/Sidebar.svelte:24`).
|
||||
- Some flows reach into `page.current` from outside the sidebar:
|
||||
- Hotkey press forces dictation: `+layout.svelte:139, 181`.
|
||||
- First run gate on mount: `+layout.svelte:350`.
|
||||
- Settings page "open evening wind down" button: `SettingsPage.svelte:816`.
|
||||
- Tray menu wind down event: `+layout.svelte:251-254`.
|
||||
- First run completion paths back to dictation: `FirstRunPage.svelte:83, 139, 146, 155`.
|
||||
- Implementation intentions store can route to tasks (`implementationIntentions.svelte.ts:125, 136, 142`).
|
||||
- Shutdown ritual exit: `ShutdownRitualPage.svelte:67`.
|
||||
- TaskSidebar "open tasks page" link: `TaskSidebar.svelte:48`.
|
||||
|
||||
## Where the shell tucks pages in
|
||||
|
||||
The shell renders sidebar plus a single main slot: `<div class="flex-1 overflow-hidden bg-bg">{@render children()}</div>`. The optional task sidebar (`page.taskSidebarOpen`) can dock a `TaskSidebar` panel beside any main page that is not first run.
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages: dictation](pages/dictation.md). [settings](pages/settings.md). [history](pages/history.md). [tasks](pages/tasks.md). [files](pages/files.md). [first run](pages/first-run.md).
|
||||
- [Windows and routes](windows-and-routes.md). For the route file structure and the secondary windows.
|
||||
- [Stores](stores.md). The `page` store that drives the switch.
|
||||
77
docs/architecture-map/01-frontend/pages/dictation.md
Normal file
77
docs/architecture-map/01-frontend/pages/dictation.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: Dictation page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Dictation page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → Dictation
|
||||
|
||||
**Plain English summary.** This is the recording surface. Press the hotkey or the mic button, watch live partial text stream into a textarea, then on stop run the AI cleanup, extract tasks, copy to clipboard or paste into the focused application. The page handles model loading, the live transcription session, the preview overlay, optional template insertion, and task extraction.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/DictationPage.svelte`
|
||||
- **LOC:** 1 100
|
||||
- **Imports (selected):**
|
||||
- Tauri: `Channel`, `invoke` from `@tauri-apps/api/core`. `emit` from `@tauri-apps/api/event`. `getCurrentWindow` from `@tauri-apps/api/window`.
|
||||
- Stores: `page`, `settings`, `templates`, `profiles`, `addToHistory`, `addTask`, `tasks` from `page.svelte.ts`. `markGenerating`, `markGenerationDone` from `llmStatus.svelte.ts`. `profilesStore` from `profiles.svelte.ts`. `toasts`. `getPreferences` from `preferences.svelte.ts`.
|
||||
- Components: `Card`, `ModelDownloader`, `EmptyState`, `SpeakerButton`.
|
||||
- Utils: `exportTranscript`, `extractTasks`, `pad`, `FEEDBACK_TIMEOUT_MS`, `bionicReading` action, `measurePreWrap`, `transcriptPretextFont`, `transcriptPretextLineHeight`, `playStartCue`, `playStopCue`, `playCompleteCue`.
|
||||
- External: `lucide-svelte` icons (`Mic`, `Loader2`, `SquareCheck`, `AlertTriangle`).
|
||||
- **Used by:** `src/routes/+page.svelte:20` when `page.current === "dictation"`. Also forced by global hotkey and first run path.
|
||||
|
||||
## What's in here
|
||||
|
||||
### Local state (selected)
|
||||
|
||||
- `transcript` (string), `segments` (array of `{start, end, text}`).
|
||||
- `recording` is on the global `page` store; this page mirrors it.
|
||||
- `timerInterval`, `startTime`, `timerText` (mm:ss).
|
||||
- Model lifecycle flags: `modelReady`, `modelLoading`, `needsDownload`.
|
||||
- AI flags: `aiProcessing`, `aiStatus`, `extractedCount`.
|
||||
- Live session: `sessionId`, `drainingSessionId`, `lastResultAt`, `lastLiveActivityAt`, `liveWarning`.
|
||||
- Tauri channels (typed): `resultChannel`, `statusChannel` opened by `new Channel(...)`.
|
||||
- UI: `showExportMenu`, `saved`, `insertPos` (cursor-based insertion), `activeTemplate`.
|
||||
- `runtimeCapabilities` (from `get_runtime_capabilities`, used to decide engine availability and CUDA presence).
|
||||
|
||||
### Lifecycle
|
||||
|
||||
- `onMount`. Loads `runtimeCapabilities` (`DictationPage.svelte:134`). Sets up the global hotkey custom event listener (`magnotia:toggle-recording`, dispatched from the `+layout.svelte` hotkey path).
|
||||
- `onDestroy`. Tears down listeners and timers.
|
||||
|
||||
### Recording flow (high level)
|
||||
|
||||
1. Toggle from the mic button or the `magnotia:toggle-recording` window event.
|
||||
2. If model not ready, ensure model: check `check_engine`, `check_parakeet_engine`, `check_llm_model`, then load via `load_model` / `load_parakeet_model` / `load_llm_model` as required (`DictationPage.svelte:241-272`).
|
||||
3. Open two `Channel<message>` instances (`DictationPage.svelte:341-342`) and call `start_live_transcription_session` with the channel handles.
|
||||
4. As the backend pushes status and partial result messages, append text to `transcript`, update `segments`, and broadcast `preview-append` to the preview overlay window (`DictationPage.svelte:165, 381, 385`). If the preview is enabled and not already open, `open_preview_window` is invoked.
|
||||
5. On stop, call `stop_live_transcription_session`, then run AI cleanup (`cleanup_transcript_text_cmd`) gated on `get_llm_status` (`DictationPage.svelte:464-472`).
|
||||
6. Optionally extract tasks via `extract_tasks_from_transcript_cmd` (`DictationPage.svelte:487-491`) and add them via the `addTask` store helper.
|
||||
7. Push to history with `addToHistory` (which calls `add_transcript`).
|
||||
8. Auto copy and/or auto paste based on `settings.autoCopy` / `settings.autoPaste`. Paste path uses `paste_text` (`DictationPage.svelte:544-554`); fallback to `copy_to_clipboard`.
|
||||
9. Emit `preview-cleanup`, `preview-final`, `preview-hide` to drive the overlay state machine (`DictationPage.svelte:531, 539, 606`).
|
||||
|
||||
## Tauri command surface
|
||||
|
||||
`get_runtime_capabilities`, `check_llm_model`, `load_llm_model`, `load_parakeet_model`, `load_model`, `start_live_transcription_session`, `stop_live_transcription_session`, `open_preview_window`, `get_llm_status`, `cleanup_transcript_text_cmd`, `extract_tasks_from_transcript_cmd`, `paste_text`, `copy_to_clipboard`.
|
||||
|
||||
Plus `emit("preview-append" | "preview-listening" | "preview-cleanup" | "preview-final" | "preview-hide")`.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- The page uses `// @ts-nocheck`. Adding type safety here would catch a class of regressions, especially around the `Channel<T>` payload shape.
|
||||
- The cursor based insertion (`insertPos`) is fragile. Editing the textarea while a live session is running can corrupt the segment offset map. Tests around `extractTasks` only cover the post stop path.
|
||||
- Multiple paths can flip `recording` (button, hotkey custom event, error early-out). Treat the page as a state machine with a single source of truth and you will save yourself.
|
||||
- `extractedCount` is purely cosmetic. It is reset on recording start.
|
||||
- The "live activity" stalled detector is timer based (`lastLiveActivityAt`). On a stalled session the user sees `liveWarning`. The recovery path is "stop and start again".
|
||||
|
||||
## See also
|
||||
|
||||
- [Stores](../stores.md). `page`, `settings`, `llmStatus` reactivity.
|
||||
- [Frontend ↔ Tauri bridge](../frontend-tauri-bridge.md). All commands and events.
|
||||
- [Components](../components.md). `Card`, `ModelDownloader`, `SpeakerButton`, `EmptyState`.
|
||||
- [../03-audio-transcription/README.md](../../03-audio-transcription/README.md). Live session plumbing on the Rust side.
|
||||
- [../04-llm-formatting-mcp/README.md](../../04-llm-formatting-mcp/README.md). Cleanup and task extraction commands.
|
||||
63
docs/architecture-map/01-frontend/pages/files.md
Normal file
63
docs/architecture-map/01-frontend/pages/files.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: Files page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Files page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → Files
|
||||
|
||||
**Plain English summary.** Drag and drop or browse local audio/video files for transcription. Same engine as live dictation, just file mode. On finish, results land in the page (preview text, segments) and into `addToHistory`.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/FilesPage.svelte`
|
||||
- **LOC:** 263
|
||||
- **Imports (selected):**
|
||||
- Tauri: `invoke` (core), `listen` (event).
|
||||
- Stores: `settings`, `addToHistory` from `page.svelte.ts`. `profilesStore`. `toasts` (transitive via store helpers).
|
||||
- Components: `Card`, `EmptyState`.
|
||||
- Utils: `exportTranscript`.
|
||||
- External: `Upload` icon from `lucide-svelte`. `@tauri-apps/plugin-dialog` (lazy import).
|
||||
|
||||
## What's in here
|
||||
|
||||
### Drag and drop
|
||||
|
||||
- `onMount` registers three Tauri events:
|
||||
- `tauri://drag-drop` (`FilesPage.svelte:28`). Filters the dropped paths to supported extensions and starts transcription.
|
||||
- `tauri://drag-enter` (`FilesPage.svelte:34`). Sets `isDragOver = true`.
|
||||
- `tauri://drag-leave` (`FilesPage.svelte:35`). Sets `isDragOver = false`.
|
||||
- `onDestroy` calls each unlisten.
|
||||
|
||||
### Browse
|
||||
|
||||
- `handleBrowse()` lazy imports `@tauri-apps/plugin-dialog` and opens with the audio/video filter (`mp3, wav, m4a, mp4, flac, ogg, webm, mov, mkv`).
|
||||
|
||||
### Transcription
|
||||
|
||||
- Calls `invoke("transcribe_file", { ... })` (`FilesPage.svelte:76`). Receives transcript text + segments.
|
||||
- Optional `copy_to_clipboard` on completion.
|
||||
- Pushes result into history via the store helper.
|
||||
|
||||
### State
|
||||
|
||||
- `fileTranscript`, `segments`, `isDragOver`, `progress`, `progressText`, `fileName`, `error`, `transcribing`.
|
||||
|
||||
## Tauri command surface
|
||||
|
||||
`transcribe_file`, `copy_to_clipboard`. Plus dialog plugin.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- The drag and drop events require the Tauri config to declare drag drop on the relevant window. If the events do not fire, check `tauri.conf.json` and slice 02.
|
||||
- Long files block the UI of this page until `transcribe_file` resolves. There is no abort.
|
||||
- The supported extensions list is duplicated between the file dialog filter and (presumably) the Rust side. Drift risk.
|
||||
- Multi file drag drop iterates serially. No queue UI.
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages: dictation](dictation.md). The live counterpart.
|
||||
- [Frontend ↔ Tauri bridge](../frontend-tauri-bridge.md).
|
||||
59
docs/architecture-map/01-frontend/pages/first-run.md
Normal file
59
docs/architecture-map/01-frontend/pages/first-run.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: First run page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# First run page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → First run
|
||||
|
||||
**Plain English summary.** What the user sees the first time they launch Magnotia, before any model is on disk. Probes hardware, recommends a Whisper model size, and downloads the chosen model (Whisper or Parakeet). On success, transitions to the dictation page.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/FirstRunPage.svelte`
|
||||
- **LOC:** 337
|
||||
- **Imports:**
|
||||
- Tauri: `invoke`, `listen`.
|
||||
- Stores: `page`, `settings`, `saveSettings` from `page.svelte.ts`. `toasts`.
|
||||
- Components: `UnicodeSpinner`.
|
||||
- External: `lucide-svelte` (`Download`, `CheckCircle`, `Sunrise`, `Moon`, `Play`).
|
||||
- **Includes:** also `ShutdownRitualPage` is imported here (suggesting the "evening ritual" preview lives in this same flow). Verify on read; treat as a coupled flow.
|
||||
|
||||
## What's in here
|
||||
|
||||
### Probe
|
||||
|
||||
- `onMount` calls `probe_system` and `rank_models` (`FirstRunPage.svelte:31-32`) to compute recommended models given the user's RAM, GPU, OS.
|
||||
- Sets `probing = false` and renders the recommendation UI.
|
||||
|
||||
### Download
|
||||
|
||||
- `downloadAndGo(modelId)` (`FirstRunPage.svelte:55-95`):
|
||||
- Subscribes to `model-download-progress` and `parakeet-download-progress` events.
|
||||
- Calls `download_model` then `load_model` (Whisper) or `download_parakeet_model` then `load_parakeet_model`.
|
||||
- On success, sets `ready = true` and routes to dictation (`page.current = "dictation"`).
|
||||
- Computes `estimatedMinutes` from elapsed and progress percent (`derived.by`).
|
||||
|
||||
### Triggering
|
||||
|
||||
- The shell auto sets `page.current = "first-run"` if `list_models` and `list_parakeet_models` are both empty (`+layout.svelte:346-353`).
|
||||
- Skip path: clicking "Get started" without a model lets the user opt out (still routes to dictation).
|
||||
|
||||
## Tauri command surface
|
||||
|
||||
`probe_system`, `rank_models`, `download_model`, `load_model`, `download_parakeet_model`, `load_parakeet_model`. Events: `model-download-progress`, `parakeet-download-progress`.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- The download is a single shot. There is no resume on cancel; if the user closes the app mid download, they restart from zero. Slice 02/05 may improve this later.
|
||||
- `estimatedMinutes` is a linear extrapolation. Network speed is not constant; expect the value to wiggle.
|
||||
- The page imports `ShutdownRitualPage` but the value of doing so should be confirmed (potential dead import).
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages overview](../pages-overview.md). When this page is forced.
|
||||
- [Frontend ↔ Tauri bridge](../frontend-tauri-bridge.md). Probe and download commands.
|
||||
- [Windows and routes](../windows-and-routes.md). Shell triggers.
|
||||
71
docs/architecture-map/01-frontend/pages/history.md
Normal file
71
docs/architecture-map/01-frontend/pages/history.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: History page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# History page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → History
|
||||
|
||||
**Plain English summary.** The transcript browser. Lists everything saved to SQLite, full text searchable via FTS5, with inline expansion to read the full body, audio playback, starring, manual + auto tagging, copy, delete, export to Markdown, and "open in viewer" handoff to the dedicated transcript editor window.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/HistoryPage.svelte`
|
||||
- **LOC:** 974
|
||||
- **Imports (selected):**
|
||||
- Tauri: `invoke` and `convertFileSrc` from `@tauri-apps/api/core`.
|
||||
- Stores: `historyStore` helpers from `page.svelte.ts` (`history`, `loadHistory`, `deleteFromHistoryById`, `renameHistoryEntry`), `toasts`, `getPreferences`.
|
||||
- Utils: `deriveAutoTags`, `buildFrontmatter`, `buildMarkdown`, `normaliseTag` (frontmatter). `saveTranscriptAsMarkdown`, `exportTranscriptsToDir` (saveMarkdown). `clampTextLines`, `measurePreWrap` (textMeasure). `bodyPretextLineHeight`, `pretextFontShorthand` (accessibilityTypography). `buildCumulativeOffsets`, `findVisibleRange` (virtualList). `formatTime`, `formatDuration` (time). `PLAYBACK_SPEEDS` (constants).
|
||||
- Components: `Card`, `EmptyState`.
|
||||
- External: `lucide-svelte` (`Search`, `Clock`, `Play`, `Pause`, `FileText`, `Mic`, `ChevronDown`, `ExternalLink`, `Star`, `Tag`).
|
||||
|
||||
## What's in here
|
||||
|
||||
### List + search
|
||||
|
||||
- Loads via `loadHistory` (which calls `list_transcripts` in Rust).
|
||||
- Search input filters client side (FTS happens server side via `search_transcripts` for some flows; check `page.svelte.ts`).
|
||||
- Virtualised scrolling using `buildCumulativeOffsets` and `findVisibleRange` from `utils/virtualList.ts`. Row heights use `measurePreWrap` to compute pre wrap heights from the live preferences font.
|
||||
- Constants: `COLLAPSED_ROW_MIN_HEIGHT`, `COLLAPSED_ROW_VERTICAL_PADDING`, `EXPANDED_BASE_HEIGHT`, `EXPANDED_PADDING`, etc.
|
||||
|
||||
### Audio playback
|
||||
|
||||
- `convertFileSrc()` maps the saved audio path to a webview URL.
|
||||
- Inline `<audio>` element. Speed control uses `PLAYBACK_SPEEDS` (`[0.5, 1, 1.5, 2, 3, 5]`).
|
||||
- A `requestAnimationFrame` loop advances `currentTime`. `cancelAnimationFrame` on stop.
|
||||
|
||||
### Per row actions
|
||||
|
||||
- Star (toggles `starred` flag, persisted via `update_transcript`).
|
||||
- Copy (`copy_to_clipboard`).
|
||||
- Delete (confirms, then calls `delete_transcript`).
|
||||
- Open in viewer window (`open_viewer_window`, then handoff via `localStorage`).
|
||||
- Auto tag (calls `extract_content_tags_cmd`, returns suggested tags merged into `tags`).
|
||||
- Rename via `renameHistoryEntry` store helper.
|
||||
- Export to markdown via `saveTranscriptAsMarkdown`.
|
||||
|
||||
### Bulk
|
||||
|
||||
- Bulk auto tag walks the visible list and calls `extract_content_tags_cmd` per row.
|
||||
- Bulk export to directory via `exportTranscriptsToDir`.
|
||||
|
||||
## Tauri command surface
|
||||
|
||||
`delete_transcript`, `copy_to_clipboard`, `open_viewer_window`, `extract_content_tags_cmd`. Plus indirect commands from store helpers (`add_transcript`, `update_transcript`, etc) and the dialog plugin used by `saveMarkdown.ts`.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- Virtual scrolling assumes stable row heights once measured. Toggling preferences (font size, line height) invalidates the measure. The page recomputes on `getPreferences()` change but watch for stutter on rapid changes.
|
||||
- The viewer handoff writes the transcript ID to `localStorage`. The viewer window then loads from SQLite. Do not put transcript text in `localStorage`.
|
||||
- Auto tag is rate limited only by user clicks. Bulk over a large library can hammer the LLM. Consider a guard if scaling.
|
||||
- `convertFileSrc` paths are valid only inside the Tauri webview. If the audio path is missing or moved, `<audio>` will silently fail; show an error state.
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages: dictation](dictation.md). Where transcripts originate.
|
||||
- [Stores](../stores.md). `page.svelte.ts` history helpers.
|
||||
- [Frontend ↔ Tauri bridge](../frontend-tauri-bridge.md). Commands referenced.
|
||||
- [Windows and routes](../windows-and-routes.md). The viewer window handoff.
|
||||
72
docs/architecture-map/01-frontend/pages/settings.md
Normal file
72
docs/architecture-map/01-frontend/pages/settings.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: Settings page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Settings page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → Settings
|
||||
|
||||
**Plain English summary.** The configuration panel. Owns audio device selection, vocabulary, profiles, templates, transcription engine choice and model management (Whisper + Parakeet + LLM), the global hotkey, rituals, nudges, accessibility, text-to-speech, diagnostics, and locale selection. It is by far the largest single file in the frontend at 2 484 lines.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/SettingsPage.svelte`
|
||||
- **LOC:** 2 484
|
||||
- **Imports (selected):**
|
||||
- Stores: `settings`, `saveSettings`, `profiles`, `saveProfiles`, `templates`, `saveTemplates`, `page`, `addProfileTaskList`, `removeProfileTaskList` from `page.svelte.ts`. `getPreferences`, `updatePreferences`. `profilesStore`, `DEFAULT_PROFILE_ID`. `toasts`. `refreshLlmStatus`.
|
||||
- Components: `Card`, `Toggle`, `SegmentedButton`, `HotkeyRecorder`, `ImplementationRulesEditor`, `SettingsGroup`, `ZonePicker`, `AccessibilityControls`.
|
||||
- Utils: `clampTextLines`, `bodyPretextLineHeight`, `pretextFontShorthand`, `formatDuration`.
|
||||
- i18n: `_`, `SUPPORTED_LOCALES`, `setLocale`, `currentLocale`.
|
||||
- External: `lucide-svelte` (`Check`, `Search`, `X`, `Mic`, `BookOpen`, `Type`, `Sparkles`, `SquareCheck`, `Clipboard`, `Sliders`).
|
||||
|
||||
## Sections (top level)
|
||||
|
||||
Built from `SettingsGroup` accordions. Top level groups (line refs are anchors in the file):
|
||||
|
||||
1. **Audio** (`SettingsPage.svelte:1141`). Device list (from `list_audio_devices`), microphone selection.
|
||||
2. **Vocabulary** (`SettingsPage.svelte:1191`). Two nested groups:
|
||||
- **Terms and profiles** (`SettingsPage.svelte:1197`). Vocabulary terms attached to the active profile, plus the active profile selector.
|
||||
- **Profiles and templates** (`SettingsPage.svelte:1415`). Two further nested groups:
|
||||
- **Profiles** (`SettingsPage.svelte:1423`). Create, rename, delete; default profile cannot be deleted.
|
||||
- **Templates** (`SettingsPage.svelte:1481`). Per profile templates injected at recording start.
|
||||
3. **Processing** (also referred to in file as the engine + AI tier section). Phase 9c style group with `onopen` hook that probes `check_engine`, `check_parakeet_engine`, `list_models`, `detect_paste_backends`, etc.
|
||||
4. **Hotkey**. Wraps `HotkeyRecorder`. Search navigation jumps here when the user clicks the hotkey chip in another section (`SettingsPage.svelte:477`).
|
||||
5. **Rituals**. Morning triage, evening wind down toggles.
|
||||
6. **Nudges and implementation intentions**. Wraps `ImplementationRulesEditor`.
|
||||
7. **Accessibility**. Wraps `AccessibilityControls`. Theme, zone (`ZonePicker`), font family, sizes, line height, letter spacing, bionic reading, reduce motion.
|
||||
8. **Read aloud (TTS)** (`SettingsPage.svelte:759`). Voice selection from `tts_list_voices`, rate, sample play.
|
||||
9. **Diagnostics** (`SettingsPage.svelte:377-405`). Generates and saves a redacted diagnostic report.
|
||||
10. **Tasks** (sparkline toggle, relocated in Phase 9c).
|
||||
11. **Launch at login**. Wires `@tauri-apps/plugin-autostart`.
|
||||
12. **Locale**. svelte-i18n locale picker.
|
||||
|
||||
## Key state
|
||||
|
||||
- `audioDevices`, `downloadedModels`, `parakeetOk`, `parakeetDownloaded`, `pasteBackends`, `systemInfo`, `runtimeCapabilities`, `llmLoaded`, `llmModels`, `llmStatuses`, `ttsVoices`.
|
||||
- Search query (`X`/`Search` icons) drives a force open mode for `SettingsGroup` (`SettingsPage.svelte:432-477`).
|
||||
- Three concurrent download progress listeners: Whisper (`model-download-progress`), Parakeet (`parakeet-download-progress`), LLM (`magnotia:llm-download-progress`) (`SettingsPage.svelte:864-880`).
|
||||
|
||||
## Tauri command surface
|
||||
|
||||
Audio: `list_audio_devices`. Models (Whisper): `list_models`, `download_model`, `load_model`, `check_engine`. Models (Parakeet): `check_parakeet_engine`, `check_parakeet_model`, `download_parakeet_model`, `load_parakeet_model`. Models (LLM): `recommend_llm_tier`, `check_llm_model`, `download_llm_model`, `load_llm_model`, `unload_llm_model`, `delete_llm_model`, `test_llm_model`, `get_llm_status`. Capabilities: `get_runtime_capabilities`, `probe_system`, `detect_paste_backends`. TTS: `tts_list_voices`, `tts_speak`. Diagnostics: `generate_diagnostic_report`, `save_diagnostic_report`. Plus the implicit `save_preferences` invoked by the preferences store on every accessibility toggle.
|
||||
|
||||
Events listened to: `model-download-progress`, `parakeet-download-progress`, `magnotia:llm-download-progress`.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- 2 484 lines, hand rolled accordion, Phase 9c handover already deferred the deeper restructure. New options should be added inside the existing `SettingsGroup` topology rather than threaded into the global header.
|
||||
- `settings.theme` ("Light"/"Dark"/"System") is the legacy field; `preferences.theme` is the new one. `+layout.svelte:61-68` re maps every effect cycle. Adding new theme options needs both stores.
|
||||
- `page.current = "shutdown"` button at line 816 is the only entrance from settings into the wind down ritual (other than the tray).
|
||||
- The `model-download-progress` event payload is shared across Whisper and Parakeet listeners. Take care that progress UI does not cross wires when both downloads run concurrently (rare but possible if `+layout.svelte` triggers a prewarm while Settings is open).
|
||||
- The diagnostics path strips secrets in Rust; do not re add raw paths or env values to the report payload here.
|
||||
- Accessibility writes go through `preferences` (Tauri persisted) but `settings.fontSize` writes through `localStorage`. Two persistence channels for similar surfaces.
|
||||
|
||||
## See also
|
||||
|
||||
- [Components](../components.md). `SettingsGroup`, `HotkeyRecorder`, `ImplementationRulesEditor`, `ZonePicker`, `AccessibilityControls`, `Toggle`, `SegmentedButton`.
|
||||
- [Stores](../stores.md). `page` settings + profiles + templates. `preferences`. `llmStatus`.
|
||||
- [Frontend ↔ Tauri bridge](../frontend-tauri-bridge.md). All command names referenced here.
|
||||
- [Internationalisation](../i18n.md). Locale picker.
|
||||
65
docs/architecture-map/01-frontend/pages/tasks.md
Normal file
65
docs/architecture-map/01-frontend/pages/tasks.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: Tasks page
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Tasks page
|
||||
|
||||
> **Where you are:** [Architecture map](../../README.md) → [Frontend](../README.md) → [Pages overview](../pages-overview.md) → Tasks
|
||||
|
||||
**Plain English summary.** The full screen tasks board. Inbox, today, soon, later buckets. Per task energy chips (low/medium/high), per task lists, micro steps, completion sparkline, search, sort, drag to reorder, and a "pop out" button that opens the same tasks in a small persistent floating window via `open_task_window`.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/pages/TasksPage.svelte`
|
||||
- **LOC:** 725
|
||||
- **Imports (selected):**
|
||||
- Stores: from `page.svelte.ts`: `tasks`, `addTask`, `completeTask`, `uncompleteTask`, `deleteTask`, `updateTask`, `setTaskEnergy`, `taskLists`, `addTaskList`, `renameTaskList`, `deleteTaskList`, `settings`, `saveSettings`. From `completionStats.svelte.ts`: `recentCompletions`, `todayCount`.
|
||||
- Components: `WipTaskList`, `EmptyState`, `CompletionSparkline`, `EnergyChip`, `Card`.
|
||||
- Utils: `formatTimestamp` (time). `BUCKET_COLORS`, `EFFORT_LABELS`, `EFFORT_ORDER` (constants).
|
||||
- External: `lucide-svelte` (`SquareCheck`, `Search`, `ExternalLink`, `ChevronLeft`, `ArrowUpDown`, `Plus`, `X`, `ChevronRight`, `Zap`).
|
||||
|
||||
## What's in here
|
||||
|
||||
### Local state
|
||||
|
||||
- `activeBucket` (`"all" | "inbox" | "today" | "soon" | "later"`).
|
||||
- `activeListId` (`"all" | "inbox" | <listId>`).
|
||||
- `showCompleted`, `quickInput`, `searchQuery`, `sidebarCollapsed`.
|
||||
|
||||
### Render
|
||||
|
||||
- Quick add input (top). Sets bucket via segmented choice, energy via `EnergyChip`.
|
||||
- Filter / sort header. Sort modes use `EFFORT_ORDER` for effort comparisons.
|
||||
- Task list body uses `WipTaskList` (which renders rows with `MicroSteps` expansion).
|
||||
- "Pop out" icon → `invoke("open_task_window")` (`TasksPage.svelte:231`).
|
||||
- Sparkline panel renders `CompletionSparkline` if `settings.showMomentumSparkline` is true.
|
||||
|
||||
### Stores it depends on
|
||||
|
||||
- `tasks` (live array). `taskLists`. `recentCompletions` for the sparkline.
|
||||
|
||||
### Events
|
||||
|
||||
- Listens implicitly to: `magnotia:task-completed`, `magnotia:task-uncompleted`, `magnotia:task-deleted`, `magnotia:step-completed` are emitted from store helpers and consumed by `completionStats.svelte.ts` to refresh the sparkline.
|
||||
|
||||
## Tauri command surface (direct)
|
||||
|
||||
- `open_task_window` (page action).
|
||||
|
||||
The store helpers used here call into Rust through `add_task_cmd`, `complete_task_cmd`, `uncomplete_task_cmd`, `delete_task_cmd`, plus list management commands (`*_task_list_cmd`).
|
||||
|
||||
## Watch outs
|
||||
|
||||
- Drag and drop is heavy on this page. Reorder mutations should always go via the store helpers; do not manipulate `tasks` directly or you will desync the SQLite source of truth.
|
||||
- The sparkline is gated on `settings.showMomentumSparkline` (true by default). It re renders on the four `magnotia:task-*` events, plus window focus for date rollover.
|
||||
- "Pop out" deliberately does not pass any state. The float window reads the same store, which is shared via store hydration on mount and `localStorage` for `settings`.
|
||||
- Sort, filter, search are not persisted. Reload returns to defaults.
|
||||
|
||||
## See also
|
||||
|
||||
- [Components](../components.md). `WipTaskList`, `MicroSteps`, `EnergyChip`, `CompletionSparkline`.
|
||||
- [Stores](../stores.md). `page` task helpers, `completionStats`.
|
||||
- [Windows and routes](../windows-and-routes.md). The float window equivalent.
|
||||
130
docs/architecture-map/01-frontend/stores.md
Normal file
130
docs/architecture-map/01-frontend/stores.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: Stores
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Stores
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Stores
|
||||
|
||||
**Plain English summary.** Reactive state. Magnotia uses Svelte 5 runes (`$state`, `$derived`, `$effect`) instead of legacy stores. Each file under `src/lib/stores/` exports one (or more) `$state` objects plus the helper functions that read or mutate them. Components import the state directly and Svelte tracks reactivity automatically. Two persistence channels: `localStorage` for settings, profiles, task lists, templates; Tauri (`save_preferences`) for accessibility preferences.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/lib/stores/`
|
||||
- **Files:** 10 (4 085 LOC across stores + utils + types).
|
||||
- **Cross window sync:**
|
||||
- `localStorage` `storage` event in the float window for settings.
|
||||
- Tauri `emit("magnotia:preferences-changed")` for preferences (handled by every layout).
|
||||
- **Persistence keys:** `magnotia_settings`, `magnotia_profiles`, `magnotia_task_lists`, `magnotia_templates`, `magnotia_locale`, plus a viewer handoff key.
|
||||
|
||||
## The stores
|
||||
|
||||
### `page.svelte.ts` (697 LOC). The big one.
|
||||
|
||||
Owns:
|
||||
- `page` (`PageState`): `current`, `status`, `statusColor`, `activeProfile`, `recording`, `timerText`, `handedness`, `taskSidebarOpen`. Initial: `current = "dictation"`.
|
||||
- `settings` (`SettingsState`). Persisted to `localStorage["magnotia_settings"]` via `utils/settingsMigrations.ts`. Defaults at line 60 onward. Includes engine choice, model size, language, device, format mode, fillers, anti hallucination, British English, auto copy/paste, transcription preview, meeting auto capture (+ apps list), sound cues + volume, timestamps, theme, font size, AI tier, LLM model id and prompt preset, GPU concurrency, prewarm flag, save audio, output folder, global hotkey, sidebar collapsed, microphone device, energy state, match my energy, TTS voice + rate, rituals (morning/evening + time), launch at login, ritualsPromptSeen, nudges (enabled/muted/speakAloud), showMomentumSparkline.
|
||||
- `profiles`, `templates`, `taskLists` arrays.
|
||||
- `tasks` and `history` arrays.
|
||||
|
||||
Helpers (selected, with line refs):
|
||||
- `addToHistory` → `add_transcript` (`page.svelte.ts:234`).
|
||||
- `mapTranscriptRow`, `saveTranscriptMeta` → `update_transcript` (`page.svelte.ts:272`).
|
||||
- `deleteFromHistoryById` → `delete_transcript` (`page.svelte.ts:320`).
|
||||
- `addTask`, `completeTask`, `uncompleteTask`, `deleteTask`, `setTaskEnergy`, `updateTask` → matching `*_task_cmd` Rust commands. `completeTask` and friends emit `magnotia:task-completed | -uncompleted | -deleted` window events (line 470 onward).
|
||||
- Task lists: `addTaskList`, `renameTaskList`, `deleteTaskList`, `moveTaskList`, `sortTaskLists`, `moveTaskToList`, `moveTaskListToProfile`.
|
||||
- Profile mutators: `addProfileTaskList`, `removeProfileTaskList`.
|
||||
- `saveSettings`, `saveProfiles`, `saveTemplates` write to `localStorage`.
|
||||
|
||||
### `preferences.svelte.ts` (172 LOC).
|
||||
|
||||
Owns the `Preferences` shape: `theme` ("light" | "dark" | "system"), `zone` ("default" | ...), `accessibility` (`fontFamily`, `fontSize`, `letterSpacing`, `lineHeight`, `transcriptSize`, `bionicReading`, `reduceMotion`).
|
||||
|
||||
- DOM is the source of truth at runtime: `readFromDOM()` reads `<html>` data attributes and CSS variables; `applyToDOM(prefs)` writes them.
|
||||
- Persists via `invoke("save_preferences", { preferences: JSON.stringify(prefs) })` with a debounce. Failure shows a single toast per process (`preferences.svelte.ts:101-120`).
|
||||
- Cross window: `broadcastPreferences` calls `emit("magnotia:preferences-changed", { source, prefs })`. Receivers in `+layout.svelte` and the secondary `+layout@.svelte` files apply external prefs after a label check.
|
||||
- Exposes `getPreferences`, `updatePreferences`, `updateAccessibility`, `applyExternalPreferences`, `PREFERENCES_CHANGED_EVENT` constant.
|
||||
- Font families resolved from a constant map (Lexend, Atkinson, OpenDyslexic).
|
||||
|
||||
### `profiles.svelte.ts` (123 LOC).
|
||||
|
||||
The vocabulary profile store (separate from the `profiles` array on `page.svelte.ts`). Holds:
|
||||
- `activeProfileId`, list of profiles, vocabulary terms.
|
||||
|
||||
Tauri commands: `load_profiles_cmd` (implicit on load), `update_profile_cmd`, `delete_profile_cmd`, `delete_profile_term_cmd`, plus add term commands.
|
||||
Exposes `DEFAULT_PROFILE_ID`.
|
||||
|
||||
### `toasts.svelte.ts` (99 LOC).
|
||||
|
||||
Toast notifications. State is an array of `{id, severity, title, body, duration}`.
|
||||
|
||||
Helpers: `toasts.info(title, body?)`, `.warn`, `.error`, `.success`, plus `dismiss(id)`. Auto dismiss via `setTimeout`. Read by `ToastViewport.svelte`.
|
||||
|
||||
### `llmStatus.svelte.ts` (64 LOC).
|
||||
|
||||
Tracks the LLM lifecycle pill. State is one of `idle | loading | generating | downloading | unavailable`.
|
||||
|
||||
- `refreshLlmStatus(aiTier)` calls `get_llm_status` and updates the chip.
|
||||
- `markGenerating()`, `markGenerationDone()` are called around `cleanup_transcript_text_cmd` in `DictationPage.svelte`.
|
||||
|
||||
### `completionStats.svelte.ts` (59 LOC).
|
||||
|
||||
Phase 8 gamification. Owns `recentCompletions` (`DailyCompletionCount[]`) and a `todayCount` derivation.
|
||||
|
||||
Refresh triggers (no polling): module load, `magnotia:task-completed`, `magnotia:step-completed`, `magnotia:task-uncompleted`, `magnotia:task-deleted`, window focus (for midnight rollover).
|
||||
|
||||
### `focusTimer.svelte.ts` (238 LOC).
|
||||
|
||||
Owns the floating focus timer. State: target ms, started at, label, taskId, paused.
|
||||
|
||||
- `setInterval` at 250 ms (`TICK_INTERVAL_MS`).
|
||||
- Triggered by `magnotia:start-timer` window events. Emits `magnotia:focus-timer-cancelled` and `magnotia:focus-timer-complete` on transitions.
|
||||
|
||||
### `implementationIntentions.svelte.ts` (260 LOC).
|
||||
|
||||
"When X happens, do Y" rules engine. Owns the rules array.
|
||||
|
||||
- 30 second `setInterval` (`TIME_RULE_POLL_MS`) checks time based rules.
|
||||
- On match, can route `page.current = "tasks"` (lines 125, 136, 142) and call `tts_speak` if speak aloud is enabled (line 170).
|
||||
- Uses `delete_implementation_rule` to remove a rule (line 82).
|
||||
- Emits `magnotia:implementation-rules-changed` after writes.
|
||||
- Lifecycle: `startImplementationIntentions`, `stopImplementationIntentions` from `+layout.svelte`.
|
||||
|
||||
### `nudgeBus.svelte.ts` (292 LOC).
|
||||
|
||||
Owns the nudge engine. Two `setInterval` handles:
|
||||
- `blurCheckHandle` for window blur detection.
|
||||
- `triagePollHandle` at 5 minute cadence checking morning triage triggers.
|
||||
- Plus a `microStepTimers` Map for per task delayed notifications.
|
||||
|
||||
Key commands: `deliver_nudge` (line 112), `tts_speak` (line 119).
|
||||
Emits `magnotia:morning-triage-finished` after the modal closes.
|
||||
Lifecycle: `startNudgeBus`, `stopNudgeBus` from `+layout.svelte`.
|
||||
|
||||
### `speaker.svelte.ts` (10 LOC).
|
||||
|
||||
Tiny. Holds `speakingId` so `SpeakerButton` instances can debounce / cancel each other. Candidate for collapsing into a util (README debt note 9).
|
||||
|
||||
## Cross store traffic
|
||||
|
||||
- `DictationPage.completeRecording()` → `addToHistory` (page) → `add_transcript` Rust → push to history.
|
||||
- `MicroSteps` "start timer" button → `magnotia:start-timer` → `focusTimer` store transitions → `FocusTimer` component renders ring.
|
||||
- `TasksPage.addTask` → `tasks` store → emits `magnotia:task-completed/...` → `completionStats` refreshes → `CompletionSparkline` re renders.
|
||||
- `SettingsPage` accessibility toggle → `updatePreferences` → DOM write + `save_preferences` invoke + `magnotia:preferences-changed` emit → secondary windows mirror.
|
||||
- Float window settings sync uses `localStorage` `storage` event, not the Tauri preference event. Drift candidate.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- `page.svelte.ts` is doing a lot. Splitting transcripts, tasks, task lists, settings, profiles into separate stores would help but is outside this map's job.
|
||||
- `settings` and `preferences` overlap (theme, font size, `transcriptSize`). Settings are localStorage only; preferences are Tauri persisted. Race possible during the migration `$effect`.
|
||||
- `nudgeBus` and `implementationIntentions` both run timers. They are torn down in `+layout.svelte` `onDestroy`. Only the main window starts them.
|
||||
- `completionStats` listens on the DOM `window` for events; it does not subscribe to `tasks` directly. Adding a new task mutation that does not emit one of the `magnotia:task-*` events will silently break the sparkline.
|
||||
|
||||
## See also
|
||||
|
||||
- [Components](components.md). Consumers.
|
||||
- [Frontend ↔ Tauri bridge](frontend-tauri-bridge.md). The `invoke` and `emit` calls per store.
|
||||
- [Actions, utils, types](actions-utils-types.md). `settingsMigrations`, `errors`, `storage`, `time`, `osInfo`, `runtime` helpers used by stores.
|
||||
119
docs/architecture-map/01-frontend/windows-and-routes.md
Normal file
119
docs/architecture-map/01-frontend/windows-and-routes.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: Windows and SvelteKit routes
|
||||
type: architecture-map-page
|
||||
slice: 01-frontend
|
||||
last_verified: 2026/05/09
|
||||
---
|
||||
|
||||
# Windows and routes
|
||||
|
||||
> **Where you are:** [Architecture map](../README.md) → [Frontend](README.md) → Windows and routes
|
||||
|
||||
**Plain English summary.** Magnotia is a single SvelteKit build that runs as multiple desktop windows. The Rust side opens four webviews (main, tasks float, transcript viewer, transcription preview) and points each one at a different SvelteKit route. The frontend uses SvelteKit's `+layout@.svelte` "break" trick so secondary windows skip the main shell (sidebar, titlebar, toast viewport) and render a focused, single purpose UI.
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Path:** `src/routes/`
|
||||
- **LOC:** 2 740 across the route tree.
|
||||
- **Key files:**
|
||||
- `src/routes/+layout.js:5`. Disables SSR. Adapter is `adapter-static` with `index.html` fallback (`svelte.config.js:6`). The whole tree is SPA only.
|
||||
- `src/routes/+layout.svelte`. The shell. Sidebar, custom titlebar (non Linux only), toast viewport, focus timer overlay, morning triage modal, resize handles, global hotkey wiring, OS detection, profile load, first run gate, error capture.
|
||||
- `src/routes/+page.svelte`. The main window's page switch (`page.current` → 7 page modules).
|
||||
- `src/routes/float/+layout@.svelte`. "Break" layout for the tasks float window.
|
||||
- `src/routes/float/+page.svelte`. Tasks float window UI.
|
||||
- `src/routes/viewer/+layout@.svelte`. Break layout for the transcript editor window.
|
||||
- `src/routes/viewer/+page.svelte`. Transcript editor with audio playback.
|
||||
- `src/routes/preview/+layout@.svelte`. Break layout for the live transcription preview overlay.
|
||||
- `src/routes/preview/+page.svelte`. Preview state machine: listening → live → cleanup → final.
|
||||
- **Imports:**
|
||||
- Internal: every store, plus shell components (`Sidebar`, `TaskSidebar`, `Titlebar`, `ToastViewport`, `ResizeHandles`, `FocusTimer`, `MorningTriageModal`).
|
||||
- External: `@tauri-apps/api/core` (invoke, Channel, convertFileSrc), `@tauri-apps/api/event` (listen, emit), `@tauri-apps/api/window` (getCurrentWindow), `@tauri-apps/plugin-global-shortcut`, `@tauri-apps/plugin-dialog`, `lucide-svelte`.
|
||||
- **Used by:** Tauri (slice 02). Window labels are `main`, `tasks-float`, `transcript-viewer`, `transcription-preview`. Each is created with the matching route URL.
|
||||
|
||||
## What's in here
|
||||
|
||||
### `src/routes/+layout.js` (5 LOC)
|
||||
|
||||
Single line: `export const ssr = false;`. Tauri has no Node server, so SvelteKit must run as a SPA.
|
||||
|
||||
### `src/routes/+layout.svelte` (493 LOC)
|
||||
|
||||
The shell. Mounts on every window (the secondary windows then render only `{@render children()}` and suppress the chrome).
|
||||
|
||||
Responsibilities:
|
||||
- Initialise svelte-i18n (`initI18n()` is idempotent across windows, `+layout.svelte:38`).
|
||||
- Detect Tauri runtime (`hasTauriRuntime`) and OS (`loadOsInfo`). Linux uses native KWin/Mutter decorations; macOS and Windows render the custom `Titlebar` plus invisible `ResizeHandles`. Default `useCustomChrome = false` to avoid a flash on Linux (`+layout.svelte:49`).
|
||||
- Hotkey backend selection. On Wayland, attempt the evdev backend (`check_hotkey_access`). Otherwise fall back to `tauri-plugin-global-shortcut`. Falls back to "unavailable" if neither path works (`+layout.svelte:76-102`).
|
||||
- Hotkey registration. Only the `main` window owns the global shortcut. The function debounces evdev autorepeat at 120 ms (Handy issue #1143 referenced in comments, `+layout.svelte:171-186`).
|
||||
- Cross window listeners: `magnotia:hotkey-pressed` (evdev path), `magnotia:open-wind-down` (tray menu), `magnotia:preferences-changed` (sync prefs across windows). Apply external preferences if the source label differs from our own.
|
||||
- Theme migration `$effect`. Reads `settings.theme` (legacy "Light" / "Dark" / "System") and writes `preferences.theme` ("light" / "dark" / "system"). See README debt note 2.
|
||||
- Font size CSS variable `--font-size-transcript` set on `<body>` from `settings.fontSize`.
|
||||
- Global error capture. `window.onerror` and `unhandledrejection` forward to `log_frontend_error` Rust command. Best effort, swallow throws.
|
||||
- First run check on mount. If `list_models` and `list_parakeet_models` both return empty arrays, switch `page.current` to `"first-run"`.
|
||||
- Background update check via `check_for_update`. Toast on result.
|
||||
- Pre warm default model on mount if `settings.prewarmModelOnStartup` (calls `prewarm_default_model_cmd`).
|
||||
- Meeting auto capture poller (`$effect`). When `settings.meetingAutoCapture` is true, polls `detect_meeting_processes` every 15 s with the configured patterns (default `["zoom", "teams"]`). Edge triggered (toast only on first match per session). Tear down on effect re run.
|
||||
- Sidebar collapse heuristic. `[` toggles, narrow viewport collapses automatically.
|
||||
- Render branches: `isSecondaryWindow` (URL starts with `/float` or `/viewer`) renders only children. Otherwise: optional Titlebar, sidebar (hidden on first run), main slot, optional task sidebar (when `page.taskSidebarOpen`).
|
||||
- Always rendered alongside: `<ToastViewport />`, `<FocusTimer />`, `<MorningTriageModal />`, `<ResizeHandles />` (custom chrome only).
|
||||
|
||||
### `src/routes/+page.svelte` (33 LOC)
|
||||
|
||||
Pure dispatcher. Switches the seven page modules from `page.current`:
|
||||
- `first-run` → `FirstRunPage`
|
||||
- `dictation` → `DictationPage`
|
||||
- `files` → `FilesPage`
|
||||
- `tasks` → `TasksPage`
|
||||
- `history` → `HistoryPage`
|
||||
- `settings` → `SettingsPage`
|
||||
- `shutdown` → `ShutdownRitualPage`
|
||||
|
||||
Also redirects the legacy `page.current === "profiles"` to `"settings"` via `$effect` (debt note 5).
|
||||
|
||||
### `src/routes/float/+layout@.svelte` (99 LOC)
|
||||
|
||||
The `@.svelte` suffix tells SvelteKit to skip parent layouts. Reimports `app.css`, mounts `Titlebar` (non Linux) and `FocusTimer`, applies the same theme migration `$effect`, listens on the browser `storage` event (not Tauri events) to sync `settings` from main window writes, and handles the `task-window-focus` Tauri event to glow and auto focus the quick add input.
|
||||
|
||||
### `src/routes/float/+page.svelte` (481 LOC)
|
||||
|
||||
Tasks float window. Self contained quick add, list filter, sort menu, completed toggle, list management (rename, delete, reorder, move to profile), drag and drop reordering. Reads tasks straight from the `page.svelte.ts` store helpers. No `invoke()` calls of its own; mutations go via `addTask`, `completeTask`, etc, which then call Rust.
|
||||
|
||||
### `src/routes/viewer/+layout@.svelte` (77 LOC)
|
||||
|
||||
Same break layout pattern. Mounts `Titlebar`, `ToastViewport`, applies preferences sync via `magnotia:preferences-changed`, applies theme.
|
||||
|
||||
### `src/routes/viewer/+page.svelte` (606 LOC)
|
||||
|
||||
Transcript editor. Hydrates from a transcript ID handed off via `localStorage` (`magnotia:viewer-handoff` style key, see file). Fetches the full row from SQLite via `get_transcript`. Renders an `<audio>` element using `convertFileSrc()` to map the saved audio path to a webview URL. Provides per segment editing, debounced autosave through `saveTranscriptMeta`, virtual scrolling via `VirtualSegmentList`, playback speed control (`PLAYBACK_SPEEDS`), starring and tagging.
|
||||
|
||||
### `src/routes/preview/+layout@.svelte` (68 LOC)
|
||||
|
||||
Mounts only what the overlay needs: theme, preferences sync. No sidebar, no titlebar, no toast viewport.
|
||||
|
||||
### `src/routes/preview/+page.svelte` (274 LOC)
|
||||
|
||||
Live transcription overlay. Listens for `preview-listening`, `preview-cleanup`, `preview-hide` (and reads partial text via the same `Channel` pattern as Dictation, in some flows). State machine: `listening → live → cleanup → final → auto hide`. Provides a copy button (uses `navigator.clipboard.writeText` first, falls back to `copy_to_clipboard` invoke) and a revert to raw button. Auto hide timer constants live at the top of the file.
|
||||
|
||||
## Data flow
|
||||
|
||||
- Window creation: Rust opens four webviews and routes them to `/`, `/float`, `/viewer`, `/preview`. The same JS bundle loads in each.
|
||||
- Cross window state:
|
||||
- `localStorage` carries `magnotia_settings` and the viewer handoff payload. Float window listens on the browser `storage` event to mirror settings.
|
||||
- Tauri events carry preferences (`magnotia:preferences-changed`), tray driven navigation (`magnotia:open-wind-down`), and float window focus (`task-window-focus`).
|
||||
- Hotkey: only the main window registers, but every window's layout includes the migration `$effect`. The label guard at `+layout.svelte:115-121` prevents secondary windows from re registering.
|
||||
- First run gating: only the main window evaluates and sets `page.current = "first-run"`. The float/viewer/preview windows skip the shell altogether.
|
||||
|
||||
## Watch outs
|
||||
|
||||
- The `useCustomChrome` flash is mitigated by defaulting to `false` on Linux. If you ever flip the default, expect a one frame flash of custom chrome before `loadOsInfo()` resolves.
|
||||
- The break layouts duplicate the theme migration `$effect`. Touch one, touch all. Same for `applyExternalPreferences` listeners.
|
||||
- `transcription-preview` is a borderless overlay with `WindowTypeHint = Utility`. Layout assumptions about chrome height do not apply.
|
||||
- `+layout.svelte` calls `applyExternalPreferences` only after a payload `source` check. If you ever add a fifth window, its label must be propagated correctly or the window will echo its own preference writes.
|
||||
- Drag drop on `FilesPage.svelte` listens on `tauri://drag-drop` events, which require the window to declare drag drop in `tauri.conf.json`. Slice 02 owns that surface.
|
||||
|
||||
## See also
|
||||
|
||||
- [Pages overview](pages-overview.md). What `page.current` ends up rendering.
|
||||
- [Frontend ↔ Tauri bridge](frontend-tauri-bridge.md). Every command and event referenced above.
|
||||
- [App shell and styling](app-shell-and-styling.md). The CSS plumbing that the shell relies on.
|
||||
- [../02-tauri-runtime/README.md](../02-tauri-runtime/README.md). Window creation, tray, and the matching event surface.
|
||||
Reference in New Issue
Block a user