Feature chore bug 006 #20

Merged
iamdoubz merged 65 commits from feature_chore_bug_006 into main 2026-07-11 09:26:39 -05:00
65 Commits
Author SHA1 Message Date
iamdoubz 490a960676 Add optional dependencies section 2026-07-11 09:23:36 -05:00
iamdoubz 379bdf532f chore: sync Cargo.lock to version 0.4.0 2026-07-11 09:10:17 -05:00
iamdoubz 95f07e1dba fix(transcription): scope streamer-test closures so out isn't borrow-locked 2026-07-11 09:08:20 -05:00
iamdoubz 931a2b78b4 fix(media): blank doc line before the tool list (clippy doc_lazy_continuation) 2026-07-11 09:08:19 -05:00
iamdoubz 89254bc070 fix(audio): update audio_level test calls for the new mic arg 2026-07-11 09:08:18 -05:00
iamdoubz 22a37c5f4e docs: README for v0.4.0 — new features + Granola/Meetily/WA comparison (Granola MCP) 2026-07-11 08:58:06 -05:00
iamdoubz cecfe20eec chore: bump tauri config version to 0.4.0 2026-07-11 08:58:05 -05:00
iamdoubz 6fbf967fdb chore: bump crate version to 0.4.0 2026-07-11 08:58:04 -05:00
iamdoubz 9f3c1b65d0 chore: bump version to 0.4.0 2026-07-11 08:58:03 -05:00
iamdoubz a4b2b9a292 feat(transcription): drive live worker with StreamTuning; store only committed segments 2026-07-11 08:42:25 -05:00
iamdoubz 12e23143f6 feat(transcription): streaming growing-window + interim + commit-on-pause (FR-TRX-2) 2026-07-11 08:42:24 -05:00
iamdoubz fd556c57a1 feat(import): import_media — file/URL to a transcribed, diarized meeting; auto_record default 2026-07-11 00:38:19 -05:00
iamdoubz 1ea8011dc8 feat: register media module + import_media command 2026-07-11 00:38:18 -05:00
iamdoubz 002f8caa2b feat(media): transcode local files + URLs to 16kHz wav via ffmpeg/yt-dlp 2026-07-11 00:38:16 -05:00
iamdoubz d72509df5f feat: add auto_record_calendar setting (FR-CAL) 2026-07-11 00:38:15 -05:00
iamdoubz 7ef45bb315 feat(ui): Add meeting button + import modal wiring in the header 2026-07-11 00:37:53 -05:00
iamdoubz bd251b991a feat(ui): add-a-meeting modal for importing a file or URL 2026-07-11 00:37:51 -05:00
iamdoubz dfaeb7c873 feat(ui): import_media command in the typed api 2026-07-11 00:37:50 -05:00
iamdoubz 6b164cd02a feat(ui): auto-start-on-calendar-event toggle in Settings > Recording 2026-07-11 00:31:34 -05:00
iamdoubz 21101054a4 feat(ui): arm a one-shot auto-record timer for the next calendar event (FR-CAL) 2026-07-11 00:31:32 -05:00
iamdoubz 16200a130e feat(ui): let recording.start attach a calendar event id 2026-07-11 00:31:31 -05:00
iamdoubz 86f115642c feat(ui): default auto_record_calendar off in the settings store 2026-07-11 00:31:29 -05:00
iamdoubz 1760d4ae24 feat(ui): add auto_record_calendar to the settings contract 2026-07-11 00:31:16 -05:00
iamdoubz 2681a5d8c8 feat(audio): forward mic/system flag on the recording://level event 2026-07-11 00:27:42 -05:00
iamdoubz 233d641041 feat(audio): emit tagged mic + system level updates from capture (FR-CAP-5/7) 2026-07-11 00:27:41 -05:00
iamdoubz 29589663b2 feat(ui): pass mic level + enabled state to the level meter 2026-07-11 00:27:06 -05:00
iamdoubz 28058e205d feat(ui): overlay mic level on the meter in the accent colour (FR-CAP-5/7) 2026-07-11 00:27:05 -05:00
iamdoubz 235129d0d6 feat(ui): track microphone level separately in the recording store 2026-07-11 00:27:03 -05:00
iamdoubz 59c2d70643 feat(ui): tag recording level events with mic vs system source (FR-CAP-7) 2026-07-11 00:27:02 -05:00
iamdoubz e082f99f32 feat(ui): speaker diarization download cards in Settings > Hardware 2026-07-11 00:23:19 -05:00
iamdoubz 5260a4240b feat(ui): load/download/remove diarization models in the settings store 2026-07-11 00:23:18 -05:00
iamdoubz bf644ea0be feat(ui): expose diarization models over the typed api (FR-MODEL-1) 2026-07-11 00:23:17 -05:00
iamdoubz 13fb9984da feat(ui): model download cards + PST import range + calendar cleanup
Settings > Hardware: the Models list is now a bordered card row per
model with active/installed badges and a real progress bar for
downloads (was plain text links + a bare "downloading NN%" string),
using the app's existing lucide icon set.

Settings > Calendar: an Import range picker (30/90/180/365 days/all
time, persisted, applied to both a manual Import and the pst_auto_sync
startup re-import) and a Clean up section (delete all / older-than-N
threshold, confirm() gated, always reports how many events were kept
because they're linked to a meeting) -- fixes the 14,686-row bloat from
importing a mailbox's entire unbounded history.
2026-07-11 00:13:02 -05:00
iamdoubz 99d50583cf feat(ui): live notes pane, click-to-annotate transcript, resizable split
Granola-style redesign: the live-recording view gains a Notes pane
(bound to recording.notesText) alongside the transcript, and each
transcript line is clickable to attach/edit a note for that moment
(recording.segmentNotes, badge on annotated lines).

Also, for both the live and finalized-meeting layouts: the
transcript/notes split is now drag-resizable and each side
independently collapsible (Splitter + layout store), and the
post-finalize Notes pane defaults to Preview (rendered) instead of raw
Editor mode, since the merged document is now properly sectioned rather
than a single-speaker blob.
2026-07-11 00:13:01 -05:00
iamdoubz 0027becd10 style(ui): polish Upload now / Save action items / provider select
Upload now and Save action items now use the existing .primary
solid-accent button style (with icons) instead of unstyled native
buttons; the AI provider dropdown gets real border/background/hover
states matching the app's other selects.
2026-07-11 00:12:45 -05:00
iamdoubz e3c80b565c feat(ui): resizable/hideable meetings-list and summary panes
Left (meetings list) and right (summary) panes gain drag-to-resize via
Splitter and a collapse/expand toggle (thin rail + panel icon), sizes
and collapsed state persisted via the layout store.
2026-07-11 00:12:44 -05:00
iamdoubz 2a637e78ad feat(ui): default pst_import_range_days in the settings fallback 2026-07-11 00:12:29 -05:00
iamdoubz 80ad2a5f76 feat(ui): calendar store passes rangeDays through to importPst 2026-07-11 00:12:28 -05:00
iamdoubz 6622ae81ff feat(ui): recording store gains live notes state
notesText (debounced save, flushed on stop before finalize reads it),
segmentNotes Map keyed by anchor_ms, setNotesText/setSegmentNote.
2026-07-11 00:12:26 -05:00
iamdoubz 90b2d51d08 feat(ui): API bindings for live notes and calendar range/cleanup
updateLiveNotes/setSegmentNote; import_pst's rangeDays param;
cleanupCalendarEvents; pst_import_range_days on AppSettings.
2026-07-11 00:12:15 -05:00
iamdoubz 7081040475 feat(ui): layout store for resizable/hideable panes
Pane widths, collapsed flags, and the transcript/notes split ratio,
persisted to localStorage (pure client-side UI ephemera, not app data --
no Tauri settings round-trip needed).
2026-07-11 00:12:04 -05:00
iamdoubz 7dfcbdf247 feat(ui): draggable Splitter component for resizable panes
WAI-ARIA "window splitter" pattern (role=separator, keyboard-operable) --
reusable divider used by the resizable/hideable pane layout.
2026-07-11 00:12:01 -05:00
iamdoubz 864cd33a5f docs: document live notes commands and calendar range/cleanup commands
update_live_notes/set_segment_note; import_pst's new rangeDays param;
new cleanup_calendar_events command.
2026-07-11 00:11:53 -05:00
iamdoubz 94eafd4fae docs: document manual_notes.json (Granola-style live notes redesign) 2026-07-11 00:11:52 -05:00
iamdoubz 45ddc2bd3e feat(commands): live notes commands + calendar range/cleanup commands
Live notes (Granola-style redesign):
- update_live_notes/set_segment_note: live-session-only, mutate
  RecordingSession.manual_notes and write-through to manual_notes.json.
- stop_recording, reprocess_transcript, and resume_transcription now call
  MarkdownNotes::merge (loading manual_notes.json from disk for the
  latter two, which have no live RecordingSession) instead of the old
  transcript-only to_markdown, so re-transcription/crash-recovery never
  silently drops what the user typed.
- refresh_notes_and_notify no longer re-renders/overwrites notes.md on a
  post-finalize speaker rename/merge -- that used to clobber manual
  edits; it still updates the speakers table and emits the live-refresh
  event.

Calendar range/cleanup (14,686-row bloat fix):
- import_pst_core/import_pst take an optional range_days, converted to a
  from timestamp for PstSource.
- New cleanup_calendar_events command wrapping the new store method.
2026-07-11 00:11:45 -05:00
iamdoubz bb226dcb5e feat(core): wire up live notes and calendar range/cleanup commands
- RecordingSession.manual_notes; register update_live_notes/set_segment_note.
- Register cleanup_calendar_events; pass pst_import_range_days through to
  the pst_auto_sync startup re-import so it applies the same range as the
  last manual import, not the full mailbox history.
2026-07-11 00:11:32 -05:00
iamdoubz 98b183d2b0 feat(storage): cleanup_calendar_events, protects meeting-linked rows
CalendarCleanupResult + Store::cleanup_calendar_events(older_than_unix):
None deletes every unlinked event ("Delete all"), Some(cutoff) only those
starting before it. An event referenced by meetings.calendar_event_id is
always kept regardless of age or the "delete all" choice; participant
links cascade via the existing ON DELETE CASCADE FK.
2026-07-11 00:11:19 -05:00
iamdoubz 8828fca805 fix(calendar): honor from/to range on PST import
PstSource used to silently ignore CalImport.from/to (only Graph honored
a range) -- a long-lived mailbox has no natural upper bound on history,
so importing "everything" meant every recurring series expanded across
its full lifetime (up to 500 occurrences each) plus every one-off entry
the file ever held (e.g. a decade of Outlook's auto-generated yearly
holidays). Filters parsed events by starts_at after readpst parsing,
after collect_events -- doesn't need to touch how readpst is invoked or
how recurrence expansion works.
2026-07-11 00:11:02 -05:00
iamdoubz 9057b25e92 feat(notes): MarkdownNotes::merge folds manual notes into the transcript
Adds the Notes/Transcript-sectioned merge (freeform notes under ## Notes,
transcript dialogue under ## Transcript with per-moment annotations
placed as > note callouts right after the paragraph they anchor to,
unmatched anchors surfaced under "Other notes" rather than dropped) that
stop_recording now calls instead of the old transcript-only to_markdown.
2026-07-11 00:10:47 -05:00
iamdoubz 41b5553836 feat(core): add ManualNotes/SegmentNote types; pst_import_range_days setting
Two features:
- Granola-style live notes redesign: ManualNotes/SegmentNote are the IPC +
  on-disk shape of manual_notes.json (freeform notes typed during
  recording, plus per-moment annotations anchored by timestamp).
- Calendar bloat fix: Settings.pst_import_range_days persists how far back
  a PST import should go (None = full history), applied to both a manual
  import and the pst_auto_sync startup re-import.
2026-07-11 00:10:37 -05:00
iamdoubz 538db4372f feat(core): add manual_notes.json path helper
Granola-style live notes redesign: manual_notes_file(id) mirrors the
existing meeting_dir()-based helpers for the new per-meeting raw
user-notes artifact.
2026-07-11 00:10:24 -05:00
iamdoubz 8906f8c8ed chore(ui): disable the default WebView right-click menu
Back/Forward/Reload/Inspect doesn't belong in a native-feeling desktop
app. Disabled app-wide for now, per explicit instruction -- a
WhispAssist-specific context menu is a deferred follow-up, not decided
against.
2026-07-10 22:08:25 -05:00
iamdoubz 2f62e4b13a fix(calendar): run readpst headless instead of popping a console window
readpst.exe is a console-subsystem binary; WhispAssist has no console of
its own, so Windows created a brand new visible console window for it on
every PST import. CREATE_NO_WINDOW spawns it fully headless -- its
stdout/stderr are still captured normally via .output().
2026-07-10 22:08:19 -05:00
iamdoubz 3214bd2740 fix(calendar): stable content-based raw_uid for UID-less events; UTC dedup keys
parse_vevents used to leave raw_uid: None for any VEVENT with no UID
line, which the storage layer's unique index exempts from dedup -- so
that event duplicated on every re-import forever. Falls back to
content_uid (subject|organizer|starts_at|ends_at) so re-parsing the same
source resolves to the same key. Also fixes ymd_digits, the per-occurrence
dedup-key suffix for expanded recurring events, to format in UTC instead
of the machine's local timezone -- it could otherwise compute a different
date (and therefore a different key) for the same occurrence across a DST
transition or timezone change between imports.
2026-07-10 19:22:18 -05:00
iamdoubz e7b951cb90 fix(storage): collapse existing duplicate calendar_events rows
One-time cleanup for anyone already hit by the reimport-duplication bug:
groups existing rows by (source, raw_uid) -- backfilling a content-based
key first for rows that had none -- keeps the row a meeting is attached
to (or the first survivor otherwise), repoints any meeting pointing at a
row about to be removed, then deletes the rest. The unique index is
dropped and recreated around this since the backfill step can
momentarily produce rows that collide before they're deduped.
2026-07-10 19:22:10 -05:00
iamdoubz a341afc52f fix(commands): reindex search after generate_summary writes summary.json
summary.json is sealed straight to disk, not through a store write, so
the FTS index needs an explicit nudge to pick up newly generated
summaries (FR-SEARCH-1).
2026-07-10 19:22:04 -05:00
iamdoubz 83babc99cd fix(storage): reindex FTS on tags/summary; default a stable calendar raw_uid
Two bugs, one file:

- Search: reindex_fts now also indexes summary_text and tags_text, and
  runs on every set_tags call (it previously only fired from
  finalize_meeting/update_notes, so tag and summary changes never touched
  the index at all). Promoted from a private helper to a Store trait
  method so commands.rs can call it after summary.json is written
  (summary lives only on disk, not through a store write).

- Calendar dedup: import_calendar_events used to insert whatever raw_uid
  the caller passed, including None -- which the (source, raw_uid)
  unique index explicitly exempts from dedup, so any event without a
  captured UID duplicated on every re-import forever (the reported
  1762 -> 11,764 row bug). Defaults to a deterministic content-derived
  key (calendar::content_uid) when raw_uid is missing, and adds a
  regression test that re-importing the same batch repeatedly doesn't
  grow the table.
2026-07-10 19:21:59 -05:00
iamdoubz 4015b5132e feat(storage): add summary/tags columns to the FTS5 search index
meeting_fts only ever indexed title/transcript/notes -- search silently
never covered summary or tags despite FR-SEARCH-1 requiring it (bug
report: "search not working" for those). meeting_fts is a derived index
(never a source of truth), so drop+recreate is safe: backfill_fts
repopulates every meeting from scratch on next startup.
2026-07-10 19:21:48 -05:00
iamdoubz b5f5ef172c feat(commands): auto-label the mic speaker "You" at stop_recording
Wires VoiceSample creation into start_recording (mic-enabled path) and
runs the voiceprint match after the final diarization pass in
stop_recording: on a confident match, persists "You" and "Speaker 2",
"Speaker 3"... via the existing rename_speaker store path, skipping any
label the user already renamed live.
2026-07-10 19:05:28 -05:00
iamdoubz 69eedc9354 feat(core): track the mic voiceprint sample on RecordingSession
mic_voice_sample carries the VoiceSample handle from start_recording
through to stop_recording so the final diarization pass can run the
mic-speaker voiceprint match.
2026-07-10 19:05:23 -05:00
iamdoubz c9b5b91473 chore(diarization): register the new voiceprint module 2026-07-10 19:05:18 -05:00
iamdoubz dca797aaa3 feat(diarization): voiceprint match to identify the mic speaker cluster
Mic and system audio are already summed into one mono stream before
diarization runs, so clustering alone can't tell which cluster is the
user's own voice. match_mic_speaker compares a mic-only sample's speaker
embedding (same sherpa-onnx model diarization already uses) against each
diarized cluster's own audio and returns a label->name map: best match ->
"You", the rest -> "Speaker 2", "Speaker 3", etc. Returns empty (no
guessing) when the mic sample is too short or no cluster clears the
similarity threshold.
2026-07-10 19:05:14 -05:00
iamdoubz b532acbb3c feat(audio): capture a bounded mic-only sample for voiceprint matching
VoiceSample collects the first ~8s of raw 16kHz-mono mic audio during a
recording, then stops accepting once full. Feeds the mic-speaker
voiceprint match in diarization::voiceprint (bug: mic speaker mislabeled
S1/S2 instead of "You").
2026-07-10 19:05:07 -05:00
iamdoubz 469df7524f fix(transcription): drop whisper-hallucinated segments on silent windows 2026-07-10 18:51:08 -05:00
iamdoubz ec864c24f0 fix(transcription): medium quantized model only exists as q5_0 upstream 2026-07-10 18:51:07 -05:00