db9c50c5485152bf656e7a046ddbb0e2ee4db566
490 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| db9c50c548 |
v0.3 Phase 3: status grammar — LumotiaNotice left-bar refactor
Phase 3 audit found that both LumotiaStatusPill and LumotiaNotice
already shipped in v0.2 Phase 5. Phase 3 reduced to an audit-and-adapt
pass.
LumotiaStatusPill ($lib/components/StatusPill.svelte) is already
correct for quietware. Pattern: neutral pill background + neutral
label text + role colour confined to a 6x6 px dot. Colour is
supplementary, label is always perceivable. Works unchanged across
all three quietware modes. No changes shipped.
LumotiaNotice ($lib/ui/LumotiaNotice.svelte) refactored to the
left-bar accent pattern. Previously the role colour rendered on the
icon outline and on the entire surround border at 30% opacity; in
quietware light mode that left the bright #FFCD00 caution colour
faded against cream paper. New pattern:
- 4-px solid LEFT border in the tone colour, 100% opacity.
- Soft tone-tinted background (10% opacity) for ambient cue.
- Subtle neutral outer border on the other three sides.
- Icon, title and body text all use --color-text.
Visible signal lives on the large bar (works at any contrast level);
foreground text uses the always-legible neutral. Matches Material
Design "banner with leading accent" and IBM Carbon "inline
notification" guidance. ARIA semantics (role="alert" for danger,
role="status" for the rest) preserved.
Design-system-v2 preview route already imports both primitives. The
updated Notice renders correctly with the existing preview content;
no additional wiring required.
Caution remains a fill-only convention on cream by design. Any
darkened yellow reads as olive ("looks like poop", per Jake), so the
fill-only rule stays in force and this pattern delivers it cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| a9972f76bb |
v0.3 Phase 2: build flag wiring
Wires VITE_LUMOTIA_QUIETWARE=1 to set <html data-design="quietware"> via a $effect in src/routes/+layout.svelte. Without the flag the runtime app behaves exactly as v0.2; with the flag the v0.3 token overrides in src/design-system/v0.3-quietware-tokens.css activate. Hot-reload safety: if the env var is unset between dev rebuilds, the attribute is cleared so devs do not see a stuck quietware surface. Activation paths: VITE_LUMOTIA_QUIETWARE=1 npm run dev (development) VITE_LUMOTIA_QUIETWARE=1 npm run build (production) Or manually via dev tools: <html data-design="quietware"> Combine with data-theme="light" (existing v0.2 theme wiring) and data-contrast="high" (existing v0.2 contrast wiring) to reach the light and high-contrast quietware modes. Smallest possible PR. No new dependencies. No bundle-size impact in the off path. No component changes. Plan doc updated to mark Phase 2 as landed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 7075c205fc |
v0.3 Phase 1: palette amendment to color.adobe.com primaries
Third palette iteration. Source: Jake's color.adobe.com primaries
attached to the Phase 1 review reply 2026-05-15:
#FF0700 red -> --color-danger
#000AFF blue -> --color-info
#00FF56 green -> --color-success
#FFCD00 yellow -> --color-caution
Each primary is used literally where it clears WCAG AA on the
relevant background. Where it does not, a text-safe sibling at the
same hue and saturation is derived.
LIGHT MODE on cream paper
--info #000AFF Jake literal 7.96 AAA
--danger #E60600 sibling 4.51 AA
--success #00852D sibling 4.51 AA
--caution #FFCD00 Jake literal 1.42 (fill-only by convention)
DARK MODE on lifted Coffee Bean #2A1620
--info #7076FF lifted from blue 4.64 AA
--danger #FF2A24 lifted from red 4.54 AA
--success #00FF56 Jake literal 12.51 AAA
--caution #FFCD00 Jake literal 11.33 AAA
HIGH CONTRAST on black, S=80% L=70%
--info #757AF0 5.80 AA
--danger #F07975 7.70 AAA
--success #75F09F 14.72 AAA
--caution #F0D875 14.78 AAA
Caution remains a fill-only convention on cream. Any darkened yellow
reads as olive or mustard, which Jake refused at the round-2 stage
(see capture 2026-05-15-re-lumotia-v03-renders-revised-palette-).
No component changes. No behavioural changes. Phase 1 still inert
without html[data-design="quietware"]. Supersedes the cobalt-pegged
square palette in commit
|
|||
| f718aded16 |
v0.3 Phase 1: Tactile Quietware tokens
Foundation for the v0.3 Tactile Quietware release. Tokens only. No
component refactors, no layout changes, no behavioural shifts. The new
palette and typography activate only when html[data-design="quietware"]
is set; the runtime v0.2 surface is unaffected without the attribute.
What landed.
- Three-mode palette (light, dark, high-contrast) at
src/design-system/v0.3-quietware-tokens.css. Strict 90deg square
colour harmony pegged at cobalt blue #0047AB. Hues: 215 / 305 / 35
/ 125. Saturation locked at 100%. Lightness adjusted per role for
WCAG AA 4.5:1 on the relevant background; computed values
documented in the plan doc.
- V4 font stack self-hosted as WOFF2:
Work Sans (variable, roman + italic) for body, UI, controls.
Young Serif for brand moments only.
JetBrains Mono stays via existing src/fonts/jetbrains-mono.woff2.
- Import wired in src/app.css directly after the tailwind import.
The new tokens are inert until data-design="quietware" is set on
<html>.
- Reduced-motion override scoped to quietware so prefers-reduced-motion
actually bites inside the new design system from day one.
- Plan doc at docs/release/v0.3-tactile-quietware.md with the full
seven-phase plan (Phase 1 logged), four high-priority persona test
gates (Pawel, Simone, Chris, Ron), and the Phase 0 gov.uk DfE
accessibility tools checklist.
Activation paths.
- Manual now: dev tools, set <html data-design="quietware">.
- Phase 2 will wire VITE_LUMOTIA_QUIETWARE=1 through +layout.svelte.
Verified.
- npm run check produced 26 errors but all 26 are pre-existing in
files this PR does not touch (LumotiaTooltip.svelte missing
bits-ui import; tests/e2e/smoke.spec.ts missing playwright types).
Tracked separately, not blocking v0.3 Phase 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
|
|
eecedbdecd |
Merge pull request #13 from jakejars/feat/v0.2-frontend-overhaul
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
audit / cargo audit (push) Has been cancelled
audit / npm audit (push) Has been cancelled
Feat/v0.2 frontend overhaul |
||
|
|
eab9ed0073 | Merge branch 'main' into feat/v0.2-frontend-overhaul | ||
| 7f933f3ca2 |
v0.2 UI capture: scripts/capture-v0.2-screenshots.mjs
Spins up `npm run dev:frontend` (Vite without Tauri), drives Playwright
Chromium at 1440x900, and writes a PNG per UI surface to
/home/jake/lumotia-v0.2-screenshots/.
Surfaces captured (16 total):
01 Dictation (default)
02 Files
03 Tasks
04 History
05 Settings
06 Dictation × dark/light × cave/energy/reset (6 surface sets)
07 /float — frame-less task panel
08 /viewer — transcript viewer
09 /preview — Wayland-hardened transcription overlay
10 /design-system-v2 — internal primitives showcase
The design-system-v2 route is gated by VITE_LUMOTIA_DESIGN_SYSTEM_V2=1
per Phase 5; the screenshot run picks this up via a local-only
`.env.local` (now gitignored). shootRoute() waits on a Lumotia
text node before screenshotting so the 11-primitive showcase has time
to hydrate.
Browser-preview-only console noise ("transformCallback") is expected:
some Tauri-only code paths still call invoke() during initial render
even with hasTauriRuntime() gates upstream. No effect on the
captured surfaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| f03f8a01b0 |
v0.2 Phase 8: full release gate — all checks green
cargo fmt --check ✓ cargo clippy --workspace --all-targets -- -D warnings ✓ cargo test --workspace ✓ cargo nextest run --workspace ✓ 435/435 npm run check ✓ 0/0/5704 files npm test ✓ 13/13 (2 files) npm run test:browser ✓ 3/3 in Chromium npm run test:e2e ✓ 16/16 × 2 viewports npm run analyze ✓ reports/bundle-stats.html (1.7 MB) scripts/dogfood-rebrand-drill.sh ✓ 8/8 (sandbox) npm run guard:no-skeleton ✓ clean Two small Phase 8 corrections landed alongside the gate: vite.config.js: the jsdom suite was picking up `src/lib/ui/*.browser.test.ts`, which only works under the @vitest/browser-playwright provider. Added the browser-suffix glob to the jsdom suite's exclude list so the two runners stop double-running the same files. playwright.config.ts: bumped the global `expect.timeout` to 15 s. The cold first-compile of the Vite SPA tree was exceeding Playwright's default 5 s `toBeVisible` timeout on the 900x700 project on dogfood runs. The 1440x900 project (which runs second after a warm cache) never hit it. Phase 8 closes Phase-7 page migrations. Branch is ready for the finishing-a-development-branch handoff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| ba851680ce |
v0.2 Phase 7.8 / 7.9 / 7.10: secondary windows — float / viewer / preview
Combined commit for the three secondary windows. Each +layout@.svelte
was already migrated in Phase 3 KI-05 (the legacy theme-sync $effect
was deleted). The +page.svelte content for each window is explicitly
bespoke per docs/release/v0.2-frontend-overhaul.md §6.3:
/float — Lumotia-To-do panel: list pills, drag-and-drop between
lists, context menus, pin-on-top, custom titlebar drag
region. No Card/EmptyState/Toggle wrappers apply.
/viewer — Transcript viewer with audio player, segment scrubbing,
speaker labels. Bionic-reading action + per-region
accessibility typography are load-bearing. No wrappers.
/preview — Wayland-hardened transcription preview overlay.
WindowTypeHint::Utility, never steals focus, hidden
from Alt+Tab. The plan's hard rule "/preview uses zero
portaled primitives" is honoured — no LumotiaDialog,
LumotiaSelect, LumotiaCombobox, LumotiaTooltip, or
LumotiaMenu (all of which carry Bits UI Floating-UI
portals that target document.body).
Phase 7 closes here. All 10 sub-phases complete; the per-page gate
ran green after each (check 0/0). Full Phase 8 gate fires next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| b5f622f128 |
v0.2 Phase 7.7: SettingsPage — wrapper sweep via import-only swap
SettingsPage is 2 791 LOC with ~95 form controls. The plan called for section-by-section migration with selective Formsnap; the wrapper aliases land Phase 4 made that unnecessary on the per-section level. Because LumotiaCard / LumotiaToggle / LumotiaSettingsGroup / LumotiaStatusPill keep the exact prop API of the underlying components, all that's needed to migrate every existing markup site is repointing the four local import names: Card → $lib/ui/LumotiaCard.svelte Toggle → $lib/ui/LumotiaToggle.svelte SettingsGroup → $lib/ui/LumotiaSettingsGroup.svelte StatusPill → $lib/ui/LumotiaStatusPill.svelte The 90+ <Card>, <Toggle>, <SettingsGroup>, <StatusPill> usages compile unchanged because the local symbols still resolve to compatible components. Existing IA is preserved verbatim — section ordering, SegmentedButton bindings, HotkeyRecorder, ZonePicker, ModelDownloader, and the Phase 3 KI-05 theme bindings all stay in place. Formsnap is intentionally NOT pulled into SettingsPage in v0.2. The form here is a wide tree of independent settings; Superforms + Formsnap would force a heavyweight schema layer for no observable validation win. Per-page gate: npm run check (0/0/5704 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 4fa8df638b |
v0.2 Phase 7.6: DictationPage — centrepiece wrapper sweep
This is the page Settings will inherit grammar from, so it's worth a
slightly fuller sweep than the earlier ones — without touching the
recording state machine, the SVG VisualTimer, the waveform bars, the
transcript textarea, ModelDownloader, or SpeakerButton, which are all
bespoke per the §6.3 do-not-wrap list.
- StatusPill import → LumotiaStatusPill (all use sites swapped)
- PostCaptureCard import → LumotiaPostCaptureCard
- Card import → LumotiaCard
- EmptyState import → LumotiaEmptyState
- LumotiaNotice import added; the inline `liveWarning` panel now
uses LumotiaNotice tone=caution
The danger-tinted error block (lines ~1130) was left verbatim — it
already nests a StatusPill (now LumotiaStatusPill), a <details>
disclosure, and a Dismiss button in a structure LumotiaNotice's
single-icon contract doesn't model cleanly. Phase 7's primary
output for this page is import-level coherence; behavioural
identity stays untouched.
Bespoke surfaces preserved: recording controls, VisualTimer, waveform,
transcript surface (bionic action + accessibility typography), all
hotkey wiring, all live-session state.
Per-page gate: npm run check (0/0/5704 files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 021a5fc196 |
v0.2 Phase 7.5: HistoryPage — wrapper sweep
Targeted migration on a 1 225-LOC page. The FTS5 search input stays a plain <input> (LumotiaCombobox needs an options list; free-text search doesn't fit the API cleanly enough to justify a rewrite for v0.2). Row patterns + clear-all modal stay verbatim — their bespoke ARIA and inline arm-confirm state are core to the page's identity. - Card import → LumotiaCard (4 use sites bulk-swapped) - EmptyState import → LumotiaEmptyState (4 use sites) - LumotiaButton import added for selective use in follow-up sweeps Bespoke surfaces left verbatim: VirtualSegmentList, audio player, clear-all type-the-word modal, tag-chip filter bar. Per-page gate: npm run check (0/0/5704 files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| b6c065ffd1 |
v0.2 Phase 7.4: TasksPage — minimal wrapper sweep
Targeted migration per plan ("wrap, don't rewrite"). The page's
identity surfaces (energy chips, search input, quick-capture input,
bucket tabs, WipTaskList) stay verbatim — their rich ARIA and custom
radio-group semantics outweigh wrapper coherence here.
- Dead Card import removed (never used in markup)
- EmptyState → LumotiaEmptyState
- "Pop out" toolbar button → LumotiaButton variant=tertiary
WipTaskList, CompletionSparkline, EnergyChip stay bespoke per
docs/release/v0.2-frontend-overhaul.md §6.3.
Per-page gate: npm run check (0/0/5704 files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| cb69772f4e |
v0.2 Phase 7.3: FirstRunPage — wrapper sweep
Migrated the onboarding step cluster's ad-hoc button pairs to the new grammar. Skip-link tertiary buttons stay as plain <button> with underline because they're intentionally low-emphasis (text-only). - Step CTA buttons (primary + secondary) → LumotiaButton variants - Autostart "Saving…" pair → LumotiaButton loading + disabled - Error notice → LumotiaNotice tone=danger with body content snippet - Download progress bar → LumotiaProgress Bespoke surfaces left verbatim: model-pick cards (rich content tiles with Recommended/Downloaded pills), UnicodeSpinner, and the test-recording quote-block. Per-page gate: npm run check (0/0/5704 files). e2e baseline untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| d812410039 |
v0.2 Phase 7.2: FilesPage — wrapper sweep
Migrated FilesPage chrome to the new grammar; the drop-zone affordance and transcript textarea (the page's identity surfaces) stay verbatim. - Card import → LumotiaCard - EmptyState import → LumotiaEmptyState - "Browse Files" filled button → LumotiaButton variant=primary size=lg - Bottom "Copy" / "Export" toolbar → LumotiaButton variant=tertiary - Custom export dropdown → LumotiaMenu (Bits UI DropdownMenu) - Inline danger error → LumotiaNotice tone=danger - Custom progress bar → LumotiaProgress Banks the LumotiaField + LumotiaNotice patterns the plan called out; Field stays on the textarea (transcript surface is intentionally naked inside the card per brand spec). Per-page gate: npm run check (0/0/5704 files). Vitest / browser-mode / e2e baselines unchanged (no behaviour change to the smoke surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3614c94885 |
v0.2 Phase 7.1: ShutdownRitualPage — wrapper sweep
Migrated the two ad-hoc buttons to wrappers; everything else (display- only reflective copy, the open-loops list, the Newport shutdown template) stays verbatim since the page is intentionally low-grammar. - Back-arrow button → LumotiaIconButton (icon=ArrowLeft, size=sm) - "Close" button → LumotiaButton variant=primary Bespoke: none on this page (no recording state, no transcript surface). Per-page gate: npm run check (0/0/5704 files). Vitest / browser-mode / e2e baselines stay green (no behaviour change). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| a9733544c0 |
v0.2 Phase 6: shell split — AppRuntime / AppChrome / AppOverlays
src/routes/+layout.svelte was 537 LOC of mixed runtime, chrome and overlay concerns. Split into three single-purpose shells under src/lib/shell/, with +layout.svelte reduced to ~28 LOC of pure composition. AppRuntime (no DOM beyond <svelte:window>): - Global hotkey dual backend (evdev / tauri-plugin-global-shortcut) - 120ms hotkey debounce (sacred behaviour §5 #2) - PREFERENCES_CHANGED_EVENT listener (sacred §5 #4) - KI-05 one-shot legacy-theme migration - Sidebar hotkeys: [ toggle, Ctrl+K, Ctrl+, (sacred §5 #10) - Wind-down tray listener - Meeting auto-capture poller - Global frontend error capture - Nudge bus + implementation intentions lifecycle - Font-size CSS var $effect - Window resize → sidebar auto-collapse - Onboarding/first-run check + update check + LLM status warm-up AppChrome (the visual shell): - Titlebar (OS-aware via customChrome helper) - Sidebar (recording-state-aware — sacred §5 #1 stays in Sidebar.svelte verbatim) - Main slot - TaskSidebar conditional rail AppOverlays (mounted-once globals): - ToastViewport - FocusTimer - MorningTriageModal - ResizeHandles (OS-gated) src/lib/utils/customChrome.svelte.ts holds the single source of truth for useCustomChrome, a module-level $state both AppChrome and AppOverlays subscribe to. Each only ever sees one loadOsInfo() call between them. Secondary windows still escape via their own +layout@.svelte; the defensive isSecondaryWindow check in +layout.svelte stays so a direct /float, /viewer, /preview navigation through the root layout also drops the chrome. Phase 6 per-page gate green: npm run check (0/0/5704 files), npm test, npm run test:browser (3/3), npm run test:e2e (16/16). No regressions in the Phase 1 smoke baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| c60f0aa5a5 |
v0.2 Phase 5: 11 primitives + gated design-system-v2 preview
Custom-styled primitives (no headless dep): LumotiaButton — primary/secondary/tertiary/destructive × sm/md/lg LumotiaIconButton — square icon-only; ghost/filled/destructive LumotiaNotice — info/caution/danger/success inline notice LumotiaProgress — native <progress> + token theming LumotiaField — plain + Formsnap modes share the same markup Bits UI 2.18.1 wrappers (warm-brutalist styling): LumotiaSelect — single-select, options=[] LumotiaCombobox — searchable; one-way inputValue + oninput LumotiaDialog — controlled open; closable + footer snippet LumotiaTabs — orchestrates List/Trigger/Content from a tabs array LumotiaTooltip — wraps Provider + Root + Trigger + Content LumotiaMenu — DropdownMenu items=[] with destructive variant design-system-v2 preview route: src/routes/design-system-v2/+page.ts gates with VITE_LUMOTIA_DESIGN_SYSTEM_V2=1. Without the flag the load() throws 404 — route-level gate, not nav- hidden. Run via VITE_LUMOTIA_DESIGN_SYSTEM_V2=1 npm run dev:frontend to see the showcase. Browser-mode component test: src/lib/ui/LumotiaButton.browser.test.ts. Covers render, click, and disabled-blocks-click. Validates that vitest-browser-svelte + the @vitest/browser-playwright provider land Phase 1's tooling contract end-to-end. 3/3 passing in Chromium. Type fix: LumotiaIconButton and LumotiaMenu accept icon: any so lucide-svelte's legacy SvelteComponentTyped shape composes with our Svelte 5 wrappers without forcing a // @ts-nocheck escape hatch on every call site. Tightens to Component<…> once lucide-svelte ships a Svelte 5 build. Type fix: LumotiaCombobox honours Bits UI 2.x Combobox.Root's one-way inputValue contract. The wrapper drops bind:inputValue and exposes an oninput callback so caller-owned filter pipelines (HistoryPage FTS5, ModelDownloader) can drive options upstream. Phase 5 per-page gate green: npm run check (0/0/5700 files), npm test, npm run test:browser (3/3 in Chromium), npm run test:e2e (16/16), guard-no-skeleton clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 8c9708a508 |
v0.2 Phase 4: wrapper alias layer (src/lib/ui/)
Six thin alias wrappers, same prop APIs as the underlying components. Lets pages migrate imports from $lib/components/* to $lib/ui/* one file at a time without touching markup, and gives Phase 5+ a place to tighten grammar without churning every call site. LumotiaCard → Card.svelte LumotiaStatusPill → StatusPill.svelte LumotiaToggle → Toggle.svelte (forwards bind:checked, bind:loading) LumotiaSettingsGroup → SettingsGroup.svelte (typed Props for svelte-check) LumotiaEmptyState → EmptyState.svelte LumotiaPostCaptureCard → PostCaptureCard.svelte Per the plan, the underlying components in src/lib/components/ are untouched. They get retired during the per-page migrations in Phase 7 once no consumer remains. LumotiaSettingsGroup mirrors the underlying Props interface explicitly because Svelte 5's spread-into-typed-component caught a real missing- `title` error during svelte-check. The mirrored interface keeps call sites type-safe when importing via $lib/ui/. Phase 4 per-phase gate green: npm run check (0/0/4135 files), npm test (all green), npm run test:e2e (16/16), npm run guard:no-skeleton (clean). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 66e25aa778 |
v0.2 Phase 3: additive semantic tokens + KI-05 resolution
Additive token grammar (no renames, no replacements):
- --color-caution (dark #e8be4a, light #a08a1f) becomes the canonical
name for tuned-amber notice surfaces in the new wrapper grammar
- --color-warning is kept as a CSS var() alias of --color-caution so
every existing text-warning / bg-warning call site stays valid
- --color-info (dark #7a9ec0, light #3d6a8a) is the soft blue-grey
signal for the new LumotiaNotice info variant
- --color-accent-environment (dark #8fae9a, light #4a7058) is an
optional sage/moss support token for empty-state illustrations
and environment-neutral status dots. NOT a brand swap — amber/
copper --color-accent stays primary
Mirrored in src/design-system/colors_and_type.css (the buildless
preview pages bypass Tailwind so the duplication is intentional).
KI-05 resolved in the same commit, per the plan:
- src/lib/types/app.ts: drop `theme` from SettingsState
- src/lib/stores/page.svelte.ts: drop `theme: "Dark"` from defaults
- src/routes/+layout.svelte: drop the migration $effect, add a
one-shot migrateLegacyTheme() on mount that copies any historical
lumotia_settings.theme into preferences.theme and strips the
legacy field. Idempotent — subsequent loads short-circuit
- src/routes/{float,viewer,preview}/+layout@.svelte: drop the same
$effect; secondary windows inherit theme via PREFERENCES_CHANGED_EVENT
- src/lib/pages/SettingsPage.svelte: both SegmentedButton bindings
(quick-settings row at :1272, Appearance group at :2606) now use
Svelte 5 function bindings ({ get, set }) backed by prefs.theme
and updatePreferences. No SegmentedButton API change
Phase 3 per-page gate green: npm run check (0/0/4129 files),
npm test (13/13), npm run test:e2e (16/16). No regressions in
the Phase 1 smoke baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 94e6a79515 |
v0.2 Phase 2: install Bits UI / Formsnap / Superforms / Zod / @internationalized/date
Exact-pinned per the plan: bits-ui@2.18.1 — headless Svelte 5 primitives formsnap@2.0.1 — field+label+error wrapper sveltekit-superforms@2.30.1 — form state + validation zod@4.4.3 — schema validation (superforms accepts ^3.25 || ^4) @internationalized/date@3.12.1 — bits-ui peer dep Phase 2 gate green: npm audit signatures (273 verified registry sigs + 93 attestations), npm run check (clean), npm test (clean), npm run test:e2e (16/16). No regressions in the Phase 1 smoke baseline. @chenglou/pretext audit (plan risk item): kept — still referenced in src/lib/utils/textMeasure.ts and src/lib/shims.d.ts. No primitives wired yet — that's Phase 5. This commit only puts the headless layer on disk so Phase 4/5 can import from it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 100e04fa70 |
v0.2 Phase 0+1: planning doc + tooling baseline
Phase 0 — docs/release/v0.2-frontend-overhaul.md as single source of truth for the v0.2 frontend coherence pass. Records hard rules, tooling pins, sacred-behaviour contract list, wrapper catalogue, per-page migration order, verification matrix, KI-05 plan, and the explicit "DO NOT add Skeleton" line. Phase 1 — tooling baseline. All exact-pinned per the plan: - @playwright/test@1.60.0 + playwright@1.60.0 + @axe-core/playwright@4.11.3 - rollup-plugin-visualizer@7.0.1 (wired behind ANALYZE=1) - @vitest/browser@4.1.6 + @vitest/browser-playwright@4.1.6 (provider) - vitest-browser-svelte@2.1.1 (runes-aware Svelte 5 bridge) - cargo-nextest installed globally New configs: playwright.config.ts (frontend-only, dev:frontend webServer, 900x700 + 1440x900 projects, visual baselines deferred), vitest.browser.config.js (separate from jsdom suite). New scripts: test:e2e, test:e2e:ui, test:browser, analyze, test:rust:fast, guard:no-skeleton. guard-no-skeleton.mjs walks package.json + package-lock + src/ for any @skeletonlabs reference and exits 1 if found — locks in the no-Skeleton hard rule for any future agent. Smoke baseline (tests/e2e/smoke.spec.ts): 16 tests passing across two viewports — app loads without Tauri runtime, keyboard nav, axe scan (color-contrast deferred to Phase 7 per docs/release/v0.1-contrast-audit.md), light/dark theme cycle, all three sensory zones (cave/energy/reset). 10 screenshots emitted to test-results/ as non-failing artefacts. Surfaced + fixed two browser-preview bugs while landing the baseline: - src/lib/utils/osInfo.ts: FALLBACK_BROWSER_INFO was evaluated at SSR module-load (navigator undefined → os: 'unknown'), and the UA check ran before navigator.platform — so Playwright's Windows-UA Chromium on a Linux runner detected as Windows, useCustomChrome went true, Titlebar mounted and tripped Tauri-only APIs. Now lazy-built per call; platform is the primary signal, UA only a fallback. - src/lib/components/Titlebar.svelte: defensive hasTauriRuntime() guard on every handler and the $effect. Titlebar should not crash if any future code path mounts it without Tauri. .gitignore: reports/, .playwright/, test-results/, playwright-report/. Per-phase gate green: npm run check (0/0), npm test (0/0), npm run test:e2e (16/16), npm run guard:no-skeleton (clean). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3770815fbf |
agent: lumotia — v0.1 release-completion run
Closes the code-side v0.1 ship gate. All quality gates green: cargo fmt/clippy/test (~327 tests), npm check (0/0), vitest 13/13, scripts/dogfood-rebrand-drill.sh 8/8. Phase F — first-run onboarding promoted to v0.1 - FirstRunPage with skip-to-main + failure recovery + event recording - Six onboarding commands (record/list/has-completed + lumotia_events) - Storage migration v17 (onboarding_events + lumotia_events tables) UI hardening (in-scope items from v0.1-ui-hardening.md) - StatusPill + PostCaptureCard components, 21st preview entry - Sidebar recording-as-sacred-state (opacity + aria-disabled, reduced-motion) - Settings 6-section regroup + Help section + Activation log + Privacy toggle - Error-state copy sweep (DictationPage + SettingsPage, plain-language) - Global :focus-visible rule, textarea outlines restored - Ctrl+K / Ctrl+, / Escape bindings in +layout LLM resilience - rule_based_extract_tasks (regex-free imperative-verb extractor) + extract_tasks_with_fallback wrapper — task extraction never returns zero - tokio::time::timeout(120s) wraps cleanup/tags/tasks commands Release artefacts - LICENSE (canonical AGPL-3.0), CHANGELOG (Keep-a-Changelog format) - v0.1-release-notes, privacy-and-ai-use, install-warnings, tester-onboarding-kit, tester-acceptance-runbook, code-signing-setup, apple-silicon-rb08-runbook, virtual-audio-setup, v0.1-contrast-audit - Workspace versioning + AGPL spdx; npm exact-pin (10 ranges removed) - AppImage SHA-256 sidecar in build.yml - README v0.1 section + Reporting-issues; canonical repo slug Closure pass — items moved from human-required to code-complete - KI-02 Linux idle inhibit: zbus 5 → org.freedesktop.login1.Manager.Inhibit - KI-03 Windows sleep prevention: SetThreadExecutionState(ES_CONTINUOUS|...) - acquire/release_idle_inhibit Tauri commands, wired in DictationPage - Diagnostic-bundle frontend wire-up (Settings → Help button) - WCAG-AA contrast fix via .btn-filled-text utility (no token changes) - 8 destructive-action sites wrapped in plain-language confirm() guards - KNOWN-ISSUES.md + v0.1-known-limitations.md updated (KI-02/03 fixed) Scripts - pre-tag-verify.sh, tag-day.sh, smoke-linux + driver - parse-diagnostic-bundle.sh, parse-activation-log.py Per-item audit trail: docs/release/v0.1-completion-status.md Remaining: W-01…W-08 (signing certs, hardware probes, smoke matrix, tester recruitment) — see docs/release/v0.1-known-limitations.md. |
|||
| bf1b68275a |
agent: lumotia — Pass 1 v0.1 checklist refinements + Pass 2 v0.1 UI hardening boundary doc
Operationalises the ChatGPT review of the v0.1 release-doc set into two
related landings. Both bounded; no Garden Inbox work, no architecture
refactor, no full redesign.
PASS 1 — v0.1-checklist.md refinements
=======================================
Seven targeted edits to the existing checklist:
1. Cold setup vs warm activation split. Tester acceptance test was
conflating model-download time (variable, network-dependent) with
UX-controlled flow time. Two-phase pass:
- Cold setup: install → onboarding → ready-to-record (no time bound)
- Warm activation: model-ready → first recording within 3 minutes
Steps 1-4 are cold; 5-10 are warm.
2. Migration-aware onboarding wording. "Skip-onboarding path for users
who already have transcripts on disk" → "Migration-aware onboarding:
existing users with valid data are not forced through first-run, but
can launch the tutorial manually from Settings → Help."
3. UI acceptance section. New testable items between Documentation
surface and Quality gates — turns "redo the UI" into measurable
requirements:
- Main capture action visible <1s on Home
- Recording state not communicated by colour alone
- 10-step flow completable at 900×700 + keyboard only
- Destructive actions reversible or confirmed
- Every async state has sidebar status chip feedback
- Error states preserve raw transcript + plain-words next step
- Settings Start Here / Privacy / Accessibility findable
- Focus ring visible everywhere
- prefers-reduced-motion respected
- WCAG AA contrast spot-check both modes
- Post-capture card surfaces (display-only; NOT Garden Inbox)
4. Supported platforms scope. New subsection before smoke-test matrix
explicitly naming:
- Primary (must work end-to-end): Linux Fedora + Ubuntu LTS
- Best-effort (announced if smoke-tested): macOS Apple Silicon,
Windows 11
- Not announced unless smoke-tested: macOS Intel
5. P0/P1/P2 smoke-test severity replacing "any ❌ blocks tag":
- P0 — blocks tag (tester spine on a primary platform)
- P1 — ships only with explicit known-limitation entry
- P2 — does not block private beta (not-announced platform / v0.2
feature)
Pre-tag verification confirms no unresolved P0 or undocumented P1.
6. "Telemetry" → "local activation log". Re-worded the activation
metrics capture mechanism. Word choice deliberate — privacy-conscious
audience reacts to "telemetry" itself. Surface: Settings →
Diagnostics → Activation log. Nothing sent automatically.
7. Support burden signal. New activation-metric subsection covering
the AI-assisted-indie risk that every issue becomes a support call:
- Self-service rate ≥ 70% (issues filed to bug tracker, not inbox)
- Diagnostic bundle (logs + system info + crash dumps; skips
transcript content + audio by default)
- Top-3 setup failures documented after first 5 testers
PASS 2 — v0.1-ui-hardening.md
==============================
New strict-boundary doc at docs/release/v0.1-ui-hardening.md (262
lines). Anchored on the line:
The v0.1 UI pass is not there to make Lumotia beautiful. It is there
to make the first successful capture inevitable.
Step 0 (before any code change): walk the 20 existing
src/design-system/preview/ files and classify each item as
already-good / needs-v0.1-hardening / v0.2-polish. Don't rebuild what
works. Inventory table inline in the doc cross-references each preview
file to the in-scope items below.
IN SCOPE (10 items, each testable):
1. Home capture clarity — big record button, status pill, last-capture
preview, capped Now/Tasks at 1-3 visible
2. Recording as sacred UI state — hide settings/history/advanced
during capture; show only timer/pause/stop/cancel + live transcript
+ level meter
3. Post-capture card — the v0.1 headline UI artefact. Display-only
surface of raw + cleaned + tasks + microsteps + 4 actions
(Save/Export/StartFirstMicroStep/OpenInHistory). Explicitly NOT
Garden Inbox: no routing, no accept/edit/park/archive, no
backlinks, no confidence scores
4. First-run onboarding polish — single clear next action per step,
pre-supplied prompt for test recording, graceful failure recovery,
skip-to-main escape hatch (tracked as known-limitations follow-up)
5. Settings sanity pass — 6 sections in order: Start Here /
Transcription / Models / Tasks / Accessibility / Privacy / Advanced.
Full 7-group progressive-disclosure regroup deferred to v0.2
6. Error-state copy sweep — every error preserves raw transcript,
explains in plain words, says next user action, no stack traces
user-facing
7. Keyboard flow — entire 10-step tester acceptance completable by
keyboard only, focus ring visible, no hover-only controls
8. Responsive at 900×700 + 1440×900 ONLY — ultrawide / mobile / split-
screen deferred to v0.2 unless a tester reports them
9. Accessibility practical checks (WCAG-style, not certification) —
keyboard, focus, not-colour-alone, reduced motion, contrast spot-
check, literal-words status labels, form-label association
10. Status labels everywhere — new StatusPill component (no existing
class found in survey); add to design-system/preview/components-
status-pills.html. Pill states: Ready / Recording / Paused /
Transcribing / Cleaning / Extracting tasks / Saved / Exported /
Needs review / Failed safely
OUT OF SCOPE (the traps to refuse — each ships in v0.2 or later):
- New visual identity (brand book v3 PDF is locked)
- New navigation model
- Garden Inbox (review cards, routing, accept/edit/park/archive,
related notes, backlinks, P-P-T detection)
- Suggested routing
- Backlinks
- Graph view
- Canvas view
- New animation system
- Full SettingsPage 7-group redesign
- Obsidian plugin
- Cloud / provider UI
Plus a "Definition of done" with 8 specific completion criteria, and
cross-references to checklist + Garden roadmap + how-built + design-
system preview + locked brand book.
VERIFICATION
============
- cargo fmt --check: clean (no Rust touched)
- All four release docs cross-reference cleanly
- No new tests required (boundary docs, not code)
- v0.1 ship gate unchanged in shape, sharpened in detail
|
|||
| c5460a169c |
agent: lumotia — release-doc set + two pre-release audits (MCP + LLM failure)
Operationalises the ChatGPT/Jake roadmap-synthesis pass into four
release-boundary documents at docs/release/. Synthesis call:
v0.1 = stable local capture product
v0.2 = Garden Inbox / review cards
v1.0 = PKM-complete + commercial track
Two factual audits ran first per Jake's explicit instruction — release
hardening only, no architecture refactors, no Garden Inbox work:
AUDIT 1 — MCP surface
=====================
Verdict: PASSES the v0.1 trust posture.
- Read-only by design (`//! No writes — Lumotia's Tauri app remains the only writer`)
- Stdio-only transport (newline-delimited JSON-RPC 2.0); no TCP/Unix
listener, no bind, no network exposure
- Database opened via `lumotia_storage::init_readonly` — structurally
enforced, not just convention
- 4 tools, all SELECT-only: list_transcripts, get_transcript,
search_transcripts, list_tasks
- Zero matches for INSERT/UPDATE/DELETE/fs::write/fs::remove/
create_dir/spawn_blocking in the crate
- Separate binary (`crates/mcp/src/main.rs`) — not part of the running
Tauri app; user must explicitly launch and wire into client config
- Honest nuance flagged in known-limitations: a wired client gets read
access to the entire transcript history + task list — no per-row
permission boundary in v0.1
AUDIT 2 — LLM failure surface
=============================
Verdict: data-loss path PASSES; UX-wedge path PARTIAL (documented).
- post_process_segments (file + live pipeline): tracing::warn! on Err,
segments stay at rule-based output. Raw transcript preserved.
- cleanup_transcript_text_cmd (DictationPage): frontend try/catch
returns raw text unchanged on Err. Raw transcript preserved.
- extract_tasks_from_transcript_cmd (DictationPage): frontend falls
back to rule-based extractTasks (regex + verb list) on Err.
- extract_content_tags_cmd (HistoryPage): per-row try/catch; toast on
failure; transcript untouched.
- Hung llama.cpp: no tokio::time::timeout on the spawn_blocking call.
Raw transcript preserved; rest of app functional; LLM status chip
stays on "Cleaning up" until restart. Soft edge — documented in
known-limitations as v0.2 hygiene candidate. Not implemented per
"release hardening only" instruction.
THE FOUR DOCS
=============
docs/release/v0.1-checklist.md
- 10-step tester acceptance test (install → capture → cleanup →
task → MicroSteps → timer → history search)
- Must-ship list per surface (product, onboarding, artefacts, docs,
quality gates, trust+security, release-blockers, smoke-test
matrix)
- Activation metrics for private beta (3 min to first capture, 3
captures in 24h, 7-day return, etc.) + v0.1 public launch
(20 install, 15 first-capture, 10 return, 5 pay-£39)
- Pre-tag verification sequence
- Explicit out-of-scope list (Garden Inbox, Phases B-E/G/I/J, etc.)
docs/release/v0.1-known-limitations.md
- User-facing rewrite, not engineer-speak
- Power assertions per platform (Linux idle / macOS App Nap / Windows
sleep) with practical workarounds
- MCP read-only/local-only posture with the
"all transcripts visible to your wired client" honest nuance
- AI cleanup/extraction failure table — what fails, what you see,
what's preserved
- Settings page progressive-disclosure status
- Internal engine refactor (orchestrator dormant) framed for users
- Explicit "what's NOT in v0.1" call-outs
- Reporting issues + crash-dump location
docs/release/v0.2-garden-roadmap.md
- Headline: "review cards for turning messy dictations into notes,
tasks, topics and links" — tangible, not PKM-overloaded
- Garden Inbox scope (raw / cleaned / suggested title-type-folder-
project / extracted tasks / suggested tags / possible links /
confidence / Accept-Edit-Park-Archive)
- Engine architecture Phases B-E pairing
- Explicit "NOT in v0.2" list (no graph, no canvas, no PKM marketing,
no cloud provider, no premium voices)
- Open decisions for v0.2 scope freeze deferred until v0.1 ships +
20 testers run
docs/release/how-lumotia-is-built.md
- Public-facing trust page; honest disclosure that AI-assisted
human-directed, then evidence
- The real silent-data-loss bug the drill caught (Phase A.7 fix
|
|||
| b6b7e8e86c |
agent: lumotia — Phase B dogfood plan — B.2-B.15 audit trail + finishing summary
Records the per-item verdict + commit hash for every Phase B audit item (B.2 through B.15). Status block flipped to Complete 2026/05/14. Items table moved every row from Pending → Done or Documented pass with a one-paragraph outcome summary. Each Done item also has a full section in the Done items list, mirroring B.1's existing structure (surface, why it matters, fix, verification). Surgical commits in the audit pass: |
|||
| 1c4ac98504 |
agent: lumotia — Phase B.10 pin focusTimer expired-rehydrate startTick invariant
Phase B.10 audit of commit |
|||
| 401b6c3654 |
agent: lumotia — Phase B.9 strip Qwen <think>…</think> reasoning before JSON-envelope scan
Phase B.9 audit of commit
|
|||
| 813f024cdb |
agent: lumotia — Phase B.8 bridge storage events into tracing subscriber
Phase B.8 audit of commits |
|||
| f252c1b50e |
agent: lumotia — Phase B.7 close unload-during-load TOCTOU on LlmEngine
Phase B.7 audit of commit
|
|||
| 7f0e1b0375 |
agent: lumotia — Phase B.6 pin IPC-allowlist vs capability-JSON mirror invariant
Phase B.6 audit of commits |
|||
| d8fa4ff64e |
agent: lumotia — Phase B.5 close symlink-target bypass in write_text_file_cmd path scope
Phase B.5 audit of commits a2b47db/a48653c/b3da58c (Trust-1 — write path allowlist), |
|||
| 20ef6c459b |
agent: lumotia — Phase B.4 close restore-during-purge race via atomic DELETE RETURNING
Phase B.4 audit of commits |
|||
| 31e3f5a099 |
agent: lumotia — Phase B.3 unlink .part on ResumeUnsupported so retry can recover
Phase B.3 audit of commit
|
|||
| 643985d2a8 |
agent: lumotia — Phase B.2 fix misleading "force-abort" doc + test name on supervisor shutdown
Phase B.2 audit of commit
|
|||
| e993786700 |
agent: lumotia — Phase B dogfood plan + B.1 audit trail
Captures the 15-item Phase B plan (code-atomiser-fix wave verification) at
docs/superpowers/plans/2026-05-14-phase-b-dogfood-plan.md so the per-item
methodology, status, and findings survive across sessions.
B.1 already done (commit
|
|||
| 6c212a0d2c |
agent: lumotia — Phase B.1 fix misleading comment on start_live lifecycle ordering
Phase B.1 survey finding (commit
|
|||
| ff8dda06d0 |
agent: lumotia — Phase A.7 fix startup-order race that silently orphaned legacy data
Critical bug surfaced by the dogfood drill: every upgrading Magnotia user
would silently keep a fresh empty Lumotia install while their Magnotia
data sat orphaned next to it. Drill caught it on the first real run
under sandboxed HOME.
ROOT CAUSE
src-tauri/src/lib.rs::run() previously called the migrations from inside
the Tauri setup hook (post `tauri::Builder::default()`). But three
sequential actions BEFORE the setup hook had already created the
destination directories:
1. init_tracing() -> logs_dir() -> create_dir_all(app_data_dir/logs)
creates the lumotia/ root.
2. install_panic_hook() -> crashes_dir() -> create_dir_all() ditto.
3. Tauri's WebKitGTK runtime / plugin chain creates the bundle-id-keyed
consulting.corbel.lumotia/ dir eagerly when the WebContext spins up
(mediakeys, storage, WebKitCache subdirs appeared even without our
hook explicitly creating them).
By the time the setup-hook migrations fired, every legacy candidate
returned `TargetAlreadyExists` (paths.rs) or `BothExistLegacyPreserved`
(tauri_app_data_migration.rs) — both silent no-op codepaths. Legacy
data was left untouched, fresh Lumotia install gained no transcripts,
settings, or window state.
FIX
Migrate BEFORE any other code touches app_data_dir().
src-tauri/src/tauri_app_data_migration.rs:
- NEW_BUNDLE_ID const ("consulting.corbel.lumotia"). MUST agree with
tauri.conf.json#identifier; reviewer-enforced invariant.
- Renamed private `legacy_tauri_app_data_dir_for` -> public
`tauri_app_data_dir_for(identifier)`. Function is parameterised by
bundle id; the "legacy" name was misleading after this change.
- New `current_tauri_app_data_dir()` resolves the NEW bundle path
from platform env vars (same convention Tauri 2 uses), so the
pre-runtime migration can address its destination without
needing an AppHandle.
src-tauri/src/lib.rs:
- New `migrate_user_data_pre_runtime()` orchestrates the two
migrations + ambiguity guard. Uses `eprintln!` for surface events
(tracing not yet initialised at this stage; stderr lands in
journald / foreground terminal which is the right transport for
boot-phase output). FATAL errors call process::exit(1) — the
setup-hook version returned Err from the closure, equivalent
effect.
- run() now calls migrate_user_data_pre_runtime() as its first line,
BEFORE init_tracing(), install_panic_hook(), and the Tauri
builder.
- Setup-hook migration blocks deleted (~90 lines). Setup hook now
starts with a one-line comment pointing at the pre-runtime fn.
VERIFICATION
Re-ran the dogfood drill (scripts/dogfood-rebrand-drill.sh) — 8/8 probes
pass after the fix (was 4/8). Both stderr lines fire:
[lumotia-startup] migrated legacy magnotia data dir to lumotia:
.../magnotia -> .../lumotia (renamed_db=true, elapsed_ms=0)
[lumotia-startup] migrated Tauri app_data_dir from legacy bundle
identifier: .../uk.co.corbel.magnotia ->
.../consulting.corbel.lumotia (elapsed_ms=0)
On-disk post-state confirms: magnotia/ gone, lumotia/ has migrated db
+ recordings, uk.co.corbel.magnotia/ preserved as backup,
consulting.corbel.lumotia/localStorage/leveldb/ has migrated data.
- cargo fmt --check: clean
- cargo clippy --workspace --all-targets -- -D warnings: clean
- cargo test --workspace: 409/0 (no regression)
|
|||
| 2aac366f32 |
agent: lumotia — Phase A.6 dogfood drill for rebrand migration on real OS paths
scripts/dogfood-rebrand-drill.sh — end-to-end probe that launches the real
lumotia binary against synthetic legacy magnotia state on disk, then
verifies both migration paths produced the expected outcome:
1. paths.rs: ~/.local/share/magnotia/ -> ~/.local/share/lumotia/, including
magnotia.db -> lumotia.db rename + non-DB companion files carried along
by the directory rename.
2. tauri_app_data_migration.rs: ~/.local/share/uk.co.corbel.magnotia/
copied via atomic staging to ~/.local/share/consulting.corbel.lumotia/,
with legacy preserved as a backup and staging dir cleaned up.
Closes the last gap in Phase A: every other test (paths::tests + storage
integration test + localStorageMigration.test.ts) uses synthetic in-process
state. The drill is the only verification that the real binary's startup
hook calls migrate_legacy_data_dir + migrate_tauri_app_data_dir_with_paths
against real OS path resolution.
Two modes:
(default) Sandbox: HOME=<tempdir>, faithful on Linux. NOT
faithful on macOS — Tauri 2 uses
NSSearchPathForDirectoriesInDomains which ignores
HOME overrides. Drill refuses to start in sandbox
mode on macOS rather than silently writing to the
user's real Application Support tree.
--against-real-home Real $HOME. Refuses to start if any lumotia data
already exists at the real paths (no clobbering
real user data). Cleans up planted state on exit
unless --keep is passed.
Eight probes covering: data-dir rename outcome, db file rename, legacy
removal, companion file survival, Tauri app_data_dir copy, legacy-backup
preservation, staging-dir cleanup, and lumotia_startup log line presence.
README: documents the drill alongside cargo test + npm test in the
Testing section, with the macOS caveat clearly flagged.
Not run as part of this commit — the drill launches a Tauri WebView
window for a few seconds. Jake to invoke when ready to dogfood.
|
|||
| 206ac6219d |
agent: lumotia — Phase A.5 vitest scaffold + localStorageMigration unit tests
First frontend unit test framework on Lumotia. Pinned exact versions for supply-chain hygiene (matches the rust-toolchain.toml discipline from the |
|||
| 18a64f5c56 |
agent: lumotia — Phase A.3 remove dead migration_sentinel method + fix architecture-map claim
Phase A.3 finding: AppPaths::migration_sentinel was added with intent
during the rebrand-architecture phase but never wired to any caller.
Exhaustive grep across crates/ src-tauri/ src/ docs/ surfaces:
- 1 definition (paths.rs)
- 1 architecture-map description that ASSERTS the method is in use
- 0 production callers
- 0 test references
Both boot-time migrations (migrate_legacy_data_dir +
migrate_tauri_app_data_dir_with_paths) are idempotent by construction:
each re-probes the legacy path via Path::exists() on every boot and
short-circuits on the steady state. A sentinel file would optimise the
probe but is not required for correctness; one syscall per legacy
candidate at startup is negligible.
Per the atomiser principle of removing dead surface area rather than
keeping stale promises:
- Delete AppPaths::migration_sentinel entirely
- Update docs/architecture-map/.../core-paths.md to describe the actual
idempotency model (re-probing) rather than the sentinel pattern that
was never implemented
- Steer future migrations toward storage/src/migrations.rs schema_version
(transactional, survives backup/restore) rather than reintroducing
filesystem sentinels
Verification:
- cargo test -p lumotia-core paths::: 17/17 (no test relied on the method)
- cargo clippy -p lumotia-core --all-targets -- -D warnings: clean
Phase A.4 (stray-magnotia string scan): clean. Every magnotia reference
in the tree is legitimate — migration source paths, documentation, or
test fixtures. The rebrand cascade was thorough.
|
|||
| 43d319fd5a |
agent: lumotia — Phase A.1+A.2 rebrand migration tests + copy_dir_recursive hardening
Phase A of dogfood verification for the Magnotia -> Lumotia rebrand
cascade. The existing in-crate unit tests prove the migration copies
bytes correctly; this commit closes the gaps an atomiser-grade review
would flag.
Phase A.1 — end-to-end integration test (crates/storage/tests/legacy_db_migration.rs):
Seeds a real on-disk magnotia.db via lumotia_storage::init (which runs
every schema migration head-to-tail), inserts a transcript via the
public API, drops the pool, runs migrate_legacy_data_dir_with_pairs,
then re-opens the migrated lumotia.db and asserts the transcript is
queryable. Three scenarios covered:
1. Legacy-only -> migrate -> reopen -> row survives. Also verifies a
non-DB companion file is carried along by the directory rename.
2. Idempotency: first boot migrates, user writes new data, second
boot is a no-op and BOTH rows survive.
3. Both-paths-present: refuses to merge, target's empty DB is
preserved, legacy retained on disk as a backup.
Wires the test surface by renaming the previously-private
migrate_legacy_data_dir_inner to pub migrate_legacy_data_dir_with_pairs
(mirroring migrate_tauri_app_data_dir_with_paths in the sibling
tauri_app_data_migration module).
Phase A.2a — copy_dir_recursive hardening (crates/core/src/paths.rs):
Pre-existing footgun: the fall-through branch called std::fs::copy()
on any DirEntry that was not a symlink or a directory. On Unix that
includes FIFOs, sockets, and char/block device nodes. Opening a FIFO
for read with no writer attached blocks forever — a stale debug FIFO
in the user's ~/.magnotia tree would silently hang first launch.
The branch now explicitly distinguishes is_file() (real regular file
-> copy) from anything else (-> Err with ErrorKind::Unsupported,
naming the offending path). Migration becomes re-runnable once the
user cleans up the offending node. Same-filesystem rename via
std::fs::rename is atomic and unaffected; only the EXDEV fallback path
touches the new guard.
Phase A.2b — three adversarial probes (crates/core/src/paths.rs tests):
- FIFO inside the legacy tree: copy_dir_recursive must return an
Unsupported error WITHOUT hanging. Test bounded by a 5s wall clock
+ a worker thread so a regression to the old fall-through would
surface as a panic, not a stalled CI job.
- Unreadable file (mode 0000): copy_dir_recursive must surface
PermissionDenied, not silently skip. Skips its core assertion under
euid 0 (root bypasses DAC permissions, would mask the regression).
- Dangling symlink (target nonexistent): symlink is recreated at
destination with link target preserved verbatim; the migration
does NOT try to dereference and does NOT abort the rest of the copy.
Verification:
- cargo fmt --check: clean
- cargo clippy --workspace --all-targets -- -D warnings: clean
- cargo test --workspace: 409 passed, 0 failed (up from 405 pre-commit;
3 storage integration tests + 3 paths adversarial + 1 net carry-over)
|
|||
| 27661c816e |
agent: lumotia — pin rust toolchain + workspace clippy/fmt sweep
rust-toolchain.toml pins to stable 1.94.1 so contributors and CI runners share the exact rustc / rustfmt / clippy versions. Without the pin, every machine surfaces a different lint set depending on its local install — six pre-existing lints showed up on 1.94.1 that 1.93-era HANDOVER reported clean. Clippy fixes (all pre-existing, not introduced by feature work): - crates/storage/src/database.rs: std::iter::repeat().take() -> repeat_n() - crates/llm/src/lib.rs (docs): "+ frontends" was parsed as a markdown bullet continuation by rustdoc, breaking doc-lazy-continuation. Reworded to "and". - crates/llm/src/lib.rs (loop): while-let-on-iterator -> for-loop. - src-tauri/src/commands/security.rs: .iter().any(|a| *a == x) -> .contains(&x). - src-tauri/src/lib.rs: io::Error::new(Other, e) -> io::Error::other(e). - src-tauri/src/tauri_app_data_migration.rs: drop function-tail `return`s inside cfg blocks; each platform's block now ends with a tail expression. cargo fmt sweep across the workspace. Mechanical layout-only changes; no semantics affected. Workspace gates after this commit: - cargo fmt --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean - cargo test --workspace: 405/0 (will become 409/0 with Phase A.1+A.2) |
|||
| e4d56b831f |
agent: lumotia — supply-chain pre-flight (npm audit signatures + install discipline)
Adds defence-in-depth against npm-worm attacks (Shai-Hulud / mini-Shai-Hulud). - run.sh: gates dev launch on `npm audit signatures` whenever package-lock.json is newer than .lumotia-last-audit. Fails loud on signature mismatch. Skip with LUMOTIA_SKIP_AUDIT=1 for offline dev. - README: documents `npm ci --ignore-scripts` as the install discipline (blocks the postinstall vector worms exploit) and explains the audit hook. - .gitignore: excludes the per-clone audit stamp. Lumotia's current tree (192 packages) cross-references clean against the mini-Shai-Hulud affected-package list — this is preventive, not remedial. |
|||
|
|
1f259f7b06 | Magnotia --> Lumotia rebrand changes | ||
| 65abfa2ed9 |
agent: code-atomiser-fix — span propagation across live + model-load spawns (Obs-3)
Before this commit `grep -rIn '#\[instrument\|.instrument(\|in_current_span()'`
returned zero matches across the entire workspace. Every tokio::spawn
and thread::spawn lost its parent span, so structured fields recorded
at the call site (session_id, chunk_id, model_id) did not propagate to
log lines emitted inside the spawn. During concurrent-session incidents
the operator could not correlate a runaway log line back to the request
that started it.
Targeted four highest-value join points:
* src-tauri/src/commands/live.rs::run_live_session
#[tracing::instrument(skip_all, fields(session_id, engine, language))]
Attaches the span to the spawn_blocking worker so every per-chunk
warning carries the session id that owns it.
* src-tauri/src/commands/live.rs::maybe_dispatch_chunk
Manual span attach pattern (#[instrument] can't decorate a closure):
capture the parent span before thread::spawn, .enter() it on the new
OS thread, then open an "inference" child span with chunk_id +
duration_secs. Without this, whisper backend warnings appear
unparented and a runaway chunk can't be traced back to its session.
* src-tauri/src/commands/models.rs::ensure_model_loaded
#[instrument(skip_all, fields(model_id, engine, concurrent))]
Multi-second load + sequential-GPU guard logs now carry the model
in flight as a structured field.
* crates/llm/src/lib.rs::load_model
#[instrument(skip_all, fields(model_id, use_gpu))]
Same rationale for LLM loads. Tags llama-backend init lines and
GPU sequential-guard events with the model identifier.
Storage/audio/hotkey/MCP crates left uninstrumented in this commit —
future sweep. The four sites above are the canonical concurrent-load
correlation points; everything else fans out from them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| d1391b34ac |
agent: code-atomiser-fix — drop lumotia_live custom target in live.rs (Obs-1, Obs-2)
The drain-timeout warning in LiveSessionRuntime emitted with `target: "lumotia_live"`, which EnvFilter treats as the literal target string and not as a substring of `lumotia_lib::commands::live`. The operator's documented triage filter (`RUST_LOG=info,lumotia=debug,lumotia_lib::commands::live=debug`, per docs/superpowers/audits/2026-05-10-phase10a-dogfood-notes.md) therefore silenced the only warning that surfaces a wedged inference worker. Drop the explicit `target:` so the emit picks up its module-path target and falls under the existing filter directive. `lumotia_startup`, `lumotia_storage`, `lumotia_hotkey`, etc. remain deliberately custom targets — each is a separate semantic phase with its own dedicated EnvFilter directive. Regression test asserts no `target: "lumotia_live"` literal remains in live.rs by scanning the file's own source. Skips comment lines so the rationale prose does not self-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 12b413d645 |
agent: code-atomiser-fix — broaden clipboard/paste guards to documented secondary windows
The Trust-3/Trust-6 fix in commit
|
|||
| 9653e25e32 |
agent: code-atomiser-fix — extension allowlist + size cap on transcribe_file (Trust-5)
`transcribe_file` already had `ensure_main_window`, but accepted an arbitrary `path: String` and fed it straight to `lumotia_audio::decode_audio_file_limited`. The OS file picker typically constrains the user's path, but the IPC surface itself never checked: a compromised webview could point the decoder at a 50 GiB sparse file (OOM the worker), or a deliberately-malformed blob with an extension chosen to provoke a parser bug in Symphonia. This change adds defence-in-depth: - extension allowlist (`wav`, `mp3`, `m4a`, `mp4`, `flac`, `ogg`, `opus`, `webm`, `aac`) matched case-insensitively. Anything else, including no extension at all, is rejected with a clear error; - 1 GiB ceiling on the input file. Stats via `std::fs::metadata` (which resolves symlinks) so the cap sees the real blob, not a symlink-target lie. The 2-hour duration cap still runs after decode for the realistic-audio case. The validation lives in a pure helper, `validate_transcribe_input`, so the rule can be unit-tested without spawning Tauri or hitting the decoder. Eight unit tests cover: accepts plain `.wav`, accepts uppercase `.MP3`, accepts every allowlisted extension, rejects `.so` payload, rejects missing extension, rejects oversize file, accepts exactly-at-cap file, rejects path-traversal with disallowed extension. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 87e6248774 |
agent: code-atomiser-fix — trash view + restore button in HistoryPage (Rev-2 UI)
Adds a Live | Trash toggle to the HistoryPage header. The Trash tab
lists soft-deleted transcripts via the new list_trashed_transcripts
Tauri command and offers per-row Restore via restore_transcript.
Permanently-delete-from-trash is intentionally deferred — the 30-day
startup purge (TRANSCRIPT_TRASH_RETENTION_DAYS in src-tauri/src/lib.rs)
handles hard-removal until that UI lands. The retention policy is
surfaced in the panel header: Trashed items are kept for 30 days,
then permanently deleted on next app start.
Design notes:
- View toggle is a tablist of two pill buttons (Live | Trash); the
aria-selected attribute reflects the active mode.
- Switching to Trash triggers an effect that loads the list once.
Switching back to Live discards the trash data so stale rows
don't reappear on toggle.
- Live-mode header controls (Starred, Tag all untagged, Clear All)
are hidden in Trash mode and replaced with a Refresh button.
- Restore drops the row from the in-memory trash list rather than
splicing into history; the live store reloads from SQLite on its
own initialisation path, so we avoid drifting the in-memory shape
from the canonical source.
- The created timestamp is shown rather than the deletion
timestamp; deleted_at is not currently in the TranscriptDto and
expanding the DTO is out of scope for this fix.
- Audio files may already have been removed by delete_transcript's
best-effort filesystem cleanup, so restored text + metadata may
surface without playable audio (documented in the storage-layer
restore_transcript contract).
TODO(test): no Svelte component test framework wired in the repo
(vitest not installed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|