Commit Graph

362 Commits

Author SHA1 Message Date
jars
fdf27db0a1 perf+fix: DMABUF default on Linux, popout ACL fixes, plugin version sync, JFK bench fixture
Bundled work from a low-end laptop (Ryzen 5 4650U / Vega 6 / Linux Mint
22.2 / X11) profiling pass.

perf: WEBKIT_DISABLE_DMABUF_RENDERER=1 default on all Linux
  Previously only set on Wayland sessions. Empirically it's a
  significant idle-cost win on integrated GPUs in either session type:
  env-var matrix (release binary, 75s settle, 10s jiffies CPU sample)
  showed magnotia idle CPU 12.30% → 2.80% of one core and idle GPU
  17% → 10% on this hardware. Users can opt back in by exporting
  WEBKIT_DISABLE_DMABUF_RENDERER=0. The Wayland-only XWayland
  fallback (GDK_BACKEND=x11, WINIT_UNIX_BACKEND=x11) is unchanged.

fix: secondary-windows ACL — allow set-always-on-top
  The float window's pin toggle calls setAlwaysOnTop() but the
  secondary-windows capability didn't permit it, so the popout was
  stuck always-on-top regardless of the pin state. Adds the
  core:window:allow-set-always-on-top permission. Narrow scope.

fix: guard registerGlobalHotkey against non-main webviews
  Cross-window settings sync via localStorage can re-fire the
  $effect(() => settings.globalHotkey) callback inside popout webviews
  where the main layout's registerGlobalHotkey is reachable. Adds an
  early-return when the current window label is not "main", so the
  popout doesn't trigger an ACL-denied register/unregister and the
  user no longer sees a spurious "Hotkey not registered" toast when
  popouts are open. Keeps the global-shortcut perm scoped to main.

build: pin @tauri-apps/api 2.10.1 + @tauri-apps/plugin-dialog 2.7.1
  Match the Rust crate versions tauri-cli's version-mismatch check
  enforces during release builds. Without this, `npm run tauri build`
  exits 0 silently while emitting an Error and never producing
  binaries.

test: add crates/transcription/tests/jfk_bench.rs
  Reproducible RTF regression fixture. Env-gated on
  MAGNOTIA_WHISPER_TEST_MODEL + MAGNOTIA_WHISPER_TEST_AUDIO so it
  never runs in CI without setup. Loads the JFK WAV inline (no hound
  dep), times model load + cold + warm transcribe, prints SUMMARY.
  Baselines on this hardware:
    --release --features whisper:               cold RTF 0.054, warm 0.050, RSS 125 MB
    --release --features whisper,whisper-vulkan: cold RTF 0.029, warm 0.028, RSS 125 MB
  Vulkan on RADV/Vega 6 nearly halves transcription latency for
  Whisper Tiny — useful baseline for Phase 10 hardware-recommendation
  scoring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:09:03 +01:00
2dfd7167fd fix(a11y): Toggle aria-invalid briefly when async onChange rejects
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
When an async onChange handler rejects, the toggle snapped back to its
previous value silently — screen reader users had no signal that the
action failed, only that the toggle suddenly returned to its original
state. Now aria-invalid="true" exposes the failure on the role=switch
button for 1.5s after rejection, so assistive tech announces the error.
Self-clears via setTimeout; no API change for callers.
2026-05-07 11:31:47 +01:00
aecb191705 refactor(theme): tokenise hard-coded amber shadows and reconcile duplicate @font-face declarations
Hard-coded rgba(214,132,80,X) shadows were tied to the dark-theme accent
and stayed off-hue in light mode (where the accent is a darker #a3683a).
Six tokens added to app.css @theme and mirrored in the light-theme block
plus design-system/colors_and_type.css:

  --shadow-accent-sm     0 0 8px   0.25 alpha  Toggle on-state
  --shadow-accent-md     0 4px 16px 0.3       ModelDownloader card
  --shadow-accent-lg     0 4px 20px 0.3       DictationPage record button
  --shadow-accent-glow   0 0 8px   0.4        progress-bar glow
  --shadow-accent-pill   0 1px 4px 0.3        SegmentedButton pill
  --shadow-accent-raised 0 2px 8px 0.2        FilesPage browse tile

Migrated seven sites: Toggle, SegmentedButton, ModelDownloader (x2),
FilesPage (x2), DictationPage record button, SettingsPage locale pill.
Focus-ring shadows (0_0_0_3px_rgba(...,0.1)) left alone — handled in
parallel via the --accent-shadow-focus token.

@font-face: app.css and colors_and_type.css both declare the same five
fonts. Duplication is unavoidable because preview/*.html loads the
design-system file directly without Vite, so it cannot share the
runtime declarations. font-weight ranges and font-style reconciled to
match exactly (Atkinson 200-800 not 400-700, JetBrains gains font-style:
normal); src URLs intentionally differ (root-absolute vs relative).
Comments added to both copies explaining the contract.
2026-05-07 11:31:14 +01:00
f4c0635549 fix(a11y): guard sidebar-toggle keypress against custom widgets and scope transcript font-size effect
- Extend isInputFocused to also bail when document.activeElement sits
  inside a custom widget with role combobox/listbox/radio/switch/menuitem/tab
  so single-letter shortcuts like '[' do not collapse the sidebar while
  focused on SegmentedButton, ZonePicker, etc.
- Move the transcript font-size CSS variable write from documentElement
  to document.body. Consumers inherit body styles, and scoping the write
  there avoids invalidating root-level styles every time fontSize changes.
2026-05-07 11:27:42 +01:00
736896c2b8 fix(a11y): announce HistoryPage inline-confirm armed state and tag-chip pressed state 2026-05-07 11:24:53 +01:00
9b17425bc9 fix(a11y): name FilesPage drop zone region and denest Card 2026-05-07 11:23:55 +01:00
d3554951e0 fix(a11y): privacy badge role/label and stronger search focus ring in light theme 2026-05-07 11:23:23 +01:00
47b005ad70 fix(a11y): bump record button to 48x48 and announce Recording stopped to screen readers
Record button bumped from 40x40 to 48x48 (WCAG 2.5.5 AA requires 44x44; 48 chosen as forgivable oversize for the most-touched control). Inner stop-square and record-circle bumped 14x14 to 16x16, Loader2 16 to 18, to keep the visual ratio.

Recording-status sr-only live region now announces both transitions: "Recording started" on start and "Recording stopped" on stop, with the stop message clearing after 1200ms so the live region does not hold stale text. Transition tracked via a plain let prevRecording (not $state) since it is bookkeeping and must not retrigger the effect. aria-atomic="true" added so the whole region re-reads on update.
2026-05-07 11:20:35 +01:00
ca3a55320b fix(a11y): bump all text below 12px to 12px floor and promote tertiary-on-body to secondary
Brand guidelines (docs/brand/magnotia-brand-guidelines.md §4) say "Never go
below 12px for any text" and "tertiary text must be ≥18px bold or ≥24px
regular". Audit found 246 sub-12px className sites and 166 cases of
text-text-tertiary paired with body sizes.

Bumped text-[9/10/11px] → text-[12px] across 26 .svelte files in src/lib
and src/routes. Promoted text-text-tertiary → text-text-secondary at body
sizes (12-14px) where context allowed; left placeholder pseudo-states,
ternary-conditional inactive states, and line-through "done" states alone
(8 residual pairings, all decorative).

Affects neurodivergent users on 1366×768 budget hardware most directly.
svelte-check: 0 errors, 0 warnings.
2026-05-07 11:18:10 +01:00
6da15d1ac8 feat(ui): wire deferred Card tone and elevation variants
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
2026-05-07 10:53:30 +01:00
9ba7babcb0 feat(settings): add quick-settings row for theme, font size, and hotkey 2026-05-07 10:52:51 +01:00
5778696140 feat(dictation): surface "Talk now, think later" empty state and hide post-transcript actions
Reduces first-run cognitive load. Save / Copy / Clear / Export now appear only
once the user has a transcript or is recording, sliding in via animate-fade-in
(brand --duration-ui + ease-out-quart). Template and Extract Tasks remain
always-visible because they shape transcription rather than acting on completed
content.

Empty state now leads with the catchphrase as a brand statement
(font-display italic 28px) with the hotkey hint demoted to body-font tertiary.
EmptyState gains an optional headline prop; callers without it render unchanged.
2026-05-07 10:49:37 +01:00
d2f64a231d feat(theme): bump accent chroma, push sensory zones apart, richer status tokens
Phase 10b colour push: theme reads as Magnotia, not "subdued generic dark".
Same lightness band as Phase 10a (AA preserved), more chroma across accent,
status tokens, and zone surfaces.

Dark accent: #c98555 -> #d68450 (subtle/hover/glow recomputed).
Light accent: #a06a3e -> #a3683a.
Dark status: success #7ec89a -> #5fc28a, danger #e87171 -> #e85f5f,
warning #e8c86e -> #e8be4a.
Light status: success #2f7549 -> #1f7344, danger #a83838 -> #b32626,
warning #b89a3e -> #a08a1f.

Sensory zones pushed further apart so each zone reads as its own
environment: cave cools toward blue-grey, energy warms toward red-orange,
reset cools toward green. AA on body text verified for every Card surface
in both themes.

Stale accent rgba (201,133,85) replaced with new (214,132,80) across nine
files. --shadow-accent in colors_and_type.css mirrored.

No secondary tint token added: Magnotia's identity is amber-as-the-only-
meaningful-colour. Cheaper to add a second meaningful colour later if a
real need appears than to dilute the discipline now.

Default surfaces, text tokens, and borders untouched per scope.
2026-05-07 10:46:35 +01:00
ec09f8ffdb feat(settings): finalise polish — autostart Toggle dedupe, AI Default/Advanced split, vocab consistency, model ETA, privacy badge, group icons
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
Six changes, one coherent polish pass on SettingsPage:

1. Autostart toggle dedupe. The 27-line custom button that mirrored
   Toggle's markup to dodge a bind:checked race is gone. Toggle's new
   async onChange + snap-back covers the OS round-trip; setLaunchAtLogin
   re-throws on failure so Toggle reverts checked. autostartSyncing
   state retired.

2. AI Assistant Default/Advanced split. Cleanup preset and GPU
   concurrency moved into a nested SettingsGroup title="Advanced",
   closed by default. The Assistant page now leads with four
   first-decision controls (tier, model picker, status, prewarm) and
   parks the tuning knobs behind a disclosure. Search keywords cascade
   from the Advanced group up to AI Assistant up to AI & Processing.

3. Vocabulary disclosure consistency. The hand-rolled Profiles and
   Templates sub-panels (with their own ChevronRight buttons +
   showProfiles/showTemplates state) are now nested SettingsGroups.
   The count badges live in the description slot ("3 profiles ·
   custom vocabulary..."). showProfiles, showTemplates, and the
   ChevronRight import retired.

4. Model-download ETA in Settings. Both whisper and LLM download
   chips now show "{percent}% · {bytes} / {total} · {duration} left",
   reusing formatDuration from utils/time.ts. New formatBytes +
   etaSecondsFromPercent helpers in SettingsPage; bytes pulled from
   the existing emit payload (DownloadProgress snake_case for whisper,
   done/total for the LLM event). FirstRunPage already does the
   timestamp-based ETA; we mirror its shape.

5. Privacy badge in sticky header. Single chip "100% local · no
   telemetry · no cloud" added next to the Settings heading using
   bg-accent-subtle + text-text-tertiary. The longer About list
   stays put as the deep-dive.

6. SettingsGroup icons on the seven top-level groups. Mic, BookOpen,
   Type, Sparkles, SquareCheck, Clipboard, Sliders — paired with the
   existing literal text titles per the icon-with-label brand rule.
   Inner nested groups stay icon-free.

Verification: npm run check returns 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:19:26 +01:00
aa1ba48e07 feat(card): add tone (subtle, danger) and elevation (raised) variant props
Both props default to current behaviour, so all existing call sites render
unchanged. Subtle uses bg-bg-elevated for quieter empty-state contexts;
danger uses bg-danger/5 + border-danger/30 for calm caution panels (not
red-alert loud, matching brand). Raised layers shadow-md on top of the
existing low-contrast shadow for floating or detached surfaces.
2026-05-07 10:09:14 +01:00
3a27031eba feat(settings-group): add optional icon prop and tighten title/description rhythm
Title bumped to font-semibold so it scans first; description gets tracking-wide
plus mt-1 for cleaner rhythm. Chevron alignment shifted from mt-0.5 to mt-1 to
match the new spacing. Optional `icon` prop renders a Lucide component between
the chevron and the title block at 16x16, text-tertiary, no colour change on
open. Layout is preserved exactly when no icon is passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:07:39 +01:00
aebf6cd149 feat(toggle): add disabled, loading, and async onChange props
Extends the shared Toggle with three optional props while keeping the
existing bind:checked path untouched for current call sites.

- disabled: dims the control, sets aria-disabled, and early-returns on click.
- loading: forces a Loader2 spinner inside the thumb, applies cursor-wait
  and aria-busy, and blocks interaction. Cursor-wait wins over disabled's
  not-allowed when both are set, matching loading-precedence guidance.
- onChange(next): when supplied, replaces the immediate flip. Promise
  returns drive an internal pending flag so the toggle renders loading
  while the handler resolves; on resolve checked flips, on reject the
  toggle snaps back. Synchronous handlers flip immediately after the call.

Brand motion preserved: 150ms duration, cubic-bezier(0.2, 0.8, 0.2, 1),
no transition-all.
2026-05-07 10:06:06 +01:00
5e3a9f9f42 fix(ui): promote modal overlay to token, lift modal above grain, dedupe FilesPage drop-zone
- Add --color-overlay-dim token to app.css @theme (rgba(15,14,12,0.7) dark, rgba(26,24,22,0.55) light); mirror as --overlay-dim in design-system/colors_and_type.css.
- MorningTriageModal: replace inlined rgba scrim with var(--color-overlay-dim), drop backdrop-blur-sm (brand opposes glassmorphism by default), bump container to z-[60] so it sits above the .grain z-50 overlay.
- FilesPage: collapse two near-identical drop-zone blocks into one bordered region with conditional inner content; preserves isDragOver hover behaviour and aria region.
2026-05-07 10:04:30 +01:00
13af425dfa fix(hotkey): surface Esc-to-cancel hint and replace recording-keyframe reuse with success flash
Two scoped fixes to HotkeyRecorder:

- Append "Esc to cancel." to the recording prompt so the existing Escape
  handler is discoverable. srStatus already mentions Escape; no harmonisation
  needed.
- Replace animate-pulse-warm on the captured state with a static
  bg-success/10 + border-success/40 flash. pulse-warm is the
  recording-active keyframe; reusing it for the saved state muddled the
  visual vocabulary. The global * transition rule already animates
  background-color and border-color over --duration-ui (150ms) with the
  brand cubic-bezier ease, so no new keyframe is needed and prefers-reduced-motion
  is honoured automatically. Also swapped transition-all for transition-colors
  to comply with the no-transition-all brand-motion rule.
2026-05-07 10:02:08 +01:00
a4cca58289 fix(history): replace native confirm() with inline two-click confirmation
Native window.confirm() is OS-styled and breaks the warm-amber UI
tone with an abrupt brutalist modal. The brand voice is "calm,
informative, solution-first; never blame the user", and confirm() is
none of those.

Replaced both destructive paths (Clear All, Delete N selected) with
a two-click inline pattern: first click arms the trigger and morphs
it into "Delete X? [Confirm] [Cancel]" inline pills; auto-cancels
after 4s so a user who walked away doesn't return to a primed delete
button. No new modal, no new component, no new dependency.

Both timers are cleared in onDestroy so unmounting the page mid-arm
doesn't leak.
2026-05-07 09:21:04 +01:00
e857a814ad fix(ui): remove side-stripe borders in favour of subtle bg tints
Side-stripe borders (border-left: 2px or 3px in an accent colour, with
the rest of the element having no border) are the textbook AI-coded-IDE
"selected-state" pattern. The brand vocabulary is colour-tint and
chevron, not stripe-and-fill.

Replaced eight sites:
- VirtualSegmentList active/match/default segment rows: bg-accent/10,
  bg-warning/10, hover:bg-hover (drop the stripes; bg-warning bumped
  /5 → /10 so it's visible without the stripe doing the work).
- viewer/+page.svelte: same pattern as VirtualSegmentList.
- TasksPage profile-list tabs: bg-accent/10 + font-medium for active,
  hover:bg-hover for inactive. rounded-md added so the tint follows
  the row outline.
- ToastViewport: replaced border-left + variant colour with full 1px
  border + bg tint + border-color tint (color-mix() with the matching
  semantic token at 35% / 10%). Also removed the orphan --moss /
  --signal / --ember tokens — they were not defined in app.css and
  fell back to hex literals.
- preview/+page.svelte: dropped the phase-coloured left stripe and
  the borderColorClass derivation; the header already has a pulsing
  dot / animated bars / spinner for each phase, so the stripe was
  redundant.
2026-05-07 09:18:54 +01:00
360e5457dc fix(copy): replace em-dashes in user-facing strings with commas/periods
Per the project's no-dashes feedback rule (see CORBEL-Main memory
feedback_no_dashes.md), em-dashes in user-visible copy are the single
biggest "AI wrote this" surface tell. Forty-two occurrences across ten
files: ten in SettingsPage's group descriptions and option labels
(audio device picker, vocabulary help, prompt placeholders, model
descriptions), four in FirstRunPage's setup steps, three in
DictationPage / FilesPage / Energy chip tooltips, plus the privacy
bullets ("100% offline, no Python required, no cloud, no accounts,
no telemetry"), the rejection toast, MicroSteps thumb labels, and the
shutdown ritual prompts.

Replacement rule: subordinate-clause em-dashes followed by lowercase
become commas; sentence-break em-dashes followed by capitals become
full stops. Code comments left intact.

One non-em-dash regression caught while reviewing: TasksPage's energy
filter "no selection" placeholder showed "—" as a literal label; the
script changed it to a comma which read as a UI bug. Replaced with
"Any" instead.
2026-05-07 09:16:37 +01:00
637ed89eac feat(settings): ship search filter that opens matching groups
The settingsSearch state was declared in Phase 9c with a comment
describing the intended behaviour but no <input> bound to it. With 21
SettingsGroup instances and a 2,261-line page, finding any specific
control required scrolling and opening every group. For the
neurodivergent audience this is exactly the cognitive-load failure
the brand commits to avoiding.

Added a sticky search input at the top of the page (paired with the
"Settings" title in a single sticky header that bg-bg-matches the
page so groups don't bleed under it). Each SettingsGroup now passes
through searchMatches against its title, description, and a curated
keyword string. Parent groups inherit their children's keywords so a
search for "GPU" expands AI & Processing and the AI Assistant inner
group together.

Updated SettingsGroup to push prop changes to the DOM via $effect:
the native <details> element's open attribute is mutated by user
clicks outside Svelte's reactive graph, so a parent prop change
needs to force-sync. User-driven toggles where the prop value didn't
change keep their local state because $effect only re-runs on diff.

Verified live: baseline shows 3 groups open (Transcription, Terms &
profiles, Capture & export), "GPU" opens AI & Processing + AI
Assistant only (other 19 closed), "ritual" opens Tasks & Rituals +
Rituals only, X-button clear restores baseline.
2026-05-07 09:13:38 +01:00
7a21d0c7fc fix(ui): sync stale shadow rgba from pre-Phase-10a amber to current accent
Eighteen sites across SegmentedButton, Toggle, HotkeyRecorder,
ModelDownloader, FilesPage, and SettingsPage hardcoded the old amber
rgba(232,168,124,...) for focus rings, glows, and progress bars. After
the Phase 10a a11y darkening of --accent to #c98555, the inline shadows
no longer matched the accent fill they were paired with, so every
focused input rendered a glow at a noticeably different hue from its
border.

Replaced all of them with the current accent rgba(201,133,85,...).

Toggle component also had two motion violations: an active:scale-95
press-shrink and a cubic-bezier(0.16,1,0.3,1) (ease-out-expo) thumb
transition. Both replaced with the brand's --ease curve
(0.2,0.8,0.2,1) and the scale dropped, in line with the record-button
fix in the previous commit.

Removed redundant inline transition-duration overrides on FilesPage
Browse button (the global * rule already sets --duration-ui) and on
ModelDownloader's primary CTA (also dropped its active:scale-[0.97]).
2026-05-07 09:07:14 +01:00
a545c7a12d fix(dictation): drop bouncy press motion and update record-button glow rgba
Record button had active:scale-[0.93] transition-all, which contradicts
the brand motion guideline ("never bounce, slow, calm, deliberate"). The
record button is the most-touched control in the app; a 7% press-shrink
on it sets the wrong tone for everything else.

Removed the scale transform, removed the inline transition-duration
override (the global * rule in app.css already animates the named
properties at --duration-ui). Also updated the inline shadow rgba from
(232,168,124,0.3) to (201,133,85,0.3) so the glow tracks the new
a11y-darkened accent fill instead of the pre-Phase-10a amber.
2026-05-07 09:05:23 +01:00
f274611d23 fix(app): scope user-select:none to chrome elements only
Body had user-select:none globally so users could not copy error
strings, model paths, profile names, transcripts, status messages,
or any text into search or another tool. The brand essence is
"clarity without friction" and this was friction.

Body now defaults to user-select:text (the standard for text content),
and the chrome elements that should never be drag-selected (button,
summary, role=button|switch|tab|menuitem, data-no-select) opt out.

Verified live: body=text, button=none, paragraph=text (inherited).
2026-05-07 09:04:10 +01:00
fdeda6c642 fix(design-system): sync colors_and_type.css to app.css authoritative tokens
The runtime app reads tokens from app.css @theme; this file ships values
to the buildless preview pages under design-system/preview/*.html.
Several values had drifted from the Phase 10a a11y darkening done in
app.css, so the previews showed brighter accents and lighter tertiary
text than what users actually see.

Aligned: dark --text-tertiary, dark --accent (+ hover/subtle/glow/
border-focus/shadow-accent), light --text-tertiary, light --accent
(+ hover/subtle/glow), light --success, light --danger.

Header banner now states the file's role explicitly so future edits
don't recreate the drift.
2026-05-07 09:03:11 +01:00
2f85ec5806 fix(settings): rename text-error/border-error to text-danger so error states actually render
The Tailwind v4 @theme block defines --color-danger as the semantic error
token, and 15+ files across the codebase use text-danger consistently.
SettingsPage was the lone outlier with five text-error / border-error
sites (audio devices, profile delete, vocabulary, term delete, diagnostic
report). Without a --color-error token, those utilities resolved to the
default text colour, so error messages rendered in warm cream instead of
red. Verified live via Playwright probe before and after.
2026-05-07 09:01:41 +01:00
d234ef394e docs(readme): refresh bundled LLM list to Qwen3.5/3.6
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
README LLM-formatting section now states four Qwen tiers (Qwen3.5 2B /
4B / 9B + Qwen3.6 27B) and the magnotia-llm crate row reflects the
four-tier registry. The whisper-ecosystem context doc gets the same
refresh and cites unsloth as the GGUF source.

Older roadmap and Phase-0 audit docs left untouched — they are dated
historical artefacts and rewriting them would muddy the audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:59:37 +01:00
0f105f0e15 chore(llm): update callers for renamed model variants
Picks up the registry rename in the front-end and Tauri command layer:

- src/lib/types/app.ts: LlmModelIdStr now lists the four new ids
  (qwen3_5_2b / qwen3_5_4b / qwen3_5_9b / qwen3_6_27b).
- src/lib/pages/SettingsPage.svelte: LLM_MODELS table rebuilt with
  four tiers (Minimal / Standard / High / Maximum), matching subtitles
  and download-size copy. selectedLlmModelId fallback, hardware-warning
  thresholds, tier-availability check, and ensureRecommendedLlmTier
  fallback all retargeted at the new ids. The Maximum tier surfaces a
  64 GB / 24 GB warning so users with mid-range hardware see honest
  expectations.
- src-tauri/src/commands/llm.rs and commands/tasks.rs: doc-comment
  examples refreshed (Qwen3 4B → Qwen3.5 4B, Qwen3's tokenizer →
  Qwen's tokenizer — the BPE family is shared).
- src/lib/stores/llmStatus.svelte.ts: chip-detail example updated.

cargo build --workspace clean. cargo test --workspace clean.
npx svelte-check reports one pre-existing error in vite.config.js
(unused @ts-expect-error directive, dates back to the original
scaffold commit 9926a42); not introduced here, out of scope to fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:59:03 +01:00
699cb7e08e feat(llm): bump model registry to Qwen3.5 + Qwen3.6 family (4 tiers)
Replaces the three older Qwen3 variants with a four-tier ladder spanning
a wider hardware range:

- Qwen3_5_2B_Q4   (Minimal, 8 GB RAM, ~1.3 GB download)
- Qwen3_5_4B_Q4   (Standard, 16 GB RAM / 6 GB VRAM, ~2.7 GB) — DEFAULT
- Qwen3_5_9B_Q4   (High, 32 GB RAM / 12 GB VRAM, ~5.7 GB)
- Qwen3_6_27B_Q4  (Maximum, 64 GB RAM / 24 GB VRAM, ~17 GB)

All four GGUFs sourced from unsloth's HF org with pinned commit SHAs.
Sizes and SHA256 hashes verified against the live X-Linked-Etag /
X-Linked-Size headers on the LFS CDN. Q4_K_M quantisation throughout
(common sweet-spot for cleanup + task extraction).

recommend_tier rewritten to span four bands; default_tier moves from
the old 4B-Instruct-2507 to Qwen3.5 4B. The 27B Maximum tier honestly
needs 64 GB RAM to run without partial offload — surfaced in the
description string so the Settings UI can warn realistically.

In-tree smoke tests (smoke.rs, content_tags_smoke.rs) updated to
reference the new smallest tier so a developer's MAGNOTIA_LLM_TEST_MODEL
points at the cheapest GGUF to download. Crate description in
crates/llm/Cargo.toml refreshed to mention the new family.

NOTE (out of scope; not fixed): the size_bytes / sha256 / hf_url
methods could collapse into a single LlmModelMetadata table to remove
four parallel match arms. Layer 2 cleanup, separate session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 09:57:21 +01:00
c42a144aad agent: audit — Phase 1 lean-pass scan deliverable (read-only, review pending)
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
2026-05-01 08:24:53 +01:00
b410c6196b docs(handovers): D1 — move dated HANDOVERs to docs/handovers/ with rebrand-origin note
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
2026-05-01 08:05:00 +01:00
10480ccbc7 docs(readme): B3 — clarify crate count (9 library + Tauri app crate) 2026-05-01 08:03:42 +01:00
16cbd7c13c docs(readme): B2 — refresh stale test count (245 → 220+ lib + 67 Tauri-app) 2026-05-01 08:03:16 +01:00
4e16dbefb4 chore(crates/llm): add missing description field per contributing rule 2026-05-01 08:02:29 +01:00
548cb3889b docs(readme): A3 + D2 — document 6 missing Tauri command modules; correct module count and call out utility modules 2026-05-01 08:02:14 +01:00
211f576ebd docs(readme): A2 — remove Moonshine claim from magnotia-core description 2026-05-01 08:00:47 +01:00
3e9739db1e docs(readme): A1 — replace fictional stores list with actual ten store files 2026-05-01 08:00:22 +01:00
Claude
7ff7295567 docs(audit): add fix-areas section to Phase 0 + Phases 1-8 playbook
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
Phase 0 update: replaces the loose "quick wins" list with a structured
Fix Areas section organised into impact tiers (A: high-impact README
truth fixes, B: low-effort self-violation fixes, C: structural smells
deferred to Phase 2, D: hygiene). Each task names the file, the change,
the verification command, and the effort estimate.

New playbook: docs/audit/phases-1-8-playbook.md — step-by-step
acquisition-grade audit procedure for the remaining seven phases. Each
phase has goal, inputs, procedure (with concrete commands), deliverable,
acceptance criteria, and time estimate. Designed to be picked up
independently from any phase.
2026-05-01 06:38:54 +00:00
Claude
0b6be94f3e docs(audit): add Phase 0 cartography report
Inventory of workspace shape, LOC, dependency graph, public API surface,
external surfaces (Tauri commands / MCP tools / frontend routes), and a
README↔code drift log. Input for Phase 1 (lean-pass).

Surfaces five concrete README drifts (one HIGH: stores list fiction; two
MED: undocumented Tauri modules and a Moonshine claim with no registry
entry) and three structural smells worth a Phase-2 follow-up.
2026-04-30 14:51:18 +00:00
Claude
89c63891fa chore: rebrand from Kon/Corbie to Magnotia
Replace all instances of the legacy product names "Kon" and "Corbie" with
"Magnotia" across user-facing copy, code identifiers, package names, bundle
ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE
terminal) reference and the parent CORBEL company name.

- Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary
- Updates package.json, tauri.conf.json (productName + identifier)
- Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations
- Renames brand and roadmap docs
- Regenerates Cargo.lock and package-lock.json

Verified: svelte-check passes; pure-rust crates compile under new names.
2026-04-30 13:06:55 +00:00
749403697a fix(ui): SettingsPage launch-at-login toggle — replace bounce-easing with ease-out-quart
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
Final impeccable detect cleanup. The launch-at-login toggle thumb in the
Tasks & Rituals section was using cubic-bezier(0.34, 1.56, 0.64, 1) — the
same overshoot bounce that was replaced in Toggle.svelte by commit
6469663. Match the convention here so all toggle animations use the
same exponential ease.

Single-line CSS change. Visual smoke not exercised because the dev
server is winding down between subagents; npm run build confirms the
file still compiles clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:52:08 +01:00
eb3d2f90ab refactor(settings): regroup SettingsPage into 7 progressive-disclosure groups
Phase 9c follow-up. The 2309-line hand-rolled accordion is replaced with
seven SettingsGroup-wrapped top-level groups, each containing the
relevant existing sub-sections as nested SettingsGroups:

1. Audio — microphone (input device).
2. Vocabulary — terms & profiles, profiles & templates (legacy manager
   moved in here so all profile / vocabulary state lives together).
3. Transcription — engine, format mode, model management, compute device,
   language. Defaults to open to preserve the prior accordion's landing.
4. AI & Processing — post-processing toggles, AI Assistant tier and
   model management, if-then implementation rules. AI Assistant and
   if-then rules moved out of their original positions to sit alongside
   the deterministic post-processing toggles.
5. Tasks & Rituals — rituals (incl. launch-at-login, kept bundled with
   the existing Rituals UI block to avoid splitting that block), tasks
   page (sparkline), nudges.
6. Output & Capture — read aloud (TTS, lazy-loaded on first open via the
   new SettingsGroup `onopen` hook), capture & export.
7. Appearance & System — global hotkey, appearance (theme/zone/font/
   locale), accessibility, about (engine status + diagnostics).

Deviations from Codex's 7-group spec:
- Launch-at-login stays inside the Rituals sub-group (was bundled there
  in the existing markup; relocating would require splitting the
  Rituals UI block, which is out of scope for this pass).
- Profiles & Templates legacy manager pulled into the Vocabulary group
  rather than appearance/system.

Implementation notes:
- SettingsGroup gains an optional `onopen` callback prop, fired once on
  the first closed→open transition. Used by Read aloud to lazy-load TTS
  voices and by AI & Processing to refresh LLM status. Replaces the
  former toggleAiSection / toggleReadAloudSection imperative handlers.
- The centralised openSection state is removed; each <details> manages
  its own disclosure.
- Tokens are inherited from app.css; the prior global token darkening
  (commit 2da0a5b) covers all section labels via the existing utility
  classes — no per-component label-class swaps were added.

Search box deferred to a follow-up commit. Forcing `open=true` on
SettingsGroup based on a filter input would require either a controlled
`open` prop or a {#key} re-mount strategy, both of which add risk to
this restructure pass.

Tauri-bridged commands cannot be exercised in browser-only `npm run
dev`; structural verification done via npm build, npm check, and a
live dev server fetch of /settings. Real persistence smoke needs a
tauri dev session.

Verification:
- cargo fmt --check: pre-existing whitespace diffs in src-tauri/src/
  live.rs and src-tauri/src/lib.rs only (untouched by this commit).
- cargo clippy --all-targets -- -D warnings: clean.
- cargo test: 283 tests pass.
- npm run check: 1 pre-existing error in vite.config.js:5; 0 new.
- npm run build: clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:50:28 +01:00
6469663c25 fix(ui): impeccable detect — perf + taste touch-ups across four components
Mechanical fixes from `npx impeccable detect`:

- Sidebar.svelte: replace `transition: width, min-width` on the aside
  with a wrapping CSS-grid container animating `grid-template-columns`.
  Avoids per-frame layout cost from animating `width` directly.
- MorningTriageModal.svelte: swap pure `bg-black/50` overlay for the
  brand deep-neutral `rgba(26, 24, 22, 0.5)` (#1a1816 @ 50%). TODO left
  in source to promote this to a `--color-overlay` token in app.css.
- Toggle.svelte: drop bouncy `cubic-bezier(0.34, 1.56, 0.64, 1)`
  (1.56 overshoot) for ease-out-quart `cubic-bezier(0.16, 1, 0.3, 1)`.
  Still snappy, no overshoot — better fit for a toggle.
- TasksPage.svelte: same grid-template-columns refactor as Sidebar
  for the list-sidebar `transition: width` declaration.

Verification:
- npm run check: 1 pre-existing error (vite.config.js:5), 1 unrelated
  warning in SettingsGroup (out of scope, owned by parallel subagent).
- npm run build: clean.
- cargo clippy --all-targets -- -D warnings: clean (with LIBCLANG_PATH).
- cargo test --workspace: 283 passed, 0 failed.
- cargo fmt --check: pre-existing diffs in main.rs / lib.rs (no Rust
  files were touched in this commit).

Manual smoke deferred — `npm run dev` is in use by the SettingsPage
subagent, so the build-clean signal is the proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:48:14 +01:00
22d554e85b fix(a11y): T4 / T7 / H3 — energy chip + filter pill + per-row checkbox
Phase 10a a11y audit (2026-04-29):

T4: Energy radio chip text in dark mode read at 3.48:1 (text-text-tertiary
  on chip bg). Bumped non-selected chips to text-text-secondary so the
  hierarchy still reads but the resting state clears AA.

T7: Selected energy radio (and the match-my-energy toggle next to it)
  used bg-accent/15 — visually indistinguishable from surrounding bg in
  dark theme. Bumped to bg-accent/25 and added a 1px accent/30 border on
  the selected state so the selected pill is unambiguous in both themes.

H3: Per-row bulk-select checkbox in HistoryPage rested at opacity-50,
  which drops the unchecked outline below 3:1 over bg-bg-card. Bumped
  base opacity to 70%; hover/selected states unchanged.

Note on T6 (energy radiogroup arrow keys): verified the keyboard handler
in TasksPage. It is attached to the wrapper element with role=radiogroup,
and arrow-key events on the focused radio child bubble up to the wrapper
because the focused radio sits inside it. The handler reads
settings.currentEnergy (not the focused element) to pick the next index,
then focuses the new radio explicitly via querySelector. ArrowRight /
ArrowLeft / ArrowUp / ArrowDown / Home / End all wired correctly. No
change needed — pattern is sound.

Resolves: T4, T7, H3. T6 verified working as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:04:51 +01:00
fa734c869f fix(a11y): P5 / G4 — add focus trap to MorningTriageModal
Phase 10a a11y audit (2026-04-29) flagged the morning triage modal as
having role="dialog" and aria-modal="true" but no focus trap, so Tab
leaks out to the sidebar/page beneath. Escape-to-close was already
wired and is preserved.

Behaviour now matches the W3C dialog pattern:

- On open, capture the invoking element (document.activeElement) and
  move focus to the first focusable inside the dialog.
- Tab cycles forward; Shift+Tab cycles backward. Wrap-around between
  first and last focusable.
- On close, restore focus to the captured invoker.
- The dialog container itself gets tabindex=-1 so it can hold focus
  if no children are focusable (e.g. during the loading state).

The focusable selector excludes aria-hidden elements and elements with
no offsetParent (display:none / visibility:hidden), so dynamic content
between loading -> tasks -> action buttons stays in sync.

Resolves: G4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:04:32 +01:00
d089fdb37f fix(a11y): P3 — drop focus:outline-none on inputs; restore global :focus-visible
Phase 10a a11y audit (2026-04-29) flagged seven inputs across the app
that strip the global 2px :focus-visible outline (defined in app.css:251)
without providing a comparable replacement. Net effect: keyboard users
see at most a 1px border-colour shift on focus, sometimes nothing.

The fix removes the focus:outline-none override so the global rule
applies. Affected inputs:

- FilesPage: file-transcript textarea (F2).
- TasksPage: search input, quick-add input, inline list-edit, new-list
  input (T1, T2, T3, plus the new-list rename input).
- HistoryPage: top search, inline title rename, tag-add (H1).
- ImplementationRulesEditor: trigger/surface/task selects + speak-line
  input (S7).
- TaskSidebar, WipTaskList: quick-add inputs (S8).

The 1px focus:border-accent on inputs that had it is retained as a
secondary cue; the global 2px ring is now the primary indicator and
matches button focus across the app.

Resolves: F2, T1, T2, T3, H1, S7, S8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:04:23 +01:00
2da0a5bab8 fix(a11y): P1/P2/G3/FR3 — darken tertiary/accent/success/danger tokens to meet AA
Phase 10a a11y audit (2026-04-29) flagged 14 contrast failures rooted in
four tokens. Single token-level change resolves them across both themes.

P1 (text-text-tertiary): #8a8578 → #6b6557 light, #716b60 → #8c8678 dark.
  Token was used as both decorative tertiary and body-grade label, so it
  must clear AA 4.5:1. Lifts ratios from ~3.3-3.7:1 to ~4.7-5.0:1 across
  the surfaces it appears on (sidebar tagline + footer, dictation footer,
  files hint, tasks empty state, history empty state, settings descriptions
  and section labels, shutdown trail copy, first-run skip links).

P2 (color-accent): #b87a4a → #a06a3e light, #e8a87c → #c98555 dark. White
  text on accent fill (Browse Files button, selected segmented pills,
  link-style buttons) now clears AA. Dark theme worst case was 2.03:1 on
  Browse Files; the new dark accent clears 4.5:1 with white. Subtle/hover/
  glow tokens recomputed off the new bases.

G3 (color-success light): #3d8a5a → #2f7549. Sidebar Ready status text
  and other success copy on cream surfaces clears AA.

FR3 / D4 (color-danger light): #c44d4d → #a83838. Browser-mode warning
  and hardware-probe error copy clears AA on cream.

Resolves: G1, G2, G3, D1, D2, D3, D4, F1, F3, F4, T5, H2, S1, S2, S3,
SD2, FR2, FR3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:03:37 +01:00
jars
be5a7146ca Merge pull request #10 from jakejars/claude/android-target
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
Add Android support with platform-specific feature gating
2026-04-25 19:23:29 +01:00