Files
Lumotia/docs/architecture-map/05-core-storage-hotkey-build/storage-crud-tasks.md
jars a1f3f3f134 docs: architecture map (initial 5-slice generation, 105 pages)
Five-slice navigable map of the entire codebase under
docs/architecture-map/. Each slice is a self-contained
breadcrumbed sub-tree:

  01-frontend (16)              Svelte/SvelteKit UI
  02-tauri-runtime (26)         src-tauri commands + lifecycle
  03-audio-transcription (16)   audio + transcription crates
  04-llm-formatting-mcp (19)    llm, ai-formatting, mcp, cloud
  05-core-storage-hotkey-build  core, storage, hotkey, workspace,
                          (26) CI, dev glue

Plus master README.md and data-flow-end-to-end.md tracing
audio bytes from microphone to FTS5 search to MCP read.

Generated by 5 parallel subagents on 2026/05/09 against
HEAD 3c47000. Each page has YAML frontmatter, file:line code
refs, sibling cross-links, plain-English summaries.

Aggregated debt surfaced (full lists in master README):
RB-08 macOS power assertion, schema head drift v14 vs v15,
VAD blocked on ort version conflict, streaming primitives
not wired into live.rs, no prompt versioning, MCP has no
auth, cloud-providers in-memory keystore, SettingsPage
2 484 LOC, commands/live.rs 1 737 LOC, dual theme system,
brand rename to Lumenote pending across the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:04:13 +01:00

6.8 KiB

name, type, slice, last_verified
name type slice last_verified
Storage tasks and subtasks CRUD architecture-map-page 05-core-storage-hotkey-build 2026/05/09

Storage tasks and subtasks CRUD

Where you are: Architecture mapCore, Storage, Hotkey, Build → Storage tasks CRUD

Plain English summary. Tasks are the things the user wants to do. Subtasks are tasks with a parent_task_id set. Both live in the same tasks table with a self-referential foreign key. The completion path includes a small bit of business logic: completing the last subtask auto-completes the parent (and stamps auto_completed = 1 so the daily-completion analytics can exclude cascade-completed parents).

At a glance

  • Source: crates/storage/src/database.rs:301-631.
  • Public surface: insert_task, list_tasks, get_task_by_id, update_task, set_task_energy, complete_task, uncomplete_task, delete_task, insert_subtask, list_subtasks, complete_subtask_and_check_parent, list_recent_completions.
  • Public types: TaskRow, DailyCompletionCount.
  • Consumers: slice 2 task commands; the LLM micro-step decomposer (slice 4) calls insert_subtask; the gamification badge on the Tasks page reads list_recent_completions.

Public types

TaskRowcrates/storage/src/database.rs:825

pub struct TaskRow {
    pub id: String,
    pub text: String,
    pub bucket: String,
    pub list_id: Option<String>,
    pub effort: Option<String>,
    pub notes: String,           // v4
    pub done: bool,
    pub done_at: Option<String>,
    pub created_at: String,
    pub source_transcript_id: Option<String>,
    pub parent_task_id: Option<String>,  // v3
    pub energy: Option<String>,          // v11; "high" | "medium" | "brain_dead" | None
}

DailyCompletionCountcrates/storage/src/database.rs:573

#[derive(serde::Serialize)]
pub struct DailyCompletionCount {
    pub day: String,   // "YYYY-MM-DD" in local time
    pub count: u32,
}

Serialised because slice 2 forwards it to the frontend sparkline.

Functions

insert_task(pool, id, text, bucket, source_transcript_id, list_id, effort, energy)crates/storage/src/database.rs:301

Positional signature, eight arguments. Documented at database.rs:293 as deliberately flat — it mirrors the tasks schema columns. Refactor to a params struct only if another nullable lands after energy.

list_tasks(pool) -> Result<Vec<TaskRow>>crates/storage/src/database.rs:328

SELECT ... FROM tasks ORDER BY created_at DESC. Returns every row, including completed ones. Frontend filters.

get_task_by_id(pool, id) -> Result<Option<TaskRow>>crates/storage/src/database.rs:341

Single-row select.

update_task(pool, id, text, bucket, list_id, effort, notes, energy)crates/storage/src/database.rs:361

Updates the user-editable fields. done and done_at are not updated through this function — completion has its own pair (complete_task / uncomplete_task).

set_task_energy(pool, id, energy) -> Result<TaskRow>crates/storage/src/database.rs:402

Single-column update. Returns the row post-update so the frontend gets confirmation. Energy must be one of "high", "medium", "brain_dead", or None — enforced by the DB-level CHECK from migration v11.

insert_subtask(pool, parent_id, id, text, bucket)crates/storage/src/database.rs:415

Same as insert_task but with parent_task_id set. Inherits the bucket from the parent.

list_subtasks(pool, parent_id) -> Result<Vec<TaskRow>>crates/storage/src/database.rs:431

SELECT ... FROM tasks WHERE parent_task_id = ? ORDER BY created_at ASC.

complete_subtask_and_check_parent(pool, subtask_id) -> Result<()>crates/storage/src/database.rs:447

Business logic, in one function so it lives in one transaction:

  1. Mark the subtask done.
  2. If every sibling subtask is now done, mark the parent done with auto_completed = 1.

The auto_completed flag (v13) lets list_recent_completions exclude cascade-completed parents from the daily-count sparkline so the user sees one count per real action, not one count per parent action plus one per subtask completion.

complete_task(pool, id)crates/storage/src/database.rs:498

UPDATE tasks SET done = 1, done_at = datetime('now') WHERE id = ?.

uncomplete_task(pool, id)crates/storage/src/database.rs:507

UPDATE tasks SET done = 0, done_at = NULL, auto_completed = 0 WHERE id = ?.

This is the function flagged in the 2026-04-22 review as RB-15 ("doesn't reopen auto-completed parents"). The current implementation only un-completes the row passed in. If a user un-completes a child of an auto-completed parent, the parent stays done. The fix is to walk up the parent_task_id chain and reset auto-completed parents whose subtasks are no longer all-done. Tracked as a release-blocker (per code-review-2026-04-22.md); resolution status to verify against current HANDOVER.md.

delete_task(pool, id)crates/storage/src/database.rs:550

DELETE FROM tasks WHERE id = ?. The parent_task_id REFERENCES tasks(id) ON DELETE CASCADE from migration v3 removes child subtasks automatically.

list_recent_completions(pool, days) -> Result<Vec<DailyCompletionCount>>crates/storage/src/database.rs:578

The Phase 8 momentum sparkline. Steps:

  1. Clamp days to [1, 365].
  2. Group tasks rows by DATE(done_at, 'localtime') for done = 1 AND auto_completed = 0.
  3. Compose a fixed-length output array by left-joining the grouped result against an N-day "spine" generated by re-querying SQLite for each offset (so SQLite owns the calendar arithmetic, including DST and month boundaries).

Today the frontend always asks for days = 7. The clamp prevents a wild value producing an empty or huge series.

Watch-outs

  • uncomplete_task does not reopen auto-completed parents. RB-15 in the 2026-04-22 review. Status: see slice debt section in README.md.
  • list_tasks returns every task ever created. Soft-delete is not modelled; deleted tasks are gone. Completed tasks pile up over time. Frontend is responsible for filtering.
  • Energy column accepts NULL but the CHECK only fires on non-NULL values. Migration v11's check is CHECK (energy IN ('high', 'medium', 'brain_dead') OR energy IS NULL). Worth a regression test that asserts an invalid string fails.
  • auto_completed flag is set in code, not via trigger. A future migration that adds another path to "complete this task" (eg an LLM-driven auto-complete) needs to remember to set the flag. Worth a row-level audit.

See also