Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions internal/mcp/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ func ResolveInstructions() string {
// instructions (apps/desktop/src/mcp/instructions.ts), so an agent behaves
// the same whichever binary is serving the vault.
const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain .md files on\n" +
"disk, rendered live with KaTeX, TikZ, function-plot, JSXGraph, and\n" +
"Mermaid. Treat the vault like a shared filesystem. Prompts will be\n" +
"disk, rendered live with KaTeX, TikZ, function-plot, JSXGraph,\n" +
"Mermaid, and D2. Treat the vault like a shared filesystem. Prompts will be\n" +
"short; you must infer the right note shape, voice, and visuals from\n" +
"the subject itself.\n" +
"\n" +
Expand All @@ -38,7 +38,7 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
" bullet-list template onto a subject that wants prose, and never\n" +
" force prose onto a subject that wants steps.\n" +
"2. **Always use the native renderers for anything visual.** KaTeX\n" +
" for math, TikZ / function-plot / JSXGraph / Mermaid for every\n" +
" for math, TikZ / function-plot / JSXGraph / Mermaid / D2 for every\n" +
" diagram. Never ASCII art, never unicode-arrow sketches.\n" +
"3. **Structure logically, flow consistently.** Decide the section\n" +
" order before writing. Sections carry a reader from orientation\n" +
Expand Down Expand Up @@ -167,7 +167,7 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
"made of `/ \\ | - + * ^ . o > < \u2192 \u2190 \u2191 \u2193 \u2022` or\n" +
"box-drawing characters, fenced or not, is a bug. If you\n" +
"catch yourself about to make one, delete it and emit a ```tikz (or\n" +
"```function-plot / ```jsxgraph / ```mermaid) block instead.\n" +
"```function-plot / ```jsxgraph / ```mermaid / ```d2) block instead.\n" +
"Triggers that mean \"use TikZ, not ASCII\": axes, origin, point at,\n" +
"arrow from\u2026to, head to tail, parallelogram, rotation, projection,\n" +
"span, plane, angle, triangle, unit circle, subspace, tangent, normal,\n" +
Expand All @@ -180,7 +180,13 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
" vectors over axes.\n" +
"- ```jsxgraph: interactive / draggable constructions.\n" +
"- ```mermaid: flow, sequence, class, state, ER, mindmap,\n" +
" Gantt. NOT for coordinate geometry.\n" +
" Gantt. NOT for coordinate geometry. Labels are plain text plus\n" +
" `<br>` breaks: no other HTML and no `$$` math inside a label\n" +
" (put the formula in a KaTeX block next to the diagram).\n" +
"- ```d2: architecture, infrastructure and system diagrams, nested\n" +
" containers, data models (`shape: sql_table`) and sequence\n" +
" diagrams. Labels may be Markdown (`|md \u2026|`). Icons, images and\n" +
" links are not drawn: a diagram never fetches anything.\n" +
"\n" +
"Fence body rules:\n" +
"- `tikz`: emit the TikZ content itself, usually a bare\n" +
Expand Down Expand Up @@ -208,7 +214,9 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
"`due:YYYY-MM-DD`, `!high` / `!med` / `!low`, `@waiting`, and\n" +
"`#tag`.\n" +
"\n" +
"**Callouts**: `> [!note]`, `> [!tip]`, `> [!warning]`.\n" +
"**Callouts**: `> [!note]`, `> [!tip]`, `> [!warning]`. A `-` right after the\n" +
"type (`> [!example]- Screenshots`) makes the callout start collapsed, a `+`\n" +
"starts it expanded; use `-` for long blocks a reader opens on demand.\n" +
"\n" +
"## Tool etiquette\n" +
"\n" +
Expand All @@ -227,6 +235,28 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
" `tasks: false`/`note`, excluded folders). Pass\n" +
" includeExcluded: true only when the user asks for everything.\n" +
"\n" +
"## Comments: reviewing a note together\n" +
"\n" +
"Notes carry comment threads, kept beside the note and shown in the app's\n" +
"Comments panel. A user who asks you to review, answer, or discuss a note\n" +
"usually means through those threads, like a pull request, not by editing\n" +
"the body.\n" +
"\n" +
"- Start with `list_comments` on the note. Each thread shows the passage it\n" +
" is anchored to, the line it sits on now, who wrote what (`author` is\n" +
" null for the user), and the replies so far.\n" +
"- Answer a thread with `reply_to_comment`; it lands under the user's\n" +
" comment, signed with your name. Reply to every open thread you were asked\n" +
" about, one reply per thread, and keep replies short and concrete.\n" +
"- Raise something new with `add_comment`, passing `anchor_text` copied\n" +
" verbatim from the note so the comment highlights that passage in the app.\n" +
" Omit it only for a note-level remark.\n" +
"- Change the note body only when the user asks for the change; when a thread\n" +
" ends in \"do it\", make the edit with the editing tools, then reply in the\n" +
" thread saying what changed.\n" +
"- `resolve_comment` only when the user says the thread is settled or asks\n" +
" you to close it. Resolved threads stay in the note's history.\n" +
"\n" +
"## Self-check before every write\n" +
"\n" +
"Scan the markdown before sending it. Fix, don\u2019t ship:\n" +
Expand Down Expand Up @@ -289,6 +319,19 @@ const DefaultInstructions = "You are connected to a user's ZenNotes vault: plain
" B -- no --> D[Do Y]\n" +
"```\n" +
"\n" +
"Architecture:\n" +
"\n" +
"```d2\n" +
"direction: right\n" +
"app: Desktop app {\n" +
" ui: Renderer\n" +
" main: Main process\n" +
" ui -> main: IPC\n" +
"}\n" +
"cloud: Sync server {shape: cloud}\n" +
"app.main -> cloud: sync\n" +
"```\n" +
"\n" +
"Adjust these. Don't invent ASCII replacements.\n" +
"\n" +
"## Intent mapping\n" +
Expand Down
27 changes: 27 additions & 0 deletions internal/tui/tasks_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ func TestKanbanCustomStatusUsesConfiguredThenDiscoveredColumns(t *testing.T) {
}
}

// Desktop #949: `status: done` closes a task note and names its column, so a
// configured Done column keeps it; checkbox tasks and unlisted statuses do not.
func TestKanbanCustomStatusKeepsClosedTaskNotesInConfiguredColumns(t *testing.T) {
status := func(s string) map[string]string { return map[string]string{"status": s} }
tasks := []vault.Task{
{ID: "done-note", Kind: "file", Checked: true, Fields: status("done")},
{ID: "active-note", Kind: "file", InProgress: true, Fields: status("in-progress")},
{ID: "cancelled-note", Kind: "file", Cancelled: true, Fields: status("cancelled")},
{ID: "finished-note", Kind: "file", Checked: true, Fields: status("completed")},
{ID: "checked-step", Checked: true, Fields: status("in-progress")},
{ID: "shipped-step", Checked: true, Fields: status("done")},
{ID: "next-step", Fields: status("in-progress")},
}
v := &tasksView{groupBy: "field:status", tasks: tasks}
a := &App{prefs: prefsView{Prefs: config.Prefs{KanbanStatuses: []string{"open", "in-progress", "waiting", "done"}}}}
v.buildColumns(a)
assertKanbanColumnIDs(t, v, []string{"open", "in-progress", "waiting", "done", "__none__"})
assertKanbanCards(t, v, "done", []string{"done-note"})
assertKanbanCards(t, v, "in-progress", []string{"active-note", "next-step"})

v = &tasksView{groupBy: "field:status", tasks: tasks}
a.prefs.KanbanStatuses = []string{"in-progress", "done", "cancelled"}
v.buildColumns(a)
assertKanbanColumnIDs(t, v, []string{"in-progress", "done", "cancelled", "__none__"})
assertKanbanCards(t, v, "cancelled", []string{"cancelled-note"})
}

func TestKanbanProjectFieldSeparatesLiteralNoneFromMissingValue(t *testing.T) {
v := &tasksView{groupBy: "field:project", tasks: []vault.Task{
{ID: "literal-none", Fields: map[string]string{"project": "none"}},
Expand Down
20 changes: 18 additions & 2 deletions internal/tui/views_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,24 @@ func (v *tasksView) buildColumns(a *App) {
}
}
configured := len(values)
for _, t := range v.tasks {
configuredSet := map[string]bool{}
for _, val := range values {
configuredSet[val] = true
}
// Closed tasks stay off a board, with one exception (desktop #949): a
// task note whose value names a configured column stays in it, done or
// cancelled. A task note's `status:` is its one state, so `status: done`
// both closes it and names its column. Only configured columns count, or
// every finished note would become a column; checkbox tasks keep the rule,
// since a status inherited from the note would pull in checked subtasks.
onBoard := func(t vault.Task) bool {
if !t.Checked && !t.Cancelled && !t.Forwarded {
return true
}
return t.Kind == "file" && configuredSet[strings.ToLower(strings.TrimSpace(t.Fields[key]))]
}
for _, t := range v.tasks {
if onBoard(t) {
add(t.Fields[key])
}
}
Expand All @@ -406,7 +422,7 @@ func (v *tasksView) buildColumns(a *App) {
}
cols = append(cols, kanbanColumn{id: "__none__", title: "No " + key})
for _, t := range v.tasks {
if t.Checked || t.Cancelled || t.Forwarded {
if !onBoard(t) {
continue
}
val := strings.ToLower(strings.TrimSpace(t.Fields[key]))
Expand Down
Loading