Files
Lumotia/docs/architecture-map/02-tauri-runtime/commands/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

120 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: Tasks and decomposition
type: architecture-map-page
slice: 02-tauri-runtime
last_verified: 2026/05/09
---
# `commands::tasks`
> **Where you are:** [Architecture map](../../README.md) → [Tauri runtime](../README.md) → [Commands](README.md) → Tasks
**Plain English summary.** Eleven commands wrapping the `magnotia_storage` task CRUD plus two LLM-driven actions. Standard CRUD: create / list / update / complete / uncomplete / delete a task, plus subtask CRUD (insert / list / complete) and a daily-completion-counts query for the Phase 8 Tasks-page momentum sparkline. The two LLM actions: `decompose_and_store` (break a parent task into subtasks via the local LLM with HITL feedback as few-shot exemplars) and `extract_tasks_from_transcript_cmd` (extract task lines from a recently-finished transcript, again with feedback exemplars).
## At a glance
- Path: `src-tauri/src/commands/tasks.rs`.
- LOC: 402.
- Tauri commands exposed (11 total):
- `create_task_cmd(state, request: CreateTaskRequest) -> Result<TaskDto, String>`.
- `list_tasks_cmd(state) -> Result<Vec<TaskDto>, String>`.
- `update_task_cmd(state, id, patch: UpdateTaskRequest) -> Result<TaskDto, String>`. Patch shape (text / bucket / list_id / effort / notes).
- `complete_task_cmd(state, id) -> Result<(), String>`. Stamps server-side `done_at`.
- `delete_task_cmd(state, id) -> Result<(), String>`.
- `uncomplete_task_cmd(state, id) -> Result<(), String>`. Clears `done_at`.
- `set_task_energy_cmd(state, id, energy: Option<String>) -> Result<TaskDto, String>`. Always writes; passes `None` to clear.
- `decompose_and_store(state, parent_task_id, profile_id) -> Result<Vec<TaskDto>, String>`.
- `extract_tasks_from_transcript_cmd(state, transcript, profile_id) -> Result<Vec<String>, String>`.
- `list_subtasks_cmd(state, parent_task_id) -> Result<Vec<TaskDto>, String>`.
- `complete_subtask_cmd(state, subtask_id) -> Result<(), String>`.
- `list_recent_completions_cmd(state, days) -> Result<Vec<DailyCompletionCount>, String>`.
- Events emitted: none.
- Depends on: `magnotia_storage::{insert_task, list_tasks, update_task, complete_task, uncomplete_task, delete_task, set_task_energy, get_task_by_id, insert_subtask, list_subtasks, complete_subtask_and_check_parent, list_recent_completions, list_feedback_examples, FeedbackTargetType, TaskRow, DailyCompletionCount, FeedbackRow}`, `magnotia_llm::prompts::FeedbackExample`, `uuid::Uuid`.
- Called from frontend at: Tasks page (CRUD, subtasks, sparkline), dictation result panel ("Extract tasks" button), parent-task expand UI ("Decompose").
## What's in here
### `TaskDto` (`src-tauri/src/commands/tasks.rs:24`)
camelCase frontend shape with id, text, bucket, listId, effort, notes, done, doneAt, createdAt, sourceTranscriptId, parentTaskId, energy.
### Energy validation (`:60`)
`ENERGY_LEVELS = ["high", "medium", "brain_dead"]` — kept as a const so frontend and storage validate against the same set. SQLite migration v11 enforces the same set with a CHECK constraint.
### CRUD wrappers
- `create_task_cmd` (`:92`) inserts and re-fetches so the frontend gets the canonical row (server-side timestamps, defaulted columns).
- `update_task_cmd` (`:140`) — patch shape, COALESCE-style updates in storage. `done` / `doneAt` are deliberately absent — those flow through `complete_task_cmd` / `uncomplete_task_cmd`.
- `set_task_energy_cmd` (`:200`) — separate from update because update uses `COALESCE` semantics where `None` means "preserve". This command always writes, so passing `None` actually clears the column.
### `to_llm_examples` (`:222`)
Converts feedback rows from storage into the few-shot exemplar shape the LLM crate consumes. Reconstructs `input` from `context_json` (parent task text or transcript chunk). Rows with malformed `context_json` are logged and dropped instead of silently filtered, so data-integrity regressions surface.
### `example_char_cost` (`:263`) and `trim_to_budget` (`:276`)
Char budget for the few-shot exemplars: keeps the prompt under token budget regardless of how many feedback rows the user has accumulated. Cap at 5 examples AND a char budget.
### `decompose_and_store` (`:290`)
1. Fetch the parent task or 404.
2. Pull up to 5 micro-step feedback exemplars filtered by profile, char-trimmed.
3. `spawn_blocking` runs `engine.decompose_task_with_feedback(parent_text, &examples)`.
4. Insert each generated step as a subtask (`uuid::v4()` for the id), re-fetch, accumulate.
5. Return the list.
NO `PowerAssertion::begin` here — the App Nap pattern is consistent in `commands::llm` but not yet uniformly applied here. Flag for follow-up.
### `extract_tasks_from_transcript_cmd` (`:345`)
Same shape as `decompose_and_store` but with `FeedbackTargetType::TaskExtraction` and the engine's `extract_tasks_with_feedback`. Returns the raw task strings — the frontend decides whether to insert them.
### `list_subtasks_cmd` / `complete_subtask_cmd` (`:370`, `:381`)
`complete_subtask_cmd` calls `complete_subtask_and_check_parent` which is the storage helper that stamps the subtask done and, if all siblings are done, also stamps the parent (Phase 8 micro-completion bookkeeping).
### `list_recent_completions_cmd` (`:394`)
Fixed-length oldest-first daily counts. Empty days are explicit zeros. The Tasks-page sparkline reads this directly.
## Data flow
```
Tasks page mount -> list_tasks_cmd -> [TaskDto, ...]
Tasks page create -> create_task_cmd(req) -> TaskDto
Tasks page edit -> update_task_cmd(id, patch) -> TaskDto
Tasks page check -> complete_task_cmd(id) -> ()
Tasks page reopen -> uncomplete_task_cmd(id) -> ()
Tasks page energy chip -> set_task_energy_cmd(id, energy) -> TaskDto
Tasks page sparkline -> list_recent_completions_cmd(days) -> [{date, count}, ...]
Tasks page Decompose button -> decompose_and_store(parent_id, profile_id)
-> get parent task
-> list 5 micro-step feedback rows (profile-scoped)
-> trim_to_budget
-> spawn_blocking(engine.decompose_task_with_feedback)
-> insert subtasks
-> [TaskDto, ...]
Dictation panel Extract tasks -> extract_tasks_from_transcript_cmd(text, profile_id)
-> list 5 task-extraction feedback rows
-> spawn_blocking(engine.extract_tasks_with_feedback)
-> [String, ...]
```
## Watch-outs
- **No `ensure_main_window` guard.** Tasks UI lives in the main window AND in the always-on-top task float (which has the secondary-windows capability). The float can call list / complete / set-energy / list-recent-completions etc. — that's intentional — but it can also fire `decompose_and_store` and `extract_tasks_from_transcript_cmd`, which spend LLM tokens. If you want to lock this down, add the guard to the LLM-spending commands.
- **No `PowerAssertion`.** `decompose_and_store` and `extract_tasks_from_transcript_cmd` both run synchronous LLM inference for several seconds. macOS can App Nap them. Add `PowerAssertion::begin("magnotia LLM task decomposition")` and similar.
- **The patch shape passes `Option<String>` for every column.** Currently the storage layer's `update_task` uses COALESCE: `Some` overwrites, `None` preserves. This means there's no way to clear `notes` or `effort` to empty via `update_task_cmd` — you can only set them to a non-empty string. If a user wants to clear a field, they'd need a fresh task or a dedicated clear command.
- **Decompose stores subtasks one-by-one in a loop** (`:329`). Each iteration is a separate DB transaction. Acceptable for typical 37-step decompositions; if a future LLM produces 50 steps, batch the inserts.
- **`extract_tasks_from_transcript_cmd` returns just `Vec<String>` and does NOT insert.** Frontend chooses what to insert. Confusing because the sibling `decompose_and_store` *does* insert. Convention is dictated by UX (decomposition is one-click, extraction reviews-then-inserts).
## See also
- [LLM](llm.md) — the engine that runs the decomposition / extraction.
- [Feedback](feedback.md) — the table that feeds the few-shot exemplars.
- [Profiles](profiles.md) — `profile_id` is the scoping key for feedback rows.
- [Window management](windows.md) — the task-float window (`tasks-float`) that consumes `list_tasks_cmd`.