llm-canvas-darwin-x64 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/llm-canvas +0 -0
- package/package.json +21 -0
- package/skills/canvas-workspace/SKILL.md +320 -0
- package/workspace/app.js +727 -0
- package/workspace/comments.js +345 -0
- package/workspace/index.html +136 -0
- package/workspace/lib/highlight.min.js +1244 -0
- package/workspace/lib/hljs-github-dark.min.css +10 -0
- package/workspace/lib/mermaid.min.js +2029 -0
- package/workspace/lib/tailwind.js +83 -0
- package/workspace/lib/three.min.js +7 -0
- package/workspace/lib/turndown-plugin-gfm.min.js +165 -0
- package/workspace/lib/turndown.min.js +974 -0
- package/workspace/styles.css +454 -0
- package/workspace/theme.css +136 -0
package/bin/llm-canvas
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-canvas-darwin-x64",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An LLM-native local workspace for substantial coding-agent output — agents write structured HTML sections into a project-scoped browser workspace instead of dumping into the chat transcript. — prebuilt binary for darwin/x64.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/maxali/llm-canvas.git"
|
|
9
|
+
},
|
|
10
|
+
"os": [
|
|
11
|
+
"darwin"
|
|
12
|
+
],
|
|
13
|
+
"cpu": [
|
|
14
|
+
"x64"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/",
|
|
18
|
+
"workspace/",
|
|
19
|
+
"skills/"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canvas-workspace
|
|
3
|
+
description: Use when about to produce substantial artifact-style output the user will revisit, navigate, share, or interact with — codebase research, architecture explainers, multi-option comparisons, implementation plans, PR explainers, design exploration, incident reports, or custom one-off editor UIs. Routes output into a project-scoped local browser workspace by writing HTML section files into `.canvas/sessions/<slug>/sections/` instead of dumping markdown into the chat transcript.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Canvas Workspace
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Canvas is a local browser workspace agents write to **as files on disk**. The contract is the filesystem:
|
|
11
|
+
|
|
12
|
+
- One HTML file per section at `.canvas/sessions/<slug>/sections/<id>.html`.
|
|
13
|
+
- The `llm-canvas` daemon watches that tree and hot-reloads the browser when files appear or change. This skill starts the daemon for you when it isn't already running (see Workflow step 1).
|
|
14
|
+
- The daemon serves the workspace shell, the component CSS, and the library bundles (Mermaid, hljs, Tailwind, three.js) **from the package itself** — resolved relative to the CLI's own location, not the project. They are not copied into `.canvas/`. The `--project` directory only locates `.canvas/` (your session data).
|
|
15
|
+
- The browser shell (the server picks an available port per project and records it in `.canvas/runtime.json` as `workspace_url`) renders inline `<section>` files into the workspace and embeds full HTML docs (`<!doctype html>`) as same-origin iframes.
|
|
16
|
+
|
|
17
|
+
**The daemon is required to view a session.** The section files persist on disk as plain HTML and are not trapped in an opaque format, but the styled workspace view only exists while the daemon is running. There is no vendored-CSS offline-render path.
|
|
18
|
+
|
|
19
|
+
**Core principle: chat is for conversation; Canvas is for artifacts.**
|
|
20
|
+
|
|
21
|
+
If the user will want to scroll back to it tomorrow, navigate it like a document, share a URL, commit it, or click something inside it — it belongs in Canvas, not chat.
|
|
22
|
+
|
|
23
|
+
**This skill governs *authoring* Canvas sessions — not modifying Canvas itself.** If the user asks to change the Canvas product (the daemon, the workspace shell, the CLI) or this skill's own instructions — e.g. "update the canvas-workspace skill", "fix the Canvas daemon" — that is ordinary code/doc editing in the `llm-canvas` repo. Treat it as a normal engineering task; do **not** route it through the session workflow below, and do **not** assume it means "update a session". The skill's own name (`/canvas-workspace`) is ambiguous: it can mean the skill, the product, or a session. When the request is ambiguous, ask which they mean *before* exploring or asking anything else.
|
|
24
|
+
|
|
25
|
+
## When to Use
|
|
26
|
+
|
|
27
|
+
**Use Canvas for** (substantial, multi-section, durable):
|
|
28
|
+
|
|
29
|
+
- Codebase research and system explainers ("how does rate limiting work")
|
|
30
|
+
- Architecture overviews and walkthroughs
|
|
31
|
+
- Multi-option comparisons and design exploration
|
|
32
|
+
- Implementation plans with multiple phases or files
|
|
33
|
+
- PR explainers and code review writeups
|
|
34
|
+
- Incident reports and post-mortems
|
|
35
|
+
- Custom one-off editor UIs (ticket prioritizer, flag editor, config diff)
|
|
36
|
+
- Anything you would otherwise emit as 500+ words of markdown with 2+ H2 sections
|
|
37
|
+
|
|
38
|
+
**Do NOT use Canvas for** (preserve the existing chat experience):
|
|
39
|
+
|
|
40
|
+
- Trivial Q&A ("what's the port?", "which version?")
|
|
41
|
+
- Code edits — the diff is the artifact
|
|
42
|
+
- Debugging conversation and clarifying questions
|
|
43
|
+
- Status updates ("done", "tests pass", "fixed it")
|
|
44
|
+
- Numeric or computational results
|
|
45
|
+
- Single-paragraph answers under ~300 words
|
|
46
|
+
|
|
47
|
+
**The test:** "Would the user want to revisit this in three days?" Yes → Canvas. No → chat.
|
|
48
|
+
|
|
49
|
+
Over-triggering Canvas (opening it for a 3-line answer) is the dominant failure mode the product names explicitly. Don't.
|
|
50
|
+
|
|
51
|
+
**User overrides win.** If the user explicitly says "answer in chat", "don't use Canvas", "no canvas this time", "just paste it here", or similar — honor it. You may flag once that Canvas would suit the output, then comply. Explicit user instruction beats this skill (system prompt hierarchy).
|
|
52
|
+
|
|
53
|
+
**No browser, no Canvas.** If the user is on SSH-only, in a CI environment, or otherwise headless, fall back to chat — Canvas requires a local browser tab.
|
|
54
|
+
|
|
55
|
+
## Workflow (filesystem contract — no MCP yet)
|
|
56
|
+
|
|
57
|
+
There is no MCP server today. You write to Canvas using your normal **Write** tool against specific paths, and **curl** for non-file operations like focus. The server's file watcher does the rest.
|
|
58
|
+
|
|
59
|
+
1. **Ensure the daemon is running, serving *this* project, and learn the URL.** `cat .canvas/runtime.json` from Bash — it contains `{ host, port, workspace_url, project_root, ... }`; `workspace_url` is the base for everything below.
|
|
60
|
+
|
|
61
|
+
`runtime.json` is a per-project file, but it records a **global, port-keyed** daemon process. It survives daemon death and goes silently wrong the moment another project's daemon takes that port — a stale `runtime.json` can point at a port where a **different repo's** daemon is happily running. So a successful `api/runtime` response is **not** proof the daemon is yours. Always verify identity:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
curl -fsS "$(jq -r .workspace_url .canvas/runtime.json)api/runtime" | jq -r .project_root
|
|
65
|
+
# this MUST equal "$PWD" — if it doesn't, the runtime.json is stale and points at another repo's daemon
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Start (or restart) the daemon yourself if** the file is missing, `api/runtime` fails, **or** `project_root` does not match `$PWD`. In that last case, do not reuse or trust the recorded daemon — it belongs to another repo; ignore it and start a fresh one for `$PWD`. Steps:
|
|
69
|
+
1. Run `llm-canvas start --project "$PWD"` (backgrounded).
|
|
70
|
+
2. Poll for `.canvas/runtime.json` (a few hundred ms, up to ~5s) and re-read `workspace_url`.
|
|
71
|
+
|
|
72
|
+
If `llm-canvas` is not on `PATH`, Canvas isn't installed yet — tell the user: *"Canvas isn't installed — `npm i -g llm-canvas && llm-canvas install-skill` and I'll route the next answer there."* Then answer in chat for this turn. **Never** create `docs/*.md` or any other substitute artifact — chat is the only fallback.
|
|
73
|
+
|
|
74
|
+
`--project "$PWD"` points the daemon at the *current project's* `.canvas/`. The shell/CSS/libs come from the CLI's own location regardless of where it's started, so an answer written here gets the bundled styling no matter which repo you're in.
|
|
75
|
+
|
|
76
|
+
2. **Check prior work** — if the prompt references previous work, look for a matching session. The **filesystem is the source of truth**: `ls .canvas/sessions/` lists exactly the sessions in *this* repo, independent of which daemon answers. Use the API only for titles/metadata on top, and only after the step-1 identity check passes — a cross-wired daemon will return another project's sessions. Don't duplicate prior sessions; resume them.
|
|
77
|
+
```bash
|
|
78
|
+
ls .canvas/sessions/ # ground truth: sessions in this repo
|
|
79
|
+
BASE=$(jq -r .workspace_url .canvas/runtime.json)
|
|
80
|
+
curl -fsS "${BASE}api/sessions" # titles + metadata (only after step 1's identity check passes)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
3. **Create or resume the session.** New session:
|
|
84
|
+
```bash
|
|
85
|
+
BASE=$(jq -r .workspace_url .canvas/runtime.json)
|
|
86
|
+
curl -fsS -X POST "${BASE}api/sessions" \
|
|
87
|
+
-H 'content-type: application/json' \
|
|
88
|
+
-d '{"title": "Rate limiting research", "slug": "rate-limiting", "purpose": "Explain how the limiter works end-to-end"}'
|
|
89
|
+
```
|
|
90
|
+
Resuming: skip this step and write straight into the existing slug.
|
|
91
|
+
|
|
92
|
+
4. **Plan sections before writing** — list the coherent units you'll cover (overview, request flow, config, edge cases, open questions, custom editor). Many small sections beat one giant section. Sections are the unit of navigation, focus, and hot-reload.
|
|
93
|
+
|
|
94
|
+
5. **Write sections one at a time using the Write tool**, each to its own file:
|
|
95
|
+
```
|
|
96
|
+
.canvas/sessions/<slug>/sections/<section-id>.html
|
|
97
|
+
```
|
|
98
|
+
Section IDs are lowercase kebab-case, stable across rewrites. Rewriting the same path replaces the section in place; the browser hot-reloads just that node. Do **NOT** put multiple sections in one file. Do **NOT** write an `index.html` — the workspace shell handles that.
|
|
99
|
+
|
|
100
|
+
6. **Focus the most important section:**
|
|
101
|
+
```bash
|
|
102
|
+
BASE=$(jq -r .workspace_url .canvas/runtime.json)
|
|
103
|
+
curl -fsS -X POST "${BASE}api/sessions/<slug>/focus/<section-id>"
|
|
104
|
+
```
|
|
105
|
+
Land the user on the section with the answer, not the default "overview".
|
|
106
|
+
|
|
107
|
+
7. **Reply in chat with a pointer + tight summary** — 3–6 lines including the session URL (`workspace_url` + `s/<slug>`, e.g. `http://127.0.0.1:43127/s/<slug>` — read the actual port from `.canvas/runtime.json`), what's in there, and where you focused them. Chat becomes a navigable command history.
|
|
108
|
+
|
|
109
|
+
## Authoring
|
|
110
|
+
|
|
111
|
+
### Inline form (default — 90% of sections)
|
|
112
|
+
|
|
113
|
+
A single top-level `<section>` element with a `data-canvas-title` attribute. That attribute is the *only* required marker — it's the title source-of-truth. Use for prose, diagrams, code, tables, forms — anything that's content rather than a self-contained app.
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<section data-canvas-title="Request Flow">
|
|
117
|
+
<h2 class="text-xl font-semibold">How a request flows through the limiter</h2>
|
|
118
|
+
<p class="text-slate-600">A request enters at
|
|
119
|
+
<a class="canvas-fileref" href="file:///abs/path/handler.ts#L42">handler.ts:42</a>,
|
|
120
|
+
hits the limiter middleware, and either short-circuits with 429 or proceeds to the resolver.
|
|
121
|
+
</p>
|
|
122
|
+
|
|
123
|
+
<div class="mermaid">
|
|
124
|
+
sequenceDiagram
|
|
125
|
+
Client->>Middleware: HTTP request
|
|
126
|
+
Middleware->>Redis: INCR bucket
|
|
127
|
+
Redis-->>Middleware: count
|
|
128
|
+
alt over limit
|
|
129
|
+
Middleware-->>Client: 429
|
|
130
|
+
else under limit
|
|
131
|
+
Middleware->>Resolver: continue
|
|
132
|
+
end
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<pre><code class="language-typescript">if (count > limit) return res.status(429).end();
|
|
136
|
+
await next();</code></pre>
|
|
137
|
+
|
|
138
|
+
<div class="canvas-callout info">
|
|
139
|
+
The increment is fire-and-forget; the response comes from a pipelined GET.
|
|
140
|
+
</div>
|
|
141
|
+
</section>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The workspace parses this and injects the element's children into the section host. Mermaid, hljs, and the stylesheet are applied automatically.
|
|
145
|
+
|
|
146
|
+
### Document form (escape hatch — for custom editor UIs)
|
|
147
|
+
|
|
148
|
+
A full HTML document starting with `<!doctype html>` or `<html>`. Used when the section needs its own scripts, state, or styles — a drag-and-drop prioritizer, a flag editor, a config diff with toggles. Embedded as a same-origin iframe; the iframe has its own DOM, `localStorage`, `fetch`, etc.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<!doctype html>
|
|
152
|
+
<html data-canvas-title="Flag editor">
|
|
153
|
+
<head><meta charset="utf-8"><style>/* iframe-local styles */</style></head>
|
|
154
|
+
<body>
|
|
155
|
+
<!-- own scripts, own state -->
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Default to inline. Reach for document only when you need a real interactive surface.** Prose, diagrams, and code do not need document form.
|
|
161
|
+
|
|
162
|
+
### Styling: Tailwind + Canvas component classes
|
|
163
|
+
|
|
164
|
+
The workspace bundles **Tailwind (Play CDN, served locally)**. Use Tailwind utility classes for general layout, spacing, typography, and color. A small set of Canvas component classes layers on top for product-specific semantics:
|
|
165
|
+
|
|
166
|
+
- `.canvas-callout.info|warn|danger` — highlighted note block
|
|
167
|
+
- `.canvas-card` — bordered content block
|
|
168
|
+
- `.canvas-pill` — small uppercase tag
|
|
169
|
+
- `.canvas-table` — styled table
|
|
170
|
+
- `.canvas-fileref` — monospace file:line link
|
|
171
|
+
- `.canvas-section-host` — applied to each section's outer container by the shell; you don't add it
|
|
172
|
+
|
|
173
|
+
Either vocabulary works. Lean on Tailwind for layout; lean on `.canvas-*` for "this is a Canvas thing" semantics.
|
|
174
|
+
|
|
175
|
+
### Colors and theme
|
|
176
|
+
|
|
177
|
+
The workspace is **theme-aware** — light and dark, switched by the toggle beside
|
|
178
|
+
the zoom control. Write section HTML that adapts to both.
|
|
179
|
+
|
|
180
|
+
- **Default to inherited color.** Don't set a text color on body prose — it
|
|
181
|
+
inherits the theme foreground from the shell, which flips with the theme.
|
|
182
|
+
- **Use the design tokens for surfaces.** `var(--bg)`, `var(--bg-elevated)`,
|
|
183
|
+
`var(--fg)`, `var(--fg-muted)`, `var(--border)`, `var(--accent)` are defined
|
|
184
|
+
for both themes and are available in inline sections and document-mode
|
|
185
|
+
iframes alike.
|
|
186
|
+
- **Use the component classes** — `.canvas-callout` (`.info/.warn/.danger`),
|
|
187
|
+
`.canvas-table`, `.canvas-card`, `.canvas-pill`, `.canvas-fileref`. They are
|
|
188
|
+
token-driven and theme automatically.
|
|
189
|
+
- **Avoid hardcoded light- or dark-specific utilities for primary content** —
|
|
190
|
+
e.g. `text-slate-900` or `text-slate-100`, `bg-white` or `bg-slate-900`. They
|
|
191
|
+
pin the content to one theme and break in the other. (`<pre>` blocks are the
|
|
192
|
+
exception — the shell styles them dark with light code text in both themes.)
|
|
193
|
+
|
|
194
|
+
### Diagrams, code, tables
|
|
195
|
+
|
|
196
|
+
- **Diagrams:** Mermaid via `<div class="mermaid">…</div>` for flowcharts, sequence, ER, gantt. Inline SVG for custom illustrations. Three.js is also bundled at `/lib/three.min.js` and is loadable from a *document-form* section if you need 3D. Do NOT draw ASCII diagrams.
|
|
197
|
+
- **Code:** `<pre><code class="language-XX">…</code></pre>`. highlight.js applies github-dark theme automatically. Use `<mark>` to annotate spans of interest.
|
|
198
|
+
- **Tables:** real `<table>` with `.canvas-table` (or Tailwind utilities — your call).
|
|
199
|
+
|
|
200
|
+
### File citations
|
|
201
|
+
|
|
202
|
+
Preserve `path:line` citations the way you would in chat. Render as `<a class="canvas-fileref" href="file:///abs/path#L42">path:42</a>`.
|
|
203
|
+
|
|
204
|
+
## Inbox (user → agent notes)
|
|
205
|
+
|
|
206
|
+
The workspace has an **inbox**: a collapsible "Messages" panel at the bottom of `<main>` with a compose box. The user types a note there; it's tied to the session (and to whichever section they had focused) and persisted to `.canvas/sessions/<slug>/inbox.jsonl`. This is the upstream channel — browser to agent.
|
|
207
|
+
|
|
208
|
+
It is **one-way**. The user's notes show in the panel; your acknowledgements show only as a dim "✓ Acknowledged" line under each note. You answer in chat as usual — don't try to "reply in the thread".
|
|
209
|
+
|
|
210
|
+
The `choice`/`form` widget protocol (clickable decisions in section HTML) is still deferred. Don't put `data-canvas-widget` markup in sections — it won't do anything yet. When you need a decision, ask in your chat-side reply.
|
|
211
|
+
|
|
212
|
+
### Reading and acknowledging notes
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
BASE=$(jq -r .workspace_url .canvas/runtime.json)
|
|
216
|
+
curl -fsS "${BASE}api/sessions/<slug>/inbox" # JSON array of inbox events, append order
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
A note is **unhandled** if it's a `comment.created` whose `id` is not the `ref_id` of any `comment.handled` event in the same file. For each unhandled note: read it, do the work it asks for (e.g. update or add a section), then acknowledge it so it stops surfacing:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
BASE=$(jq -r .workspace_url .canvas/runtime.json)
|
|
223
|
+
curl -fsS -X POST "${BASE}api/sessions/<slug>/inbox" \
|
|
224
|
+
-H 'content-type: application/json' \
|
|
225
|
+
-d '{"type":"comment.handled","ref_id":"<the note id>","text":"<one line: what you did>"}'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
(The `text` is recorded for traceability but not shown in the UI.)
|
|
229
|
+
|
|
230
|
+
### Surface inbox notes automatically (optional hook)
|
|
231
|
+
|
|
232
|
+
Add a `UserPromptSubmit` hook to the project's `.claude/settings.json` and Claude Code injects any pending notes into your context at the start of each turn — no manual polling:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"hooks": {
|
|
237
|
+
"UserPromptSubmit": [
|
|
238
|
+
{
|
|
239
|
+
"matcher": "",
|
|
240
|
+
"hooks": [
|
|
241
|
+
{
|
|
242
|
+
"type": "command",
|
|
243
|
+
"command": "llm-canvas inbox --project \"$CLAUDE_PROJECT_DIR\" --pending 2>/dev/null || true"
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
It reads `inbox.jsonl` straight off disk (the daemon need not be running), prints nothing when nothing is pending, and `|| true` makes it a silent no-op if Canvas isn't installed. It is **opt-in** — `llm-canvas start` does not install it; paste the snippet yourself. Even with the hook active, still POST a `comment.handled` ack after handling a note, or it keeps surfacing.
|
|
253
|
+
|
|
254
|
+
## Resuming prior work
|
|
255
|
+
|
|
256
|
+
When the user says "continue the X plan" or "come back to the Y research":
|
|
257
|
+
|
|
258
|
+
1. `ls .canvas/sessions/` → find the matching slug (filesystem ground truth). For titles/metadata: `BASE=$(jq -r .workspace_url .canvas/runtime.json)`, then `curl -fsS "${BASE}api/sessions"` — only after the step-1 daemon-identity check passes.
|
|
259
|
+
2. `curl -fsS "${BASE}api/sessions/<slug>/sections"` → see the existing structure.
|
|
260
|
+
3. **Check the inbox:** `curl -fsS "${BASE}api/sessions/<slug>/inbox"` → for each `comment.created` with no matching `comment.handled` (by `ref_id`), that's a note the user left for you. Handle it as part of resuming, then POST a `comment.handled` ack (see the Inbox section). If the optional `UserPromptSubmit` hook is installed, pending notes are already in your context.
|
|
261
|
+
4. `Read` the section files you'll modify so you know the current state, not your last memory of it (and so you don't clobber user edits).
|
|
262
|
+
5. Continue with `Write` against the existing section paths; same section_id = in-place update.
|
|
263
|
+
|
|
264
|
+
If the user has hand-edited a section, your write will replace it. Read before writing if there's any chance of conflict.
|
|
265
|
+
|
|
266
|
+
## Chat-side reply
|
|
267
|
+
|
|
268
|
+
When Canvas owns the output, chat is a pointer, not a duplicate:
|
|
269
|
+
|
|
270
|
+
> Documented in Canvas: http://127.0.0.1:43127/s/rate-limiting
|
|
271
|
+
>
|
|
272
|
+
> Six sections — overview, request flow, Redis fallback, config, edge cases, and a flag editor at the bottom you can use to tune the RPS limit. Focused you on Redis fallback since that's where the bug looks like it lives.
|
|
273
|
+
|
|
274
|
+
(The URL above is an example — read the real base from `workspace_url` in `.canvas/runtime.json` and append `s/<slug>`; the port varies per project.)
|
|
275
|
+
|
|
276
|
+
Pointer + what's there + where you focused them. Do not paste the full content into chat too — the section HTML lives on disk and the daemon (which this skill keeps running) renders it; duplicating it in chat defeats the point.
|
|
277
|
+
|
|
278
|
+
## Red flags — STOP and rethink
|
|
279
|
+
|
|
280
|
+
| Thought | Reality |
|
|
281
|
+
|---|---|
|
|
282
|
+
| "This is small enough to just chat" | If you used 2+ H2s, it isn't small. Canvas. |
|
|
283
|
+
| "I'll dump markdown now, port later" | Nobody ports. Write to Canvas first or not at all. |
|
|
284
|
+
| "The user didn't ask for Canvas" | Users ask questions, not output media. Pick the medium. |
|
|
285
|
+
| "Markdown is more universal" | The user is reading in a chat window they can't navigate or revisit. |
|
|
286
|
+
| "I'll just write one big `index.html`" | NO. The shell renders `index.html`. You write one file per section under `sections/`. |
|
|
287
|
+
| "I'll write a giant single section file with everything" | Sections are the navigation, focus, and reload unit. Split into 4–8 small files. |
|
|
288
|
+
| "I'll use document form, it's powerful" | Document form is for custom editor UIs only. Default is inline. |
|
|
289
|
+
| "I'll also write the whole thing in chat as backup" | Don't double-write. The section files are on disk; the daemon renders them; chat gets a pointer. |
|
|
290
|
+
| "Canvas isn't running, I'll just chat" | Start it yourself — `llm-canvas start --project "$PWD"` (backgrounded). Chat-fallback only if `llm-canvas` is not on PATH. |
|
|
291
|
+
| "`api/runtime` answered, so the daemon is mine" | A stale `runtime.json` can point at another repo's daemon on the same port. Verify `project_root` === `$PWD` before trusting any `api/*` call. |
|
|
292
|
+
| "User said `/canvas-workspace`, so they want a session" | The skill's own name is ambiguous — skill, product, or session. Ask which they mean; don't assume session work. |
|
|
293
|
+
| "Canvas isn't installed, I'll write a markdown file in docs/ instead" | No. If you can't start the daemon, answer in chat. Never invent a substitute artifact. |
|
|
294
|
+
| "It's just a quick walkthrough" | A walkthrough with 3 diagrams and a table is not quick. Canvas. |
|
|
295
|
+
| "I already started typing the answer in chat" | Sunk cost. Stop, create the session, write sections. Don't double-write. |
|
|
296
|
+
| "The PRD/spec already decided this" | A comparison confirming or reopening a prior decision is exactly what the user will revisit. Canvas. |
|
|
297
|
+
| "I'll add extra root attributes to be safe" | Only `data-canvas-title` is required. Don't invent ceremony. |
|
|
298
|
+
| "I'll pin colors to one theme" | The workspace is light *and* dark. Hardcoded `text-slate-900`/`text-slate-100` or `bg-white`/`bg-slate-900` breaks in the other theme. Default to inherited color; use tokens / `.canvas-callout.*`. |
|
|
299
|
+
|
|
300
|
+
## Common mistakes
|
|
301
|
+
|
|
302
|
+
- **Writing one mega `index.html`.** That's not how Canvas works. Write `sections/<id>.html`, one per coherent unit.
|
|
303
|
+
- **Missing `data-canvas-title`.** Title comes from that attribute on the root element.
|
|
304
|
+
- **ASCII diagrams.** Use Mermaid (`<div class="mermaid">…</div>`) or inline SVG.
|
|
305
|
+
- **Section IDs that aren't kebab-case.** Use `^[a-z0-9][a-z0-9-]*$` (e.g. `request-flow`, `open-questions`, `flag-editor`).
|
|
306
|
+
- **Skipping the focus call.** The user lands on the session root by default; focus the answer.
|
|
307
|
+
- **Skipping the chat summary.** The user needs to know you wrote something and where to look.
|
|
308
|
+
- **Document form for prose.** Wraps content in an iframe for no reason. Use inline.
|
|
309
|
+
- **Triggering Canvas for trivial answers.** A 3-line answer in Canvas is the failure mode that gets the tool disabled. Stay in chat.
|
|
310
|
+
- **Trusting a stale `runtime.json`.** It's a per-project file pointing at a global, port-keyed daemon. Before any `api/*` call, confirm `api/runtime`'s `project_root` equals `$PWD` — otherwise you're reading and writing another repo's sessions.
|
|
311
|
+
- **Listing sessions only via the API.** `ls .canvas/sessions/` is the ground truth for *this* repo. The API depends on which daemon answers; a cross-wired daemon hands you another project's sessions.
|
|
312
|
+
- **Interrogating before exploring.** Don't ask "which session?" before running `ls .canvas/sessions/`. Cheap local checks first; then ask a precise question, if any remains.
|
|
313
|
+
- **Pinning section HTML to one theme.** The workspace is light *and* dark. Don't hardcode theme-specific colors (`text-slate-900`/`text-slate-100`, `bg-white`/`bg-slate-900`) for primary content — it breaks in the other theme. Default to inherited color; use the design tokens and `.canvas-callout.*` for highlighted blocks.
|
|
314
|
+
|
|
315
|
+
## When you're unsure
|
|
316
|
+
|
|
317
|
+
Ask one question: **"Would the user want to revisit this in three days?"**
|
|
318
|
+
|
|
319
|
+
- Yes → Canvas (and structure it as multiple small section files).
|
|
320
|
+
- No → chat (and answer directly, with file:line citations).
|