Commit Graph

51 Commits

Author SHA1 Message Date
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
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
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
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
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
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
dd45f10cd4 polish(phase9): sparkline + badge motion and a11y
Sparkline: friendlier aria-label ("3 completed today. 14 total over
the last 7 days." rather than a numeric list), per-bar <title>
tooltips with absolute date + count, 30 ms stagger entrance via
scaleY animation. Badge: 180 ms opacity + translateY entrance on
mount; conditional render means each new badge re-fires the
animation. Both animations respect prefers-reduced-motion.

The earlier draft tabindex=0 on the SVG was correctly flagged by
svelte-check as noninteractive_tabindex; SVG role="img" + aria-label
is sufficient for SR navigation without putting it in the keyboard
tab order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:17:53 +01:00
6269aab0d2 feat(phase9): SettingsGroup component
Reusable progressive-disclosure wrapper around the native <details>
element. Animated chevron, hover + focus-visible affordances, and
prefers-reduced-motion guard. Designed to host the six collapsed
groups in the Phase 9 SettingsPage restructure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:14:32 +01:00
54ddd41265 feat(gamification): CompletionSparkline component
Tiny inline SVG. Seven bars, zero-days render as 1 px baseline stubs.
fill=currentColor so the parent's text colour (tertiary ink on the
Tasks header) drives it. Self-hides if all 7 days are zero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:41:51 +01:00
6cd1c22c0f feat(intentions): Phase 7 — if-then rules for task / time / triage triggers
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
Small if-then automation layer. Rules persist in SQLite; the runner
lives on the frontend and binds to the Phase 6 event bus so the rule
pipeline reuses the same delivery primitives (timer events, TTS,
Tasks navigation).

Storage:
- Migration v12 adds implementation_rules (id, enabled, trigger_kind,
  trigger_value, actions_json, last_fired_key, created_at,
  updated_at) with enabled+trigger_kind index for the runner's hot
  path.
- CRUD helpers: insert / list / get / set-enabled / mark-fired /
  delete, plus a round-trip test.

Commands (all main-window-guarded via ensure_main_window):
- list_implementation_rules
- create_implementation_rule — validates HH:MM, checks the target
  task exists at save time for surface-task actions, caps
  speak-line at 240 chars, pins v1 timers to 5 minutes.
- set_implementation_rule_enabled
- mark_implementation_rule_fired — main-thread idempotency shim so
  the runner can atomically claim a fire.
- delete_implementation_rule

Runner (implementationIntentions.svelte.ts):
- Subscribes to kon:task-completed and kon:morning-triage-finished
  (MorningTriageModal now emits on all three exit paths — empty,
  skipped, picked — so skip counts as finishing).
- 30 s poll for time-of-day rules, plus an immediate check on startup
  so a rule whose time has already passed today catches up once.
- Idempotency via last_fired_key composed as YYYY-MM-DD@HH:MM for
  time rules; new time rules whose HH:MM has already passed today
  are pre-seeded so they don't fire retroactively on save.
- Rules are paused when Nudges "Mute for now" is on — a hard mute
  stops all rule delivery in addition to OS notifications.
- Stale-task safety: if a surface-task action's target has been
  deleted, the runner opens Tasks and warns clearly rather than
  pretending to surface something that's gone.

Editor (ImplementationRulesEditor.svelte):
- Lives in Settings under a new "If-then rules" accordion section.
- `If` picker: time of day (with time input), a task completes,
  morning triage finishes.
- `Then` composer: optional surface (inbox / today / all tasks /
  specific task), optional 5-min timer, optional speak-aloud line.
- Saved rules list with enable toggle + delete.

Rules table integration for Phase 10b rename sweep: add
implementation_rules to the kon.db → corbie.db migration shim when
that phase lands.

Gates: fmt, clippy -D warnings, cargo test 265/0, svelte-check
0/0, npm build green. Pre-existing Vite chunk warning on sounds.ts
is unrelated to Phase 7.
2026-04-24 19:27:06 +01:00
eebea8cb9a feat(nudges): Phase 6 — Margot soft-touch nudges via frontend nudge bus
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
Frontend-owned nudge bus that consumes in-app signals Corbie already
produces, applies suppression, and fans out to OS notification + an
optional TTS read-aloud. OS-wide keyboard/window activity detection
stays deferred per the revised roadmap — the plan before rewrite
would have been brittle on Wayland, permission-heavy on macOS, and
low-quality everywhere.

Triggers (v1, all in-app signals):
- inactivity_with_active_timer — timer running, window blurred ≥ 90 s,
  at least 60 s into the timer.
- pending_morning_triage — ritual enabled, past 10:00 local, last
  shown ≠ today. Polls every 5 min while focused.
- micro_step_idle — micro-step decomposition created, no child step
  or parent task completed within 15 min.

Suppression:
- Respects nudgesEnabled + nudgesMuted.
- No nudge while the app has focus (document.hasFocus).
- Hard cap 3 per rolling hour.
- Permission requested via @tauri-apps/plugin-notification on first
  delivery; denial is silently respected.

Rust side:
- tauri-plugin-notification registered + ACL entries on the main-
  window capability only (secondary windows can't fire nudges).
- commands::nudges::deliver_nudge — thin wrapper, security-guarded
  via ensure_main_window, delegates to the plugin. No DB writes —
  the roadmap's nudges-audit table is deferred until a concrete need
  emerges.

Frontend glue:
- nudgeBus.svelte.ts — subscribes to window events, applies
  suppression, calls deliver_nudge (+ tts_speak when speakAloud is
  on).
- kon:task-completed now dispatched on complete_task_cmd success.
- kon:microstep-generated + kon:step-completed dispatched from
  MicroSteps so the idle trigger can clear itself on any engagement.
- kon:focus-timer-cancelled added to focusTimer so the bus can reset
  its inactivity state on cancel, not only on natural completion.
- nudgeBus started from +layout.svelte onMount, stopped on destroy.

Settings:
- New "Nudges" section with three toggles: Enable nudges, Mute for
  now (separate so a hard mute doesn't lose preferences),
  Speak nudges aloud (reuses Phase 4 TTS).
- All default OFF. No first-run prompt — nudges are a Settings-found
  feature rather than a walkthrough step.

Out of scope per the revised Phase 6 spec: OS-wide keyboard/window
hooks, biometric signals, custom trigger editor (Phase 7), notification
sound (platform variance too high for Layer-1 — revisit in Phase 9
polish with a bundled .wav).

Gates: fmt clean, clippy -D warnings clean, cargo test 262/0 (4
existing + 262 current), npm run check 0/0, npm run build green.
2026-04-24 19:05:21 +01:00
3cf3e41899 feat(rituals): Phase 5 — morning triage, evening wind-down, autostart
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
Three opt-in rituals, all default OFF. Research-anchored (Barkley's
point-of-performance, Sweller cognitive-load theory, Newport shutdown
ritual, Gollwitzer implementation intentions, Thaler/Sunstein nudge
with informed consent for the ADHD audience).

Morning triage: modal gated on ritualsMorning toggle, configurable
trigger time (default 08:00 to respect ADHD sleep inertia rather than
the spec's 06:00), "pick up to three for today" with a gentle swap
message on the fourth attempt. Skip sets last-shown-today so it never
re-prompts the same calendar day. last-shown persists via kon_storage.

Evening wind-down: dedicated page, user-triggered only (tray menu +
Settings button). Mechanical closure + physical reset + intentional
cue — the whole Newport template. Open loops are read-only reflection;
Tasks page owns transactions. Copy is additive throughout: "you
finished X today", never "you didn't finish Y".

Autostart: tauri-plugin-autostart registered (LaunchAgent on macOS,
.desktop on Linux, registry Run on Windows). No bespoke Rust commands
— frontend calls the plugin's invoke-handlers directly. Toggle in
Settings is one-way (click → OS call → state update) to avoid the UI
lying during the round-trip. First-run presents a forced-choice prompt
for all three options, with "skip all" escape hatches per step.

Copy audit against RSD literature: no "overdue", "failed", or
day-to-day comparison framing anywhere in ritual surfaces.

Post-v0.1 ideas captured in the roadmap: calendar integration
(read-only ICS as interim, cloud sync parked) and right-click-to-task
(in-app simple, system-wide a separate phase).
2026-04-24 17:48:01 +01:00
9f53702c7e feat(tts): Phase 4 — Read Page Aloud with OS-native voices
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
Platform-dispatched TTS (spd-say + espeak-ng fallback on Linux, say on
macOS, PowerShell System.Speech on Windows) with a shared SpeakerButton
component. Tap to speak, tap again to stop; only one button speaks at
a time so two surfaces don't talk over each other. Text always travels
via argv (or a PowerShell here-string delivered through -EncodedCommand
on Windows) so user content never enters a shell string.

Mount points: DictationPage transcript footer, transcript viewer header,
per-step in MicroSteps. Settings gains a "Read aloud" accordion with
voice picker (lazy-loaded from the OS synth), rate slider 0.5-2.0x,
and a British-English test utterance.

Rust tests cover rate mapping, NaN handling, and Windows here-string
terminator safety. No pause/resume, no SSML, no cloud voices — that
stays out of scope per the Layer-1 roadmap.
2026-04-24 16:01:47 +01:00
1d4f1070a2 feat(energy): Phase 3 — match-my-energy task sort + tri-state tag column
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
Closes Phase 3 of the 2026-04-23 feature-complete roadmap. Incorporates
the Codex plan-review fixes from this session: profile-free index, tri-
state update command, and de-prioritise-not-hide semantics.

Storage (kon-storage):
- Migration v11 adds `energy TEXT` to `tasks` with a CHECK constraint on
  `high | medium | brain_dead | NULL`. Index `(energy, created_at DESC)`
  — deliberately not per-profile because the tasks table carries no
  profile_id column yet (tracked as a separate gap in HANDOVER).
- `TaskRow.energy: Option<String>` plus `task_row_from` read.
- `insert_task` signature grows by one optional arg (`energy`). Allowed
  `too_many_arguments` with a rationale comment — the positional shape
  matches the column order and flipping to a params struct would have
  rippled through every caller for cosmetic benefit only.
- New `set_task_energy(pool, id, Option<&str>) -> TaskRow`. Lives as its
  own function because `update_task` uses COALESCE to let `None` mean
  "preserve" — which would make clearing the tag impossible.
- Two new tests: round-trip including explicit NULL clear, and CHECK
  constraint rejection of unknown values.
- Tests updated for the v10 → v11 version bump.

Tauri (src-tauri):
- `TaskDto.energy`. `CreateTaskRequest.energy` (optional). Inline
  validation against the allowed set before hitting the DB, so frontend
  bugs surface as friendly errors instead of CHECK-constraint failures.
- New `set_task_energy_cmd` command mirroring the storage tri-state API.

Frontend (svelte):
- `EnergyLevel` type added to `types/app.ts`. `TaskDto`, `TaskEntry`, and
  `TaskDraft` grow an `energy` field.
- `SettingsState.currentEnergy` (persisted) + `matchMyEnergy` (persisted
  toggle). Defaults: null + false — no surface change until user opts in.
- `setTaskEnergy(id, EnergyLevel | null)` action on the task store.
  Calls the dedicated Tauri command, updates local state, broadcasts to
  sibling windows.
- `EnergyChip.svelte` — new component. Cycles unset → High → Medium →
  Brain-Dead → unset on click. Colour tokens: accent / warning /
  text-tertiary (deliberately not danger-red for Brain-Dead — the brief
  is explicit that this state must not feel pathologised).
- Chip rendered on every task row in TasksPage and every row in
  WipTaskList. Hidden-until-hover when energy is unset so untagged rows
  stay calm; always visible once tagged because the colour is the signal.
- Tasks page header gains a "I feel" segmented control and a
  "Match my energy" toggle. When both are active, matching tasks sort
  to the top — unset tasks are treated as Medium-equivalent. Nothing is
  ever hidden; this is a de-prioritisation, not a filter.

Deferred / out of scope:
- LLM-driven surfacing (brief says "The AI surfaces...") — deterministic
  client-side sort is v1; LLM layer is a later phase.
- tasks.profile_id + per-profile energy sort — separate migration.

All green: cargo build + 251 tests + clippy -D warnings (0 warnings)
+ fmt + svelte-check (0/0) + npm run build.
2026-04-24 14:53:19 +01:00
d307722c7a fix(feedback): Phase 2 follow-up — Codex review MAJORs + NIT
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
Independent review surfaced three majors and one nit. All actioned.

MAJOR 1 — profile scoping:
`decompose_and_store` and `extract_tasks_from_transcript_cmd` now
accept an optional `profile_id` (wired from `profilesStore.activeProfileId`
in MicroSteps.svelte and DictationPage.svelte), and thread it into the
feedback-retrieval query so per-profile decomposition styles do not
leak into each other. `record_feedback` gets the same treatment.

MAJOR 2 — prompt-budget regression on long inputs:
New `trim_to_budget` helper + `FEW_SHOT_CHAR_BUDGET = 2000` char cap
in `src-tauri/src/commands/tasks.rs`. Retrieval still pulls up to 5
rows but they are char-counted and truncated against the budget
before being sent to the LLM. Char cost matches the `Input: ...\n
Good output: ...` render path so the budget maps cleanly to ~570
Qwen3 tokens, well inside the 8192-context reserve after the 512-
or 768-token response allocation. Oldest-first drop order (iteration
stops at cost exceeded) preserves the most recent correction which
is the one carrying the user's live preference.

MAJOR 3 — inline edit stale-rollback race:
`saveEdit` in MicroSteps.svelte now stamps a monotonic per-step
`saveToken`. Each edit bumps the token; on failure the rollback
only fires if `saveToken[step.id] === myToken`, so a slow-failing
first save can no longer overwrite a faster successful second save.

NIT — retrieval ordering stability:
`list_feedback_examples` ORDER BY now `created_at DESC, id DESC`.
SQLite timestamp precision is one second; without the secondary
key, bursty feedback within the same second would select
non-deterministically.

Also: malformed `context_json` now warns via eprintln! rather than
disappearing silently — Codex minor.

All green: cargo build + 249 tests + clippy -D warnings + fmt
+ svelte-check (0/0) + npm run build.
2026-04-24 13:32:52 +01:00
46be0a5aca feat(feedback): Phase 2 — HITL thumbs + correction capture with prompt-conditioning loop
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
Closes the human-in-the-loop gap from docs/brief/feature-set.md and
Phase 2 of the 2026-04-23 feature-complete roadmap.

Storage (kon-storage):
- Migration v10 adds the `feedback` table: (target_type, target_id,
  rating, original_text, corrected_text, context_json, profile_id,
  created_at) with CHECK constraints on target_type and rating, plus
  indexes on (target_type, rating, created_at DESC) for prompt-time
  retrieval and (profile_id, target_type, created_at DESC) for
  per-profile scoping.
- New public API: `FeedbackTargetType`, `RecordFeedbackParams`,
  `FeedbackRow`, `record_feedback`, `list_feedback_examples`.
- Tests updated — the RB-02 rollback regression now discovers the
  real max version at runtime instead of hard-coding v10 for its
  poison migration.

LLM (kon-llm):
- `prompts::FeedbackExample` — local shape for few-shot exemplars so
  kon-llm stays independent of kon-storage.
- `prompts::build_conditioned_system_prompt` — appends a "here is
  the style this user prefers" block to the base system prompt
  when examples are available; returns the base prompt unchanged
  when empty, so new users and early sessions see generic output.
- `LlmEngine::decompose_task_with_feedback` and
  `LlmEngine::extract_tasks_with_feedback` thread examples through
  to the builder. The old one-arg variants are preserved and now
  call through with an empty slice.
- 4 unit tests covering empty, empty-input-skip, correction-wins,
  and thumbs-up-only fallback.

Tauri (src-tauri):
- New commands::feedback module: `record_feedback`,
  `list_feedback_examples_cmd`.
- `decompose_and_store` and `extract_tasks_from_transcript_cmd`
  now fetch the last 5 positive/neutral feedback rows for their
  target type and pass them through to the LLM, wiring the
  learning loop end-to-end.
- Shared `to_llm_examples` helper parses the `context_json.input`
  field (where the recorder stashes the parent task text / transcript
  chunk) back into the exemplar shape.

Frontend (MicroSteps.svelte):
- Thumbs-up and thumbs-down buttons on every micro-step row.
  Hover-revealed; the vote recolours the icon; clicking again
  clears the local highlight (the row itself stays in the audit
  trail).
- Pencil icon + double-click to edit step text. Save flows through
  update_task_cmd for persistence and records a correction feedback
  row with (original_text, corrected_text) — the highest-value
  training signal.
- Parent task text is captured in context_json.input at record time
  so the prompt builder can reconstruct the (input, preferred-output)
  pair on subsequent decompositions.
- Feedback capture is best-effort — a record_feedback failure never
  interrupts the primary action.

What's deferred to a later phase:
- Thumbs + corrections on extracted tasks (same pipeline, different
  surface — probably TasksPage after the AI-extraction path)
- Thumbs on transcript cleanup output
- Semantic retrieval over the feedback corpus (once there is enough
  data to justify embedding infrastructure; the storage shape is
  already ready for it)
2026-04-24 12:53:51 +01:00
f25f8db818 feat(focus-timer): integrate with float window + add pop-out button
Jake's feedback on Phase 1: make the timer pinnable / always-on-top,
combined with the existing Now-list pop-out. Two changes:

1. Mount <FocusTimer /> in src/routes/float/+layout@.svelte so the
   running countdown stays visible in the always-on-top float window
   alongside the WIP task list. No content change to the float page
   itself — the timer is a global overlay.

2. Add a pop-out icon to the main-window focus timer that opens the
   existing /float route via window.open. One click → timer + Now
   list pinned on top without touching main window focus. Hidden
   inside the float window itself (detected via URL) so you cannot
   recursively pop out.

Result matches the Todo float-out UX the user already knows:
click ExternalLink, you get a small always-on-top window with
tasks + a live countdown ring in the top-right.
2026-04-24 12:06:37 +01:00
0c34a29367 feat(focus-timer): Phase 1 — visual countdown ring + just-start timer
Closes the Core MVP gap in docs/brief/feature-set.md ("visual time
representation") and wires the dangling kon:start-timer emit that
MicroSteps.svelte has been firing into the void since the stub was
written. Implements phase 1 of the 2026-04-23 feature-complete
roadmap.

New:
- src/lib/stores/focusTimer.svelte.ts — singleton timer store with
  localStorage persistence so a timer started in Dictation survives
  page nav, window close, and reopen. Gentle WebAudio chime at
  completion (no bundled asset). 250 ms tick. Completion flash for
  3 s before auto-clear.
- src/lib/components/FocusTimer.svelte — floating top-right overlay
  with SVG progress ring (shrinking colour: accent -> warning in
  the final 15% -> success on completion). Cancel + "+1 min" on
  hover. Renders nothing when idle.

Wired in +layout.svelte next to ToastViewport.

Two triggers now in the app:
- MicroSteps row 2-min button (pre-existing emit, previously no
  listener)
- WipTaskList row 5-min button (new; the brief's "just-start"
  from the Now column)

Respects prefers-reduced-motion via the existing
[data-reduce-motion] attribute.

Out of scope, carried to later phases: rhythmic voice anchoring
(Phase 6), custom-duration picker, multi-timer UI, native OS
notification (deferred to Phase 6 with the full nudge pipeline).
2026-04-24 11:50:45 +01:00
ad311d278f feat(sidebar B.1 #31): visible LLM status chip with live state
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
The LLM runtime has been quiet since it shipped in Phase 3 — users
had no surface-level signal that cleanup was loaded, warming, or
actively generating. Settings has verbose status text internally,
but a dictation-flow user never opens Settings during a run.

New: a shared $state store drives a small chip in the sidebar that
reflects the true LLM state in ≤500 ms of any transition (brief
item #31 acceptance). Five states:

  off        → hidden (user has aiTier === "off")
  warming    → model download or first load in flight; amber pulse
  ready      → loaded + idle; green dot
  generating → cleanup_transcript_text_cmd or
               extract_tasks_from_transcript_cmd in flight; accent
               pulse with Sparkles icon
  error      → last operation failed; red dot with AlertTriangle

The store exposes three calls: refreshLlmStatus(aiTier) (polls the
backend), markGenerating(detail) / markGenerationDone(success).
DictationPage wraps its cleanup + extract calls in mark-generating
pairs. SettingsPage's LLM load / unload / delete / download paths
also refresh the global store so Settings-initiated transitions
surface in the sidebar immediately. The chip collapses to a
dot-only compact form when the sidebar is collapsed.

No backend changes — everything wires onto the existing
`get_llm_status` Tauri command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:40:02 +01:00
Cursor Agent
df58d98adc feat(B.1 #5): per-OS hotkey capability matrix with inline rejection copy
Adds src/lib/utils/hotkeyValidity.ts with validateHotkey(combo, os)
and wires it into HotkeyRecorder.svelte. Rules:

  - X11/Wayland/Linux: reject single-key combos unless the trigger
    is F13..F24 (the conventional global-shortcut escape hatch).
  - Windows: reject combos whose only modifier is a right-hand
    variant (RCtrl/RAlt) — matches Handy #966's failure mode where
    RegisterHotKey silently ignores them.
  - macOS: reject Fn-only combos and bare-key combos (non-function
    keys).
  - unknown OS: pass through — better to ship a flawed combo than
    reject one we can't validate.

When validation fails, the recorder leaves the previous known-good
hotkey intact and surfaces an inline warning-coloured sentence
explaining why, with actionable copy ("add a modifier", "use the
left-hand equivalent", etc.). The save button disappears because
settings.globalHotkey is never written to — no state drift.

Matches Handy #917 / #1019 / #966 / #956, brief item #5.

Co-authored-by: jars <jakejars@users.noreply.github.com>
2026-04-21 15:59:01 +01:00
5e09ab9bd3 fix(hotkey): track modifier state manually so Super+key records on KDE Wayland
webkit2gtk under KDE Wayland does not set e.metaKey on subsequent events
after the compositor intercepts Super — but the Super keydown itself is
still delivered with e.key === "Super". Track pressed modifiers from
raw keydown/keyup and OR with the webkit booleans so combos like Super+E
(which OpenWhispr already registers successfully via evdev) can now be
captured in Kon's recorder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:49:08 +01:00
39c967c33b fix(hotkey): resolve physical-key codes; Super-as-modifier; surface backend registration errors
Three correctness fixes to the hotkey recorder based on dogfood logs:

1. Modifier set now includes Super / OS / Hyper (webkit2gtk on Linux
   fires e.key === "Super" for the Windows key — previously that key
   got captured as the final trigger, producing invalid 'Ctrl+Shift+Super'
   strings the evdev parser rejected).

2. resolveTriggerKey() uses e.code (physical, shift-independent key)
   to resolve shifted punctuation back to the unshifted name the evdev
   parser understands: '+' -> '=', '|' -> '\\', etc. Letters and digits
   also use e.code (KeyA -> A, Digit1 -> 1) to avoid layout quirks.

3. Numpad keys intentionally not mapped to main-keyboard equivalents —
   they are distinct evdev codes. Leaves the parser to reject them so
   the user gets a toast instead of a silently-wrong binding.

Registration failures now surface as a toast and revert
settings.globalHotkey to the last successfully-registered value (if
any), so the UI cannot lie about what is actually bound.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:36:59 +01:00
d6bf9ed245 refactor(frontend): migrate JS modules to TypeScript
Wholesale JS -> TS migration of the frontend — stores, utils, actions,
and all Svelte component scripts adopt type annotations. Compile-time
surfaces (app.d.ts, lib/types/) added for shared DTO types.

Build plumbing:
  - package.json: dev:frontend script that runs svelte-kit sync first
  - tauri.conf.json: beforeDevCommand points at dev:frontend
  - run.sh: dropped the sed-hack that temporarily blanked beforeDevCommand;
    now relies on npm run dev:frontend to avoid double-Vite
  - jsconfig.json: allowImportingTsExtensions

Preserves all Group 1 behaviour:
  - page.svelte.ts keeps loadHistory / loadTasks Tauri-first, no
    localStorage; saveTranscriptMeta + mapTranscriptRow + mapTaskRow
    intact; update_task_cmd and update_transcript_meta_cmd invocations
    carry the correct payload shape.
  - Toasts, preferences stores typed without behaviour change.
  - Viewer still routes segment edits through saveTranscriptMeta; the
    Task 1.5 TODO markers are gone.

taskExtractor.ts is functionally improved during the migration:
  - multi-task matches in the same sentence
  - list-style shopping-verb expansion (get bread, milk, and cheese)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:05:54 +01:00
27d85a0b28 fix(tailwind): move ResizeHandles CSS to app.css global sheet
@tailwindcss/vite:generate:serve threw 'Invalid declaration:
getCurrentWindow' on ResizeHandles.svelte?svelte&type=style&lang.css
even after comment sanitisation. The style block is plain CSS with no
Tailwind directives, but the per-component virtual CSS module route
was hitting a parse bug in the Tailwind v4 + Svelte 5 combination.

Workaround: move the CSS into app.css (class names are already
component-specific, no scoping loss) and drop the component-local
<style> block. This sidesteps the virtual-module route entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:42:20 +01:00
ab88dab82a fix(tailwind): strip remaining apostrophes from ResizeHandles comments
Follow-up to 92ac7ea. Two apostrophes remained (KWin's and don't)
that kept tripping Tailwind v4 JIT the same way. Removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:22:47 +01:00
92ac7eada3 fix(tailwind): strip all backticks from ResizeHandles comments — Tailwind v4 JIT parses backtick-wrapped text as CSS declarations
Follow-up to d1d344b. Tailwind's scanner still saw backtick-quoted
fragments like `decorations: false` and `position: fixed` in the
script comments as CSS property declarations, tripping on the next
JS identifier (getCurrentWindow). Removing all backticks from the
comment block sidesteps the entire scanner path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:13:41 +01:00
d1d344b3dd fix(tailwind): reword ResizeHandles comment to avoid apostrophe+backtick in Tailwind JIT scan
Tailwind v4 JIT scanner mis-tokenised the comment 'Tauri`s `data-tauri-drag-region`'
as an unterminated string literal, breaking dev-server HMR. Comment rewritten
to avoid apostrophes near backticks. No behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:43:09 +01:00
ea48d03cee agent: dogfood polish 2026/04/19 — Linux native chrome + History redesign + mic picker cleanup
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
Second dogfood sprint. Headline fix: Linux now uses native KWin/Mutter
decorations instead of fragile frameless `startResizeDragging`, which
collapsed diagonal corner resize to a single axis and made drag feel
laggy. macOS / Windows keep custom chrome via `useCustomChrome` gate.

Other changes:

- Cross-window preferences sync via `kon:preferences-changed` Tauri
  event — theme and font changes propagate live to float/viewer.
- Hotkey recorder rewritten to use capture-phase document listener
  gated by $effect; button focus was unreliable in webkit2gtk.
- History page redesigned for cognitive-load hygiene: title-first
  compact row, inline title input, Edit popout opening /viewer in
  edit mode, clipboard export as .md with YAML frontmatter, manual
  tag chips + + Add tag input, header tag filter (cap 7), global
  Starred filter, `tag:xyz` search syntax.
- `deriveAutoTags` kept as empty hook for post-Task-7 LLM topic tags;
  research found all previous auto-tag chips redundant with row UI.
- Viewer window adds edit mode with debounced-save textarea; native
  title renamed to "Kon - Transcription Editor".
- Window minimums updated per GNOME HIG + WCAG reflow research:
  main 960x600, float 360x480, editor 560x520.
- Microphone picker filters raw ALSA strings (hw:, plughw:, front:,
  sysdefault:, null) and dedupes by CARD=X. New `description` field
  on DeviceInfo reads /proc/asound/cards so Blue Yeti shows as "Blue
  Microphones" instead of the short "Microphones" card name.
- GPU reporting fixed: get_runtime_capabilities now returns
  accelerators=[cpu,vulkan] and whisper.supports_gpu=true, matching
  the transcribe-rs whisper-vulkan feature linked unconditionally.
- ResizeHandles kept for macOS/Windows frameless: 12px edges, 20px
  corners via CSS vars, pointerdown + setPointerCapture, corners
  above edges in z-order, rendered as sibling (not child) of the
  animated layout root so `position: fixed` is viewport-relative.
- Dueling drag-region handlers removed — `data-tauri-drag-region` and
  manual `startDragging()` were stacked on the same elements; kept
  the manual handler which has the button/input early-return logic.

See HANDOVER.md for the full session log and deferred items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:30:42 +01:00
674fc2c4b8 feat(ui): add MicroSteps expand/collapse to WipTaskList; exclude subtasks from WIP count 2026-04-19 10:51:11 +01:00
442fa6656e feat(ui): add MicroSteps component with decompose button, step checklist, and Just Start timer 2026-04-19 10:49:01 +01:00
f384641097 fix(frontend): commit missing utility modules (P0 — imports resolved to nothing)
textMeasure.js, virtualList.js, and accessibilityTypography.js were
added in the 2026/04/04 session alongside VirtualSegmentList.svelte but
never committed. All four are imported from DictationPage, HistoryPage,
SettingsPage, and the viewer — a fresh clone had unresolvable imports.

textMeasure.js: pretext-backed text measurement with LRU cache.
  measureTextHeight, measurePreWrap, clampTextLines, invalidateCache.
virtualList.js: binary-search visible range for variable-height virtual
  lists. buildCumulativeOffsets + findVisibleRange.
accessibilityTypography.js: pretext font/line-height helpers that read
  the user's accessibility preference store (Lexend / Atkinson /
  OpenDyslexic mapping).
VirtualSegmentList.svelte: virtualised transcript segment renderer used
  by the viewer page; depends on all three utils above.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 09:03:17 +01:00
69d768e803 ui: Day 3 — global toast system + first error-toast wiring on DictationPage
THE PROBLEM (Codex review + architecture-review.md §1, §14):
Multiple critical paths discard errors silently — `let _ = insert_transcript(...)`,
`.catch(() => {})`, inline `error = ...` state that not every page surfaces. ND
users in particular need explicit feedback when something fails; silent
failure is worse than no feature.

SHIPPED:

src/lib/stores/toasts.svelte.js (new):
- Minimal in-house toast store (~80 lines, no svelte-french-toast dep)
- Severity → palette: info/success → moss, warn → signal, error → ember
- Auto-dismiss durations per severity (success 3s, info 4s, warn 6s,
  error sticky)
- `invokeWithToast(invoke, command, args, errorTitle?)` helper wraps a
  Tauri invoke and toasts on failure while still throwing for callers
  that need to handle the error themselves

src/lib/components/ToastViewport.svelte (new):
- Reads from the toasts store
- Bottom-right stack, max-width clamp on small screens
- aria-live=polite + role=alert on error toasts (screen-reader friendly)
- Slide-in animation honours `html.reduce-motion` for the existing
  preferences toggle
- Severity left-border in brand colours via existing CSS variables

src/routes/+layout.svelte:
- Mounts <ToastViewport /> once at the app root so toasts work from any
  page or window

src/lib/pages/DictationPage.svelte:
- First error-toast wiring: failures starting recording now show a
  sticky toast in addition to the inline `error` panel
- Replaces a previously easy-to-miss failure-mode

NEXT (Day 4 batch):
- Wire toasts into FilesPage, HistoryPage, SettingsPage, ModelDownloader
  (all currently swallow errors)
- Add `add_transcript`, `list_transcripts`, `update_transcript` (closes the
  long-standing TODO Codex flagged), `delete_transcript` Tauri commands
  wrapping the existing storage layer
- Switch addToHistory to dual-write (localStorage + SQLite) so the
  canonical store catches up with the actual transcript flow
- FTS5 transcripts_fts virtual table + search command
- Wrap multi-row writes (decompose_and_store) in DB transactions
2026-04-17 13:06:36 +01:00
fdc4a3cba5 agent: fix — infinite reactivity loop in TaskSidebar froze entire app
prevTaskIds was declared as $state, causing the $effect that writes to
it to re-trigger itself infinitely (effect_update_depth_exceeded). The
variable is just a comparison reference — doesn't need to be reactive.
Changed to a plain let.

Also removed debug event loggers and reverted grain z-index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:56:54 +01:00
jake
32677e785b agent: components — add WIP task list and visual timer components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:46:53 +00:00
jake
5ba4606de9 agent: components — add accessibility controls panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:46:53 +00:00
jake
ea99b4285d agent: components — add sensory zone picker with live preview
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:46:53 +00:00
jake
1e6e22d455 agent: components — add shared EmptyState component
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:46:53 +00:00
jake
4bbe9a0a84 agent: components — restyle Card, Toggle, SegmentedButton with brand tokens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:44:49 +00:00
jake
f1a9fe8383 agent: titlebar — restyle with brand tokens, add compact variant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:44:49 +00:00
jake
3f6d718bb2 feat(kon): collapsible sidebar — toggle with [ key, auto-collapse below 900px, icons-only mode
- Add sidebarCollapsed to settings defaults (persists via localStorage on saveSettings)
- Sidebar collapses to 48px icons-only view with smooth 200ms transition
- Toggle button (chevron) at top of nav; [ keyboard shortcut in layout (input-safe)
- Auto-collapse on window resize below 900px (checked on mount and on resize event)
- Titlebar left spacer tracks sidebar width reactively (48px / 210px)
- All labels, profile selector, status text hidden in collapsed mode; nav items show tooltips via title attribute

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:26:08 +00:00
jake
ad31249e88 fix(kon): remove all Ramble references — titles, exports, copy, version bump to v1.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:09:51 +00:00