Commit Graph
91 Commits
Author SHA1 Message Date
iamdoubz 04628bcb46 docs: document template_id column and list_note_templates/start_recording (T8.1, FR-NOTE-5) 2026-07-02 11:26:05 -05:00
iamdoubz 31e644a671 test: fix calendar_link_smoke_test for NewMeeting.template_id (T8.1) 2026-07-02 11:25:59 -05:00
iamdoubz 9a2177dd62 feat(templates): register list_note_templates command (T8.1, FR-NOTE-5) 2026-07-02 11:25:53 -05:00
iamdoubz 2f56891d40 feat(templates): apply templates at all 4 notes-render call sites (T8.1, FR-NOTE-5)
Adds StartRecordingArgs.template_id + list_note_templates command;
renames built_in_template to summary_prompt_bias to disambiguate it
from the new NoteTemplate concept (same meeting-type identifiers,
different purpose). Also fixes refresh_notes_and_notify (the
rename/merge-speaker refresh path) to route through
Store::update_notes instead of a raw std::fs::write — it was bypassing
T8.2's FTS reindexing, a gap missed there because this call site uses
meeting_dir(meeting_id) without the & that my earlier grep matched on.
2026-07-02 11:25:48 -05:00
iamdoubz 6b5b359438 feat(templates): persist template_id; finalize_meeting returns it (T8.1, FR-NOTE-5)
NewMeeting/Meeting gain template_id; finalize_meeting now returns it
(fetched via one cheap scalar SELECT) so callers that only have
segments/speakers in memory (not a fetched Meeting) can resolve the
template without a separate round-trip.
2026-07-02 11:25:41 -05:00
iamdoubz f63076ad06 feat(templates): NoteTemplate catalog + apply in to_markdown (T8.1, FR-NOTE-5)
4 built-in templates (Standup, 1:1, Sales Call, Retro), keyed by the
same meeting-type ids commands::summary_prompt_bias already uses for
LLM-prompt biasing — one identifier space shared by both, but two
independent lookups (structuring notes.md vs. biasing a summary
prompt). Section headers are prepended as an empty scaffold above the
auto-rendered speaker dialogue.
2026-07-02 11:25:34 -05:00
iamdoubz 67354642ab feat(templates): add meetings.template_id column (T8.1, FR-NOTE-5)
Templates themselves are a built-in Rust catalog, not a DB table —
this just remembers which one a meeting picked, so re-rendering
notes.md on reprocess/resume keeps the same section structure.
2026-07-02 11:25:27 -05:00
iamdoubz fd220641ac feat(ui): add Export PDF / Export Word buttons (T8.4, FR-NOTE-4) 2026-07-02 11:03:01 -05:00
iamdoubz 7b03b12f25 feat(ui): extend exportMeeting's format type to include pdf/docx (T8.4, FR-NOTE-4) 2026-07-02 11:02:57 -05:00
iamdoubz 06bdf6d469 feat(export): wire pdf/docx formats into export_meeting (T8.4, FR-NOTE-4) 2026-07-02 11:02:52 -05:00
iamdoubz 0425f64cfe feat(export): DOCX renderer via docx-rs (T8.4, FR-NOTE-4)
Bullet items get a literal "• " prefix rather than a real Word
numbered-list definition — this is read-only generated content, not
something the user continues typing into, so numbering-continuity
machinery isn't worth the extra API surface.
2026-07-02 11:02:46 -05:00
iamdoubz ec7ae5c01f feat(export): PDF renderer via printpdf + built-in Helvetica (T8.4, FR-NOTE-4)
Character-count word wrap (printpdf's builtin fonts expose no glyph
metrics) and per-boldness-run line groups (a bold speaker-name prefix
gets its own line above the dialogue) — simple choices that keep the
renderer self-contained with no bundled font asset.
2026-07-02 11:02:41 -05:00
iamdoubz f6a5b9583a feat(export): parse notes Markdown into a block model for PDF/Word (T8.4, FR-NOTE-4)
Fills in the ExportFormat::Pdf/Docx arms that previously errored
"lands in Phase 8". markdown_to_blocks() covers headings, paragraphs,
bullet/task lists, and bold runs — everything MarkdownNotes::to_markdown
and the notes editor's toolbar actually produce, not general Markdown.
Shared by both new renderer submodules (pdf, docx).
2026-07-02 11:02:35 -05:00
iamdoubz 3dec299c4c feat(export): add printpdf/docx-rs/pulldown-cmark deps (T8.4, FR-NOTE-4)
Pure-Rust PDF/Word generation and Markdown parsing — no external
binary or cloud conversion service, consistent with the fully-local
invariant.
2026-07-02 11:02:29 -05:00
iamdoubz 8f08b17be6 feat(ui): editable tags section in the meeting detail panel (T8.3, FR-SEARCH-2) 2026-07-02 10:38:59 -05:00
iamdoubz bf74a3cc92 feat(ui): tag/date filters and tag chips in the meetings list (T8.3, FR-SEARCH-2) 2026-07-02 10:38:54 -05:00
iamdoubz eb17cc68e0 feat(ui): add filter/tag state and setTags/loadTags to the meetings store (T8.3, FR-SEARCH-2) 2026-07-02 10:38:50 -05:00
iamdoubz 15f72aaca6 feat(ui): add MeetingFilter type + setTags/listTags wrappers (T8.3, FR-SEARCH-2) 2026-07-02 10:38:45 -05:00
iamdoubz d41ecc5106 docs: update list_meetings signature; add list_tags contract (T8.3, FR-SEARCH-2)
Drops limit/offset (never implemented, no pagination need yet at local
meeting counts) in favor of from/to date filters, matching FR-SEARCH-2's
"filter by date, tag, or participant" and what was actually built.
2026-07-02 10:38:41 -05:00
iamdoubz e6e209e1c4 feat(tags): register set_tags/list_tags commands (T8.3, FR-SEARCH-2) 2026-07-02 10:38:36 -05:00
iamdoubz d1ca2f63dd feat(tags): add set_tags/list_tags commands; extend list_meetings (T8.3, FR-SEARCH-2) 2026-07-02 10:38:31 -05:00
iamdoubz fc6a2fa0aa feat(tags): wire tags + list filters (T8.3, FR-SEARCH-2)
tags/meeting_tags existed since migration 0001, completely unused.
Adds upsert_tag/tags_for_meeting (mirroring upsert_participant's
find-or-create pattern) and real set_tags/list_tags implementations.

Extends list_meetings with tag/participant/date filters via a new
MeetingFilter struct, replacing the old query-only signature — the
participant filter matches either a speaker mapped to that participant
or an attendee of the meeting's linked calendar event, since neither
signal alone covers "who was in this meeting." Tags are populated on
Meeting/MeetingListItem/SearchHit via a per-row lookup (N+1, but
meeting counts are small enough that this is simpler than GROUP_CONCAT
string-splitting).
2026-07-02 10:38:26 -05:00
iamdoubz c2d282e5ba feat(tags): add tags fields to MeetingListItem/SearchHit (T8.3, FR-SEARCH-2) 2026-07-02 10:38:19 -05:00
iamdoubz 66d4c614bb feat(ui): replace title-only filter with full-text search (T8.2, FR-SEARCH-1)
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.
2026-07-02 10:08:12 -05:00
iamdoubz 3d1129ff1e feat(ui): add search state + method to the meetings store (T8.2, FR-SEARCH-1) 2026-07-02 10:08:02 -05:00
iamdoubz 354588e1ab feat(ui): add SearchHit type + search() client wrapper (T8.2, FR-SEARCH-1) 2026-07-02 10:07:55 -05:00
iamdoubz f2992508e0 docs: define SearchHit's shape for the search command (T8.2, FR-SEARCH-1)
Was referenced in the contract but never actually specified.
2026-07-02 10:07:48 -05:00
iamdoubz c9c049ce2a feat(search): register the search command (T8.2, FR-SEARCH-1) 2026-07-02 10:07:40 -05:00
iamdoubz d928dfd51d feat(search): add search command; route notes.md writes through the store (T8.2, FR-SEARCH-1)
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.
2026-07-02 10:07:29 -05:00
iamdoubz 68379f8f56 feat(search): wire meeting_fts population + real search (T8.2, FR-SEARCH-1)
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.
2026-07-02 10:07:20 -05:00
iamdoubz b5aa1714ad feat(search): add SearchHit type (T8.2, FR-SEARCH-1)
Same fields as MeetingListItem plus a highlighted snippet of what matched.
2026-07-02 10:07:03 -05:00
iamdoubz 90bc053ad3 Merge pull request 'Hermes 007' (#7) from hermes-007 into main
Reviewed-on: #7
2026-07-02 07:58:38 -05:00
iamdoubz 4d59e7f646 fix(ui): show a loading state for the meetings list (T7.1, FR-UX-1)
meetings.loading existed in the store but nothing read it — the list
briefly showed "No meetings yet" on first load before data arrived.
2026-07-02 07:45:18 -05:00
iamdoubz a0a4cf12b5 feat(ui): wire real summary + action-item rendering into SummaryPanel (T7.1, FR-UX-1)
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.
2026-07-02 07:45:11 -05:00
iamdoubz 6dc68ac92c feat(ui): summary generation + action-item confirmation in the meetings store (T7.1)
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.
2026-07-02 07:45:03 -05:00
iamdoubz 075eec6a95 feat(ui): add summary/action-item types + errorMessage() helper (T7.1, FR-LLM-2/3/4)
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.
2026-07-02 07:44:56 -05:00
iamdoubz b2d423e3ec refactor(ui): use the shared renderMarkdown() in TranscriptNotes (T7.1) 2026-07-02 07:44:47 -05:00
iamdoubz 4609d93faf refactor(ui): extract shared sanitized-Markdown renderer (T7.1)
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.
2026-07-02 07:44:41 -05:00
iamdoubz 5f8ba8253e feat(ui): focus trap for consent dialog + recording-state live region (T7.5, FR-UX-4)
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.
2026-07-02 07:31:13 -05:00
iamdoubz d94472f476 fix(ui): label the symbol-only notes-toolbar buttons (T7.5, FR-UX-4)
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.
2026-07-02 07:31:07 -05:00
iamdoubz 6629b692ca fix(ui): add accessible name to the meetings search input (T7.5, FR-UX-4)
A placeholder isn't a reliable accessible name (it disappears on input
and isn't consistently exposed) — add an explicit aria-label.
2026-07-02 07:31:00 -05:00
iamdoubz 8d1a0047ee feat(ui): trap focus in the Settings dialog (T7.5, FR-UX-4) 2026-07-02 07:30:54 -05:00
iamdoubz 90e59ee186 feat(ui): add trapFocus action for modal dialogs (T7.5, FR-UX-4)
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.
2026-07-02 07:30:48 -05:00
iamdoubz b95ffa5bc1 feat(ui): global keyboard shortcuts for record toggle + Settings (T7.4, FR-UX-3)
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).
2026-07-01 22:38:09 -05:00
iamdoubz bbe8a28144 feat(ui): wire LevelMeter and device notice into the header (T7.3) 2026-07-01 21:09:59 -05:00
iamdoubz ff5144e9f0 feat(ui): add LevelMeter component + device notice in the header (T7.3, FR-CAP-5/6)
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.
2026-07-01 21:09:54 -05:00
iamdoubz 9df6331ecb feat(ui): track live level + device notice in the recording store (T7.3) 2026-07-01 21:09:48 -05:00
iamdoubz 4419f7bd5f feat(ui): type recording://level/device events (T7.3) 2026-07-01 21:09:43 -05:00
iamdoubz fb512889b1 docs: add recording://device event contract (T7.3, FR-CAP-6) 2026-07-01 21:09:38 -05:00
iamdoubz fc9c1d9fc2 feat(audio): forward capture-event sink to recording://level/device (T7.3)
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".
2026-07-01 21:09:34 -05:00