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.mdD2,crates/templates/src/expand.rs(style: scan withfind,get()). GOAL:pub fn parse(src: &str) -> Result<Template>;Segment { Text(String), Command { src: String, line: u32, kind: Kind } },Kind { Output, Js };<%-,-%>,<%_,_%>applied to neighbouringText; limits of D2. TESTS: golden cases from the Templater whitespace-control docs; unterminated<%errors with its line;<%*givesKind::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(onemod). FILES TO OPEN:spec.mdD3,crates/bases/src/expr/parse.rsandast.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 isError::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}`giveUnsupported; 2 KiB limit. VERIFY: standing. DO NOT: evaluate. - 3.
tp.datemodule 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.mdD3,crates/templates/src/date.rs,crates/templates/src/format.rs. GOAL:pub(crate) fn call_date(name: &str, args: &[Value], ctx: &Ctx) -> Result<Value>fornow/tomorrow/yesterday/weekday;parse_iso_duration(&str) -> Option<Dur { years, months, days, secs }>andadd_dur(DateTime, Dur) -> DateTimewith month-end clamping.Ctxis declared in task 4's file; for this task use a minimal local structDateCtx { now, week_start }and let task 4 adapt. TESTS: the docs examples withnow = 2026-10-13 15:04:05;-7,"P1Y","-P1W","P1M2D","PT2H"; reference date from a title string withreference_format;weekday(.., 0),7,-7with Monday and Sunday week starts; Jan 31 +P1M= Feb 28/29; invalid format token passes through asformatdoes. VERIFY: standing. DO NOT: read the clock; handletp.file. - 4.
Ctx,tp.file,tp.frontmatter,tp.systemevaluation andrender(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.mdD3 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) -> RenderedevaluatingExpr(paths fortp.file.title, calls,+,??); unknowntp.*keeps the command text and adds aRenderError;cursor(n)records a mark and renders empty;cursor_append;rename/moverecorded;Jssegments 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; cloneread_noteresults 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.mdD4 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;Nonewith throw aborts (empty text +cancelled);Nonewithout throw gives ""; prompt argument built withtp.file.title + " notes". VERIFY: standing. DO NOT: add UI types. - 6.
includeand 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.mdD3,crates/bases/src/expr/mod.rsandcrates/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 viaCtx.read_note, depth 4, templates inside rendered with the sameCtx; non-tppaths evaluated throughnotescraft_baseswith aRowRefbuilt fromCtx(path,tags,frontmatter), result converted to text withValue::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 aRenderError. 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, onemod templater;incrates/engine/src/lib.rs, oneregister_templater(reg)incrates/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; helperrender_text(session, text, ctx) -> String(legacy expand then templater) called from the three legacy sites with prompts defaulted;applymodes insert/replace_note;rename/moveactions applied after write throughnote.rename's code path; errors returned in the result, never as a command failure. TESTS:templater.rson aTempVault: 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 fornotescraft-engine+--test templater --test core_commands. DO NOT: touch the UI; change the legacyexpand. - 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.mdD5. 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 whentrigger_on_new_fileand 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 withinvalid_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 ofcrates/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: whentemplate.inputsis non-empty show the form (libraryModal,Input,Textarea,Select; no raw egui widgets), submit callstemplate.apply/new_note, caret goes tocursor; 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.tomlcheck only. GOAL: update status/API/numbers; list the supported function table in ARCHITECTURE. VERIFY:cargo xtask ci(whole set). DO NOT: edit code.