agent-canvas 0.1.15 → 0.1.17
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/agent-canvas.ts +3 -0
- package/bin/lib/commands/install.ts +7 -7
- package/bin/lib/commands/instructions.ts +66 -0
- package/bin/lib/instructions/component-callout.md +27 -0
- package/bin/lib/instructions/component-checklist.md +17 -0
- package/bin/lib/instructions/component-choice.md +27 -0
- package/bin/lib/instructions/component-codeblock.md +24 -0
- package/bin/lib/instructions/component-custom.md +34 -0
- package/bin/lib/instructions/component-diff.md +18 -0
- package/bin/lib/instructions/component-filepreview.md +15 -0
- package/bin/lib/instructions/component-image.md +17 -0
- package/bin/lib/instructions/component-item.md +43 -0
- package/bin/lib/instructions/component-markdown.md +33 -0
- package/bin/lib/instructions/component-mermaid.md +40 -0
- package/bin/lib/instructions/component-multichoice.md +19 -0
- package/bin/lib/instructions/component-note.md +11 -0
- package/bin/lib/instructions/component-priority.md +15 -0
- package/bin/lib/instructions/component-rangeinput.md +20 -0
- package/bin/lib/instructions/component-section.md +22 -0
- package/bin/lib/instructions/component-svg.md +31 -0
- package/bin/lib/instructions/component-table.md +21 -0
- package/bin/lib/instructions/component-usefeedback.md +33 -0
- package/bin/lib/instructions/component-userinput.md +17 -0
- package/bin/lib/instructions/flow-decision.md +53 -0
- package/bin/lib/instructions/flow-explain.md +47 -0
- package/bin/lib/instructions/flow-feature.md +188 -0
- package/bin/lib/instructions/flow-plan.md +106 -0
- package/bin/lib/instructions/flow-review.md +60 -0
- package/bin/lib/instructions/overview.md +173 -0
- package/bin/lib/instructions/theming.md +50 -0
- package/daemon/client/AnnotationSidebar.tsx +40 -20
- package/daemon/client/App.tsx +8 -1
- package/daemon/client/unifiedDiff.ts +12 -1
- package/daemon/dist/client.js +35 -35
- package/daemon/src/handlers/api.ts +3 -1
- package/daemon/src/server.ts +1 -1
- package/daemon/src/websocket.ts +62 -1
- package/package.json +1 -1
- package/skills/canvas/SKILL.md +9 -194
- package/skills/canvas/components.md +0 -403
- package/skills/canvas/flows.md +0 -406
|
@@ -17,20 +17,21 @@ interface AnnotationSidebarProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function AnnotationSidebar({ onPreview, onSubmit, collapseButton }: AnnotationSidebarProps) {
|
|
20
|
-
const { isReadOnly, selectedRevision, currentRevision, revisions } = useContext(RevisionContext);
|
|
20
|
+
const { isReadOnly, selectedRevision, currentRevision, revisions, agentWatching } = useContext(RevisionContext);
|
|
21
21
|
const sessionId = useContext(SessionContext);
|
|
22
|
-
const isCurrentButSubmitted = isReadOnly && selectedRevision === currentRevision;
|
|
23
22
|
const selectedRevInfo = revisions.find((r) => r.revision === selectedRevision);
|
|
23
|
+
const isCurrentButSubmitted = isReadOnly && selectedRevision === currentRevision;
|
|
24
|
+
const feedbackConsumed = !!selectedRevInfo?.feedbackConsumed;
|
|
24
25
|
const roundLabel = selectedRevInfo?.label || `Round ${selectedRevision}`;
|
|
25
26
|
|
|
26
27
|
if (isReadOnly) {
|
|
27
|
-
return <ReadOnlyAnnotationSidebar sessionId={sessionId} revision={selectedRevision} label={roundLabel} waitingForUpdate={isCurrentButSubmitted} collapseButton={collapseButton} />;
|
|
28
|
+
return <ReadOnlyAnnotationSidebar sessionId={sessionId} revision={selectedRevision} label={roundLabel} waitingForUpdate={isCurrentButSubmitted} feedbackConsumed={feedbackConsumed} agentWatching={agentWatching} collapseButton={collapseButton} />;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
return <AnnotationSidebarInner onPreview={onPreview} onSubmit={onSubmit} collapseButton={collapseButton} />;
|
|
31
|
+
return <AnnotationSidebarInner onPreview={onPreview} onSubmit={onSubmit} agentWatching={agentWatching} collapseButton={collapseButton} />;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function FeedbackDisplay({ sessionId, revision, label, waitingForUpdate, collapseButton }: { sessionId: string; revision: number; label: string; waitingForUpdate?: boolean; collapseButton?: React.ReactNode }) {
|
|
34
|
+
function FeedbackDisplay({ sessionId, revision, label, waitingForUpdate, feedbackConsumed, agentWatching, collapseButton }: { sessionId: string; revision: number; label: string; waitingForUpdate?: boolean; feedbackConsumed?: boolean; agentWatching?: boolean; collapseButton?: React.ReactNode }) {
|
|
34
35
|
const [feedback, setFeedback] = useState<string | null>(null);
|
|
35
36
|
const [loading, setLoading] = useState(true);
|
|
36
37
|
|
|
@@ -51,12 +52,7 @@ function FeedbackDisplay({ sessionId, revision, label, waitingForUpdate, collaps
|
|
|
51
52
|
{collapseButton}
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
|
-
{waitingForUpdate &&
|
|
55
|
-
<div className="mx-4 mb-3 px-3 py-2.5 rounded-lg bg-accent-green-muted flex items-center gap-2 flex-shrink-0">
|
|
56
|
-
<span className="w-2 h-2 rounded-full bg-accent-green animate-pulse" />
|
|
57
|
-
<span className="text-[12px] font-body text-accent-green">Waiting for next revision...</span>
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
55
|
+
{waitingForUpdate && <WaitingBanner feedbackConsumed={feedbackConsumed} agentWatching={agentWatching} />}
|
|
60
56
|
|
|
61
57
|
<div className="flex-1 overflow-y-auto px-4">
|
|
62
58
|
{loading ? (
|
|
@@ -71,9 +67,34 @@ function FeedbackDisplay({ sessionId, revision, label, waitingForUpdate, collaps
|
|
|
71
67
|
);
|
|
72
68
|
}
|
|
73
69
|
|
|
70
|
+
function WaitingBanner({ feedbackConsumed, agentWatching }: { feedbackConsumed?: boolean; agentWatching?: boolean }) {
|
|
71
|
+
if (feedbackConsumed) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="mx-4 mb-3 px-3 py-2.5 rounded-lg bg-accent-green-muted flex items-center gap-2 flex-shrink-0">
|
|
74
|
+
<span className="w-2 h-2 rounded-full bg-accent-green animate-pulse" />
|
|
75
|
+
<span className="text-[12px] font-body text-accent-green">Feedback received — waiting for next revision...</span>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (agentWatching) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="mx-4 mb-3 px-3 py-2.5 rounded-lg bg-accent-blue-muted flex items-center gap-2 flex-shrink-0">
|
|
82
|
+
<span className="w-2 h-2 rounded-full bg-accent-blue animate-pulse" />
|
|
83
|
+
<span className="text-[12px] font-body text-accent-blue">Waiting for agent to pick up feedback...</span>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
<div className="mx-4 mb-3 px-3 py-2.5 rounded-lg bg-accent-amber-muted flex items-center gap-2 flex-shrink-0">
|
|
89
|
+
<span className="w-2 h-2 rounded-full bg-accent-amber" />
|
|
90
|
+
<span className="text-[12px] font-body text-accent-amber">Agent disconnected — tell Claude to check feedback</span>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
type ReadOnlyTab = "feedback" | "annotations";
|
|
75
96
|
|
|
76
|
-
function ReadOnlyAnnotationSidebar({ sessionId, revision, label, waitingForUpdate, collapseButton }: { sessionId: string; revision: number; label: string; waitingForUpdate?: boolean; collapseButton?: React.ReactNode }) {
|
|
97
|
+
function ReadOnlyAnnotationSidebar({ sessionId, revision, label, waitingForUpdate, feedbackConsumed, agentWatching, collapseButton }: { sessionId: string; revision: number; label: string; waitingForUpdate?: boolean; feedbackConsumed?: boolean; agentWatching?: boolean; collapseButton?: React.ReactNode }) {
|
|
77
98
|
const { annotations, generalNote, activeAnnotationId, setActiveAnnotationId } = useAnnotations();
|
|
78
99
|
const { setActiveView } = useContext(ActiveViewContext);
|
|
79
100
|
const hasAnnotations = annotations.length > 0 || generalNote.trim().length > 0;
|
|
@@ -81,7 +102,7 @@ function ReadOnlyAnnotationSidebar({ sessionId, revision, label, waitingForUpdat
|
|
|
81
102
|
|
|
82
103
|
// If no annotations in localStorage, just show feedback
|
|
83
104
|
if (!hasAnnotations) {
|
|
84
|
-
return <FeedbackDisplay sessionId={sessionId} revision={revision} label={label} waitingForUpdate={waitingForUpdate} collapseButton={collapseButton} />;
|
|
105
|
+
return <FeedbackDisplay sessionId={sessionId} revision={revision} label={label} waitingForUpdate={waitingForUpdate} feedbackConsumed={feedbackConsumed} agentWatching={agentWatching} collapseButton={collapseButton} />;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
return (
|
|
@@ -114,12 +135,7 @@ function ReadOnlyAnnotationSidebar({ sessionId, revision, label, waitingForUpdat
|
|
|
114
135
|
{collapseButton}
|
|
115
136
|
</div>
|
|
116
137
|
|
|
117
|
-
{waitingForUpdate &&
|
|
118
|
-
<div className="mx-4 mb-3 px-3 py-2.5 rounded-lg bg-accent-green-muted flex items-center gap-2 flex-shrink-0">
|
|
119
|
-
<span className="w-2 h-2 rounded-full bg-accent-green animate-pulse" />
|
|
120
|
-
<span className="text-[12px] font-body text-accent-green">Waiting for next revision...</span>
|
|
121
|
-
</div>
|
|
122
|
-
)}
|
|
138
|
+
{waitingForUpdate && <WaitingBanner feedbackConsumed={feedbackConsumed} agentWatching={agentWatching} />}
|
|
123
139
|
|
|
124
140
|
{activeTab === "feedback" ? (
|
|
125
141
|
<FeedbackDisplayContent sessionId={sessionId} revision={revision} />
|
|
@@ -287,7 +303,7 @@ function ReadOnlyAnnotationList({ annotations, generalNote, activeAnnotationId,
|
|
|
287
303
|
);
|
|
288
304
|
}
|
|
289
305
|
|
|
290
|
-
function AnnotationSidebarInner({ onPreview, onSubmit, collapseButton }: AnnotationSidebarProps) {
|
|
306
|
+
function AnnotationSidebarInner({ onPreview, onSubmit, agentWatching, collapseButton }: AnnotationSidebarProps & { agentWatching: boolean }) {
|
|
291
307
|
const {
|
|
292
308
|
annotations, updateAnnotation, removeAnnotation,
|
|
293
309
|
addAnnotationImage, removeAnnotationImage,
|
|
@@ -422,6 +438,10 @@ function AnnotationSidebarInner({ onPreview, onSubmit, collapseButton }: Annotat
|
|
|
422
438
|
{annotations.length > 0 && (
|
|
423
439
|
<span className="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-border-subtle text-[10px] font-medium text-text-secondary">{annotations.length}</span>
|
|
424
440
|
)}
|
|
441
|
+
<span
|
|
442
|
+
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${agentWatching ? "bg-accent-green" : "bg-accent-amber"}`}
|
|
443
|
+
title={agentWatching ? "Agent connected" : "Agent disconnected"}
|
|
444
|
+
/>
|
|
425
445
|
</span>
|
|
426
446
|
{collapseButton}
|
|
427
447
|
</div>
|
package/daemon/client/App.tsx
CHANGED
|
@@ -32,6 +32,7 @@ export interface RevisionInfo {
|
|
|
32
32
|
canvasFiles: CanvasFileInfo[];
|
|
33
33
|
createdAt: string;
|
|
34
34
|
hasFeedback: boolean;
|
|
35
|
+
feedbackConsumed: boolean;
|
|
35
36
|
response?: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -57,6 +58,7 @@ export const RevisionContext = createContext<{
|
|
|
57
58
|
isReadOnly: boolean;
|
|
58
59
|
compareRevision: { left: number; right: number } | null;
|
|
59
60
|
setCompareRevision: (rev: { left: number; right: number } | null) => void;
|
|
61
|
+
agentWatching: boolean;
|
|
60
62
|
}>({
|
|
61
63
|
currentRevision: 1,
|
|
62
64
|
selectedRevision: 1,
|
|
@@ -65,6 +67,7 @@ export const RevisionContext = createContext<{
|
|
|
65
67
|
isReadOnly: false,
|
|
66
68
|
compareRevision: null,
|
|
67
69
|
setCompareRevision: () => {},
|
|
70
|
+
agentWatching: false,
|
|
68
71
|
});
|
|
69
72
|
|
|
70
73
|
function resolveTheme(pref: string): "light" | "dark" {
|
|
@@ -179,6 +182,7 @@ function App() {
|
|
|
179
182
|
const [revisions, setRevisions] = useState<RevisionInfo[]>([]);
|
|
180
183
|
const [compareRevision, setCompareRevision] = useState<{ left: number; right: number } | null>(null);
|
|
181
184
|
const [connected, setConnected] = useState(false);
|
|
185
|
+
const [agentWatching, setAgentWatching] = useState(false);
|
|
182
186
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
183
187
|
const [canvasFiles, setCanvasFiles] = useState<string[]>([]);
|
|
184
188
|
const [activeView, setActiveViewRaw] = useState<ActiveView>({ type: "overview" });
|
|
@@ -314,6 +318,9 @@ function App() {
|
|
|
314
318
|
if (data.type === "revision-updated") {
|
|
315
319
|
if (data.revisions) setRevisions(data.revisions);
|
|
316
320
|
}
|
|
321
|
+
if (data.type === "watcher-status") {
|
|
322
|
+
setAgentWatching(!!data.watching);
|
|
323
|
+
}
|
|
317
324
|
} catch {}
|
|
318
325
|
};
|
|
319
326
|
};
|
|
@@ -336,7 +343,7 @@ function App() {
|
|
|
336
343
|
|
|
337
344
|
return (
|
|
338
345
|
<SessionContext.Provider value={sessionId}>
|
|
339
|
-
<RevisionContext.Provider value={{ currentRevision, selectedRevision, revisions, setSelectedRevision, isReadOnly, compareRevision, setCompareRevision }}>
|
|
346
|
+
<RevisionContext.Provider value={{ currentRevision, selectedRevision, revisions, setSelectedRevision, isReadOnly, compareRevision, setCompareRevision, agentWatching }}>
|
|
340
347
|
<AnnotationProvider key={`${sessionId}:${selectedRevision}`} sessionId={sessionId} revision={selectedRevision} isReadOnly={isReadOnly}>
|
|
341
348
|
<ActiveViewContext.Provider value={{ activeView, setActiveView, openFiles, closeFile, canvasFiles }}>
|
|
342
349
|
<ActiveViewCtx.Provider value={{ setActiveView }}>
|
|
@@ -250,7 +250,9 @@ function processBlockWithChildren(
|
|
|
250
250
|
} else if (cm.kind === "removed") {
|
|
251
251
|
hasChanges = true;
|
|
252
252
|
const ghost = ghostBlock(cm.oldBlock!.element);
|
|
253
|
-
const
|
|
253
|
+
const refNode = findInsertionPoint(childMatches, cm, cloneMap.newMap);
|
|
254
|
+
// refNode may be deeply nested — walk up to find a direct child of the container
|
|
255
|
+
const insertBefore = refNode ? directChildOf(newContainer, refNode) : null;
|
|
254
256
|
newContainer.insertBefore(ghost, insertBefore);
|
|
255
257
|
} else if (cm.kind === "added") {
|
|
256
258
|
hasChanges = true;
|
|
@@ -374,6 +376,15 @@ function findInsertionPoint(
|
|
|
374
376
|
return null;
|
|
375
377
|
}
|
|
376
378
|
|
|
379
|
+
/** Walk up from `el` to find the ancestor that is a direct child of `container`. */
|
|
380
|
+
function directChildOf(container: HTMLElement, el: HTMLElement): HTMLElement | null {
|
|
381
|
+
let node: HTMLElement | null = el;
|
|
382
|
+
while (node && node.parentElement !== container) {
|
|
383
|
+
node = node.parentElement;
|
|
384
|
+
}
|
|
385
|
+
return node;
|
|
386
|
+
}
|
|
387
|
+
|
|
377
388
|
/* ── Element-level diff for blocks with rich internal structure (e.g. markdown) ── */
|
|
378
389
|
|
|
379
390
|
/**
|