The search box now searches transcripts + notes (not just titles) and
shows a matched snippet per result. FTS5 is word-based rather than
substring-based, a deliberate tradeoff for gaining full-content search.
The initial notes.md render after finalize/resume/recovery bypassed
the Store abstraction entirely (raw std::fs::write) — routing it
through update_notes means that first render also gets FTS-indexed and
bumps updated_at, instead of only later manual edits doing either.
meeting_fts existed since migration 0001 but nothing ever wrote to it.
Adds reindex_fts (called from finalize_meeting/update_notes so the
index can't drift from what's on disk), a one-time startup backfill
for meetings finalized before this feature existed, delete_meeting
cleanup (meeting_fts is a virtual table with no FK/CASCADE support),
and a real Store::search() replacing the hardcoded stub. Query input
is phrase-quoted per token so stray FTS5 operators in free-text search
input can't throw a MATCH syntax error.
Replaces the static "Summary"/"Action items" placeholder text with:
generate button (hidden behind a "configure an LLM provider" message
when none is set), live token streaming while generating, rendered
summary + decisions once done, and an editable action-item checklist
with a save step before anything gets confirmed as a task.
Streams tokens for whichever meeting is generating (gated by
summarizingId so a stale stream can't clobber a summary if the user
switches selection mid-generation), and refreshes the selected meeting
once llm://done or confirm_action_items lands.
Types for SummaryFile/ActionItem/LlmStatus and a confirmActionItems
wrapper + onLlmDone listener, so the frontend can consume what
generate_summary/confirm_action_items already produce on the backend.
Also adds errorMessage(): every backend command rejects with
{ kind, message } (see src-tauri/src/error.rs), not a native Error, so
`e instanceof Error` is always false for it and `String(e)` renders as
the literal text "[object Object]" — found by actually clicking
"Generate summary" against an unreachable Ollama endpoint.
Notes and LLM-generated summaries both render untrusted Markdown via
{@html} into a webview with an IPC bridge to the Rust backend, so the
DOMPurify sanitization step is a real security boundary, not cosmetic —
worth one shared implementation instead of a second inline copy that
could drift out of sync.
Traps focus in the recording-consent overlay the same way as Settings.
Adds a visually-hidden aria-live region announcing "Recording
started/paused/stopped" — previously nothing in the header was a live
region, so a non-visual user had no way to learn recording had begun.
Bold/Italic/Bullet-list/Checkbox buttons rendered a bare glyph with
only a title tooltip; add matching aria-label so a screen reader
announces the action rather than the raw Unicode character name.
Moves focus into the dialog on mount, cycles Tab/Shift+Tab within its
focusable elements, and restores focus to whatever had it before the
dialog opened once it closes.
Ctrl+Shift+R toggles recording, Ctrl+, toggles Settings, Escape closes
whichever overlay (consent notice, then Settings) is open. Shortcuts
never hijack typing in an input/textarea/select/contenteditable target;
Escape is exempt from that guard so it always closes an open modal.
"Template apply" from the roadmap item isn't included — there's no
template-selection UI yet (that lands with T8.1 in Phase 8).
A simple rms-fill + peak-marker bar rather than a scrolling waveform —
either satisfies the requirement with far less state. Shows a warning
banner while a capture device reconnect is in progress.
start_recording now creates the EventSink alongside the existing frame
channel and forwards Level/DeviceChanged onto a small fire-and-forget
thread that emits "recording://level" and "recording://device".
Adds AudioLevel (rms/peak per chunk, throttled to ~20Hz) and
CaptureEvent::DeviceChanged emitted through a new EventSink alongside
the existing FrameSink. On a capture read failure, reopens against
whatever is now the default render device and continues the same WAV
file if the new format is compatible; otherwise fails loudly rather
than silently corrupting the recording.
Resolves settings.theme against prefers-color-scheme for "system" and
updates live if the OS preference changes. Adds a theme selector next
to the settings gear. Also adds --danger/--warning/--success tokens
tuned per-theme for WCAG AA contrast (the prior fixed hex values failed
4.5:1 against the dark palette).
Shows the LLM endpoint's local/remote status, sync enabled state, the
derived egress allowlist, and enabled sync targets with third-party/TLS
labeling, so the UI can prove local-only handling to the user.
Reports LLM endpoint/local-only status and enabled sync targets so the
UI can prove local-only handling, per the shape fixed in
docs/04-api-contracts.md. Sync targets stay empty until Phase 9 lands
SyncManager; list_sync_targets' not_implemented error is swallowed
rather than failing the whole self-check.