agent-canvas 0.1.16 → 0.1.18

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.
Files changed (41) hide show
  1. package/bin/agent-canvas.ts +3 -0
  2. package/bin/lib/commands/install.ts +7 -7
  3. package/bin/lib/commands/instructions.ts +66 -0
  4. package/bin/lib/instructions/component-callout.md +27 -0
  5. package/bin/lib/instructions/component-checklist.md +17 -0
  6. package/bin/lib/instructions/component-choice.md +27 -0
  7. package/bin/lib/instructions/component-codeblock.md +24 -0
  8. package/bin/lib/instructions/component-custom.md +34 -0
  9. package/bin/lib/instructions/component-diff.md +18 -0
  10. package/bin/lib/instructions/component-filepreview.md +15 -0
  11. package/bin/lib/instructions/component-image.md +17 -0
  12. package/bin/lib/instructions/component-item.md +43 -0
  13. package/bin/lib/instructions/component-markdown.md +33 -0
  14. package/bin/lib/instructions/component-mermaid.md +40 -0
  15. package/bin/lib/instructions/component-multichoice.md +19 -0
  16. package/bin/lib/instructions/component-note.md +11 -0
  17. package/bin/lib/instructions/component-priority.md +15 -0
  18. package/bin/lib/instructions/component-rangeinput.md +20 -0
  19. package/bin/lib/instructions/component-section.md +22 -0
  20. package/bin/lib/instructions/component-svg.md +31 -0
  21. package/bin/lib/instructions/component-table.md +21 -0
  22. package/bin/lib/instructions/component-usefeedback.md +33 -0
  23. package/bin/lib/instructions/component-userinput.md +17 -0
  24. package/bin/lib/instructions/flow-decision.md +53 -0
  25. package/bin/lib/instructions/flow-explain.md +47 -0
  26. package/bin/lib/instructions/flow-feature.md +188 -0
  27. package/bin/lib/instructions/flow-plan.md +106 -0
  28. package/bin/lib/instructions/flow-review.md +60 -0
  29. package/bin/lib/instructions/overview.md +173 -0
  30. package/bin/lib/instructions/theming.md +50 -0
  31. package/daemon/client/AnnotationSidebar.tsx +40 -20
  32. package/daemon/client/App.tsx +8 -1
  33. package/daemon/dist/client.js +35 -35
  34. package/daemon/src/compiler.ts +1 -0
  35. package/daemon/src/handlers/api.ts +3 -1
  36. package/daemon/src/server.ts +1 -1
  37. package/daemon/src/websocket.ts +62 -1
  38. package/package.json +1 -1
  39. package/skills/canvas/SKILL.md +9 -194
  40. package/skills/canvas/components.md +0 -403
  41. package/skills/canvas/flows.md +0 -406
@@ -130,6 +130,7 @@ export async function compilePlan(jsx: string, projectRoot?: string): Promise<Co
130
130
  entrypoints: [tmpFile],
131
131
  format: "esm",
132
132
  external: ["react", "react-dom", "#canvas/components", "#canvas/runtime"],
133
+ jsx: { runtime: "automatic", importSource: "react" },
133
134
  });
134
135
 
135
136
  if (!result.success) {
@@ -8,11 +8,12 @@ import type { Route } from "../router";
8
8
  export interface ApiContext {
9
9
  sessionManager: SessionManager;
10
10
  broadcastPlanUpdate: (id: string) => void;
11
+ broadcastRevisionUpdate: (id: string) => void;
11
12
  port: number;
12
13
  }
13
14
 
14
15
  export function createApiHandlers(ctx: ApiContext): Route[] {
15
- const { sessionManager, broadcastPlanUpdate, port } = ctx;
16
+ const { sessionManager, broadcastPlanUpdate, broadcastRevisionUpdate, port } = ctx;
16
17
 
17
18
  /**
18
19
  * Read *.jsx canvas files from a directory.
@@ -170,6 +171,7 @@ export function createApiHandlers(ctx: ApiContext): Route[] {
170
171
  const result = sessionManager.getLatestUnconsumedFeedback(sessionId);
171
172
  if (!result) return jsonResponse({ found: false });
172
173
  sessionManager.consumeFeedback(sessionId, result.revision);
174
+ broadcastRevisionUpdate(sessionId);
173
175
  return jsonResponse({ found: true, revision: result.revision, feedback: result.feedback });
174
176
  }
175
177
 
@@ -21,7 +21,7 @@ const sessionManager = new SessionManager();
21
21
  const wsManager = createWebSocketManager(sessionManager);
22
22
 
23
23
  const routes = [
24
- ...createApiHandlers({ sessionManager, broadcastPlanUpdate: wsManager.broadcastPlanUpdate, port: PORT }),
24
+ ...createApiHandlers({ sessionManager, broadcastPlanUpdate: wsManager.broadcastPlanUpdate, broadcastRevisionUpdate: wsManager.broadcastRevisionUpdate, port: PORT }),
25
25
  ...createFileHandlers(sessionManager),
26
26
  ...createUploadHandlers(sessionManager),
27
27
  ...createStaticHandlers(),
@@ -36,12 +36,63 @@ export function createWebSocketManager(sessionManager: SessionManager) {
36
36
  }
37
37
  }
38
38
 
39
+ function broadcastWatcherStatus(sessionId: string) {
40
+ const sockets = browserSockets.get(sessionId);
41
+ if (!sockets) return;
42
+ const watching = (waitSockets.get(sessionId)?.size ?? 0) > 0;
43
+ const payload = JSON.stringify({ type: "watcher-status", watching });
44
+ for (const ws of sockets) {
45
+ ws.send(payload);
46
+ }
47
+ }
48
+
49
+ // Ping wait sockets periodically to detect dead connections.
50
+ // We can't rely on ws.close() triggering the close handler for dead sockets,
51
+ // so we manually remove dead sockets and broadcast status.
52
+ const PING_INTERVAL = 5_000;
53
+ const pongReceived = new WeakSet<ServerWebSocket<WSData>>();
54
+
55
+ function removeWaitSocket(ws: ServerWebSocket<WSData>, sessionId: string) {
56
+ const sockets = waitSockets.get(sessionId);
57
+ if (sockets) {
58
+ sockets.delete(ws);
59
+ if (sockets.size === 0) waitSockets.delete(sessionId);
60
+ }
61
+ try { ws.close(); } catch {}
62
+ broadcastWatcherStatus(sessionId);
63
+ }
64
+
65
+ setInterval(() => {
66
+ for (const [sessionId, sockets] of waitSockets) {
67
+ for (const ws of sockets) {
68
+ if (!pongReceived.has(ws)) {
69
+ // No pong since last ping — connection is dead
70
+ removeWaitSocket(ws, sessionId);
71
+ continue;
72
+ }
73
+ pongReceived.delete(ws);
74
+ try { ws.ping(); } catch {
75
+ removeWaitSocket(ws, sessionId);
76
+ }
77
+ }
78
+ }
79
+ }, PING_INTERVAL);
80
+
39
81
  const handlers = {
40
82
  open(ws: ServerWebSocket<WSData>) {
41
83
  const { type, sessionId } = ws.data;
42
84
  const map = type === "browser" ? browserSockets : waitSockets;
43
85
  if (!map.has(sessionId)) map.set(sessionId, new Set());
44
86
  map.get(sessionId)!.add(ws);
87
+ if (type === "browser") {
88
+ // Send current watcher status to newly connected browser
89
+ const watching = (waitSockets.get(sessionId)?.size ?? 0) > 0;
90
+ ws.send(JSON.stringify({ type: "watcher-status", watching }));
91
+ } else {
92
+ // CLI waiter connected — notify browsers
93
+ pongReceived.add(ws); // Give it a free pass on first interval
94
+ broadcastWatcherStatus(sessionId);
95
+ }
45
96
  },
46
97
  message(ws: ServerWebSocket<WSData>, message: string | Buffer) {
47
98
  const { type, sessionId } = ws.data;
@@ -59,6 +110,7 @@ export function createWebSocketManager(sessionManager: SessionManager) {
59
110
  const waiters = waitSockets.get(sessionId);
60
111
  if (waiters && waiters.size > 0) {
61
112
  sessionManager.consumeFeedback(sessionId, session.currentRevision);
113
+ broadcastRevisionUpdate(sessionId);
62
114
  const payload = JSON.stringify({ type: "submit", feedback });
63
115
  for (const waiter of waiters) {
64
116
  waiter.send(payload);
@@ -71,13 +123,22 @@ export function createWebSocketManager(sessionManager: SessionManager) {
71
123
  } catch {}
72
124
  }
73
125
  },
126
+ pong(ws: ServerWebSocket<WSData>) {
127
+ if (ws.data.type === "wait") {
128
+ pongReceived.add(ws);
129
+ }
130
+ },
74
131
  close(ws: ServerWebSocket<WSData>) {
75
132
  const { type, sessionId } = ws.data;
76
133
  const map = type === "browser" ? browserSockets : waitSockets;
77
134
  map.get(sessionId)?.delete(ws);
78
135
  if (map.get(sessionId)?.size === 0) map.delete(sessionId);
136
+ if (type === "wait") {
137
+ // CLI waiter disconnected — notify browsers
138
+ broadcastWatcherStatus(sessionId);
139
+ }
79
140
  },
80
141
  };
81
142
 
82
- return { handlers, broadcastPlanUpdate, broadcastRevisionUpdate };
143
+ return { handlers, broadcastPlanUpdate, broadcastRevisionUpdate, broadcastWatcherStatus };
83
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-canvas",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Interactive visual canvas for Claude Code — rich annotatable documents in the browser",
5
5
  "bin": {
6
6
  "agent-canvas": "bin/agent-canvas.js"
@@ -11,203 +11,18 @@ disable-model-invocation: true
11
11
 
12
12
  ---
13
13
 
14
- # Canvas — Interactive Visual Documents
14
+ ## Session
15
15
 
16
- Canvas opens a rich, annotatable document in the user's browser. You write JSX, the user reviews and annotates it like Google Docs, and feedback comes back as markdown. Use it whenever structured visual communication beats inline chat.
16
+ Your canvas session ID is: `${CLAUDE_SESSION_ID}`
17
17
 
18
- **Read the flow reference before starting**: Check `${CLAUDE_SKILL_DIR}/flows.md` to pick the right flow for your task. Each flow has a specific sequence of canvases with templates.
18
+ Use this value for:
19
+ - File paths: `.claude/agent-canvas/${CLAUDE_SESSION_ID}/`
20
+ - CLI flags: `--session ${CLAUDE_SESSION_ID}`
19
21
 
20
- **Read the component reference as needed**: Check `${CLAUDE_SKILL_DIR}/components.md` for the full component API.
22
+ ## Instructions
21
23
 
22
- ## Core Mechanics
24
+ !`bunx agent-canvas instructions`
23
25
 
24
- ### Writing a Canvas
26
+ !`bunx agent-canvas instructions --list`
25
27
 
26
- Use the **Write** tool to create a `.jsx` file in `.claude/agent-canvas/${CLAUDE_SESSION_ID}/`:
27
-
28
- ```jsx
29
- // Write to: .claude/agent-canvas/${CLAUDE_SESSION_ID}/plan.jsx
30
-
31
- <Section title="Authentication Redesign">
32
- A proposal to replace session-based auth with JWT tokens.
33
-
34
- <Item label="Current state" badge="context" badgeVariant="info">
35
- The app uses express-session with Redis store.
36
- <FilePreview path="src/auth/session.ts" lines={[1, 45]} />
37
- </Item>
38
-
39
- <Item label="Proposed change" badge="proposal">
40
- Switch to stateless JWT with refresh token rotation.
41
- <Callout type="warning">This will invalidate all existing sessions.</Callout>
42
- </Item>
43
- </Section>
44
- ```
45
-
46
- Components are auto-available — no imports needed. The file is a JSX fragment (just tags) or a full module with `export default`.
47
-
48
- ### Pushing
49
-
50
- After writing canvas files, push the directory to open all canvases in the browser:
51
-
52
- ```bash
53
- bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID} --label "Implementation Plan"
54
- ```
55
-
56
- This pushes all `*.jsx` files in the directory as a snapshot (like a git commit). Each file appears as a separate tab in the browser. The canvas opens in the user's browser (first time) and the command exits immediately. **Always show the `browserUrl` from the push output to the user** so they can open the canvas manually if auto-open didn't work.
57
-
58
- ### Waiting for feedback
59
-
60
- After pushing, start the watch command **in the background** using the Bash tool's `run_in_background` parameter:
61
-
62
- ```bash
63
- # Use Bash tool with run_in_background: true
64
- bunx agent-canvas watch --session ${CLAUDE_SESSION_ID}
65
- ```
66
-
67
- This runs the watch process in the background. You will be **automatically notified** when the user submits feedback — the output will contain the feedback markdown. Do NOT poll, sleep, or proactively check on it. Just stop and wait for the notification.
68
-
69
- If feedback was already submitted but not yet consumed, the command returns immediately with the existing feedback.
70
-
71
- **Important**: After starting the background watch, do not continue with other work unless instructed. Wait for the notification that feedback has arrived before proceeding.
72
-
73
- ### Checking for feedback without blocking
74
-
75
- Use `fetch` to retrieve feedback if the user tells you they've already submitted:
76
-
77
- ```bash
78
- bunx agent-canvas fetch --session ${CLAUDE_SESSION_ID}
79
- ```
80
-
81
- Returns immediately — prints feedback to stdout if available, otherwise produces no output. Use this when the user tells you they've submitted feedback (e.g. after a reconnect or if the background watch was lost).
82
-
83
- ### Iterating
84
-
85
- Use the **Edit** tool to modify the existing JSX based on feedback — targeted edits, not full rewrites. Then push and watch again:
86
-
87
- ```bash
88
- bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID} --label "Implementation Plan (revised)"
89
-
90
- # Use Bash tool with run_in_background: true
91
- bunx agent-canvas watch --session ${CLAUDE_SESSION_ID}
92
- ```
93
-
94
- Each push creates a new **round** (revision) — all canvas files are snapshotted. The user sees which files changed in the revision selector.
95
-
96
- ### Responding to feedback
97
-
98
- When pushing a revised canvas after receiving user feedback, use `--response` to tell the user how you addressed their feedback. The response renders as a short banner at the top of the canvas.
99
-
100
- ```bash
101
- bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID} --label "Plan (revised)" --response "Incorporated all feedback. Switched to connection pooling as suggested. I kept the sync approach for writes — see the note in Phase 2 for my reasoning."
102
- ```
103
-
104
- Use `--response` to:
105
- - Confirm what you incorporated and how
106
- - Explain any opposing stance — if you disagree with a suggestion, say why
107
- - Call out anything you intentionally did NOT change and why
108
-
109
- Keep it concise (2-4 sentences). The user can see the diff in the canvas UI, so focus on the "why" not the "what".
110
-
111
- ### Multiple Canvases
112
-
113
- All `*.jsx` files in the canvas directory are pushed together as a snapshot. Each file appears as a separate tab in the browser. Use different files for different phases or concerns:
114
-
115
- ```
116
- .claude/agent-canvas/${CLAUDE_SESSION_ID}/
117
- discovery.jsx # Discovery interview
118
- requirements.jsx # Requirements spec
119
- plan.jsx # Implementation plan
120
- ```
121
-
122
- Write new files as phases progress, then push the directory. Previous phase files remain visible as context. The user can annotate any canvas and submit one combined feedback per revision.
123
-
124
- ```bash
125
- # Write discovery.jsx first, push and get feedback
126
- bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID} --label "Discovery"
127
- bunx agent-canvas watch --session ${CLAUDE_SESSION_ID} # run_in_background: true
128
-
129
- # Later, add requirements.jsx, push again — both files visible as tabs
130
- bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID} --label "Requirements"
131
- bunx agent-canvas watch --session ${CLAUDE_SESSION_ID} # run_in_background: true
132
- ```
133
-
134
- ### File Location
135
-
136
- All canvas files go in `.claude/agent-canvas/${CLAUDE_SESSION_ID}/` within the project root. Add `.claude/agent-canvas/` to `.gitignore` — these are ephemeral working files.
137
-
138
- ## Choosing a Flow
139
-
140
- Determine which flow fits before starting. See `${CLAUDE_SKILL_DIR}/flows.md` for full details with templates.
141
-
142
- ```
143
- User wants something built/changed?
144
- ├─ Vague request ("add auth", "refactor the API")
145
- │ └─ FEATURE flow: discovery → requirements → plan → [implement] → summary
146
- ├─ Clear request with specifics
147
- │ └─ PLAN flow: plan → [implement] → summary
148
- ├─ Complete instructions, just wants execution
149
- │ └─ Skip canvas, implement directly, optionally push summary
150
-
151
- User wants to understand something?
152
- ├─ "How does X work?", "Explain the auth system"
153
- │ └─ EXPLAIN flow: single explanatory canvas
154
-
155
- User wants review/audit?
156
- ├─ "Review this code", "Check for issues"
157
- │ └─ REVIEW flow: findings canvas with categorized issues
158
-
159
- User wants to make a decision?
160
- ├─ "Should we use X or Y?", "What database?"
161
- │ └─ DECISION flow: options with tradeoffs, recommendation
162
- ```
163
-
164
- **Adapt dynamically**: flows are guidelines, not rigid pipelines. Pivot if needed, skip if the user says so.
165
-
166
- ## Flow Execution Pattern
167
-
168
- 1. **Determine flow** from user intent
169
- 2. **Announce** briefly: "I'll start with discovery, then create a detailed plan."
170
- 3. **Write canvas JSX** with the Write tool to `.claude/agent-canvas/${CLAUDE_SESSION_ID}/<name>.jsx`
171
- 4. **Push the directory + tell the user**: Push with `bunx agent-canvas push .claude/agent-canvas/${CLAUDE_SESSION_ID}/ --session ${CLAUDE_SESSION_ID}` and show the `browserUrl` from the output so the user can open it
172
- 5. **IMMEDIATELY watch in background** — run `bunx agent-canvas watch --session ${CLAUDE_SESSION_ID}` with `run_in_background: true` right after push. Never stop after pushing. The push→watch sequence is atomic: no push without a watch. You will be notified when feedback arrives — do not poll or sleep.
173
- 6. **Read feedback** — check for annotations, answers, added context files, approval
174
- 7. **Edit with the Edit tool and re-push + watch**, or advance to next phase
175
- 8. **After implementation**, push a summary canvas
176
-
177
- ## Interview Integration
178
-
179
- For discovery phases, use iterative canvases or offer a checklist:
180
-
181
- ```jsx
182
- <Section title="Before we start">
183
- Which areas should we discuss in depth?
184
-
185
- <MultiChoice id="interview-areas" label="Select areas to cover" required
186
- options={[
187
- "Backend architecture & data model",
188
- "API design & contracts",
189
- "Frontend / UI behavior",
190
- "Edge cases & error handling",
191
- "Performance & scaling",
192
- "Security considerations",
193
- "Testing strategy",
194
- "Migration / deployment"
195
- ]} />
196
-
197
- <UserInput id="anything-else" label="Anything else I should know upfront?" />
198
- </Section>
199
- ```
200
-
201
- Then interview deeply in each selected area across subsequent rounds. Or ask in chat first: "Would you prefer a thorough interview before I plan, or should I make assumptions and you correct in review?"
202
-
203
- ## Important Rules
204
-
205
- - **Write** canvas files to `.claude/agent-canvas/${CLAUDE_SESSION_ID}/` using the Write tool. Never use bash heredocs or cat.
206
- - **Edit** canvas files using the Edit tool. Never rewrite entire files — make targeted edits.
207
- - JSX can be a fragment (just tags) or a full module with `export default function Canvas()`.
208
- - Every `<Item>` and interactive component needs a unique `id`.
209
- - `<FilePreview path="...">` paths are relative to project root.
210
- - For inline styling use `style={{ ... }}` — Tailwind is NOT available in canvas JSX.
211
- - Read any files the user added to context (listed under "Added context" in feedback) with the Read tool before the next iteration.
212
- - Always push a summary canvas after implementation, even if brief.
213
- - When the user approves, confirm what you'll do next before proceeding.
28
+ **Getting detailed docs**: Run `bunx agent-canvas instructions <topic>` for detailed documentation on any component or flow. For example: `bunx agent-canvas instructions component-mermaid` or `bunx agent-canvas instructions flow-feature`.