10 KiB
Tasks: Kanban board note (roadmap item 13)
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.
First tool call rule: after at most three reads, 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.
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; the 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/kanban(packagenotescraft-kanban,[lints] workspace = true, deps:notescraft-markdown,thiserror), register it in the root[workspace.dependencies]and inxtask/src/layers.rsat L1. - Q1: save a board with the real Obsidian Kanban plugin (3 lanes, one Complete lane, archive one card,
a card with a date, tag, link and a continuation line) and commit it as
crates/kanban/tests/fixtures/real-board.md. Agents never invent this fixture.
Phase 1 - the pure crate notescraft-kanban (no I/O)
- 1. Parse a board (brief
briefs/11-kanban-parse.md). OWNS:crates/kanban/src/lib.rs,crates/kanban/src/error.rs,crates/kanban/src/model.rs,crates/kanban/src/parse.rs,crates/kanban/tests/parse.rs. FILES TO OPEN:spec.mdD1 to D3,crates/kanban/tests/fixtures/real-board.md,crates/markdown/src/frontmatter/mod.rs(frontmatter end offset). GOAL:Board,Lane,Card,Error,pub fn parse(src: &str) -> Result<Board>with D2 limits;kanban-pluginfrontmatter required (boardorbasic) elseError::NotABoard. TESTS: real-board fixture lanes/cards/complete/archive counts; extra paragraph between lanes lands inLane.extra; multi-line card; block id;## Title (5)max items; CRLF file; nokanban-pluginisNotABoard; 201 lanes refused. VERIFY: standing,-p notescraft-kanban. DO NOT: serialise (task 2). - 2. Serialise with byte fidelity.
OWNS:
crates/kanban/src/write.rs,crates/kanban/tests/roundtrip.rs,crates/kanban/src/lib.rs(onemod). FILES TO OPEN:spec.mdD3,crates/kanban/src/parse.rs,crates/kanban/src/model.rs. GOAL:pub fn to_markdown(board: &Board) -> String. NeedsBoardto keep what is needed for fidelity (add private raw fields toCard/Lanesuch as the original list marker and original text; editmodel.rs+parse.rsminimally for them). TESTS: real-board round trip byte-identical; generated boards (a seeded loop of 200 random boards built from a small grammar) satisfy parse(write(parse(x))) == parse(x); untouched card keeps its original marker (*) when another card changes; trailing text after the footer kept. VERIFY: standing. DO NOT: normalise anything that is not in the D3 list. - 3. Settings footer and card meta.
OWNS:
crates/kanban/src/settings.rs,crates/kanban/src/meta.rs,crates/kanban/tests/settings.rs,crates/kanban/tests/meta.rs,crates/kanban/src/lib.rs(twomod). FILES TO OPEN:spec.mdD4 and D5,crates/markdown/src/scanning/scan.rs(all_tags) and the link scanner next to it. GOAL:Settings::from_board(&Board),Settings::set_list_collapse(&mut Board, &[bool])(rewrites the footer only for known keys, unknown keys kept in order),card_meta(&Card, &Settings) -> Meta. TESTS: footer with unknown keys survives alist-collapsechange in order; missing footer gets one created only on a settings write;@{2026-10-21}and@@{13:00}; custom trigger!; tags and[[links]]found; nested JSON values opaque. VERIFY: standing. DO NOT: addserde_json. - 4. Board operations.
OWNS:
crates/kanban/src/ops.rs,crates/kanban/tests/ops.rs,crates/kanban/src/lib.rs(mod,pub use). FILES TO OPEN:spec.mdD6 table,crates/kanban/src/model.rs. GOAL: methods onBoard:add_card(lane, text, Position),edit_card,move_card(from, to)(complete-lane check flipschecked),toggle_card,archive_card(creates the archive on first use),delete_card,add_lane,rename_lane,move_lane,delete_lane,archive_lane_cards. All returnResult<()>withError::OutOfRangefor bad indexes; no clamping. TESTS: each op; index just past the end is an error; move within a lane (down and up) gives the right order; move into a Complete lane sets[x]; archive creates***+## Archive; delete last lane leaves a valid board; byte diff ofmove_cardby one touches exactly two list lines. VERIFY: standing +cargo xtask layers. DO NOT: do I/O.
Phase 2 - engine
- 5.
kanban.read/kanban.is_board/kanban.createand the write commands. OWNS:crates/engine/src/kanban/mod.rs,.../commands.rs,.../apply.rs, onemod kanban;incrates/engine/src/lib.rs, oneregister_kanban(reg)incrates/engine/src/commands.rs::register_core,crates/engine/src/ai/assistant_gate.rs,crates/engine/tests/core_commands.rs(CORE_IDS),crates/engine/tests/kanban.rs. FILES TO OPEN:spec.mdD6,crates/engine/src/bases/commands.rs+write.rs(group to copy),crates/engine/src/vault/properties.rs(undoable buffer edit),crates/engine/src/vault/notes.rs(write_note),crates/index/src/props.rs(note_propsforis_board). GOAL: D6 table;expect_hashguard (conflict); open buffer gets one undoable edit;is_boardfrom the index property,readre-parses the text; board JSON usescard_meta. TESTS:kanban.rson aTempVault: read the real fixture to golden JSON; move writes only two lines (byte compare); conflict on stale hash; open buffer one undo step; create writes a valid board thatreadaccepts; read-only session refuses writes; a non-board note givesinvalid_params. VERIFY: standing fornotescraft-engine+--test kanban --test core_commands. DO NOT: touch the UI; write outside the vault.
Phase 3 - UI (lead wires the dispatch lines)
- 6. Board view shell: lanes, cards, read-only rendering.
OWNS:
crates/ui-egui/src/views/kanban_view/mod.rs,.../state.rs,.../lane.rs,.../card.rs,crates/ui-egui/src/text/views/kanban/mod.rs, new[ui.kanban]table at the END ofcrates/i18n/locales/en.toml,crates/ui-egui/assets/icons/kanban.svg,crates/ui-egui/src/design/icons.rs(oneicon!("kanban")),crates/ui-egui/tests/kanban_view.rs. FILES TO OPEN:crates/ui-egui/src/views/base_view/state.rs,crates/ui-egui/src/views/base_view/table/grid.rs,.claude/skills/notescraft-egui-screen/,crates/ui-egui/src/views/note_view/blocks/inlines.rs(inline Markdown painting to reuse for card text). GOAL: D7 layout and visuals: lanes as floating cards, 280 px width, horizontal scroll, inline Markdown card text, tag/date chips, collapsed lanes, "Open as Markdown" header button. TESTS: kittest on the fixture vault: lanes and cards visible, a collapsed lane hides cards, a complete card shows the check, header button toggles. VERIFY: standing +cargo xtask i18n+cargo xtask tree; snapshot PNG at 1440x900 read and compared to the plugin screenshot description. DO NOT: hardcode strings; use raw egui buttons where a widget exists. - 7. Editing: add, edit, toggle, archive, delete cards; lane menu.
OWNS:
crates/ui-egui/src/views/kanban_view/add.rs,.../edit.rs,.../lane_menu.rs,crates/ui-egui/tests/kanban_edit.rs,crates/ui-egui/src/text/views/kanban/mod.rs(append keys). FILES TO OPEN:crates/ui-egui/src/widgets/context_menu.rs,crates/ui-egui/src/views/base_view/menus/(menu pattern),ConfirmModalusage incrates/ui-egui/src/views/trash_view/. GOAL: inlineTextareaadd/edit, Cmd+Enter save, Esc cancel, checkbox toggle, lane menu (rename, move, archive cards, delete with confirm, collapse writeslist-collapse). Each action = one engine call withexpect_hash; onconflictreload and show a toast. TESTS: add a card then undo in the editor restores; edit commits on Cmd+Enter; delete asks first; collapse persists to the footer. VERIFY: standing + i18n. DO NOT: mutate the board locally before the engine answers (render from the engine's answer). - 8. Drag and drop between and within lanes, keyboard moves.
OWNS:
crates/ui-egui/src/views/kanban_view/drag.rs,.../keys.rs,crates/ui-egui/tests/kanban_drag.rs. FILES TO OPEN:crates/ui-egui/src/panels/tree/(existing drag source/target code),crates/ui-egui/src/views/base_view/table/selection.rs. GOAL:CardRef { lane, index }payload viaegui::DragAndDrop, drop line indicator, drop on lane header appends, Alt+arrows move, arrow focus; a drop sends onekanban.card.move. TESTS: kittest drags a card to another lane (one engine call counted), within the same lane to index 0, Alt+Down moves one place, dropping on itself is a no-op (no call). VERIFY: standing +cargo xtask tree. DO NOT: send a move while still dragging. - 9. Note tab integration: open boards as boards, New board command, Explorer icon.
OWNS:
crates/ui-egui/src/shell/app/window/views.rs(theTabKind::Notearm only),crates/ui-egui/src/shell/app/window/commands.rs(palette command "Kanban: New board"),crates/ui-egui/src/panels/tree/rows.rs(board icon on rows, only if Q3 is yes),crates/ui-egui/tests/kanban_open.rs. FILES TO OPEN:crates/ui-egui/src/shell/app/window/views.rs:229(dispatch),crates/ui-egui/src/widgets/tabs/kind.rs,docs/CODE-MAP.md. GOAL: a note withkanban.is_boardtrue renderskanban_viewunless the tab has "open as markdown"; palette "Kanban: New board" runskanban.createand opens it; cacheis_boardper path and index generation. TESTS: opening a board note shows lanes; "Open as Markdown" shows the editor; a normal note is untouched; creating a board opens it. VERIFY: standing + i18n. DO NOT: parse the file in the UI. - 10. Docs and status.
OWNS:
ROADMAP.md,docs/ARCHITECTURE.md,docs/CODE-MAP.md(kanban rows),specs/plugins-parity/roadmap.md(mark 13). GOAL: crate row, command list, status numbers. VERIFY:cargo xtask ci. DO NOT: edit code.