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
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
/* ── Core palette (dark · ink + cyan) ────────────────────── */
|
|
22
22
|
--c-bg: #081524;
|
|
23
23
|
--c-panel: #0f1d31;
|
|
24
|
-
--c-panel-soft: #
|
|
24
|
+
--c-panel-soft: #13243b;
|
|
25
25
|
--c-line: #1b2c44;
|
|
26
26
|
--c-text: #e6eef7;
|
|
27
|
-
--c-text-soft: #
|
|
27
|
+
--c-text-soft: #b9c6d6;
|
|
28
28
|
--c-muted: #8ea3bd;
|
|
29
|
-
--c-dim: #
|
|
29
|
+
--c-dim: #6b7d94;
|
|
30
30
|
--c-accent: #4BBCFF;
|
|
31
31
|
--c-ok: #2fd07f;
|
|
32
32
|
--c-warn: #f4c542;
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
/* Common aliases authored HTML might use. */
|
|
47
47
|
--color-bg: #081524;
|
|
48
48
|
--color-panel: #0f1d31;
|
|
49
|
-
--color-surface: #
|
|
49
|
+
--color-surface: #13243b;
|
|
50
50
|
--color-border: #1b2c44;
|
|
51
51
|
--color-text: #e6eef7;
|
|
52
52
|
--color-text-primary: #e6eef7;
|
|
53
|
-
--color-text-secondary: #
|
|
53
|
+
--color-text-secondary: #b9c6d6;
|
|
54
54
|
--color-text-muted: #8ea3bd;
|
|
55
|
-
--color-text-dim: #
|
|
55
|
+
--color-text-dim: #6b7d94;
|
|
56
56
|
--color-accent: #4BBCFF;
|
|
57
57
|
--color-success: #2fd07f;
|
|
58
58
|
--color-warning: #f4c542;
|
|
@@ -336,6 +336,18 @@ interface SlopegraphProps {
|
|
|
336
336
|
height?: number | null;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/** Separate close labels without moving the data points they describe. */
|
|
340
|
+
function slopeLabelPositions(values: number[], top: number, bottom: number): number[] {
|
|
341
|
+
const order = values.map((y, index) => ({ y, index })).sort((a, b) => a.y - b.y);
|
|
342
|
+
const gap = Math.min(14, (bottom - top) / Math.max(1, order.length - 1));
|
|
343
|
+
for (let i = 1; i < order.length; i++) order[i].y = Math.max(order[i].y, order[i - 1].y + gap);
|
|
344
|
+
if (order.length) order[order.length - 1].y = Math.min(order[order.length - 1].y, bottom);
|
|
345
|
+
for (let i = order.length - 2; i >= 0; i--) order[i].y = Math.min(order[i].y, order[i + 1].y - gap);
|
|
346
|
+
const result = [...values];
|
|
347
|
+
for (const { y, index } of order) result[index] = y;
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
339
351
|
function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
|
|
340
352
|
const stroke = props.color ?? ACCENT;
|
|
341
353
|
const rows = (props.data ?? []).map((row) => ({
|
|
@@ -359,6 +371,16 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
|
|
|
359
371
|
// fills without pushing a scrollbar onto the iframe document.
|
|
360
372
|
const plotH = chartPlotHeight(height, Boolean(props.title));
|
|
361
373
|
const scaleY = (v: number) => topPad + (1 - (v - min) / (max - min)) * (plotH - topPad - botPad);
|
|
374
|
+
const beforeLabels = slopeLabelPositions(
|
|
375
|
+
rows.map((row) => scaleY(row.before)),
|
|
376
|
+
topPad,
|
|
377
|
+
plotH - botPad,
|
|
378
|
+
);
|
|
379
|
+
const afterLabels = slopeLabelPositions(
|
|
380
|
+
rows.map((row) => scaleY(row.after)),
|
|
381
|
+
topPad,
|
|
382
|
+
plotH - botPad,
|
|
383
|
+
);
|
|
362
384
|
|
|
363
385
|
return (
|
|
364
386
|
<div ref={frameRef} className="pmx-chart pmx-chart--slopegraph" style={{ height }}>
|
|
@@ -390,10 +412,30 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
|
|
|
390
412
|
<line x1={leftX} y1={y1} x2={rightX} y2={y2} stroke={lineColor} strokeWidth={1.5} />
|
|
391
413
|
<circle cx={leftX} cy={y1} r={2.5} fill={lineColor} />
|
|
392
414
|
<circle cx={rightX} cy={y2} r={2.5} fill={lineColor} />
|
|
393
|
-
|
|
415
|
+
{beforeLabels[i] !== y1 && (
|
|
416
|
+
<line x1={leftX} y1={y1} x2={leftX - 5} y2={beforeLabels[i]} stroke={lineColor} />
|
|
417
|
+
)}
|
|
418
|
+
{afterLabels[i] !== y2 && (
|
|
419
|
+
<line x1={rightX} y1={y2} x2={rightX + 5} y2={afterLabels[i]} stroke={lineColor} />
|
|
420
|
+
)}
|
|
421
|
+
<text
|
|
422
|
+
x={leftX - 8}
|
|
423
|
+
y={beforeLabels[i]}
|
|
424
|
+
textAnchor="end"
|
|
425
|
+
dominantBaseline="central"
|
|
426
|
+
fontSize={11}
|
|
427
|
+
fill={INK}
|
|
428
|
+
>
|
|
394
429
|
{`${row.label} ${row.before}`}
|
|
395
430
|
</text>
|
|
396
|
-
<text
|
|
431
|
+
<text
|
|
432
|
+
x={rightX + 8}
|
|
433
|
+
y={afterLabels[i]}
|
|
434
|
+
textAnchor="start"
|
|
435
|
+
dominantBaseline="central"
|
|
436
|
+
fontSize={11}
|
|
437
|
+
fill={INK}
|
|
438
|
+
>
|
|
397
439
|
{`${row.after} ${row.label}`}
|
|
398
440
|
</text>
|
|
399
441
|
</g>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
@source "../../../../node_modules/@json-render/shadcn/dist/index.js";
|
|
5
5
|
|
|
6
|
-
@custom-variant dark (&:is(
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
7
7
|
|
|
8
8
|
@theme {
|
|
9
9
|
--font-sans: "IBM Plex Sans", system-ui, sans-serif;
|
|
@@ -99,6 +99,66 @@
|
|
|
99
99
|
--chart-6: #ffffff;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/* Named themes share the canvas surface, including nested cards and charts.
|
|
103
|
+
Explicit dark/light remain available for authored design experiments. */
|
|
104
|
+
:root[data-canvas-theme] {
|
|
105
|
+
--radius: 6px;
|
|
106
|
+
--text-xs: 11px;
|
|
107
|
+
--text-sm: 12px;
|
|
108
|
+
--text-base: 13px;
|
|
109
|
+
--text-lg: 15px;
|
|
110
|
+
--text-xl: 20px;
|
|
111
|
+
--background: var(--c-panel);
|
|
112
|
+
--foreground: var(--c-text);
|
|
113
|
+
--card: var(--c-panel-soft);
|
|
114
|
+
--card-foreground: var(--c-text);
|
|
115
|
+
--popover: var(--c-panel);
|
|
116
|
+
--popover-foreground: var(--c-text);
|
|
117
|
+
--primary: var(--c-accent);
|
|
118
|
+
--primary-foreground: var(--c-on-accent, var(--c-on-pin));
|
|
119
|
+
--secondary: var(--c-panel-soft);
|
|
120
|
+
--secondary-foreground: var(--c-text);
|
|
121
|
+
--muted: var(--c-panel-soft);
|
|
122
|
+
--muted-foreground: var(--c-muted);
|
|
123
|
+
--accent: var(--c-panel-soft);
|
|
124
|
+
--accent-foreground: var(--c-text);
|
|
125
|
+
--destructive: var(--c-danger);
|
|
126
|
+
--border: var(--c-line);
|
|
127
|
+
--input: var(--c-line);
|
|
128
|
+
--ring: var(--c-accent);
|
|
129
|
+
--chart-1: var(--c-accent);
|
|
130
|
+
--chart-2: var(--c-rel-supports);
|
|
131
|
+
--chart-3: var(--c-rel-contradicts);
|
|
132
|
+
--chart-4: var(--c-rel-cites);
|
|
133
|
+
--chart-5: var(--c-rel-informs);
|
|
134
|
+
--chart-6: var(--c-muted);
|
|
135
|
+
font-size: 16px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* surface-theme's unlayered body rule otherwise wins over Tailwind's base layer. */
|
|
139
|
+
body {
|
|
140
|
+
background: var(--background);
|
|
141
|
+
color: var(--foreground);
|
|
142
|
+
padding: 0;
|
|
143
|
+
font-family: var(--font-sans);
|
|
144
|
+
font-size: 13px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.pmx-viewer-root { width: 100%; min-width: 0; }
|
|
148
|
+
[data-embedded] .pmx-viewer-root:not(:has(> [data-slot="card"])) { padding: 12px 14px; }
|
|
149
|
+
[data-embedded] .pmx-viewer-root > [data-slot="card"] {
|
|
150
|
+
width: 100%;
|
|
151
|
+
max-width: none;
|
|
152
|
+
border: 0;
|
|
153
|
+
border-radius: 0;
|
|
154
|
+
box-shadow: none;
|
|
155
|
+
background: transparent;
|
|
156
|
+
padding: 12px 14px;
|
|
157
|
+
}
|
|
158
|
+
[data-embedded][data-duplicate-title] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"] { display: none; }
|
|
159
|
+
[data-embedded][data-duplicate-title] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-header"]:not(:has([data-slot="card-description"])) { display: none; }
|
|
160
|
+
[data-embedded] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-content"] { padding: 0; }
|
|
161
|
+
|
|
102
162
|
@theme inline {
|
|
103
163
|
--radius-sm: calc(var(--radius) - 4px);
|
|
104
164
|
--radius-md: calc(var(--radius) - 2px);
|
|
@@ -159,7 +219,7 @@ button {
|
|
|
159
219
|
border: 1px solid transparent;
|
|
160
220
|
border-radius: 9999px;
|
|
161
221
|
padding: 0.125rem 0.5rem;
|
|
162
|
-
font-size: 0.75rem;
|
|
222
|
+
font-size: max(11px, 0.75rem);
|
|
163
223
|
font-weight: 500;
|
|
164
224
|
line-height: 1.25rem;
|
|
165
225
|
}
|
|
@@ -23,6 +23,7 @@ import { extraChartComponents } from '../charts/extra-components';
|
|
|
23
23
|
import { tufteChartComponents } from '../charts/tufte-components';
|
|
24
24
|
import { pmxCanvasDirectives } from '../directives';
|
|
25
25
|
import { JsonRenderDevtools } from '@json-render/devtools-react';
|
|
26
|
+
import { canvasThemeScheme, isCanvasTheme } from '../../shared/themes.js';
|
|
26
27
|
|
|
27
28
|
type BadgeVariant =
|
|
28
29
|
| 'default'
|
|
@@ -167,10 +168,12 @@ function syncPreferredTheme(): void {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
function applyTheme(theme: unknown): void {
|
|
170
|
-
if (theme !== 'dark' && theme !== 'light' && theme
|
|
171
|
+
if (theme !== 'dark' && theme !== 'light' && !isCanvasTheme(theme)) return;
|
|
171
172
|
document.documentElement.setAttribute('data-theme', theme);
|
|
172
|
-
document.documentElement.
|
|
173
|
-
|
|
173
|
+
document.documentElement.toggleAttribute('data-canvas-theme', isCanvasTheme(theme));
|
|
174
|
+
const scheme = theme === 'light' ? 'light' : canvasThemeScheme(theme);
|
|
175
|
+
document.documentElement.classList.toggle('dark', scheme === 'dark');
|
|
176
|
+
document.documentElement.style.colorScheme = scheme;
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
function App({ store }: { store: StateStore }) {
|
|
@@ -235,11 +238,15 @@ function App({ store }: { store: StateStore }) {
|
|
|
235
238
|
padding: 0,
|
|
236
239
|
boxSizing: 'border-box' as const,
|
|
237
240
|
}
|
|
238
|
-
: { minHeight: '100vh', padding:
|
|
241
|
+
: { minHeight: '100vh', padding: 0, boxSizing: 'border-box' as const };
|
|
239
242
|
return (
|
|
240
|
-
<div
|
|
243
|
+
<div
|
|
244
|
+
style={containerStyle}
|
|
245
|
+
data-embedded={!isSite ? '' : undefined}
|
|
246
|
+
data-duplicate-title={spec.elements[spec.root]?.props?.title === document.title ? '' : undefined}
|
|
247
|
+
>
|
|
241
248
|
<JSONUIProvider registry={registry} store={store} directives={pmxCanvasDirectives} handlers={buildAxHandlers()}>
|
|
242
|
-
<div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
|
|
249
|
+
<div className="pmx-viewer-root" style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
|
|
243
250
|
<Renderer spec={spec} registry={registry} loading={false} />
|
|
244
251
|
</div>
|
|
245
252
|
{window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? <JsonRenderDevtools position="right" /> : null}
|
|
@@ -282,8 +289,25 @@ if (root) {
|
|
|
282
289
|
// Standalone viewers have no state token and render immediately.
|
|
283
290
|
window.addEventListener('message', (event: MessageEvent) => {
|
|
284
291
|
if (event.source !== window.parent) return;
|
|
285
|
-
const m = event.data as {
|
|
292
|
+
const m = event.data as {
|
|
293
|
+
source?: string;
|
|
294
|
+
type?: string;
|
|
295
|
+
token?: string;
|
|
296
|
+
state?: unknown;
|
|
297
|
+
theme?: unknown;
|
|
298
|
+
title?: unknown;
|
|
299
|
+
} | null;
|
|
286
300
|
if (!m) return;
|
|
301
|
+
if (m.source === HTML_SURFACE_PUSH_SOURCE && m.type === 'theme-update' && stateToken && m.token === stateToken) {
|
|
302
|
+
applyTheme(m.theme);
|
|
303
|
+
if (typeof m.title === 'string') {
|
|
304
|
+
document.title = m.title;
|
|
305
|
+
const spec = window.__PMX_CANVAS_JSON_RENDER_SPEC__;
|
|
306
|
+
document
|
|
307
|
+
.querySelector('[data-embedded]')
|
|
308
|
+
?.toggleAttribute('data-duplicate-title', spec?.elements[spec.root]?.props?.title === m.title);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
287
311
|
if (m.source === JSON_VIEWER_STATE_SOURCE && m.type === 'restore' && stateToken && m.token === stateToken) {
|
|
288
312
|
if (m.state === null || (typeof m.state === 'object' && !Array.isArray(m.state)))
|
|
289
313
|
mount(m.state as Record<string, unknown> | null);
|
|
@@ -6,6 +6,7 @@ import { applySpecPatch, parseSpecStreamLine, type Spec, type SpecStreamLine } f
|
|
|
6
6
|
import { allComponentDefinitions, catalog, validateShadcnElementProps, type JsonRenderIssue } from './catalog.js';
|
|
7
7
|
import { findUnknownDirectiveKey, isDynamicPropValue } from './directives.js';
|
|
8
8
|
import { contentHeightReporterTag } from '../shared/content-height-reporter.js';
|
|
9
|
+
import type { CanvasThemeName } from '../shared/themes.js';
|
|
9
10
|
|
|
10
11
|
export interface JsonRenderSpec {
|
|
11
12
|
root: string;
|
|
@@ -1030,7 +1031,7 @@ export async function readJsonRenderBundle(): Promise<{ js: string; css: string
|
|
|
1030
1031
|
export async function buildJsonRenderViewerHtml(options: {
|
|
1031
1032
|
title: string;
|
|
1032
1033
|
spec: JsonRenderSpec;
|
|
1033
|
-
theme?:
|
|
1034
|
+
theme?: CanvasThemeName | 'dark' | 'light';
|
|
1034
1035
|
display?: 'expanded' | 'site';
|
|
1035
1036
|
devtools?: boolean;
|
|
1036
1037
|
nodeId?: string;
|
|
@@ -1076,7 +1077,7 @@ export async function buildJsonRenderViewerHtml(options: {
|
|
|
1076
1077
|
const heightReporter = options.frameToken ? contentHeightReporterTag(options.frameToken) : '';
|
|
1077
1078
|
return buildAppHtml({
|
|
1078
1079
|
title: options.title,
|
|
1079
|
-
css: cssBundle
|
|
1080
|
+
css: `${readFileSync(join(import.meta.dir, '../client/theme/surface-theme.css'), 'utf-8')}\n${cssBundle}`,
|
|
1080
1081
|
js: escapeInlineScriptSource(boot),
|
|
1081
1082
|
head: `<meta name="color-scheme" content="light dark" />${heightReporter}`,
|
|
1082
1083
|
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
14
14
|
import { Marked, Renderer, type RendererThis, type Tokens } from 'marked';
|
|
15
|
-
import { canvasThemeScheme } from '../shared/themes.js';
|
|
15
|
+
import { canvasThemeScheme, normalizeCanvasThemeName, type CanvasThemeName } from '../shared/themes.js';
|
|
16
16
|
import { delimiterForPath, parseDelimitedText } from '../shared/delimited-text.js';
|
|
17
17
|
import { buildJsonRenderViewerHtml, escapeInlineScriptSource, readJsonRenderBundle } from '../json-render/server.js';
|
|
18
18
|
import type { JsonRenderSpec } from '../json-render/server.js';
|
|
@@ -58,6 +58,7 @@ interface ExportCard {
|
|
|
58
58
|
w: number;
|
|
59
59
|
h: number;
|
|
60
60
|
group?: true;
|
|
61
|
+
heading?: true;
|
|
61
62
|
html?: string;
|
|
62
63
|
frame?: string;
|
|
63
64
|
}
|
|
@@ -66,7 +67,7 @@ interface Collected {
|
|
|
66
67
|
manifest: ExportManifest;
|
|
67
68
|
cards: ExportCard[];
|
|
68
69
|
edges: CanvasEdge[];
|
|
69
|
-
|
|
70
|
+
theme: CanvasThemeName;
|
|
70
71
|
needsJsonRender: boolean;
|
|
71
72
|
needsMermaid: boolean;
|
|
72
73
|
}
|
|
@@ -207,9 +208,7 @@ async function collect(
|
|
|
207
208
|
const read = canvasState.readBoard(boardId);
|
|
208
209
|
if (!read) return null;
|
|
209
210
|
const { board, state } = read;
|
|
210
|
-
const theme =
|
|
211
|
-
// Exports collapse to a scheme; surface documents take the theme that draws it.
|
|
212
|
-
const surfaceTheme = theme === 'light' ? 'daylight' : 'harbor';
|
|
211
|
+
const theme = normalizeCanvasThemeName(state.theme ?? canvasState.theme);
|
|
213
212
|
const remoteImages = new Set<string>();
|
|
214
213
|
const links = new Set<string>();
|
|
215
214
|
const frameNetworkDestinations = new Set<string>();
|
|
@@ -278,6 +277,7 @@ async function collect(
|
|
|
278
277
|
card.group = true;
|
|
279
278
|
break;
|
|
280
279
|
case 'markdown':
|
|
280
|
+
if (data.display === 'heading') card.heading = true;
|
|
281
281
|
card.html = `<div class="md">${await markdown.parse(text(data.content))}</div>`;
|
|
282
282
|
break;
|
|
283
283
|
case 'status':
|
|
@@ -385,8 +385,7 @@ async function collect(
|
|
|
385
385
|
const html = text(data.html) || text(data.content);
|
|
386
386
|
collectFrameNetworkDestinations(html, frameNetworkDestinations);
|
|
387
387
|
manifest.frames += 1;
|
|
388
|
-
if (withFrames)
|
|
389
|
-
card.frame = buildHtmlSurfaceDocument(html, { theme: surfaceTheme, title, inlineThemeCss: themeCss });
|
|
388
|
+
if (withFrames) card.frame = buildHtmlSurfaceDocument(html, { theme, title, inlineThemeCss: themeCss });
|
|
390
389
|
break;
|
|
391
390
|
}
|
|
392
391
|
case 'mermaid':
|
|
@@ -398,7 +397,7 @@ async function collect(
|
|
|
398
397
|
'<script src="/canvas/mermaid-entry.js"></script>',
|
|
399
398
|
`<script src="${MERMAID_SRC}"></script>`,
|
|
400
399
|
),
|
|
401
|
-
{ theme
|
|
400
|
+
{ theme, title, inlineThemeCss: themeCss },
|
|
402
401
|
);
|
|
403
402
|
}
|
|
404
403
|
break;
|
|
@@ -415,7 +414,8 @@ async function collect(
|
|
|
415
414
|
title,
|
|
416
415
|
spec: data.spec as JsonRenderSpec,
|
|
417
416
|
theme,
|
|
418
|
-
|
|
417
|
+
// The saved card owns the viewport, just as on the live canvas.
|
|
418
|
+
// Omitting display selects embedded layout and its duplicate-title policy.
|
|
419
419
|
});
|
|
420
420
|
}
|
|
421
421
|
break;
|
|
@@ -444,7 +444,7 @@ async function collect(
|
|
|
444
444
|
manifest.links = [...links];
|
|
445
445
|
manifest.frameNetworkDestinations = [...frameNetworkDestinations];
|
|
446
446
|
manifest.embeddedCodeCanAccessNetwork = manifest.frames > 0;
|
|
447
|
-
return { manifest, cards, edges: state.edges,
|
|
447
|
+
return { manifest, cards, edges: state.edges, theme, needsJsonRender, needsMermaid };
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
/** What an export of `boardId` would put in the file, without building it. */
|
|
@@ -467,7 +467,7 @@ export async function buildBoardExport(
|
|
|
467
467
|
): Promise<{ html: string; manifest: ExportManifest } | null> {
|
|
468
468
|
const collected = await collect(boardId, includeFiles, true, includeDerivedText);
|
|
469
469
|
if (!collected) return null;
|
|
470
|
-
const { manifest, cards, edges: snapshotEdges,
|
|
470
|
+
const { manifest, cards, edges: snapshotEdges, theme, needsJsonRender, needsMermaid } = collected;
|
|
471
471
|
|
|
472
472
|
// Store each viewer bundle once; frames reference it by placeholder.
|
|
473
473
|
const assets: Record<string, string> = {};
|
|
@@ -496,7 +496,7 @@ export async function buildBoardExport(
|
|
|
496
496
|
const exportedAt = new Date().toISOString();
|
|
497
497
|
const html = exportPage({
|
|
498
498
|
title: manifest.boardName,
|
|
499
|
-
|
|
499
|
+
theme,
|
|
500
500
|
exportedAt,
|
|
501
501
|
board: { cards, edges },
|
|
502
502
|
assets,
|
|
@@ -536,7 +536,7 @@ export function exportsFolder(): string | null {
|
|
|
536
536
|
|
|
537
537
|
function exportPage(input: {
|
|
538
538
|
title: string;
|
|
539
|
-
|
|
539
|
+
theme: CanvasThemeName;
|
|
540
540
|
exportedAt: string;
|
|
541
541
|
board: {
|
|
542
542
|
cards: ExportCard[];
|
|
@@ -545,14 +545,22 @@ function exportPage(input: {
|
|
|
545
545
|
assets: Record<string, string>;
|
|
546
546
|
}): string {
|
|
547
547
|
const date = new Date(input.exportedAt).toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' });
|
|
548
|
+
// Use the workbench's shipped faces and shared palette without offline requests.
|
|
549
|
+
const fonts = (readAsset('global.css').match(/@font-face\s*\{[^}]*\}/g) ?? [])
|
|
550
|
+
.join('\n')
|
|
551
|
+
.replace(
|
|
552
|
+
/url\("fonts\/([\w.-]+\.woff2)"\)/g,
|
|
553
|
+
(_, file: string) =>
|
|
554
|
+
`url("data:font/woff2;base64,${readFileSync(join(canvasBundleDir(), 'fonts', file)).toString('base64')}")`,
|
|
555
|
+
);
|
|
548
556
|
return `<!doctype html>
|
|
549
|
-
<html lang="en" data-scheme="${input.
|
|
557
|
+
<html lang="en" data-scheme="${canvasThemeScheme(input.theme)}" data-theme="${input.theme}">
|
|
550
558
|
<head>
|
|
551
559
|
<meta charset="utf-8">
|
|
552
560
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
553
561
|
<meta name="generator" content="PMX Canvas">
|
|
554
562
|
<title>${escapeHtml(input.title)}</title>
|
|
555
|
-
<style>${EXPORT_CSS}</style>
|
|
563
|
+
<style>${fonts}\n${readAsset('surface-theme.css')}\n${EXPORT_CSS}</style>
|
|
556
564
|
</head>
|
|
557
565
|
<body>
|
|
558
566
|
<header class="bar">
|
|
@@ -574,10 +582,9 @@ function exportPage(input: {
|
|
|
574
582
|
}
|
|
575
583
|
|
|
576
584
|
const EXPORT_CSS = `
|
|
577
|
-
:root{--bg
|
|
578
|
-
[data-scheme=light]{--bg:#f4f6f8;--panel:#ffffff;--line:#d5dbe1;--text:#17202a;--soft:#5b6875;--accent:#0a6fc2;--add:#1c7a4f;--del:#b3263a}
|
|
585
|
+
:root{--bg:var(--c-bg);--panel:var(--c-panel);--line:var(--c-line);--text:var(--c-text);--soft:var(--c-muted);--accent:var(--c-accent);--add:var(--c-ok);--del:var(--c-danger)}
|
|
579
586
|
*{box-sizing:border-box}
|
|
580
|
-
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:
|
|
587
|
+
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:13px/1.6 var(--font-ui)}
|
|
581
588
|
.bar{position:fixed;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 16px;background:var(--panel);border-bottom:1px solid var(--line);z-index:5}
|
|
582
589
|
.bar .name{display:flex;flex-direction:column;min-width:0}
|
|
583
590
|
.bar .name strong,.bar .name span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
@@ -592,12 +599,16 @@ button:focus-visible,a:focus-visible,[tabindex]:focus-visible{outline:2px solid
|
|
|
592
599
|
#edges{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
|
|
593
600
|
#edges line{stroke:var(--soft);stroke-width:1.5}
|
|
594
601
|
#edges text{fill:var(--soft);font-size:12px;text-anchor:middle}
|
|
595
|
-
.card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:
|
|
602
|
+
.card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:var(--r-node);overflow:hidden;box-shadow:var(--e-1)}
|
|
596
603
|
.card.group{background:color-mix(in srgb,var(--accent) 5%,transparent);border-style:dashed;box-shadow:none}
|
|
597
|
-
.card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:
|
|
604
|
+
.card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:7px 38px 7px 12px;font-weight:600;font-size:12px;line-height:22px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:transparent}
|
|
598
605
|
button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--soft);font-size:16px}
|
|
599
606
|
.card.group .card-title{border-bottom:none;cursor:default;color:var(--soft)}
|
|
600
|
-
.card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:
|
|
607
|
+
.card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:10px 14px;font-size:var(--fs-ui);color:var(--c-text-soft);overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
|
|
608
|
+
.card.heading{background:transparent;border-color:transparent;box-shadow:none}
|
|
609
|
+
.card.heading .card-title{padding:8px 38px 0 16px;border:0;font-size:var(--fs-heading);line-height:28px;letter-spacing:-.01em}
|
|
610
|
+
.card.heading .card-body{padding:0 16px 8px;color:var(--soft)}
|
|
611
|
+
.card.heading:not(:hover):not(:focus-within) .card-title:after{visibility:hidden}
|
|
601
612
|
.card-body>:first-child,.md>:first-child{margin-top:0}.card-body>:last-child,.md>:last-child{margin-bottom:0}
|
|
602
613
|
.card iframe,#overlay-body iframe{flex:1;min-height:0;width:100%;height:100%;border:0;background:var(--bg)}
|
|
603
614
|
.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:1.4em 0 .6em;line-height:1.3;font-weight:650;letter-spacing:-.02em}
|
|
@@ -607,7 +618,7 @@ button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--s
|
|
|
607
618
|
.md input[type=checkbox]{accent-color:var(--accent);margin-right:.5em}
|
|
608
619
|
.md blockquote{margin:1em 0;padding:10px 16px;border-left:3px solid var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));color:var(--soft)}
|
|
609
620
|
.md blockquote>:last-child{margin-bottom:0}.md hr{border:0;border-top:1px solid var(--line);margin:24px 0}
|
|
610
|
-
.md code,pre{font:13px/1.65
|
|
621
|
+
.md code,pre{font:13px/1.65 var(--font-code);tab-size:2}
|
|
611
622
|
.md :not(pre)>code{padding:2px 5px;border:1px solid var(--line);border-radius:4px;background:color-mix(in srgb,var(--soft) 8%,var(--panel))}
|
|
612
623
|
pre{margin:0 0 16px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--soft) 5%,var(--panel));white-space:pre;overflow:auto;overflow-wrap:normal;overscroll-behavior:contain}
|
|
613
624
|
.prose{white-space:pre-wrap}.table-scroll{width:100%;max-width:100%;overflow:auto;margin:16px 0;border:1px solid var(--line);border-radius:8px;overscroll-behavior:contain}
|
|
@@ -629,7 +640,7 @@ details{margin-top:16px}summary{cursor:pointer;color:var(--soft);margin-bottom:1
|
|
|
629
640
|
#overlay .sheet{width:min(1100px,100%);height:min(90vh,100%);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
|
|
630
641
|
#overlay .sheet>header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line)}
|
|
631
642
|
#overlay-body{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
632
|
-
#overlay-body .card-body{padding:28px 32px}#overlay-body .md{max-width:960px;margin:0 auto}
|
|
643
|
+
#overlay-body .card-body{padding:28px 32px;font-size:15px}#overlay-body .md{max-width:960px;margin:0 auto}
|
|
633
644
|
@media(max-width:600px){#overlay{padding:8px}#overlay .sheet{height:100%}#overlay-body .card-body{padding:18px}.bar{padding:0 10px}.bar .name span{display:none}}
|
|
634
645
|
`;
|
|
635
646
|
|
|
@@ -660,7 +671,7 @@ const body = (card) => {
|
|
|
660
671
|
const ordered = [...board.cards].sort((a, b) => (b.group ? 1 : 0) - (a.group ? 1 : 0));
|
|
661
672
|
for (const card of ordered) {
|
|
662
673
|
const el = document.createElement('section');
|
|
663
|
-
el.className = 'card' + (card.group ? ' group' : '');
|
|
674
|
+
el.className = 'card' + (card.group ? ' group' : '') + (card.heading ? ' heading' : '');
|
|
664
675
|
el.dataset.nodeId = card.id;
|
|
665
676
|
el.dataset.nodeType = card.kind;
|
|
666
677
|
Object.assign(el.style, { left: card.x + 'px', top: card.y + 'px', width: card.w + 'px', height: card.h + 'px' });
|
|
@@ -58,6 +58,7 @@ export function validCanvasNodeUpdates(input: unknown): CanvasNodeUpdate[] {
|
|
|
58
58
|
return (
|
|
59
59
|
typeof update.id === 'string' &&
|
|
60
60
|
(update.collapsed === undefined || typeof update.collapsed === 'boolean') &&
|
|
61
|
+
(update.zIndex === undefined || finite(update.zIndex) !== undefined) &&
|
|
61
62
|
validateCanvasNodePatch(update) === null
|
|
62
63
|
);
|
|
63
64
|
});
|
|
@@ -364,6 +364,7 @@ export interface CanvasNodeUpdate {
|
|
|
364
364
|
position?: { x: number; y: number };
|
|
365
365
|
size?: { width: number; height: number };
|
|
366
366
|
collapsed?: boolean;
|
|
367
|
+
zIndex?: number;
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
|
|
@@ -3220,6 +3221,9 @@ class CanvasStateManager {
|
|
|
3220
3221
|
if (update.collapsed !== undefined && update.collapsed !== existing.collapsed) {
|
|
3221
3222
|
nextPatch.collapsed = update.collapsed;
|
|
3222
3223
|
}
|
|
3224
|
+
if (update.zIndex !== undefined && update.zIndex !== existing.zIndex) {
|
|
3225
|
+
nextPatch.zIndex = update.zIndex;
|
|
3226
|
+
}
|
|
3223
3227
|
if (Object.keys(nextPatch).length === 0) {
|
|
3224
3228
|
skipped++;
|
|
3225
3229
|
continue;
|
|
@@ -3977,4 +3981,5 @@ class CanvasStateManager {
|
|
|
3977
3981
|
// Module-level singleton — safe because Bun is single-threaded and this
|
|
3978
3982
|
// module is imported once per process. Agent tools and the HTTP server share
|
|
3979
3983
|
// the same instance; no locking needed.
|
|
3984
|
+
export const boardCatalogEpoch = randomUUID();
|
|
3980
3985
|
export const canvasState = new CanvasStateManager();
|