pmx-canvas 0.7.2 → 0.8.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/.github/extensions/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/.github/extensions/pmx-canvas/extension.mjs +25 -172
- package/.github/extensions/pmx-canvas/mcp-bootstrap.ts +198 -0
- package/.github/extensions/pmx-canvas/runtime.d.mts +41 -0
- package/.github/extensions/pmx-canvas/runtime.json +3 -0
- package/.github/extensions/pmx-canvas/runtime.mjs +194 -0
- package/.github/extensions/pmx-canvas/start-mcp.mjs +51 -0
- package/.github/extensions/pmx-canvas/tools.json +2560 -0
- package/CHANGELOG.md +60 -0
- package/Readme.md +44 -16
- package/dist/canvas/fonts/OFL.txt +93 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
- package/dist/canvas/fonts/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
- package/dist/canvas/global.css +844 -710
- package/dist/canvas/index.js +92 -90
- package/dist/canvas/mcp-app.html +7878 -0
- package/dist/canvas/mermaid-entry.js +1 -1
- package/dist/canvas/surface-theme.css +151 -46
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +55 -55
- package/dist/types/cli/commands/record.d.ts +1 -1
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +24 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +3 -1
- package/dist/types/client/icons.d.ts +1 -2
- package/dist/types/client/mcp-app/index.d.ts +1 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/ax-node-actions.d.ts +2 -2
- package/dist/types/client/nodes/markdown-sanitize.d.ts +2 -0
- package/dist/types/client/nodes/use-workbench-asset.d.ts +2 -0
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/attention-store.d.ts +0 -5
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/context-status-store.d.ts +53 -0
- package/dist/types/client/state/intent-bridge.d.ts +4 -1
- package/dist/types/client/state/near-pin-store.d.ts +16 -0
- package/dist/types/client/state/presence-store.d.ts +5 -0
- package/dist/types/client/state/session-store.d.ts +33 -3
- package/dist/types/client/state/workbench-transport.d.ts +16 -0
- package/dist/types/client/theme/tokens.d.ts +1 -3
- package/dist/types/client/types.d.ts +9 -0
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +3 -1
- package/dist/types/mcp/app-server.d.ts +3 -0
- package/dist/types/mcp/canvas-access.d.ts +1 -1
- package/dist/types/mcp/canvas-app.d.ts +4 -0
- package/dist/types/mcp/workbench-app.d.ts +4 -0
- package/dist/types/server/agent-presence.d.ts +1 -1
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +8 -0
- package/dist/types/server/canvas-geometry.d.ts +25 -0
- package/dist/types/server/canvas-operations.d.ts +1 -11
- package/dist/types/server/canvas-state.d.ts +25 -3
- package/dist/types/server/context-brief.d.ts +8 -1
- package/dist/types/server/context-reads.d.ts +30 -7
- package/dist/types/server/index.d.ts +6 -1
- package/dist/types/server/mcp-app-runtime.d.ts +1 -0
- package/dist/types/server/operations/http.d.ts +2 -0
- package/dist/types/server/operations/invoker.d.ts +4 -2
- package/dist/types/server/operations/ops/nodes.d.ts +0 -6
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/spatial-analysis.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +22 -0
- package/dist/types/shared/agent-presence.d.ts +3 -10
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/dist/types/shared/canvas-app.d.ts +54 -0
- package/dist/types/shared/themes.d.ts +1 -1
- package/docs/RELEASE.md +7 -3
- package/docs/agent-plugin.md +123 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/docs/ax-state-contract.md +40 -0
- package/docs/cli.md +7 -2
- package/docs/design/AgentContext.dc.html +186 -0
- package/docs/design/Before.dc.html +208 -0
- package/docs/design/BoardPins.dc.html +151 -0
- package/docs/design/Chrome.dc.html +208 -0
- package/docs/design/Context.dc.html +239 -0
- package/docs/design/Flows.dc.html +181 -0
- package/docs/design/Home.dc.html +209 -0
- package/docs/design/HomeGraph.dc.html +211 -0
- package/docs/design/HomeMap.dc.html +117 -0
- package/docs/design/HomeTeam.dc.html +176 -0
- package/docs/design/Light.dc.html +28 -0
- package/docs/design/Main.dc.html +280 -0
- package/docs/design/NearPin.dc.html +260 -0
- package/docs/design/Pane600.dc.html +120 -0
- package/docs/design/Relations.dc.html +297 -0
- package/docs/design/Themes.dc.html +121 -0
- package/docs/design/TokenSystem.dc.html +136 -0
- package/docs/design/Tokens.dc.html +163 -0
- package/docs/design/Types1.dc.html +177 -0
- package/docs/design/Types2.dc.html +226 -0
- package/docs/design/Types3.dc.html +195 -0
- package/docs/design/Uses.dc.html +152 -0
- package/docs/design/Vision.dc.html +116 -0
- package/docs/design/Zoom.dc.html +82 -0
- package/docs/design/canvas.json +274 -0
- package/docs/design.md +214 -0
- package/docs/environment.md +1 -1
- package/docs/http-api.md +80 -15
- package/docs/mcp.md +198 -3
- package/docs/node-types.md +34 -10
- package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
- package/docs/plans/plan-015-design-foundation-wave-0.md +177 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +148 -0
- package/docs/product-vision-2026-09.md +47 -7
- package/docs/screenshot.png +0 -0
- package/docs/sdk.md +1 -0
- package/package.json +10 -4
- package/plugins/pmx-canvas/LICENSE +21 -0
- package/plugins/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +930 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/plugins/pmx-canvas/mcp.json +12 -0
- package/plugins/pmx-canvas/plugin.json +24 -0
- package/plugins/pmx-canvas/runtime.json +3 -0
- package/plugins/pmx-canvas/runtime.mjs +194 -0
- package/plugins/pmx-canvas/start-mcp.mjs +51 -0
- package/plugins/pmx-canvas/tools.json +2560 -0
- package/skills/pmx-canvas/SKILL.md +31 -16
- package/skills/pmx-canvas/evals/evals.json +69 -0
- package/skills/pmx-canvas/references/full-reference.md +14 -10
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +20 -8
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +54 -4
- package/src/cli/commands/copilot.ts +5 -0
- package/src/cli/commands/view.ts +1 -1
- package/src/cli/index.ts +8 -3
- package/src/client/App.tsx +6 -1
- package/src/client/canvas/AgentPresenceLayer.tsx +3 -3
- package/src/client/canvas/AnnotationLayer.tsx +1 -1
- package/src/client/canvas/CanvasNode.tsx +208 -220
- package/src/client/canvas/CanvasViewport.tsx +6 -2
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +25 -23
- package/src/client/canvas/CommandPalette.tsx +3 -2
- package/src/client/canvas/ContextMenu.tsx +11 -0
- package/src/client/canvas/EdgeLayer.tsx +1 -1
- package/src/client/canvas/ExportDialog.tsx +33 -6
- package/src/client/canvas/FeedbackDialog.tsx +10 -8
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/Minimap.tsx +12 -7
- package/src/client/canvas/NodeContextMark.tsx +156 -0
- package/src/client/canvas/SessionPanel.tsx +4 -7
- package/src/client/canvas/SessionReceipt.tsx +114 -17
- package/src/client/canvas/ShortcutOverlay.tsx +6 -1
- package/src/client/canvas/SnapshotPanel.tsx +11 -2
- package/src/client/canvas/ToolRail.tsx +58 -108
- package/src/client/canvas/TopBar.tsx +16 -11
- package/src/client/icons.tsx +2 -12
- package/src/client/mcp-app/index.tsx +137 -0
- package/src/client/mcp-app/style.css +10 -0
- package/src/client/nodes/AttachmentNode.tsx +8 -1
- package/src/client/nodes/AxStepControls.tsx +1 -1
- package/src/client/nodes/ContextNode.tsx +9 -9
- package/src/client/nodes/ExtAppFrame.tsx +13 -8
- package/src/client/nodes/FileNode.tsx +8 -5
- package/src/client/nodes/ImageNode.tsx +4 -2
- package/src/client/nodes/InlineMarkdownEditor.tsx +2 -1
- package/src/client/nodes/LedgerNode.tsx +2 -2
- package/src/client/nodes/MarkdownNode.tsx +44 -9
- package/src/client/nodes/McpAppNode.tsx +22 -4
- package/src/client/nodes/PromptNode.tsx +5 -17
- package/src/client/nodes/ResponseNode.tsx +3 -15
- package/src/client/nodes/StatusNode.tsx +2 -2
- package/src/client/nodes/StatusSummary.tsx +3 -1
- package/src/client/nodes/TraceNode.tsx +6 -4
- package/src/client/nodes/WebpageNode.tsx +1 -1
- package/src/client/nodes/ax-node-actions.ts +2 -2
- package/src/client/nodes/iframe-document-url.ts +3 -2
- package/src/client/nodes/markdown-sanitize.ts +10 -0
- package/src/client/nodes/surface-url.ts +10 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/nodes/use-workbench-asset.ts +26 -0
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/attention-bridge.ts +0 -8
- package/src/client/state/attention-store.ts +0 -17
- package/src/client/state/canvas-store.ts +7 -2
- package/src/client/state/context-status-store.ts +66 -0
- package/src/client/state/human-store.ts +2 -0
- package/src/client/state/iframe-mode.ts +5 -0
- package/src/client/state/intent-bridge.ts +24 -6
- package/src/client/state/near-pin-store.ts +53 -0
- package/src/client/state/presence-store.ts +5 -10
- package/src/client/state/session-store.ts +106 -9
- package/src/client/state/sse-bridge.ts +26 -7
- package/src/client/state/theme-override.ts +1 -1
- package/src/client/state/workbench-transport.ts +36 -0
- package/src/client/theme/global.css +844 -710
- package/src/client/theme/surface-theme.css +151 -46
- package/src/client/theme/tokens.ts +7 -11
- package/src/client/types.ts +10 -0
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +79 -15
- package/src/mcp/app-server.ts +64 -0
- package/src/mcp/canvas-access.ts +14 -11
- package/src/mcp/canvas-app.ts +318 -0
- package/src/mcp/server.ts +1 -1
- package/src/mcp/workbench-app.ts +211 -0
- package/src/server/agent-presence.ts +45 -6
- package/src/server/ax-context.ts +1 -1
- package/src/server/ax-state.ts +1 -1
- package/src/server/board-export.ts +5 -2
- package/src/server/canvas-db.ts +46 -12
- package/src/server/canvas-geometry.ts +64 -0
- package/src/server/canvas-operations.ts +6 -24
- package/src/server/canvas-state.ts +105 -15
- package/src/server/canvas-validation.ts +19 -4
- package/src/server/context-brief.ts +39 -2
- package/src/server/context-reads.ts +86 -15
- package/src/server/demo-state.json +31 -31
- package/src/server/html-surface.ts +4 -5
- package/src/server/index.ts +19 -3
- package/src/server/mcp-app-runtime.ts +11 -0
- package/src/server/operations/composites.ts +4 -3
- package/src/server/operations/http.ts +4 -4
- package/src/server/operations/invoker.ts +13 -4
- package/src/server/operations/ops/ax-read.ts +1 -1
- package/src/server/operations/ops/ax-timeline.ts +37 -0
- package/src/server/operations/ops/boards.ts +1 -1
- package/src/server/operations/ops/canvas-wire.ts +3 -6
- package/src/server/operations/ops/intent.ts +31 -2
- package/src/server/operations/ops/json-render.ts +1 -1
- package/src/server/operations/ops/nodes.ts +8 -76
- package/src/server/operations/ops/query.ts +8 -2
- package/src/server/operations/ops/snapshots.ts +4 -1
- package/src/server/operations/ops/viewport.ts +29 -5
- package/src/server/operations/registry.ts +38 -4
- package/src/server/scope-fence.ts +55 -10
- package/src/server/server.ts +86 -6
- package/src/server/spatial-analysis.ts +2 -2
- package/src/server/workboard.ts +6 -1
- package/src/{server → shared}/agent-context.ts +2 -2
- package/src/shared/agent-presence.ts +3 -15
- package/src/shared/ax-surface-protocol.ts +3 -0
- package/src/shared/canvas-app.ts +62 -0
- package/src/shared/themes.ts +5 -5
- package/src/client/canvas/FocusFieldLayer.tsx +0 -116
- package/src/client/canvas/kind-colors.ts +0 -23
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import { useState } from 'preact/hooks';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
+
import { toggleContextPin } from '../state/canvas-store';
|
|
3
|
+
import {
|
|
4
|
+
activityLensNodeIds,
|
|
5
|
+
dismissSessionReceipt,
|
|
6
|
+
type ReceiptNode,
|
|
7
|
+
type SessionContextActivity,
|
|
8
|
+
sessionReceipt,
|
|
9
|
+
setActivityLens,
|
|
10
|
+
} from '../state/session-store';
|
|
11
|
+
import { IconClose, IconPin } from '../icons';
|
|
12
|
+
import { GLYPHS } from './NodeContextMark';
|
|
13
|
+
import { workbenchFetch } from '../state/workbench-transport';
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* Session receipt (rail-chrome-v2 phase 5, design item 2): a dismissible card
|
|
7
17
|
* at the canvas region's top-right after a session ends — what the session did
|
|
8
18
|
* (items / done / vetoed), the pre-session snapshot (taken at attach, so View
|
|
9
19
|
* diff shows the session's changes and a restore undoes them), and History
|
|
10
|
-
* (the snapshots panel). Client-side state, cleared on dismiss
|
|
20
|
+
* (the snapshots panel). Client-side state, cleared on dismiss (see
|
|
21
|
+
* applySessionReceipt for which endings show at all and how a burst merges
|
|
22
|
+
* into one card).
|
|
11
23
|
*/
|
|
12
24
|
|
|
13
25
|
export interface DiffSummary {
|
|
@@ -28,17 +40,96 @@ export function summarizeDiff(diff: unknown): DiffSummary | null {
|
|
|
28
40
|
};
|
|
29
41
|
}
|
|
30
42
|
|
|
43
|
+
const titles = (nodes: ReceiptNode[]) => nodes.map((node) => node.title).join(', ');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* What the session did with context (docs/design/AgentContext.dc.html): an
|
|
47
|
+
* amber line for pins it read that changed since, one row per kind of touch,
|
|
48
|
+
* and the lens switch that dims every node the session did not touch.
|
|
49
|
+
*/
|
|
50
|
+
function ReceiptContext({ context, label }: { context: SessionContextActivity; label: string }) {
|
|
51
|
+
const rows: Array<{ key: keyof SessionContextActivity; name: string; glyph: keyof typeof GLYPHS | 'pin' }> = [
|
|
52
|
+
{ key: 'read', name: 'Read', glyph: 'eye' },
|
|
53
|
+
{ key: 'pinned', name: 'Pinned', glyph: 'pin' },
|
|
54
|
+
{ key: 'created', name: 'Created', glyph: 'spark' },
|
|
55
|
+
{ key: 'edited', name: 'Edited', glyph: 'pen' },
|
|
56
|
+
];
|
|
57
|
+
const present = rows.filter((row) => context[row.key].length > 0);
|
|
58
|
+
if (present.length === 0 && context.changedSinceRead.length === 0) return null;
|
|
59
|
+
const lensOn = activityLensNodeIds.value !== null;
|
|
60
|
+
return (
|
|
61
|
+
<div class="session-receipt-context" data-testid="session-receipt-context">
|
|
62
|
+
{context.changedSinceRead.length > 0 && (
|
|
63
|
+
<div class="session-receipt-warn">
|
|
64
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
65
|
+
<path d={GLYPHS.warn} />
|
|
66
|
+
</svg>
|
|
67
|
+
<span>
|
|
68
|
+
{titles(context.changedSinceRead)} changed after {label} read{' '}
|
|
69
|
+
{context.changedSinceRead.length === 1 ? 'it' : 'them'}.
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
{present.map((row) => (
|
|
74
|
+
<div key={row.key} class="session-receipt-context-row" data-row={row.key}>
|
|
75
|
+
<div class="session-receipt-context-head">
|
|
76
|
+
{row.glyph === 'pin' ? (
|
|
77
|
+
<IconPin />
|
|
78
|
+
) : (
|
|
79
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
80
|
+
<path d={GLYPHS[row.glyph]} />
|
|
81
|
+
</svg>
|
|
82
|
+
)}
|
|
83
|
+
<span class="session-receipt-context-name">{row.name}</span>
|
|
84
|
+
<span class="session-receipt-context-count">{context[row.key].length}</span>
|
|
85
|
+
{row.key === 'pinned' && (
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="session-receipt-mini"
|
|
89
|
+
onClick={() => {
|
|
90
|
+
for (const node of context.pinned) toggleContextPin(node.id);
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
Unpin
|
|
94
|
+
</button>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
<div class="session-receipt-context-items">
|
|
98
|
+
{row.key === 'pinned'
|
|
99
|
+
? context.pinned.map((node) => (node.reason ? `${node.title} — “${node.reason}”` : node.title)).join('; ')
|
|
100
|
+
: titles(context[row.key])}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
))}
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
role="switch"
|
|
107
|
+
aria-checked={lensOn}
|
|
108
|
+
class={`session-receipt-lens${lensOn ? ' is-on' : ''}`}
|
|
109
|
+
onClick={() => setActivityLens(!lensOn)}
|
|
110
|
+
>
|
|
111
|
+
<span class="session-receipt-lens-track" aria-hidden="true">
|
|
112
|
+
<span class="session-receipt-lens-knob" />
|
|
113
|
+
</span>
|
|
114
|
+
Dim untouched nodes
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
31
120
|
export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => void }) {
|
|
32
121
|
const receipt = sessionReceipt.value;
|
|
33
122
|
const [diff, setDiff] = useState<DiffSummary | null>(null);
|
|
34
123
|
const [loadingDiff, setLoadingDiff] = useState(false);
|
|
124
|
+
// A new or merged receipt: the old diff no longer describes it.
|
|
125
|
+
useEffect(() => setDiff(null), [receipt]);
|
|
35
126
|
if (!receipt) return null;
|
|
36
127
|
|
|
37
128
|
const viewDiff = async () => {
|
|
38
129
|
if (!receipt.snapshot) return;
|
|
39
130
|
setLoadingDiff(true);
|
|
40
131
|
try {
|
|
41
|
-
const response = await
|
|
132
|
+
const response = await workbenchFetch(`/api/canvas/snapshots/${encodeURIComponent(receipt.snapshot.id)}/diff`, {
|
|
42
133
|
headers: { 'x-pmx-workbench': '1' },
|
|
43
134
|
});
|
|
44
135
|
if (response.ok) {
|
|
@@ -60,13 +151,15 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
60
151
|
<div class="session-receipt-head">
|
|
61
152
|
<span class="session-receipt-dot" aria-hidden="true" />
|
|
62
153
|
<span class="session-receipt-title">
|
|
63
|
-
{receipt.
|
|
64
|
-
?
|
|
65
|
-
: receipt.endedBy === '
|
|
66
|
-
? 'Session ended
|
|
67
|
-
: receipt.endedBy === '
|
|
68
|
-
? 'Session ended
|
|
69
|
-
:
|
|
154
|
+
{receipt.sessions > 1
|
|
155
|
+
? `${receipt.sessions} sessions ended`
|
|
156
|
+
: receipt.endedBy === 'human'
|
|
157
|
+
? 'Session ended by you'
|
|
158
|
+
: receipt.endedBy === 'idle-timeout'
|
|
159
|
+
? 'Session ended — idle timeout'
|
|
160
|
+
: receipt.endedBy === 'agent'
|
|
161
|
+
? 'Session ended by the agent'
|
|
162
|
+
: 'Session ended'}
|
|
70
163
|
{endedLabel ? ` · ${endedLabel}` : ''}
|
|
71
164
|
</span>
|
|
72
165
|
<button
|
|
@@ -78,6 +171,7 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
78
171
|
<IconClose />
|
|
79
172
|
</button>
|
|
80
173
|
</div>
|
|
174
|
+
{receipt.sessions > 1 && <div class="session-receipt-who">{receipt.labels.join(', ')}</div>}
|
|
81
175
|
<div class="session-receipt-tiles">
|
|
82
176
|
<div class="session-receipt-tile">
|
|
83
177
|
<span class="session-receipt-tile-label">Items</span>
|
|
@@ -108,12 +202,15 @@ export function SessionReceipt({ onOpenSnapshots }: { onOpenSnapshots: () => voi
|
|
|
108
202
|
</div>
|
|
109
203
|
)}
|
|
110
204
|
</div>
|
|
205
|
+
<ReceiptContext context={receipt.context} label={receipt.labels.join(', ')} />
|
|
111
206
|
<div class="session-receipt-note">
|
|
112
|
-
{receipt.
|
|
113
|
-
? '
|
|
114
|
-
: receipt.
|
|
115
|
-
? '
|
|
116
|
-
:
|
|
207
|
+
{receipt.sessions > 1
|
|
208
|
+
? 'History has each session’s diff and the snapshot from before it.'
|
|
209
|
+
: receipt.snapshot
|
|
210
|
+
? 'A snapshot of the board from before this session is saved — restore it to undo the session.'
|
|
211
|
+
: receipt.context.created.length === 0 && receipt.context.edited.length === 0
|
|
212
|
+
? 'The board itself did not change — nothing to restore.'
|
|
213
|
+
: 'The board was empty when the session started — nothing to restore.'}
|
|
117
214
|
</div>
|
|
118
215
|
{diff && (
|
|
119
216
|
<div class="session-receipt-diff" data-testid="session-receipt-diff">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isHostedWorkbench } from '../state/workbench-transport';
|
|
1
2
|
import { isMac, modChord } from '../utils/platform';
|
|
2
3
|
|
|
3
4
|
interface ShortcutGroup {
|
|
@@ -74,7 +75,11 @@ export function ShortcutOverlay({ onClose }: { onClose: () => void }) {
|
|
|
74
75
|
{group.shortcuts.map((s) => (
|
|
75
76
|
<div key={s.keys} class="shortcut-row">
|
|
76
77
|
<kbd class="shortcut-keys">{s.keys}</kbd>
|
|
77
|
-
<span class="shortcut-desc">
|
|
78
|
+
<span class="shortcut-desc">
|
|
79
|
+
{isHostedWorkbench() && s.desc.startsWith('Undo / redo')
|
|
80
|
+
? 'Undo / redo — available in the local workbench only'
|
|
81
|
+
: s.desc}
|
|
82
|
+
</span>
|
|
78
83
|
</div>
|
|
79
84
|
))}
|
|
80
85
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isHostedWorkbench } from '../state/workbench-transport';
|
|
1
2
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
2
3
|
import { IconClose } from '../icons';
|
|
3
4
|
import {
|
|
@@ -171,7 +172,11 @@ export function SnapshotPanel({
|
|
|
171
172
|
type="button"
|
|
172
173
|
class="snapshot-action-btn snapshot-action-restore"
|
|
173
174
|
onClick={() => setConfirming({ id: snap.id, action: 'restore' })}
|
|
174
|
-
title=
|
|
175
|
+
title={
|
|
176
|
+
isHostedWorkbench()
|
|
177
|
+
? 'Restore this snapshot — replaces the current canvas'
|
|
178
|
+
: 'Restore this snapshot — replaces the current canvas (undoable)'
|
|
179
|
+
}
|
|
175
180
|
disabled={restoringId !== null}
|
|
176
181
|
>
|
|
177
182
|
{restoringId === snap.id ? 'Restoring…' : restoreLabel}
|
|
@@ -219,7 +224,11 @@ export function SnapshotPanel({
|
|
|
219
224
|
{saving ? '…' : 'Save'}
|
|
220
225
|
</button>
|
|
221
226
|
</div>
|
|
222
|
-
<div class="snapshot-restore-note">
|
|
227
|
+
<div class="snapshot-restore-note">
|
|
228
|
+
{isHostedWorkbench()
|
|
229
|
+
? 'Restoring replaces the current canvas. Save a snapshot first to keep your current state.'
|
|
230
|
+
: 'Restoring replaces the current canvas. You can undo it if needed.'}
|
|
231
|
+
</div>
|
|
223
232
|
|
|
224
233
|
<div class="snapshot-list">
|
|
225
234
|
{loading && <div class="snapshot-empty">Loading…</div>}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { isHostedWorkbench, workbenchFetch } from '../state/workbench-transport';
|
|
2
3
|
import {
|
|
3
4
|
IconArrange,
|
|
4
5
|
IconClearTrace,
|
|
@@ -8,10 +9,7 @@ import {
|
|
|
8
9
|
IconLogo,
|
|
9
10
|
IconMinimap,
|
|
10
11
|
IconMoon,
|
|
11
|
-
IconNodeFile,
|
|
12
12
|
IconNodeGroup,
|
|
13
|
-
IconNodeHtml,
|
|
14
|
-
IconNodeImage,
|
|
15
13
|
IconNodeMarkdown,
|
|
16
14
|
IconNodeWebpage,
|
|
17
15
|
IconPen,
|
|
@@ -177,37 +175,33 @@ export function ToolRail({
|
|
|
177
175
|
const traceNodeCount = Array.from(nodes.value.values()).filter((n) => n.type === 'trace').length;
|
|
178
176
|
const edgeCount = edges.value.size;
|
|
179
177
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
|
180
|
-
const [openMenu, setOpenMenu] = useState<null | 'theme'
|
|
181
|
-
const [menuAnchor, setMenuAnchor] = useState<{
|
|
178
|
+
const [openMenu, setOpenMenu] = useState<null | 'theme'>(null);
|
|
179
|
+
const [menuAnchor, setMenuAnchor] = useState<{ bottom: number; right: number } | null>(null);
|
|
182
180
|
const railRef = useRef<HTMLDivElement>(null);
|
|
183
|
-
const
|
|
181
|
+
const fileInput = useRef<HTMLInputElement>(null);
|
|
184
182
|
|
|
185
|
-
const toggleMenu = (menu: 'theme'
|
|
183
|
+
const toggleMenu = (menu: 'theme') => (e: MouseEvent) => {
|
|
186
184
|
if (openMenu === menu) {
|
|
187
185
|
setOpenMenu(null);
|
|
188
186
|
return;
|
|
189
187
|
}
|
|
190
188
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
191
|
-
setMenuAnchor({
|
|
189
|
+
setMenuAnchor({ bottom: rect.bottom, right: rect.right });
|
|
192
190
|
setOpenMenu(menu);
|
|
193
191
|
};
|
|
194
192
|
|
|
195
193
|
// The rail scrolls (overflow-y:auto), so an absolutely-positioned popover
|
|
196
194
|
// would be clipped by it. Fixed positioning from the trigger's rect escapes
|
|
197
|
-
// the scroll container; the
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
right: 'auto',
|
|
208
|
-
}
|
|
209
|
-
: { position: 'fixed' as const, left: `${menuAnchor.right + 8}px`, top: `${menuAnchor.top}px`, right: 'auto' }
|
|
210
|
-
: undefined;
|
|
195
|
+
// the scroll container; the theme menu bottom-aligns so it grows upward.
|
|
196
|
+
const sideMenuStyle = menuAnchor
|
|
197
|
+
? {
|
|
198
|
+
position: 'fixed' as const,
|
|
199
|
+
left: `${menuAnchor.right + 8}px`,
|
|
200
|
+
bottom: `${window.innerHeight - menuAnchor.bottom}px`,
|
|
201
|
+
top: 'auto',
|
|
202
|
+
right: 'auto',
|
|
203
|
+
}
|
|
204
|
+
: undefined;
|
|
211
205
|
|
|
212
206
|
useEffect(() => {
|
|
213
207
|
if (!openMenu) return;
|
|
@@ -236,7 +230,7 @@ export function ToolRail({
|
|
|
236
230
|
const activeTheme = normalizeCanvasThemeName(canvasTheme.value);
|
|
237
231
|
|
|
238
232
|
const sendIntent = (type: string, payload: Record<string, unknown> = {}) => {
|
|
239
|
-
|
|
233
|
+
workbenchFetch(`/api/workbench/intent?_ts=${Date.now()}`, {
|
|
240
234
|
method: 'POST',
|
|
241
235
|
headers: { 'Content-Type': 'application/json' },
|
|
242
236
|
body: JSON.stringify({ type, payload }),
|
|
@@ -331,22 +325,20 @@ export function ToolRail({
|
|
|
331
325
|
>
|
|
332
326
|
<IconNodeMarkdown size={15} />
|
|
333
327
|
</RailButton>
|
|
334
|
-
<RailButton
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
<RailButton label="Attach document" onClick={() => documentInput.current?.click()}>
|
|
328
|
+
<RailButton
|
|
329
|
+
label="Attach files"
|
|
330
|
+
ariaLabel="Attach files"
|
|
331
|
+
detail="Images (PNG, JPEG, SVG…), Markdown and text/code files. PDF, Word, Excel, PowerPoint and OpenDocument use agent import with review."
|
|
332
|
+
onClick={() => fileInput.current?.click()}
|
|
333
|
+
>
|
|
341
334
|
<IconUpload size={15} />
|
|
342
335
|
</RailButton>
|
|
343
336
|
<input
|
|
344
|
-
ref={
|
|
337
|
+
ref={fileInput}
|
|
345
338
|
type="file"
|
|
346
339
|
hidden
|
|
347
340
|
multiple
|
|
348
|
-
|
|
349
|
-
aria-label="Attach documents"
|
|
341
|
+
aria-label="Attach files"
|
|
350
342
|
onChange={(event) => {
|
|
351
343
|
const area = canvasArea();
|
|
352
344
|
const view = viewport.value;
|
|
@@ -361,17 +353,6 @@ export function ToolRail({
|
|
|
361
353
|
<RailButton label="Webpage" shortcut="W" onClick={() => promptedCreate('webpage')}>
|
|
362
354
|
<IconNodeWebpage size={15} />
|
|
363
355
|
</RailButton>
|
|
364
|
-
<RailButton
|
|
365
|
-
label="HTML surface"
|
|
366
|
-
shortcut="H"
|
|
367
|
-
onClick={() =>
|
|
368
|
-
void createNodeInView({ type: 'html', title: 'HTML surface' }).catch((error) =>
|
|
369
|
-
logRailError('create html', error),
|
|
370
|
-
)
|
|
371
|
-
}
|
|
372
|
-
>
|
|
373
|
-
<IconNodeHtml />
|
|
374
|
-
</RailButton>
|
|
375
356
|
<RailButton
|
|
376
357
|
label="Group"
|
|
377
358
|
shortcut="G"
|
|
@@ -383,61 +364,28 @@ export function ToolRail({
|
|
|
383
364
|
>
|
|
384
365
|
<IconNodeGroup size={15} />
|
|
385
366
|
</RailButton>
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
{
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
onClick={() => {
|
|
409
|
-
onSetAnnotationTool(annotationTool === 'pen' ? null : 'pen');
|
|
410
|
-
setOpenMenu(null);
|
|
411
|
-
}}
|
|
412
|
-
>
|
|
413
|
-
<IconPen />
|
|
414
|
-
<span>{annotationTool === 'pen' ? 'Stop annotating' : 'Draw (A)'}</span>
|
|
415
|
-
</button>
|
|
416
|
-
<button
|
|
417
|
-
type="button"
|
|
418
|
-
class={`toolbar-menu-item${annotationTool === 'text' ? ' active' : ''}`}
|
|
419
|
-
onClick={() => {
|
|
420
|
-
onSetAnnotationTool(annotationTool === 'text' ? null : 'text');
|
|
421
|
-
setOpenMenu(null);
|
|
422
|
-
}}
|
|
423
|
-
>
|
|
424
|
-
<IconTextAnnotation />
|
|
425
|
-
<span>{annotationTool === 'text' ? 'Stop text notes' : 'Text note'}</span>
|
|
426
|
-
</button>
|
|
427
|
-
<button
|
|
428
|
-
type="button"
|
|
429
|
-
class={`toolbar-menu-item${annotationTool === 'eraser' ? ' active' : ''}`}
|
|
430
|
-
onClick={() => {
|
|
431
|
-
onSetAnnotationTool(annotationTool === 'eraser' ? null : 'eraser');
|
|
432
|
-
setOpenMenu(null);
|
|
433
|
-
}}
|
|
434
|
-
>
|
|
435
|
-
<IconEraser />
|
|
436
|
-
<span>{annotationTool === 'eraser' ? 'Stop erasing' : 'Eraser'}</span>
|
|
437
|
-
</button>
|
|
438
|
-
</div>
|
|
439
|
-
)}
|
|
440
|
-
</span>
|
|
367
|
+
<RailButton
|
|
368
|
+
label="Draw"
|
|
369
|
+
shortcut="A"
|
|
370
|
+
active={annotationTool === 'pen'}
|
|
371
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'pen' ? null : 'pen')}
|
|
372
|
+
>
|
|
373
|
+
<IconPen />
|
|
374
|
+
</RailButton>
|
|
375
|
+
<RailButton
|
|
376
|
+
label="Text note"
|
|
377
|
+
active={annotationTool === 'text'}
|
|
378
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'text' ? null : 'text')}
|
|
379
|
+
>
|
|
380
|
+
<IconTextAnnotation />
|
|
381
|
+
</RailButton>
|
|
382
|
+
<RailButton
|
|
383
|
+
label="Eraser"
|
|
384
|
+
active={annotationTool === 'eraser'}
|
|
385
|
+
onClick={() => onSetAnnotationTool(annotationTool === 'eraser' ? null : 'eraser')}
|
|
386
|
+
>
|
|
387
|
+
<IconEraser />
|
|
388
|
+
</RailButton>
|
|
441
389
|
|
|
442
390
|
<div class="rail-spacer" />
|
|
443
391
|
|
|
@@ -451,14 +399,16 @@ export function ToolRail({
|
|
|
451
399
|
>
|
|
452
400
|
<IconArrange />
|
|
453
401
|
</RailButton>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
402
|
+
{!isHostedWorkbench() && (
|
|
403
|
+
<RailButton
|
|
404
|
+
label={isTraceOn ? 'Disable trace' : 'Enable trace'}
|
|
405
|
+
active={isTraceOn}
|
|
406
|
+
onClick={() => sendIntent('trace-toggle', { enabled: !isTraceOn })}
|
|
407
|
+
>
|
|
408
|
+
<IconTrace />
|
|
409
|
+
</RailButton>
|
|
410
|
+
)}
|
|
411
|
+
{!isHostedWorkbench() && (isTraceOn || traceNodeCount > 0) && (
|
|
462
412
|
<RailButton label="Clear trace" onClick={() => sendIntent('trace-clear')}>
|
|
463
413
|
<IconClearTrace />
|
|
464
414
|
</RailButton>
|
|
@@ -485,7 +435,7 @@ export function ToolRail({
|
|
|
485
435
|
{canvasThemeScheme(activeTheme) === 'dark' ? <IconSun /> : <IconMoon />}
|
|
486
436
|
</RailButton>
|
|
487
437
|
{openMenu === 'theme' && (
|
|
488
|
-
<div class="toolbar-menu" style={sideMenuStyle
|
|
438
|
+
<div class="toolbar-menu" style={sideMenuStyle} role="menu" aria-label="Theme">
|
|
489
439
|
{CANVAS_THEMES.map((name) => (
|
|
490
440
|
<button
|
|
491
441
|
key={name}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exportDialogOpen } from './ExportDialog';
|
|
2
2
|
import { activeBoardId } from '../state/boards-store';
|
|
3
3
|
import { BoardSwitcher } from './BoardSwitcher';
|
|
4
|
+
import { isHostedWorkbench, workbenchFetch } from '../state/workbench-transport';
|
|
4
5
|
import type { ComponentChildren } from 'preact';
|
|
5
6
|
import { presenting } from '../state/presentation';
|
|
6
7
|
import { useEffect, useState } from 'preact/hooks';
|
|
@@ -24,8 +25,7 @@ import { degradedState } from './ConnectionBanner';
|
|
|
24
25
|
import { ExternalWriterIndicator } from './ExternalWriters';
|
|
25
26
|
import { useNow } from './use-now';
|
|
26
27
|
import { agentPhaseLabel } from '../../shared/agent-presence.js';
|
|
27
|
-
import {
|
|
28
|
-
import { activeSession, agentPresences, attachedSessions, contextBudget } from '../state/presence-store';
|
|
28
|
+
import { activeSession, agentPresences, attachedSessions, contextBudget, writerColor } from '../state/presence-store';
|
|
29
29
|
import { endSession, pendingGates, startSession } from '../state/session-store';
|
|
30
30
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
31
31
|
|
|
@@ -56,6 +56,7 @@ function AgentChip() {
|
|
|
56
56
|
!allSessions.some(
|
|
57
57
|
(candidate) =>
|
|
58
58
|
candidate.sessionId !== session.sessionId &&
|
|
59
|
+
!candidate.parentAgentId &&
|
|
59
60
|
(candidate.sessionId === session.parentAgentId || candidate.source === session.parentAgentId),
|
|
60
61
|
),
|
|
61
62
|
);
|
|
@@ -83,12 +84,16 @@ function AgentChip() {
|
|
|
83
84
|
<span class="agent-chips">
|
|
84
85
|
{visible.map((session) => {
|
|
85
86
|
// Fleet roll-up: workers declaring this session as their parent count
|
|
86
|
-
// into its chip instead of growing the bar one chip per worker.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
// into its chip instead of growing the bar one chip per worker. A
|
|
88
|
+
// worker's own chip carries no roll-up: same-source siblings would
|
|
89
|
+
// otherwise match it through `session.source`.
|
|
90
|
+
const workers = session.parentAgentId
|
|
91
|
+
? 0
|
|
92
|
+
: agentPresences.value.filter(
|
|
93
|
+
(presence) =>
|
|
94
|
+
presence.parentAgentId != null &&
|
|
95
|
+
(presence.parentAgentId === session.sessionId || presence.parentAgentId === session.source),
|
|
96
|
+
).length;
|
|
92
97
|
return (
|
|
93
98
|
<BarHint
|
|
94
99
|
key={session.sessionId}
|
|
@@ -99,7 +104,7 @@ function AgentChip() {
|
|
|
99
104
|
<span
|
|
100
105
|
class={`agent-chip phase-${session.phase}`}
|
|
101
106
|
data-phase={session.phase}
|
|
102
|
-
style={{ '--identity-color':
|
|
107
|
+
style={{ '--identity-color': writerColor(session.sessionId) }}
|
|
103
108
|
>
|
|
104
109
|
<span class="agent-chip-dot" aria-hidden="true" />
|
|
105
110
|
<span class="agent-chip-label">{agentPhaseLabel(session)}</span>
|
|
@@ -272,7 +277,7 @@ export function TopBar() {
|
|
|
272
277
|
const [workspaceName, setWorkspaceName] = useState<string>('');
|
|
273
278
|
useEffect(() => {
|
|
274
279
|
let cancelled = false;
|
|
275
|
-
|
|
280
|
+
workbenchFetch('/health')
|
|
276
281
|
.then((r) => (r.ok ? r.json() : null))
|
|
277
282
|
.then((data: { workspace?: string } | null) => {
|
|
278
283
|
if (cancelled || typeof data?.workspace !== 'string' || !data.workspace) return;
|
|
@@ -327,7 +332,7 @@ export function TopBar() {
|
|
|
327
332
|
<GateBadge />
|
|
328
333
|
<ContextBudget />
|
|
329
334
|
<ExternalWriterIndicator />
|
|
330
|
-
<StartSessionButton />
|
|
335
|
+
{!isHostedWorkbench() && <StartSessionButton />}
|
|
331
336
|
|
|
332
337
|
<div class="top-bar-sep" />
|
|
333
338
|
|
package/src/client/icons.tsx
CHANGED
|
@@ -112,14 +112,6 @@ export function IconUndo(p: IconProps): JSX.Element {
|
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
export function IconSparkle(p: IconProps): JSX.Element {
|
|
116
|
-
return (
|
|
117
|
-
<Icon {...p}>
|
|
118
|
-
<path d="m8 1 2 5 5 2-5 2-2 5-2-5-5-2 5-2Z" />
|
|
119
|
-
</Icon>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
115
|
/** Upload into a tray — attach a document. */
|
|
124
116
|
export function IconUpload(p: IconProps): JSX.Element {
|
|
125
117
|
return (
|
|
@@ -326,13 +318,11 @@ export function IconSteer(p: IconProps): JSX.Element {
|
|
|
326
318
|
);
|
|
327
319
|
}
|
|
328
320
|
|
|
329
|
-
/**
|
|
321
|
+
/** Push pin — context pinning (docs/design/Chrome.dc.html). */
|
|
330
322
|
export function IconPin(p: IconProps): JSX.Element {
|
|
331
323
|
return (
|
|
332
324
|
<Icon {...p}>
|
|
333
|
-
<
|
|
334
|
-
<line x1="11" y1="1.5" x2="11" y2="5" />
|
|
335
|
-
<circle cx="11" cy="4.5" r="1.6" fill="currentColor" stroke="none" />
|
|
325
|
+
<path d="M6 2.67h4l-.53 3.66 1.86 2H4.67l1.86-2zM8 8.33v5" />
|
|
336
326
|
</Icon>
|
|
337
327
|
);
|
|
338
328
|
}
|