From 2f56891d40face7bdc1c7ce69bfba80b1be11f51 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Thu, 2 Jul 2026 11:25:48 -0500 Subject: [PATCH] feat(templates): apply templates at all 4 notes-render call sites (T8.1, FR-NOTE-5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src-tauri/src/commands.rs | 67 +++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index eaac5cc..aeee57f 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -36,6 +36,8 @@ pub struct StartRecordingArgs { /// Retain audio as .wav? Defaults to false (ADR-0009). Controls retention, not capture. #[serde(default)] pub record: bool, + /// Note-template id (Phase 8, T8.1, FR-NOTE-5) — see `notes::built_in_note_templates`. + pub template_id: Option, } // ---- File-backed settings (consent/default-retention/storage policy) ---- @@ -215,6 +217,7 @@ pub async fn start_recording( .meeting_title .unwrap_or_else(|| "Untitled meeting".to_string()), calendar_event_id: args.calendar_event_id, + template_id: args.template_id, }) .await .map_err(|e| WaError::new("storage", e.to_string()))?; @@ -477,7 +480,7 @@ pub async fn stop_recording( // T2.10: persist transcript.json (via finalize_meeting) and notes.md // *before* touching the working WAV, so a crash here still leaves a // recoverable, regenerable meeting. - state + let template_id = state .store .finalize_meeting( &meeting_id, @@ -494,7 +497,11 @@ pub async fn stop_recording( .await .map_err(|e| WaError::new("storage", e.to_string()))?; - let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None); + let template = template_id + .as_deref() + .and_then(crate::notes::note_template_by_id); + let notes_md = + crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None, template.as_ref()); let _ = state.store.update_notes(&meeting_id, ¬es_md).await; let _ = app.emit( @@ -613,9 +620,17 @@ async fn refresh_notes_and_notify( .get_meeting(meeting_id) .await .map_err(|e| WaError::new("storage", e.to_string()))?; - let notes_md = - crate::notes::MarkdownNotes.to_markdown(&meeting.segments, &meeting.speakers, None); - let _ = std::fs::write(meeting_dir(meeting_id).join("notes.md"), notes_md); + let template = meeting + .template_id + .as_deref() + .and_then(crate::notes::note_template_by_id); + let notes_md = crate::notes::MarkdownNotes.to_markdown( + &meeting.segments, + &meeting.speakers, + None, + template.as_ref(), + ); + let _ = state.store.update_notes(meeting_id, ¬es_md).await; let _ = app.emit( "diarization://updated", serde_json::json!({ "meetingId": meeting_id, "speakers": meeting.speakers }), @@ -881,6 +896,10 @@ pub async fn reprocess_transcript( .map(|s| (s.end_ms / 1000) as i64) .unwrap_or(meeting.duration_secs.unwrap_or(0)); + let template = meeting + .template_id + .as_deref() + .and_then(crate::notes::note_template_by_id); state .store .finalize_meeting( @@ -898,7 +917,12 @@ pub async fn reprocess_transcript( .await .map_err(|e| WaError::new("storage", e.to_string()))?; - let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &meeting.speakers, None); + let notes_md = crate::notes::MarkdownNotes.to_markdown( + &segments, + &meeting.speakers, + None, + template.as_ref(), + ); let _ = state.store.update_notes(&meeting_id, ¬es_md).await; let _ = app.emit( @@ -961,7 +985,7 @@ pub async fn resume_transcription( .map(|s| (s.end_ms / 1000) as i64) .unwrap_or(0); - state + let template_id = state .store .finalize_meeting( &meeting_id, @@ -980,7 +1004,11 @@ pub async fn resume_transcription( .await .map_err(|e| WaError::new("storage", e.to_string()))?; - let notes_md = crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None); + let template = template_id + .as_deref() + .and_then(crate::notes::note_template_by_id); + let notes_md = + crate::notes::MarkdownNotes.to_markdown(&segments, &speakers, None, template.as_ref()); let _ = state.store.update_notes(&meeting_id, ¬es_md).await; let _ = app.emit( @@ -1050,6 +1078,13 @@ pub async fn search(state: State<'_, AppState>, query: String) -> WaResult WaResult> { + Ok(crate::notes::built_in_note_templates()) +} + #[tauri::command] pub async fn get_meeting(state: State<'_, AppState>, meeting_id: MeetingId) -> WaResult { state @@ -1261,10 +1296,13 @@ pub async fn pull_ollama_model(app: AppHandle, model: String) -> WaResult<()> { .map_err(|e| WaError::new("llm", e.to_string())) } -/// Built-in prompt biases for common meeting shapes (T5.4). User-authored -/// templates are Phase 8's FR-NOTE-5 — an unrecognized id is ignored rather -/// than erroring, so a stale/removed template id never blocks a summary. -fn built_in_template(id: &str) -> Option<&'static str> { +/// Built-in LLM-prompt biases for common meeting shapes (T5.4) — distinct +/// from `notes::NoteTemplate` (Phase 8, T8.1/FR-NOTE-5), which structures +/// notes.md itself. Both use the same meeting-type identifiers +/// ("standup"/"retro"/"one-on-one") since they describe the same kinds of +/// meetings, but are two independent lookups. An unrecognized id is ignored +/// rather than erroring, so a stale/removed template id never blocks a summary. +fn summary_prompt_bias(id: &str) -> Option<&'static str> { match id { "standup" => Some( "This is a daily standup. Focus the summary on what each person did, what's \ @@ -1304,7 +1342,9 @@ fn build_prompt(meeting: &Meeting, template_id: Option<&str>) -> crate::llm::Pro crate::llm::Prompt { transcript: meeting.notes_markdown.clone(), metadata, - template: template_id.and_then(built_in_template).map(str::to_string), + template: template_id + .and_then(summary_prompt_bias) + .map(str::to_string), } } @@ -1779,6 +1819,7 @@ mod tests { summary: None, calendar_event_id: None, tags: Vec::new(), + template_id: None, } }