Files
2026-10-21 21:01:23 -03:00

22 KiB
Raw Permalink Blame History

Tasks: assistant AI Elements

Spec: specs/assistant-ai-elements/spec.md. One task per Sonnet agent. Paths are under crates/ui-egui/src/panels/assistant_panel/ unless marked (P/ = that prefix). Every task: read AGENTS.md, docs/CODE-MAP.md, skills notescraft-agent-workflow, notescraft-rust-safety, notescraft-egui-screen, notescraft-verify-loop. Strings go to crates/ui-egui/src/text/assistant_panel.rs + crates/i18n/locales/en.toml (append at the END of the table); rustfmt --edition 2024 <your files> only (never cargo fmt --all); do not commit; do not touch files of other agents (check git status).

Common TESTS recipe (egui_kittest, copy crates/ui-egui/tests/assistant_flow.rs and P/tests.rs): build an AssistantPanel, feed Events through apply_event, draw at Vec2::new(320.0, 700.0) panel width, assert with queries on labels and with real clicks (Action pushed), assert no horizontal overflow (content rect right <= panel right). Common PNG render: add the scenario to P/chat/demo.rs and run cargo run -p notescraft-ui-egui --example snapshot -- --vault <v> --out ui.png --size 1440x900 plus a 320 wide variant (--size 320x700), --theme catppuccin-latte once, then READ the PNG and compare with the spec section. Common VERIFY: cargo test -p notescraft-ui-egui assistant, cargo clippy -p notescraft-ui-egui --all-targets -- -D warnings, cargo xtask i18n, cargo xtask tree, cargo xtask layers.

T0 note (aiA, 2026-10-21): trace widget landed, uncommitted. Module P/chat/trace/{line,view}.rs, state P/state/trace.rs. trace::TraceStatus {Running, Done, Failed} (from_acp); NO Pending yet. line::Header {salt, status, title, description: Option<&str>, badge: Option<&str>, nested}, shown with line::show(ui, tokens, &header, |ui| body). Open state lives in egui memory under the line id; there is no default_open argument yet (T7/T4 add it when needed). view::tool(ui, tokens, &ToolLine, actions) and view::subagent(ui, tokens, panel, &Subagent, actions); conversation::show_tool feeds them. New Message variant: Subagent(Box<trace::Subagent>) (id, title, status, started, nested calls). Strings: text/panels/assistant_trace.rs, [ui.assistant-trace]. T1-T3 status: see the checkboxes below.

Order and parallelism

T0 (gate: subagent trace task landed)
 -> T1 split conversation.rs                      [chat/conversation.rs, new chat/message/]
     -> T2 Message turns + footer                 [chat/message/*, state/model.rs (turn index)]
     -> T3 Chat scroll + empty state              [chat/view.rs, chat/scroll/*]
     -> model.rs chain, strictly in sequence: T4 Reasoning -> T5 Plan+Queue -> T6 Confirmation -> T7 Work summary
 parallel with the chain (disjoint files, after T1):
     T8 Context card            [chat/usage.rs]
     T9 Changed notes tree      [state/review.rs, new chat/changes/*, chat/view.rs strip call]
     T10 Citations + Sources    [chat/sources*.rs, new chat/cite/*]
     T11 Prompt Input restyle -> T12 Suggestions -> T13 Model Selector UI   [input/composer.rs chain, header.rs]
     T14 ACP config options (crates/acp, then wire into T13)
     T15 Engine checkpoint journal + commands  [crates/engine only] -> T16 Checkpoint UI [needs T9, T2]
 last: T17 Bases table in message [needs T7], T18 docs/status

Same-file conflicts: chat/view.rs = T3, T9 (one line), T12, T16: run in that order. state/model.rs = T2, T4, T5, T6, T7, T16: sequential. input/composer.rs = T11, T12, T13: sequential. chat/header.rs = T13 only. Tasks with disjoint OWNS may run in parallel: {T8}, {T9}, {T10}, {T11->T12->T13}, {T14}, {T15}, and the model chain.

[x] T0 Gate: confirm the subagent/trace work landed (no code)

GOAL: read the other agent's result (git log -5, git status, grep for the trace widget under P/chat/). Record in a 10-line note at the top of this file: widget module path, its API (status enum, description, open state), Message variants it added. Every later task uses that widget; if it lacks Pending status, a description line or default_open, T1 adds them (report to owner first, it is the other agent's file). OWNS: this file only. DO NOT: edit source.

[x] T1 Split chat/conversation.rs (437 lines) with no behaviour change

OWNS: P/chat/conversation.rs (keep as thin dispatcher), new P/chat/message/{mod,markdown,system,typing}.rs; P/chat/mod.rs (one mod message;). FILES TO OPEN: P/chat/conversation.rs, P/chat/view.rs (callers), P/mod.rs (the flat use chat::conversation aliases; add use chat::message; only if needed). GOAL: move markdown*, fenced_blocks (with its test), system, typing, to chat/message/. conversation.rs < 400 lines. Move strings it moves into the text table. Public paths unchanged. TESTS: existing tests unchanged and green (P/tests.rs, chat/view_tests.rs, tests/assistant_flow.rs). VERIFY: common + before/after snapshot PNG identical. DO NOT: change layout or behaviour; add features; touch model.rs.

Docs: https://ui.shadcn.com/docs/ai-elements/base/message OWNS: new P/chat/message/{turn,footer}.rs, P/chat/conversation.rs (call), P/state/actions.rs (Action::EditPrompt(index) if needed), P/state/model.rs (helper turn_of(index) only). FILES TO OPEN: P/chat/conversation.rs::{item, reply_actions}, P/state/actions.rs, P/state/model.rs, P/input/composer.rs (prefill). GOAL: footer (icon buttons, tooltips) under EVERY finished agent reply, not only the last: Copy (icon swaps to check for 2 s, crate::motion), Retry only on the last, Edit on user bubbles (puts text in the composer through prefill, focus composer). Reply separated from next user turn by consistent spacing. Hidden in history-viewing mode for Retry/Edit. TESTS: kittest 320 pt: two turns -> two footers; click Copy -> clipboard text equals reply and icon state changes; click Edit -> panel.composer() equals the prompt; while connection.running no footer on the streaming reply. DO NOT: add good/bad buttons; change message data layout beyond the helper.

[x] T3 Chat: scroll anchoring and empty state

Docs: https://ui.shadcn.com/docs/ai-elements/base/chat OWNS: P/chat/view.rs, new P/chat/scroll/{mod,anchor,empty}.rs. FILES TO OPEN: P/chat/view.rs::show (scroll area, jump pill), P/chat/demo.rs. GOAL: (1) a new user message settles near the top of the viewport and the reply streams beneath; while the user is at the live edge the view follows, if they scrolled up it does not (keep stick_to_bottom semantics, no jitter). (2) The "Jump to latest" text pill becomes an icon button (arrow-down) with a dot when new content arrived while away. (3) Empty state when a connection exists and there are no messages: heading, one line, slot for the suggestion row (T12 fills it). Move FOOTER_HEIGHT/jump consts and strings out of view.rs; keep file < 400. TESTS: kittest: push 40 messages, scroll up, stream a chunk -> offset unchanged and dot visible; click jump -> at bottom; new user message -> its top within the first quarter of the viewport. DO NOT: change the composer, header or review strip.

[x] T4 Reasoning on the trace widget

Docs: https://ui.shadcn.com/docs/ai-elements/base/reasoning OWNS: P/state/model.rs (Message::Thought gets started: Instant, secs: Option<u32>; chunk(); close on next non-thought event), P/chat/transcript.rs (line_for unchanged text), new P/chat/message/reasoning.rs, P/chat/conversation.rs (one arm). FILES TO OPEN: P/state/model.rs::{Message, chunk, apply_update}, trace widget (T0), P/state/model_tests.rs. GOAL: "Thinking" + spinner + shimmer while the last message is the Thought and the turn runs; open while streaming, auto-closes when it finishes, a manual toggle wins (store in egui memory by message index); finished = "Thought for 5s" (under 1 s: "Thought"). Empty text = plain line, not expandable. Respect motion::enabled. TESTS: unit: duration is set when an Agent chunk or ToolCall follows; kittest: label "Thinking" while running, "Thought for" after; user closed state survives a later chunk. DO NOT: change crates/acp or persisted format (transcript keeps role Thought).

[x] T5 Plan + Queue (one widget, two placements)

Docs: https://ui.shadcn.com/docs/ai-elements/base/plan , https://ui.shadcn.com/docs/ai-elements/base/queue OWNS: P/state/model.rs (apply_update Plan arm: replace the existing Message::Plan of the current turn in place instead of pushing), new P/chat/message/plan.rs, new P/chat/message/queue.rs (sticky strip), P/chat/view.rs (one call above composer; sequence after T3). FILES TO OPEN: P/state/model.rs, P/chat/conversation.rs Plan arm, crates/acp/src/events.rs::PlanEntry (statuses pending/in_progress/completed), P/chat/transcript.rs::line_for (Plan -> text line). GOAL: in-thread Plan: title "Plan · 2 of 5 done", rows via trace widget (done check, running dot + shimmer, pending circle), collapsed automatically when all done, one per turn. Queue strip above the composer while the turn runs and entries are not all complete: sections "To do" and "Completed" (collapsible, completed collapsed), hidden when no plan. Unknown status string = pending. TESTS: unit: two Plan events in one turn -> one Message::Plan; a new user turn starts a new one. kittest 320 pt: counts text, sections, strip hidden after PromptDone. Persisted-transcript view still shows the plan text. DO NOT: add a Queue data source; change ACP parsing.

[x] T6 Confirmation (permission requests)

Docs: https://ui.shadcn.com/docs/ai-elements/base/confirmation OWNS: P/state/model.rs (Message::Permission gets answer: Option<String> option name; expire_permissions only expires UNANSWERED ones), P/state/actions.rs (AnswerPermission records the answer), new P/chat/message/permission.rs, P/chat/conversation.rs arm, P/state/permission_tests.rs. FILES TO OPEN: those, P/chat/view.rs::write_requested, P/chat/header.rs (grant), crates/engine/src/ai/assistant_gate.rs (read only). GOAL: card = title (tool title via tool_title), what it touches (locations as note links), consequence line per twist (writes off / review on / review off), buttons Deny / Allow once / Always allow ordered by option kind (allow primary, reject outline). After answer the card collapses to one line "Allowed once" / "Always allowed" / "Denied" in success/muted/danger tone and stays in the thread; expired unanswered becomes "No answer" (not the current generic System line). Disabled in history mode. TESTS: kittest 320 pt: three options -> three buttons, click maps to the right option id; after click, line text; permission for kind edit with capabilities.read_only shows the grant consequence; PromptDone before answer -> "No answer". DO NOT: change the ACP answer call or the gate.

[x] T7 Per-turn work summary (Chain of Thought + Task)

Docs: https://ui.shadcn.com/docs/ai-elements/base/chain-of-thought , https://ui.shadcn.com/docs/ai-elements/base/task OWNS: P/chat/conversation.rs (replace tool_group call), new P/chat/turn/{mod,summary}.rs. FILES TO OPEN: P/chat/conversation.rs::{show, tool_group}, trace widget, T4/T5/T6 results. GOAL: consecutive non-text items of a turn (Thought, Tool, Plan stays outside) fold under one trace line. Running: present tense of the latest tool by kind ("Searching notes", "Reading Daily/2026-10-21"), spinner. Done: "Searched 3 notes, edited 1" (count by kind, max 3 clauses then "and N more"), error tone if any failed ("1 failed"). Auto-open while a call awaits review or permission; user toggle wins. Inside: rows from the Tool restyle (do not redraw them). TESTS: unit: summary string for mixes of kinds, singular/plural, failure; kittest: running vs done title, auto-open on pending permission. DO NOT: restyle the tool rows; invent kinds beyond ACP's seven.

[x] T8 Context card

Docs: https://ui.shadcn.com/docs/ai-elements/base/context OWNS: P/chat/usage.rs (+ its tests), P/input/composer.rs one call site only if the signature changes (coordinate: sequence before T11). GOAL: bar becomes a percent button ("74%") with a ring or bar; click or hover opens a small card: used / max, bar with warn/danger tokens (existing 70/90 rules), cost when > 0, at >= 90% hint "Start a new chat" with a New chat action. Keep Usage parsing. No input/output rows (ACP has none). TESTS: unit: ratio/label unchanged; kittest 320 pt: trigger text at 30/75/92, card opens on click, Action::NewChat at 92. DO NOT: change Usage::from_update.

T9 Changed notes tree (File Tree + review)

Docs: https://ui.shadcn.com/docs/ai-elements/base/file-tree OWNS: P/state/review.rs, new P/chat/changes/{mod,tree,row}.rs, P/chat/view.rs (replace the strip+scroll block with one call; sequence after T3), P/state/model.rs NOT touched (read panel.pending). FILES TO OPEN: P/state/review.rs, P/state/actions.rs (Accept/Reject/OpenDiff/AcceptAll/RejectAll), crates/engine/src/ai/assistant_queue.rs::PendingChange, P/chat/view.rs review block. GOAL: card "Changed notes · 4 waiting" with folder tree built from PendingChange.path (folders collapsible, default open under 8 files), row = kind badge (Create/Modify/Delete/Rename + +a −d from diff_view::counts), click opens diff, hover shows Accept / Reject icon buttons; folder row has Accept folder / Reject folder; footer Accept all / Reject all. One change = plain row without tree. Opaque changes (no path) listed under "Other". Show pending_errors per row. Bounded: 200 max (engine cap), render with show_rows. TESTS: unit: tree builder (nesting, sorting, single file, empty path); kittest 320 pt: click Accept on a row -> Action::Accept(id), folder accept -> all ids; error text shown. DO NOT: change the engine queue or accept semantics; applied-changes list is T16.

T10 Citations and Sources

Docs: https://ui.shadcn.com/docs/ai-elements/base/inline-citation , https://ui.shadcn.com/docs/ai-elements/base/sources OWNS: P/chat/sources_view.rs, P/chat/sources.rs (marker only), new P/chat/cite/{mod,card,preview,wikilink}.rs, P/chat/message/markdown.rs (hook for chips; sequence after T1). FILES TO OPEN: P/chat/sources_view.rs, P/chat/sources.rs::MARKER_SCHEME, crate::note_view link result (clicked_link), crates/ui-egui/src/views/share_dialog/mod.rs (worker pattern), engine note.read. GOAL: Sources line "Used 3 notes" on the trace widget; rows = note name, heading, tokens; click -> OpenNote with open_range. [n] and [[wikilink]] in agent text render as chips with a hover card (title, path, heading, first 3 lines). Preview text loaded off the UI thread via the worker pattern, cached per path with a size cap (e.g. 64 entries, 2 KiB each), never unwrap. Unresolved link = muted chip, card says not found, no navigation. TESTS: kittest 320 pt: agent text "see Meeting notes [1]" -> two chips, hover shows card from a fixture vault note, click -> Action::OpenNote; unresolved chip not clickable. DO NOT: change the knowledge crate or how passages are sent; do not read vault files in the paint path.

[x] T11 Prompt Input restyle

Docs: https://ui.shadcn.com/docs/ai-elements/base/prompt-input OWNS: P/input/composer.rs, new P/input/bar.rs (bottom bar split out), P/input/composer_tests.rs. FILES TO OPEN: P/input/composer.rs, P/input/context.rs (Chip), P/input/composer_keyboard.rs. GOAL: chips as pills (kind icon, truncated name, x), consistent height; bottom bar: attach (+), current-note toggle, usage trigger slot, spacer, circular icon send button (arrow) that becomes stop while running and a spinner state while starting. Keep Enter / Shift+Enter, mentions, drag-drop. composer.rs < 400 lines. TESTS: existing composer tests green; kittest: running -> stop button sends Action::Cancel; chip x removes. DO NOT: add image attachments (needs PromptBlock::Image; separate spec); touch the model selector (T13).

[x] T12 Suggestions

Docs: https://ui.shadcn.com/docs/ai-elements/base/suggestion OWNS: P/input/composer.rs (remove QUICK_ACTIONS; sequence after T11), new P/input/suggestions.rs, P/chat/scroll/empty.rs (slot from T3), P/chat/view.rs (sequence after T9). GOAL: horizontally scrolling row of outline pills; click SENDS (Action::Send with the text) instead of prefilling. Empty state set: "Summarize this note" (only with an active note), "Find notes that link here", "What did I write this week?"; plugin chips (Template, Calendar, Diagram) only when settings.plugin_tools enables them. After a finished turn, show agent available_commands_update names if T14 parsed them (optional, leave a clear TODO-free seam: a Vec<String> argument). TESTS: kittest 320 pt: row scrolls, click sends exact text, hidden while running and in history mode. DO NOT: invent LLM-generated follow-ups.

[x] T13 Model Selector UI

Docs: https://ui.shadcn.com/docs/ai-elements/base/model-selector OWNS: P/chat/header.rs (connection button + menu leave), new P/input/model_selector.rs, P/input/bar.rs (slot), views/settings_view/assistant_connections* NOT touched. FILES TO OPEN: P/chat/header.rs::{connection_button, connection_menu}, P/backend/prompt.rs::selected, crates/engine/src/ai/connections.rs. GOAL: popup anchored to the composer bar: groups "Agents" (ACP) and "Endpoints" (OpenAI, model name shown), radio selection, "Manage connections" -> Action::OpenSettings. Header keeps only the status dot, new chat, history, overflow. Effort/model menus: render nothing until T14 provides options. TESTS: kittest 320 pt: popup lists all saved connections grouped, selecting emits the existing select action, popup stays inside the panel rect. DO NOT: change connection storage or settings UI.

T14 ACP config options (protocol)

OWNS: crates/acp/src/events.rs (new SessionUpdate::ConfigOptions, lenient parse), crates/acp/src/command.rs + client/wire.rs (session/set_config_option), crates/acp/tests/, then a small wiring in P/state/model.rs and P/input/model_selector.rs (sequence after T6/T7 and T13). GOAL: parse config_option_update (id, name, category model/thought_level, options, current value) and the options returned by session/new; send session/set_config_option; selector shows model and effort menus only when the agent lists them. Unknown shapes stay Other. Verify the exact field names against the ACP schema (Context7 / agentclientprotocol.com) before coding. TESTS: crates/acp/tests/protocol_flow.rs style with examples/stub_agent.rs extended; kittest for menus. DO NOT: enable fs or terminal capabilities.

[x] T15 Engine checkpoint journal and commands

OWNS: new crates/engine/src/ai/checkpoint/{mod,journal,restore,commands}.rs (<= 400 lines each), crates/engine/src/ai/mod.rs, crates/engine/src/commands.rs (ONE register_* call), crates/engine/tests/core_commands.rs (CORE_IDS), new crates/engine/tests/assistant_checkpoint.rs. FILES TO OPEN: ai/assistant_queue.rs (accept path), ai/assistant_chat_io.rs (write_private use), ai/assistant_gate.rs (READ/WRITE lists), tests/common/mod.rs, history_* and trash_* commands. GOAL: spec section 4.3. Journal appended in the accept path and direct-write path for source: "assistant"; commands assistant.checkpoint_list, assistant.checkpoint_restore (reverse replay through the normal write path, hash conflict check, report restored and conflicts, never overwrite a changed note). Not callable by the assistant. TESTS: engine integration: create/modify/delete/rename turn -> restore -> vault equals before; edited-since note reported as conflict and untouched; over-cap entry marked non-restorable; assistant source denied; restore creates version-history snapshots. DO NOT: store old text inside notes or outside .notescraft/; skip craft-atomic; add a dependency.

T16 Checkpoint UI

Docs: https://ui.shadcn.com/docs/ai-elements/base/checkpoint OWNS: new P/chat/changes/checkpoint.rs, P/chat/conversation.rs (marker call), P/state/actions.rs (Action::RestoreCheckpoint), P/state/model.rs (turn ids; sequential chain), P/chat/view.rs (sequence last), engine call via engine_command. GOAL: after each turn that changed notes: separator marker with the file count and a Restore button; click -> crate::dialog listing files by kind; confirm runs assistant.checkpoint_restore on a worker; result System line with restored and conflicts (conflict row opens version history for the note). Applied-changes list for the turn appears in the Changed notes tree (T9) as "Applied". Not shown for read-only turns or in history mode. TESTS: kittest with a fixture vault: turn with an accepted modify -> marker visible -> restore -> note text equals old; conflict case shows the error. DO NOT: truncate the conversation; run IO in the paint path.

T17 Bases result table in a message

OWNS: new P/chat/message/base_table.rs, P/chat/message/mod.rs, row hook in the trace tool row (one call). FILES TO OPEN: crates/engine/src/bases/ (query result shape), views/base_view/table/ (read-only reuse if cheap), tool title notescraft_base_*. GOAL: a completed Bases query tool row opens to a bounded table (50 rows x 8 columns, truncated cells, "N more" line) and an "Open in Base" link. Parse defensively; malformed = plain text summary. TESTS: unit parse of a fixture result; kittest 320 pt table scrolls horizontally inside its own area, page does not. DO NOT: run queries from the UI; add dependencies.

T18 Docs and status

OWNS: ROADMAP.md, docs/ARCHITECTURE.md (assistant panel section), docs/CODE-MAP.md (new folders chat/message, chat/turn, chat/changes, chat/cite, chat/scroll, input/*). Update only status, public API, numbers.

PaperCraft (designs, later, not a coding task)

Project 'NotesCraft Plugins', file id 3accb94a799e7b22; artboard list in spec section 7. Designs should be done before T3-T13 start if the owner wants pixel parity; otherwise implement from the spec and reconcile after.