Commit Graph

111 Commits

Author SHA1 Message Date
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
b333c6229e chore(hardening): tighten security and footprint defaults 2026-04-24 19:03:57 +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
b344e8a580 fix(a11y): Phase 3 follow-up — implement ARIA radio-group keyboard pattern for energy selector
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
Codex post-implementation review flagged one MAJOR: the energy segmented
control declared `role="radiogroup"` / `role="radio"` but only wired
`onclick`. No arrow-key navigation, no Home/End, no roving tabindex.
Keyboard users got four independent tab stops while assistive tech was
told it was a single radio group — a broken ARIA contract.

Fix (W3C APG Radio Group pattern):
- Extract the options list as `energyOptions` so the render loop and
  the keyboard handler share one source of truth.
- `energyRadioKeydown` handles ArrowLeft/Right/Up/Down (cycle wraps),
  Home (first), End (last).
- Roving tabindex: the currently-checked button gets `tabindex=0`,
  the rest get `tabindex=-1`, matching the APG recipe. Focus moves
  with selection.
- The radiogroup container gets `tabindex="-1"` to satisfy the
  svelte-check a11y rule without creating its own tab stop.

All green: 251 tests, clippy -D warnings, fmt, svelte-check 0/0, build.
2026-04-24 14:58:50 +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
5c17544a63 fix(sounds): fall back to default volume on out-of-range input
resolveVolume previously clamped any value >=1 to full blast. If a
future settings change ever leaks a 0–100 scale through (instead of
0–1), the user gets jump-scared at max volume. Treat any v>1 as a
scale-drift bug and play at DEFAULT_VOLUME instead.

Also reject NaN / Infinity explicitly rather than relying on the
<=0 branch catching them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:32:51 +01:00
ce2b4fdac6 feat(ai B.1 #15 + A.1 #28): cleanup presets and sequential-GPU guard
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
Two new Settings → AI knobs that compose cleanly with what already
shipped (aiTier, LLM model, translator prompt framing).

**B.1 #15 — Named cleanup presets.** LlmPromptPreset enum
(Default / Email / Notes / Code) appends a short context hint onto
the CLEANUP_PROMPT just before generation. Presets shape tone and
structure ("email paragraph", "bulleted meeting notes", "preserve
technical terms") without licensing the content-editing the
translator-not-editor framing forbids. cleanup_transcript_text_cmd
now takes `preset: Option<String>` which runs through the new
LlmPromptPreset::parse (normalises aliases like "meeting-notes",
collapses unknown values to Default).

**A.1 #28 — Sequential-GPU guard.** New LocalEngine::unload drops
the backend + model_id so a subsequent load actually reclaims VRAM.
load_llm_model, load_model, and load_parakeet_model Tauri commands
grow an optional `concurrent: bool` argument. When concurrent is
Some(false), loading LLM first unloads whisper+parakeet, and vice
versa — prevents VRAM OOM on tight-VRAM setups. Default is the
previous parallel behaviour so nothing changes for multi-GB cards.
Transcribe-in-progress paths (transcribe_pcm, transcribe_file, live)
pass None, so mid-dictation model loads don't accidentally tear
down the LLM.

Settings UI (AI section):
- Cleanup preset segmented button + descriptive copy for each option.
- GPU concurrency segmented button with explicit trade-off text
  ("faster transitions vs fits in tight VRAM").

Frontend wiring:
- settings.llmPromptPreset flows from DictationPage's
  cleanupTranscriptIfEnabled into the Tauri command.
- settings.aiGpuConcurrency flows from both DictationPage (auto-load
  on record) and SettingsPage (manual load/unload buttons) as
  `concurrent: "parallel" === true` to the load commands.

Tests: three new preset cases in crates/ai-formatting/src/llm_client.rs
(parse aliases, suffix non-empty for non-default, default suffix
empty). All 139 existing lib tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:12:48 +01:00
a57da0feb5 feat(llm B.1 #27): Test LLM button with classified error diagnostics
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 brief's pain point is opaque load failures: llama-cpp-2's errors
bubble up as raw C++ strings ("cudaMalloc failed: out of memory",
"invalid gguf magic"). A user seeing that has no path to recovery.

New backend command test_llm_model runs a staged diagnostic:
  1. Model not downloaded → `not-downloaded` + download hint.
  2. File size ≤90% of expected → `incomplete` (stalled download)
     + re-download hint. Matters because llama-cpp-2 can segfault
     on truncated GGUF rather than returning cleanly.
  3. Requested model already loaded → `ready`, no side effects.
  4. Otherwise attempt a real load. On failure, classify_llm_load_error
     maps the raw string to one of:
       - load-failed-vram         (OOM / cudaMalloc / allocation)
       - load-failed-corrupt      (GGUF magic / unsupported format)
       - load-failed-permission   (permission denied / access denied)
       - load-failed-other        (catch-all)
     Each category has a prewritten actionable hint pointing at the
     specific Settings surface (tier picker, re-download, file perms).

classify_llm_load_error is pure-string and unit-tested — 8 cases
covering the main categories plus edge cases (OOM alias, Windows
"Access is denied", unknown errors). Ordered narrow-to-broad so
overlap doesn't misclassify.

Settings UI gets a "Test" button in the AI section's action row,
visible whenever the model is downloaded (both downloaded-idle and
loaded states). Shows inline hint below the status line when the
test surfaces one. Refreshes both local and global LLM status after
the test since a successful test implicitly loads the model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:04:11 +01:00
70b97c5273 feat(ui B.1 #20): sound cues on start / stop / complete via Web Audio
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
Hands-off feedback for recording lifecycle: a short C5 pitch at
record start, a falling G4 at record stop, and a staggered C5–E5–G5
major third when the finalise flow completes. Synthesised at runtime
via the Web Audio API (OscillatorNode + linear-ramped GainNode
envelope) rather than shipping WAV assets — keeps the binary size
flat and lets us tweak timbre without touching bundled files.

Off by default. Settings → Output exposes the toggle with a volume
slider (0–100%, default 15%) and a "Test" button that plays the
completion cue so the user can confirm loudness without recording.

Hooked at three call sites in DictationPage:
- playStartCue after page.recording = true in startRecording
- playStopCue at the top of stopRecording
- playCompleteCue just before `saved = true` at the end of
  finaliseTranscription's transcript-present branch

All three no-op when settings.soundCues is false. The Web Audio
context is lazily constructed on first cue (most browsers suspend
it until a user gesture — Tauri's webview inherits that). If the
AudioContext can't be built we silently drop the cue rather than
throwing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:48:44 +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
ae4c1e3c6d feat(preview B.1 #17): raw-transcript revert button in preview overlay
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
Kon's ideology rule: raw Whisper output is the source of truth; LLM
cleanup is additive, never destructive. The preview overlay already
tracks both rawText and finalText across the listening → live →
cleanup → final phases — but until now the user had no one-click path
from final to raw if cleanup changed their meaning.

Frontend: a context-aware "Use raw" / "Copy raw" button appears in
the preview overlay's final phase, only when rawText and finalText
actually differ (Raw format mode or LLM-off leaves the button hidden).
Two behaviours depending on how the transcript reached the target:

  - settings.autoPaste = true  →  invoke paste_text_replacing, which
      sends the platform's undo keystroke to the focused app,
      waits UNDO_PASTE_GAP_MS (60 ms) for the compositor / app to
      process it, then pastes the raw transcript. The preview hides
      itself beforehand so the keystroke doesn't race focus
      (existing Wayland-hardening path).
  - settings.autoPaste = false →  nothing was pasted in the first
      place, so just overwrite the clipboard with raw. User's own
      paste yields raw.

Backend: new paste_text_replacing Tauri command plus a mirror of the
paste-backend matrix for undo (wtype -M ctrl z / xdotool key ctrl+z /
ydotool keycodes 29:1 44:1 44:0 29:0 / osascript cmd+z / SendKeys '^z').
Reuses the pick_linux_backend_order Wayland-vs-X11 preference.
Registered in the Tauri command handler.

Acceptance per the brief: "after paste, Ctrl+Z within 5 s replaces
LLM output with raw transcript" — satisfied via the 4 s auto-hide
window on the preview's final phase. The click extends auto-hide so
the user actually sees the confirmation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:29:43 +01:00
8ba5641451 feat: add design system handoff to feat/design-system branch
Adds colors_and_type.css token system, fonts (Lexend, Instrument Serif Italic,
JetBrains Mono, Atkinson Hyperlegible Next, OpenDyslexic), SVG assets (wordmark,
waveform mark, grain), HTML preview spec cards, UI kit, and SKILL.md reference
under src/design-system/. Foundation for applying the new Kon visual identity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:03:49 +01:00
Cursor Agent
0338495a57 fix(vocab): dedupe bulk import case-insensitively within the paste
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
Previously the bulk import ran new Set(...) on raw trimmed strings
before lowercasing, so 'ACME' and 'acme' both survived the first
dedupe pass. Neither existed in the store, so both got added —
defeating the commit message's claim that pasting the same block
twice with different casing is a no-op.

Collapse case variants at the initial dedupe step using a lowercase
seen-set, keeping the first occurrence's casing as written.

Co-authored-by: jars <jakejars@users.noreply.github.com>
2026-04-21 16:00:03 +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
Cursor Agent
8e5e034df1 feat(B.1 #4 UX): debounce hotkey press events by 120ms
A rapid double-tap of the global hotkey, evdev autorepeat, or a
sticky-key compositor quirk (KDE's 'slow keys') can all deliver
the same press twice within ~100ms. Without a guard, the recording
toggles into and out of the same frame and the capture is lost.

Gates the evdev 'kon:hotkey-pressed' forwarder in +layout.svelte
behind a 120ms debounce (Date.now()-based; no timers, so no tail
latency for a legitimate single press). The debounce is intentionally
shorter than a deliberate double-press cadence but longer than any
autorepeat interval we've seen in the wild.

The audio-stream-warming half of brief item #4 (Handy #1143) lives
in Workstream A's Phase A.3 warm-up WAV; this covers the UX side.

Co-authored-by: jars <jakejars@users.noreply.github.com>
2026-04-21 15:59:01 +01:00
Cursor Agent
cc3bffa72c feat(B.1 #11): versioned settings schema with forward migration
Adds src/lib/utils/settingsMigrations.ts exposing
loadSettingsWithMigration() / saveSettingsWithVersion() around a
{version, data} envelope in localStorage["kon_settings"]. The
migration chain is indexed by destination version so adding a v2
is one MIGRATIONS[2] = (prev) => next entry away from working.

Legacy bare-object settings blobs are treated as v0 and folded into
v1 identically to before — no user-facing reset — but an unreadable
blob now surfaces a single Settings reset toast instead of silently
dropping data.

Covers Handy #602 ('settings reset on update') and unblocks the
other B.2/B.3 SettingsState additions listed in
docs/whisper-ecosystem/workstream-B.md: every subsequent field
lands behind a MIGRATIONS step, so older Kon builds stay readable.

Co-authored-by: jars <jakejars@users.noreply.github.com>
2026-04-21 15:59:01 +01:00
42335c04c5 feat(vocab): bulk import for profile terms
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
Settings → Vocabulary gets a "Bulk add from a list…" disclosure under the
single-term row. Expanding reveals a textarea; paste newline- or
comma-separated terms, hit Import, and the page loops addTerm for each
entry the active profile doesn't already have.

Dedupes case-insensitively against the existing term list so pasting the
same block twice is a no-op. Skipped + failed counts surface via toast;
persistent errors (any failing term) also land in vocabularyError so the
inline panel explains what went wrong.

Covers OpenWhispr issue #460 — one-at-a-time entry becomes friction past
roughly ten terms. No backend changes; addTerm is already in profilesStore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:40:18 +01:00
eb60a8bfd3 feat(preview): floating transcription overlay with listening→live→cleanup→final phases
Ported the best bits of OpenWhispr's TranscriptionPreviewOverlay into Kon's
window conventions. Off by default — toggle in Settings → Output → "Floating
preview when Kon is unfocused". Opens only when the main window isn't
focused at the start of a recording, so it never adds noise when the user
can already see the transcript in the main surface.

Phase state machine (src/routes/preview/+page.svelte):
- listening — pulsing dot, no text yet
- live      — animated bars + streaming raw Whisper output
- cleanup   — accent bars while the LLM cleanup pass runs
- final     — checkmark + formatted text + 4s auto-hide

Data plumbing: raw segment text is captured before post_process_segments in
live.rs (new raw_text field on LiveResultMessage) and in transcription.rs
(new raw_text in the transcription-result payload). DictationPage forwards
raw_text to the overlay via Tauri global events — preview-listening on
start, preview-append per chunk, preview-cleanup before the LLM pass,
preview-final with the formatted text, preview-hide when a run produced no
transcript (empty recording / cancel).

Window is always_on_top, skip_taskbar, focused=false so it never steals
focus from whatever the user is dictating into. open_preview_window shows
an existing hidden preview or builds it fresh; close_preview_window hides
without destroying so the next open is instant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:49:29 +01:00
ba0d59f563 feat(meeting): opt-in process-list reminder when a meeting app starts
Default off. When on, the layout polls detect_meeting_processes every 15s
with the user's app-name patterns. On a fresh match (edge-triggered — no
re-toast until the app goes away and comes back) we fire a reminder toast
that tells the user which meeting app appeared and their global hotkey. We
never start recording on this signal; the ideology rule says the user
decides. The signal is a single channel: process list match only — no mic
activity heuristic, no calendar.

Backend adds kon_core::process_watch::{list_running_process_names,
match_meeting_patterns} over sysinfo, exposed to the frontend as the
detect_meeting_processes Tauri command.

Settings ships two new fields — meetingAutoCapture (bool) and
meetingAutoCaptureApps (string[]) — with a comma-separated input in the
Output section. Default app list is ["zoom", "teams"], user-editable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:54:55 +01:00
b8baa65bd2 feat(i18n): scaffold svelte-i18n with en/es/de locales and language selector
initI18n (src/lib/i18n/index.ts) registers three locales and picks the
initial one in order: kon_locale in localStorage > navigator.language short
code > en. +layout.svelte calls it once at app boot; guarded so per-window
re-init is a no-op.

Locale files are deliberately sparse — this is a scaffolding pass so strings
can be migrated incrementally. The Settings → Appearance → Language picker
plus its own description is the first real consumer; everything else
continues to render as hardcoded text until extracted.

Also: split the @chenglou/pretext ambient shim into src/lib/shims.d.ts. The
declaration previously lived in app.d.ts alongside a top-level `export {}`,
which made app.d.ts a module — scoping `declare module` to its own imports
and breaking resolution from src/lib/utils/textMeasure.ts. The fresh
.svelte-kit sync triggered by installing svelte-i18n surfaced it. Ambient
shim files must stay script-scoped (no top-level imports/exports).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:49:03 +01:00
4c0c876ade feat(paste): auto-insert transcript at cursor via wtype/xdotool/ydotool/osascript/SendKeys
Adds an opt-in "auto-paste into focused window" toggle. When enabled, the
dictation pipeline sets the clipboard and then sends a Ctrl+V / Cmd+V
keystroke to whatever window currently has focus — the common case after a
global-hotkey dictation, since Kon's own window never stole focus.

Backend (src-tauri/src/commands/paste.rs) probes for a platform paste tool
and falls back cleanly:
- Linux Wayland: wtype > ydotool > xdotool
- Linux X11: xdotool > ydotool > wtype
- macOS: osascript System Events keystroke
- Windows: PowerShell WScript.Shell SendKeys

detect_paste_backends is a pure probe used by Settings to describe the
available backend next to the toggle (or nudge the user to install one).
paste_text always copies first, so auto-paste failure degrades to the
existing clipboard-only behaviour and surfaces a warn toast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:45:16 +01:00
36efcf2320 feat(onboarding): parakeet-as-default pinned by test; FirstRunPage handles distil ids
Parakeet-TDT scores 85 on any GPU-equipped English-capable system (Instant
speed + Great accuracy + GPU boost + headroom) vs ~75 for the best distilled
Whisper. A new test in recommendation.rs locks this in so future scoring
tweaks don't silently regress it.

FirstRunPage previously stored settings.modelSize by title-casing a lowercased
alias — which worked for Tiny/Base/Small/Medium but produced
"Whisper-distil-small-en" for the new distil ids. Swap to an id→label map
and pass the raw model id through to download_model/load_model; the backend
already accepts full ids via the whisper_model_id fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:41:22 +01:00
4561810751 feat(whisper): add Distil-Whisper Small and Large v3 as first-class models
Two new registry entries (crates/core/src/model_registry.rs):
- whisper-distil-small-en — 336 MB, ~6× faster than whisper-small-en
- whisper-distil-large-v3 — 1.55 GB, near large-v3 accuracy at medium size

Both are whisper.cpp-compatible GGML binaries hosted on HF by the
distil-whisper org; no runtime change, just wider model choice. English-only
by design (matches upstream Distil-Whisper).

The Settings model picker widens to six options — Tiny, Base, Small,
Distil-S, Medium, Distil-L — ordered roughly by accuracy. Download/load
commands now take the resolved model id (whisper-distil-*) instead of the
lowercased label, so the frontend owns the label↔id mapping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:39:03 +01:00
d1eb56fac9 feat(llm): wire Phase 3 local LLM runtime via llama-cpp-2
kon-llm now owns a real LlamaBackend + LlamaModel, with three Qwen3 tiers
(1.7B Q4, 4B-Instruct-2507 Q4, 14B Q5) selectable per hardware. Downloads
are resumable with SHA-256 verification and stored under ~/.kon/models/llm.

Engine exposes three high-level surfaces — all greedy/temp-0, GBNF-constrained
where output shape matters:
- cleanup_text (prompt-injection-hardened system prompt; profile terms
  appended as "preserve these spellings" suffix)
- decompose_task (3–7 micro-steps, constrained JSON array)
- extract_tasks (optional-array; empty when no explicit commitments)

post_process_segments now takes an Option<&LlmEngine> and, when loaded and
format_mode != Raw, joins segments → cleanup → replaces segments with the
cleaned text (first segment span). Rule-based path still runs first; LLM
errors log and keep rule-based output.

Tauri commands: recommend_llm_tier, check_llm_model, download_llm_model,
load_llm_model, unload_llm_model, delete_llm_model, get_llm_status,
cleanup_transcript_text_cmd, extract_tasks_from_transcript_cmd,
decompose_and_store (LLM-backed subtasks).

Settings: AI tier toggle (off / cleanup / tasks), model picker with
downloaded/loaded status, download progress events via
kon:llm-download-progress.

Dictation: ensureLlmModelLoaded on mount, cleanupTranscriptIfEnabled after
stop when tier != off and format_mode != Raw, LLM task extraction when
tier=tasks (regex fallback on failure).

Interim: both llama-cpp-sys-2 and whisper-rs-sys statically link their own
ggml, so src-tauri/build.rs emits -Wl,--allow-multiple-definition on Linux.
Replace with a system-ggml shared-lib setup as a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:31:51 +01:00
34fce3cf9e feat: OpenWhispr-inspired transcription polish pass
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
Major quality pass on top of Phase 2. Five substantive changes plus
cross-cutting touches across audio, hotkey, transcription, and Tauri
command layers.

  Transcription quality

  - Long-audio chunking in commands/transcription.rs: Parakeet and large
    file transcription now chunk-and-recompose with overlap trimming, so
    the live-path chunking advantage extends to file-based workflows.
  - Stateful live speech gate in commands/live.rs on top of the earlier
    duplicate-boundary filtering — distinguishes start-of-speech from
    mid-speech and holds state across chunks.

  Auto-learning corrections

  - New crates/ai-formatting/src/correction_learning.rs: extracts user
    text corrections from viewer edits and proposes additions to the
    active profile's vocabulary.
  - src-tauri/src/commands/profiles.rs bridge for frontend-driven
    confirmation of learned terms.
  - src/routes/viewer/+page.svelte hooks the learning path into the
    segment-edit flow so corrections feed profile_terms without a
    separate 'train this profile' UX.

  Transcript profile provenance

  - Migration v8 (crates/storage/src/migrations.rs) adds profile_id to
    transcripts, defaulting to DEFAULT_PROFILE_ID so existing rows stay
    valid.
  - crates/storage/src/database.rs: TranscriptRow + CRUD carry profile_id.
  - src-tauri/src/commands/transcripts.rs: add_transcript accepts and
    persists profile_id.
  - DictationPage.svelte + FilesPage.svelte send activeProfileId on
    capture so learned corrections are attributed to the right profile.

  Cleanup prompt contract

  - crates/ai-formatting/src/llm_client.rs hardened: the CLEANUP_PROMPT
    now specifies concrete do/do-not rules, ready for a real model-backed
    cleanup pass. The llm_client is still a stub — kon-llm remains unwired
    — but the prompt shape is final.

  Cross-cutting polish

  - Minor touches in audio (capture/decode/resample), hotkey (lib/linux/stub),
    core, transcription (concurrency/model_manager/local_engine/whisper_rs),
    and the rest of src-tauri/src/commands/*: error-path tightening, log
    clarity, TS-migration follow-ups (@ts-nocheck additions for incremental
    typing).

Verified locally: npm run check, cargo test -p kon-ai-formatting,
cargo test -p kon-storage, cargo test -p kon --lib commands::live::tests,
cargo check — all green.

Scope boundary: kon-llm crate is still a stub; task extraction remains
rule-based. Bundled local-LLM runtime is the next clean step and is not
in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 22:39:08 +01:00
28acdcfa6d fix(dictation): show user's configured hotkey in hints, not hardcoded Ctrl+Shift+R
Both the status-line hint next to the record button and the empty-state
message now read settings.globalHotkey reactively, so 'Press record or
Super+E' (or whatever the user has bound) stays in sync with the actual
shortcut.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:53:07 +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
7474cd24ba feat(ui): profile picker + per-profile vocabulary; transcribe invokes carry profileId; drop buildInitialPrompt 2026-04-19 20:57:46 +01:00
7b804eacba feat(store): profiles store with create/update/delete/active selection 2026-04-19 20:53:43 +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
e248923f5d feat(transcripts): migration v5 meta — starred, tags, template, language, segments persisted 2026-04-19 16:35:17 +01:00
9378980639 feat(tasks): persist list_id/effort/notes + update_task_cmd — close Task 2 metadata gap 2026-04-19 16:23:25 +01:00
db5c739f22 refactor(state): drop kon_tasks localStorage cache — Tauri-first, UI-on-success 2026-04-19 16:14:49 +01:00
6113e6d784 refactor(viewer): drop kon_history localStorage; route edits through update_transcript 2026-04-19 16:06:19 +01:00
5e3bc369de refactor(state): drop kon_history localStorage cache — SQLite canonical 2026-04-19 15:59:04 +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
f54d55d110 fix(tasks): add explicit parentTaskId field to addTask shape — makes !t.parentTaskId filter intent clear 2026-04-19 10:55:30 +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