feat(action-items): table-backed action_items on Meeting; reconcile deletes (FR-LLM-3)
This commit is contained in:
1 parent
9c74f84c5b
commit
a0faaa94b4
1 file changed
+35
@@ -104,6 +104,12 @@ pub struct Meeting {
|
||||
/// Note-template id (Phase 8, T8.1, FR-NOTE-5), if one was picked at
|
||||
/// creation — resolved against `notes::templates::catalog()`.
|
||||
pub template_id: Option<String>,
|
||||
/// Confirmed/edited action items from the `action_items` table — the
|
||||
/// source of truth the user manages directly (add/edit/delete via
|
||||
/// `confirm_action_items`, FR-LLM-3). Falls back to the drafts parsed
|
||||
/// into `summary.json` when the table has none yet, so a freshly
|
||||
/// generated summary still shows its suggestions.
|
||||
pub action_items: Vec<ActionItem>,
|
||||
}
|
||||
|
||||
/// A calendar event with its attendees (T6.3/T6.4, FR-CAL-1/3) — the
|
||||
@@ -892,6 +898,14 @@ impl Store for SqliteStore {
|
||||
let summary = read_artifact(&folder.join("summary.json"))
|
||||
.and_then(|s| serde_json::from_str::<SummaryFile>(&s).ok());
|
||||
let tags = self.tags_for_meeting(id).await?;
|
||||
// Table is the source of truth for confirmed/edited items; fall back
|
||||
// to the summary's parsed drafts only when nothing's been saved yet.
|
||||
let mut action_items = self.list_action_items(id).await?;
|
||||
if action_items.is_empty() {
|
||||
if let Some(s) = &summary {
|
||||
action_items = s.action_items.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Meeting {
|
||||
id: row.get("id"),
|
||||
@@ -911,6 +925,7 @@ impl Store for SqliteStore {
|
||||
calendar_event_id: row.get("calendar_event_id"),
|
||||
tags,
|
||||
template_id: row.get("template_id"),
|
||||
action_items,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1254,6 +1269,26 @@ impl Store for SqliteStore {
|
||||
items: &[ActionItem],
|
||||
) -> Result<Vec<ActionItem>, StoreError> {
|
||||
let now = now_unix();
|
||||
// Reconcile deletes: any row previously saved for this meeting that the
|
||||
// caller no longer includes was removed in the UI (FR-LLM-3). Action
|
||||
// items per meeting number in the low tens, so a per-row delete loop is
|
||||
// fine. ponytail: O(n) delete scan, batch it only if n ever gets large.
|
||||
let keep: std::collections::HashSet<&str> =
|
||||
items.iter().filter_map(|i| i.id.as_deref()).collect();
|
||||
let existing_ids: Vec<String> =
|
||||
sqlx::query_scalar("SELECT id FROM action_items WHERE meeting_id = ?")
|
||||
.bind(id)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
for eid in &existing_ids {
|
||||
if !keep.contains(eid.as_str()) {
|
||||
sqlx::query("DELETE FROM action_items WHERE id = ? AND meeting_id = ?")
|
||||
.bind(eid)
|
||||
.bind(id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
let mut saved = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
let item_id = match &item.id {
|
||||
|
||||
Reference in new issue
Block a user