9 Commits

Author SHA1 Message Date
4700668df1 docs(readme): refresh test count 136 -> 245
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
2026-04-24 09:53:26 +01:00
4e947dec21 docs: 2026-04-23 handover + test count refresh (136 -> 245) 2026-04-24 09:52:23 +01:00
509b983c09 chore(deps-dev): bump vite (dependabot, npm_and_yarn group) 2026-04-24 09:44:13 +01:00
0b1c492edd chore(deps-dev): bump @sveltejs/kit (dependabot) 2026-04-24 09:44:13 +01:00
6579c5fb6a chore(deps-dev): bump picomatch (dependabot) 2026-04-24 09:44:13 +01:00
fe61661305 chore(lint): clean up clippy warnings across workspace
Auto-applied cargo clippy --fix across 11 files — needless return,
unnecessary cast, map_or simplification, repeat().take() → repeat_n(),
iter().any() → contains(), manual char comparison, lifetime elision,
push_str single-char, reference immediately dereferenced.

Also fixed three lints on file_storage.rs manually: two doc-list-item
overindentations, plus the same needless-return. Baseline main was
not clippy-clean with -D warnings before; after this pass one
needless_range_loop warning remains (live.rs:1089) that clippy's
suggested rewrite would make less readable — left for a dedicated
refactor session.

Build + workspace tests remain green (245 passing, 0 failing, 1
ignored).
2026-04-24 09:43:56 +01:00
dependabot[bot]
f8c9769e04 chore(deps-dev): bump picomatch
Bumps the npm_and_yarn group with 1 update in the / directory: [picomatch](https://github.com/micromatch/picomatch).


Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 15:10:24 +00:00
dependabot[bot]
becbf69c35 chore(deps-dev): bump vite in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.4.1 to 6.4.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.2
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 15:10:23 +00:00
dependabot[bot]
b8b953dfa8 chore(deps-dev): bump @sveltejs/kit
Bumps the npm_and_yarn group with 1 update in the / directory: [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit).


Updates `@sveltejs/kit` from 2.55.0 to 2.57.1
- [Release notes](https://github.com/sveltejs/kit/releases)
- [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.57.1/packages/kit)

---
updated-dependencies:
- dependency-name: "@sveltejs/kit"
  dependency-version: 2.57.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 15:10:15 +00:00
16 changed files with 256 additions and 115 deletions

97
HANDOVER-2026-04-19.md Normal file
View File

@@ -0,0 +1,97 @@
---
name: handover-2026-04-19
type: reference
tags: [handover, session, kon]
description: Session handover — 2026/04/19 dogfood polish + cross-platform window chrome
---
# Kon Handover — 2026/04/19
Second dogfood sprint. Four phases: (1) fix bugs surfaced on first real use, (2) redesign History for cognitive-load hygiene, (3) resolve broken window resize/drag on Linux Wayland, (4) clean up microphone picker.
## What shipped this session
### Cross-window preferences sync
- `preferences.svelte.js` emits `kon:preferences-changed` Tauri event on update.
- Main / viewer / float layouts listen and call `applyExternalPreferences` without re-emit, so theme and font changes propagate live across sibling windows.
- Echo suppressed via source window label check.
### Hotkey recorder
- Root cause of "can't change hotkey": button-level `onkeydown` relied on post-click keyboard focus, which webkit2gtk on Linux does not guarantee.
- Fix: `document.addEventListener("keydown", ..., { capture: true })` inside a `$effect` gated by `recording`. Beats any descendant handler. Escape now cancels.
### History page redesign (research-backed)
- Compact row now shows the **title** (or "Untitled"), not body-preview text — metadata already lives in the row columns (date, duration, source icon).
- Expanded row gets an inline title input (replaces the old Rename prompt modal).
- **Edit** button opens the viewer window in `edit` mode (editable textarea, debounced save to localStorage + storage-event sync back to main history).
- **Export .md** copies a full YAML-frontmatter markdown document to the clipboard — paste into Obsidian.
- **Tags**: `$lib/utils/frontmatter.js` exposes `deriveAutoTags` (currently returns `[]`), `buildFrontmatter`, `serialiseFrontmatter`, `buildMarkdown`. Manual tags stored as `item.manualTags`, rendered as removable chips in the expanded row with `+ add tag` input.
- Header tag chip bar (cap 7, click to filter, × to clear), plus `tag:xyz` search syntax.
- Global **Starred** filter toggle in the History header.
- Research memo found all five previous auto-tag families redundant with existing row UI — kept the derivation hook for the post-Task-7 `topic:*` content tag from kon-llm.
- Duplicate-transcript render fix: expanded `<p>` only if compact preview actually truncated.
### Viewer / editor popout
- `/viewer` route now reads `kon_viewer_mode` from localStorage ("view" | "edit").
- Edit mode renders a plain textarea bound to `item.text`; 400ms debounced save flushes on input, final flush on `onDestroy`. Segment-specific controls (Compact, Starred) hidden in edit mode.
- Native title: **"Kon - Transcription Editor"**.
### Platform-aware window chrome (Linux fix)
**Root cause:** Tauri v2 frameless `decorations: false` on KDE Wayland + webkit2gtk does not honour diagonal corner resize (collapses `NorthEast` etc. to a single axis via GTK's `gtk_window_begin_resize_drag`), and `data-tauri-drag-region` adds noticeable drag latency. Setting `setPointerCapture` ahead of `startResizeDragging` does not help once the compositor has taken over the pointer grab. Verified via Context7 docs + Codex diagnosis — Linux frameless is a known-fragile path.
**Fix:**
- Linux uses **native KWin/Mutter decorations**. `src-tauri/tauri.linux.conf.json` overlays `decorations: true` + full main window config (title, sizes) — overlays **replace** the windows array, so every field must be present, not just the delta. `src-tauri/src/commands/windows.rs` uses `cfg!(target_os = "linux")` to set decorations per window.
- macOS / Windows keep custom chrome. `src/lib/utils/osInfo.js` `isLinux()` gates `<Titlebar>` and `<ResizeHandles>` via `useCustomChrome = $state(false)`; flips to `!isLinux()` after `loadOsInfo()` resolves.
- Dueling drag-region handlers removed across Titlebar, float page, viewer page — everywhere a manual `startDragging()` lives, the `data-tauri-drag-region` attribute was deleted (they're alternatives per Tauri docs, not combinable).
- `ResizeHandles` kept for macOS/Windows frameless: 12 px edges / 20 px corners via CSS vars (`--kon-resize-edge`, `--kon-resize-corner`), `pointerdown` + `setPointerCapture`, corners with explicit higher z-index. Handles rendered as siblings of the animated layout div so `position: fixed` is viewport-relative rather than captured by the transform containing block.
### Window minimum sizes (evidence-backed)
Research pass cited GNOME HIG (1024×600 desktop / 360×294 mobile floors), WCAG 2.2 SC 1.4.10 Reflow (320 CSS px), Raycast 750×474 as a reference for single-pane working width, and consistent A11y principle that nothing should clip in the default configuration.
| Window | Was | Now | Rationale |
|---|---|---|---|
| Main | 1020×540 | **960×600** | Fits 210 px sidebar + ~750 px content; GNOME vertical floor. |
| Float | 400×400 | **360×480** | 360 = GNOME mobile floor; 480 fits pills + quick-add + sort + ~6 task rows without scroll. |
| Transcript editor | 450×500 | **560×520** | Exceeds WCAG reflow floor; ~60-70 char measure for editing. |
### Microphone picker cleanup
- ALSA enumeration was leaking `hw:`, `plughw:`, `front:`, `sysdefault:`, `null` et al into the dropdown.
- `SettingsPage.svelte` now renders only sentinel devices (`default`, `pipewire`, `pulse`) + one entry per unique sound card, keyed off the `sysdefault:CARD=X` alias.
- `crates/audio/src/capture.rs` reads `/proc/asound/cards` and populates a new `description` field on `DeviceInfo` with the card's full product string (e.g. "Blue Microphones" for Jake's Yeti). Frontend prefers description → CARD=X short name → raw name.
### GPU reporting
- `commands/models.rs::get_runtime_capabilities` was hardcoded to `accelerators: vec!["cpu"]` and `supports_gpu: false` for whisper. Updated to `["cpu", "vulkan"]` and whisper `supports_gpu: true`, reflecting that `crates/transcription/Cargo.toml` links transcribe-rs with the `whisper-vulkan` feature unconditionally.
- Settings now shows the Vulkan option instead of the "This build is CPU-only" notice.
### Desktop shortcut
- `~/Desktop/Kon.desktop` launcher with the 128×128 icon, `Terminal=true` so logs are visible and Ctrl+C cleanly stops the run.sh wrapper.
## What's deferred
- **Transparent windows (`transparent: true`)** — Tauri issue #13270 reports this smooths drag/resize further on Linux, but it's moot now that Linux uses native decorations.
- **File-system export (.md save dialog)** — currently clipboard-only. Needs a Rust `write_text_file` command for plugin-less file writes.
- **Bulk select + bulk export** in History.
- **LLM-powered content tags** (`topic:*`, `intent:*`) — slots into Task 7 `kon-llm` stub once Phase 3 wires real llama-cpp-2.
- **Settings UX overhaul** — Jake flagged that current settings feel overwhelming. Proposed: bunch high-traffic settings, hide advanced behind a toggle. Brainstorm + plan deferred to a dedicated session.
- **Task 7 (MicroSteps end-to-end)** — storage + Tauri CRUD + kon-llm stub + frontend dual-write all landed in an earlier commit chain. The MicroSteps UI was written as the final task 7 step but not yet dogfooded against the stub LLM. Needs manual walkthrough.
## Gotchas discovered today
| Issue | Fix |
|---|---|
| `tauri.linux.conf.json` stripped title and min sizes from main window | Overlay **replaces** the windows array — include every field, not just the delta |
| `data-tauri-drag-region` + manual `startDragging()` on the same node caused drag latency | Pick one — we use manual `startDragging` for the button/input early-return logic |
| Corner resize collapsed to single axis on KWin Wayland | Native decorations on Linux side-step the whole frameless path |
| `animate-float-enter` on the viewer/float layout root created a containing block that broke `position: fixed` on ResizeHandles children | Render ResizeHandles as a sibling of the animated div, not a descendant |
| Kon binary auto-respawned on file-save while a second run.sh was also launching → two visible instances sharing one Vite server | Do not script `./run.sh` while the user has already launched via the desktop icon; rely on HMR |
| `run.sh` leaves `"beforeDevCommand": ""` in tauri.conf.json if its cleanup trap is bypassed (e.g. SIGKILL) | Cleanup trap restores `"npm run dev"` on graceful exit; SIGTERM (not SIGKILL) is the right kill signal |
| `/proc/asound/cards` header lines have leading whitespace for 2-digit card ID alignment | Parser trims leading whitespace before checking for leading digit |
## How to resume
```
Picking up Kon dogfooding from 2026/04/19.
HANDOVER is at HANDOVER.md in the project root.
Active priorities: (1) confirm resize/drag/mic cleanup, (2) Task 7 MicroSteps
dogfood with kon-llm stub, (3) Settings UX brainstorm.
```

View File

@@ -1,97 +1,83 @@
--- ---
name: handover-2026-04-19 name: handover-2026-04-23
type: reference type: reference
tags: [handover, session, kon] tags: [handover, session, kon, consolidation]
description: Session handover — 2026/04/19 dogfood polish + cross-platform window chrome description: Session handover — 2026/04/23 branch consolidation + main-in-its-best-state pass
--- ---
# Kon Handover — 2026/04/19 # Kon Handover — 2026/04/23
Second dogfood sprint. Four phases: (1) fix bugs surfaced on first real use, (2) redesign History for cognitive-load hygiene, (3) resolve broken window resize/drag on Linux Wayland, (4) clean up microphone picker. Consolidation session. Jake asked for "all branches to main and the repo in its best state" ahead of Corbie rebrand. This session brought outstanding Dependabot PRs into `main`, cleaned workspace lints, recovered an orphan stash onto a dedicated branch, and established a clean baseline for the post-rebrand push.
## Rebrand note
The product is in the process of being renamed **Kon → Corbie**. As of 2026/04/23 the canonical product name in any new copy (grant applications, landing pages) is **Corbie** (sentence case). The GitHub repo `jakejars/kon` and the Gitea repo `jake/kon` both still carry the `kon` name, intentionally — repo rename is pending Jake's own hand and is not blocking the rebrand copy-wise. Code paths (`crates/`, `src-tauri/`, package name `kon@0.1.0`) remain as-is until the repo rename lands; renaming the codebase identifiers is a separate coordinated sweep. See `memory/project_corbie_rebrand.md` in CORBEL-Main for the full rebrand state.
## What shipped this session ## What shipped this session
### Cross-window preferences sync ### Baseline validation
- `preferences.svelte.js` emits `kon:preferences-changed` Tauri event on update. - Fresh `cargo build`, `cargo test --workspace`, `cargo fmt --check`, `npm run check`, `npm run build` run against the pre-consolidation tip (`9b0067b`, tagged as `pre-consolidation-2026-04-23` for recovery).
- Main / viewer / float layouts listen and call `applyExternalPreferences` without re-emit, so theme and font changes propagate live across sibling windows. - `npm run build` initially failed with a missing-dependency error for `@chenglou/pretext`. Root cause: stale partial `node_modules` install dated 2026-04-21 07:45 — the directory for the package existed but was empty. `npm install` recovered it; package is present in lockfile and needed by `src/lib/utils/textMeasure.ts`.
- Echo suppressed via source window label check. - `cargo clippy -- -D warnings` initially failed with 3 errors in `crates/storage/src/file_storage.rs` (two doc-list overindentations, one needless return). These were trivial style issues that clippy had never enforced at CI strictness.
### Hotkey recorder ### Workspace clippy cleanup (commit `fe61661`)
- Root cause of "can't change hotkey": button-level `onkeydown` relied on post-click keyboard focus, which webkit2gtk on Linux does not guarantee. - Applied `cargo clippy --fix` across 11 files: `crates/audio`, `crates/hotkey`, `crates/storage`, `crates/transcription`, `src-tauri`.
- Fix: `document.addEventListener("keydown", ..., { capture: true })` inside a `$effect` gated by `recording`. Beats any descendant handler. Escape now cancels. - Net -2 lines. No behavioural changes — pure lint cleanup (needless returns, unnecessary casts, `iter().any()``contains()`, `repeat().take()``repeat_n`, lifetime elision, `map_or` simplification, reference-immediately-dereferenced).
- One remaining warning left untouched: `needless_range_loop` at `src-tauri/src/commands/live.rs:1089` — clippy's suggested iterator rewrite would make it less readable. Earmarked for a focused refactor session.
- Build + 245 workspace tests remain green post-fix.
### History page redesign (research-backed) ### Dependabot merges (commits `6579c5f`, `0b1c492`, `509b983`)
- Compact row now shows the **title** (or "Untitled"), not body-preview text — metadata already lives in the row columns (date, duration, source icon). Three dev-dep bumps landed as `--no-ff` merge commits:
- Expanded row gets an inline title input (replaces the old Rename prompt modal).
- **Edit** button opens the viewer window in `edit` mode (editable textarea, debounced save to localStorage + storage-event sync back to main history).
- **Export .md** copies a full YAML-frontmatter markdown document to the clipboard — paste into Obsidian.
- **Tags**: `$lib/utils/frontmatter.js` exposes `deriveAutoTags` (currently returns `[]`), `buildFrontmatter`, `serialiseFrontmatter`, `buildMarkdown`. Manual tags stored as `item.manualTags`, rendered as removable chips in the expanded row with `+ add tag` input.
- Header tag chip bar (cap 7, click to filter, × to clear), plus `tag:xyz` search syntax.
- Global **Starred** filter toggle in the History header.
- Research memo found all five previous auto-tag families redundant with existing row UI — kept the derivation hook for the post-Task-7 `topic:*` content tag from kon-llm.
- Duplicate-transcript render fix: expanded `<p>` only if compact preview actually truncated.
### Viewer / editor popout | # | Package | File changes |
- `/viewer` route now reads `kon_viewer_mode` from localStorage ("view" | "edit"). |---|---|---|
- Edit mode renders a plain textarea bound to `item.text`; 400ms debounced save flushes on input, final flush on `onDestroy`. Segment-specific controls (Compact, Starred) hidden in edit mode. | 1 | `picomatch` | `package-lock.json` only |
- Native title: **"Kon - Transcription Editor"**. | 2 | `@sveltejs/kit` | `package.json` + `package-lock.json` |
| 3 | `vite` (npm_and_yarn group) | `package.json` + `package-lock.json` |
### Platform-aware window chrome (Linux fix) After the three merges: `npm install` clean, `npm run build` green, `npm run check` 0 errors / 0 warnings. Vulnerability count went from 6 (1 low / 2 moderate / 3 high) to 5 (3 low / 2 moderate) — the three highs cleared.
**Root cause:** Tauri v2 frameless `decorations: false` on KDE Wayland + webkit2gtk does not honour diagonal corner resize (collapses `NorthEast` etc. to a single axis via GTK's `gtk_window_begin_resize_drag`), and `data-tauri-drag-region` adds noticeable drag latency. Setting `setPointerCapture` ahead of `startResizeDragging` does not help once the compositor has taken over the pointer grab. Verified via Context7 docs + Codex diagnosis — Linux frameless is a known-fragile path.
**Fix:** ### Orphan-stash recovery (branch `feat/design-system-recover`, commit `8855db8`)
- Linux uses **native KWin/Mutter decorations**. `src-tauri/tauri.linux.conf.json` overlays `decorations: true` + full main window config (title, sizes) — overlays **replace** the windows array, so every field must be present, not just the delta. `src-tauri/src/commands/windows.rs` uses `cfg!(target_os = "linux")` to set decorations per window. - A WIP stash sat on `stash@{0}`, pinned to parent commit `1296173` — the tip of the deleted `feat/design-system` branch. ~1000 LOC across 24 files: `ai-formatting/rule_based.rs` rewrite, `transcription/local_engine.rs`, core `hardware.rs` + `recommendation.rs`, `audio/resample.rs`, and a sweep across Svelte UI (`TaskSidebar`, `FilesPage`, `TasksPage`, `WipTaskList`, `ModelDownloader`, `Titlebar`, `viewer`, `float`).
- macOS / Windows keep custom chrome. `src/lib/utils/osInfo.js` `isLinux()` gates `<Titlebar>` and `<ResizeHandles>` via `useCustomChrome = $state(false)`; flips to `!isLinux()` after `loadOsInfo()` resolves. - Recovered using `git stash branch`, which creates a branch from the stash's parent commit and applies the stash content. Parent commit still existed in the reflog even though no branch referenced it.
- Dueling drag-region handlers removed across Titlebar, float page, viewer page — everywhere a manual `startDragging()` lives, the `data-tauri-drag-region` attribute was deleted (they're alternatives per Tauri docs, not combinable). - Committed the full recovery as `wip(design-system): recover orphan stash` on the new branch. **Not merged to main.** Needs a focused rebase session: heavy conflicts expected in `TasksPage`, `FilesPage`, `ai-formatting/rule_based.rs`, and `transcription/local_engine.rs` against current main since those all saw release-blocker fixes after the stash was taken. Also missing the 2026-04-23 clippy cleanup.
- `ResizeHandles` kept for macOS/Windows frameless: 12 px edges / 20 px corners via CSS vars (`--kon-resize-edge`, `--kon-resize-corner`), `pointerdown` + `setPointerCapture`, corners with explicit higher z-index. Handles rendered as siblings of the animated layout div so `position: fixed` is viewport-relative rather than captured by the transform containing block. - Branch pushed to `github` for remote preservation.
### Window minimum sizes (evidence-backed) ## Release-blocker state (unchanged this session)
Research pass cited GNOME HIG (1024×600 desktop / 360×294 mobile floors), WCAG 2.2 SC 1.4.10 Reflow (320 CSS px), Raycast 750×474 as a reference for single-pane working width, and consistent A11y principle that nothing should clip in the default configuration.
| Window | Was | Now | Rationale | From `docs/issues/README.md`:
|---|---|---|---|
| Main | 1020×540 | **960×600** | Fits 210 px sidebar + ~750 px content; GNOME vertical floor. |
| Float | 400×400 | **360×480** | 360 = GNOME mobile floor; 480 fits pills + quick-add + sort + ~6 task rows without scroll. |
| Transcript editor | 450×500 | **560×520** | Exceeds WCAG reflow floor; ~60-70 char measure for editing. |
### Microphone picker cleanup - **0 open CRITICAL**
- ALSA enumeration was leaking `hw:`, `plughw:`, `front:`, `sysdefault:`, `null` et al into the dropdown. - **1 open MAJOR** — RB-08 `power-assertion-macos-objc2` (awaits manual runtime verification on a real macOS machine: `pmset -g assertions` during a background live-session)
- `SettingsPage.svelte` now renders only sentinel devices (`default`, `pipewire`, `pulse`) + one entry per unique sound card, keyed off the `sysdefault:CARD=X` alias. - **11 RBs resolved**
- `crates/audio/src/capture.rs` reads `/proc/asound/cards` and populates a new `description` field on `DeviceInfo` with the card's full product string (e.g. "Blue Microphones" for Jake's Yeti). Frontend prefers description → CARD=X short name → raw name. - **11 CR items resolved** from the 2026-04-22 code review
### GPU reporting RB-08 continues to gate v0.1 tagging.
- `commands/models.rs::get_runtime_capabilities` was hardcoded to `accelerators: vec!["cpu"]` and `supports_gpu: false` for whisper. Updated to `["cpu", "vulkan"]` and whisper `supports_gpu: true`, reflecting that `crates/transcription/Cargo.toml` links transcribe-rs with the `whisper-vulkan` feature unconditionally.
- Settings now shows the Vulkan option instead of the "This build is CPU-only" notice.
### Desktop shortcut ## Deferred (unchanged from 2026/04/19 handover unless noted)
- `~/Desktop/Kon.desktop` launcher with the 128×128 icon, `Terminal=true` so logs are visible and Ctrl+C cleanly stops the run.sh wrapper.
## What's deferred - **RB-08 verification** — needs macOS hardware access
- **Design-system work** — parked on `feat/design-system-recover`; revisit in a dedicated session
- **`live.rs:1089` needless_range_loop refactor** — clippy suggestion is ugly, deserves a real rewrite
- **Kon → Corbie codebase rename** — package name, crate prefixes, repo name on Gitea + GitHub. Coordinated sweep, not yet scheduled
- **Transparent windows**, **file-system export**, **bulk select/export**, **LLM content tags**, **Settings UX overhaul**, **Task 7 walk-through** — all carried over from 2026-04-19 handover
- **CI clippy enforcement** — 1 warning remains on main; turning on `-D warnings` in CI means either fixing it or explicit `#[allow]`
- **Transparent windows (`transparent: true`)** — Tauri issue #13270 reports this smooths drag/resize further on Linux, but it's moot now that Linux uses native decorations. ## Repo state at session end
- **File-system export (.md save dialog)** — currently clipboard-only. Needs a Rust `write_text_file` command for plugin-less file writes.
- **Bulk select + bulk export** in History.
- **LLM-powered content tags** (`topic:*`, `intent:*`) — slots into Task 7 `kon-llm` stub once Phase 3 wires real llama-cpp-2.
- **Settings UX overhaul** — Jake flagged that current settings feel overwhelming. Proposed: bunch high-traffic settings, hide advanced behind a toggle. Brainstorm + plan deferred to a dedicated session.
- **Task 7 (MicroSteps end-to-end)** — storage + Tauri CRUD + kon-llm stub + frontend dual-write all landed in an earlier commit chain. The MicroSteps UI was written as the final task 7 step but not yet dogfooded against the stub LLM. Needs manual walkthrough.
## Gotchas discovered today - `main` at `509b983` — 7 commits ahead of origin at session start (1 clippy + 3 dependabot original commits + 3 merge commits)
- `feat/design-system-recover` at `8855db8` — WIP recovery, pushed to `github` only
- Tags: `pre-consolidation-2026-04-23` points at `9b0067b` (restore anchor)
- Stashes: none
- Dependabot branches: still present locally; can be pruned now that merges landed
| Issue | Fix | ## Full pre-push verification (Phase 5)
|---|---|
| `tauri.linux.conf.json` stripped title and min sizes from main window | Overlay **replaces** the windows array — include every field, not just the delta |
| `data-tauri-drag-region` + manual `startDragging()` on the same node caused drag latency | Pick one — we use manual `startDragging` for the button/input early-return logic |
| Corner resize collapsed to single axis on KWin Wayland | Native decorations on Linux side-step the whole frameless path |
| `animate-float-enter` on the viewer/float layout root created a containing block that broke `position: fixed` on ResizeHandles children | Render ResizeHandles as a sibling of the animated div, not a descendant |
| Kon binary auto-respawned on file-save while a second run.sh was also launching → two visible instances sharing one Vite server | Do not script `./run.sh` while the user has already launched via the desktop icon; rely on HMR |
| `run.sh` leaves `"beforeDevCommand": ""` in tauri.conf.json if its cleanup trap is bypassed (e.g. SIGKILL) | Cleanup trap restores `"npm run dev"` on graceful exit; SIGTERM (not SIGKILL) is the right kill signal |
| `/proc/asound/cards` header lines have leading whitespace for 2-digit card ID alignment | Parser trims leading whitespace before checking for leading digit |
## How to resume Run at end of session — see commit log after `509b983` for the verification commit, and `outputs/` for the full log.
``` ## Anchors
Picking up Kon dogfooding from 2026/04/19.
HANDOVER is at HANDOVER.md in the project root. - Previous handover: [HANDOVER-2026-04-19.md](HANDOVER-2026-04-19.md)
Active priorities: (1) confirm resize/drag/mic cleanup, (2) Task 7 MicroSteps - Release-blocker index: [docs/issues/README.md](docs/issues/README.md)
dogfood with kon-llm stub, (3) Settings UX brainstorm. - Rebrand memory: `~/.claude/projects/-home-jake-Documents-CORBEL-Main/memory/project_corbie_rebrand.md`
``` - Active-focus upstream: `context/active-focus.md` in CORBEL-Main

View File

@@ -11,7 +11,7 @@ Kon is a local-first, cognitive-load-aware dictation and task-capture desktop ap
**Pre-alpha.** Actively dogfooded on Linux (KDE Plasma 6 on Wayland). macOS and Windows targets are in scope and exercised by CI, but not yet beta-ready. One primary user; open source-intent with licence TBD before public beta. **Pre-alpha.** Actively dogfooded on Linux (KDE Plasma 6 on Wayland). macOS and Windows targets are in scope and exercised by CI, but not yet beta-ready. One primary user; open source-intent with licence TBD before public beta.
- Current `main`: see commit log - Current `main`: see commit log
- 136 automated lib tests across 10 crates, all passing - 245 automated lib tests across 10 crates, all passing
- Cross-platform CI (Linux / macOS / Windows) via GitHub Actions - Cross-platform CI (Linux / macOS / Windows) via GitHub Actions
--- ---
@@ -288,7 +288,7 @@ CI also builds release installers on tag push (see `.github/workflows/build.yml`
### Testing ### Testing
```bash ```bash
cargo test --workspace --lib # 136 tests across 10 crates cargo test --workspace --lib # 245 tests across 10 crates
npm run check # svelte-check (type-checks .svelte files) npm run check # svelte-check (type-checks .svelte files)
cargo check --workspace --all-targets cargo check --workspace --all-targets
``` ```

View File

@@ -112,7 +112,7 @@ impl MicrophoneCapture {
for device in devices { for device in devices {
let name = device_display_name(&device).unwrap_or_else(|| "<unnamed>".to_string()); let name = device_display_name(&device).unwrap_or_else(|| "<unnamed>".to_string());
let (sample_rate, channels) = match device.default_input_config() { let (sample_rate, channels) = match device.default_input_config() {
Ok(cfg) => (cfg.sample_rate(), cfg.channels() as u16), Ok(cfg) => (cfg.sample_rate(), cfg.channels()),
Err(_) => (0, 0), Err(_) => (0, 0),
}; };
let is_likely_monitor = is_monitor_name(&name); let is_likely_monitor = is_monitor_name(&name);
@@ -277,11 +277,7 @@ fn device_display_name(device: &cpal::Device) -> Option<String> {
/// `pipewire` / `default` → `None` /// `pipewire` / `default` → `None`
fn extract_card_id(name: &str) -> Option<&str> { fn extract_card_id(name: &str) -> Option<&str> {
let rest = name.split("CARD=").nth(1)?; let rest = name.split("CARD=").nth(1)?;
Some( Some(rest.split([',', ';']).next().unwrap_or(rest))
rest.split(|c: char| c == ',' || c == ';')
.next()
.unwrap_or(rest),
)
} }
/// Read `/proc/asound/cards` and return a map from ALSA card short name /// Read `/proc/asound/cards` and return a map from ALSA card short name
@@ -361,7 +357,7 @@ fn open_and_validate(
.default_input_config() .default_input_config()
.map_err(|e| KonError::AudioCaptureFailed(format!("default_input_config: {e}")))?; .map_err(|e| KonError::AudioCaptureFailed(format!("default_input_config: {e}")))?;
let sample_rate = config.sample_rate(); let sample_rate = config.sample_rate();
let channels = config.channels() as u16; let channels = config.channels();
let format = config.sample_format(); let format = config.sample_format();
eprintln!( eprintln!(

View File

@@ -343,14 +343,14 @@ async fn device_listener(
fn is_event_device(path: &Path) -> bool { fn is_event_device(path: &Path) -> bool {
path.file_name() path.file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.map_or(false, |n| n.starts_with("event")) .is_some_and(|n| n.starts_with("event"))
} }
/// Return true when the device's reported key set includes the combo's /// Return true when the device's reported key set includes the combo's
/// configured trigger key. A device that reports no keys at all (for /// configured trigger key. A device that reports no keys at all (for
/// example a mouse whose `EV_KEY` capability is buttons only) is rejected. /// example a mouse whose `EV_KEY` capability is buttons only) is rejected.
fn device_supports_combo(supported: Option<&AttributeSetRef<Key>>, combo: &HotkeyCombo) -> bool { fn device_supports_combo(supported: Option<&AttributeSetRef<Key>>, combo: &HotkeyCombo) -> bool {
supported.map_or(false, |keys| keys.contains(Key::new(combo.key_code))) supported.is_some_and(|keys| keys.contains(Key::new(combo.key_code)))
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -2,11 +2,11 @@ use std::path::PathBuf;
/// Resolve the per-user app data directory, following each OS's convention: /// Resolve the per-user app data directory, following each OS's convention:
/// ///
/// - Windows: `%LOCALAPPDATA%\kon\` e.g. `C:\Users\Jake\AppData\Local\kon` /// - Windows: `%LOCALAPPDATA%\kon\` e.g. `C:\Users\Jake\AppData\Local\kon`
/// - macOS: `~/Library/Application Support/Kon/` /// - macOS: `~/Library/Application Support/Kon/`
/// - Linux: `$XDG_DATA_HOME/kon` or `~/.local/share/kon` (XDG Base Directory), /// - Linux: `$XDG_DATA_HOME/kon` or `~/.local/share/kon` (XDG Base Directory),
/// with a fallback to the legacy `~/.kon/` if it already exists, so /// with a fallback to the legacy `~/.kon/` if it already exists, so
/// existing installs keep working. /// existing installs keep working.
/// - Other Unix: `~/.kon/` /// - Other Unix: `~/.kon/`
/// ///
/// TODO: Consolidate with `crates/transcription/src/model_manager.rs::dirs_path()` /// TODO: Consolidate with `crates/transcription/src/model_manager.rs::dirs_path()`
@@ -45,7 +45,7 @@ pub fn app_data_dir() -> PathBuf {
return PathBuf::from(xdg).join("kon"); return PathBuf::from(xdg).join("kon");
} }
} }
return PathBuf::from(home).join(".local").join("share").join("kon"); PathBuf::from(home).join(".local").join("share").join("kon")
} }
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]

View File

@@ -158,7 +158,7 @@ mod tests {
let mut total_pushed: u64 = 0; let mut total_pushed: u64 = 0;
let tentative_per_cycle: u64 = 200; let tentative_per_cycle: u64 = 200;
for _ in 0..100 { for _ in 0..100 {
buf.extend(std::iter::repeat(0.25_f32).take(16_000)); buf.extend(std::iter::repeat_n(0.25_f32, 16_000));
total_pushed += 16_000; total_pushed += 16_000;
let commit_point = total_pushed - tentative_per_cycle; let commit_point = total_pushed - tentative_per_cycle;
start = trim_buffer_to_commit_point(&mut buf, start, commit_point); start = trim_buffer_to_commit_point(&mut buf, start, commit_point);
@@ -199,7 +199,7 @@ mod tests {
// Simulate a capture buffer that has received 1.2 s of audio // Simulate a capture buffer that has received 1.2 s of audio
// starting at t=0. // starting at t=0.
let mut buf: Vec<f32> = std::iter::repeat(0.1_f32).take(19_200).collect(); let mut buf: Vec<f32> = std::iter::repeat_n(0.1_f32, 19_200).collect();
let new_start = trim_buffer_to_commit_point(&mut buf, 0, commit_idx); let new_start = trim_buffer_to_commit_point(&mut buf, 0, commit_idx);
assert_eq!(new_start, 8_000); assert_eq!(new_start, 8_000);
assert_eq!(buf.len(), 19_200 - 8_000); assert_eq!(buf.len(), 19_200 - 8_000);

View File

@@ -306,7 +306,7 @@ impl VadChunker for RmsVadChunker {
.saturating_sub(self.pending.len() as u64); .saturating_sub(self.pending.len() as u64);
let pad_len = FRAME_SAMPLES - self.pending.len(); let pad_len = FRAME_SAMPLES - self.pending.len();
let mut padded = std::mem::take(&mut self.pending); let mut padded = std::mem::take(&mut self.pending);
padded.extend(std::iter::repeat(0.0_f32).take(pad_len)); padded.extend(std::iter::repeat_n(0.0_f32, pad_len));
if let Some(chunk) = self.consume_frame(padded, frame_start) { if let Some(chunk) = self.consume_frame(padded, frame_start) {
emitted.push(chunk); emitted.push(chunk);
} }

88
package-lock.json generated
View File

@@ -19,7 +19,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-static": "^3.0.6", "@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.9.0", "@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
@@ -27,7 +27,7 @@
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^6.0.3" "vite": "^6.4.2"
} }
}, },
"node_modules/@chenglou/pretext": { "node_modules/@chenglou/pretext": {
@@ -958,9 +958,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.55.0", "version": "2.57.1",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.57.1.tgz",
"integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==", "integrity": "sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -987,7 +987,7 @@
"@opentelemetry/api": "^1.0.0", "@opentelemetry/api": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0",
"typescript": "^5.3.3", "typescript": "^5.3.3 || ^6.0.0",
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
@@ -1262,6 +1262,70 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.8.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.8.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
@@ -2375,9 +2439,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3098,9 +3162,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -24,7 +24,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-static": "^3.0.6", "@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.9.0", "@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
@@ -32,6 +32,6 @@
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^6.0.3" "vite": "^6.4.2"
} }
} }

View File

@@ -64,7 +64,7 @@ fn assert_localhost_llm_csp() {
let tokens: Vec<&str> = connect_src.split_whitespace().collect(); let tokens: Vec<&str> = connect_src.split_whitespace().collect();
for required in ["http://127.0.0.1:*", "ws://127.0.0.1:*"] { for required in ["http://127.0.0.1:*", "ws://127.0.0.1:*"] {
assert!( assert!(
tokens.iter().any(|t| *t == required), tokens.contains(&required),
"build.rs: tauri.conf.json CSP connect-src must permit {required} \ "build.rs: tauri.conf.json CSP connect-src must permit {required} \
for local LLM connectivity (brief item #2). Current connect-src: \ for local LLM connectivity (brief item #2). Current connect-src: \
{connect_src:?}" {connect_src:?}"

View File

@@ -247,7 +247,7 @@ pub async fn generate_diagnostic_report(
.map(|d| d.as_secs()) .map(|d| d.as_secs())
.unwrap_or(0); .unwrap_or(0);
out.push_str(&format!("- Generated: unix `{}`\n", now)); out.push_str(&format!("- Generated: unix `{}`\n", now));
out.push_str("\n"); out.push('\n');
out.push_str( out.push_str(
"> This report is local-only until you choose to share it. \ "> This report is local-only until you choose to share it. \
Review the contents below before sending to anyone.\n\n", Review the contents below before sending to anyone.\n\n",

View File

@@ -1155,12 +1155,10 @@ fn longest_common_token_subsequence(a: &[&str], b: &[&str]) -> usize {
} }
fn is_low_signal_token(token: &str) -> bool { fn is_low_signal_token(token: &str) -> bool {
LOW_SIGNAL_TOKENS LOW_SIGNAL_TOKENS.contains(&token)
.iter()
.any(|low_signal| *low_signal == token)
} }
fn meaningful_tokens<'a>(text: &'a str) -> Vec<&'a str> { fn meaningful_tokens(text: &str) -> Vec<&str> {
text.split_whitespace() text.split_whitespace()
.filter(|token| !token.is_empty() && token.len() > 1 && !is_low_signal_token(token)) .filter(|token| !token.is_empty() && token.len() > 1 && !is_low_signal_token(token))
.collect() .collect()

View File

@@ -412,7 +412,7 @@ pub fn detect_active_compute_device() -> ActiveComputeDevice {
reason: None, reason: None,
}; };
} }
return ActiveComputeDevice { ActiveComputeDevice {
kind: "cpu".into(), kind: "cpu".into(),
label: "CPU (fallback)".into(), label: "CPU (fallback)".into(),
reason: Some( reason: Some(
@@ -420,7 +420,7 @@ pub fn detect_active_compute_device() -> ActiveComputeDevice {
libvulkan1 (Linux) to enable GPU acceleration." libvulkan1 (Linux) to enable GPU acceleration."
.into(), .into(),
), ),
}; }
} }
} }

View File

@@ -345,7 +345,7 @@ fn classify_terminal(raw: &str) -> Option<String> {
fn detect_focused_window_class() -> Option<String> { fn detect_focused_window_class() -> Option<String> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
return detect_focused_window_class_linux(); detect_focused_window_class_linux()
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {

View File

@@ -234,7 +234,7 @@ pub fn run() {
// Runtime-warning banner: push CPU-feature + Vulkan-loader // Runtime-warning banner: push CPU-feature + Vulkan-loader
// fallbacks to the frontend so Settings can render a one-line // fallbacks to the frontend so Settings can render a one-line
// hint. No-ops on a fully-supported box. // hint. No-ops on a fully-supported box.
crate::commands::models::emit_runtime_warnings(&app.handle()); crate::commands::models::emit_runtime_warnings(app.handle());
if let Err(e) = tray::setup(app) { if let Err(e) = tray::setup(app) {
eprintln!("Failed to setup tray: {e}"); eprintln!("Failed to setup tray: {e}");