Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
name, description, type, tags, created, status, author
| name | description | type | tags | created | status | author | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Phase 10a static slice audit | Static a11y + contrast + CI verification produced by Wren on 2026/04/25 as the agent-runnable portion of Phase 10a, ahead of Jake's manual walkthrough and feedback-document pass. | audit |
|
2026/04/25 | findings | Wren (CORBEL's resident agent) on behalf of Jake Sames |
Phase 10a — Static Slice Audit
Scope. Everything in Phase 10a that an agent can verify without a running dev server. The dogfood walkthrough, RB-08 macOS power- assertion verification, and runtime keyboard / screen-reader traversal stay with Jake (or Rachmann for the Mac slot).
Baseline.
mainat0ca4e0e. 277 cargo tests pass. clippy / fmt / svelte-check / npm build all clean.
Summary of findings
- A11y static rules: clean. svelte-check reports 0/0 across 3957
files. Spot-checks confirm consistent
aria-labelon icon-only buttons andaria-hidden="true"on inner SVGs. Global:focus-visiblering is set in design tokens. - Contrast: real fails in light theme + small dim text in both
themes. Nine token pairs miss WCAG AA-normal (4.5:1). One pair
(
warningonbg, light) misses AA-large too. - CI: cross-platform
cargo checkmatrix exists and runs on every push tomain. The full Tauri installer build (build.yml) has never been exercised end-to-end and should be triggered by manual workflow_dispatch before tagging v0.1.0.
A11y baseline
npm run check(svelte-check 4): 0 errors / 0 warnings on 3957 files. Svelte's a11y ruleset enforces<img>alt text, form-input labels, click-handler-on-non-interactive, autofocus restrictions, noninteractive-tabindex, and roughly twenty other rules. All pass.- Spot-checked icon-only buttons across
Sidebar.svelte,ToastViewport.svelte,MicroSteps.svelte,HotkeyRecorder.svelte,HistoryPage.svelte,TasksPage.svelte. Every button carriesaria-label. Every inner<svg>carriesaria-hidden="true". The pattern is consistent enough to assume it's the convention rather than coincidence. - Global focus ring at
src/design-system/colors_and_type.css:225::focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-md); }. Covers every focusable element by default. prefers-reduced-motionguards present in 8 files (ToastViewport,CompletionSparkline,SettingsGroup,DictationPage,TasksPage,app.css,colors_and_type.css, plus thepreferences.svelte.tsstore that exposes the flag). These cover the four scaled / staggered animations Phase 8 + 9 introduced. Hover-only colour transitions across the rest of the app rely on the global*transition rule and don't pose a vestibular risk; they're under the reduced-motion threshold.
Contrast audit (WCAG 2.1 AA)
Computed via Python (sRGB → linear-RGB → relative luminance → Web Content Accessibility Guidelines contrast ratio). Floors: 4.5:1 for normal text, 3:1 for large text (≥18px regular or ≥14px bold) and UI components.
Dark theme
| Foreground | Background | Ratio | AA-normal | AA-large |
|---|---|---|---|---|
text |
bg |
16.38 | PASS | PASS |
text |
bg-elevated |
15.35 | PASS | PASS |
text |
bg-card |
14.77 | PASS | PASS |
text |
sidebar |
15.90 | PASS | PASS |
text |
nav-active |
14.12 | PASS | PASS |
text |
hover |
14.44 | PASS | PASS |
text-secondary |
bg |
6.39 | PASS | PASS |
text-secondary |
bg-elevated |
5.99 | PASS | PASS |
text-secondary |
bg-card |
5.76 | PASS | PASS |
text-secondary |
sidebar |
6.20 | PASS | PASS |
text-secondary |
hover |
5.63 | PASS | PASS |
text-tertiary |
bg |
3.65 | FAIL | PASS |
text-tertiary |
bg-elevated |
3.42 | FAIL | PASS |
text-tertiary |
bg-card |
3.29 | FAIL | PASS |
text-tertiary |
sidebar |
3.54 | FAIL | PASS |
accent |
bg |
9.48 | PASS | PASS |
accent |
bg-elevated |
8.89 | PASS | PASS |
accent |
bg-card |
8.55 | PASS | PASS |
accent |
sidebar |
9.21 | PASS | PASS |
success |
bg |
9.75 | PASS | PASS |
success |
bg-card |
8.80 | PASS | PASS |
danger |
bg |
6.46 | PASS | PASS |
danger |
bg-card |
5.83 | PASS | PASS |
warning |
bg |
11.87 | PASS | PASS |
warning |
bg-card |
10.70 | PASS | PASS |
Light theme
| Foreground | Background | Ratio | AA-normal | AA-large |
|---|---|---|---|---|
text |
bg |
16.70 | PASS | PASS |
text |
bg-elevated |
15.58 | PASS | PASS |
text |
bg-card |
17.70 | PASS | PASS |
text |
sidebar |
15.85 | PASS | PASS |
text |
nav-active |
14.24 | PASS | PASS |
text |
hover |
14.64 | PASS | PASS |
text-secondary |
bg |
6.77 | PASS | PASS |
text-secondary |
bg-elevated |
6.32 | PASS | PASS |
text-secondary |
bg-card |
7.18 | PASS | PASS |
text-secondary |
sidebar |
6.43 | PASS | PASS |
text-secondary |
hover |
5.94 | PASS | PASS |
text-tertiary |
bg |
3.47 | FAIL | PASS |
text-tertiary |
bg-elevated |
3.24 | FAIL | PASS |
text-tertiary |
bg-card |
3.68 | FAIL | PASS |
text-tertiary |
sidebar |
3.30 | FAIL | PASS |
accent |
bg |
3.35 | FAIL | PASS |
accent |
bg-elevated |
3.12 | FAIL | PASS |
accent |
bg-card |
3.55 | FAIL | PASS |
accent |
sidebar |
3.18 | FAIL | PASS |
success |
bg |
3.98 | FAIL | PASS |
success |
bg-card |
4.22 | FAIL | PASS |
danger |
bg |
4.39 | FAIL | PASS |
danger |
bg-card |
4.65 | PASS | PASS |
warning |
bg |
2.56 | FAIL | FAIL |
warning |
bg-card |
2.72 | FAIL | FAIL |
Severity ranking
- Light-theme
warningon every surface — fails AA-large too (2.56–2.72:1). Anywhere this colour appears againstbgorbg-cardis unreadable for low-vision users. Highest priority. - Light-theme
accentas link colour — globala { color: var(--accent) }rule atcolors_and_type.css:220puts a 3.35:1 swatch on body text. Link semantics need AA-normal (4.5). text-tertiaryin both themes — used for.k-caption(12px),.k-eyebrow(10px),kbd(11px) and many hint-text contexts (399 class-name hits across the codebase). All small text. All fail AA-normal in both themes.- Light-theme
successon bg / bg-card — borderline (3.98 / 4.22). Fine if used only for icons / chips ≥18px, fails for inline body copy. - Light-theme
dangeron bg — borderline (4.39); 4.65 on bg-card. Fine if used for chips, marginal for inline.
Suggested token shifts (estimates, eyeball before committing)
Numbers below are starting points, not finished colours. Re-run the contrast script against any candidate before merging.
--text-tertiary(light):#8a8578→ roughly#6f6a5c. Aim ≥4.5:1 against#faf8f5.--text-tertiary(dark):#716b60→ roughly#888278. Aim ≥4.5:1 against#0f0e0c.--accent(light):#b87a4a→ roughly#9a6535. Aim ≥4.5:1 for link role; UI / icon use already passes.--warning(light):#b89a3e→ roughly#8a7220.--success(light) and--danger(light): nudge by ~5–10% if you find them used as inline text during the walkthrough.
The Python contrast helper used to generate these tables is small enough to keep inline if useful — it's at the bottom of this file.
CI matrix state
Three workflows in .github/workflows/:
check.yml. Runscargo checkonubuntu-22.04,windows-latest,macos-lateston every push tomainand every PR. Concurrent-cancels older runs on the same branch. Should be green at0ca4e0ebut cannot be confirmed from this machine (ghCLI not installed). Worth eyeballing in the GitHub Actions UI before declaring 10a done.build.yml. Full Tauri installer build (Linux .AppImage + .deb, Windows .msi + .exe, macOS .dmg + .app). Triggers on tag pushv*or manual workflow_dispatch. Has never been exercised end-to-end. First v0.1.0 tag will be its trial run. Strong recommendation:Run workflowagainstmainonce before tagging, to catch matrix-specific failures (Vulkan SDK on Windows, libclang location on Linux 22.04, MoltenVK on macOS) without the release artefact pressure.audit.yml. Weekly cargo + npm audit. Independent of v0.1.
Clean-install test plan
Run on a spare user account or a fresh VM, with no prior Lumotia / Lumotia data. Three iterations: one per platform.
- Install the artefact from the platform's
build.ymloutput. - Launch from a clean shell (
lumotiafrom PATH, or the .app / Start-menu shortcut). - Verify first-run setup flow renders. Walk through the Whisper / LLM model download for the smallest tier.
- Confirm app data lands at the expected path:
- Linux:
~/.local/share/lumotia/(will become~/.local/share/ lumotia/after Phase 10b rename). - macOS:
~/Library/Application Support/com.corbel.lumotia/. - Windows:
%APPDATA%\com.corbel.lumotia\.
- Linux:
- Record a 10-second brain-dump → cleanup → task extraction.
Confirm no log leakage to stderr that references
target/or dev-only paths. - Quit the app. Open the SQLite db (
lumotia.dbfor now) and verifySELECT version FROM schema_version ORDER BY version DESC LIMIT 1returns14. - Re-launch. Confirm settings persist, history shows the test transcript with manual + LLM tags.
- Optional but recommended: launch with
RUST_LOG=debugonce and archive the log. Anything referencing/home/jake/Documents/ CORBEL-Projects/lumotia/target/is a dev-leak bug.
For Phase 10c this gets re-run after the rename sweep to confirm
the migration shim correctly moves ~/.local/share/lumotia/ →
~/.local/share/lumotia/ and renames lumotia.db → lumotia.db.
Walkthrough checklist (deferred from Phase 9d)
These need a running dev server, so they belong in Jake's testing session:
- Keyboard-only traversal across every page. Tab order respects visual order. No keyboard traps.
- Focus-visible ring shows on every focusable element. Zero invisible focus states.
- WCAG AA contrast verified visually in both themes after any token shifts from the suggestions above.
- Dark-mode parity check: every page renders correctly in light + dark. No hard-coded greys that look fine in one theme and broken in the other.
- Icon-only-button audit: hover reveals the title attribute or the aria-label is announced by VoiceOver / Orca / Narrator.
prefers-reduced-motion: reduceenabled in OS — sparkline stagger, badge entrance, settings-group chevron all stop.- Screen-reader smoke test: at least announce page titles, primary-button labels, and the sparkline summary line on one platform's SR.
Appendix — contrast helper
Drop into scripts/contrast.py or run inline. Pass two hex strings
(with or without #); prints the AA verdict.
def srgb_to_lin(c):
c = c / 255
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
def luminance(hex_color):
h = hex_color.lstrip('#')
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
return 0.2126 * srgb_to_lin(r) + 0.7152 * srgb_to_lin(g) + 0.0722 * srgb_to_lin(b)
def contrast(c1, c2):
l1, l2 = luminance(c1), luminance(c2)
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
Anchors
- Roadmap: docs/roadmap/2026-04-23-lumotia-feature-complete-roadmap.md
- Phase 9 spec: docs/superpowers/specs/2026-04-24-phase9-polish-debt-design.md
- Release-blocker index: docs/issues/README.md
- Latest handover: HANDOVER.md