Files
Marcos Paulo de3ec375ae
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
docs: specs and briefs for Tasks, Templater, Kanban and icons/banners
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 06:06:54 -03:00

15 KiB

Tasks: Tasks index, query blocks and Tasks view (roadmap item 11)

One task = one agent's work (OpenCode brief template, specs/agents/opencode-runbook.md). [ ] open. Spec: spec.md (D-numbers point into it). Rules for every task: AGENTS.md, docs/CODE-MAP.md. Phases run in order; tasks inside a phase marked "parallel" touch disjoint files.

First tool call rule: after at most three reads (the FILES TO OPEN you need), your FIRST write is the first OWNS file: a compiling stub with the signatures from GOAL and ONE failing test. Then make it pass. Do not explore the repo; docs/CODE-MAP.md already says where things are.

Standing VERIFY: cargo test -p <pkg> ; cargo clippy -p <pkg> --all-targets -- -D warnings ; rustfmt --edition 2024 <owned files>. UI/i18n tasks add cargo xtask i18n; last task of a phase adds cargo xtask layers and cargo xtask tree. Always export PATH="$HOME/.cargo/bin:$PATH". Standing DO NOT: edit Cargo.toml/Cargo.lock, files outside OWNS, git state, cargo fmt --all; add allow attributes; use unwrap/expect/panic/[i]/[a..b] on input-derived data; add a dependency.

Owner/lead step 0 (not an agent task)

Create the empty crate crates/tasks (package notescraft-tasks, [lints] workspace = true, deps: notescraft-markdown, notescraft-templates, notescraft-regex, thiserror), register it in the root [workspace.dependencies] and in xtask/src/layers.rs at L3. Answer Q1 of the spec.

Phase 1 - the pure crate notescraft-tasks (no I/O)

  • 1. Task line parse and format (brief briefs/09-task-line-parser.md). OWNS: crates/tasks/src/lib.rs, crates/tasks/src/error.rs, crates/tasks/src/task.rs, crates/tasks/src/parse.rs, crates/tasks/tests/parse.rs, crates/tasks/tests/fixtures/lines.txt. FILES TO OPEN: spec.md D2, crates/engine/src/calendar/tasks.rs (the existing parse_line, do not edit), crates/templates/src/date.rs (Date), crates/markdown/src/scanning/scan.rs (all_tags). GOAL: pub fn parse_line(line: &str) -> Option<Task> (path/line/heading left empty, filled by scan_note), pub fn format_line(task: &Task) -> String, types of D2 with Default. TESTS: golden lines.txt (one task per line, ||| then the expected debug-free summary) built from the Tasks emoji page; round trip for fully signified lines; unknown emoji stays in description; 3 000-char line is None; - [ ] without text is a task with empty description; * [x], 1. [ ], + [-] markers; bad date 📅 2026-13-40 keeps the text and sets no date. VERIFY: standing, -p notescraft-tasks. DO NOT: scan notes (task 5); write a second tag regex.
  • 2. Status types, next_status, toggle without recurrence. OWNS: crates/tasks/src/status.rs, crates/tasks/src/toggle.rs, crates/tasks/tests/toggle.rs, crates/tasks/src/lib.rs (two mod lines). FILES TO OPEN: spec.md D3, crates/tasks/src/task.rs. GOAL: status_type(char) -> StatusType, next_status(char) -> char, pub struct ToggleCfg { set_done_date: bool, remove_scheduled_on_recurrence: bool, next_below: bool } with Default, pub struct Toggle { lines: Vec<String>, next_index: Option<usize> }, pub fn toggle(line: &str, today: Date, cfg: &ToggleCfg) -> Option<Toggle> (non-recurring paths only; a recurring line returns the plain toggle until task 4). TESTS: TODO to DONE adds ✅ 2026-10-22; DONE to TODO removes it; / to DONE; - to TODO; set_done_date=false; 🏁 delete gives empty lines; text outside signifiers byte-preserved (compare with the original minus the changed span); indent and marker kept. VERIFY: standing. DO NOT: handle recurrence; touch the calendar crate.
  • 3. Recurrence rule parse and next date. OWNS: crates/tasks/src/recur.rs, crates/tasks/tests/recur.rs, crates/tasks/src/lib.rs (one mod + pub use). FILES TO OPEN: spec.md D4, crates/templates/src/date.rs (Date, Weekday, add-days API), crates/calendar/src/recur.rs (style and limits only; do NOT depend on it). GOAL: Rule::parse(&str) -> Result<Rule>, Rule::next(&self, reference: Date) -> Option<Date>, Rule::when_done(&self) -> bool, Rule::to_text(&self) -> String (canonical). Grammar table of D4. Limits: N <= 1 000, text 128 chars. TESTS: every example row of the Tasks Recurring page (copy them as table-driven cases with a start date and the expected next date); every month on the 31st skips Nov; every month from 2026-01-31 gives 2026-02-28 then 2026-03-28; every 3 months from Jan 31 gives Apr 30; every weekday from Friday gives Monday; every month on the last Friday; every April and December on the 1st and 24th; invalid text (every, daily, FREQ=WEEKLY) is Err. VERIFY: standing. DO NOT: add end conditions; loop without a bound (cap the month walk at 48 steps).
  • 4. Toggle with recurrence. OWNS: crates/tasks/src/toggle.rs (edit), crates/tasks/tests/toggle_recur.rs. FILES TO OPEN: spec.md D3 and D4, crates/tasks/src/recur.rs, crates/tasks/src/toggle.rs. GOAL: complete a recurring task: two lines (next first, or below with next_below), next task has shifted due/scheduled/start keeping distances, no ✅, status TODO, 🆔/⛔ dropped, scheduled removed when the cfg says so; no date on the task or an invalid next date means plain toggle, no new line; when done uses today as reference. TESTS: the three examples of the Tasks page (late completion default vs when done; scheduled+due distance kept); task with only 🛫; task with no date and a rule; 🏁 delete plus rule deletes the original and keeps the next. VERIFY: standing. DO NOT: change the signature of toggle.
  • 5. Scan a note into tasks. OWNS: crates/tasks/src/scan.rs, crates/tasks/tests/scan.rs, crates/tasks/src/lib.rs (mod, pub use). FILES TO OPEN: spec.md D5, crates/markdown/src/frontmatter/mod.rs (frontmatter end offset), crates/markdown/src/scanning/scan.rs (fence handling). GOAL: pub struct ScanCfg { global_filter: Option<String> }, pub fn scan_note(path: &str, text: &str, cfg: &ScanCfg) -> Vec<Task> filling path, line, heading, parent_line, original. TESTS: tasks in a fence are ignored; frontmatter lines ignored; heading text tracked across levels; nested sub-task parent_line; global filter on and off; 1 500 tasks in one note cap at 1 000; CRLF text keeps original without \r. VERIFY: standing. DO NOT: read files.
  • 6. Query parse (parallel with 7 only after 5 compiles; they share Query, so run 6 then 7). OWNS: crates/tasks/src/query/mod.rs, crates/tasks/src/query/parse.rs, crates/tasks/src/query/dates.rs, crates/tasks/tests/query_parse.rs. FILES TO OPEN: spec.md D7, crates/tasks/src/task.rs. GOAL: Query::parse(&str) -> Result<Query, QueryError>; filter AST Filter { Not(Box), And(Vec), Or(Vec), Xor(Box,Box), Leaf(Leaf) }; date word parser parse_date_word(&str, today, week_start) -> Option<DateRange> (absolute, today, next monday, this|next|last week|month|quarter|year, 2026-10, 2026-Q4). TESTS: every filter line of D7 parses; (due today) AND (priority is high); unknown line gives QueryError { line: 3 }; 101 lines refused; filter by function gives the clear "not supported" error; {{query.file.path}} placeholder recorded. VERIFY: standing. DO NOT: evaluate anything.
  • 7. Query run: filter, sort, group, limit. OWNS: crates/tasks/src/query/run.rs, crates/tasks/src/query/sort.rs, crates/tasks/src/query/group.rs, crates/tasks/tests/query_run.rs. FILES TO OPEN: spec.md D7, crates/tasks/src/query/mod.rs, crates/regex/src/lib.rs (public API of notescraft-regex). GOAL: run(&Query, &[Task], today, week_start, &QueryCtx) -> Ran; QueryCtx { path: &str } substitutes placeholders; urgency sort uses the Tasks formula (documented in the file header). TESTS: 30-task fixture with a golden result per feature (each filter family, boolean forms, sort by two keys + reverse, group by heading/due/tags with reverse, limit 5, limit groups to 2); regex filter; invalid regex is a QueryError at parse time, not at run; 40 000 tasks, one filter + sort under 30 ms (#[ignore] perf test). VERIFY: standing + cargo xtask layers. DO NOT: clone Tasks (groups hold indexes).

Phase 2 - engine

  • 8. Task index and tasks.list / tasks.query / tasks.reindex. OWNS: crates/engine/src/tasks/mod.rs, crates/engine/src/tasks/store.rs, crates/engine/src/tasks/commands.rs, crates/engine/src/prefs/tasks_config.rs, one mod tasks; in crates/engine/src/lib.rs, one register_tasks(reg) call in crates/engine/src/commands.rs::register_core, crates/engine/src/ai/assistant_gate.rs (READ list), crates/engine/tests/core_commands.rs (CORE_IDS), crates/engine/tests/tasks_query.rs. FILES TO OPEN: crates/engine/src/bases/mod.rs + commands.rs (the group to copy), crates/engine/src/bases/source.rs (index generation cache), crates/engine/src/prefs/files_config.rs + crates/engine/tests/files_config_viewers.rs (settings pattern), crates/engine/src/calendar/tasks.rs (do not edit). GOAL: D5 store (lazy worker-thread build, mtime-incremental refresh, .notescraft/cache/tasks.json versioned and bounded), D8 read commands, [tasks] settings, Session accessor named in the report if one is unavoidable. TESTS: tasks_query.rs drives the registry on a TempVault of 50 notes: golden JSON for a query; edit a note through write_note and the next query sees it; fence tasks ignored; global_filter setting; invalid query returns {error:{line}} not a command error; cache file reloaded after restart (new Session); read-only session still reads. VERIFY: standing for notescraft-engine + cargo test -p notescraft-engine --test tasks_query --test core_commands. DO NOT: scan on the UI thread; add a file watcher.
  • 9. tasks.toggle and tasks.set with the stale-line guard. OWNS: crates/engine/src/tasks/write.rs, crates/engine/src/tasks/commands.rs (append), crates/engine/src/ai/assistant_gate.rs (WRITE list), crates/engine/tests/core_commands.rs, crates/engine/tests/tasks_write.rs. FILES TO OPEN: spec.md D6, crates/engine/src/vault/properties.rs (how an open buffer gets an undoable edit), crates/engine/src/vault/buffers.rs, crates/engine/src/vault/notes.rs (write_note). GOAL: D6 exactly: expect check, +/-20 line search, conflict, buffer edit when open, write_note when closed, re-scan of that note and generation bump. TESTS: toggle plain task (byte compare: only that line changes); recurring task adds the next line; open buffer gets one undo step; stale line found 3 lines lower; vanished line gives conflict; read-only session refused; tasks.set due null removes 📅; note with CRLF keeps CRLF. VERIFY: standing + --test tasks_write. DO NOT: write outside the vault; touch .obsidian/.
  • 10. Editor hook and calendar agenda on the index (small, parallel with 11). OWNS: crates/editor/src/keys.rs (add on_toggle_task_at, leave on_toggle_task), crates/editor/tests/toggle_task_at.rs, crates/engine/src/calendar/tasks.rs (switch scan to the shared parser, keep the output Items identical), crates/engine/tests/calendar_tasks.rs (existing test must stay green). FILES TO OPEN: crates/editor/src/keys.rs:123, crates/engine/src/calendar/tasks.rs, crates/engine/src/calendar/mod.rs. GOAL: on_toggle_task_at(text, pos, today: Date) -> Option<Edit>: a line with a recognised signifier set goes through notescraft_tasks::toggle (returns a multi-line Edit), all other lines behave exactly like on_toggle_task; agenda also shows ⏳ and 🛫 only when the lead answers Q2 yes (leave a const SHOW_SCHEDULED: bool). TESTS: recurring line produces 2 lines and a caret on the original; plain line equals old behaviour for 20 existing cases; agenda output unchanged for the existing fixtures. VERIFY: standing for notescraft-editor and notescraft-engine. DO NOT: change on_toggle_task behaviour; add a dependency cycle (editor L5 -> tasks L3 is fine, check cargo xtask layers).

Phase 3 - UI (lead wires the shell hook lines)

  • 11. TabKind::Tasks, the Tasks view list and query bar. OWNS: crates/ui-egui/src/views/tasks_view/mod.rs, .../state.rs, .../list.rs, .../query_bar.rs, .../presets.rs, crates/ui-egui/src/widgets/tabs/kind.rs (const TASKS_PATH, enum arm, icon, label), crates/ui-egui/src/text/views/tasks_view/mod.rs, new [ui.tasks] table appended at the END of crates/i18n/locales/en.toml, crates/ui-egui/tests/tasks_view.rs. FILES TO OPEN: crates/ui-egui/src/views/base_view/state.rs (queue engine calls, pump), crates/ui-egui/src/views/base_view/table/grid.rs (virtualised rows), .claude/skills/notescraft-egui-screen/, docs/CODE-MAP.md UI table. GOAL: D9 tab with presets, query bar (multi-line, error under the field with line number), grouped virtualised rows, checkbox calls tasks.toggle, path link opens the note. Dispatch arm in shell/app/window/views.rs::show and the palette entry are lead work, listed in the report. TESTS: kittest on a fixture vault: preset Today lists the due task, ticking a recurring task shows the next one, a bad query shows the error line; only visible rows built (counter) with 2 000 tasks. VERIFY: standing + cargo xtask i18n + cargo xtask tree; render the snapshot PNG and read it. DO NOT: hardcode strings; use raw egui buttons where a widget exists.
  • 12. ```tasks query block in the note view. OWNS: crates/ui-egui/src/views/note_view/blocks/tasks_block/mod.rs, .../rows.rs (reuse tasks_view::list row painter via pub(crate)), crates/ui-egui/tests/tasks_block.rs. FILES TO OPEN: crates/ui-egui/src/views/note_view/blocks/query/mod.rs (copy: fence detection, refresh), crates/ui-egui/src/views/note_view/blocks.rs. GOAL: fence info tasks renders results with from_path = the note; checkbox works; refresh on generation change; errors inline; a "Edit query" toggle shows the source. TESTS: block in a note lists tasks, ticking updates the source note on disk, error line shown. VERIFY: standing + cargo xtask i18n. DO NOT: query on every frame (only on generation change or edit).
  • 13. Rich note view checkbox click uses tasks.toggle; docs and status. OWNS: crates/ui-egui/src/views/note_view/blocks/rich/text_line.rs (checkbox click handler only), crates/ui-egui/tests/note_task_click.rs, ROADMAP.md, docs/ARCHITECTURE.md, docs/CODE-MAP.md (add tasks row), specs/plugins-parity/roadmap.md (mark 11). FILES TO OPEN: crates/ui-egui/src/views/note_view/blocks/rich/text_line.rs, crates/ui-egui/src/shell/app/window/commands.rs (engine_command). GOAL: clicking a checkbox on a task line with signifiers calls tasks.toggle; lines without signifiers keep the old path. Update the three docs and SPEC 6.1 mismatch note. TESTS: click on a recurring task inserts the next line; plain click unchanged. VERIFY: cargo xtask ci (whole set). DO NOT: change the checkbox look.