pmx-canvas 0.10.0 → 0.11.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/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +246 -108
- package/CHANGELOG.md +32 -0
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
- package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
- package/dist/canvas/chunks/index-194m3hc3.js +2 -0
- package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
- package/dist/canvas/chunks/index-353m7b0p.js +2 -0
- package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
- package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
- package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
- package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-nprm96xp.js +16 -0
- package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
- package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
- package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
- package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
- package/dist/canvas/chunks/index-s4j678gj.js +2 -0
- package/dist/canvas/chunks/index-tb1wf525.js +179 -0
- package/dist/canvas/chunks/index-vv56h019.js +2 -0
- package/dist/canvas/chunks/index-w2mra17t.js +207 -0
- package/dist/canvas/chunks/index-wgv7en21.js +232 -0
- package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
- package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
- package/dist/canvas/global.css +389 -229
- package/dist/canvas/index.js +141 -211
- package/dist/canvas/mcp-app.html +4205 -554
- package/dist/canvas/mermaid-entry.js +338 -338
- package/dist/canvas/surface-theme.css +6 -6
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +49 -49
- package/dist/types/client/canvas/BarHint.d.ts +5 -3
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/auto-fit.d.ts +4 -5
- package/dist/types/client/mermaid-entry.d.ts +1 -2
- package/dist/types/client/mermaid-render.d.ts +1 -0
- package/dist/types/client/nodes/BoardNode.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
- package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
- package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
- package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
- package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
- package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +3 -1
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +10 -5
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/Types1.dc.html +3 -2
- package/docs/design/Types2.dc.html +1 -1
- package/docs/design/Types3.dc.html +1 -1
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +1 -1
- package/docs/design.md +169 -4
- package/docs/http-api.md +51 -1
- package/docs/mcp.md +8 -0
- package/docs/node-types.md +38 -3
- package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
- package/docs/plans/plan-019-post-0.9-roadmap.md +283 -11
- package/docs/product-vision-2026-09.md +9 -0
- package/package.json +2 -2
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +246 -108
- package/skills/pmx-canvas/SKILL.md +11 -18
- package/skills/pmx-canvas/references/full-reference.md +9 -4
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/CanvasNode.tsx +34 -47
- package/src/client/canvas/CanvasViewport.tsx +6 -7
- package/src/client/canvas/ContextMenu.tsx +63 -3
- package/src/client/canvas/EdgeLayer.tsx +365 -169
- package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/TopBar.tsx +24 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +6 -1
- package/src/client/mermaid-entry.ts +6 -20
- package/src/client/mermaid-render.ts +39 -0
- package/src/client/nodes/AxStepControls.tsx +45 -69
- package/src/client/nodes/BoardNode.tsx +65 -9
- package/src/client/nodes/ContextNode.tsx +88 -396
- package/src/client/nodes/ExtAppFrame.tsx +283 -1462
- package/src/client/nodes/FileNode.tsx +92 -81
- package/src/client/nodes/HtmlNode.tsx +8 -36
- package/src/client/nodes/LedgerNode.tsx +31 -17
- package/src/client/nodes/MarkdownNode.tsx +8 -17
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +12 -14
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +22 -7
- package/src/client/nodes/StatusSummary.tsx +5 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +262 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-iframe-content-height.ts +34 -10
- package/src/client/nodes/use-surface-frame.ts +22 -11
- package/src/client/nodes/viewer-runtime-state.ts +41 -35
- package/src/client/state/canvas-store.ts +18 -12
- package/src/client/state/intent-bridge.ts +1 -0
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +389 -229
- package/src/client/theme/surface-theme.css +6 -6
- package/src/json-render/charts/tufte-components.tsx +44 -2
- package/src/json-render/renderer/index.css +62 -2
- package/src/json-render/renderer/index.tsx +31 -7
- package/src/json-render/server.ts +3 -2
- package/src/server/board-export.ts +35 -24
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-state.ts +5 -0
- package/src/server/demo-state.json +106 -100
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/index.ts +19 -9
- package/src/server/operations/mcp-contracts.ts +17 -2
- package/src/server/operations/ops/boards.ts +1 -3
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/server.ts +7 -59
- package/src/server/tour-control.ts +66 -6
- package/src/shared/ax-surface-protocol.ts +0 -11
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/tour.ts +8 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPortal } from 'preact/compat';
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'preact/hooks';
|
|
2
3
|
import { BarHint } from '../canvas/BarHint';
|
|
3
4
|
import { IconPlusCircle, IconRefresh } from '../icons';
|
|
4
5
|
import { delimiterForPath, parseDelimitedText } from '../../shared/delimited-text';
|
|
@@ -85,6 +86,10 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
|
|
|
85
86
|
const [loading, setLoading] = useState(!cachedContent && !!filePath && !servesBytes);
|
|
86
87
|
const [error, setError] = useState<string | null>(null);
|
|
87
88
|
const [bytesVersion, setBytesVersion] = useState(0);
|
|
89
|
+
const [header, setHeader] = useState<Element | null>(null);
|
|
90
|
+
useLayoutEffect(() => {
|
|
91
|
+
setHeader(expanded ? null : document.querySelector(`[data-file-toolbar="${node.id}"]`));
|
|
92
|
+
}, [node.id, expanded]);
|
|
88
93
|
|
|
89
94
|
// Load file content on mount or when path changes
|
|
90
95
|
useEffect(() => {
|
|
@@ -202,106 +207,112 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
|
|
|
202
207
|
return <div style={{ color: 'var(--c-dim)', fontStyle: 'italic', padding: '12px' }}>No file path set</div>;
|
|
203
208
|
}
|
|
204
209
|
|
|
205
|
-
|
|
210
|
+
const toolbar = (
|
|
206
211
|
<div
|
|
207
212
|
style={{
|
|
208
213
|
display: 'flex',
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
alignItems: 'center',
|
|
215
|
+
gap: '8px',
|
|
216
|
+
minWidth: 0,
|
|
217
|
+
padding: expanded ? '6px 10px' : 0,
|
|
218
|
+
flexShrink: 0,
|
|
213
219
|
}}
|
|
214
220
|
>
|
|
215
|
-
|
|
216
|
-
<div
|
|
217
|
-
style={{
|
|
218
|
-
display: 'flex',
|
|
219
|
-
alignItems: 'center',
|
|
220
|
-
gap: '8px',
|
|
221
|
-
padding: '6px 10px',
|
|
222
|
-
borderBottom: '1px solid var(--c-line)',
|
|
223
|
-
flexShrink: 0,
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
221
|
+
<div class="file-toolbar-metadata">
|
|
226
222
|
<span
|
|
227
223
|
style={{
|
|
228
|
-
fontSize: '
|
|
224
|
+
fontSize: 'var(--fs-meta)',
|
|
229
225
|
padding: '1px 5px',
|
|
230
|
-
background: '
|
|
231
|
-
color: 'var(--c-
|
|
232
|
-
borderRadius: '
|
|
226
|
+
background: 'var(--c-panel-soft)',
|
|
227
|
+
color: 'var(--c-muted)',
|
|
228
|
+
borderRadius: 'var(--r-control)',
|
|
233
229
|
fontWeight: 600,
|
|
234
230
|
}}
|
|
235
231
|
>
|
|
236
232
|
{lang}
|
|
237
233
|
</span>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
234
|
+
{(expanded || title !== filePath.split(/[\\/]/).pop()) && (
|
|
235
|
+
<span
|
|
236
|
+
style={{
|
|
237
|
+
color: 'var(--c-text-soft)',
|
|
238
|
+
fontSize: expanded ? 'var(--fs-ui)' : 'var(--fs-meta)',
|
|
239
|
+
overflow: 'hidden',
|
|
240
|
+
textOverflow: 'ellipsis',
|
|
241
|
+
whiteSpace: 'nowrap',
|
|
242
|
+
flex: 1,
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
{expanded ? filePath : filePath.split(/[\\/]/).pop()}
|
|
246
|
+
</span>
|
|
247
|
+
)}
|
|
250
248
|
{lineCount !== undefined && (
|
|
251
|
-
<span style={{ color: 'var(--c-dim)', fontSize: '
|
|
249
|
+
<span style={{ color: 'var(--c-dim)', fontSize: 'var(--fs-meta)', flexShrink: 0 }}>{lineCount} lines</span>
|
|
252
250
|
)}
|
|
253
|
-
{updatedAt && (
|
|
254
|
-
<span style={{ color: 'var(--c-dim)', fontSize: '
|
|
251
|
+
{expanded && updatedAt && (
|
|
252
|
+
<span style={{ color: 'var(--c-dim)', fontSize: 'var(--fs-meta)', flexShrink: 0 }}>
|
|
255
253
|
{new Date(updatedAt).toLocaleTimeString()}
|
|
256
254
|
</span>
|
|
257
255
|
)}
|
|
258
|
-
<BarHint label="Mark this file as AX evidence" align="end" fitWithin=".node-content">
|
|
259
|
-
<button
|
|
260
|
-
type="button"
|
|
261
|
-
class="ax-node-action"
|
|
262
|
-
aria-label="Mark this file as AX evidence"
|
|
263
|
-
onClick={(e) => {
|
|
264
|
-
e.stopPropagation();
|
|
265
|
-
void runNodeAxInteraction(
|
|
266
|
-
node,
|
|
267
|
-
'ax.evidence.add',
|
|
268
|
-
{ kind: 'file', title: filePath.split('/').pop() || filePath, ref: filePath },
|
|
269
|
-
'Marked as evidence',
|
|
270
|
-
);
|
|
271
|
-
}}
|
|
272
|
-
style={{
|
|
273
|
-
background: 'none',
|
|
274
|
-
border: 'none',
|
|
275
|
-
color: 'var(--c-muted)',
|
|
276
|
-
cursor: 'pointer',
|
|
277
|
-
padding: '2px 4px',
|
|
278
|
-
fontSize: '12px',
|
|
279
|
-
flexShrink: 0,
|
|
280
|
-
}}
|
|
281
|
-
>
|
|
282
|
-
<IconPlusCircle size={14} />
|
|
283
|
-
</button>
|
|
284
|
-
</BarHint>
|
|
285
|
-
<BarHint label="Reload file" align="end" fitWithin=".node-content">
|
|
286
|
-
<button
|
|
287
|
-
type="button"
|
|
288
|
-
onClick={handleReload}
|
|
289
|
-
aria-label="Reload file"
|
|
290
|
-
style={{
|
|
291
|
-
background: 'none',
|
|
292
|
-
border: 'none',
|
|
293
|
-
color: 'var(--c-muted)',
|
|
294
|
-
cursor: 'pointer',
|
|
295
|
-
padding: '2px 4px',
|
|
296
|
-
fontSize: '12px',
|
|
297
|
-
flexShrink: 0,
|
|
298
|
-
}}
|
|
299
|
-
>
|
|
300
|
-
<IconRefresh size={14} />
|
|
301
|
-
</button>
|
|
302
|
-
</BarHint>
|
|
303
256
|
</div>
|
|
257
|
+
<BarHint label="Mark this file as AX evidence" align="end" fitWithin=".node-content">
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
class="ax-node-action"
|
|
261
|
+
aria-label="Mark this file as AX evidence"
|
|
262
|
+
onClick={(e) => {
|
|
263
|
+
e.stopPropagation();
|
|
264
|
+
void runNodeAxInteraction(
|
|
265
|
+
node,
|
|
266
|
+
'ax.evidence.add',
|
|
267
|
+
{ kind: 'file', title: filePath.split('/').pop() || filePath, ref: filePath },
|
|
268
|
+
'Marked as evidence',
|
|
269
|
+
);
|
|
270
|
+
}}
|
|
271
|
+
style={{
|
|
272
|
+
background: 'none',
|
|
273
|
+
border: 'none',
|
|
274
|
+
color: 'var(--c-muted)',
|
|
275
|
+
cursor: 'pointer',
|
|
276
|
+
padding: '2px 4px',
|
|
277
|
+
fontSize: '12px',
|
|
278
|
+
flexShrink: 0,
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
<IconPlusCircle size={14} />
|
|
282
|
+
</button>
|
|
283
|
+
</BarHint>
|
|
284
|
+
<BarHint label="Reload file" align="end" fitWithin=".node-content">
|
|
285
|
+
<button
|
|
286
|
+
type="button"
|
|
287
|
+
onClick={handleReload}
|
|
288
|
+
aria-label="Reload file"
|
|
289
|
+
style={{
|
|
290
|
+
background: 'none',
|
|
291
|
+
border: 'none',
|
|
292
|
+
color: 'var(--c-muted)',
|
|
293
|
+
cursor: 'pointer',
|
|
294
|
+
padding: '2px 4px',
|
|
295
|
+
fontSize: '12px',
|
|
296
|
+
flexShrink: 0,
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
<IconRefresh size={14} />
|
|
300
|
+
</button>
|
|
301
|
+
</BarHint>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
304
|
|
|
305
|
+
return (
|
|
306
|
+
<div
|
|
307
|
+
style={{
|
|
308
|
+
display: 'flex',
|
|
309
|
+
flexDirection: 'column',
|
|
310
|
+
height: '100%',
|
|
311
|
+
fontFamily: 'var(--font-code)',
|
|
312
|
+
fontSize: expanded ? '13px' : 'var(--fs-ui)',
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
{header ? createPortal(toolbar, header) : toolbar}
|
|
305
316
|
{/* Content area */}
|
|
306
317
|
<div
|
|
307
318
|
style={{
|
|
@@ -3,10 +3,11 @@ import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
|
3
3
|
import { axSurfaceState, canvasTheme } from '../state/canvas-store';
|
|
4
4
|
import type { CanvasNodeState } from '../types';
|
|
5
5
|
import { nodeSurfaceUrl, surfaceContentHash } from './surface-url';
|
|
6
|
-
import { RefreshingViewerFrame } from './
|
|
6
|
+
import { RefreshingViewerFrame, waitForViewerPaint } from './ViewerFrame';
|
|
7
7
|
import { useAxSurfaceBridge } from './use-ax-surface-bridge';
|
|
8
8
|
import { useIframeContentHeight } from './use-iframe-content-height';
|
|
9
9
|
import { useSurfaceFrame } from './use-surface-frame';
|
|
10
|
+
import { usePrimitiveJson } from './primitive-copy';
|
|
10
11
|
|
|
11
12
|
export function shouldShowPresentationControls(node: CanvasNodeState): boolean {
|
|
12
13
|
return node.type === 'html' && node.data.presentation === true;
|
|
@@ -36,8 +37,6 @@ export function HtmlNode({
|
|
|
36
37
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
37
38
|
const theme = canvasTheme.value;
|
|
38
39
|
const axCapabilitySignature = htmlAxCapabilitySignature(node.data.axCapabilities);
|
|
39
|
-
// Stable per-mount nonce that authorizes parent → iframe theme-update messages.
|
|
40
|
-
const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
|
|
41
40
|
// Rotate the nonce when capabilities change: both the URL and injected srcdoc
|
|
42
41
|
// change, rebootstrapping the bridge and rejecting emits from the old document.
|
|
43
42
|
const axToken = useMemo(() => `ax-${crypto.randomUUID()}`, [axCapabilitySignature]);
|
|
@@ -50,6 +49,9 @@ export function HtmlNode({
|
|
|
50
49
|
? node.data.content
|
|
51
50
|
: '';
|
|
52
51
|
const v = useMemo(() => surfaceContentHash(html), [html]);
|
|
52
|
+
// A replacement must not answer a copy request with the old document's data.
|
|
53
|
+
const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, [html]);
|
|
54
|
+
usePrimitiveJson(node, themeToken, iframeRef, expanded);
|
|
53
55
|
|
|
54
56
|
// The in-canvas iframe and the "Open as site" tab load the SAME server-rendered
|
|
55
57
|
// surface URL (/api/canvas/surface/:id) — one render path, no content fork.
|
|
@@ -77,7 +79,7 @@ export function HtmlNode({
|
|
|
77
79
|
|
|
78
80
|
// Grow the node to fit the surface's reported content height (grow-only, gated).
|
|
79
81
|
// Never in the expanded overlay — there the surface fills the large overlay frame.
|
|
80
|
-
useIframeContentHeight(node, iframeRef, expanded ? '' : frameToken);
|
|
82
|
+
const measureContent = useIframeContentHeight(node, iframeRef, expanded ? '' : frameToken);
|
|
81
83
|
|
|
82
84
|
// Phase 3 HTML bridge — the shared sandboxed-surface trust boundary (M2).
|
|
83
85
|
useAxSurfaceBridge({ enabled: true, token: axToken, nodeId: node.id, sourceSurface: 'html-node', iframeRef });
|
|
@@ -121,38 +123,8 @@ export function HtmlNode({
|
|
|
121
123
|
return () => window.clearTimeout(id);
|
|
122
124
|
}, [autoFocus, surfaceSrc]);
|
|
123
125
|
|
|
124
|
-
const handleDocumentLoad = (frame: HTMLIFrameElement, signal: AbortSignal): Promise<boolean> => {
|
|
125
|
-
if (signal.aborted) return Promise.resolve(false);
|
|
126
|
-
return new Promise((resolve) => {
|
|
127
|
-
const finish = (ready: boolean) => {
|
|
128
|
-
clearTimeout(timer);
|
|
129
|
-
window.removeEventListener('message', receive);
|
|
130
|
-
signal.removeEventListener('abort', cancel);
|
|
131
|
-
resolve(ready);
|
|
132
|
-
};
|
|
133
|
-
const cancel = () => finish(false);
|
|
134
|
-
const receive = (event: MessageEvent) => {
|
|
135
|
-
if (
|
|
136
|
-
event.source !== frame.contentWindow ||
|
|
137
|
-
event.data?.source !== HTML_SURFACE_PUSH_SOURCE ||
|
|
138
|
-
event.data?.token !== themeToken
|
|
139
|
-
)
|
|
140
|
-
return;
|
|
141
|
-
// A healthy offscreen frame can wait for paint without a deadline.
|
|
142
|
-
if (event.data.type === 'mounted') clearTimeout(timer);
|
|
143
|
-
if (event.data.type === 'ready') finish(true);
|
|
144
|
-
};
|
|
145
|
-
const timer = setTimeout(cancel, 10_000);
|
|
146
|
-
window.addEventListener('message', receive);
|
|
147
|
-
signal.addEventListener('abort', cancel, { once: true });
|
|
148
|
-
frame.contentWindow?.postMessage(
|
|
149
|
-
{ source: HTML_SURFACE_PUSH_SOURCE, type: 'check-ready', token: themeToken },
|
|
150
|
-
'*',
|
|
151
|
-
);
|
|
152
|
-
});
|
|
153
|
-
};
|
|
154
|
-
|
|
155
126
|
const handleFrameLoad = () => {
|
|
127
|
+
measureContent();
|
|
156
128
|
iframeRef.current?.contentWindow?.postMessage(
|
|
157
129
|
{
|
|
158
130
|
source: HTML_SURFACE_PUSH_SOURCE,
|
|
@@ -205,7 +177,7 @@ export function HtmlNode({
|
|
|
205
177
|
allow=""
|
|
206
178
|
tabIndex={autoFocus ? 0 : undefined}
|
|
207
179
|
onLoad={handleFrameLoad}
|
|
208
|
-
onDocumentLoad={
|
|
180
|
+
onDocumentLoad={(frame, signal) => waitForViewerPaint(frame, themeToken, signal)}
|
|
209
181
|
/>
|
|
210
182
|
);
|
|
211
183
|
}
|
|
@@ -27,22 +27,36 @@ export function LedgerNode({ node }: { node: CanvasNodeState }) {
|
|
|
27
27
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '12px' }}>
|
|
28
28
|
{lines.length > 0 && (
|
|
29
29
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
30
|
-
{lines.map((line, i) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
30
|
+
{lines.map((line, i) => {
|
|
31
|
+
const timed = /^(\d{2}:\d{2}(?::\d{2})?)\s+(.+)$/.exec(line);
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
key={i}
|
|
35
|
+
style={{
|
|
36
|
+
display: 'grid',
|
|
37
|
+
gridTemplateColumns: timed ? '44px 1fr' : '1fr',
|
|
38
|
+
gap: '10px',
|
|
39
|
+
padding: '3px 0',
|
|
40
|
+
color: 'var(--c-text)',
|
|
41
|
+
fontFamily: 'var(--font-ui)',
|
|
42
|
+
fontSize: 'var(--fs-ui)',
|
|
43
|
+
whiteSpace: 'pre-wrap',
|
|
44
|
+
wordBreak: 'break-word',
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{timed ? (
|
|
48
|
+
<>
|
|
49
|
+
<span style={{ fontFamily: 'var(--font-code)', fontSize: 'var(--fs-meta)', color: 'var(--c-dim)' }}>
|
|
50
|
+
{timed[1]}
|
|
51
|
+
</span>
|
|
52
|
+
<span>{timed[2]}</span>
|
|
53
|
+
</>
|
|
54
|
+
) : (
|
|
55
|
+
line
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
46
60
|
</div>
|
|
47
61
|
)}
|
|
48
62
|
{entries.map(([key, value]) => (
|
|
@@ -53,7 +67,7 @@ export function LedgerNode({ node }: { node: CanvasNodeState }) {
|
|
|
53
67
|
justifyContent: 'space-between',
|
|
54
68
|
gap: '8px',
|
|
55
69
|
padding: '3px 0',
|
|
56
|
-
borderBottom: '1px solid
|
|
70
|
+
borderBottom: '1px solid var(--c-line)',
|
|
57
71
|
}}
|
|
58
72
|
>
|
|
59
73
|
<span style={{ color: 'var(--c-muted)', fontSize: '11px', flexShrink: 0 }}>
|
|
@@ -6,7 +6,7 @@ import { IconArrowLeft } from '../icons';
|
|
|
6
6
|
import { expandNode, nodes, updateNodeData } from '../state/canvas-store';
|
|
7
7
|
import { fetchFile, renderMarkdown, updateNodeFromClient } from '../state/intent-bridge';
|
|
8
8
|
import type { CanvasNodeState } from '../types';
|
|
9
|
-
import {
|
|
9
|
+
import { openMemoryTarget, boardList } from '../state/boards-store';
|
|
10
10
|
import { showToast } from '../state/attention-bridge';
|
|
11
11
|
import { MdFormatBar } from './MdFormatBar';
|
|
12
12
|
import { handleFormatShortcut, handleTab } from './md-format';
|
|
@@ -189,7 +189,6 @@ export async function waitForMarkdownSave(boardId: string | null, nodeId: string
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
export function MarkdownNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
192
|
-
const board = activeBoard();
|
|
193
192
|
const boardId = activeBoardId.value;
|
|
194
193
|
const draftKey = JSON.stringify([boardId, node.id]);
|
|
195
194
|
const draft = useMemo(() => {
|
|
@@ -523,7 +522,7 @@ export function MarkdownNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
523
522
|
<div
|
|
524
523
|
style={{
|
|
525
524
|
padding: '4px 8px',
|
|
526
|
-
fontSize: '
|
|
525
|
+
fontSize: 'var(--fs-meta)',
|
|
527
526
|
background: 'color-mix(in srgb, var(--c-ok) 10%, transparent)',
|
|
528
527
|
color: 'var(--c-ok)',
|
|
529
528
|
borderBottom: '1px solid color-mix(in srgb, var(--c-ok) 20%, transparent)',
|
|
@@ -699,21 +698,13 @@ export function MarkdownNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
699
698
|
{String((node.data.source as Record<string, unknown>).reference ?? 'Location not supplied')}
|
|
700
699
|
</div>
|
|
701
700
|
)}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
<button
|
|
705
|
-
|
|
706
|
-
class="md-readme-btn"
|
|
707
|
-
aria-pressed={board.readmeNodeId === node.id}
|
|
708
|
-
onClick={() => void setBoardReadme(board.id, board.readmeNodeId === node.id ? null : node.id)}
|
|
709
|
-
>
|
|
710
|
-
{board.readmeNodeId === node.id ? 'README' : 'Set as README'}
|
|
701
|
+
{node.data.display !== 'heading' && (
|
|
702
|
+
<div class="md-card-actions">
|
|
703
|
+
<button type="button" class="md-card-edit" onClick={() => expandNode(node.id)}>
|
|
704
|
+
Edit
|
|
711
705
|
</button>
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
Edit
|
|
715
|
-
</button>
|
|
716
|
-
</div>
|
|
706
|
+
</div>
|
|
707
|
+
)}
|
|
717
708
|
</div>
|
|
718
709
|
);
|
|
719
710
|
}
|