feat(sync): auto-resync artifacts on edit (notes/summary/transcript/tags/action items) (FR-SYNC-5)

This commit is contained in:
iamdoubz
2026-07-11 22:37:51 -05:00
parent 836547596b
commit 70eb182eb4
+39 -2
View File
@@ -1732,6 +1732,7 @@ pub async fn reprocess_transcript(
template.as_ref(),
);
let _ = state.store.update_notes(&meeting_id, &notes_md).await;
spawn_auto_sync(&app, state.store.clone(), meeting_id.clone());
let _ = app.emit(
"transcript://finalized",
@@ -2044,6 +2045,7 @@ pub async fn list_meetings(
/// Replaces a meeting's complete tag set (Phase 8, FR-SEARCH-2).
#[tauri::command]
pub async fn set_tags(
app: AppHandle,
state: State<'_, AppState>,
meeting_id: MeetingId,
tags: Vec<String>,
@@ -2052,7 +2054,9 @@ pub async fn set_tags(
.store
.set_tags(&meeting_id, &tags)
.await
.map_err(|e| WaError::new("storage", e.to_string()))
.map_err(|e| WaError::new("storage", e.to_string()))?;
spawn_auto_sync(&app, state.store.clone(), meeting_id);
Ok(())
}
/// All known tag names, sorted, for filter/autocomplete UI (Phase 8, FR-SEARCH-2).
@@ -2250,6 +2254,7 @@ pub(crate) fn cleanup_playback_temp() {
#[tauri::command]
pub async fn update_notes(
app: AppHandle,
state: State<'_, AppState>,
meeting_id: MeetingId,
markdown: String,
@@ -2258,7 +2263,9 @@ pub async fn update_notes(
.store
.update_notes(&meeting_id, &markdown)
.await
.map_err(|e| WaError::new("storage", e.to_string()))
.map_err(|e| WaError::new("storage", e.to_string()))?;
spawn_auto_sync(&app, state.store.clone(), meeting_id);
Ok(())
}
/// `format` ∈ `md | pdf | docx | bundle`. `dest` is a file path for
@@ -2710,6 +2717,7 @@ pub async fn generate_summary(
if let Err(e) = state.store.reindex_fts(&meeting_id).await {
tracing::warn!("failed to reindex search after summary generation: {e}");
}
spawn_auto_sync(&app, state.store.clone(), meeting_id.clone());
let _ = app.emit(
"llm://done",
@@ -2723,6 +2731,7 @@ pub async fn generate_summary(
/// T8.6, FR-CAL-5).
#[tauri::command]
pub async fn confirm_action_items(
app: AppHandle,
state: State<'_, AppState>,
meeting_id: MeetingId,
items: Vec<ActionItem>,
@@ -2758,6 +2767,7 @@ pub async fn confirm_action_items(
_ => crate::reminders::cancel(id),
}
}
spawn_auto_sync(&app, state.store.clone(), meeting_id);
Ok(())
}
@@ -3689,6 +3699,33 @@ pub(crate) async fn enqueue_meeting_sync(
Ok(queued)
}
/// Re-sync a meeting's artifacts after an edit (notes / summary / transcript /
/// tags / action items), mirroring sync-on-finalize (T9.5, FR-SYNC-5). A no-op
/// unless sync is enabled; only finalize-trigger targets are touched and
/// unchanged files are deduped by SHA-256, so a save that didn't alter an
/// uploaded artifact queues (and uploads) nothing. Fire-and-forget so the edit
/// command returns immediately.
///
/// ponytail: fires on every (debounced) save; if WebDAV PUT volume from live
/// note-typing ever matters, coarsen the debounce or trigger on blur instead.
pub(crate) fn spawn_auto_sync(
app: &AppHandle,
store: std::sync::Arc<dyn crate::storage::Store>,
meeting_id: MeetingId,
) {
if !load_settings().sync_enabled {
return;
}
let app = app.clone();
tauri::async_runtime::spawn(async move {
match enqueue_meeting_sync(store.as_ref(), &meeting_id, None, true).await {
Ok(0) => {} // nothing changed
Ok(_) => pump_sync(&app, store.as_ref()).await, // upload the changes
Err(e) => tracing::warn!("auto-sync enqueue failed: {e:?}"),
}
});
}
/// Upload one job's file: ensure the remote dir, PUT, report bytes sent.
async fn upload_job(
target: &dyn crate::sync::SyncTarget,