eecedbdecd0c5b6cb79c7b0f8b3d5c73b8b634ed
113 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 3770815fbf |
agent: lumotia — v0.1 release-completion run
Closes the code-side v0.1 ship gate. All quality gates green: cargo fmt/clippy/test (~327 tests), npm check (0/0), vitest 13/13, scripts/dogfood-rebrand-drill.sh 8/8. Phase F — first-run onboarding promoted to v0.1 - FirstRunPage with skip-to-main + failure recovery + event recording - Six onboarding commands (record/list/has-completed + lumotia_events) - Storage migration v17 (onboarding_events + lumotia_events tables) UI hardening (in-scope items from v0.1-ui-hardening.md) - StatusPill + PostCaptureCard components, 21st preview entry - Sidebar recording-as-sacred-state (opacity + aria-disabled, reduced-motion) - Settings 6-section regroup + Help section + Activation log + Privacy toggle - Error-state copy sweep (DictationPage + SettingsPage, plain-language) - Global :focus-visible rule, textarea outlines restored - Ctrl+K / Ctrl+, / Escape bindings in +layout LLM resilience - rule_based_extract_tasks (regex-free imperative-verb extractor) + extract_tasks_with_fallback wrapper — task extraction never returns zero - tokio::time::timeout(120s) wraps cleanup/tags/tasks commands Release artefacts - LICENSE (canonical AGPL-3.0), CHANGELOG (Keep-a-Changelog format) - v0.1-release-notes, privacy-and-ai-use, install-warnings, tester-onboarding-kit, tester-acceptance-runbook, code-signing-setup, apple-silicon-rb08-runbook, virtual-audio-setup, v0.1-contrast-audit - Workspace versioning + AGPL spdx; npm exact-pin (10 ranges removed) - AppImage SHA-256 sidecar in build.yml - README v0.1 section + Reporting-issues; canonical repo slug Closure pass — items moved from human-required to code-complete - KI-02 Linux idle inhibit: zbus 5 → org.freedesktop.login1.Manager.Inhibit - KI-03 Windows sleep prevention: SetThreadExecutionState(ES_CONTINUOUS|...) - acquire/release_idle_inhibit Tauri commands, wired in DictationPage - Diagnostic-bundle frontend wire-up (Settings → Help button) - WCAG-AA contrast fix via .btn-filled-text utility (no token changes) - 8 destructive-action sites wrapped in plain-language confirm() guards - KNOWN-ISSUES.md + v0.1-known-limitations.md updated (KI-02/03 fixed) Scripts - pre-tag-verify.sh, tag-day.sh, smoke-linux + driver - parse-diagnostic-bundle.sh, parse-activation-log.py Per-item audit trail: docs/release/v0.1-completion-status.md Remaining: W-01…W-08 (signing certs, hardware probes, smoke matrix, tester recruitment) — see docs/release/v0.1-known-limitations.md. |
|||
| 7f0e1b0375 |
agent: lumotia — Phase B.6 pin IPC-allowlist vs capability-JSON mirror invariant
Phase B.6 audit of commits |
|||
| d8fa4ff64e |
agent: lumotia — Phase B.5 close symlink-target bypass in write_text_file_cmd path scope
Phase B.5 audit of commits a2b47db/a48653c/b3da58c (Trust-1 — write path allowlist), |
|||
| 6c212a0d2c |
agent: lumotia — Phase B.1 fix misleading comment on start_live lifecycle ordering
Phase B.1 survey finding (commit
|
|||
| 27661c816e |
agent: lumotia — pin rust toolchain + workspace clippy/fmt sweep
rust-toolchain.toml pins to stable 1.94.1 so contributors and CI runners share the exact rustc / rustfmt / clippy versions. Without the pin, every machine surfaces a different lint set depending on its local install — six pre-existing lints showed up on 1.94.1 that 1.93-era HANDOVER reported clean. Clippy fixes (all pre-existing, not introduced by feature work): - crates/storage/src/database.rs: std::iter::repeat().take() -> repeat_n() - crates/llm/src/lib.rs (docs): "+ frontends" was parsed as a markdown bullet continuation by rustdoc, breaking doc-lazy-continuation. Reworded to "and". - crates/llm/src/lib.rs (loop): while-let-on-iterator -> for-loop. - src-tauri/src/commands/security.rs: .iter().any(|a| *a == x) -> .contains(&x). - src-tauri/src/lib.rs: io::Error::new(Other, e) -> io::Error::other(e). - src-tauri/src/tauri_app_data_migration.rs: drop function-tail `return`s inside cfg blocks; each platform's block now ends with a tail expression. cargo fmt sweep across the workspace. Mechanical layout-only changes; no semantics affected. Workspace gates after this commit: - cargo fmt --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean - cargo test --workspace: 405/0 (will become 409/0 with Phase A.1+A.2) |
|||
| 65abfa2ed9 |
agent: code-atomiser-fix — span propagation across live + model-load spawns (Obs-3)
Before this commit `grep -rIn '#\[instrument\|.instrument(\|in_current_span()'`
returned zero matches across the entire workspace. Every tokio::spawn
and thread::spawn lost its parent span, so structured fields recorded
at the call site (session_id, chunk_id, model_id) did not propagate to
log lines emitted inside the spawn. During concurrent-session incidents
the operator could not correlate a runaway log line back to the request
that started it.
Targeted four highest-value join points:
* src-tauri/src/commands/live.rs::run_live_session
#[tracing::instrument(skip_all, fields(session_id, engine, language))]
Attaches the span to the spawn_blocking worker so every per-chunk
warning carries the session id that owns it.
* src-tauri/src/commands/live.rs::maybe_dispatch_chunk
Manual span attach pattern (#[instrument] can't decorate a closure):
capture the parent span before thread::spawn, .enter() it on the new
OS thread, then open an "inference" child span with chunk_id +
duration_secs. Without this, whisper backend warnings appear
unparented and a runaway chunk can't be traced back to its session.
* src-tauri/src/commands/models.rs::ensure_model_loaded
#[instrument(skip_all, fields(model_id, engine, concurrent))]
Multi-second load + sequential-GPU guard logs now carry the model
in flight as a structured field.
* crates/llm/src/lib.rs::load_model
#[instrument(skip_all, fields(model_id, use_gpu))]
Same rationale for LLM loads. Tags llama-backend init lines and
GPU sequential-guard events with the model identifier.
Storage/audio/hotkey/MCP crates left uninstrumented in this commit —
future sweep. The four sites above are the canonical concurrent-load
correlation points; everything else fans out from them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| d1391b34ac |
agent: code-atomiser-fix — drop lumotia_live custom target in live.rs (Obs-1, Obs-2)
The drain-timeout warning in LiveSessionRuntime emitted with `target: "lumotia_live"`, which EnvFilter treats as the literal target string and not as a substring of `lumotia_lib::commands::live`. The operator's documented triage filter (`RUST_LOG=info,lumotia=debug,lumotia_lib::commands::live=debug`, per docs/superpowers/audits/2026-05-10-phase10a-dogfood-notes.md) therefore silenced the only warning that surfaces a wedged inference worker. Drop the explicit `target:` so the emit picks up its module-path target and falls under the existing filter directive. `lumotia_startup`, `lumotia_storage`, `lumotia_hotkey`, etc. remain deliberately custom targets — each is a separate semantic phase with its own dedicated EnvFilter directive. Regression test asserts no `target: "lumotia_live"` literal remains in live.rs by scanning the file's own source. Skips comment lines so the rationale prose does not self-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 12b413d645 |
agent: code-atomiser-fix — broaden clipboard/paste guards to documented secondary windows
The Trust-3/Trust-6 fix in commit
|
|||
| 9653e25e32 |
agent: code-atomiser-fix — extension allowlist + size cap on transcribe_file (Trust-5)
`transcribe_file` already had `ensure_main_window`, but accepted an arbitrary `path: String` and fed it straight to `lumotia_audio::decode_audio_file_limited`. The OS file picker typically constrains the user's path, but the IPC surface itself never checked: a compromised webview could point the decoder at a 50 GiB sparse file (OOM the worker), or a deliberately-malformed blob with an extension chosen to provoke a parser bug in Symphonia. This change adds defence-in-depth: - extension allowlist (`wav`, `mp3`, `m4a`, `mp4`, `flac`, `ogg`, `opus`, `webm`, `aac`) matched case-insensitively. Anything else, including no extension at all, is rejected with a clear error; - 1 GiB ceiling on the input file. Stats via `std::fs::metadata` (which resolves symlinks) so the cap sees the real blob, not a symlink-target lie. The 2-hour duration cap still runs after decode for the realistic-audio case. The validation lives in a pure helper, `validate_transcribe_input`, so the rule can be unit-tested without spawning Tauri or hitting the decoder. Eight unit tests cover: accepts plain `.wav`, accepts uppercase `.MP3`, accepts every allowlisted extension, rejects `.so` payload, rejects missing extension, rejects oversize file, accepts exactly-at-cap file, rejects path-traversal with disallowed extension. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| f7af7b07bb |
agent: code-atomiser-fix — main-window guard on extract_content_tags_cmd (Trust-4)
`extract_content_tags_cmd` was the only LLM command in `commands/llm.rs`
that did not call `ensure_main_window`. Every sibling (`load_llm_model`,
`unload_llm_model`, `delete_llm_model`, `test_llm_model`,
`cleanup_transcript_text_cmd`, `download_llm_model`) gates on it.
Without the guard a secondary-window webview could trigger a multi-
second llama.cpp inference run, blocking the LLM engine for the main
window and leaking model-inferred tags out of the History page's trust
boundary.
This change:
- adds a `window: tauri::WebviewWindow` parameter (Tauri injects it
automatically — `HistoryPage.svelte`'s `invoke("extract_content_tags_cmd",
…)` call site is unchanged and `npm run check` is clean);
- calls `ensure_main_window(&window)?` before the engine check so the
rejection is fast and the cap mirrors the rest of the surface.
Behaviour is otherwise identical: same engine path, same spawn_blocking,
same App-Nap power assertion.
The shared `ensure_main_window_label` test in `commands::security`
already covers the secondary-window rejection behaviour; no
command-level scaffolding for handler-style tests exists in this
codebase, so introducing one for a single new line was out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 7aee5348bc |
agent: code-atomiser-fix — main-window guard + size cap for clipboard surface (Trust-3, Trust-6)
`paste_text`, `paste_text_replacing`, and `copy_to_clipboard` previously exposed asymmetric trust against the rest of the Tauri command surface: no `ensure_main_window` guard and no payload-size cap. A compromised webview could synthesise an arbitrary Ctrl+V into the foreground application or write multi-megabyte payloads into the system clipboard without restriction. `paste_text*` is particularly hot because it also synthesises keystrokes into whatever app currently has focus. This change: - adds `ensure_main_window(&window)?` to all three commands. Each now takes a `tauri::WebviewWindow` parameter that Tauri injects automatically — frontend invoke call sites are unchanged in their TypeScript signatures and `npm run check` is green; - introduces a shared 1 MiB cap (`MAX_CLIPBOARD_BYTES` / `MAX_PASTE_BYTES`) that both surfaces enforce identically. Drift between the two caps would let an attacker copy a >1 MiB payload via one command and paste it via the other; a unit test asserts the constants stay in lock-step. Tests added: - `commands::clipboard::tests` — accepts normal payload, accepts exactly-at-cap, rejects above-cap. - `commands::paste::tests_paste_size_cap` — accepts typical dictation payload, rejects above-cap, asserts paste cap matches clipboard cap. Note: `copy_to_clipboard` is currently invoked from the preview (`/preview`) and viewer (`/viewer`) routes (HistoryPage and DictationPage too, but those run in the main window). After this change the preview and viewer invocations will surface a "main window only" error at runtime. `npm run check` cannot catch this — flagged for follow-up; the fix is to refactor those routes to delegate the copy through the main window via an event. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| b3da58cd6b |
agent: code-atomiser-fix — write_text_file_cmd path scope (Trust-1, redo)
The earlier Trust-1 commits |
|||
| a48653c93c |
agent: code-atomiser-fix — write_text_file_cmd path scope (Trust-1, corrective)
Corrective re-apply of Trust-1. The original commit |
|||
| 99f4ecdecc |
agent: code-atomiser-fix — Tauri commands for trash list + restore
Adds two `#[tauri::command]` wrappers around the soft-delete pair that
landed with migration v16 in commit
|
|||
| ed449ccc1f |
agent: code-atomiser-fix — validate output_folder against recordings_dir (Trust-2)
resolve_recording_path() previously joined the webview-supplied
output_folder string verbatim into a PathBuf and then mkdir -p'd +
WAV-wrote at that path. The webview is a (mostly-)trusted surface in
Tauri, but the live-transcription command's input is JSON from the
frontend with no schema enforcement on the path field — so a
compromised page, a Tauri IPC sender on an OEM build, or a future
plugin reaching the same command can pipe through paths like `/etc`,
`/var/log`, or anywhere else the Lumotia process can write.
The new flow:
- None / empty → fall back to the default `app_local_data_dir/recordings`
base. Always safe.
- Non-empty → call validate_output_folder, which:
1. Ensures the default base exists (so canonicalise can succeed on
first launch).
2. Creates the requested path if it doesn't exist (so canonicalise
can resolve it; an empty directory outside the base is the only
side effect of an attempted escape, which is acceptable for the
trust gain).
3. Canonicalises both base and requested paths (resolves `..` and
symlinks).
4. Requires the canonical requested path to start_with the canonical
base. Reject otherwise with a message naming the trust boundary.
A user who legitimately wants recordings elsewhere routes through
Settings (a separately-validated persisted-preferences boundary). The
command surface stays constrained.
Regression tests (commands::audio::tests):
- validate_output_folder_accepts_base_itself
- validate_output_folder_accepts_descendant
- validate_output_folder_rejects_etc — `/etc` attack shape
- validate_output_folder_rejects_parent_escape — `..`-walk attack
- validate_output_folder_rejects_sibling_dir — prefix-overlap attack
(`recordings-backdoor` vs `recordings`). Canonical starts_with on
PathBufs correctly rejects this; a naive string-prefix check would
have let it through.
- validate_output_folder_rejects_symlink_pointing_out (Unix only) —
in-base symlink to outside path must be rejected after canonicalise
follows the link.
cargo test -p lumotia --lib commands::audio::tests: 8 passed.
cargo test --workspace: all green.
npm run check: 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 07f6755961 |
agent: code-atomiser-fix — drain_inference deadline from task.duration_secs (Time-bomb-1)
F3 derived the drain_inference timeout from the CHUNK_SAMPLES constant and capped it near 6s. That breaks on the realistic worst-case configuration (slow CPU + Whisper large-v3, 3-5x realtime): a 4-second chunk legitimately takes ~20s to clear the decoder, but the F3 budget aborted it after 6s and treated healthy work as a wedge — the lifecycle keeps surviving, but every long-tail chunk gets cancelled and the user sees their final stretch of dictation get dropped. The deadline now derives from the in-flight task's own duration_secs multiplied by a REALTIME_SAFETY_MULTIPLIER constant (5x — the documented upper bound for the slowest supported backend), with a DRAIN_TIMEOUT_FLOOR of 2s so sub-second tail chunks still get enough wall-clock to amortise model load, OS scheduling jitter, and the abort-callback's own poll cadence. Both constants sit next to CHUNK_SAMPLES with doc comments explaining the rationale. Defensive: non-finite or non-positive durations fall back to the floor so a malformed task can't produce a NaN/overflow budget. Regression tests (commands::live::tests): - drain_timeout_scales_with_inflight_chunk_duration_secs: 4.0s chunk must get 20s budget (5x), not the old 6s cap. - drain_timeout_honours_floor_for_short_chunks: 0.3s chunk produces 1.5s scaled value, must be clamped to the 2s floor. - drain_timeout_uses_floor_when_no_inflight_task: well-defined fallback for the (in practice unreachable) None branch. - drain_timeout_rejects_non_finite_duration: NaN / inf / 0 / negative fall back to floor. cargo test -p lumotia --lib commands::live::tests::drain: 4 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 1068ad9c7d |
agent: code-atomiser-fix — hotkey supervisor rearchitecture (Race-1, Race-2, TOCTOU)
Fixes three interlocking concurrency leaks in the evdev hotkey listener flagged by the atomiser full-sweep. Every spawned task is now owned by a SupervisorHandle that broadcasts cooperative shutdown and joins every JoinHandle with a 2s per-task timeout on stop(). Per-device attachment is now insert-before-spawn under one mutex hold, closing the TOCTOU window. The Tauri command layer now stores the forwarder JoinHandle alongside the listener so reconfigures join it cleanly instead of leaking one permanent forwarder per hotkey change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| afbd33d33e |
agent: code-atomiser-fix — test_llm_model respects caller GPU preference (Race-8)
Add an Option<bool> use_gpu parameter to test_llm_model with the same default-true semantics as load_llm_model. Hard-coding true triggered an engine tear-down/rebuild when a parallel load_llm_model(use_gpu=false) was in flight (LlmEngine::load_model triples on id+path+use_gpu) and silently flipped the user's GPU mode underneath the test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 094b533ef2 |
agent: code-atomiser-fix — surface capture-thread drops + bypass validation requeue cap
dropped_chunks was incremented on cpal-callback channel-full and validation requeue overflow but never read by the live session, so the UI's dropped_audio_ms missed callback-level losses entirely. Architecture doc had flagged this as a TODO. Also: the 350ms validation buffer was requeued via try_send into the same 32-slot channel, silently dropping past the cap on small-buffer audio hosts (WASAPI exclusive, low-latency ALSA at 256 frames -> ~65 chunks). Fix: live runtime reads MicrophoneCapture::dropped_chunks() on each recv_audio tick (LiveSessionRuntime::poll_capture_drops) and converts the per-chunk-duration delta into the dropped_audio_ms surfaced to the UI overload status. Per-chunk duration is derived from the most recent AudioChunk's sample_rate + samples-per-channel so it adapts to whatever rate cpal is delivering at. Validation requeue moved from try_send into the bounded channel onto a VecDeque<AudioChunk> returned alongside the Receiver; ActiveCapture drains the replay buffer before reading rx in recv_audio, bypassing the 32-slot cap entirely. Architecture doc updated to remove the TODO and document the new pre-roll path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 5725836f40 |
agent: code-atomiser-fix — cancellable Whisper inference + bounded drain + lock-over-await
Three findings chain: thread::spawn discarded the JoinHandle and gave no cancellation route into the whisper backend; drain_inference busy- polled with no timeout; stop_live_transcription_session held the lifecycle AsyncMutex across the await of a non-cancellable spawn_blocking handle. Together: a single wedged inference (ggml deadlock, GPU stall) bricked every future start/stop until app restart. Fix: each inference task carries an Arc<AtomicBool> abort_flag; the flag is wired into whisper-rs::FullParams::set_abort_callback_safe so the spawned blocking thread checks it and exits cleanly. drain_inference is bounded by a deadline (3 x chunk_duration, min 2s); on expiry the flag is set, the receiver is dropped, and a typed Error::InferenceTimeout status is surfaced. Drop for InferenceTask asserts the abort flag so any '?'-propagation or panic unwind closes the cancellation route without relying on the explicit drain path. stop_live_transcription_session restructured so the lifecycle guard is dropped BEFORE the JoinHandle is awaited; start_live_transcription_session releases the guard explicitly after installing the RunningLiveSession. Unit test added: dropping_inference_task_sets_abort_flag covers the Race-A regression directly. A real Race-B drain-timeout test would require a wedged whisper-rs, which is hard to fixture; that path is covered by the SAFETY comment and cargo build + manual smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 26c7307607 |
agent: lumotia-rebrand — docs, scripts, root config, residuals
Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 16081095e0 |
agent: lumotia-rebrand — localStorage keys + event channels migration
Phase 7 of the rebrand cascade. Persisted UI state + inter-window event
channels migrated from magnotia to lumotia naming, with one-shot
localStorage key migration so dogfooded UI state survives the rename.
src/lib/utils/localStorageMigration.ts (new):
- migrateLocalStorageKey(old, new): idempotent + crash-safe shim.
- If new key exists, removes old (lumotia value is authoritative).
- If only old exists, copies value to new key, removes old.
- If neither, no-op.
- migrateLocalStorageKeys(pairs): batch wrapper.
src/lib/stores/page.svelte.ts:
- 4 key constants renamed to lumotia_settings / lumotia_profiles /
lumotia_task_lists / lumotia_templates.
- BroadcastChannel name renamed to lumotia_task_lists.
- migrateLocalStorageKeys() called at module load before any read.
src/lib/stores/focusTimer.svelte.ts:
- STORAGE_KEY renamed to lumotia.focusTimer.v1.
- migrateLocalStorageKey() called at module load.
Event channels (magnotia: -> lumotia:) renamed across frontend + Rust:
- magnotia:toggle-recording (src/routes/+layout.svelte)
- magnotia:hotkey-pressed / -released (src-tauri/src/commands/hotkey.rs +
consumers)
- magnotia:open-wind-down (src-tauri/src/tray.rs + consumer)
- magnotia:llm-download-progress (src-tauri/src/commands/llm.rs)
- magnotia:preferences-changed (src/lib/stores/preferences.svelte.ts +
consumers)
- magnotia:start-timer (nudgeBus + dispatch sites)
- magnotia:focus-timer-{complete,cancelled} (focusTimer + nudgeBus)
- magnotia:microstep-generated (nudgeBus + dispatch sites)
- magnotia:step-completed (nudgeBus + dispatch sites)
- magnotia:task-{completed,uncompleted,deleted} (page.svelte.ts +
nudgeBus + consumers)
Storage-event filters in src/routes/{float,viewer,preview}/+layout@.svelte
updated to filter on lumotia_settings.
User-facing toast strings still say "Magnotia" — deferred to Phase 8
(frontend strings).
npm run check: 0 errors / 0 warnings (3958 files).
cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 86f83b7a45 |
agent: lumotia-rebrand — data dir migration shim + paths.rs rename
Phase 5 of the rebrand cascade per locked decision D1 (migrate in place).
crates/core/src/paths.rs:
- Hardcoded subdir strings renamed: magnotia/Magnotia -> lumotia/Lumotia
across all four OS branches (Linux XDG + dot-legacy, macOS Application
Support, Windows LOCALAPPDATA, fallback dot-dir).
- Database filename: magnotia.db -> lumotia.db.
- Test path fixtures renamed: /tmp/magnotia-test -> /tmp/lumotia-test.
- New MigrationStatus enum (Migrated / TargetAlreadyExists / NoLegacyFound).
- New migrate_legacy_data_dir() that probes the platform-correct legacy
magnotia path, renames it to the lumotia equivalent via fs::rename, and
also renames magnotia.db -> lumotia.db inside if found. Idempotent: safe
to call on every boot. Refuses to overwrite an existing lumotia dir to
protect user data.
- Four new unit tests covering all branches via the test-friendly
migrate_legacy_data_dir_inner that takes an explicit legacy path.
crates/storage/src/database.rs:
- New migrate_legacy_setting_keys(pool) that renames any settings rows
with key matching magnotia_* to lumotia_*. Single SQL UPDATE with
NOT EXISTS guard so it leaves rows alone if a lumotia_ row already
exists. Re-exported from lib.rs.
src-tauri/src/lib.rs:
- Calls migrate_legacy_data_dir(&app_data_dir()) at the start of setup()
BEFORE database_path() resolves the now-renamed dir. Logs migration
outcome to lumotia_startup tracing target.
- Calls migrate_legacy_setting_keys(&db) immediately after init_db
returns. Logs only when rows are actually renamed.
src-tauri/src/{lib,commands/{diagnostics,rituals,transcripts}}.rs +
crates/storage/src/database.rs:
- All in-code references to magnotia_preferences, magnotia_history (in
comments), and magnotia_morning_triage_last_shown renamed to lumotia_*.
cargo build --workspace passes. cargo test --workspace: 334 pass, 0 fail
(up from 330; +4 paths::tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| e2a5feb718 |
agent: lumotia-rebrand — tracing filter targets
Phase 4 of the rebrand cascade. Updates the default RUST_LOG filter in src-tauri/src/lib.rs and all 'target: "magnotia_startup"' string literals across src-tauri/src/lib.rs and src-tauri/src/commands/models.rs. Default filter (before): warn,magnotia=info,lumotia_lib=info,lumotia_audio=info, magnotia_startup=info,lumotia_hotkey=info,lumotia_ai_formatting=info Default filter (after): warn,lumotia=info,lumotia_lib=info,lumotia_core=info, lumotia_audio=info,lumotia_hotkey=info,lumotia_ai_formatting=info, lumotia_llm=info,lumotia_storage=info,lumotia_transcription=info, lumotia_startup=info magnotia_startup was a logical tracing target (not a crate); renamed to lumotia_startup for consistency. magnotia_lib was already renamed to lumotia_lib in Phase 2. Added per-crate filter entries for parity across the workspace. cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| ce6dc1e728 |
agent: lumotia-rebrand — fix QC blockers for phase 2
Phase 2 QC found three explicit blockers + a broader sweep needed:
Explicit (QC-named):
- crates/mcp/src/lib.rs:15 — SERVER_NAME public MCP wire identity
- crates/transcription/build.rs:59 — panic message prefix
- crates/llm/tests/content_tags_smoke.rs:7 — docstring -p flag
Swept (string literals + dev env vars + doc comments + test fixtures):
- crates/mcp/src/main.rs — eprintln log prefixes
- src-tauri/src/commands/diagnostics.rs — diagnostic filename + MAGNOTIA_VERSION
- src-tauri/src/commands/audio.rs — recording filename pattern lumotia-<secs>-...wav
- src-tauri/src/commands/fs.rs — test placeholder path
- crates/transcription/src/model_manager.rs — .lumotia-verified marker
- crates/storage/src/database.rs — lumotia-storage-ro-<pid> temp dirs + doc comments
- crates/cloud-providers/src/keystore.rs — LUMOTIA_API_KEY_<PROVIDER> env var
- crates/audio/src/{wav,decode}.rs — lumotia_test_* / lumotia_decode_* test fixtures
- crates/core/src/tuning.rs — LUMOTIA_INFERENCE_THREADS env var
- All MAGNOTIA_LLM_TEST_MODEL / MAGNOTIA_WHISPER_TEST_* env vars
- Doc comments referencing crate names
Excluded (intentional Phase 4/5 scope):
- magnotia_preferences, magnotia_history, magnotia_morning_triage_last_shown
DB setting keys (Phase 5 paths.rs migration)
- magnotia_startup tracing target (Phase 4)
- crates/core/src/paths.rs (Phase 5 wholesale rewrite + migration shim)
cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 089349d966 |
agent: lumotia-rebrand — rust workspace crates magnotia-* -> lumotia-*
Phase 2 of the rebrand cascade. Renames all 9 workspace crates from magnotia-* to lumotia-* plus the src-tauri binary crate name: - magnotia-ai-formatting -> lumotia-ai-formatting - magnotia-audio -> lumotia-audio - magnotia-cloud-providers -> lumotia-cloud-providers - magnotia-core -> lumotia-core - magnotia-hotkey -> lumotia-hotkey - magnotia-llm -> lumotia-llm - magnotia-mcp -> lumotia-mcp - magnotia-storage -> lumotia-storage - magnotia-transcription -> lumotia-transcription - magnotia -> lumotia (src-tauri binary) - magnotia_lib -> lumotia_lib (src-tauri lib target) Crate directories (crates/audio/ etc.) stay as-is; only the Cargo.toml [package] name field changes plus all consumer module imports (magnotia_core -> lumotia_core, etc.). Remaining magnotia_* references at this point are intentional and scoped to later phases: tracing targets (Phase 4), DB setting keys magnotia_preferences/magnotia_history (Phase 5). cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 184214b60a |
agent: engine slop residuals B — eprintln → tracing sweep
Replaces 22 production eprintln! sites with structured tracing events across 8 files. Closes Area B of the post-prognosis residuals plan (docs/superpowers/plans/2026-05-12-engine-slop-residuals.md). Files touched (22 sites): - crates/hotkey/src/linux.rs (2) — hotplug watcher degraded-mode warnings - crates/ai-formatting/src/pipeline.rs (1) — LLM cleanup fallback warning - src-tauri/src/commands/transcription.rs (1) — chunking dispatch info - src-tauri/src/commands/diagnostics.rs (1) — crashes-dir setup warning - src-tauri/src/commands/tasks.rs (1) — malformed feedback row warning - src-tauri/src/commands/power.rs (3) — App Nap acquire/release/fail - src-tauri/src/commands/models.rs (5) — Whisper warmup lifecycle - src-tauri/src/commands/live.rs (8) — session start, chunk dispatch, per-chunk delivery, inference errors, worker disconnects, listener loss, status-channel cascade Levels: error for unrecoverable failures (inference disconnect, panic, status cascade), warn for recoverable degradation (LLM fallback, malformed rows, App Nap fail, hotplug watcher fail), info for lifecycle (session start, chunk processed, App Nap acquire/release, warmup complete, chunking dispatch), debug for per-chunk noise (speech-gate skip, chunk dispatch). Two new dependencies and two new filter targets: - tracing = "0.1" added to crates/hotkey and crates/ai-formatting - Default EnvFilter in src-tauri/src/lib.rs::init_tracing extended with magnotia_hotkey=info,magnotia_ai_formatting=info so the new targets emit at the default level Out of scope (intentional, left as-is): - crates/mcp/src/main.rs — CLI binary, stderr is the log contract (module docstring) so the JSON-RPC stdout stream stays clean - crates/*/tests/*.rs and crates/core/examples/tuning_log_demo.rs — test/example diagnostic output relies on --nocapture stdio semantics Discovery during sweep (not fixed — separate follow-up): hotkey crate has 6 existing log:: calls (log::error/warn/info/debug) but the workspace builds tracing-subscriber without the tracing-log feature, so those events are currently silent. Worth a follow-up to either add the tracing-log bridge or migrate hotkey's existing log:: calls to tracing::. Verification: - cargo fmt --all - cargo check --workspace --all-targets — clean - cargo test --workspace — 330+ tests, zero failures - rg eprintln! src-tauri/src/commands/ crates/hotkey/src/ crates/ai-formatting/src/ — zero hits Pre-existing working-tree churn in crates/llm/, src/lib/pages/, src/lib/utils/saveMarkdown.ts and the untracked phase10a dogfood notes deliberately left unstaged per Jake's instruction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| db654deecc |
agent: engine slop pass — DSP, typed errors, regex parsing, tracing, audit fixes
External code review on 2026-05-12 rated the codebase 4/10 across audio DSP, error typing, JS injection, env-var safety, ALSA parsing, and async logging. This commit lands the prognosis-level fixes plus three audit follow-ups. Audio/DSP: - StreamingResampler/rubato confirmed in the live capture path - regression test at 12 kHz (rms < 0.01, ~40 dB) catches naive decimation - near-Nyquist test at 9 kHz (rms < 0.05, ~26 dB) exercises transition band Core errors: - Other(String) removed; ProviderNotRegistered introduced - Io variant restructured as struct with kind/message/raw_os_error - FileNotFound display quotes paths - Configuration variant removed (unused) Core types: - ModelId, EngineName backed by Cow<'static, str>; const borrowed ctor - Megabytes::from_gb takes u64 (was f64) - AudioSamples::sample_rate is NonZeroU32; zero-rate defensive branch removed Capture: - /proc/asound/cards parsing rewritten as anchored regex (OnceLock) - regression test covers product names with embedded colons - monitor_pattern_detection test restored alongside the regex test - DEAD_SILENCE_FLOOR promoted to module-level with rationale - DEVICE_VALIDATION_MS, SILENCE_RMS_FLOOR documented with field-observation rationale - RMS validation loop made idiomatic - eprintln! migrated to tracing with structured fields and targets Tauri startup: - unsafe std::env::set_var removed; ensure_x11_on_wayland renamed to warn_if_x11_env_unset_on_wayland (launcher/wrapper owns env-var contract) - DB init + log prune + preferences load collapsed to one block_on - build_preferences_script rewrites JS injection from JSON.parse string to direct object literal plus malformed-JSON guard and unit tests - WebKitGTK microphone auto-grant logs warning at startup - tracing subscriber initialised at top of run() (warn,magnotia=info,... on stderr; honors RUST_LOG); previously eprintln→tracing migration was silent because no subscriber existed Filename counter: - RECORDING_COUNTER uses SeqCst Tests: cargo test --workspace --lib green (322 passed, 0 failed across 10 crates). Three independent audits (original cleanup → Wren → fresh Codex subagent) concur on no critical findings. Deferred to docs/superpowers/plans/2026-05-12-engine-slop-residuals.md: storage-layer typed errors, remaining eprintln→tracing sweep, capture actor-model refactor, property-based DSP testing, frontend/backend error boundary cleanup. |
|||
| b463c32f17 | chore: stabilize current head before Phase 10a QC | |||
|
|
b16fc179b3 |
chore: remove 11 orphan Tauri commands and downstream dead code
Sweep of registered-but-never-invoked commands surfaced by an audit against the frontend invoke() call sites. Each was confirmed dead via grep across src-tauri, src, and crates: no caller anywhere. Removed commands: - check_model, count_transcripts_command, get_profile_cmd, install_update, list_feedback_examples_cmd (utility/CRUD shapes never wired) - save_audio, start_native_capture, stop_native_capture (native-capture path superseded by the live transcription session) - transcribe_pcm, transcribe_pcm_parakeet (PCM commands superseded by live session; no frontend caller) - close_preview_window (preview window is hidden via the core:window:allow-hide capability, not the command) Cascade in audio.rs (~430 lines removed): - CaptureWorker, NativeCaptureState struct + impl, stop_worker, append_recorded_chunk, MAX_NATIVE_CAPTURE_RETURN_SAMPLES, persist_audio_samples - The two cfg(test) tests that exercised stop_worker (the recording_filename tests stay, supporting resolve_recording_path which the live session uses) Cascade elsewhere: - FeedbackDto struct and its From<FeedbackRow> impl - Stale storage imports in feedback.rs, profiles.rs, transcripts.rs - tauri::Emitter import in transcription.rs - app.manage(NativeCaptureState::new()) in lib.rs setup generate_handler entries removed for all 11 commands. cargo check passes cleanly with zero warnings; no tests reference any deleted symbols. Net: 709 deletions, 17 insertions across 9 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2a45cb8033 |
fix: model-loaded guards on transcribe_pcm, power assertion guards on tasks LLM commands
transcribe_pcm and transcribe_pcm_parakeet did not check that their respective engines were loaded before clone+spawn_blocking. transcribe_file already calls ensure_model_loaded; these now mirror that posture with a friendly error when the engine is unloaded, matching what extract_content_tags_cmd does. decompose_and_store and extract_tasks_from_transcript_cmd ran multi-second LLM inference inside spawn_blocking without the PowerAssertion guard that cleanup_transcript_text_cmd and extract_content_tags_cmd already use. Both now begin a guard so the macOS App-Nap inhibitor (and the planned Linux/Windows equivalents per KI-02, KI-03) can pin the process for the duration of the inference. HANDOVER.md gets a status note clarifying it captures Phase 9 state. The schema head referenced (v14) was the head at session time; current head is v15 (`idx_transcripts_profile_created` composite index) per the architecture map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e5e12387e8 |
docs: add KNOWN-ISSUES.md and rewrite PowerAssertion top doc
PowerAssertion file-level doc previously claimed Linux logind and Windows SetThreadExecutionState implementations in present tense. Both are no-ops; the macOS path compiles but is unverified on Apple Silicon (RB-08). Rewrite top doc to state present vs planned posture and reference KNOWN-ISSUES.md. Surfaced as tracked limitations: - KI-01: macOS App Nap guard pending Apple Silicon verification - KI-02: Linux power assertion is a no-op - KI-03: Windows power assertion is a no-op - KI-04: magnotia-cloud-providers crate not user-exposed in v0.1 (in-memory keystore needs OS keychain before any save-key UX) README links to KNOWN-ISSUES.md from Status, Platform support table, and Project documentation. Platform support table notes adjusted per OS to reflect actual idle-inhibit posture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d6bde52d6e |
refactor(tauri): use magnotia_core::hardware::vulkan_loader_available
Delete the local duplicate fn and libloading dependency from src-tauri; import the canonical implementation from magnotia-core::hardware instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
| 0f105f0e15 |
chore(llm): update callers for renamed model variants
Picks up the registry rename in the front-end and Tauri command layer:
- src/lib/types/app.ts: LlmModelIdStr now lists the four new ids
(qwen3_5_2b / qwen3_5_4b / qwen3_5_9b / qwen3_6_27b).
- src/lib/pages/SettingsPage.svelte: LLM_MODELS table rebuilt with
four tiers (Minimal / Standard / High / Maximum), matching subtitles
and download-size copy. selectedLlmModelId fallback, hardware-warning
thresholds, tier-availability check, and ensureRecommendedLlmTier
fallback all retargeted at the new ids. The Maximum tier surfaces a
64 GB / 24 GB warning so users with mid-range hardware see honest
expectations.
- src-tauri/src/commands/llm.rs and commands/tasks.rs: doc-comment
examples refreshed (Qwen3 4B → Qwen3.5 4B, Qwen3's tokenizer →
Qwen's tokenizer — the BPE family is shared).
- src/lib/stores/llmStatus.svelte.ts: chip-detail example updated.
cargo build --workspace clean. cargo test --workspace clean.
npx svelte-check reports one pre-existing error in vite.config.js
(unused @ts-expect-error directive, dates back to the original
scaffold commit
|
|||
|
|
89c63891fa |
chore: rebrand from Kon/Corbie to Magnotia
Replace all instances of the legacy product names "Kon" and "Corbie" with "Magnotia" across user-facing copy, code identifiers, package names, bundle ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE terminal) reference and the parent CORBEL company name. - Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary - Updates package.json, tauri.conf.json (productName + identifier) - Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations - Renames brand and roadmap docs - Regenerates Cargo.lock and package-lock.json Verified: svelte-check passes; pure-rust crates compile under new names. |
||
|
|
4abc2356c2 |
build(android): cfg-gate desktop-only Tauri surfaces for android target
Phase 1 of the Android same-repo target plan: make the workspace
compilable for `aarch64-linux-android` (and the other NDK ABIs) by
removing the desktop-only crate dependencies and command bodies from the
Android build. After this commit, `tauri android init` followed by
`cargo tauri android build` is structurally unblocked — the remaining
work is the SDK/NDK toolchain (off-sandbox), the Svelte single-window
refactor, and the Phase 3 MVP feature surface.
What's gated under `cfg(not(target_os = "android"))`:
- src-tauri/Cargo.toml: `tauri = { features = ["tray-icon"] }` is now
declared in the desktop-only target block. The `global-shortcut`,
`window-state`, and `autostart` plugins join it — none of the three
support Android natively. The base `tauri = "2"` plus `dialog`,
`opener`, and `notification` plugins remain unconditional because they
do support Android.
- src-tauri/src/lib.rs: `mod tray` declaration, the matching
`tray::setup(app)` call, the close-to-tray `WindowEvent::CloseRequested`
handler, and the `.plugin(tauri_plugin_global_shortcut::*)` /
`_autostart` / `_window_state` chain are all desktop-only. The
builder is split with a single `#[cfg(not(target_os = "android"))]`
branch that adds the desktop plugins on top of the universal base.
- src-tauri/src/commands/tts.rs: `tts_speak` previously had three
`#[cfg(target_os = ...)]` branches but no fallback, so on Android the
`spawned` binding was unbound and the function failed to compile.
Mirrored the existing `paste.rs` not-implemented fallback. Same fix
for `list_voices_impl`. Frontend will hide the Read Page Aloud button
on Android via `isAndroid()`.
- src-tauri/src/commands/windows.rs: all four multi-window commands
(`open_task_window`, `open_preview_window`, `close_preview_window`,
`open_viewer_window`) get an Android stub that returns a clear
"Multi-window is not supported on Android" error. Tauri on Android
is single-Activity; the previously-secondary content (preview overlay,
transcript viewer, task float) will live as routes inside the main
window, gated by `isAndroid()` on the frontend.
What's *not* changed:
- Top-level `identifier` in tauri.conf.json stays `uk.co.corbel.kon`.
The Phase 10b Kon → Corbie rename sweep will land
`corbel.technology.corbie` as part of a coherent rebrand commit
rather than fragmenting the rename across this branch.
- `bundle.android.minSdkVersion: 24` added so a future
`tauri android init` knows to target Android 7.0+ (Vulkan available,
scoped storage starts at 29 — we'll surface scoped-storage paths
via Tauri's dialog plugin on Phase 3).
- `kon-hotkey` already exports a non-Linux stub; no changes needed.
- `commands/meeting.rs` still calls `process_watch::list_running_process_names()`
which compiles on Android but returns an empty list (SELinux blocks
/proc walk on API 24+). Frontend will hide the toggle on Android.
Verification: 91/91 tests still pass on the buildable-in-sandbox crates
(kon-storage 60, kon-core 16, kon-mcp 9, kon-hotkey 4, kon-cloud-providers
2). svelte-check 0/0 across 3957 files. The src-tauri crate itself can't
be compiled in this sandbox (no webkit2gtk); CI's desktop builders will
exercise the desktop branch, and Jake's Android-equipped dev box will
exercise the Android branch via `tauri android init`.
https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb
|
||
|
|
73f8c45f86 |
fix(live): self-stop worker when both result and status channels are dead
`emit_live_result` already detected a lost result_channel listener: it sent a one-shot status warning and from then on short-circuited future result sends. But if the status_channel listener was also gone — which is what happens when the user closes the main window without calling stop_live_transcription_session — the worker kept polling inflight inference every 10 ms forever, holding a model loaded on the GPU and keeping the WAV writer file handle open until the process exited. When the warning send to status_channel also returns Err, the entire frontend channel pair is dead. Self-assert stop_flag from inside emit_live_result so the worker drains and exits cleanly. Existing user- initiated stop semantics are unchanged. - Threaded `stop_flag: &Arc<AtomicBool>` through `emit_live_result` and the free `poll_inference` (instance method already had access via `self.stop_flag`). - Existing `result_listener_loss_is_warned_once_*` test updated to pass a stop_flag and assert it stays false when only result_channel fails. - New test `dead_result_and_status_channels_self_assert_stop_flag` proves the self-stop fires when both channels Err. (src-tauri doesn't build in the audit sandbox — needs webkit2gtk; CI cross-platform compiles it.) https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb |
||
|
|
3d568148b8 |
perf(meeting): cache sysinfo System for the meeting-detection poller
`detect_meeting_processes` is called every 15 s when meeting-auto-capture is enabled. The previous `list_running_process_names` allocated a fresh `sysinfo::System` per call and walked /proc cold; on a busy host (~300 processes) that's ~50–100 ms of work, every poll, forever. Add `kon_core::process_watch::ProcessLister`, a thin wrapper around a long-lived `System` whose process table is refreshed in place. The Tauri host holds one behind a `Mutex<ProcessLister>` in a new `MeetingState` managed at app setup. The free `list_running_process_names` is kept as a convenience that constructs a fresh `ProcessLister` per call — its only remaining caller is the existing smoke test. - ProcessLister + Default in crates/core/src/process_watch.rs. - MeetingState in src-tauri/src/commands/meeting.rs; the command takes it via `tauri::State` and locks for the duration of the snapshot. - src-tauri/src/lib.rs registers MeetingState alongside the other managed states. https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb |
||
| 489c066a70 |
feat(phase9): migration v14 + storage and Tauri command extension for llm_tags
Adds llm_tags TEXT NOT NULL DEFAULT '' to the transcripts table via new migration v14. SELECT statements + TranscriptRow + transcript_row_from now carry the column. update_transcript_meta gains a sixth Option for llm_tags following the existing COALESCE pattern; an #[allow(too_many_arguments)] keeps clippy happy without inverting the signature into a struct that would just shift the indirection. The Tauri-side TranscriptDto + UpdateTranscriptMetaRequest + the update_transcript_meta_cmd command pass llm_tags through unchanged. Pre-existing manualTags persistence path now has a sibling for llmTags ready for the frontend to call. Phase 8 brittle test fix included: list_recent_completions_uses_local_day_boundary was anchoring its "-2 days" UTC offset against the local-day spine, which drifted across UTC midnight. Anchored to the local date directly so it matches the spine regardless of clock. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| ef42c95000 |
feat(phase9): extract_content_tags_cmd Tauri wrapper
Bridges LlmEngine::extract_content_tags to the frontend with the same spawn_blocking + PowerAssertion guard the cleanup_text command uses. Returns a ContentTags object serialised to camelCase JSON. Errors surface as readable strings so the frontend toast shows actionable text on the rare grammar-bypass path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 5a15c931d0 |
feat(phase9): write_text_file_cmd
Thin UTF-8 writer used by the new save-dialog path. Caller owns path safety; the source path is always OS-dialog-provided. Two unit tests: roundtrips a small UTF-8 string with non-ASCII chars and asserts a nonexistent parent path returns an actionable error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 42b423e4f4 |
feat(gamification): list_recent_completions_cmd Tauri wrapper
Thin wrapper over kon_storage::list_recent_completions, parameterised by day count. Serialises to camelCase JSON (day, count). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 6cd1c22c0f |
feat(intentions): Phase 7 — if-then rules for task / time / triage triggers
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. |
|||
| eebea8cb9a |
feat(nudges): Phase 6 — Margot soft-touch nudges via frontend nudge bus
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. |
|||
| b333c6229e | chore(hardening): tighten security and footprint defaults | |||
| 3cf3e41899 |
feat(rituals): Phase 5 — morning triage, evening wind-down, autostart
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). |
|||
| 9f53702c7e |
feat(tts): Phase 4 — Read Page Aloud with OS-native voices
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. |
|||
| 1d4f1070a2 |
feat(energy): Phase 3 — match-my-energy task sort + tri-state tag column
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. |
|||
| d307722c7a |
fix(feedback): Phase 2 follow-up — Codex review MAJORs + NIT
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. |
|||
| 46be0a5aca |
feat(feedback): Phase 2 — HITL thumbs + correction capture with prompt-conditioning loop
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) |