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

11 KiB

Tasks: Templater subset (roadmap item 12)

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/templater (package notescraft-templater, [lints] workspace = true, deps: notescraft-templates, notescraft-bases, thiserror), register it in the root [workspace.dependencies] and in xtask/src/layers.rs at L3. Answer Q1. Needs Bases tasks 1-6 (expression parser and evaluator) done: they are ([x] in bases/tasks.md).

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

  • 1. Template segmenter with whitespace control (brief briefs/10-templater-segments.md). OWNS: crates/templater/src/lib.rs, crates/templater/src/error.rs, crates/templater/src/segment.rs, crates/templater/tests/segment.rs. FILES TO OPEN: spec.md D2, crates/templates/src/expand.rs (style: scan with find, get()). GOAL: pub fn parse(src: &str) -> Result<Template>; Segment { Text(String), Command { src: String, line: u32, kind: Kind } }, Kind { Output, Js }; <%-, -%>, <%_, _%> applied to neighbouring Text; limits of D2. TESTS: golden cases from the Templater whitespace-control docs; unterminated <% errors with its line; <%* gives Kind::Js; 2 001 commands refused; 1 MiB of <% returns in < 200 ms; text without tags is one segment equal to the input. VERIFY: standing, -p notescraft-templater. DO NOT: parse expressions (task 2).
  • 2. Expression parser (the D3 grammar). OWNS: crates/templater/src/expr.rs, crates/templater/tests/expr.rs, crates/templater/src/lib.rs (one mod). FILES TO OPEN: spec.md D3, crates/bases/src/expr/parse.rs and ast.rs (style of a hand-written parser with depth limit). GOAL: pub enum Expr { Str, Num, Bool, Null, Array(Vec<Expr>), Path(Vec<PathPart>), Call { path: Vec<String>, args: Vec<Expr> }, Add(Box,Box), Coalesce(Box,Box) }, PathPart { Ident(String), Index(String) }, pub fn parse_expr(src: &str) -> Result<Expr>. Anything outside D3 is Error::Unsupported { what } ("arrow function", "ternary", ...). TESTS: every expression in D3 table parses; tp.frontmatter["my key"]; escapes; nesting depth 33 refused; a ? b : c, x => y, `a${b}` give Unsupported; 2 KiB limit. VERIFY: standing. DO NOT: evaluate.
  • 3. tp.date module and ISO 8601 durations. OWNS: crates/templater/src/modules/mod.rs, crates/templater/src/modules/date.rs, crates/templater/src/duration.rs, crates/templater/tests/date.rs. FILES TO OPEN: spec.md D3, crates/templates/src/date.rs, crates/templates/src/format.rs. GOAL: pub(crate) fn call_date(name: &str, args: &[Value], ctx: &Ctx) -> Result<Value> for now/tomorrow/yesterday/weekday; parse_iso_duration(&str) -> Option<Dur { years, months, days, secs }> and add_dur(DateTime, Dur) -> DateTime with month-end clamping. Ctx is declared in task 4's file; for this task use a minimal local struct DateCtx { now, week_start } and let task 4 adapt. TESTS: the docs examples with now = 2026-10-13 15:04:05; -7, "P1Y", "-P1W", "P1M2D", "PT2H"; reference date from a title string with reference_format; weekday(.., 0), 7, -7 with Monday and Sunday week starts; Jan 31 + P1M = Feb 28/29; invalid format token passes through as format does. VERIFY: standing. DO NOT: read the clock; handle tp.file.
  • 4. Ctx, tp.file, tp.frontmatter, tp.system evaluation and render (no prompts yet). OWNS: crates/templater/src/ctx.rs, crates/templater/src/render.rs, crates/templater/src/modules/file.rs, .../frontmatter.rs, .../system.rs, crates/templater/tests/render.rs. FILES TO OPEN: spec.md D3 and D4, crates/templater/src/expr.rs, crates/bases/src/value.rs (Value), crates/templater/src/modules/date.rs. GOAL: Ctx, Rendered, RenderError, Template::render(&self, &Ctx, &Answers) -> Rendered evaluating Expr (paths for tp.file.title, calls, +, ??); unknown tp.* keeps the command text and adds a RenderError; cursor(n) records a mark and renders empty; cursor_append; rename/move recorded; Js segments verbatim + error. Prompt/suggester calls return the empty string until task 5. TESTS: golden template with title/folder/path/tags/frontmatter/dates; unknown function kept verbatim; cursor offset equals the byte position in the output after earlier tags expanded; two cursors: lowest order wins, the other reported; ?? default for a missing frontmatter key; output cap 4x + 256 KiB. VERIFY: standing. DO NOT: do I/O; clone read_note results more than once.
  • 5. Inputs: prompts and suggesters, two-phase (D6). OWNS: crates/templater/src/inputs.rs, crates/templater/src/render.rs (edit: answers), crates/templater/tests/inputs.rs. FILES TO OPEN: spec.md D4 and D6, crates/templater/src/render.rs. GOAL: Template::inputs(&self, ctx: &Ctx) -> Vec<Input>, Answers, InputKind; render substitutes answers by command index; cancel semantics of D4; a prompt with a non-literal arg evaluated from the pure context or reported. TESTS: two prompts + suggester in order with ids; defaults; multiline; answer fills; None with throw aborts (empty text + cancelled); None without throw gives ""; prompt argument built with tp.file.title + " notes". VERIFY: standing. DO NOT: add UI types.
  • 6. include and the Bases-expression fallback. OWNS: crates/templater/src/modules/include.rs, crates/templater/src/fallback.rs, crates/templater/tests/include.rs. FILES TO OPEN: spec.md D3, crates/bases/src/expr/mod.rs and crates/bases/src/eval/mod.rs (eval, Env::new), crates/bases/src/row.rs (RowRef::new), crates/markdown/src/scanning/ (heading section extraction, find the existing helper before writing one). GOAL: tp.file.include("[[Note#Heading]]") with section/block via Ctx.read_note, depth 4, templates inside rendered with the same Ctx; non-tp paths evaluated through notescraft_bases with a RowRef built from Ctx (path, tags, frontmatter), result converted to text with Value::to_display_string. TESTS: include whole note, heading section, block; cycle A -> A stops with an error at depth 4; <% file.name.upper() %>; <% now().format("YYYY") %> equals the date module; failing bases expression becomes a RenderError. VERIFY: standing + cargo xtask layers. DO NOT: add a second section extractor if one exists.

Phase 2 - engine

  • 7. Commands template.inputs, template.render, template.apply, template.new_note, and the legacy hooks. OWNS: crates/engine/src/templater/mod.rs, .../commands.rs, .../apply.rs, one mod templater; in crates/engine/src/lib.rs, one register_templater(reg) in crates/engine/src/commands.rs::register_core, crates/engine/src/ai/assistant_gate.rs (READ: inputs, render; WRITE: apply, new_note), crates/engine/tests/core_commands.rs, crates/engine/tests/templater.rs. FILES TO OPEN: crates/engine/src/builtin/slash_commands.rs:232 (current template insertion), crates/engine/src/vault/daily.rs:140-160, crates/engine/src/periodic/mod.rs:60-70, crates/engine/src/bases/commands.rs (group to copy), crates/engine/src/vault/notes.rs. GOAL: D5 table; helper render_text(session, text, ctx) -> String (legacy expand then templater) called from the three legacy sites with prompts defaulted; apply modes insert/replace_note; rename/move actions applied after write through note.rename's code path; errors returned in the result, never as a command failure. TESTS: templater.rs on a TempVault: new note from a template with a prompt (answers supplied); insert at offset; legacy {{title}} template unchanged; daily note from a Templater template; rename action moves the file; read-only session refuses writes; a template with <%* %> still inserts, errors listed. VERIFY: standing for notescraft-engine + --test templater --test core_commands. DO NOT: touch the UI; change the legacy expand.
  • 8. [templater] settings and folder templates on new notes. OWNS: crates/engine/src/prefs/templater_config.rs, crates/engine/src/prefs/catalog/ (one listing entry), crates/engine/src/templater/folder.rs, crates/engine/tests/templater_folder.rs. FILES TO OPEN: crates/engine/src/prefs/files_config.rs + crates/engine/tests/files_config_viewers.rs (pattern), crates/engine/src/vault/notes.rs (the create-note path), spec.md D5. GOAL: config struct with validation (folder paths relative, no .., regex compile check), folder_template_for(path) -> Option<String> (deepest wins), hook in the create-note path: only when trigger_on_new_file and the new note is empty. TESTS: deepest folder wins; disabled by default; an existing non-empty note is untouched; invalid regex/path in config is rejected with invalid_params; imported files (not created through the command) untouched. VERIFY: standing. DO NOT: scan the vault.

Phase 3 - UI (lead wires the dispatch lines)

  • 9. Prompt modal, caret placement, palette entries. OWNS: crates/ui-egui/src/views/templater_prompt/mod.rs, .../form.rs, crates/ui-egui/src/text/views/templater/mod.rs, new [ui.templater] table at the END of crates/i18n/locales/en.toml, crates/ui-egui/src/shell/app/editor/palette_insert.rs (cursor placement after insert), crates/ui-egui/tests/templater_prompt.rs. FILES TO OPEN: crates/ui-egui/src/widgets/palette.rs, crates/ui-egui/src/shell/app/editor/palette_insert.rs, crates/ui-egui/src/views/share_dialog/mod.rs (a modal with a worker job), .claude/skills/notescraft-egui-screen/. GOAL: D7: when template.inputs is non-empty show the form (library Modal, Input, Textarea, Select; no raw egui widgets), submit calls template.apply/new_note, caret goes to cursor; first render error shown as a toast. TESTS: kittest: template with two prompts shows two fields, Enter applies, Esc cancels and writes nothing, caret at cursor offset. VERIFY: standing + cargo xtask i18n + cargo xtask tree; snapshot PNG read. DO NOT: hardcode strings.
  • 10. Docs and status. OWNS: ROADMAP.md, docs/ARCHITECTURE.md (templater crate row), docs/CODE-MAP.md (templater rows), specs/plugins-parity/roadmap.md (mark 12), crates/i18n/locales/en.toml check only. GOAL: update status/API/numbers; list the supported function table in ARCHITECTURE. VERIFY: cargo xtask ci (whole set). DO NOT: edit code.