pmx-canvas 0.9.3 → 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 +9418 -2016
- package/CHANGELOG.md +54 -1
- 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/mcp/canvas-access.d.ts +1 -12
- package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
- package/dist/types/mcp/server.d.ts +2 -0
- package/dist/types/server/ax-wait.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +11 -6
- package/dist/types/server/operations/http.d.ts +10 -0
- package/dist/types/server/operations/index.d.ts +2 -2
- package/dist/types/server/operations/invoker.d.ts +4 -3
- package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
- package/dist/types/server/operations/mcp.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +1 -0
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -2
- package/dist/types/server/operations/types.d.ts +12 -2
- package/dist/types/server/server.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/webpage-node.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/api-reference.md +2 -2
- package/docs/cli.md +1 -1
- 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 +82 -1
- package/docs/mcp.md +101 -21
- 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 +402 -13
- package/docs/product-vision-2026-09.md +9 -0
- package/docs/sdk.md +5 -2
- 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 +9418 -2016
- package/skills/pmx-canvas/SKILL.md +19 -21
- package/skills/pmx-canvas/references/full-reference.md +44 -6
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
- 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/ToolRail.tsx +2 -0
- 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/mcp/canvas-access.ts +6 -47
- package/src/mcp/resource-subscriptions.ts +181 -0
- package/src/mcp/server.ts +185 -219
- package/src/server/ax-wait.ts +21 -6
- package/src/server/board-export.ts +35 -24
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +6 -2
- 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 +30 -12
- package/src/server/operations/http.ts +69 -21
- package/src/server/operations/index.ts +8 -1
- package/src/server/operations/invoker.ts +53 -5
- package/src/server/operations/mcp-contracts.ts +354 -0
- package/src/server/operations/mcp.ts +153 -18
- package/src/server/operations/ops/ax-await.ts +18 -10
- package/src/server/operations/ops/ax-read.ts +2 -0
- package/src/server/operations/ops/ax-timeline.ts +2 -2
- package/src/server/operations/ops/batch.ts +27 -8
- package/src/server/operations/ops/boards.ts +1 -3
- package/src/server/operations/ops/nodes.ts +18 -3
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +24 -4
- package/src/server/operations/types.ts +16 -2
- package/src/server/server.ts +17 -59
- package/src/server/tour-control.ts +66 -6
- package/src/server/webpage-node.ts +9 -4
- 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
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"collapsed": false,
|
|
21
21
|
"pinned": false,
|
|
22
22
|
"data": {
|
|
23
|
+
"display": "heading",
|
|
23
24
|
"title": "① Start here",
|
|
24
25
|
"content": "What the agent is doing, what context it holds, what it decided, and the tool calls behind it.",
|
|
25
26
|
"strictSize": true,
|
|
@@ -387,6 +388,7 @@
|
|
|
387
388
|
"collapsed": false,
|
|
388
389
|
"pinned": false,
|
|
389
390
|
"data": {
|
|
391
|
+
"display": "heading",
|
|
390
392
|
"title": "② Files & review",
|
|
391
393
|
"content": "Inline file text, delimited data as a table, unified diffs, images, and cached webpage snapshots.",
|
|
392
394
|
"strictSize": true,
|
|
@@ -683,6 +685,7 @@
|
|
|
683
685
|
"collapsed": false,
|
|
684
686
|
"pinned": false,
|
|
685
687
|
"data": {
|
|
688
|
+
"display": "heading",
|
|
686
689
|
"title": "③ Diagrams & surfaces",
|
|
687
690
|
"content": "Mermaid renders from plain text; HTML and MCP App nodes are sandboxed iframes the agent authors.",
|
|
688
691
|
"strictSize": true,
|
|
@@ -840,12 +843,12 @@
|
|
|
840
843
|
"collapsed": false,
|
|
841
844
|
"pinned": false,
|
|
842
845
|
"data": {
|
|
843
|
-
"html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n
|
|
846
|
+
"html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n body { margin:0; font:14px/1.5 var(--font-ui); background:transparent; color:var(--c-text); padding:16px; }\n p.sub { margin:0 0 16px; font-size:12px; color:var(--c-muted); }\n ul { list-style:none; margin:0; padding:0; display:grid; gap:8px; }\n li { display:flex; align-items:center; gap:10px; padding:9px 12px; border:1px solid var(--c-line); border-radius:6px; background:var(--c-panel-soft); }\n .dot { width:8px; height:8px; border-radius:99px; flex:0 0 auto; }\n .ok .dot { background:var(--c-ok); } .warn .dot { background:var(--c-warn); } .run .dot { background:var(--c-agent); }\n .name { flex:1; } .val { font:12px var(--font-mono); color:var(--c-muted); }\n</style></head><body>\n <p class=\"sub\">Raw HTML in a sandboxed iframe. No scripts, no network, no build step.</p>\n <ul>\n <li class=\"ok\"><span class=\"dot\"></span><span class=\"name\">typecheck</span><span class=\"val\">passing</span></li>\n <li class=\"ok\"><span class=\"dot\"></span><span class=\"name\">unit tests</span><span class=\"val\">612 passed</span></li>\n <li class=\"run\"><span class=\"dot\"></span><span class=\"name\">web-canvas e2e</span><span class=\"val\">running</span></li>\n <li class=\"warn\"><span class=\"dot\"></span><span class=\"name\">bundle size</span><span class=\"val\">+4.1% vs 0.4.6</span></li>\n <li class=\"ok\"><span class=\"dot\"></span><span class=\"name\">pack dry-run</span><span class=\"val\">clean</span></li>\n </ul>\n</body></html>",
|
|
844
847
|
"summary": "Five release gates with pass/running/warning status for 0.4.7.",
|
|
845
848
|
"title": "Release gates",
|
|
846
849
|
"strictSize": true,
|
|
847
|
-
"contentSummary": "
|
|
848
|
-
"agentSummary": "Five release gates with pass/running/warning status for 0.4.7.\
|
|
850
|
+
"contentSummary": "Raw HTML in a sandboxed iframe. No scripts, no network, no build step. typecheck passing unit tests 612 passed web-canvas e2e running bundle size +4.1% vs 0.4.6 pack dry-run clean",
|
|
851
|
+
"agentSummary": "Five release gates with pass/running/warning status for 0.4.7.\nRaw HTML in a sandboxed iframe. No scripts, no network, no build step. typecheck passing unit tests 612 passed web-canvas e2e running bundle size +4.1% vs 0.4.6 pack dry-run clean"
|
|
849
852
|
},
|
|
850
853
|
"createdBy": {
|
|
851
854
|
"actor": "agent",
|
|
@@ -861,7 +864,7 @@
|
|
|
861
864
|
"freshnessStartedAt": "2026-08-01T09:00:00.000Z",
|
|
862
865
|
"kind": "html",
|
|
863
866
|
"title": "Release gates",
|
|
864
|
-
"content": "Five release gates with pass/running/warning status for 0.4.7.\
|
|
867
|
+
"content": "Five release gates with pass/running/warning status for 0.4.7.\nRaw HTML in a sandboxed iframe. No scripts, no network, no build step. typecheck passing unit tests 612 passed web-canvas e2e running bundle size +4.1% vs 0.4.6 pack dry-run clean",
|
|
865
868
|
"path": null,
|
|
866
869
|
"url": null,
|
|
867
870
|
"surfaceUrl": "/api/canvas/surface/html-panel",
|
|
@@ -883,7 +886,7 @@
|
|
|
883
886
|
"pinned": false,
|
|
884
887
|
"data": {
|
|
885
888
|
"mode": "ext-app",
|
|
886
|
-
"html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n
|
|
889
|
+
"html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n body { margin:0; font:14px/1.5 var(--font-ui); background:var(--c-panel); color:var(--c-text); }\n main { padding:16px; display:grid; gap:12px; }\n .row { display:grid; grid-template-columns:1fr auto; align-items:center; gap:12px; padding:11px 13px; border:1px solid var(--c-line); border-radius:6px; background:var(--c-panel-soft); }\n .row b { font-weight:600; font-size:13px; } .row small { display:block; color:var(--c-muted); font-size:12px; }\n .pill { font:11px var(--font-mono); padding:3px 9px; border-radius:99px; background:var(--c-panel); color:var(--c-text-soft); border:1px solid var(--c-line); }\n footer { padding:12px 16px; border-top:1px solid var(--c-line); color:var(--c-muted); font-size:12px; }\n</style></head><body>\n <main>\n <div class=\"row\"><span><b>Nodes on board</b><small>across 8 spatial bands</small></span><span class=\"pill\">69</span></div>\n <div class=\"row\"><span><b>Edges</b><small>flow · depends-on · relation · references</small></span><span class=\"pill\">6</span></div>\n <div class=\"row\"><span><b>Context pins</b><small>read via canvas://pinned-context</small></span><span class=\"pill\">3</span></div>\n <div class=\"row\"><span><b>Groups</b><small>one example frame</small></span><span class=\"pill\">1</span></div>\n </main>\n <footer>An MCP App surface rendered through the app bridge from inline HTML — no live server session required.</footer>\n</body></html>",
|
|
887
890
|
"serverName": "pmx-demo",
|
|
888
891
|
"toolName": "inspect_board",
|
|
889
892
|
"title": "Board inspector",
|
|
@@ -944,6 +947,7 @@
|
|
|
944
947
|
"collapsed": false,
|
|
945
948
|
"pinned": false,
|
|
946
949
|
"data": {
|
|
950
|
+
"display": "heading",
|
|
947
951
|
"title": "④ Charts",
|
|
948
952
|
"content": "Every canonical graph type, then the Tufte primitives in the compact bottom row.",
|
|
949
953
|
"strictSize": true,
|
|
@@ -1030,7 +1034,6 @@
|
|
|
1030
1034
|
"ms": 104
|
|
1031
1035
|
}
|
|
1032
1036
|
],
|
|
1033
|
-
"height": 245,
|
|
1034
1037
|
"xKey": "week",
|
|
1035
1038
|
"yKey": "ms"
|
|
1036
1039
|
},
|
|
@@ -1075,8 +1078,7 @@
|
|
|
1075
1078
|
}
|
|
1076
1079
|
],
|
|
1077
1080
|
"xKey": "week",
|
|
1078
|
-
"yKey": "ms"
|
|
1079
|
-
"height": 245
|
|
1081
|
+
"yKey": "ms"
|
|
1080
1082
|
},
|
|
1081
1083
|
"strictSize": true
|
|
1082
1084
|
},
|
|
@@ -1161,7 +1163,6 @@
|
|
|
1161
1163
|
"prs": 61
|
|
1162
1164
|
}
|
|
1163
1165
|
],
|
|
1164
|
-
"height": 245,
|
|
1165
1166
|
"xKey": "week",
|
|
1166
1167
|
"yKey": "prs",
|
|
1167
1168
|
"colorBy": "series",
|
|
@@ -1210,8 +1211,7 @@
|
|
|
1210
1211
|
"xKey": "week",
|
|
1211
1212
|
"yKey": "prs",
|
|
1212
1213
|
"colorBy": "series",
|
|
1213
|
-
"highlight": "max"
|
|
1214
|
-
"height": 245
|
|
1214
|
+
"highlight": "max"
|
|
1215
1215
|
},
|
|
1216
1216
|
"strictSize": true
|
|
1217
1217
|
},
|
|
@@ -1292,7 +1292,6 @@
|
|
|
1292
1292
|
"nodes": 58
|
|
1293
1293
|
}
|
|
1294
1294
|
],
|
|
1295
|
-
"height": 245,
|
|
1296
1295
|
"xKey": "day",
|
|
1297
1296
|
"yKey": "nodes"
|
|
1298
1297
|
},
|
|
@@ -1333,8 +1332,7 @@
|
|
|
1333
1332
|
}
|
|
1334
1333
|
],
|
|
1335
1334
|
"xKey": "day",
|
|
1336
|
-
"yKey": "nodes"
|
|
1337
|
-
"height": 245
|
|
1335
|
+
"yKey": "nodes"
|
|
1338
1336
|
},
|
|
1339
1337
|
"strictSize": true
|
|
1340
1338
|
},
|
|
@@ -1411,7 +1409,6 @@
|
|
|
1411
1409
|
"count": 14
|
|
1412
1410
|
}
|
|
1413
1411
|
],
|
|
1414
|
-
"height": 245,
|
|
1415
1412
|
"nameKey": "type",
|
|
1416
1413
|
"valueKey": "count",
|
|
1417
1414
|
"showLegend": true,
|
|
@@ -1450,8 +1447,7 @@
|
|
|
1450
1447
|
}
|
|
1451
1448
|
],
|
|
1452
1449
|
"nameKey": "type",
|
|
1453
|
-
"valueKey": "count"
|
|
1454
|
-
"height": 245
|
|
1450
|
+
"valueKey": "count"
|
|
1455
1451
|
},
|
|
1456
1452
|
"strictSize": true
|
|
1457
1453
|
},
|
|
@@ -1536,7 +1532,6 @@
|
|
|
1536
1532
|
"e2e": 2.4
|
|
1537
1533
|
}
|
|
1538
1534
|
],
|
|
1539
|
-
"height": 245,
|
|
1540
1535
|
"xKey": "week",
|
|
1541
1536
|
"series": [
|
|
1542
1537
|
"build",
|
|
@@ -1590,8 +1585,7 @@
|
|
|
1590
1585
|
"build",
|
|
1591
1586
|
"test",
|
|
1592
1587
|
"e2e"
|
|
1593
|
-
]
|
|
1594
|
-
"height": 245
|
|
1588
|
+
]
|
|
1595
1589
|
},
|
|
1596
1590
|
"strictSize": true
|
|
1597
1591
|
},
|
|
@@ -1672,7 +1666,6 @@
|
|
|
1672
1666
|
"hours": 13.9
|
|
1673
1667
|
}
|
|
1674
1668
|
],
|
|
1675
|
-
"height": 245,
|
|
1676
1669
|
"xKey": "week",
|
|
1677
1670
|
"barKey": "prs",
|
|
1678
1671
|
"lineKey": "hours",
|
|
@@ -1716,8 +1709,7 @@
|
|
|
1716
1709
|
],
|
|
1717
1710
|
"xKey": "week",
|
|
1718
1711
|
"barKey": "prs",
|
|
1719
|
-
"lineKey": "hours"
|
|
1720
|
-
"height": 245
|
|
1712
|
+
"lineKey": "hours"
|
|
1721
1713
|
},
|
|
1722
1714
|
"strictSize": true
|
|
1723
1715
|
},
|
|
@@ -1808,7 +1800,6 @@
|
|
|
1808
1800
|
"files": 11
|
|
1809
1801
|
}
|
|
1810
1802
|
],
|
|
1811
|
-
"height": 245,
|
|
1812
1803
|
"xKey": "changed",
|
|
1813
1804
|
"yKey": "hours",
|
|
1814
1805
|
"zKey": "files"
|
|
@@ -1861,8 +1852,7 @@
|
|
|
1861
1852
|
],
|
|
1862
1853
|
"xKey": "changed",
|
|
1863
1854
|
"yKey": "hours",
|
|
1864
|
-
"zKey": "files"
|
|
1865
|
-
"height": 245
|
|
1855
|
+
"zKey": "files"
|
|
1866
1856
|
},
|
|
1867
1857
|
"strictSize": true
|
|
1868
1858
|
},
|
|
@@ -1948,7 +1938,6 @@
|
|
|
1948
1938
|
"v046": 58
|
|
1949
1939
|
}
|
|
1950
1940
|
],
|
|
1951
|
-
"height": 245,
|
|
1952
1941
|
"axisKey": "surface",
|
|
1953
1942
|
"metrics": [
|
|
1954
1943
|
"v047",
|
|
@@ -2001,8 +1990,7 @@
|
|
|
2001
1990
|
"metrics": [
|
|
2002
1991
|
"v047",
|
|
2003
1992
|
"v046"
|
|
2004
|
-
]
|
|
2005
|
-
"height": 245
|
|
1993
|
+
]
|
|
2006
1994
|
},
|
|
2007
1995
|
"strictSize": true
|
|
2008
1996
|
},
|
|
@@ -2035,7 +2023,7 @@
|
|
|
2035
2023
|
},
|
|
2036
2024
|
"size": {
|
|
2037
2025
|
"width": 520,
|
|
2038
|
-
"height":
|
|
2026
|
+
"height": 120
|
|
2039
2027
|
},
|
|
2040
2028
|
"zIndex": 1,
|
|
2041
2029
|
"collapsed": false,
|
|
@@ -2095,7 +2083,6 @@
|
|
|
2095
2083
|
"sec": 72
|
|
2096
2084
|
}
|
|
2097
2085
|
],
|
|
2098
|
-
"height": 160,
|
|
2099
2086
|
"valueKey": "sec",
|
|
2100
2087
|
"fill": true,
|
|
2101
2088
|
"showEndDot": true,
|
|
@@ -2154,8 +2141,7 @@
|
|
|
2154
2141
|
"fill": true,
|
|
2155
2142
|
"showEndDot": true,
|
|
2156
2143
|
"showMinMax": true,
|
|
2157
|
-
"showValue": true
|
|
2158
|
-
"height": 160
|
|
2144
|
+
"showValue": true
|
|
2159
2145
|
},
|
|
2160
2146
|
"strictSize": true
|
|
2161
2147
|
},
|
|
@@ -2188,7 +2174,7 @@
|
|
|
2188
2174
|
},
|
|
2189
2175
|
"size": {
|
|
2190
2176
|
"width": 520,
|
|
2191
|
-
"height":
|
|
2177
|
+
"height": 240
|
|
2192
2178
|
},
|
|
2193
2179
|
"zIndex": 1,
|
|
2194
2180
|
"collapsed": false,
|
|
@@ -2305,7 +2291,7 @@
|
|
|
2305
2291
|
},
|
|
2306
2292
|
"size": {
|
|
2307
2293
|
"width": 520,
|
|
2308
|
-
"height":
|
|
2294
|
+
"height": 260
|
|
2309
2295
|
},
|
|
2310
2296
|
"zIndex": 1,
|
|
2311
2297
|
"collapsed": false,
|
|
@@ -2363,7 +2349,7 @@
|
|
|
2363
2349
|
]
|
|
2364
2350
|
}
|
|
2365
2351
|
],
|
|
2366
|
-
"height":
|
|
2352
|
+
"height": 192,
|
|
2367
2353
|
"labelKey": "label",
|
|
2368
2354
|
"valueKey": "value",
|
|
2369
2355
|
"targetKey": "target",
|
|
@@ -2419,7 +2405,7 @@
|
|
|
2419
2405
|
"labelKey": "label",
|
|
2420
2406
|
"targetKey": "target",
|
|
2421
2407
|
"rangesKey": "ranges",
|
|
2422
|
-
"height":
|
|
2408
|
+
"height": 192
|
|
2423
2409
|
},
|
|
2424
2410
|
"strictSize": true
|
|
2425
2411
|
},
|
|
@@ -2452,7 +2438,7 @@
|
|
|
2452
2438
|
},
|
|
2453
2439
|
"size": {
|
|
2454
2440
|
"width": 520,
|
|
2455
|
-
"height":
|
|
2441
|
+
"height": 300
|
|
2456
2442
|
},
|
|
2457
2443
|
"zIndex": 1,
|
|
2458
2444
|
"collapsed": false,
|
|
@@ -2500,7 +2486,7 @@
|
|
|
2500
2486
|
"after": 59
|
|
2501
2487
|
}
|
|
2502
2488
|
],
|
|
2503
|
-
"height":
|
|
2489
|
+
"height": 224,
|
|
2504
2490
|
"labelKey": "module",
|
|
2505
2491
|
"beforeKey": "before",
|
|
2506
2492
|
"afterKey": "after",
|
|
@@ -2550,7 +2536,7 @@
|
|
|
2550
2536
|
"beforeLabel": "0.4.6",
|
|
2551
2537
|
"afterLabel": "0.4.7",
|
|
2552
2538
|
"colorByDirection": true,
|
|
2553
|
-
"height":
|
|
2539
|
+
"height": 224
|
|
2554
2540
|
},
|
|
2555
2541
|
"strictSize": true
|
|
2556
2542
|
},
|
|
@@ -2589,6 +2575,7 @@
|
|
|
2589
2575
|
"collapsed": false,
|
|
2590
2576
|
"pinned": false,
|
|
2591
2577
|
"data": {
|
|
2578
|
+
"display": "heading",
|
|
2592
2579
|
"title": "⑤ Structured UI",
|
|
2593
2580
|
"content": "json-render nodes: an agent posts a validated component spec and the canvas renders real UI.",
|
|
2594
2581
|
"strictSize": true,
|
|
@@ -2637,7 +2624,8 @@
|
|
|
2637
2624
|
"type": "Stack",
|
|
2638
2625
|
"props": {
|
|
2639
2626
|
"direction": "vertical",
|
|
2640
|
-
"gap": "md"
|
|
2627
|
+
"gap": "md",
|
|
2628
|
+
"align": "stretch"
|
|
2641
2629
|
},
|
|
2642
2630
|
"children": [
|
|
2643
2631
|
"head",
|
|
@@ -2845,7 +2833,10 @@
|
|
|
2845
2833
|
"root": "form",
|
|
2846
2834
|
"state": {
|
|
2847
2835
|
"form": {
|
|
2848
|
-
"
|
|
2836
|
+
"summary": "",
|
|
2837
|
+
"area": "",
|
|
2838
|
+
"severity": "major",
|
|
2839
|
+
"detail": ""
|
|
2849
2840
|
}
|
|
2850
2841
|
},
|
|
2851
2842
|
"elements": {
|
|
@@ -2865,7 +2856,8 @@
|
|
|
2865
2856
|
"type": "Stack",
|
|
2866
2857
|
"props": {
|
|
2867
2858
|
"direction": "vertical",
|
|
2868
|
-
"gap": "md"
|
|
2859
|
+
"gap": "md",
|
|
2860
|
+
"align": "stretch"
|
|
2869
2861
|
},
|
|
2870
2862
|
"children": [
|
|
2871
2863
|
"summary",
|
|
@@ -2885,6 +2877,9 @@
|
|
|
2885
2877
|
"label": "Summary",
|
|
2886
2878
|
"name": "summary",
|
|
2887
2879
|
"type": "text",
|
|
2880
|
+
"value": {
|
|
2881
|
+
"$bindState": "/form/summary"
|
|
2882
|
+
},
|
|
2888
2883
|
"placeholder": "One line, user-facing"
|
|
2889
2884
|
},
|
|
2890
2885
|
"children": [],
|
|
@@ -2896,6 +2891,9 @@
|
|
|
2896
2891
|
"props": {
|
|
2897
2892
|
"label": "Area",
|
|
2898
2893
|
"name": "area",
|
|
2894
|
+
"value": {
|
|
2895
|
+
"$bindState": "/form/area"
|
|
2896
|
+
},
|
|
2899
2897
|
"options": [
|
|
2900
2898
|
"server",
|
|
2901
2899
|
"client",
|
|
@@ -2932,6 +2930,9 @@
|
|
|
2932
2930
|
"label": "Reproduction",
|
|
2933
2931
|
"name": "detail",
|
|
2934
2932
|
"rows": 4,
|
|
2933
|
+
"value": {
|
|
2934
|
+
"$bindState": "/form/detail"
|
|
2935
|
+
},
|
|
2935
2936
|
"placeholder": "Steps, expected, actual"
|
|
2936
2937
|
},
|
|
2937
2938
|
"children": [],
|
|
@@ -3043,7 +3044,8 @@
|
|
|
3043
3044
|
"type": "Stack",
|
|
3044
3045
|
"props": {
|
|
3045
3046
|
"direction": "vertical",
|
|
3046
|
-
"gap": "md"
|
|
3047
|
+
"gap": "md",
|
|
3048
|
+
"align": "stretch"
|
|
3047
3049
|
},
|
|
3048
3050
|
"children": [
|
|
3049
3051
|
"title",
|
|
@@ -3206,6 +3208,7 @@
|
|
|
3206
3208
|
"collapsed": false,
|
|
3207
3209
|
"pinned": false,
|
|
3208
3210
|
"data": {
|
|
3211
|
+
"display": "heading",
|
|
3209
3212
|
"title": "⑥ Grouping",
|
|
3210
3213
|
"content": "A group says \"these belong together\". One frame is enough — bands carry a board’s structure.",
|
|
3211
3214
|
"strictSize": true,
|
|
@@ -3289,6 +3292,7 @@
|
|
|
3289
3292
|
"data": {
|
|
3290
3293
|
"phase": "review",
|
|
3291
3294
|
"detail": "6 of 7 items merged",
|
|
3295
|
+
"progress": 85.71428571428571,
|
|
3292
3296
|
"message": "Demo board is the last one open",
|
|
3293
3297
|
"level": "ok",
|
|
3294
3298
|
"title": "Sprint state",
|
|
@@ -3418,6 +3422,7 @@
|
|
|
3418
3422
|
"collapsed": false,
|
|
3419
3423
|
"pinned": false,
|
|
3420
3424
|
"data": {
|
|
3425
|
+
"display": "heading",
|
|
3421
3426
|
"title": "⑦ HTML primitives",
|
|
3422
3427
|
"content": "All 21 generated communication surfaces — self-contained sandboxed documents, no build step.",
|
|
3423
3428
|
"strictSize": true,
|
|
@@ -3458,7 +3463,7 @@
|
|
|
3458
3463
|
"collapsed": false,
|
|
3459
3464
|
"pinned": false,
|
|
3460
3465
|
"data": {
|
|
3461
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>How should the demo board be generated?</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / choice-grid</div><h1>How should the demo board be generated?</h1><p>Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\">\n <article class=\"card emphasis\">\n <div class=\"small\">Choice 1</div>\n <h2>Hand-written fixture</h2>\n <p>Edit demo-state.json directly.</p>\n <p><strong>Tradeoff:</strong> Fastest to start, impossible to keep correct.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>No tooling</li><li>Full control of every byte</li></ul></div><div><h3>Cons</h3><ul><li>Data shapes drift from the server</li><li>Generated HTML surfaces cannot be authored by hand</li></ul></div></div>\n <pre><code>// 1100 lines of JSON nobody dares touch</code></pre>\n </article>\n <article class=\"card \">\n <div class=\"small\">Choice 2</div>\n <h2>Generate through the HTTP API</h2>\n <p>A committed script replays the real create path.</p>\n <p><strong>Tradeoff:</strong> Needs a throwaway server during generation.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>Shapes correct by construction</li><li>Regenerates after schema changes</li><li>Asserts its own invariants</li></ul></div><div><h3>Cons</h3><ul><li>One more script to maintain</li></ul></div></div>\n <pre><code>await api("POST", "/api/canvas/node", { type: "mermaid", ... })</code></pre>\n </article>\n <article class=\"card \">\n <div class=\"small\">Choice 3</div>\n <h2>Snapshot a live board</h2>\n <p>Curate by hand in the browser, then export.</p>\n <p><strong>Tradeoff:</strong> Pleasant to author, unreproducible.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>Visual authoring</li><li>No layout maths</li></ul></div><div><h3>Cons</h3><ul><li>Not reviewable in a diff</li><li>Cannot be regenerated</li></ul></div></div>\n \n </article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"items\": [\n {\n \"title\": \"Hand-written fixture\",\n \"summary\": \"Edit demo-state.json directly.\",\n \"tradeoff\": \"Fastest to start, impossible to keep correct.\",\n \"pros\": [\n \"No tooling\",\n \"Full control of every byte\"\n ],\n \"cons\": [\n \"Data shapes drift from the server\",\n \"Generated HTML surfaces cannot be authored by hand\"\n ],\n \"code\": \"// 1100 lines of JSON nobody dares touch\"\n },\n {\n \"title\": \"Generate through the HTTP API\",\n \"summary\": \"A committed script replays the real create path.\",\n \"tradeoff\": \"Needs a throwaway server during generation.\",\n \"pros\": [\n \"Shapes correct by construction\",\n \"Regenerates after schema changes\",\n \"Asserts its own invariants\"\n ],\n \"cons\": [\n \"One more script to maintain\"\n ],\n \"code\": \"await api(\\\"POST\\\", \\\"/api/canvas/node\\\", { type: \\\"mermaid\\\", ... })\"\n },\n {\n \"title\": \"Snapshot a live board\",\n \"summary\": \"Curate by hand in the browser, then export.\",\n \"tradeoff\": \"Pleasant to author, unreproducible.\",\n \"pros\": [\n \"Visual authoring\",\n \"No layout maths\"\n ],\n \"cons\": [\n \"Not reviewable in a diff\",\n \"Cannot be regenerated\"\n ]\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
3466
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>How should the demo board be generated?</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / choice-grid</div><h1>How should the demo board be generated?</h1><p>Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets.</p></div>\n </header>\n <section class=\"grid\">\n <article class=\"card emphasis\">\n <div class=\"small\">Choice 1</div>\n <h2>Hand-written fixture</h2>\n <p>Edit demo-state.json directly.</p>\n <p><strong>Tradeoff:</strong> Fastest to start, impossible to keep correct.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>No tooling</li><li>Full control of every byte</li></ul></div><div><h3>Cons</h3><ul><li>Data shapes drift from the server</li><li>Generated HTML surfaces cannot be authored by hand</li></ul></div></div>\n <pre><code>// 1100 lines of JSON nobody dares touch</code></pre>\n </article>\n <article class=\"card \">\n <div class=\"small\">Choice 2</div>\n <h2>Generate through the HTTP API</h2>\n <p>A committed script replays the real create path.</p>\n <p><strong>Tradeoff:</strong> Needs a throwaway server during generation.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>Shapes correct by construction</li><li>Regenerates after schema changes</li><li>Asserts its own invariants</li></ul></div><div><h3>Cons</h3><ul><li>One more script to maintain</li></ul></div></div>\n <pre><code>await api("POST", "/api/canvas/node", { type: "mermaid", ... })</code></pre>\n </article>\n <article class=\"card \">\n <div class=\"small\">Choice 3</div>\n <h2>Snapshot a live board</h2>\n <p>Curate by hand in the browser, then export.</p>\n <p><strong>Tradeoff:</strong> Pleasant to author, unreproducible.</p>\n <div class=\"two\"><div><h3>Pros</h3><ul><li>Visual authoring</li><li>No layout maths</li></ul></div><div><h3>Cons</h3><ul><li>Not reviewable in a diff</li><li>Cannot be regenerated</li></ul></div></div>\n \n </article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"items\": [\n {\n \"title\": \"Hand-written fixture\",\n \"summary\": \"Edit demo-state.json directly.\",\n \"tradeoff\": \"Fastest to start, impossible to keep correct.\",\n \"pros\": [\n \"No tooling\",\n \"Full control of every byte\"\n ],\n \"cons\": [\n \"Data shapes drift from the server\",\n \"Generated HTML surfaces cannot be authored by hand\"\n ],\n \"code\": \"// 1100 lines of JSON nobody dares touch\"\n },\n {\n \"title\": \"Generate through the HTTP API\",\n \"summary\": \"A committed script replays the real create path.\",\n \"tradeoff\": \"Needs a throwaway server during generation.\",\n \"pros\": [\n \"Shapes correct by construction\",\n \"Regenerates after schema changes\",\n \"Asserts its own invariants\"\n ],\n \"cons\": [\n \"One more script to maintain\"\n ],\n \"code\": \"await api(\\\"POST\\\", \\\"/api/canvas/node\\\", { type: \\\"mermaid\\\", ... })\"\n },\n {\n \"title\": \"Snapshot a live board\",\n \"summary\": \"Curate by hand in the browser, then export.\",\n \"tradeoff\": \"Pleasant to author, unreproducible.\",\n \"pros\": [\n \"Visual authoring\",\n \"No layout maths\"\n ],\n \"cons\": [\n \"Not reviewable in a diff\",\n \"Cannot be regenerated\"\n ]\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
3462
3467
|
"htmlPrimitive": "choice-grid",
|
|
3463
3468
|
"primitiveData": {
|
|
3464
3469
|
"items": [
|
|
@@ -3508,7 +3513,7 @@
|
|
|
3508
3513
|
"description": "Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets.",
|
|
3509
3514
|
"title": "How should the demo board be generated?",
|
|
3510
3515
|
"strictSize": true,
|
|
3511
|
-
"contentSummary": "How should the demo board be generated? PMX HTML primitive / choice-grid How should the demo board be generated? Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets.
|
|
3516
|
+
"contentSummary": "How should the demo board be generated? PMX HTML primitive / choice-grid How should the demo board be generated? Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets. Choice 1 Hand-written fixture Edit demo-state.json directly. Tradeoff: Fastest to start, impossible to keep correct. Pros No tooling Full control of every byte Cons Data shapes drift from the server Generated HTML surfaces cannot be authored by hand // 1100 lines of JSON nobody dares touch Choice 2 Generate through the HTTP API A committed script replays the real create path. Tradeoff: Needs a throwaway server during generation. Pros Shapes correct by construction Regenerates after schema changes Asserts its own invariants Cons One more script to maintain await api(\"POST\", \"/api/canvas/node\", { type: \"mermaid\", ... }) Choice 3 Snapshot a live board Curate by hand in the browser, then export. Tra...",
|
|
3512
3517
|
"agentSummary": "Side-by-side options with tradeoffs, pros, cons, and code or evidence snippets."
|
|
3513
3518
|
},
|
|
3514
3519
|
"createdBy": {
|
|
@@ -3546,7 +3551,7 @@
|
|
|
3546
3551
|
"collapsed": false,
|
|
3547
3552
|
"pinned": false,
|
|
3548
3553
|
"data": {
|
|
3549
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Demo board rebuild</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / plan-timeline</div><h1>Demo board rebuild</h1><p>Implementation plan with milestones, data flow, code checkpoints, and risks.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Milestones</h2><div class=\"timeline\">\n <div class=\"step\"><div class=\"dot\">1</div><div class=\"card\"><h3>Inventory the surfaces <span class=\"badge ok\">done</span></h3><p>Every node type, graph type and primitive kind.</p></div></div>\n <div class=\"step\"><div class=\"dot\">2</div><div class=\"card\"><h3>Write the generator <span class=\"badge ok\">done</span></h3><p>Create everything through the real HTTP API.</p></div></div>\n <div class=\"step\"><div class=\"dot\">3</div><div class=\"card\"><h3>Lay out the bands <span class=\"badge info\">active</span></h3><p>Spatial bands with section labels, generous gutters.</p></div></div>\n <div class=\"step\"><div class=\"dot\">4</div><div class=\"card\"><h3>Rewrite the fixture test <span class=\"badge info\">todo</span></h3><p>Keep the structural invariants, add coverage assertions.</p></div></div>\n <div class=\"step\"><div class=\"dot\">5</div><div class=\"card\"><h3>Visual pass <span class=\"badge info\">todo</span></h3><p>Screenshot each band and fix anything blank or clipped.</p></div></div></div></div><div class=\"panel\"><h2>Data Flow</h2><div class=\"flow\"><div class=\"flow-node\"><h3>generator</h3><p>HTTP create</p><strong>temp server</strong></div><div class=\"arrow\">-></div><div class=\"flow-node\"><h3>temp server</h3><p>export + stable ids</p><strong>demo-state.json</strong></div><div class=\"arrow\">-></div><div class=\"flow-node\"><h3>demo-state.json</h3><p>--demo</p><strong>seedDemoCanvas</strong></div></div><h2 style=\"margin-top:18px\">Risks</h2><div class=\"card\"><strong>Browser auto-fit reflows the board on first open</strong><p>Create every node with strictSize.</p></div><div class=\"card\"><strong>Fixture depends on files or network at render time</strong><p>Inline file text, data-URI image, cached webpage snapshot.</p></div><div class=\"card\"><strong>Server ids churn on every regeneration</strong><p>Rewrite ids to stable demo-* keys during export.</p></div></div></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Code Checkpoints</h2><h3>Regenerate</h3><pre><code>bun run scripts/generate-demo-board.ts</code></pre><h3>Verify</h3><pre><code>bun test tests/unit/demo.test.ts</code></pre></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"milestones\": [\n {\n \"title\": \"Inventory the surfaces\",\n \"detail\": \"Every node type, graph type and primitive kind.\",\n \"status\": \"done\"\n },\n {\n \"title\": \"Write the generator\",\n \"detail\": \"Create everything through the real HTTP API.\",\n \"status\": \"done\"\n },\n {\n \"title\": \"Lay out the bands\",\n \"detail\": \"Spatial bands with section labels, generous gutters.\",\n \"status\": \"active\"\n },\n {\n \"title\": \"Rewrite the fixture test\",\n \"detail\": \"Keep the structural invariants, add coverage assertions.\",\n \"status\": \"todo\"\n },\n {\n \"title\": \"Visual pass\",\n \"detail\": \"Screenshot each band and fix anything blank or clipped.\",\n \"status\": \"todo\"\n }\n ],\n \"flow\": [\n {\n \"from\": \"generator\",\n \"to\": \"temp server\",\n \"label\": \"HTTP create\"\n },\n {\n \"from\": \"temp server\",\n \"to\": \"demo-state.json\",\n \"label\": \"export + stable ids\"\n },\n {\n \"from\": \"demo-state.json\",\n \"to\": \"seedDemoCanvas\",\n \"label\": \"--demo\"\n }\n ],\n \"risks\": [\n {\n \"risk\": \"Browser auto-fit reflows the board on first open\",\n \"mitigation\": \"Create every node with strictSize.\"\n },\n {\n \"risk\": \"Fixture depends on files or network at render time\",\n \"mitigation\": \"Inline file text, data-URI image, cached webpage snapshot.\"\n },\n {\n \"risk\": \"Server ids churn on every regeneration\",\n \"mitigation\": \"Rewrite ids to stable demo-* keys during export.\"\n }\n ],\n \"snippets\": [\n {\n \"label\": \"Regenerate\",\n \"code\": \"bun run scripts/generate-demo-board.ts\"\n },\n {\n \"label\": \"Verify\",\n \"code\": \"bun test tests/unit/demo.test.ts\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
3554
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Demo board rebuild</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / plan-timeline</div><h1>Demo board rebuild</h1><p>Implementation plan with milestones, data flow, code checkpoints, and risks.</p></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Milestones</h2><div class=\"timeline\">\n <div class=\"step\"><div class=\"dot\">1</div><div class=\"card\"><h3>Inventory the surfaces <span class=\"badge ok\">done</span></h3><p>Every node type, graph type and primitive kind.</p></div></div>\n <div class=\"step\"><div class=\"dot\">2</div><div class=\"card\"><h3>Write the generator <span class=\"badge ok\">done</span></h3><p>Create everything through the real HTTP API.</p></div></div>\n <div class=\"step\"><div class=\"dot\">3</div><div class=\"card\"><h3>Lay out the bands <span class=\"badge info\">active</span></h3><p>Spatial bands with section labels, generous gutters.</p></div></div>\n <div class=\"step\"><div class=\"dot\">4</div><div class=\"card\"><h3>Rewrite the fixture test <span class=\"badge info\">todo</span></h3><p>Keep the structural invariants, add coverage assertions.</p></div></div>\n <div class=\"step\"><div class=\"dot\">5</div><div class=\"card\"><h3>Visual pass <span class=\"badge info\">todo</span></h3><p>Screenshot each band and fix anything blank or clipped.</p></div></div></div></div><div class=\"panel\"><h2>Data Flow</h2><div class=\"flow\"><div class=\"flow-node\"><h3>generator</h3><p>HTTP create</p><strong>temp server</strong></div><div class=\"arrow\">-></div><div class=\"flow-node\"><h3>temp server</h3><p>export + stable ids</p><strong>demo-state.json</strong></div><div class=\"arrow\">-></div><div class=\"flow-node\"><h3>demo-state.json</h3><p>--demo</p><strong>seedDemoCanvas</strong></div></div><h2 style=\"margin-top:18px\">Risks</h2><div class=\"card\"><strong>Browser auto-fit reflows the board on first open</strong><p>Create every node with strictSize.</p></div><div class=\"card\"><strong>Fixture depends on files or network at render time</strong><p>Inline file text, data-URI image, cached webpage snapshot.</p></div><div class=\"card\"><strong>Server ids churn on every regeneration</strong><p>Rewrite ids to stable demo-* keys during export.</p></div></div></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Code Checkpoints</h2><h3>Regenerate</h3><pre><code>bun run scripts/generate-demo-board.ts</code></pre><h3>Verify</h3><pre><code>bun test tests/unit/demo.test.ts</code></pre></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"milestones\": [\n {\n \"title\": \"Inventory the surfaces\",\n \"detail\": \"Every node type, graph type and primitive kind.\",\n \"status\": \"done\"\n },\n {\n \"title\": \"Write the generator\",\n \"detail\": \"Create everything through the real HTTP API.\",\n \"status\": \"done\"\n },\n {\n \"title\": \"Lay out the bands\",\n \"detail\": \"Spatial bands with section labels, generous gutters.\",\n \"status\": \"active\"\n },\n {\n \"title\": \"Rewrite the fixture test\",\n \"detail\": \"Keep the structural invariants, add coverage assertions.\",\n \"status\": \"todo\"\n },\n {\n \"title\": \"Visual pass\",\n \"detail\": \"Screenshot each band and fix anything blank or clipped.\",\n \"status\": \"todo\"\n }\n ],\n \"flow\": [\n {\n \"from\": \"generator\",\n \"to\": \"temp server\",\n \"label\": \"HTTP create\"\n },\n {\n \"from\": \"temp server\",\n \"to\": \"demo-state.json\",\n \"label\": \"export + stable ids\"\n },\n {\n \"from\": \"demo-state.json\",\n \"to\": \"seedDemoCanvas\",\n \"label\": \"--demo\"\n }\n ],\n \"risks\": [\n {\n \"risk\": \"Browser auto-fit reflows the board on first open\",\n \"mitigation\": \"Create every node with strictSize.\"\n },\n {\n \"risk\": \"Fixture depends on files or network at render time\",\n \"mitigation\": \"Inline file text, data-URI image, cached webpage snapshot.\"\n },\n {\n \"risk\": \"Server ids churn on every regeneration\",\n \"mitigation\": \"Rewrite ids to stable demo-* keys during export.\"\n }\n ],\n \"snippets\": [\n {\n \"label\": \"Regenerate\",\n \"code\": \"bun run scripts/generate-demo-board.ts\"\n },\n {\n \"label\": \"Verify\",\n \"code\": \"bun test tests/unit/demo.test.ts\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
3550
3555
|
"htmlPrimitive": "plan-timeline",
|
|
3551
3556
|
"primitiveData": {
|
|
3552
3557
|
"milestones": [
|
|
@@ -3621,7 +3626,7 @@
|
|
|
3621
3626
|
"description": "Implementation plan with milestones, data flow, code checkpoints, and risks.",
|
|
3622
3627
|
"title": "Demo board rebuild",
|
|
3623
3628
|
"strictSize": true,
|
|
3624
|
-
"contentSummary": "Demo board rebuild PMX HTML primitive / plan-timeline Demo board rebuild Implementation plan with milestones, data flow, code checkpoints, and risks.
|
|
3629
|
+
"contentSummary": "Demo board rebuild PMX HTML primitive / plan-timeline Demo board rebuild Implementation plan with milestones, data flow, code checkpoints, and risks. Milestones 1 Inventory the surfaces done Every node type, graph type and primitive kind. 2 Write the generator done Create everything through the real HTTP API. 3 Lay out the bands active Spatial bands with section labels, generous gutters. 4 Rewrite the fixture test todo Keep the structural invariants, add coverage assertions. 5 Visual pass todo Screenshot each band and fix anything blank or clipped. Data Flow generator HTTP create temp server -> temp server export + stable ids demo-state.json -> demo-state.json --demo seedDemoCanvas Risks Browser auto-fit reflows the board on first open Create every node with strictSize. Fixture depends on files or network at render time Inline file text, data-URI image, cached webpage snapshot. Server...",
|
|
3625
3630
|
"agentSummary": "Implementation plan with milestones, data flow, code checkpoints, and risks."
|
|
3626
3631
|
},
|
|
3627
3632
|
"createdBy": {
|
|
@@ -3659,7 +3664,7 @@
|
|
|
3659
3664
|
"collapsed": false,
|
|
3660
3665
|
"pinned": false,
|
|
3661
3666
|
"data": {
|
|
3662
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Review — auto-fit floor</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / review-sheet</div><h1>Review — auto-fit floor</h1><p>Annotated PR/code review sheet with severity-colored findings and diff excerpts.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Findings</h2>\n <article class=\"card\"><h3><span class=\"badge warn\">warning</span> Auto-fit undid the creation clamp</h3><p class=\"small\">src/client/canvas/auto-fit.ts:63</p><p>The server clamped 200x100 up to 360x180, then the DOM measure persisted 360x132.</p></article>\n <article class=\"card\"><h3><span class=\"badge info\">info</span> Floor now lives in shared/</h3><p class=\"small\">src/shared/node-sizes.ts:20</p><p>Both sides import the same table — a floor only one side knows about is not a floor.</p></article>\n <article class=\"card\"><h3><span class=\"badge ok\">ok</span> strictSize is still the escape hatch</h3><p class=\"small\">src/shared/node-sizes.ts:48</p><p>Deliberately small fixed frames opt out of both the clamp and the auto-fit floor.</p></article></div>\n <div class=\"panel\"><h2>Review Tour</h2><article class=\"card\"><h3>src/shared/node-sizes.ts</h3><p>Owns the per-type readability floor.</p></article><article class=\"card\"><h3>src/client/canvas/auto-fit.ts</h3><p>Applies the floor to the measured height.</p></article></div></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Diff Excerpt</h2><pre><code>- return fitted;\n+ const min = nodeMinSize(node.type);\n+ return min ? Math.max(fitted, min.height) : fitted;</code></pre></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"findings\": [\n {\n \"severity\": \"warning\",\n \"title\": \"Auto-fit undid the creation clamp\",\n \"file\": \"src/client/canvas/auto-fit.ts\",\n \"line\": 63,\n \"detail\": \"The server clamped 200x100 up to 360x180, then the DOM measure persisted 360x132.\"\n },\n {\n \"severity\": \"info\",\n \"title\": \"Floor now lives in shared/\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"line\": 20,\n \"detail\": \"Both sides import the same table — a floor only one side knows about is not a floor.\"\n },\n {\n \"severity\": \"ok\",\n \"title\": \"strictSize is still the escape hatch\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"line\": 48,\n \"detail\": \"Deliberately small fixed frames opt out of both the clamp and the auto-fit floor.\"\n }\n ],\n \"files\": [\n {\n \"path\": \"src/shared/node-sizes.ts\",\n \"why\": \"Owns the per-type readability floor.\"\n },\n {\n \"path\": \"src/client/canvas/auto-fit.ts\",\n \"why\": \"Applies the floor to the measured height.\"\n }\n ],\n \"diff\": \"- return fitted;\\n+ const min = nodeMinSize(node.type);\\n+ return min ? Math.max(fitted, min.height) : fitted;\"\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
3667
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Review — auto-fit floor</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / review-sheet</div><h1>Review — auto-fit floor</h1><p>Annotated PR/code review sheet with severity-colored findings and diff excerpts.</p></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Findings</h2>\n <article class=\"card\"><h3><span class=\"badge warn\">warning</span> Auto-fit undid the creation clamp</h3><p class=\"small\">src/client/canvas/auto-fit.ts:63</p><p>The server clamped 200x100 up to 360x180, then the DOM measure persisted 360x132.</p></article>\n <article class=\"card\"><h3><span class=\"badge info\">info</span> Floor now lives in shared/</h3><p class=\"small\">src/shared/node-sizes.ts:20</p><p>Both sides import the same table — a floor only one side knows about is not a floor.</p></article>\n <article class=\"card\"><h3><span class=\"badge ok\">ok</span> strictSize is still the escape hatch</h3><p class=\"small\">src/shared/node-sizes.ts:48</p><p>Deliberately small fixed frames opt out of both the clamp and the auto-fit floor.</p></article></div>\n <div class=\"panel\"><h2>Review Tour</h2><article class=\"card\"><h3>src/shared/node-sizes.ts</h3><p>Owns the per-type readability floor.</p></article><article class=\"card\"><h3>src/client/canvas/auto-fit.ts</h3><p>Applies the floor to the measured height.</p></article></div></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Diff Excerpt</h2><pre><code>- return fitted;\n+ const min = nodeMinSize(node.type);\n+ return min ? Math.max(fitted, min.height) : fitted;</code></pre></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"findings\": [\n {\n \"severity\": \"warning\",\n \"title\": \"Auto-fit undid the creation clamp\",\n \"file\": \"src/client/canvas/auto-fit.ts\",\n \"line\": 63,\n \"detail\": \"The server clamped 200x100 up to 360x180, then the DOM measure persisted 360x132.\"\n },\n {\n \"severity\": \"info\",\n \"title\": \"Floor now lives in shared/\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"line\": 20,\n \"detail\": \"Both sides import the same table — a floor only one side knows about is not a floor.\"\n },\n {\n \"severity\": \"ok\",\n \"title\": \"strictSize is still the escape hatch\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"line\": 48,\n \"detail\": \"Deliberately small fixed frames opt out of both the clamp and the auto-fit floor.\"\n }\n ],\n \"files\": [\n {\n \"path\": \"src/shared/node-sizes.ts\",\n \"why\": \"Owns the per-type readability floor.\"\n },\n {\n \"path\": \"src/client/canvas/auto-fit.ts\",\n \"why\": \"Applies the floor to the measured height.\"\n }\n ],\n \"diff\": \"- return fitted;\\n+ const min = nodeMinSize(node.type);\\n+ return min ? Math.max(fitted, min.height) : fitted;\"\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
3663
3668
|
"htmlPrimitive": "review-sheet",
|
|
3664
3669
|
"primitiveData": {
|
|
3665
3670
|
"findings": [
|
|
@@ -3700,7 +3705,7 @@
|
|
|
3700
3705
|
"description": "Annotated PR/code review sheet with severity-colored findings and diff excerpts.",
|
|
3701
3706
|
"title": "Review — auto-fit floor",
|
|
3702
3707
|
"strictSize": true,
|
|
3703
|
-
"contentSummary": "Review — auto-fit floor PMX HTML primitive / review-sheet Review — auto-fit floor Annotated PR/code review sheet with severity-colored findings and diff excerpts.
|
|
3708
|
+
"contentSummary": "Review — auto-fit floor PMX HTML primitive / review-sheet Review — auto-fit floor Annotated PR/code review sheet with severity-colored findings and diff excerpts. Findings warning Auto-fit undid the creation clamp src/client/canvas/auto-fit.ts:63 The server clamped 200x100 up to 360x180, then the DOM measure persisted 360x132. info Floor now lives in shared/ src/shared/node-sizes.ts:20 Both sides import the same table — a floor only one side knows about is not a floor. ok strictSize is still the escape hatch src/shared/node-sizes.ts:48 Deliberately small fixed frames opt out of both the clamp and the auto-fit floor. Review Tour src/shared/node-sizes.ts Owns the per-type readability floor. src/client/canvas/auto-fit.ts Applies the floor to the measured height. Diff Excerpt - return fitted; + const min = nodeMinSize(node.type); + return min ? Math.max(fitted, min.height) : fitted;",
|
|
3704
3709
|
"agentSummary": "Annotated PR/code review sheet with severity-colored findings and diff excerpts.",
|
|
3705
3710
|
"embeddedNodeIds": [
|
|
3706
3711
|
"node-sizes"
|
|
@@ -3741,7 +3746,7 @@
|
|
|
3741
3746
|
"collapsed": false,
|
|
3742
3747
|
"pinned": false,
|
|
3743
3748
|
"data": {
|
|
3744
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>PR — replace the demo board</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / pr-writeup</div><h1>PR — replace the demo board</h1><p>Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><h2>Summary</h2><p>Replaces the OKR demo board with a generated catalogue of every public canvas surface.</p><p>The old board used three node types and was dominated by group frames, so it showed almost nothing the product does.</p></section>\n <section class=\"two\" style=\"margin-top:14px\"><div class=\"card\"><h2>Before</h2><ul><li>19 nodes</li><li>3 node types</li><li>3 large group frames</li><li>hand-edited JSON</li></ul></div><div class=\"card emphasis\"><h2>After</h2><ul><li>69 nodes across 8 labelled bands</li><li>all 15 public node types</li><li>all 12 graph types and all 21 HTML primitives</li><li>generated by a committed script</li></ul></div></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>File Tour</h2><details open><summary><strong>scripts/generate-demo-board.ts</strong></summary><p>The generator, and the only place the board is authored.</p><p class=\"small\">Focus: Band layout and offline-safety rules.</p></details><details open><summary><strong>src/server/demo-state.json</strong></summary><p>Generated output — never hand-edited.</p><p class=\"small\">Focus: Stable demo-* ids.</p></details><details open><summary><strong>tests/unit/demo.test.ts</strong></summary><p>Structural invariants plus coverage assertions.</p><p class=\"small\">Focus: Type/graph/primitive coverage checks.</p></details></div>\n <div class=\"panel\"><h2>Review Focus</h2><ul><li>No node overlaps</li><li>Group containment inset</li><li>Nothing that needs disk or network at render time</li></ul><h2 style=\"margin-top:18px\">Tests</h2><ul><li>bun test tests/unit/demo.test.ts</li><li>bun run typecheck</li><li>Visual pass over each band at overview zoom</li></ul></div>\n <div class=\"panel sticky\"><h2>Rollout</h2><ul><li>No migration — the fixture is replaced wholesale</li><li>--demo still refuses to clobber a non-empty canvas</li></ul><button type=\"button\" data-copy-markdown style=\"margin-top:12px\">Copy PR markdown</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"Replaces the OKR demo board with a generated catalogue of every public canvas surface.\",\n \"why\": \"The old board used three node types and was dominated by group frames, so it showed almost nothing the product does.\",\n \"before\": [\n \"19 nodes\",\n \"3 node types\",\n \"3 large group frames\",\n \"hand-edited JSON\"\n ],\n \"after\": [\n \"69 nodes across 8 labelled bands\",\n \"all 15 public node types\",\n \"all 12 graph types and all 21 HTML primitives\",\n \"generated by a committed script\"\n ],\n \"files\": [\n {\n \"path\": \"scripts/generate-demo-board.ts\",\n \"why\": \"The generator, and the only place the board is authored.\",\n \"focus\": \"Band layout and offline-safety rules.\"\n },\n {\n \"path\": \"src/server/demo-state.json\",\n \"why\": \"Generated output — never hand-edited.\",\n \"focus\": \"Stable demo-* ids.\"\n },\n {\n \"path\": \"tests/unit/demo.test.ts\",\n \"why\": \"Structural invariants plus coverage assertions.\",\n \"focus\": \"Type/graph/primitive coverage checks.\"\n }\n ],\n \"reviewFocus\": [\n \"No node overlaps\",\n \"Group containment inset\",\n \"Nothing that needs disk or network at render time\"\n ],\n \"tests\": [\n \"bun test tests/unit/demo.test.ts\",\n \"bun run typecheck\",\n \"Visual pass over each band at overview zoom\"\n ],\n \"rollout\": [\n \"No migration — the fixture is replaced wholesale\",\n \"--demo still refuses to clobber a non-empty canvas\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nfunction prMarkdown() {\n const d = PMX_DATA;\n const lines = ['## Summary', d.summary || '', '', '## Why', d.why || '', '', '## Before / After'];\n (d.before || []).forEach((item) => lines.push('- Before: ' + item));\n (d.after || []).forEach((item) => lines.push('- After: ' + item));\n lines.push('', '## Review Focus');\n (d.reviewFocus || []).forEach((item) => lines.push('- ' + item));\n lines.push('', '## Tests');\n (d.tests || []).forEach((item) => lines.push('- ' + item));\n return lines.join('\\n');\n}\ndocument.querySelector('[data-copy-markdown]')?.addEventListener('click', () => copyText(prMarkdown()));\n</script>\n</body>\n</html>",
|
|
3749
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>PR — replace the demo board</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / pr-writeup</div><h1>PR — replace the demo board</h1><p>Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes.</p></div>\n </header>\n <section class=\"panel\"><h2>Summary</h2><p>Replaces the OKR demo board with a generated catalogue of every public canvas surface.</p><p>The old board used three node types and was dominated by group frames, so it showed almost nothing the product does.</p></section>\n <section class=\"two\" style=\"margin-top:14px\"><div class=\"card\"><h2>Before</h2><ul><li>19 nodes</li><li>3 node types</li><li>3 large group frames</li><li>hand-edited JSON</li></ul></div><div class=\"card emphasis\"><h2>After</h2><ul><li>69 nodes across 8 labelled bands</li><li>all 15 public node types</li><li>all 12 graph types and all 21 HTML primitives</li><li>generated by a committed script</li></ul></div></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>File Tour</h2><details open><summary><strong>scripts/generate-demo-board.ts</strong></summary><p>The generator, and the only place the board is authored.</p><p class=\"small\">Focus: Band layout and offline-safety rules.</p></details><details open><summary><strong>src/server/demo-state.json</strong></summary><p>Generated output — never hand-edited.</p><p class=\"small\">Focus: Stable demo-* ids.</p></details><details open><summary><strong>tests/unit/demo.test.ts</strong></summary><p>Structural invariants plus coverage assertions.</p><p class=\"small\">Focus: Type/graph/primitive coverage checks.</p></details></div>\n <div class=\"panel\"><h2>Review Focus</h2><ul><li>No node overlaps</li><li>Group containment inset</li><li>Nothing that needs disk or network at render time</li></ul><h2 style=\"margin-top:18px\">Tests</h2><ul><li>bun test tests/unit/demo.test.ts</li><li>bun run typecheck</li><li>Visual pass over each band at overview zoom</li></ul></div>\n <div class=\"panel sticky\"><h2>Rollout</h2><ul><li>No migration — the fixture is replaced wholesale</li><li>--demo still refuses to clobber a non-empty canvas</li></ul><button type=\"button\" data-copy-markdown style=\"margin-top:12px\">Copy PR markdown</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"Replaces the OKR demo board with a generated catalogue of every public canvas surface.\",\n \"why\": \"The old board used three node types and was dominated by group frames, so it showed almost nothing the product does.\",\n \"before\": [\n \"19 nodes\",\n \"3 node types\",\n \"3 large group frames\",\n \"hand-edited JSON\"\n ],\n \"after\": [\n \"69 nodes across 8 labelled bands\",\n \"all 15 public node types\",\n \"all 12 graph types and all 21 HTML primitives\",\n \"generated by a committed script\"\n ],\n \"files\": [\n {\n \"path\": \"scripts/generate-demo-board.ts\",\n \"why\": \"The generator, and the only place the board is authored.\",\n \"focus\": \"Band layout and offline-safety rules.\"\n },\n {\n \"path\": \"src/server/demo-state.json\",\n \"why\": \"Generated output — never hand-edited.\",\n \"focus\": \"Stable demo-* ids.\"\n },\n {\n \"path\": \"tests/unit/demo.test.ts\",\n \"why\": \"Structural invariants plus coverage assertions.\",\n \"focus\": \"Type/graph/primitive coverage checks.\"\n }\n ],\n \"reviewFocus\": [\n \"No node overlaps\",\n \"Group containment inset\",\n \"Nothing that needs disk or network at render time\"\n ],\n \"tests\": [\n \"bun test tests/unit/demo.test.ts\",\n \"bun run typecheck\",\n \"Visual pass over each band at overview zoom\"\n ],\n \"rollout\": [\n \"No migration — the fixture is replaced wholesale\",\n \"--demo still refuses to clobber a non-empty canvas\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nfunction prMarkdown() {\n const d = PMX_DATA;\n const lines = ['## Summary', d.summary || '', '', '## Why', d.why || '', '', '## Before / After'];\n (d.before || []).forEach((item) => lines.push('- Before: ' + item));\n (d.after || []).forEach((item) => lines.push('- After: ' + item));\n lines.push('', '## Review Focus');\n (d.reviewFocus || []).forEach((item) => lines.push('- ' + item));\n lines.push('', '## Tests');\n (d.tests || []).forEach((item) => lines.push('- ' + item));\n return lines.join('\\n');\n}\ndocument.querySelector('[data-copy-markdown]')?.addEventListener('click', () => copyText(prMarkdown()));\n</script>\n</body>\n</html>",
|
|
3745
3750
|
"htmlPrimitive": "pr-writeup",
|
|
3746
3751
|
"primitiveData": {
|
|
3747
3752
|
"summary": "Replaces the OKR demo board with a generated catalogue of every public canvas surface.",
|
|
@@ -3794,7 +3799,7 @@
|
|
|
3794
3799
|
"summary": "Replaces the OKR demo board with a generated catalogue of every public canvas surface.",
|
|
3795
3800
|
"title": "PR — replace the demo board",
|
|
3796
3801
|
"strictSize": true,
|
|
3797
|
-
"contentSummary": "PR — replace the demo board PMX HTML primitive / pr-writeup PR — replace the demo board Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes.
|
|
3802
|
+
"contentSummary": "PR — replace the demo board PMX HTML primitive / pr-writeup PR — replace the demo board Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes. Summary Replaces the OKR demo board with a generated catalogue of every public canvas surface. The old board used three node types and was dominated by group frames, so it showed almost nothing the product does. Before 19 nodes 3 node types 3 large group frames hand-edited JSON After 69 nodes across 8 labelled bands all 15 public node types all 12 graph types and all 21 HTML primitives generated by a committed script File Tour scripts/generate-demo-board.ts The generator, and the only place the board is authored. Focus: Band layout and offline-safety rules. src/server/demo-state.json Generated output — never hand-edited. Focus: Stable demo-* ids. tests/unit/demo.test.ts Structural invariants plus cov...",
|
|
3798
3803
|
"agentSummary": "Reviewer-ready PR narrative with motivation, before/after, file tour, test plan, and rollout notes."
|
|
3799
3804
|
},
|
|
3800
3805
|
"createdBy": {
|
|
@@ -3832,7 +3837,7 @@
|
|
|
3832
3837
|
"collapsed": false,
|
|
3833
3838
|
"pinned": false,
|
|
3834
3839
|
"data": {
|
|
3835
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Canvas server map</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / system-map</div><h1>Canvas server map</h1><p>Module or architecture map with boxes, relationships, entry points, and hot paths.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><h2>Entry Points</h2><div class=\"swatches\"><span class=\"badge info\">src/mcp/server.ts</span><span class=\"badge info\">src/server/server.ts</span><span class=\"badge info\">src/cli/index.ts</span></div></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><div class=\"small\">entry</div><h2>MCP server</h2><p>stdio transport, 22 tools and 14 resources.</p></article><article class=\"card\"><div class=\"small\">entry</div><h2>HTTP API</h2><p>REST + SSE on Bun.serve.</p></article><article class=\"card\"><div class=\"small\">core</div><h2>Operation registry</h2><p>One definition drives HTTP, MCP and the SDK.</p></article><article class=\"card\"><div class=\"small\">core</div><h2>CanvasStateManager</h2><p>Singleton source of truth; every mutation goes through it.</p></article><article class=\"card\"><div class=\"small\">store</div><h2>SQLite</h2><p>.pmx-canvas/canvas.db — nodes, pins, snapshots, blobs.</p></article><article class=\"card\"><div class=\"small\">view</div><h2>Preact client</h2><p>Renders layout; state survives refresh.</p></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Relationships</h2><div class=\"flow\"><div class=\"flow-node\"><strong>mcp</strong><p>tool call</p><strong>ops</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>http</strong><p>route</p><strong>ops</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>ops</strong><p>mutate</p><strong>state</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>state</strong><p>debounced save</p><strong>db</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>state</strong><p>SSE</p><strong>client</strong></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"modules\": [\n {\n \"id\": \"mcp\",\n \"title\": \"MCP server\",\n \"detail\": \"stdio transport, 22 tools and 14 resources.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"http\",\n \"title\": \"HTTP API\",\n \"detail\": \"REST + SSE on Bun.serve.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"ops\",\n \"title\": \"Operation registry\",\n \"detail\": \"One definition drives HTTP, MCP and the SDK.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"state\",\n \"title\": \"CanvasStateManager\",\n \"detail\": \"Singleton source of truth; every mutation goes through it.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"db\",\n \"title\": \"SQLite\",\n \"detail\": \".pmx-canvas/canvas.db — nodes, pins, snapshots, blobs.\",\n \"role\": \"store\"\n },\n {\n \"id\": \"client\",\n \"title\": \"Preact client\",\n \"detail\": \"Renders layout; state survives refresh.\",\n \"role\": \"view\"\n }\n ],\n \"edges\": [\n {\n \"from\": \"mcp\",\n \"to\": \"ops\",\n \"label\": \"tool call\"\n },\n {\n \"from\": \"http\",\n \"to\": \"ops\",\n \"label\": \"route\"\n },\n {\n \"from\": \"ops\",\n \"to\": \"state\",\n \"label\": \"mutate\"\n },\n {\n \"from\": \"state\",\n \"to\": \"db\",\n \"label\": \"debounced save\"\n },\n {\n \"from\": \"state\",\n \"to\": \"client\",\n \"label\": \"SSE\"\n }\n ],\n \"entryPoints\": [\n \"src/mcp/server.ts\",\n \"src/server/server.ts\",\n \"src/cli/index.ts\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
3840
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Canvas server map</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / system-map</div><h1>Canvas server map</h1><p>Module or architecture map with boxes, relationships, entry points, and hot paths.</p></div>\n </header>\n <section class=\"panel\"><h2>Entry Points</h2><div class=\"swatches\"><span class=\"badge info\">src/mcp/server.ts</span><span class=\"badge info\">src/server/server.ts</span><span class=\"badge info\">src/cli/index.ts</span></div></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><div class=\"small\">entry</div><h2>MCP server</h2><p>stdio transport, 22 tools and 14 resources.</p></article><article class=\"card\"><div class=\"small\">entry</div><h2>HTTP API</h2><p>REST + SSE on Bun.serve.</p></article><article class=\"card\"><div class=\"small\">core</div><h2>Operation registry</h2><p>One definition drives HTTP, MCP and the SDK.</p></article><article class=\"card\"><div class=\"small\">core</div><h2>CanvasStateManager</h2><p>Singleton source of truth; every mutation goes through it.</p></article><article class=\"card\"><div class=\"small\">store</div><h2>SQLite</h2><p>.pmx-canvas/canvas.db — nodes, pins, snapshots, blobs.</p></article><article class=\"card\"><div class=\"small\">view</div><h2>Preact client</h2><p>Renders layout; state survives refresh.</p></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Relationships</h2><div class=\"flow\"><div class=\"flow-node\"><strong>mcp</strong><p>tool call</p><strong>ops</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>http</strong><p>route</p><strong>ops</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>ops</strong><p>mutate</p><strong>state</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>state</strong><p>debounced save</p><strong>db</strong></div><div class=\"arrow\">+</div><div class=\"flow-node\"><strong>state</strong><p>SSE</p><strong>client</strong></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"modules\": [\n {\n \"id\": \"mcp\",\n \"title\": \"MCP server\",\n \"detail\": \"stdio transport, 22 tools and 14 resources.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"http\",\n \"title\": \"HTTP API\",\n \"detail\": \"REST + SSE on Bun.serve.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"ops\",\n \"title\": \"Operation registry\",\n \"detail\": \"One definition drives HTTP, MCP and the SDK.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"state\",\n \"title\": \"CanvasStateManager\",\n \"detail\": \"Singleton source of truth; every mutation goes through it.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"db\",\n \"title\": \"SQLite\",\n \"detail\": \".pmx-canvas/canvas.db — nodes, pins, snapshots, blobs.\",\n \"role\": \"store\"\n },\n {\n \"id\": \"client\",\n \"title\": \"Preact client\",\n \"detail\": \"Renders layout; state survives refresh.\",\n \"role\": \"view\"\n }\n ],\n \"edges\": [\n {\n \"from\": \"mcp\",\n \"to\": \"ops\",\n \"label\": \"tool call\"\n },\n {\n \"from\": \"http\",\n \"to\": \"ops\",\n \"label\": \"route\"\n },\n {\n \"from\": \"ops\",\n \"to\": \"state\",\n \"label\": \"mutate\"\n },\n {\n \"from\": \"state\",\n \"to\": \"db\",\n \"label\": \"debounced save\"\n },\n {\n \"from\": \"state\",\n \"to\": \"client\",\n \"label\": \"SSE\"\n }\n ],\n \"entryPoints\": [\n \"src/mcp/server.ts\",\n \"src/server/server.ts\",\n \"src/cli/index.ts\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
3836
3841
|
"htmlPrimitive": "system-map",
|
|
3837
3842
|
"primitiveData": {
|
|
3838
3843
|
"modules": [
|
|
@@ -3909,7 +3914,7 @@
|
|
|
3909
3914
|
"description": "Module or architecture map with boxes, relationships, entry points, and hot paths.",
|
|
3910
3915
|
"title": "Canvas server map",
|
|
3911
3916
|
"strictSize": true,
|
|
3912
|
-
"contentSummary": "Canvas server map PMX HTML primitive / system-map Canvas server map Module or architecture map with boxes, relationships, entry points, and hot paths.
|
|
3917
|
+
"contentSummary": "Canvas server map PMX HTML primitive / system-map Canvas server map Module or architecture map with boxes, relationships, entry points, and hot paths. Entry Points src/mcp/server.ts src/server/server.ts src/cli/index.ts entry MCP server stdio transport, 22 tools and 14 resources. entry HTTP API REST + SSE on Bun.serve. core Operation registry One definition drives HTTP, MCP and the SDK. core CanvasStateManager Singleton source of truth; every mutation goes through it. store SQLite .pmx-canvas/canvas.db — nodes, pins, snapshots, blobs. view Preact client Renders layout; state survives refresh. Relationships mcp tool call ops + http route ops + ops mutate state + state debounced save db + state SSE client",
|
|
3913
3918
|
"agentSummary": "Module or architecture map with boxes, relationships, entry points, and hot paths."
|
|
3914
3919
|
},
|
|
3915
3920
|
"createdBy": {
|
|
@@ -3947,7 +3952,7 @@
|
|
|
3947
3952
|
"collapsed": false,
|
|
3948
3953
|
"pinned": false,
|
|
3949
3954
|
"data": {
|
|
3950
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Walkthrough — creating a node</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / code-walkthrough</div><h1>Walkthrough — creating a node</h1><p>Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><h2>Path Summary</h2><p>One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.</p><div class=\"flow\" style=\"margin-top:12px\"><div class=\"flow-node\"><div class=\"small\">entry</div><h3>Bun.serve fetch</h3><p>Matches the operation path.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">core</div><h3>node.add</h3><p>Zod-validates the body, resolves geometry.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">core</div><h3>createBasicCanvasNode</h3><p>Per-type data builders and the size clamp.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">store</div><h3>canvasState.addNode</h3><p>Stores, schedules a save, records undo.</p></div></div><p class=\"small\" style=\"margin-top:10px\">Edges: route -> op (dispatch), op -> build (normalize), build -> state (add)</p></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>Walkthrough</h2><details open><summary><strong>1. Resolve the type</strong></summary><p class=\"small\">src/server/operations/ops/nodes.ts</p><p>A missing or unknown type is a 400, never a silent markdown node.</p><pre><code>if (!NODE_TYPE_SET.has(type)) throw new OperationError(...)</code></pre></details><details ><summary><strong>2. Build per-type data</strong></summary><p class=\"small\">src/server/canvas-operations.ts</p><p>file / image / webpage / trace each get a dedicated builder.</p><pre><code>if (input.type === "file") return buildFileNodeData(input);</code></pre></details><details ><summary><strong>3. Clamp the frame</strong></summary><p class=\"small\">src/shared/node-sizes.ts</p><p>An undersized explicit size is raised to the readability floor.</p><pre><code>clampCreateNodeSize(type, width, height, strictSize)</code></pre></details><details ><summary><strong>4. Store and broadcast</strong></summary><p class=\"small\">src/server/canvas-state.ts</p><p>addNode persists and notifies; the browser is only a renderer.</p></details></div>\n <div class=\"panel\"><h2>Key Files</h2><article class=\"card\"><h3>src/server/operations/ops/nodes.ts</h3><p>node.add / update / remove and the shared node payloads.</p></article><article class=\"card\"><h3>src/server/canvas-state.ts</h3><p>The singleton store, undo ring buffer and persistence.</p></article></div>\n <div class=\"panel sticky\"><h2>Gotchas</h2><ul><li>A new node type must also be added to isCanvasNodeType in the SSE bridge, or it renders as nothing.</li><li>Rebuild the client bundle after touching src/client — the dist bundle is not auto-built.</li></ul></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.\",\n \"modules\": [\n {\n \"id\": \"route\",\n \"title\": \"Bun.serve fetch\",\n \"detail\": \"Matches the operation path.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"op\",\n \"title\": \"node.add\",\n \"detail\": \"Zod-validates the body, resolves geometry.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"build\",\n \"title\": \"createBasicCanvasNode\",\n \"detail\": \"Per-type data builders and the size clamp.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"state\",\n \"title\": \"canvasState.addNode\",\n \"detail\": \"Stores, schedules a save, records undo.\",\n \"role\": \"store\"\n }\n ],\n \"edges\": [\n {\n \"from\": \"route\",\n \"to\": \"op\",\n \"label\": \"dispatch\"\n },\n {\n \"from\": \"op\",\n \"to\": \"build\",\n \"label\": \"normalize\"\n },\n {\n \"from\": \"build\",\n \"to\": \"state\",\n \"label\": \"add\"\n }\n ],\n \"steps\": [\n {\n \"title\": \"Resolve the type\",\n \"file\": \"src/server/operations/ops/nodes.ts\",\n \"detail\": \"A missing or unknown type is a 400, never a silent markdown node.\",\n \"code\": \"if (!NODE_TYPE_SET.has(type)) throw new OperationError(...)\"\n },\n {\n \"title\": \"Build per-type data\",\n \"file\": \"src/server/canvas-operations.ts\",\n \"detail\": \"file / image / webpage / trace each get a dedicated builder.\",\n \"code\": \"if (input.type === \\\"file\\\") return buildFileNodeData(input);\"\n },\n {\n \"title\": \"Clamp the frame\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"detail\": \"An undersized explicit size is raised to the readability floor.\",\n \"code\": \"clampCreateNodeSize(type, width, height, strictSize)\"\n },\n {\n \"title\": \"Store and broadcast\",\n \"file\": \"src/server/canvas-state.ts\",\n \"detail\": \"addNode persists and notifies; the browser is only a renderer.\"\n }\n ],\n \"keyFiles\": [\n {\n \"path\": \"src/server/operations/ops/nodes.ts\",\n \"description\": \"node.add / update / remove and the shared node payloads.\"\n },\n {\n \"path\": \"src/server/canvas-state.ts\",\n \"description\": \"The singleton store, undo ring buffer and persistence.\"\n }\n ],\n \"gotchas\": [\n \"A new node type must also be added to isCanvasNodeType in the SSE bridge, or it renders as nothing.\",\n \"Rebuild the client bundle after touching src/client — the dist bundle is not auto-built.\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
3955
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Walkthrough — creating a node</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / code-walkthrough</div><h1>Walkthrough — creating a node</h1><p>Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas.</p></div>\n </header>\n <section class=\"panel\"><h2>Path Summary</h2><p>One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.</p><div class=\"flow\" style=\"margin-top:12px\"><div class=\"flow-node\"><div class=\"small\">entry</div><h3>Bun.serve fetch</h3><p>Matches the operation path.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">core</div><h3>node.add</h3><p>Zod-validates the body, resolves geometry.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">core</div><h3>createBasicCanvasNode</h3><p>Per-type data builders and the size clamp.</p></div><div class=\"arrow\">-></div><div class=\"flow-node\"><div class=\"small\">store</div><h3>canvasState.addNode</h3><p>Stores, schedules a save, records undo.</p></div></div><p class=\"small\" style=\"margin-top:10px\">Edges: route -> op (dispatch), op -> build (normalize), build -> state (add)</p></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>Walkthrough</h2><details open><summary><strong>1. Resolve the type</strong></summary><p class=\"small\">src/server/operations/ops/nodes.ts</p><p>A missing or unknown type is a 400, never a silent markdown node.</p><pre><code>if (!NODE_TYPE_SET.has(type)) throw new OperationError(...)</code></pre></details><details ><summary><strong>2. Build per-type data</strong></summary><p class=\"small\">src/server/canvas-operations.ts</p><p>file / image / webpage / trace each get a dedicated builder.</p><pre><code>if (input.type === "file") return buildFileNodeData(input);</code></pre></details><details ><summary><strong>3. Clamp the frame</strong></summary><p class=\"small\">src/shared/node-sizes.ts</p><p>An undersized explicit size is raised to the readability floor.</p><pre><code>clampCreateNodeSize(type, width, height, strictSize)</code></pre></details><details ><summary><strong>4. Store and broadcast</strong></summary><p class=\"small\">src/server/canvas-state.ts</p><p>addNode persists and notifies; the browser is only a renderer.</p></details></div>\n <div class=\"panel\"><h2>Key Files</h2><article class=\"card\"><h3>src/server/operations/ops/nodes.ts</h3><p>node.add / update / remove and the shared node payloads.</p></article><article class=\"card\"><h3>src/server/canvas-state.ts</h3><p>The singleton store, undo ring buffer and persistence.</p></article></div>\n <div class=\"panel sticky\"><h2>Gotchas</h2><ul><li>A new node type must also be added to isCanvasNodeType in the SSE bridge, or it renders as nothing.</li><li>Rebuild the client bundle after touching src/client — the dist bundle is not auto-built.</li></ul></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.\",\n \"modules\": [\n {\n \"id\": \"route\",\n \"title\": \"Bun.serve fetch\",\n \"detail\": \"Matches the operation path.\",\n \"role\": \"entry\"\n },\n {\n \"id\": \"op\",\n \"title\": \"node.add\",\n \"detail\": \"Zod-validates the body, resolves geometry.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"build\",\n \"title\": \"createBasicCanvasNode\",\n \"detail\": \"Per-type data builders and the size clamp.\",\n \"role\": \"core\"\n },\n {\n \"id\": \"state\",\n \"title\": \"canvasState.addNode\",\n \"detail\": \"Stores, schedules a save, records undo.\",\n \"role\": \"store\"\n }\n ],\n \"edges\": [\n {\n \"from\": \"route\",\n \"to\": \"op\",\n \"label\": \"dispatch\"\n },\n {\n \"from\": \"op\",\n \"to\": \"build\",\n \"label\": \"normalize\"\n },\n {\n \"from\": \"build\",\n \"to\": \"state\",\n \"label\": \"add\"\n }\n ],\n \"steps\": [\n {\n \"title\": \"Resolve the type\",\n \"file\": \"src/server/operations/ops/nodes.ts\",\n \"detail\": \"A missing or unknown type is a 400, never a silent markdown node.\",\n \"code\": \"if (!NODE_TYPE_SET.has(type)) throw new OperationError(...)\"\n },\n {\n \"title\": \"Build per-type data\",\n \"file\": \"src/server/canvas-operations.ts\",\n \"detail\": \"file / image / webpage / trace each get a dedicated builder.\",\n \"code\": \"if (input.type === \\\"file\\\") return buildFileNodeData(input);\"\n },\n {\n \"title\": \"Clamp the frame\",\n \"file\": \"src/shared/node-sizes.ts\",\n \"detail\": \"An undersized explicit size is raised to the readability floor.\",\n \"code\": \"clampCreateNodeSize(type, width, height, strictSize)\"\n },\n {\n \"title\": \"Store and broadcast\",\n \"file\": \"src/server/canvas-state.ts\",\n \"detail\": \"addNode persists and notifies; the browser is only a renderer.\"\n }\n ],\n \"keyFiles\": [\n {\n \"path\": \"src/server/operations/ops/nodes.ts\",\n \"description\": \"node.add / update / remove and the shared node payloads.\"\n },\n {\n \"path\": \"src/server/canvas-state.ts\",\n \"description\": \"The singleton store, undo ring buffer and persistence.\"\n }\n ],\n \"gotchas\": [\n \"A new node type must also be added to isCanvasNodeType in the SSE bridge, or it renders as nothing.\",\n \"Rebuild the client bundle after touching src/client — the dist bundle is not auto-built.\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
3951
3956
|
"htmlPrimitive": "code-walkthrough",
|
|
3952
3957
|
"primitiveData": {
|
|
3953
3958
|
"summary": "One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.",
|
|
@@ -4038,7 +4043,7 @@
|
|
|
4038
4043
|
"summary": "One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation.",
|
|
4039
4044
|
"title": "Walkthrough — creating a node",
|
|
4040
4045
|
"strictSize": true,
|
|
4041
|
-
"contentSummary": "Walkthrough — creating a node PMX HTML primitive / code-walkthrough Walkthrough — creating a node Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas.
|
|
4046
|
+
"contentSummary": "Walkthrough — creating a node PMX HTML primitive / code-walkthrough Walkthrough — creating a node Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas. Path Summary One POST becomes a validated, clamped, persisted node and an SSE frame, through a single shared operation. entry Bun.serve fetch Matches the operation path. -> core node.add Zod-validates the body, resolves geometry. -> core createBasicCanvasNode Per-type data builders and the size clamp. -> store canvasState.addNode Stores, schedules a save, records undo. Edges: route -> op (dispatch), op -> build (normalize), build -> state (add) Walkthrough 1. Resolve the type src/server/operations/ops/nodes.ts A missing or unknown type is a 400, never a silent markdown node. if (!NODE_TYPE_SET.has(type)) throw new OperationError(...) 2. Build per-type data src/server/canvas-operations.ts file / image...",
|
|
4042
4047
|
"agentSummary": "Guided code-path map with modules, ordered file steps, snippets, key files, and gotchas.",
|
|
4043
4048
|
"embeddedNodeIds": [
|
|
4044
4049
|
"node-sizes"
|
|
@@ -4079,7 +4084,7 @@
|
|
|
4079
4084
|
"collapsed": false,
|
|
4080
4085
|
"pinned": false,
|
|
4081
4086
|
"data": {
|
|
4082
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Board typography directions</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / design-sheet</div><h1>Board typography directions</h1><p>Visual design directions, tokens, swatches, type samples, and rationale.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\"><article class=\"card\"><h2>Instrument</h2><p>dense, technical, high contrast</p><div class=\"swatches\"><span class=\"swatch\" title=\"#0d1220\" style=\"background:#0d1220\"></span><span class=\"swatch\" title=\"#131a2b\" style=\"background:#131a2b\"></span><span class=\"swatch\" title=\"#4a9eff\" style=\"background:#4a9eff\"></span><span class=\"swatch\" title=\"#e6ecff\" style=\"background:#e6ecff\"></span></div><p>Reads like a console. Good for status, trace and ledger nodes where density beats warmth.</p></article><article class=\"card\"><h2>Editorial</h2><p>calm, generous leading</p><div class=\"swatches\"><span class=\"swatch\" title=\"#f8f1e7\" style=\"background:#f8f1e7\"></span><span class=\"swatch\" title=\"#16120f\" style=\"background:#16120f\"></span><span class=\"swatch\" title=\"#d65a31\" style=\"background:#d65a31\"></span><span class=\"swatch\" title=\"#6b5f55\" style=\"background:#6b5f55\"></span></div><p>Better for long markdown and explainers; loses signal on dashboards.</p></article><article class=\"card\"><h2>Blueprint</h2><p>cool, structural, muted</p><div class=\"swatches\"><span class=\"swatch\" title=\"#101826\" style=\"background:#101826\"></span><span class=\"swatch\" title=\"#1b2740\" style=\"background:#1b2740\"></span><span class=\"swatch\" title=\"#7c5cff\" style=\"background:#7c5cff\"></span><span class=\"swatch\" title=\"#8ee5ad\" style=\"background:#8ee5ad\"></span></div><p>Diagram-first. Chart ink stays readable against the panel at low zoom.</p></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Tokens</h2><div class=\"grid\"><div class=\"card\"><strong>--c-bg</strong><p>#0d1220</p></div><div class=\"card\"><strong>--c-panel</strong><p>#131a2b</p></div><div class=\"card\"><strong>--c-line</strong><p>#243049</p></div><div class=\"card\"><strong>--c-accent</strong><p>#4a9eff</p></div><div class=\"card\"><strong>--c-ok</strong><p>#22c55e</p></div><div class=\"card\"><strong>--c-warn</strong><p>#eab308</p></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"directions\": [\n {\n \"title\": \"Instrument\",\n \"tone\": \"dense, technical, high contrast\",\n \"palette\": [\n \"#0d1220\",\n \"#131a2b\",\n \"#4a9eff\",\n \"#e6ecff\"\n ],\n \"rationale\": \"Reads like a console. Good for status, trace and ledger nodes where density beats warmth.\"\n },\n {\n \"title\": \"Editorial\",\n \"tone\": \"calm, generous leading\",\n \"palette\": [\n \"#f8f1e7\",\n \"#16120f\",\n \"#d65a31\",\n \"#6b5f55\"\n ],\n \"rationale\": \"Better for long markdown and explainers; loses signal on dashboards.\"\n },\n {\n \"title\": \"Blueprint\",\n \"tone\": \"cool, structural, muted\",\n \"palette\": [\n \"#101826\",\n \"#1b2740\",\n \"#7c5cff\",\n \"#8ee5ad\"\n ],\n \"rationale\": \"Diagram-first. Chart ink stays readable against the panel at low zoom.\"\n }\n ],\n \"tokens\": [\n {\n \"name\": \"--c-bg\",\n \"value\": \"#0d1220\"\n },\n {\n \"name\": \"--c-panel\",\n \"value\": \"#131a2b\"\n },\n {\n \"name\": \"--c-line\",\n \"value\": \"#243049\"\n },\n {\n \"name\": \"--c-accent\",\n \"value\": \"#4a9eff\"\n },\n {\n \"name\": \"--c-ok\",\n \"value\": \"#22c55e\"\n },\n {\n \"name\": \"--c-warn\",\n \"value\": \"#eab308\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
4087
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Board typography directions</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / design-sheet</div><h1>Board typography directions</h1><p>Visual design directions, tokens, swatches, type samples, and rationale.</p></div>\n </header>\n <section class=\"grid\"><article class=\"card\"><h2>Instrument</h2><p>dense, technical, high contrast</p><div class=\"swatches\"><span class=\"swatch\" title=\"#0d1220\" style=\"background:#0d1220\"></span><span class=\"swatch\" title=\"#131a2b\" style=\"background:#131a2b\"></span><span class=\"swatch\" title=\"#4a9eff\" style=\"background:#4a9eff\"></span><span class=\"swatch\" title=\"#e6ecff\" style=\"background:#e6ecff\"></span></div><p>Reads like a console. Good for status, trace and ledger nodes where density beats warmth.</p></article><article class=\"card\"><h2>Editorial</h2><p>calm, generous leading</p><div class=\"swatches\"><span class=\"swatch\" title=\"#f8f1e7\" style=\"background:#f8f1e7\"></span><span class=\"swatch\" title=\"#16120f\" style=\"background:#16120f\"></span><span class=\"swatch\" title=\"#d65a31\" style=\"background:#d65a31\"></span><span class=\"swatch\" title=\"#6b5f55\" style=\"background:#6b5f55\"></span></div><p>Better for long markdown and explainers; loses signal on dashboards.</p></article><article class=\"card\"><h2>Blueprint</h2><p>cool, structural, muted</p><div class=\"swatches\"><span class=\"swatch\" title=\"#101826\" style=\"background:#101826\"></span><span class=\"swatch\" title=\"#1b2740\" style=\"background:#1b2740\"></span><span class=\"swatch\" title=\"#7c5cff\" style=\"background:#7c5cff\"></span><span class=\"swatch\" title=\"#8ee5ad\" style=\"background:#8ee5ad\"></span></div><p>Diagram-first. Chart ink stays readable against the panel at low zoom.</p></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Tokens</h2><div class=\"grid\"><div class=\"card\"><strong>--c-bg</strong><p>#0d1220</p></div><div class=\"card\"><strong>--c-panel</strong><p>#131a2b</p></div><div class=\"card\"><strong>--c-line</strong><p>#243049</p></div><div class=\"card\"><strong>--c-accent</strong><p>#4a9eff</p></div><div class=\"card\"><strong>--c-ok</strong><p>#22c55e</p></div><div class=\"card\"><strong>--c-warn</strong><p>#eab308</p></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"directions\": [\n {\n \"title\": \"Instrument\",\n \"tone\": \"dense, technical, high contrast\",\n \"palette\": [\n \"#0d1220\",\n \"#131a2b\",\n \"#4a9eff\",\n \"#e6ecff\"\n ],\n \"rationale\": \"Reads like a console. Good for status, trace and ledger nodes where density beats warmth.\"\n },\n {\n \"title\": \"Editorial\",\n \"tone\": \"calm, generous leading\",\n \"palette\": [\n \"#f8f1e7\",\n \"#16120f\",\n \"#d65a31\",\n \"#6b5f55\"\n ],\n \"rationale\": \"Better for long markdown and explainers; loses signal on dashboards.\"\n },\n {\n \"title\": \"Blueprint\",\n \"tone\": \"cool, structural, muted\",\n \"palette\": [\n \"#101826\",\n \"#1b2740\",\n \"#7c5cff\",\n \"#8ee5ad\"\n ],\n \"rationale\": \"Diagram-first. Chart ink stays readable against the panel at low zoom.\"\n }\n ],\n \"tokens\": [\n {\n \"name\": \"--c-bg\",\n \"value\": \"#0d1220\"\n },\n {\n \"name\": \"--c-panel\",\n \"value\": \"#131a2b\"\n },\n {\n \"name\": \"--c-line\",\n \"value\": \"#243049\"\n },\n {\n \"name\": \"--c-accent\",\n \"value\": \"#4a9eff\"\n },\n {\n \"name\": \"--c-ok\",\n \"value\": \"#22c55e\"\n },\n {\n \"name\": \"--c-warn\",\n \"value\": \"#eab308\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
4083
4088
|
"htmlPrimitive": "design-sheet",
|
|
4084
4089
|
"primitiveData": {
|
|
4085
4090
|
"directions": [
|
|
@@ -4147,7 +4152,7 @@
|
|
|
4147
4152
|
"description": "Visual design directions, tokens, swatches, type samples, and rationale.",
|
|
4148
4153
|
"title": "Board typography directions",
|
|
4149
4154
|
"strictSize": true,
|
|
4150
|
-
"contentSummary": "Board typography directions PMX HTML primitive / design-sheet Board typography directions Visual design directions, tokens, swatches, type samples, and rationale.
|
|
4155
|
+
"contentSummary": "Board typography directions PMX HTML primitive / design-sheet Board typography directions Visual design directions, tokens, swatches, type samples, and rationale. Instrument dense, technical, high contrast Reads like a console. Good for status, trace and ledger nodes where density beats warmth. Editorial calm, generous leading Better for long markdown and explainers; loses signal on dashboards. Blueprint cool, structural, muted Diagram-first. Chart ink stays readable against the panel at low zoom. Tokens --c-bg #0d1220 --c-panel #131a2b --c-line #243049 --c-accent #4a9eff --c-ok #22c55e --c-warn #eab308",
|
|
4151
4156
|
"agentSummary": "Visual design directions, tokens, swatches, type samples, and rationale."
|
|
4152
4157
|
},
|
|
4153
4158
|
"createdBy": {
|
|
@@ -4185,7 +4190,7 @@
|
|
|
4185
4190
|
"collapsed": false,
|
|
4186
4191
|
"pinned": false,
|
|
4187
4192
|
"data": {
|
|
4188
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Node titlebar states</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / component-gallery</div><h1>Node titlebar states</h1><p>Contact sheet for component variants, states, sizes, and accessibility notes.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><h2>Node titlebar</h2><p>Variant contact sheet for fast visual review.</p></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><div class=\"small\">rest / idle node</div><h2>Default</h2><button type=\"button\">markdown · Release notes</button><p>Type chip, title, collapse and expand affordances.</p></article><article class=\"card\"><div class=\"small\">context-pinned / included in agent context</div><h2>Pinned</h2><button type=\"button\">📌 markdown · Release notes</button><p>Accent ring; appears in canvas://pinned-context.</p></article><article class=\"card\"><div class=\"small\">match / result of canvas_query search</div><h2>Search match</h2><button type=\"button\">file · node-sizes.ts</button><p>Non-matches dim rather than hide.</p></article><article class=\"card\"><div class=\"small\">running / tool call in flight</div><h2>Running trace</h2><button type=\"button\">trace · edit_file</button><p>Pulsing status dot, no layout shift on completion.</p></article><article class=\"card\"><div class=\"small\">focus / agent asked for the human’s eyes</div><h2>Attention</h2><button type=\"button\">diff · Readability floor</button><p>Pulse decays; never steals the viewport.</p></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"component\": \"Node titlebar\",\n \"variants\": [\n {\n \"label\": \"Default\",\n \"state\": \"rest\",\n \"intent\": \"idle node\",\n \"example\": \"markdown · Release notes\",\n \"note\": \"Type chip, title, collapse and expand affordances.\"\n },\n {\n \"label\": \"Pinned\",\n \"state\": \"context-pinned\",\n \"intent\": \"included in agent context\",\n \"example\": \"📌 markdown · Release notes\",\n \"note\": \"Accent ring; appears in canvas://pinned-context.\"\n },\n {\n \"label\": \"Search match\",\n \"state\": \"match\",\n \"intent\": \"result of canvas_query search\",\n \"example\": \"file · node-sizes.ts\",\n \"note\": \"Non-matches dim rather than hide.\"\n },\n {\n \"label\": \"Running trace\",\n \"state\": \"running\",\n \"intent\": \"tool call in flight\",\n \"example\": \"trace · edit_file\",\n \"note\": \"Pulsing status dot, no layout shift on completion.\"\n },\n {\n \"label\": \"Attention\",\n \"state\": \"focus\",\n \"intent\": \"agent asked for the human’s eyes\",\n \"example\": \"diff · Readability floor\",\n \"note\": \"Pulse decays; never steals the viewport.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
4193
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Node titlebar states</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / component-gallery</div><h1>Node titlebar states</h1><p>Contact sheet for component variants, states, sizes, and accessibility notes.</p></div>\n </header>\n <section class=\"panel\"><h2>Node titlebar</h2><p>Variant contact sheet for fast visual review.</p></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><div class=\"small\">rest / idle node</div><h2>Default</h2><button type=\"button\">markdown · Release notes</button><p>Type chip, title, collapse and expand affordances.</p></article><article class=\"card\"><div class=\"small\">context-pinned / included in agent context</div><h2>Pinned</h2><button type=\"button\">📌 markdown · Release notes</button><p>Accent ring; appears in canvas://pinned-context.</p></article><article class=\"card\"><div class=\"small\">match / result of canvas_query search</div><h2>Search match</h2><button type=\"button\">file · node-sizes.ts</button><p>Non-matches dim rather than hide.</p></article><article class=\"card\"><div class=\"small\">running / tool call in flight</div><h2>Running trace</h2><button type=\"button\">trace · edit_file</button><p>Pulsing status dot, no layout shift on completion.</p></article><article class=\"card\"><div class=\"small\">focus / agent asked for the human’s eyes</div><h2>Attention</h2><button type=\"button\">diff · Readability floor</button><p>Pulse decays; never steals the viewport.</p></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"component\": \"Node titlebar\",\n \"variants\": [\n {\n \"label\": \"Default\",\n \"state\": \"rest\",\n \"intent\": \"idle node\",\n \"example\": \"markdown · Release notes\",\n \"note\": \"Type chip, title, collapse and expand affordances.\"\n },\n {\n \"label\": \"Pinned\",\n \"state\": \"context-pinned\",\n \"intent\": \"included in agent context\",\n \"example\": \"📌 markdown · Release notes\",\n \"note\": \"Accent ring; appears in canvas://pinned-context.\"\n },\n {\n \"label\": \"Search match\",\n \"state\": \"match\",\n \"intent\": \"result of canvas_query search\",\n \"example\": \"file · node-sizes.ts\",\n \"note\": \"Non-matches dim rather than hide.\"\n },\n {\n \"label\": \"Running trace\",\n \"state\": \"running\",\n \"intent\": \"tool call in flight\",\n \"example\": \"trace · edit_file\",\n \"note\": \"Pulsing status dot, no layout shift on completion.\"\n },\n {\n \"label\": \"Attention\",\n \"state\": \"focus\",\n \"intent\": \"agent asked for the human’s eyes\",\n \"example\": \"diff · Readability floor\",\n \"note\": \"Pulse decays; never steals the viewport.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
4189
4194
|
"htmlPrimitive": "component-gallery",
|
|
4190
4195
|
"primitiveData": {
|
|
4191
4196
|
"component": "Node titlebar",
|
|
@@ -4230,7 +4235,7 @@
|
|
|
4230
4235
|
"description": "Contact sheet for component variants, states, sizes, and accessibility notes.",
|
|
4231
4236
|
"title": "Node titlebar states",
|
|
4232
4237
|
"strictSize": true,
|
|
4233
|
-
"contentSummary": "Node titlebar states PMX HTML primitive / component-gallery Node titlebar states Contact sheet for component variants, states, sizes, and accessibility notes.
|
|
4238
|
+
"contentSummary": "Node titlebar states PMX HTML primitive / component-gallery Node titlebar states Contact sheet for component variants, states, sizes, and accessibility notes. Node titlebar Variant contact sheet for fast visual review. rest / idle node Default markdown · Release notes Type chip, title, collapse and expand affordances. context-pinned / included in agent context Pinned 📌 markdown · Release notes Accent ring; appears in canvas://pinned-context. match / result of canvas_query search Search match file · node-sizes.ts Non-matches dim rather than hide. running / tool call in flight Running trace trace · edit_file Pulsing status dot, no layout shift on completion. focus / agent asked for the human’s eyes Attention diff · Readability floor Pulse decays; never steals the viewport.",
|
|
4234
4239
|
"agentSummary": "Contact sheet for component variants, states, sizes, and accessibility notes.",
|
|
4235
4240
|
"embeddedNodeIds": [
|
|
4236
4241
|
"node-sizes"
|
|
@@ -4271,7 +4276,7 @@
|
|
|
4271
4276
|
"collapsed": false,
|
|
4272
4277
|
"pinned": false,
|
|
4273
4278
|
"data": {
|
|
4274
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Node expand transition</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / interaction-prototype</div><h1>Node expand transition</h1><p>Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"three\"><div class=\"panel\"><h2>Stage</h2><p>Tune the expand-to-overlay transition before wiring it into CanvasNode.</p><div class=\"flow\" style=\"margin-top:16px\"><button class=\"flow-node\" type=\"button\" data-screen=\"0\"><h3>Rest</h3><p>Node sits in the band at canvas scale.</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-screen=\"1\"><h3>Lift</h3><p>Node scales toward the overlay frame; siblings dim.</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-screen=\"2\"><h3>Overlay</h3><p>Full-size surface with the same renderer, expanded props.</p></button></div><div class=\"card emphasis\" id=\"prototype-readout\" style=\"margin-top:14px\">Select a screen to inspect it.</div></div>\n <div class=\"panel\"><h2>Controls</h2><label class=\"card\"><span class=\"small\">duration</span><h3>Duration: <span data-control-value=\"0\">240</span>ms</h3><input type=\"range\" data-control=\"0\" min=\"80\" max=\"700\" value=\"240\"></label><label class=\"card\"><span class=\"small\">overshoot</span><h3>Overshoot: <span data-control-value=\"1\">6</span>%</h3><input type=\"range\" data-control=\"1\" min=\"0\" max=\"24\" value=\"6\"></label><label class=\"card\"><span class=\"small\">dim</span><h3>Backdrop dim: <span data-control-value=\"2\">62</span>%</h3><input type=\"range\" data-control=\"2\" min=\"0\" max=\"100\" value=\"62\"></label></div>\n <div class=\"panel sticky\"><h2>Notes</h2><article class=\"card\"><h3>Same renderer</h3><p>The overlay must not fork the node component, or surfaces drift.</p></article><article class=\"card\"><h3>No viewport jump</h3><p>Expanding never moves the underlying canvas.</p></article><h2 style=\"margin-top:18px\">Questions</h2><ul><li>Does overshoot help or just feel noisy at 0.4 zoom?</li><li>Should the backdrop dim scale with node count?</li></ul><pre><code>transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);</code></pre></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"scenario\": \"Tune the expand-to-overlay transition before wiring it into CanvasNode.\",\n \"controls\": [\n {\n \"key\": \"duration\",\n \"label\": \"Duration\",\n \"value\": 240,\n \"min\": 80,\n \"max\": 700,\n \"unit\": \"ms\"\n },\n {\n \"key\": \"overshoot\",\n \"label\": \"Overshoot\",\n \"value\": 6,\n \"min\": 0,\n \"max\": 24,\n \"unit\": \"%\"\n },\n {\n \"key\": \"dim\",\n \"label\": \"Backdrop dim\",\n \"value\": 62,\n \"min\": 0,\n \"max\": 100,\n \"unit\": \"%\"\n }\n ],\n \"screens\": [\n {\n \"title\": \"Rest\",\n \"detail\": \"Node sits in the band at canvas scale.\"\n },\n {\n \"title\": \"Lift\",\n \"detail\": \"Node scales toward the overlay frame; siblings dim.\"\n },\n {\n \"title\": \"Overlay\",\n \"detail\": \"Full-size surface with the same renderer, expanded props.\"\n }\n ],\n \"annotations\": [\n {\n \"title\": \"Same renderer\",\n \"detail\": \"The overlay must not fork the node component, or surfaces drift.\"\n },\n {\n \"title\": \"No viewport jump\",\n \"detail\": \"Expanding never moves the underlying canvas.\"\n }\n ],\n \"questions\": [\n \"Does overshoot help or just feel noisy at 0.4 zoom?\",\n \"Should the backdrop dim scale with node count?\"\n ],\n \"snippet\": \"transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);\"\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst screens = PMX_DATA.screens || [];\ndocument.querySelectorAll('[data-screen]').forEach((button) => button.addEventListener('click', () => {\n const screen = screens[Number(button.getAttribute('data-screen'))] || {};\n document.getElementById('prototype-readout').textContent = (screen.title || 'Screen') + ': ' + (screen.detail || '');\n}));\ndocument.querySelectorAll('[data-control]').forEach((input) => input.addEventListener('input', () => {\n document.querySelector('[data-control-value=\"' + input.getAttribute('data-control') + '\"]').textContent = input.value;\n}));\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, controls: Array.from(document.querySelectorAll('[data-control]')).map((input, index) => ({ ...(PMX_DATA.controls || [])[index], value: Number(input.value) })) });\n</script>\n</body>\n</html>",
|
|
4279
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Node expand transition</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / interaction-prototype</div><h1>Node expand transition</h1><p>Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config.</p></div>\n </header>\n <section class=\"three\"><div class=\"panel\"><h2>Stage</h2><p>Tune the expand-to-overlay transition before wiring it into CanvasNode.</p><div class=\"flow\" style=\"margin-top:16px\"><button class=\"flow-node\" type=\"button\" data-screen=\"0\"><h3>Rest</h3><p>Node sits in the band at canvas scale.</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-screen=\"1\"><h3>Lift</h3><p>Node scales toward the overlay frame; siblings dim.</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-screen=\"2\"><h3>Overlay</h3><p>Full-size surface with the same renderer, expanded props.</p></button></div><div class=\"card emphasis\" id=\"prototype-readout\" style=\"margin-top:14px\">Select a screen to inspect it.</div></div>\n <div class=\"panel\"><h2>Controls</h2><label class=\"card\"><span class=\"small\">duration</span><h3>Duration: <span data-control-value=\"0\">240</span>ms</h3><input type=\"range\" data-control=\"0\" min=\"80\" max=\"700\" value=\"240\"></label><label class=\"card\"><span class=\"small\">overshoot</span><h3>Overshoot: <span data-control-value=\"1\">6</span>%</h3><input type=\"range\" data-control=\"1\" min=\"0\" max=\"24\" value=\"6\"></label><label class=\"card\"><span class=\"small\">dim</span><h3>Backdrop dim: <span data-control-value=\"2\">62</span>%</h3><input type=\"range\" data-control=\"2\" min=\"0\" max=\"100\" value=\"62\"></label></div>\n <div class=\"panel sticky\"><h2>Notes</h2><article class=\"card\"><h3>Same renderer</h3><p>The overlay must not fork the node component, or surfaces drift.</p></article><article class=\"card\"><h3>No viewport jump</h3><p>Expanding never moves the underlying canvas.</p></article><h2 style=\"margin-top:18px\">Questions</h2><ul><li>Does overshoot help or just feel noisy at 0.4 zoom?</li><li>Should the backdrop dim scale with node count?</li></ul><pre><code>transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);</code></pre></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"scenario\": \"Tune the expand-to-overlay transition before wiring it into CanvasNode.\",\n \"controls\": [\n {\n \"key\": \"duration\",\n \"label\": \"Duration\",\n \"value\": 240,\n \"min\": 80,\n \"max\": 700,\n \"unit\": \"ms\"\n },\n {\n \"key\": \"overshoot\",\n \"label\": \"Overshoot\",\n \"value\": 6,\n \"min\": 0,\n \"max\": 24,\n \"unit\": \"%\"\n },\n {\n \"key\": \"dim\",\n \"label\": \"Backdrop dim\",\n \"value\": 62,\n \"min\": 0,\n \"max\": 100,\n \"unit\": \"%\"\n }\n ],\n \"screens\": [\n {\n \"title\": \"Rest\",\n \"detail\": \"Node sits in the band at canvas scale.\"\n },\n {\n \"title\": \"Lift\",\n \"detail\": \"Node scales toward the overlay frame; siblings dim.\"\n },\n {\n \"title\": \"Overlay\",\n \"detail\": \"Full-size surface with the same renderer, expanded props.\"\n }\n ],\n \"annotations\": [\n {\n \"title\": \"Same renderer\",\n \"detail\": \"The overlay must not fork the node component, or surfaces drift.\"\n },\n {\n \"title\": \"No viewport jump\",\n \"detail\": \"Expanding never moves the underlying canvas.\"\n }\n ],\n \"questions\": [\n \"Does overshoot help or just feel noisy at 0.4 zoom?\",\n \"Should the backdrop dim scale with node count?\"\n ],\n \"snippet\": \"transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);\"\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst screens = PMX_DATA.screens || [];\ndocument.querySelectorAll('[data-screen]').forEach((button) => button.addEventListener('click', () => {\n const screen = screens[Number(button.getAttribute('data-screen'))] || {};\n document.getElementById('prototype-readout').textContent = (screen.title || 'Screen') + ': ' + (screen.detail || '');\n}));\ndocument.querySelectorAll('[data-control]').forEach((input) => input.addEventListener('input', () => {\n document.querySelector('[data-control-value=\"' + input.getAttribute('data-control') + '\"]').textContent = input.value;\n}));\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, controls: Array.from(document.querySelectorAll('[data-control]')).map((input, index) => ({ ...(PMX_DATA.controls || [])[index], value: Number(input.value) })) });\n</script>\n</body>\n</html>",
|
|
4275
4280
|
"htmlPrimitive": "interaction-prototype",
|
|
4276
4281
|
"primitiveData": {
|
|
4277
4282
|
"scenario": "Tune the expand-to-overlay transition before wiring it into CanvasNode.",
|
|
@@ -4334,7 +4339,7 @@
|
|
|
4334
4339
|
"description": "Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config.",
|
|
4335
4340
|
"title": "Node expand transition",
|
|
4336
4341
|
"strictSize": true,
|
|
4337
|
-
"contentSummary": "Node expand transition PMX HTML primitive / interaction-prototype Node expand transition Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config.
|
|
4342
|
+
"contentSummary": "Node expand transition PMX HTML primitive / interaction-prototype Node expand transition Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config. Stage Tune the expand-to-overlay transition before wiring it into CanvasNode. Rest Node sits in the band at canvas scale. -> Lift Node scales toward the overlay frame; siblings dim. -> Overlay Full-size surface with the same renderer, expanded props. Select a screen to inspect it. Controls duration Duration: 240 ms overshoot Overshoot: 6 % dim Backdrop dim: 62 % Notes Same renderer The overlay must not fork the node component, or surfaces drift. No viewport jump Expanding never moves the underlying canvas. Questions Does overshoot help or just feel noisy at 0.4 zoom? Should the backdrop dim scale with node count? transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1);",
|
|
4338
4343
|
"agentSummary": "Throwaway interaction or motion sandbox with a live stage, controls, annotations, and copyable config."
|
|
4339
4344
|
},
|
|
4340
4345
|
"createdBy": {
|
|
@@ -4372,7 +4377,7 @@
|
|
|
4372
4377
|
"collapsed": false,
|
|
4373
4378
|
"pinned": false,
|
|
4374
4379
|
"data": {
|
|
4375
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Release pipeline</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / flowchart</div><h1>Release pipeline</h1><p>Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"flow\"><button class=\"flow-node\" type=\"button\" data-step=\"0\"><h3>Pre-flight</h3><p><span class=\"badge ok\">ok</span> 61s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"1\"><h3>Build</h3><p><span class=\"badge ok\">ok</span> 44s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"2\"><h3>Version bump</h3><p><span class=\"badge ok\">ok</span> 8s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"3\"><h3>Tag and publish</h3><p><span class=\"badge info\">active</span> —</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"4\"><h3>Smoke</h3><p><span class=\"badge info\">todo</span> —</p></button></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2 id=\"step-title\">Pre-flight</h2><p id=\"step-detail\">typecheck, lint, unit and client suites.</p></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Failure Paths</h2><article class=\"card\"><h3>Smoke: missing file in package</h3><p>Add it to the files array and publish a patch.</p></article><article class=\"card\"><h3>Tag and publish: registry 403</h3><p>Re-auth, then republish the same tag.</p></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"steps\": [\n {\n \"title\": \"Pre-flight\",\n \"detail\": \"typecheck, lint, unit and client suites.\",\n \"status\": \"ok\",\n \"duration\": \"61s\"\n },\n {\n \"title\": \"Build\",\n \"detail\": \"Client bundle, json-render bundle, types.\",\n \"status\": \"ok\",\n \"duration\": \"44s\"\n },\n {\n \"title\": \"Version bump\",\n \"detail\": \"package.json and CHANGELOG entry.\",\n \"status\": \"ok\",\n \"duration\": \"8s\"\n },\n {\n \"title\": \"Tag and publish\",\n \"detail\": \"npm publish from the tagged commit.\",\n \"status\": \"active\",\n \"duration\": \"—\"\n },\n {\n \"title\": \"Smoke\",\n \"detail\": \"Install the published tarball in a clean temp consumer.\",\n \"status\": \"todo\",\n \"duration\": \"—\"\n }\n ],\n \"failurePaths\": [\n {\n \"from\": \"Smoke\",\n \"label\": \"missing file in package\",\n \"detail\": \"Add it to the files array and publish a patch.\"\n },\n {\n \"from\": \"Tag and publish\",\n \"label\": \"registry 403\",\n \"detail\": \"Re-auth, then republish the same tag.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst steps = PMX_DATA.steps || [];\ndocument.querySelectorAll('[data-step]').forEach((button) => button.addEventListener('click', () => {\n const step = steps[Number(button.getAttribute('data-step'))] || {};\n document.getElementById('step-title').textContent = step.title || step.label || 'Step';\n document.getElementById('step-detail').textContent = step.detail || step.description || '';\n}));\n</script>\n</body>\n</html>",
|
|
4380
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Release pipeline</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / flowchart</div><h1>Release pipeline</h1><p>Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths.</p></div>\n </header>\n <section class=\"flow\"><button class=\"flow-node\" type=\"button\" data-step=\"0\"><h3>Pre-flight</h3><p><span class=\"badge ok\">ok</span> 61s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"1\"><h3>Build</h3><p><span class=\"badge ok\">ok</span> 44s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"2\"><h3>Version bump</h3><p><span class=\"badge ok\">ok</span> 8s</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"3\"><h3>Tag and publish</h3><p><span class=\"badge info\">active</span> —</p></button><div class=\"arrow\">-></div><button class=\"flow-node\" type=\"button\" data-step=\"4\"><h3>Smoke</h3><p><span class=\"badge info\">todo</span> —</p></button></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2 id=\"step-title\">Pre-flight</h2><p id=\"step-detail\">typecheck, lint, unit and client suites.</p></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Failure Paths</h2><article class=\"card\"><h3>Smoke: missing file in package</h3><p>Add it to the files array and publish a patch.</p></article><article class=\"card\"><h3>Tag and publish: registry 403</h3><p>Re-auth, then republish the same tag.</p></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"steps\": [\n {\n \"title\": \"Pre-flight\",\n \"detail\": \"typecheck, lint, unit and client suites.\",\n \"status\": \"ok\",\n \"duration\": \"61s\"\n },\n {\n \"title\": \"Build\",\n \"detail\": \"Client bundle, json-render bundle, types.\",\n \"status\": \"ok\",\n \"duration\": \"44s\"\n },\n {\n \"title\": \"Version bump\",\n \"detail\": \"package.json and CHANGELOG entry.\",\n \"status\": \"ok\",\n \"duration\": \"8s\"\n },\n {\n \"title\": \"Tag and publish\",\n \"detail\": \"npm publish from the tagged commit.\",\n \"status\": \"active\",\n \"duration\": \"—\"\n },\n {\n \"title\": \"Smoke\",\n \"detail\": \"Install the published tarball in a clean temp consumer.\",\n \"status\": \"todo\",\n \"duration\": \"—\"\n }\n ],\n \"failurePaths\": [\n {\n \"from\": \"Smoke\",\n \"label\": \"missing file in package\",\n \"detail\": \"Add it to the files array and publish a patch.\"\n },\n {\n \"from\": \"Tag and publish\",\n \"label\": \"registry 403\",\n \"detail\": \"Re-auth, then republish the same tag.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst steps = PMX_DATA.steps || [];\ndocument.querySelectorAll('[data-step]').forEach((button) => button.addEventListener('click', () => {\n const step = steps[Number(button.getAttribute('data-step'))] || {};\n document.getElementById('step-title').textContent = step.title || step.label || 'Step';\n document.getElementById('step-detail').textContent = step.detail || step.description || '';\n}));\n</script>\n</body>\n</html>",
|
|
4376
4381
|
"htmlPrimitive": "flowchart",
|
|
4377
4382
|
"primitiveData": {
|
|
4378
4383
|
"steps": [
|
|
@@ -4423,7 +4428,7 @@
|
|
|
4423
4428
|
"description": "Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths.",
|
|
4424
4429
|
"title": "Release pipeline",
|
|
4425
4430
|
"strictSize": true,
|
|
4426
|
-
"contentSummary": "Release pipeline PMX HTML primitive / flowchart Release pipeline Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths.
|
|
4431
|
+
"contentSummary": "Release pipeline PMX HTML primitive / flowchart Release pipeline Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths. Pre-flight ok 61s -> Build ok 44s -> Version bump ok 8s -> Tag and publish active — -> Smoke todo — Pre-flight typecheck, lint, unit and client suites. Failure Paths Smoke: missing file in package Add it to the files array and publish a patch. Tag and publish: registry 403 Re-auth, then republish the same tag.",
|
|
4427
4432
|
"agentSummary": "Clickable flowchart for pipelines, user journeys, process diagrams, and failure paths."
|
|
4428
4433
|
},
|
|
4429
4434
|
"createdBy": {
|
|
@@ -4461,7 +4466,7 @@
|
|
|
4461
4466
|
"collapsed": false,
|
|
4462
4467
|
"pinned": false,
|
|
4463
4468
|
"data": {
|
|
4464
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Why a spatial canvas</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / deck</div><h1>Why a spatial canvas</h1><p>Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><div class=\"small\"><span id=\"slide-count\">1</span> / 3 - use left/right arrows</div><article class=\"slide active\" data-slide=\"0\"><div><div class=\"kicker\">01</div><h2>Chat is a queue, work is a graph</h2><p></p><ul><li>A transcript only remembers order</li><li>Real work has neighbours, not turns</li><li>Space is free context</li></ul><p class=\"small\">Open on the problem, not the product.</p></div></article><article class=\"slide \" data-slide=\"1\"><div><div class=\"kicker\">02</div><h2>The canvas is the agent’s working memory</h2><p></p><ul><li>Nodes persist across sessions</li><li>Position carries meaning</li><li>The agent can read its own board back</li></ul><p class=\"small\"></p></div></article><article class=\"slide \" data-slide=\"2\"><div><div class=\"kicker\">03</div><h2>Pinning is the steering wheel</h2><p></p><ul><li>Human pins what matters</li><li>Agent reads canvas://pinned-context</li><li>No prompt engineering required</li></ul><p class=\"small\">Demo the pin loop live if there is time.</p></div></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"slides\": [\n {\n \"title\": \"Chat is a queue, work is a graph\",\n \"kicker\": \"01\",\n \"bullets\": [\n \"A transcript only remembers order\",\n \"Real work has neighbours, not turns\",\n \"Space is free context\"\n ],\n \"note\": \"Open on the problem, not the product.\"\n },\n {\n \"title\": \"The canvas is the agent’s working memory\",\n \"kicker\": \"02\",\n \"bullets\": [\n \"Nodes persist across sessions\",\n \"Position carries meaning\",\n \"The agent can read its own board back\"\n ]\n },\n {\n \"title\": \"Pinning is the steering wheel\",\n \"kicker\": \"03\",\n \"bullets\": [\n \"Human pins what matters\",\n \"Agent reads canvas://pinned-context\",\n \"No prompt engineering required\"\n ],\n \"note\": \"Demo the pin loop live if there is time.\"\n }\n ],\n \"presentation\": true,\n \"slideCount\": 3,\n \"slideTitles\": [\n \"Chat is a queue, work is a graph\",\n \"The canvas is the agent’s working memory\",\n \"Pinning is the steering wheel\"\n ],\n \"speakerNotes\": [\n \"Open on the problem, not the product.\",\n \"Demo the pin loop live if there is time.\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nlet currentSlide = 0;\nconst slides = Array.from(document.querySelectorAll('[data-slide]'));\nfunction showSlide(index) {\n if (!slides.length) return;\n currentSlide = Math.max(0, Math.min(slides.length - 1, index));\n slides.forEach((slide, i) => slide.classList.toggle('active', i === currentSlide));\n document.getElementById('slide-count').textContent = String(currentSlide + 1);\n}\ndocument.addEventListener('keydown', (event) => {\n if (event.key === 'ArrowRight' || event.key === 'PageDown' || event.key === ' ') showSlide(currentSlide + 1);\n if (event.key === 'ArrowLeft' || event.key === 'PageUp') showSlide(currentSlide - 1);\n});\n</script>\n</body>\n</html>",
|
|
4469
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Why a spatial canvas</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / deck</div><h1>Why a spatial canvas</h1><p>Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload.</p></div>\n </header>\n <section class=\"panel\"><div class=\"small\"><span id=\"slide-count\">1</span> / 3 - use left/right arrows</div><article class=\"slide active\" data-slide=\"0\"><div><div class=\"kicker\">01</div><h2>Chat is a queue, work is a graph</h2><p></p><ul><li>A transcript only remembers order</li><li>Real work has neighbours, not turns</li><li>Space is free context</li></ul><p class=\"small\">Open on the problem, not the product.</p></div></article><article class=\"slide \" data-slide=\"1\"><div><div class=\"kicker\">02</div><h2>The canvas is the agent’s working memory</h2><p></p><ul><li>Nodes persist across sessions</li><li>Position carries meaning</li><li>The agent can read its own board back</li></ul><p class=\"small\"></p></div></article><article class=\"slide \" data-slide=\"2\"><div><div class=\"kicker\">03</div><h2>Pinning is the steering wheel</h2><p></p><ul><li>Human pins what matters</li><li>Agent reads canvas://pinned-context</li><li>No prompt engineering required</li></ul><p class=\"small\">Demo the pin loop live if there is time.</p></div></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"slides\": [\n {\n \"title\": \"Chat is a queue, work is a graph\",\n \"kicker\": \"01\",\n \"bullets\": [\n \"A transcript only remembers order\",\n \"Real work has neighbours, not turns\",\n \"Space is free context\"\n ],\n \"note\": \"Open on the problem, not the product.\"\n },\n {\n \"title\": \"The canvas is the agent’s working memory\",\n \"kicker\": \"02\",\n \"bullets\": [\n \"Nodes persist across sessions\",\n \"Position carries meaning\",\n \"The agent can read its own board back\"\n ]\n },\n {\n \"title\": \"Pinning is the steering wheel\",\n \"kicker\": \"03\",\n \"bullets\": [\n \"Human pins what matters\",\n \"Agent reads canvas://pinned-context\",\n \"No prompt engineering required\"\n ],\n \"note\": \"Demo the pin loop live if there is time.\"\n }\n ],\n \"presentation\": true,\n \"slideCount\": 3,\n \"slideTitles\": [\n \"Chat is a queue, work is a graph\",\n \"The canvas is the agent’s working memory\",\n \"Pinning is the steering wheel\"\n ],\n \"speakerNotes\": [\n \"Open on the problem, not the product.\",\n \"Demo the pin loop live if there is time.\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nlet currentSlide = 0;\nconst slides = Array.from(document.querySelectorAll('[data-slide]'));\nfunction showSlide(index) {\n if (!slides.length) return;\n currentSlide = Math.max(0, Math.min(slides.length - 1, index));\n slides.forEach((slide, i) => slide.classList.toggle('active', i === currentSlide));\n document.getElementById('slide-count').textContent = String(currentSlide + 1);\n}\ndocument.addEventListener('keydown', (event) => {\n if (event.key === 'ArrowRight' || event.key === 'PageDown' || event.key === ' ') showSlide(currentSlide + 1);\n if (event.key === 'ArrowLeft' || event.key === 'PageUp') showSlide(currentSlide - 1);\n});\n</script>\n</body>\n</html>",
|
|
4465
4470
|
"htmlPrimitive": "deck",
|
|
4466
4471
|
"primitiveData": {
|
|
4467
4472
|
"slides": [
|
|
@@ -4521,7 +4526,7 @@
|
|
|
4521
4526
|
],
|
|
4522
4527
|
"title": "Why a spatial canvas",
|
|
4523
4528
|
"strictSize": true,
|
|
4524
|
-
"contentSummary": "Why a spatial canvas PMX HTML primitive / deck Why a spatial canvas Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload.
|
|
4529
|
+
"contentSummary": "Why a spatial canvas PMX HTML primitive / deck Why a spatial canvas Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload. 1 / 3 - use left/right arrows 01 Chat is a queue, work is a graph A transcript only remembers order Real work has neighbours, not turns Space is free context Open on the problem, not the product. 02 The canvas is the agent’s working memory Nodes persist across sessions Position carries meaning The agent can read its own board back 03 Pinning is the steering wheel Human pins what matters Agent reads canvas://pinned-context No prompt engineering required Demo the pin loop live if there is time.",
|
|
4525
4530
|
"agentSummary": "Arrow-key HTML deck with sections, speaker notes, and copyable JSON payload. Slides: Chat is a queue, work is a graph, The canvas is the agent’s working memory, Pinning is the steering wheel."
|
|
4526
4531
|
},
|
|
4527
4532
|
"createdBy": {
|
|
@@ -4680,7 +4685,7 @@
|
|
|
4680
4685
|
"collapsed": false,
|
|
4681
4686
|
"pinned": false,
|
|
4682
4687
|
"data": {
|
|
4683
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Figures — the collaboration loop</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / illustration-set</div><h1>Figures — the collaboration loop</h1><p>Inline SVG figure sheet with captions and per-figure SVG copy/export controls.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\"><article class=\"card figure\"><h2>The pin loop</h2><svg viewBox=\"0 0 560 260\" role=\"img\" aria-label=\"The pin loop\" data-figure=\"0\"><defs><marker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\"><path d=\"M0,0 L0,6 L9,3 z\" fill=\"currentColor\"></path></marker></defs><rect x=\"40\" y=\"60\" width=\"150\" height=\"70\" rx=\"14\" fill=\"#22c55e\" opacity=\"0.18\" stroke=\"#22c55e\" stroke-width=\"2\" /><text x=\"52\" y=\"88\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Human</text><line x1=\"196\" y1=\"95\" x2=\"300\" y2=\"95\" stroke=\"#eab308\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><rect x=\"306\" y=\"60\" width=\"150\" height=\"70\" rx=\"14\" fill=\"#7c5cff\" opacity=\"0.18\" stroke=\"#7c5cff\" stroke-width=\"2\" /><text x=\"318\" y=\"88\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Canvas</text><line x1=\"381\" y1=\"136\" x2=\"381\" y2=\"186\" stroke=\"#4a9eff\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><circle cx=\"381\" cy=\"214\" r=\"30\" fill=\"#4a9eff\" opacity=\"0.22\" stroke=\"#4a9eff\" stroke-width=\"2\" /><text x=\"381\" y=\"218\" text-anchor=\"middle\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Agent</text><text x=\"40\" y=\"214\" fill=\"var(--color-text, #e8edf2)\" font-size=\"15\" font-weight=\"700\">pins → canvas://pinned-context</text></svg><p>The human curates space; the agent reads that curation as context.</p><button type=\"button\" data-copy-svg=\"0\">Copy SVG</button></article><article class=\"card figure\"><h2>One state, many surfaces</h2><svg viewBox=\"0 0 560 260\" role=\"img\" aria-label=\"One state, many surfaces\" data-figure=\"1\"><defs><marker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\"><path d=\"M0,0 L0,6 L9,3 z\" fill=\"currentColor\"></path></marker></defs><rect x=\"30\" y=\"40\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#4a9eff\" opacity=\"0.18\" stroke=\"#4a9eff\" stroke-width=\"2\" /><text x=\"42\" y=\"68\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">MCP</text><rect x=\"30\" y=\"108\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#7c5cff\" opacity=\"0.18\" stroke=\"#7c5cff\" stroke-width=\"2\" /><text x=\"42\" y=\"136\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">HTTP</text><rect x=\"30\" y=\"176\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#22c55e\" opacity=\"0.18\" stroke=\"#22c55e\" stroke-width=\"2\" /><text x=\"42\" y=\"204\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">SDK</text><line x1=\"156\" y1=\"66\" x2=\"280\" y2=\"130\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><line x1=\"156\" y1=\"134\" x2=\"280\" y2=\"134\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><line x1=\"156\" y1=\"202\" x2=\"280\" y2=\"138\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><rect x=\"286\" y=\"104\" width=\"200\" height=\"60\" rx=\"14\" fill=\"#eab308\" opacity=\"0.18\" stroke=\"#eab308\" stroke-width=\"2\" /><text x=\"298\" y=\"132\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Operation registry</text></svg><p>MCP, HTTP and the SDK are three doors into the same operation registry.</p><button type=\"button\" data-copy-svg=\"1\">Copy SVG</button></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"figures\": [\n {\n \"title\": \"The pin loop\",\n \"caption\": \"The human curates space; the agent reads that curation as context.\",\n \"shapes\": [\n {\n \"type\": \"rect\",\n \"x\": 40,\n \"y\": 60,\n \"width\": 150,\n \"height\": 70,\n \"text\": \"Human\",\n \"color\": \"#22c55e\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 196,\n \"y1\": 95,\n \"x2\": 300,\n \"y2\": 95,\n \"color\": \"#eab308\"\n },\n {\n \"type\": \"rect\",\n \"x\": 306,\n \"y\": 60,\n \"width\": 150,\n \"height\": 70,\n \"text\": \"Canvas\",\n \"color\": \"#7c5cff\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 381,\n \"y1\": 136,\n \"x2\": 381,\n \"y2\": 186,\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"circle\",\n \"cx\": 381,\n \"cy\": 214,\n \"r\": 30,\n \"text\": \"Agent\",\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"text\",\n \"x\": 40,\n \"y\": 214,\n \"text\": \"pins → canvas://pinned-context\",\n \"color\": \"#8994b3\"\n }\n ]\n },\n {\n \"title\": \"One state, many surfaces\",\n \"caption\": \"MCP, HTTP and the SDK are three doors into the same operation registry.\",\n \"shapes\": [\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 40,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"MCP\",\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 108,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"HTTP\",\n \"color\": \"#7c5cff\"\n },\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 176,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"SDK\",\n \"color\": \"#22c55e\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 66,\n \"x2\": 280,\n \"y2\": 130,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 134,\n \"x2\": 280,\n \"y2\": 134,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 202,\n \"x2\": 280,\n \"y2\": 138,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"rect\",\n \"x\": 286,\n \"y\": 104,\n \"width\": 200,\n \"height\": 60,\n \"text\": \"Operation registry\",\n \"color\": \"#eab308\"\n }\n ]\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\ndocument.querySelectorAll('[data-copy-svg]').forEach((button) => button.addEventListener('click', () => {\n const svg = document.querySelector('[data-figure=\"' + button.getAttribute('data-copy-svg') + '\"]');\n copyText(svg ? svg.outerHTML : '');\n}));\n</script>\n</body>\n</html>",
|
|
4688
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Figures — the collaboration loop</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / illustration-set</div><h1>Figures — the collaboration loop</h1><p>Inline SVG figure sheet with captions and per-figure SVG copy/export controls.</p></div>\n </header>\n <section class=\"grid\"><article class=\"card figure\"><h2>The pin loop</h2><svg viewBox=\"0 0 560 260\" role=\"img\" aria-label=\"The pin loop\" data-figure=\"0\"><defs><marker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\"><path d=\"M0,0 L0,6 L9,3 z\" fill=\"currentColor\"></path></marker></defs><rect x=\"40\" y=\"60\" width=\"150\" height=\"70\" rx=\"14\" fill=\"#22c55e\" opacity=\"0.18\" stroke=\"#22c55e\" stroke-width=\"2\" /><text x=\"52\" y=\"88\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Human</text><line x1=\"196\" y1=\"95\" x2=\"300\" y2=\"95\" stroke=\"#eab308\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><rect x=\"306\" y=\"60\" width=\"150\" height=\"70\" rx=\"14\" fill=\"#7c5cff\" opacity=\"0.18\" stroke=\"#7c5cff\" stroke-width=\"2\" /><text x=\"318\" y=\"88\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Canvas</text><line x1=\"381\" y1=\"136\" x2=\"381\" y2=\"186\" stroke=\"#4a9eff\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><circle cx=\"381\" cy=\"214\" r=\"30\" fill=\"#4a9eff\" opacity=\"0.22\" stroke=\"#4a9eff\" stroke-width=\"2\" /><text x=\"381\" y=\"218\" text-anchor=\"middle\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Agent</text><text x=\"40\" y=\"214\" fill=\"var(--color-text, #e8edf2)\" font-size=\"15\" font-weight=\"700\">pins → canvas://pinned-context</text></svg><p>The human curates space; the agent reads that curation as context.</p><button type=\"button\" data-copy-svg=\"0\">Copy SVG</button></article><article class=\"card figure\"><h2>One state, many surfaces</h2><svg viewBox=\"0 0 560 260\" role=\"img\" aria-label=\"One state, many surfaces\" data-figure=\"1\"><defs><marker id=\"arrow\" markerWidth=\"10\" markerHeight=\"10\" refX=\"9\" refY=\"3\" orient=\"auto\" markerUnits=\"strokeWidth\"><path d=\"M0,0 L0,6 L9,3 z\" fill=\"currentColor\"></path></marker></defs><rect x=\"30\" y=\"40\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#4a9eff\" opacity=\"0.18\" stroke=\"#4a9eff\" stroke-width=\"2\" /><text x=\"42\" y=\"68\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">MCP</text><rect x=\"30\" y=\"108\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#7c5cff\" opacity=\"0.18\" stroke=\"#7c5cff\" stroke-width=\"2\" /><text x=\"42\" y=\"136\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">HTTP</text><rect x=\"30\" y=\"176\" width=\"120\" height=\"52\" rx=\"14\" fill=\"#22c55e\" opacity=\"0.18\" stroke=\"#22c55e\" stroke-width=\"2\" /><text x=\"42\" y=\"204\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">SDK</text><line x1=\"156\" y1=\"66\" x2=\"280\" y2=\"130\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><line x1=\"156\" y1=\"134\" x2=\"280\" y2=\"134\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><line x1=\"156\" y1=\"202\" x2=\"280\" y2=\"138\" stroke=\"#8994b3\" stroke-width=\"2.5\" stroke-linecap=\"round\" marker-end=\"url(#arrow)\"/><rect x=\"286\" y=\"104\" width=\"200\" height=\"60\" rx=\"14\" fill=\"#eab308\" opacity=\"0.18\" stroke=\"#eab308\" stroke-width=\"2\" /><text x=\"298\" y=\"132\" fill=\"var(--color-text, #e8edf2)\" font-size=\"13\" font-weight=\"700\">Operation registry</text></svg><p>MCP, HTTP and the SDK are three doors into the same operation registry.</p><button type=\"button\" data-copy-svg=\"1\">Copy SVG</button></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"figures\": [\n {\n \"title\": \"The pin loop\",\n \"caption\": \"The human curates space; the agent reads that curation as context.\",\n \"shapes\": [\n {\n \"type\": \"rect\",\n \"x\": 40,\n \"y\": 60,\n \"width\": 150,\n \"height\": 70,\n \"text\": \"Human\",\n \"color\": \"#22c55e\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 196,\n \"y1\": 95,\n \"x2\": 300,\n \"y2\": 95,\n \"color\": \"#eab308\"\n },\n {\n \"type\": \"rect\",\n \"x\": 306,\n \"y\": 60,\n \"width\": 150,\n \"height\": 70,\n \"text\": \"Canvas\",\n \"color\": \"#7c5cff\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 381,\n \"y1\": 136,\n \"x2\": 381,\n \"y2\": 186,\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"circle\",\n \"cx\": 381,\n \"cy\": 214,\n \"r\": 30,\n \"text\": \"Agent\",\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"text\",\n \"x\": 40,\n \"y\": 214,\n \"text\": \"pins → canvas://pinned-context\",\n \"color\": \"#8994b3\"\n }\n ]\n },\n {\n \"title\": \"One state, many surfaces\",\n \"caption\": \"MCP, HTTP and the SDK are three doors into the same operation registry.\",\n \"shapes\": [\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 40,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"MCP\",\n \"color\": \"#4a9eff\"\n },\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 108,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"HTTP\",\n \"color\": \"#7c5cff\"\n },\n {\n \"type\": \"rect\",\n \"x\": 30,\n \"y\": 176,\n \"width\": 120,\n \"height\": 52,\n \"text\": \"SDK\",\n \"color\": \"#22c55e\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 66,\n \"x2\": 280,\n \"y2\": 130,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 134,\n \"x2\": 280,\n \"y2\": 134,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"arrow\",\n \"x1\": 156,\n \"y1\": 202,\n \"x2\": 280,\n \"y2\": 138,\n \"color\": \"#8994b3\"\n },\n {\n \"type\": \"rect\",\n \"x\": 286,\n \"y\": 104,\n \"width\": 200,\n \"height\": 60,\n \"text\": \"Operation registry\",\n \"color\": \"#eab308\"\n }\n ]\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\ndocument.querySelectorAll('[data-copy-svg]').forEach((button) => button.addEventListener('click', () => {\n const svg = document.querySelector('[data-figure=\"' + button.getAttribute('data-copy-svg') + '\"]');\n copyText(svg ? svg.outerHTML : '');\n}));\n</script>\n</body>\n</html>",
|
|
4684
4689
|
"htmlPrimitive": "illustration-set",
|
|
4685
4690
|
"primitiveData": {
|
|
4686
4691
|
"figures": [
|
|
@@ -4810,7 +4815,7 @@
|
|
|
4810
4815
|
"description": "Inline SVG figure sheet with captions and per-figure SVG copy/export controls.",
|
|
4811
4816
|
"title": "Figures — the collaboration loop",
|
|
4812
4817
|
"strictSize": true,
|
|
4813
|
-
"contentSummary": "Figures — the collaboration loop PMX HTML primitive / illustration-set Figures — the collaboration loop Inline SVG figure sheet with captions and per-figure SVG copy/export controls.
|
|
4818
|
+
"contentSummary": "Figures — the collaboration loop PMX HTML primitive / illustration-set Figures — the collaboration loop Inline SVG figure sheet with captions and per-figure SVG copy/export controls. The pin loop Human Canvas Agent pins → canvas://pinned-context The human curates space; the agent reads that curation as context. Copy SVG One state, many surfaces MCP HTTP SDK Operation registry MCP, HTTP and the SDK are three doors into the same operation registry. Copy SVG",
|
|
4814
4819
|
"agentSummary": "Inline SVG figure sheet with captions and per-figure SVG copy/export controls."
|
|
4815
4820
|
},
|
|
4816
4821
|
"createdBy": {
|
|
@@ -4848,7 +4853,7 @@
|
|
|
4848
4853
|
"collapsed": false,
|
|
4849
4854
|
"pinned": false,
|
|
4850
4855
|
"data": {
|
|
4851
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>How context pins work</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / explainer</div><h1>How context pins work</h1><p>Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"panel\"><h2>TLDR</h2><p>A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.</p></section>\n <section class=\"grid\" style=\"margin-top:14px\"><details open><summary><strong>1. The human pins a node</strong></summary><p>Clicking the pin affordance adds the node id to the canvas pin set (capped at 20).</p></details><details open><summary><strong>2. The server notifies</strong></summary><p>Pin changes emit notifications/resources/updated for canvas://pinned-context.</p></details><details open><summary><strong>3. The agent reads the resource</strong></summary><p>It receives the pinned nodes plus, for each, the nearby unpinned nodes — the implicit context.</p></details><details open><summary><strong>4. Spatial neighbours come along</strong></summary><p>Proximity clustering means the human never has to pin everything, only the anchors.</p></details></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Annotated Snippets</h2><h3>Pin from an agent</h3><p>mode defaults to "set", which replaces the whole list.</p><pre><code>canvas_pin_nodes({ nodeIds: ["node-a", "node-b"], mode: "add" })</code></pre><h3>Read it back</h3><p>Includes the neighbourhood for each pin.</p><pre><code>resources/read canvas://pinned-context</code></pre></section>\n <section class=\"two\" style=\"margin-top:14px\"><div class=\"panel\"><h2>FAQ</h2><details><summary>Does pinning move or lock the node?</summary><p>No. Context pins are separate from arrange-locking; a pinned node still moves.</p></details><details><summary>What happens when a pinned node is deleted?</summary><p>The pin is dropped with it — pins always resolve to live nodes.</p></details><details><summary>Is there a limit?</summary><p>Twenty pins. Past that, curation stops being curation.</p></details></div><div class=\"panel\"><h2>Glossary</h2><div class=\"card\"><strong>Context pin</strong><p>A human-curated marker that puts a node into the agent’s context.</p></div><div class=\"card\"><strong>Neighbourhood</strong><p>The unpinned nodes spatially near a pin, surfaced alongside it.</p></div><div class=\"card\"><strong>Reading order</strong><p>Nodes sorted top-left to bottom-right, the way a human scans a board.</p></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.\",\n \"steps\": [\n {\n \"title\": \"The human pins a node\",\n \"detail\": \"Clicking the pin affordance adds the node id to the canvas pin set (capped at 20).\"\n },\n {\n \"title\": \"The server notifies\",\n \"detail\": \"Pin changes emit notifications/resources/updated for canvas://pinned-context.\"\n },\n {\n \"title\": \"The agent reads the resource\",\n \"detail\": \"It receives the pinned nodes plus, for each, the nearby unpinned nodes — the implicit context.\"\n },\n {\n \"title\": \"Spatial neighbours come along\",\n \"detail\": \"Proximity clustering means the human never has to pin everything, only the anchors.\"\n }\n ],\n \"snippets\": [\n {\n \"label\": \"Pin from an agent\",\n \"code\": \"canvas_pin_nodes({ nodeIds: [\\\"node-a\\\", \\\"node-b\\\"], mode: \\\"add\\\" })\",\n \"note\": \"mode defaults to \\\"set\\\", which replaces the whole list.\"\n },\n {\n \"label\": \"Read it back\",\n \"code\": \"resources/read canvas://pinned-context\",\n \"note\": \"Includes the neighbourhood for each pin.\"\n }\n ],\n \"faq\": [\n {\n \"q\": \"Does pinning move or lock the node?\",\n \"a\": \"No. Context pins are separate from arrange-locking; a pinned node still moves.\"\n },\n {\n \"q\": \"What happens when a pinned node is deleted?\",\n \"a\": \"The pin is dropped with it — pins always resolve to live nodes.\"\n },\n {\n \"q\": \"Is there a limit?\",\n \"a\": \"Twenty pins. Past that, curation stops being curation.\"\n }\n ],\n \"glossary\": [\n {\n \"term\": \"Context pin\",\n \"definition\": \"A human-curated marker that puts a node into the agent’s context.\"\n },\n {\n \"term\": \"Neighbourhood\",\n \"definition\": \"The unpinned nodes spatially near a pin, surfaced alongside it.\"\n },\n {\n \"term\": \"Reading order\",\n \"definition\": \"Nodes sorted top-left to bottom-right, the way a human scans a board.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
4856
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>How context pins work</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / explainer</div><h1>How context pins work</h1><p>Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary.</p></div>\n </header>\n <section class=\"panel\"><h2>TLDR</h2><p>A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.</p></section>\n <section class=\"grid\" style=\"margin-top:14px\"><details open><summary><strong>1. The human pins a node</strong></summary><p>Clicking the pin affordance adds the node id to the canvas pin set (capped at 20).</p></details><details open><summary><strong>2. The server notifies</strong></summary><p>Pin changes emit notifications/resources/updated for canvas://pinned-context.</p></details><details open><summary><strong>3. The agent reads the resource</strong></summary><p>It receives the pinned nodes plus, for each, the nearby unpinned nodes — the implicit context.</p></details><details open><summary><strong>4. Spatial neighbours come along</strong></summary><p>Proximity clustering means the human never has to pin everything, only the anchors.</p></details></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Annotated Snippets</h2><h3>Pin from an agent</h3><p>mode defaults to "set", which replaces the whole list.</p><pre><code>canvas_pin_nodes({ nodeIds: ["node-a", "node-b"], mode: "add" })</code></pre><h3>Read it back</h3><p>Includes the neighbourhood for each pin.</p><pre><code>resources/read canvas://pinned-context</code></pre></section>\n <section class=\"two\" style=\"margin-top:14px\"><div class=\"panel\"><h2>FAQ</h2><details><summary>Does pinning move or lock the node?</summary><p>No. Context pins are separate from arrange-locking; a pinned node still moves.</p></details><details><summary>What happens when a pinned node is deleted?</summary><p>The pin is dropped with it — pins always resolve to live nodes.</p></details><details><summary>Is there a limit?</summary><p>Twenty pins. Past that, curation stops being curation.</p></details></div><div class=\"panel\"><h2>Glossary</h2><div class=\"card\"><strong>Context pin</strong><p>A human-curated marker that puts a node into the agent’s context.</p></div><div class=\"card\"><strong>Neighbourhood</strong><p>The unpinned nodes spatially near a pin, surfaced alongside it.</p></div><div class=\"card\"><strong>Reading order</strong><p>Nodes sorted top-left to bottom-right, the way a human scans a board.</p></div></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"summary\": \"A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.\",\n \"steps\": [\n {\n \"title\": \"The human pins a node\",\n \"detail\": \"Clicking the pin affordance adds the node id to the canvas pin set (capped at 20).\"\n },\n {\n \"title\": \"The server notifies\",\n \"detail\": \"Pin changes emit notifications/resources/updated for canvas://pinned-context.\"\n },\n {\n \"title\": \"The agent reads the resource\",\n \"detail\": \"It receives the pinned nodes plus, for each, the nearby unpinned nodes — the implicit context.\"\n },\n {\n \"title\": \"Spatial neighbours come along\",\n \"detail\": \"Proximity clustering means the human never has to pin everything, only the anchors.\"\n }\n ],\n \"snippets\": [\n {\n \"label\": \"Pin from an agent\",\n \"code\": \"canvas_pin_nodes({ nodeIds: [\\\"node-a\\\", \\\"node-b\\\"], mode: \\\"add\\\" })\",\n \"note\": \"mode defaults to \\\"set\\\", which replaces the whole list.\"\n },\n {\n \"label\": \"Read it back\",\n \"code\": \"resources/read canvas://pinned-context\",\n \"note\": \"Includes the neighbourhood for each pin.\"\n }\n ],\n \"faq\": [\n {\n \"q\": \"Does pinning move or lock the node?\",\n \"a\": \"No. Context pins are separate from arrange-locking; a pinned node still moves.\"\n },\n {\n \"q\": \"What happens when a pinned node is deleted?\",\n \"a\": \"The pin is dropped with it — pins always resolve to live nodes.\"\n },\n {\n \"q\": \"Is there a limit?\",\n \"a\": \"Twenty pins. Past that, curation stops being curation.\"\n }\n ],\n \"glossary\": [\n {\n \"term\": \"Context pin\",\n \"definition\": \"A human-curated marker that puts a node into the agent’s context.\"\n },\n {\n \"term\": \"Neighbourhood\",\n \"definition\": \"The unpinned nodes spatially near a pin, surfaced alongside it.\"\n },\n {\n \"term\": \"Reading order\",\n \"definition\": \"Nodes sorted top-left to bottom-right, the way a human scans a board.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
4852
4857
|
"htmlPrimitive": "explainer",
|
|
4853
4858
|
"primitiveData": {
|
|
4854
4859
|
"summary": "A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.",
|
|
@@ -4915,7 +4920,7 @@
|
|
|
4915
4920
|
"summary": "A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free.",
|
|
4916
4921
|
"title": "How context pins work",
|
|
4917
4922
|
"strictSize": true,
|
|
4918
|
-
"contentSummary": "How context pins work PMX HTML primitive / explainer How context pins work Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary.
|
|
4923
|
+
"contentSummary": "How context pins work PMX HTML primitive / explainer How context pins work Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary. TLDR A pin is a small, durable statement of relevance. The human makes it with a click; the agent reads it as a resource and gets the neighbourhood around it for free. 1. The human pins a node Clicking the pin affordance adds the node id to the canvas pin set (capped at 20). 2. The server notifies Pin changes emit notifications/resources/updated for canvas://pinned-context. 3. The agent reads the resource It receives the pinned nodes plus, for each, the nearby unpinned nodes — the implicit context. 4. Spatial neighbours come along Proximity clustering means the human never has to pin everything, only the anchors. Annotated Snippets Pin from an agent mode defaults to \"set\", which replaces the whole list. canvas_pin_nodes({ no...",
|
|
4919
4924
|
"agentSummary": "Readable explainer with TLDR, collapsible steps, annotated snippets, FAQ, and glossary.",
|
|
4920
4925
|
"embeddedNodeIds": [
|
|
4921
4926
|
"node-a",
|
|
@@ -4957,7 +4962,7 @@
|
|
|
4957
4962
|
"collapsed": false,
|
|
4958
4963
|
"pinned": false,
|
|
4959
4964
|
"data": {
|
|
4960
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Blank ext-app tiles on WebKit</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / incident-report</div><h1>Blank ext-app tiles on WebKit</h1><p>Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\"><article class=\"card metric\"><div class=\"small\">Affected hosts</div><strong>Safari + WKWebView</strong><span class=\"badge warn\">warn</span></article><article class=\"card metric\"><div class=\"small\">Boards affected</div><strong>4</strong><span class=\"badge warn\">warn</span></article><article class=\"card metric\"><div class=\"small\">Data loss</div><strong>none</strong><span class=\"badge ok\">ok</span></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Executive Summary</h2><p>Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited.</p></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>Timeline</h2><div class=\"timeline\"><div class=\"step\"><div class=\"dot\">09:41</div><div class=\"card\"><h3>Report filed <span class=\"badge warn\">warn</span></h3><p>Screenshot shows four black tiles after reload.</p></div></div><div class=\"step\"><div class=\"dot\">10:05</div><div class=\"card\"><h3>Reproduced <span class=\"badge warn\">warn</span></h3><p>Only when several apps hydrate in the same frame.</p></div></div><div class=\"step\"><div class=\"dot\">10:52</div><div class=\"card\"><h3>Root cause found <span class=\"badge info\">info</span></h3><p>Burst hydration overwhelms the WebKit compositor.</p></div></div><div class=\"step\"><div class=\"dot\">12:10</div><div class=\"card\"><h3>Fix landed <span class=\"badge ok\">ok</span></h3><p>Serialized, boot-aware remount queue; one app at a time.</p></div></div><div class=\"step\"><div class=\"dot\">12:53</div><div class=\"card\"><h3>Verified <span class=\"badge ok\">ok</span></h3><p>Ten consecutive cold reloads, no black tiles.</p></div></div></div></div>\n <div class=\"panel\"><h2>Root Cause</h2><p>A fixed-stagger remount was not boot-aware: each recovery remount reboots the app, so staggered remounts overlapped into a fresh burst and the per-node one-shot flag was already spent.</p><pre><code>[extapp] remount nodeId=mcp-app-1 reason=paint-probe-timeout\n[extapp] boot-wait nodeId=mcp-app-1 elapsed=1840ms\n[extapp] settled nodeId=mcp-app-1</code></pre></div>\n <div class=\"panel sticky\"><h2>Actions</h2><label class=\"card\"><h3><input type=\"checkbox\" data-action checked> Serialize remounts behind a boot-aware queue</h3><p class=\"small\">client / 0.4.6</p></label><label class=\"card\"><h3><input type=\"checkbox\" data-action checked> Mirror the recovery trail to the daemon for hosts without devtools</h3><p class=\"small\">client / 0.4.6</p></label><label class=\"card\"><h3><input type=\"checkbox\" data-action > Add a cold-reload burst case to the e2e suite</h3><p class=\"small\">qa / 0.4.8</p></label><button type=\"button\" data-copy-actions>Copy actions</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"severity\": \"SEV-3\",\n \"status\": \"resolved\",\n \"duration\": \"3h 12m\",\n \"summary\": \"Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited.\",\n \"impact\": [\n {\n \"label\": \"Affected hosts\",\n \"value\": \"Safari + WKWebView\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Boards affected\",\n \"value\": \"4\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Data loss\",\n \"value\": \"none\",\n \"tone\": \"ok\"\n }\n ],\n \"timeline\": [\n {\n \"time\": \"09:41\",\n \"event\": \"Report filed\",\n \"detail\": \"Screenshot shows four black tiles after reload.\",\n \"tone\": \"warn\"\n },\n {\n \"time\": \"10:05\",\n \"event\": \"Reproduced\",\n \"detail\": \"Only when several apps hydrate in the same frame.\",\n \"tone\": \"warn\"\n },\n {\n \"time\": \"10:52\",\n \"event\": \"Root cause found\",\n \"detail\": \"Burst hydration overwhelms the WebKit compositor.\",\n \"tone\": \"info\"\n },\n {\n \"time\": \"12:10\",\n \"event\": \"Fix landed\",\n \"detail\": \"Serialized, boot-aware remount queue; one app at a time.\",\n \"tone\": \"ok\"\n },\n {\n \"time\": \"12:53\",\n \"event\": \"Verified\",\n \"detail\": \"Ten consecutive cold reloads, no black tiles.\",\n \"tone\": \"ok\"\n }\n ],\n \"rootCause\": \"A fixed-stagger remount was not boot-aware: each recovery remount reboots the app, so staggered remounts overlapped into a fresh burst and the per-node one-shot flag was already spent.\",\n \"logs\": \"[extapp] remount nodeId=mcp-app-1 reason=paint-probe-timeout\\n[extapp] boot-wait nodeId=mcp-app-1 elapsed=1840ms\\n[extapp] settled nodeId=mcp-app-1\",\n \"actions\": [\n {\n \"done\": true,\n \"owner\": \"client\",\n \"description\": \"Serialize remounts behind a boot-aware queue\",\n \"due\": \"0.4.6\"\n },\n {\n \"done\": true,\n \"owner\": \"client\",\n \"description\": \"Mirror the recovery trail to the daemon for hosts without devtools\",\n \"due\": \"0.4.6\"\n },\n {\n \"done\": false,\n \"owner\": \"qa\",\n \"description\": \"Add a cold-reload burst case to the e2e suite\",\n \"due\": \"0.4.8\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nfunction actionState() {\n return Array.from(document.querySelectorAll('[data-action]')).map((input, index) => ({ ...(PMX_DATA.actions || [])[index], done: input.checked }));\n}\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, actions: actionState() });\ndocument.querySelector('[data-copy-actions]')?.addEventListener('click', () => copyText(actionState().map((action) => '- [' + (action.done ? 'x' : ' ') + '] ' + (action.description || 'Action') + ' (' + (action.owner || 'owner') + ', ' + (action.due || 'due') + ')').join('\\n')));\n</script>\n</body>\n</html>",
|
|
4965
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Blank ext-app tiles on WebKit</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / incident-report</div><h1>Blank ext-app tiles on WebKit</h1><p>Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist.</p></div>\n </header>\n <section class=\"grid\"><article class=\"card metric\"><div class=\"small\">Affected hosts</div><strong>Safari + WKWebView</strong><span class=\"badge warn\">warn</span></article><article class=\"card metric\"><div class=\"small\">Boards affected</div><strong>4</strong><span class=\"badge warn\">warn</span></article><article class=\"card metric\"><div class=\"small\">Data loss</div><strong>none</strong><span class=\"badge ok\">ok</span></article></section>\n <section class=\"panel\" style=\"margin-top:14px\"><h2>Executive Summary</h2><p>Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited.</p></section>\n <section class=\"three\" style=\"margin-top:14px\"><div class=\"panel\"><h2>Timeline</h2><div class=\"timeline\"><div class=\"step\"><div class=\"dot\">09:41</div><div class=\"card\"><h3>Report filed <span class=\"badge warn\">warn</span></h3><p>Screenshot shows four black tiles after reload.</p></div></div><div class=\"step\"><div class=\"dot\">10:05</div><div class=\"card\"><h3>Reproduced <span class=\"badge warn\">warn</span></h3><p>Only when several apps hydrate in the same frame.</p></div></div><div class=\"step\"><div class=\"dot\">10:52</div><div class=\"card\"><h3>Root cause found <span class=\"badge info\">info</span></h3><p>Burst hydration overwhelms the WebKit compositor.</p></div></div><div class=\"step\"><div class=\"dot\">12:10</div><div class=\"card\"><h3>Fix landed <span class=\"badge ok\">ok</span></h3><p>Serialized, boot-aware remount queue; one app at a time.</p></div></div><div class=\"step\"><div class=\"dot\">12:53</div><div class=\"card\"><h3>Verified <span class=\"badge ok\">ok</span></h3><p>Ten consecutive cold reloads, no black tiles.</p></div></div></div></div>\n <div class=\"panel\"><h2>Root Cause</h2><p>A fixed-stagger remount was not boot-aware: each recovery remount reboots the app, so staggered remounts overlapped into a fresh burst and the per-node one-shot flag was already spent.</p><pre><code>[extapp] remount nodeId=mcp-app-1 reason=paint-probe-timeout\n[extapp] boot-wait nodeId=mcp-app-1 elapsed=1840ms\n[extapp] settled nodeId=mcp-app-1</code></pre></div>\n <div class=\"panel sticky\"><h2>Actions</h2><label class=\"card\"><h3><input type=\"checkbox\" data-action checked> Serialize remounts behind a boot-aware queue</h3><p class=\"small\">client / 0.4.6</p></label><label class=\"card\"><h3><input type=\"checkbox\" data-action checked> Mirror the recovery trail to the daemon for hosts without devtools</h3><p class=\"small\">client / 0.4.6</p></label><label class=\"card\"><h3><input type=\"checkbox\" data-action > Add a cold-reload burst case to the e2e suite</h3><p class=\"small\">qa / 0.4.8</p></label><button type=\"button\" data-copy-actions>Copy actions</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"severity\": \"SEV-3\",\n \"status\": \"resolved\",\n \"duration\": \"3h 12m\",\n \"summary\": \"Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited.\",\n \"impact\": [\n {\n \"label\": \"Affected hosts\",\n \"value\": \"Safari + WKWebView\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Boards affected\",\n \"value\": \"4\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Data loss\",\n \"value\": \"none\",\n \"tone\": \"ok\"\n }\n ],\n \"timeline\": [\n {\n \"time\": \"09:41\",\n \"event\": \"Report filed\",\n \"detail\": \"Screenshot shows four black tiles after reload.\",\n \"tone\": \"warn\"\n },\n {\n \"time\": \"10:05\",\n \"event\": \"Reproduced\",\n \"detail\": \"Only when several apps hydrate in the same frame.\",\n \"tone\": \"warn\"\n },\n {\n \"time\": \"10:52\",\n \"event\": \"Root cause found\",\n \"detail\": \"Burst hydration overwhelms the WebKit compositor.\",\n \"tone\": \"info\"\n },\n {\n \"time\": \"12:10\",\n \"event\": \"Fix landed\",\n \"detail\": \"Serialized, boot-aware remount queue; one app at a time.\",\n \"tone\": \"ok\"\n },\n {\n \"time\": \"12:53\",\n \"event\": \"Verified\",\n \"detail\": \"Ten consecutive cold reloads, no black tiles.\",\n \"tone\": \"ok\"\n }\n ],\n \"rootCause\": \"A fixed-stagger remount was not boot-aware: each recovery remount reboots the app, so staggered remounts overlapped into a fresh burst and the per-node one-shot flag was already spent.\",\n \"logs\": \"[extapp] remount nodeId=mcp-app-1 reason=paint-probe-timeout\\n[extapp] boot-wait nodeId=mcp-app-1 elapsed=1840ms\\n[extapp] settled nodeId=mcp-app-1\",\n \"actions\": [\n {\n \"done\": true,\n \"owner\": \"client\",\n \"description\": \"Serialize remounts behind a boot-aware queue\",\n \"due\": \"0.4.6\"\n },\n {\n \"done\": true,\n \"owner\": \"client\",\n \"description\": \"Mirror the recovery trail to the daemon for hosts without devtools\",\n \"due\": \"0.4.6\"\n },\n {\n \"done\": false,\n \"owner\": \"qa\",\n \"description\": \"Add a cold-reload burst case to the e2e suite\",\n \"due\": \"0.4.8\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nfunction actionState() {\n return Array.from(document.querySelectorAll('[data-action]')).map((input, index) => ({ ...(PMX_DATA.actions || [])[index], done: input.checked }));\n}\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, actions: actionState() });\ndocument.querySelector('[data-copy-actions]')?.addEventListener('click', () => copyText(actionState().map((action) => '- [' + (action.done ? 'x' : ' ') + '] ' + (action.description || 'Action') + ' (' + (action.owner || 'owner') + ', ' + (action.due || 'due') + ')').join('\\n')));\n</script>\n</body>\n</html>",
|
|
4961
4966
|
"htmlPrimitive": "incident-report",
|
|
4962
4967
|
"primitiveData": {
|
|
4963
4968
|
"severity": "SEV-3",
|
|
@@ -5040,7 +5045,7 @@
|
|
|
5040
5045
|
"summary": "Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited.",
|
|
5041
5046
|
"title": "Blank ext-app tiles on WebKit",
|
|
5042
5047
|
"strictSize": true,
|
|
5043
|
-
"contentSummary": "Blank ext-app tiles on WebKit PMX HTML primitive / incident-report Blank ext-app tiles on WebKit Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist.
|
|
5048
|
+
"contentSummary": "Blank ext-app tiles on WebKit PMX HTML primitive / incident-report Blank ext-app tiles on WebKit Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist. Affected hosts Safari + WKWebView warn Boards affected 4 warn Data loss none ok Executive Summary Several MCP App nodes rendered as black tiles after a cold reload in WebKit hosts. Content was present in the DOM but never composited. Timeline 09:41 Report filed warn Screenshot shows four black tiles after reload. 10:05 Reproduced warn Only when several apps hydrate in the same frame. 10:52 Root cause found info Burst hydration overwhelms the WebKit compositor. 12:10 Fix landed ok Serialized, boot-aware remount queue; one app at a time. 12:53 Verified ok Ten consecutive cold reloads, no black tiles. Root Cause A fixed-stagger remount was not boot-aware: each recovery remount...",
|
|
5044
5049
|
"agentSummary": "Post-incident report with impact metrics, minute-by-minute timeline, root cause, log excerpts, and action checklist.",
|
|
5045
5050
|
"embeddedNodeIds": [
|
|
5046
5051
|
"mcp-app-1"
|
|
@@ -5081,7 +5086,7 @@
|
|
|
5081
5086
|
"collapsed": false,
|
|
5082
5087
|
"pinned": false,
|
|
5083
5088
|
"data": {
|
|
5084
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Weekly canvas status</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / status-report</div><h1>Weekly canvas status</h1><p>Skimmable report with metrics, shipped/slipped lists, blockers, and next actions.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\"><article class=\"card\"><div class=\"small\">Gates</div><h2>4/5 green</h2><span class=\"badge warn\">warn</span></article><article class=\"card\"><div class=\"small\">Unit tests</div><h2>612 passing</h2><span class=\"badge ok\">ok</span></article><article class=\"card\"><div class=\"small\">Coverage</div><h2>71% lines</h2><span class=\"badge ok\">ok</span></article><article class=\"card\"><div class=\"small\">Open bugs</div><h2>7</h2><span class=\"badge warn\">warn</span></article><article class=\"card\"><div class=\"small\">Bundle</div><h2>+4.1%</h2><span class=\"badge warn\">warn</span></article></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><h2>Shipped</h2><ul><li>Delimited files render as tables instead of raw text</li><li>Shared readability floor between server and client</li><li>Generated demo board covering every public surface</li></ul></article><article class=\"card\"><h2>Slipped</h2><ul><li>Per-band deep links</li><li>Snapshot diff in the browser</li></ul></article><article class=\"card\"><h2>Risks</h2><ul><li>Bundle growth is trending up three weeks running</li><li>e2e suite is the slowest gate and the flakiest</li></ul></article><article class=\"card\"><h2>Next</h2><ul><li>Cut the bundle back under the 0.4.5 baseline</li><li>Record the 60-second tour off the new demo board</li></ul></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"metrics\": [\n {\n \"label\": \"Gates\",\n \"value\": \"4/5 green\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Unit tests\",\n \"value\": \"612 passing\",\n \"tone\": \"ok\"\n },\n {\n \"label\": \"Coverage\",\n \"value\": \"71% lines\",\n \"tone\": \"ok\"\n },\n {\n \"label\": \"Open bugs\",\n \"value\": \"7\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Bundle\",\n \"value\": \"+4.1%\",\n \"tone\": \"warn\"\n }\n ],\n \"shipped\": [\n \"Delimited files render as tables instead of raw text\",\n \"Shared readability floor between server and client\",\n \"Generated demo board covering every public surface\"\n ],\n \"slipped\": [\n \"Per-band deep links\",\n \"Snapshot diff in the browser\"\n ],\n \"risks\": [\n \"Bundle growth is trending up three weeks running\",\n \"e2e suite is the slowest gate and the flakiest\"\n ],\n \"next\": [\n \"Cut the bundle back under the 0.4.5 baseline\",\n \"Record the 60-second tour off the new demo board\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\n</script>\n</body>\n</html>",
|
|
5089
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Weekly canvas status</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / status-report</div><h1>Weekly canvas status</h1><p>Skimmable report with metrics, shipped/slipped lists, blockers, and next actions.</p></div>\n </header>\n <section class=\"grid\"><article class=\"card\"><div class=\"small\">Gates</div><h2>4/5 green</h2><span class=\"badge warn\">warn</span></article><article class=\"card\"><div class=\"small\">Unit tests</div><h2>612 passing</h2><span class=\"badge ok\">ok</span></article><article class=\"card\"><div class=\"small\">Coverage</div><h2>71% lines</h2><span class=\"badge ok\">ok</span></article><article class=\"card\"><div class=\"small\">Open bugs</div><h2>7</h2><span class=\"badge warn\">warn</span></article><article class=\"card\"><div class=\"small\">Bundle</div><h2>+4.1%</h2><span class=\"badge warn\">warn</span></article></section>\n <section class=\"grid\" style=\"margin-top:14px\"><article class=\"card\"><h2>Shipped</h2><ul><li>Delimited files render as tables instead of raw text</li><li>Shared readability floor between server and client</li><li>Generated demo board covering every public surface</li></ul></article><article class=\"card\"><h2>Slipped</h2><ul><li>Per-band deep links</li><li>Snapshot diff in the browser</li></ul></article><article class=\"card\"><h2>Risks</h2><ul><li>Bundle growth is trending up three weeks running</li><li>e2e suite is the slowest gate and the flakiest</li></ul></article><article class=\"card\"><h2>Next</h2><ul><li>Cut the bundle back under the 0.4.5 baseline</li><li>Record the 60-second tour off the new demo board</li></ul></article></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"metrics\": [\n {\n \"label\": \"Gates\",\n \"value\": \"4/5 green\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Unit tests\",\n \"value\": \"612 passing\",\n \"tone\": \"ok\"\n },\n {\n \"label\": \"Coverage\",\n \"value\": \"71% lines\",\n \"tone\": \"ok\"\n },\n {\n \"label\": \"Open bugs\",\n \"value\": \"7\",\n \"tone\": \"warn\"\n },\n {\n \"label\": \"Bundle\",\n \"value\": \"+4.1%\",\n \"tone\": \"warn\"\n }\n ],\n \"shipped\": [\n \"Delimited files render as tables instead of raw text\",\n \"Shared readability floor between server and client\",\n \"Generated demo board covering every public surface\"\n ],\n \"slipped\": [\n \"Per-band deep links\",\n \"Snapshot diff in the browser\"\n ],\n \"risks\": [\n \"Bundle growth is trending up three weeks running\",\n \"e2e suite is the slowest gate and the flakiest\"\n ],\n \"next\": [\n \"Cut the bundle back under the 0.4.5 baseline\",\n \"Record the 60-second tour off the new demo board\"\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\n</script>\n</body>\n</html>",
|
|
5085
5090
|
"htmlPrimitive": "status-report",
|
|
5086
5091
|
"primitiveData": {
|
|
5087
5092
|
"metrics": [
|
|
@@ -5132,7 +5137,7 @@
|
|
|
5132
5137
|
"description": "Skimmable report with metrics, shipped/slipped lists, blockers, and next actions.",
|
|
5133
5138
|
"title": "Weekly canvas status",
|
|
5134
5139
|
"strictSize": true,
|
|
5135
|
-
"contentSummary": "Weekly canvas status PMX HTML primitive / status-report Weekly canvas status Skimmable report with metrics, shipped/slipped lists, blockers, and next actions.
|
|
5140
|
+
"contentSummary": "Weekly canvas status PMX HTML primitive / status-report Weekly canvas status Skimmable report with metrics, shipped/slipped lists, blockers, and next actions. Gates 4/5 green warn Unit tests 612 passing ok Coverage 71% lines ok Open bugs 7 warn Bundle +4.1% warn Shipped Delimited files render as tables instead of raw text Shared readability floor between server and client Generated demo board covering every public surface Slipped Per-band deep links Snapshot diff in the browser Risks Bundle growth is trending up three weeks running e2e suite is the slowest gate and the flakiest Next Cut the bundle back under the 0.4.5 baseline Record the 60-second tour off the new demo board",
|
|
5136
5141
|
"agentSummary": "Skimmable report with metrics, shipped/slipped lists, blockers, and next actions."
|
|
5137
5142
|
},
|
|
5138
5143
|
"createdBy": {
|
|
@@ -5170,7 +5175,7 @@
|
|
|
5170
5175
|
"collapsed": false,
|
|
5171
5176
|
"pinned": false,
|
|
5172
5177
|
"data": {
|
|
5173
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Post-0.4.7 triage</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / triage-board</div><h1>Post-0.4.7 triage</h1><p>Draggable Now/Next/Later/Cut board with copy-as-markdown export.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"columns\"><div class=\"column\" data-column=\"Now\"><h2>Now</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Bundle back under baseline</h3><p>Client bundle is +4.1% over 0.4.6.</p><p class=\"small\">Trending the wrong way three weeks running.</p></article><article class=\"card ticket\" draggable=\"true\" data-ticket=\"1\"><h3>Stabilise the e2e gate</h3><p>Two reruns per week on average.</p><p class=\"small\">Slowest and flakiest gate blocks every release.</p></article></div><div class=\"column\" data-column=\"Next\"><h2>Next</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Per-band deep links</h3><p>Open the board focused on one band.</p><p class=\"small\">Makes the demo board usable as documentation.</p></article><article class=\"card ticket\" draggable=\"true\" data-ticket=\"1\"><h3>60-second tour recording</h3><p>Screen capture driven off --demo.</p><p class=\"small\">Cheapest onboarding win available.</p></article></div><div class=\"column\" data-column=\"Later\"><h2>Later</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Snapshot diff in the browser</h3><p>Today it is MCP/HTTP only.</p><p class=\"small\">Useful, not blocking.</p></article></div><div class=\"column\" data-column=\"Cut\"><h2>Cut</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Theme editor UI</h3><p>Edit CSS variables from the toolbar.</p><p class=\"small\">Nine built-in themes already cover the need.</p></article></div></section><p class=\"small\" style=\"margin-top:12px\">Drag cards between columns, then copy JSON or markdown.</p><button type=\"button\" data-copy-markdown>Copy markdown</button>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"columns\": [\n \"Now\",\n \"Next\",\n \"Later\",\n \"Cut\"\n ],\n \"items\": [\n {\n \"title\": \"Bundle back under baseline\",\n \"detail\": \"Client bundle is +4.1% over 0.4.6.\",\n \"column\": \"Now\",\n \"rationale\": \"Trending the wrong way three weeks running.\"\n },\n {\n \"title\": \"Stabilise the e2e gate\",\n \"detail\": \"Two reruns per week on average.\",\n \"column\": \"Now\",\n \"rationale\": \"Slowest and flakiest gate blocks every release.\"\n },\n {\n \"title\": \"Per-band deep links\",\n \"detail\": \"Open the board focused on one band.\",\n \"column\": \"Next\",\n \"rationale\": \"Makes the demo board usable as documentation.\"\n },\n {\n \"title\": \"60-second tour recording\",\n \"detail\": \"Screen capture driven off --demo.\",\n \"column\": \"Next\",\n \"rationale\": \"Cheapest onboarding win available.\"\n },\n {\n \"title\": \"Snapshot diff in the browser\",\n \"detail\": \"Today it is MCP/HTTP only.\",\n \"column\": \"Later\",\n \"rationale\": \"Useful, not blocking.\"\n },\n {\n \"title\": \"Theme editor UI\",\n \"detail\": \"Edit CSS variables from the toolbar.\",\n \"column\": \"Cut\",\n \"rationale\": \"Nine built-in themes already cover the need.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nlet dragged = null;\nfunction boardState() {\n return Array.from(document.querySelectorAll('.column')).map((column) => ({\n column: column.getAttribute('data-column') || 'Column',\n items: Array.from(column.querySelectorAll('.ticket')).map((ticket) => ({\n title: ticket.querySelector('h3')?.textContent || '',\n detail: ticket.querySelector('p')?.textContent || '',\n })),\n }));\n}\ndocument.querySelectorAll('.ticket').forEach((ticket) => {\n ticket.addEventListener('dragstart', () => { dragged = ticket; });\n});\ndocument.querySelectorAll('.column').forEach((column) => {\n column.addEventListener('dragover', (event) => event.preventDefault());\n column.addEventListener('drop', () => { if (dragged) column.appendChild(dragged); });\n});\nfunction boardMarkdown() {\n return boardState().map((column) => {\n const items = column.items.map((item) => '- ' + item.title + ': ' + item.detail);\n return '## ' + column.column + '\\n' + (items.join('\\n') || '- None');\n }).join('\\n\\n');\n}\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, board: boardState() });\ndocument.querySelector('[data-copy-markdown]')?.addEventListener('click', () => copyText(boardMarkdown()));\n</script>\n</body>\n</html>",
|
|
5178
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Post-0.4.7 triage</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / triage-board</div><h1>Post-0.4.7 triage</h1><p>Draggable Now/Next/Later/Cut board with copy-as-markdown export.</p></div>\n </header>\n <section class=\"columns\"><div class=\"column\" data-column=\"Now\"><h2>Now</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Bundle back under baseline</h3><p>Client bundle is +4.1% over 0.4.6.</p><p class=\"small\">Trending the wrong way three weeks running.</p></article><article class=\"card ticket\" draggable=\"true\" data-ticket=\"1\"><h3>Stabilise the e2e gate</h3><p>Two reruns per week on average.</p><p class=\"small\">Slowest and flakiest gate blocks every release.</p></article></div><div class=\"column\" data-column=\"Next\"><h2>Next</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Per-band deep links</h3><p>Open the board focused on one band.</p><p class=\"small\">Makes the demo board usable as documentation.</p></article><article class=\"card ticket\" draggable=\"true\" data-ticket=\"1\"><h3>60-second tour recording</h3><p>Screen capture driven off --demo.</p><p class=\"small\">Cheapest onboarding win available.</p></article></div><div class=\"column\" data-column=\"Later\"><h2>Later</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Snapshot diff in the browser</h3><p>Today it is MCP/HTTP only.</p><p class=\"small\">Useful, not blocking.</p></article></div><div class=\"column\" data-column=\"Cut\"><h2>Cut</h2><article class=\"card ticket\" draggable=\"true\" data-ticket=\"0\"><h3>Theme editor UI</h3><p>Edit CSS variables from the toolbar.</p><p class=\"small\">Nine built-in themes already cover the need.</p></article></div></section><p class=\"small\" style=\"margin-top:12px\">Drag cards between columns, then copy JSON or markdown.</p><button type=\"button\" data-copy-markdown>Copy markdown</button>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"columns\": [\n \"Now\",\n \"Next\",\n \"Later\",\n \"Cut\"\n ],\n \"items\": [\n {\n \"title\": \"Bundle back under baseline\",\n \"detail\": \"Client bundle is +4.1% over 0.4.6.\",\n \"column\": \"Now\",\n \"rationale\": \"Trending the wrong way three weeks running.\"\n },\n {\n \"title\": \"Stabilise the e2e gate\",\n \"detail\": \"Two reruns per week on average.\",\n \"column\": \"Now\",\n \"rationale\": \"Slowest and flakiest gate blocks every release.\"\n },\n {\n \"title\": \"Per-band deep links\",\n \"detail\": \"Open the board focused on one band.\",\n \"column\": \"Next\",\n \"rationale\": \"Makes the demo board usable as documentation.\"\n },\n {\n \"title\": \"60-second tour recording\",\n \"detail\": \"Screen capture driven off --demo.\",\n \"column\": \"Next\",\n \"rationale\": \"Cheapest onboarding win available.\"\n },\n {\n \"title\": \"Snapshot diff in the browser\",\n \"detail\": \"Today it is MCP/HTTP only.\",\n \"column\": \"Later\",\n \"rationale\": \"Useful, not blocking.\"\n },\n {\n \"title\": \"Theme editor UI\",\n \"detail\": \"Edit CSS variables from the toolbar.\",\n \"column\": \"Cut\",\n \"rationale\": \"Nine built-in themes already cover the need.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nlet dragged = null;\nfunction boardState() {\n return Array.from(document.querySelectorAll('.column')).map((column) => ({\n column: column.getAttribute('data-column') || 'Column',\n items: Array.from(column.querySelectorAll('.ticket')).map((ticket) => ({\n title: ticket.querySelector('h3')?.textContent || '',\n detail: ticket.querySelector('p')?.textContent || '',\n })),\n }));\n}\ndocument.querySelectorAll('.ticket').forEach((ticket) => {\n ticket.addEventListener('dragstart', () => { dragged = ticket; });\n});\ndocument.querySelectorAll('.column').forEach((column) => {\n column.addEventListener('dragover', (event) => event.preventDefault());\n column.addEventListener('drop', () => { if (dragged) column.appendChild(dragged); });\n});\nfunction boardMarkdown() {\n return boardState().map((column) => {\n const items = column.items.map((item) => '- ' + item.title + ': ' + item.detail);\n return '## ' + column.column + '\\n' + (items.join('\\n') || '- None');\n }).join('\\n\\n');\n}\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, board: boardState() });\ndocument.querySelector('[data-copy-markdown]')?.addEventListener('click', () => copyText(boardMarkdown()));\n</script>\n</body>\n</html>",
|
|
5174
5179
|
"htmlPrimitive": "triage-board",
|
|
5175
5180
|
"primitiveData": {
|
|
5176
5181
|
"columns": [
|
|
@@ -5221,7 +5226,7 @@
|
|
|
5221
5226
|
"description": "Draggable Now/Next/Later/Cut board with copy-as-markdown export.",
|
|
5222
5227
|
"title": "Post-0.4.7 triage",
|
|
5223
5228
|
"strictSize": true,
|
|
5224
|
-
"contentSummary": "Post-0.4.7 triage PMX HTML primitive / triage-board Post-0.4.7 triage Draggable Now/Next/Later/Cut board with copy-as-markdown export.
|
|
5229
|
+
"contentSummary": "Post-0.4.7 triage PMX HTML primitive / triage-board Post-0.4.7 triage Draggable Now/Next/Later/Cut board with copy-as-markdown export. Now Bundle back under baseline Client bundle is +4.1% over 0.4.6. Trending the wrong way three weeks running. Stabilise the e2e gate Two reruns per week on average. Slowest and flakiest gate blocks every release. Next Per-band deep links Open the board focused on one band. Makes the demo board usable as documentation. 60-second tour recording Screen capture driven off --demo. Cheapest onboarding win available. Later Snapshot diff in the browser Today it is MCP/HTTP only. Useful, not blocking. Cut Theme editor UI Edit CSS variables from the toolbar. Nine built-in themes already cover the need. Drag cards between columns, then copy JSON or markdown. Copy markdown",
|
|
5225
5230
|
"agentSummary": "Draggable Now/Next/Later/Cut board with copy-as-markdown export."
|
|
5226
5231
|
},
|
|
5227
5232
|
"createdBy": {
|
|
@@ -5259,7 +5264,7 @@
|
|
|
5259
5264
|
"collapsed": false,
|
|
5260
5265
|
"pinned": false,
|
|
5261
5266
|
"data": {
|
|
5262
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Canvas feature flags</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / config-editor</div><h1>Canvas feature flags</h1><p>Form-like editor for flags or structured config with dependency warnings and copy-diff export.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"grid\"><label class=\"card\"><div class=\"small\">Demo</div><h3><input type=\"checkbox\" data-flag=\"0\" checked> strictSize on demo nodes</h3><p>Keeps the generated band layout exactly as authored.</p><p class=\"small\">Key: strictSizeDemo</p><p class=\"small\" data-warning=\"0\"></p></label><label class=\"card\"><div class=\"small\">Layout</div><h3><input type=\"checkbox\" data-flag=\"1\" checked> Iframe content-fit</h3><p>Iframe surfaces grow to their reported content height.</p><p class=\"small\">Key: contentFit</p><p class=\"small\" data-warning=\"1\"></p></label><label class=\"card\"><div class=\"small\">Graph</div><h3><input type=\"checkbox\" data-flag=\"2\" checked> Auto dependency edges</h3><p>Derives depends-on edges between file nodes.</p><p class=\"small\">Key: codeGraph</p><p class=\"small\" data-warning=\"2\"></p></label><label class=\"card\"><div class=\"small\">AX</div><h3><input type=\"checkbox\" data-flag=\"3\" > Node AX interactions</h3><p>Lets eligible node surfaces emit AX operations.</p><p class=\"small\">Key: axInteractions / requires: axCapabilities</p><p class=\"small\" data-warning=\"3\"></p></label><label class=\"card\"><div class=\"small\">AX</div><h3><input type=\"checkbox\" data-flag=\"4\" > AX capability ceiling</h3><p>Per-node-type ceiling a node can narrow but never escalate.</p><p class=\"small\">Key: axCapabilities</p><p class=\"small\" data-warning=\"4\"></p></label><label class=\"card\"><div class=\"small\">Tooling</div><h3><input type=\"checkbox\" data-flag=\"5\" > Bun.WebView automation</h3><p>Headless webview driving for CI screenshots.</p><p class=\"small\">Key: webviewAutomation</p><p class=\"small\" data-warning=\"5\"></p></label></section><button type=\"button\" data-copy-diff style=\"margin-top:12px\">Copy diff</button>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"flags\": [\n {\n \"key\": \"strictSizeDemo\",\n \"label\": \"strictSize on demo nodes\",\n \"area\": \"Demo\",\n \"enabled\": true,\n \"description\": \"Keeps the generated band layout exactly as authored.\"\n },\n {\n \"key\": \"contentFit\",\n \"label\": \"Iframe content-fit\",\n \"area\": \"Layout\",\n \"enabled\": true,\n \"description\": \"Iframe surfaces grow to their reported content height.\"\n },\n {\n \"key\": \"codeGraph\",\n \"label\": \"Auto dependency edges\",\n \"area\": \"Graph\",\n \"enabled\": true,\n \"description\": \"Derives depends-on edges between file nodes.\"\n },\n {\n \"key\": \"axInteractions\",\n \"label\": \"Node AX interactions\",\n \"area\": \"AX\",\n \"enabled\": false,\n \"requires\": [\n \"axCapabilities\"\n ],\n \"description\": \"Lets eligible node surfaces emit AX operations.\"\n },\n {\n \"key\": \"axCapabilities\",\n \"label\": \"AX capability ceiling\",\n \"area\": \"AX\",\n \"enabled\": false,\n \"description\": \"Per-node-type ceiling a node can narrow but never escalate.\"\n },\n {\n \"key\": \"webviewAutomation\",\n \"label\": \"Bun.WebView automation\",\n \"area\": \"Tooling\",\n \"enabled\": false,\n \"description\": \"Headless webview driving for CI screenshots.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst originalFlags = PMX_DATA.flags || [];\nfunction flagState() {\n return originalFlags.map((flag, index) => ({ ...flag, enabled: document.querySelector('[data-flag=\"' + index + '\"]').checked }));\n}\nfunction refreshWarnings() {\n const state = flagState();\n const byKey = new Map(state.map((flag) => [flag.key, flag]));\n state.forEach((flag, index) => {\n const missing = (flag.requires || []).filter((key) => flag.enabled && !byKey.get(key)?.enabled);\n document.querySelector('[data-warning=\"' + index + '\"]').textContent = missing.length ? 'Warning: requires ' + missing.join(', ') : '';\n });\n}\ndocument.querySelectorAll('[data-flag]').forEach((input) => input.addEventListener('change', refreshWarnings));\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, flags: flagState() });\ndocument.querySelector('[data-copy-diff]')?.addEventListener('click', () => {\n const diff = flagState().filter((flag, index) => flag.enabled !== originalFlags[index]?.enabled).map((flag) => ({ key: flag.key, enabled: flag.enabled }));\n copyText(JSON.stringify(diff, null, 2));\n});\nrefreshWarnings();\n</script>\n</body>\n</html>",
|
|
5267
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Canvas feature flags</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / config-editor</div><h1>Canvas feature flags</h1><p>Form-like editor for flags or structured config with dependency warnings and copy-diff export.</p></div>\n </header>\n <section class=\"grid\"><label class=\"card\"><div class=\"small\">Demo</div><h3><input type=\"checkbox\" data-flag=\"0\" checked> strictSize on demo nodes</h3><p>Keeps the generated band layout exactly as authored.</p><p class=\"small\">Key: strictSizeDemo</p><p class=\"small\" data-warning=\"0\"></p></label><label class=\"card\"><div class=\"small\">Layout</div><h3><input type=\"checkbox\" data-flag=\"1\" checked> Iframe content-fit</h3><p>Iframe surfaces grow to their reported content height.</p><p class=\"small\">Key: contentFit</p><p class=\"small\" data-warning=\"1\"></p></label><label class=\"card\"><div class=\"small\">Graph</div><h3><input type=\"checkbox\" data-flag=\"2\" checked> Auto dependency edges</h3><p>Derives depends-on edges between file nodes.</p><p class=\"small\">Key: codeGraph</p><p class=\"small\" data-warning=\"2\"></p></label><label class=\"card\"><div class=\"small\">AX</div><h3><input type=\"checkbox\" data-flag=\"3\" > Node AX interactions</h3><p>Lets eligible node surfaces emit AX operations.</p><p class=\"small\">Key: axInteractions / requires: axCapabilities</p><p class=\"small\" data-warning=\"3\"></p></label><label class=\"card\"><div class=\"small\">AX</div><h3><input type=\"checkbox\" data-flag=\"4\" > AX capability ceiling</h3><p>Per-node-type ceiling a node can narrow but never escalate.</p><p class=\"small\">Key: axCapabilities</p><p class=\"small\" data-warning=\"4\"></p></label><label class=\"card\"><div class=\"small\">Tooling</div><h3><input type=\"checkbox\" data-flag=\"5\" > Bun.WebView automation</h3><p>Headless webview driving for CI screenshots.</p><p class=\"small\">Key: webviewAutomation</p><p class=\"small\" data-warning=\"5\"></p></label></section><button type=\"button\" data-copy-diff style=\"margin-top:12px\">Copy diff</button>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"flags\": [\n {\n \"key\": \"strictSizeDemo\",\n \"label\": \"strictSize on demo nodes\",\n \"area\": \"Demo\",\n \"enabled\": true,\n \"description\": \"Keeps the generated band layout exactly as authored.\"\n },\n {\n \"key\": \"contentFit\",\n \"label\": \"Iframe content-fit\",\n \"area\": \"Layout\",\n \"enabled\": true,\n \"description\": \"Iframe surfaces grow to their reported content height.\"\n },\n {\n \"key\": \"codeGraph\",\n \"label\": \"Auto dependency edges\",\n \"area\": \"Graph\",\n \"enabled\": true,\n \"description\": \"Derives depends-on edges between file nodes.\"\n },\n {\n \"key\": \"axInteractions\",\n \"label\": \"Node AX interactions\",\n \"area\": \"AX\",\n \"enabled\": false,\n \"requires\": [\n \"axCapabilities\"\n ],\n \"description\": \"Lets eligible node surfaces emit AX operations.\"\n },\n {\n \"key\": \"axCapabilities\",\n \"label\": \"AX capability ceiling\",\n \"area\": \"AX\",\n \"enabled\": false,\n \"description\": \"Per-node-type ceiling a node can narrow but never escalate.\"\n },\n {\n \"key\": \"webviewAutomation\",\n \"label\": \"Bun.WebView automation\",\n \"area\": \"Tooling\",\n \"enabled\": false,\n \"description\": \"Headless webview driving for CI screenshots.\"\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst originalFlags = PMX_DATA.flags || [];\nfunction flagState() {\n return originalFlags.map((flag, index) => ({ ...flag, enabled: document.querySelector('[data-flag=\"' + index + '\"]').checked }));\n}\nfunction refreshWarnings() {\n const state = flagState();\n const byKey = new Map(state.map((flag) => [flag.key, flag]));\n state.forEach((flag, index) => {\n const missing = (flag.requires || []).filter((key) => flag.enabled && !byKey.get(key)?.enabled);\n document.querySelector('[data-warning=\"' + index + '\"]').textContent = missing.length ? 'Warning: requires ' + missing.join(', ') : '';\n });\n}\ndocument.querySelectorAll('[data-flag]').forEach((input) => input.addEventListener('change', refreshWarnings));\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, flags: flagState() });\ndocument.querySelector('[data-copy-diff]')?.addEventListener('click', () => {\n const diff = flagState().filter((flag, index) => flag.enabled !== originalFlags[index]?.enabled).map((flag) => ({ key: flag.key, enabled: flag.enabled }));\n copyText(JSON.stringify(diff, null, 2));\n});\nrefreshWarnings();\n</script>\n</body>\n</html>",
|
|
5263
5268
|
"htmlPrimitive": "config-editor",
|
|
5264
5269
|
"primitiveData": {
|
|
5265
5270
|
"flags": [
|
|
@@ -5313,7 +5318,7 @@
|
|
|
5313
5318
|
"description": "Form-like editor for flags or structured config with dependency warnings and copy-diff export.",
|
|
5314
5319
|
"title": "Canvas feature flags",
|
|
5315
5320
|
"strictSize": true,
|
|
5316
|
-
"contentSummary": "Canvas feature flags PMX HTML primitive / config-editor Canvas feature flags Form-like editor for flags or structured config with dependency warnings and copy-diff export.
|
|
5321
|
+
"contentSummary": "Canvas feature flags PMX HTML primitive / config-editor Canvas feature flags Form-like editor for flags or structured config with dependency warnings and copy-diff export. Demo strictSize on demo nodes Keeps the generated band layout exactly as authored. Key: strictSizeDemo Layout Iframe content-fit Iframe surfaces grow to their reported content height. Key: contentFit Graph Auto dependency edges Derives depends-on edges between file nodes. Key: codeGraph AX Node AX interactions Lets eligible node surfaces emit AX operations. Key: axInteractions / requires: axCapabilities AX AX capability ceiling Per-node-type ceiling a node can narrow but never escalate. Key: axCapabilities Tooling Bun.WebView automation Headless webview driving for CI screenshots. Key: webviewAutomation Copy diff",
|
|
5317
5322
|
"agentSummary": "Form-like editor for flags or structured config with dependency warnings and copy-diff export.",
|
|
5318
5323
|
"embeddedNodeIds": [
|
|
5319
5324
|
"node-type"
|
|
@@ -5354,7 +5359,7 @@
|
|
|
5354
5359
|
"collapsed": false,
|
|
5355
5360
|
"pinned": false,
|
|
5356
5361
|
"data": {
|
|
5357
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Board summary prompt</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / prompt-tuner</div><h1>Board summary prompt</h1><p>Side-by-side prompt/template editor with live variable previews and copy export.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Template</h2><textarea id=\"template\">You are looking at a PMX Canvas board for {{project}}.\n\nSummarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids.\n\nPinned nodes are the human’s curation — weight them above everything else.</textarea><p class=\"small\"><span id=\"char-count\">0</span> characters</p></div><div class=\"panel\"><h2>Live Samples</h2><div id=\"previews\"></div><button type=\"button\" data-copy-template>Copy template</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"template\": \"You are looking at a PMX Canvas board for {{project}}.\\n\\nSummarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids.\\n\\nPinned nodes are the human’s curation — weight them above everything else.\",\n \"samples\": [\n {\n \"name\": \"Standup\",\n \"variables\": {\n \"project\": \"pmx-canvas\",\n \"audience\": \"teammate joining the standup\",\n \"sentences\": \"two\"\n }\n },\n {\n \"name\": \"Exec review\",\n \"variables\": {\n \"project\": \"the 0.4.7 release\",\n \"audience\": \"director who has not seen the tool\",\n \"sentences\": \"three\"\n }\n },\n {\n \"name\": \"Handoff\",\n \"variables\": {\n \"project\": \"the demo board rebuild\",\n \"audience\": \"agent picking the work up cold\",\n \"sentences\": \"four\"\n }\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst templateEl = document.getElementById('template');\nconst previewsEl = document.getElementById('previews');\nconst samples = PMX_DATA.samples || [];\nfunction fill(template, vars) {\n return template.replace(/{{\\s*([\\w.-]+)\\s*}}/g, (_, key) => vars?.[key] ?? '{{' + key + '}}');\n}\nfunction renderPreviews() {\n if (!templateEl) return;\n const value = templateEl.value;\n document.getElementById('char-count').textContent = String(value.length);\n if (!previewsEl) return;\n previewsEl.innerHTML = samples.map((sample) => '<div class=\"card\"><h3>' + (sample.name || 'Sample') + '</h3><div class=\"preview\">' + fill(value, sample.variables || {}).replace(/[&<>]/g, (c) => ({'&':'&','<':'<','>':'>'}[c])) + '</div></div>').join('');\n}\ntemplateEl?.addEventListener('input', renderPreviews);\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, template: templateEl?.value ?? '' });\ndocument.querySelector('[data-copy-template]')?.addEventListener('click', () => copyText(templateEl.value));\nrenderPreviews();\n</script>\n</body>\n</html>",
|
|
5362
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Board summary prompt</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / prompt-tuner</div><h1>Board summary prompt</h1><p>Side-by-side prompt/template editor with live variable previews and copy export.</p></div>\n </header>\n <section class=\"two\"><div class=\"panel\"><h2>Template</h2><textarea id=\"template\">You are looking at a PMX Canvas board for {{project}}.\n\nSummarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids.\n\nPinned nodes are the human’s curation — weight them above everything else.</textarea><p class=\"small\"><span id=\"char-count\">0</span> characters</p></div><div class=\"panel\"><h2>Live Samples</h2><div id=\"previews\"></div><button type=\"button\" data-copy-template>Copy template</button></div></section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"template\": \"You are looking at a PMX Canvas board for {{project}}.\\n\\nSummarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids.\\n\\nPinned nodes are the human’s curation — weight them above everything else.\",\n \"samples\": [\n {\n \"name\": \"Standup\",\n \"variables\": {\n \"project\": \"pmx-canvas\",\n \"audience\": \"teammate joining the standup\",\n \"sentences\": \"two\"\n }\n },\n {\n \"name\": \"Exec review\",\n \"variables\": {\n \"project\": \"the 0.4.7 release\",\n \"audience\": \"director who has not seen the tool\",\n \"sentences\": \"three\"\n }\n },\n {\n \"name\": \"Handoff\",\n \"variables\": {\n \"project\": \"the demo board rebuild\",\n \"audience\": \"agent picking the work up cold\",\n \"sentences\": \"four\"\n }\n }\n ]\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst templateEl = document.getElementById('template');\nconst previewsEl = document.getElementById('previews');\nconst samples = PMX_DATA.samples || [];\nfunction fill(template, vars) {\n return template.replace(/{{\\s*([\\w.-]+)\\s*}}/g, (_, key) => vars?.[key] ?? '{{' + key + '}}');\n}\nfunction renderPreviews() {\n if (!templateEl) return;\n const value = templateEl.value;\n document.getElementById('char-count').textContent = String(value.length);\n if (!previewsEl) return;\n previewsEl.innerHTML = samples.map((sample) => '<div class=\"card\"><h3>' + (sample.name || 'Sample') + '</h3><div class=\"preview\">' + fill(value, sample.variables || {}).replace(/[&<>]/g, (c) => ({'&':'&','<':'<','>':'>'}[c])) + '</div></div>').join('');\n}\ntemplateEl?.addEventListener('input', renderPreviews);\nwindow.__pmxGetCopyJson = () => ({ ...PMX_DATA, template: templateEl?.value ?? '' });\ndocument.querySelector('[data-copy-template]')?.addEventListener('click', () => copyText(templateEl.value));\nrenderPreviews();\n</script>\n</body>\n</html>",
|
|
5358
5363
|
"htmlPrimitive": "prompt-tuner",
|
|
5359
5364
|
"primitiveData": {
|
|
5360
5365
|
"template": "You are looking at a PMX Canvas board for {{project}}.\n\nSummarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids.\n\nPinned nodes are the human’s curation — weight them above everything else.",
|
|
@@ -5388,7 +5393,7 @@
|
|
|
5388
5393
|
"description": "Side-by-side prompt/template editor with live variable previews and copy export.",
|
|
5389
5394
|
"title": "Board summary prompt",
|
|
5390
5395
|
"strictSize": true,
|
|
5391
|
-
"contentSummary": "Board summary prompt PMX HTML primitive / prompt-tuner Board summary prompt Side-by-side prompt/template editor with live variable previews and copy export.
|
|
5396
|
+
"contentSummary": "Board summary prompt PMX HTML primitive / prompt-tuner Board summary prompt Side-by-side prompt/template editor with live variable previews and copy export. Template You are looking at a PMX Canvas board for {{project}}. Summarise it for a {{audience}} in {{sentences}} sentences. Lead with what the board is FOR, then what changed most recently. Ignore layout and node ids. Pinned nodes are the human’s curation — weight them above everything else. 0 characters Live Samples Copy template",
|
|
5392
5397
|
"agentSummary": "Side-by-side prompt/template editor with live variable previews and copy export."
|
|
5393
5398
|
},
|
|
5394
5399
|
"createdBy": {
|
|
@@ -5426,7 +5431,7 @@
|
|
|
5426
5431
|
"collapsed": false,
|
|
5427
5432
|
"pinned": false,
|
|
5428
5433
|
"data": {
|
|
5429
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Agent board</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / ax-board</div><h1>Agent board</h1><p>Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <style>\n .ax-field { display: grid; gap: 6px; margin-bottom: 10px; }\n .ax-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-work-row { margin-bottom: 10px; padding: 12px 14px; }\n .ax-work-head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }\n .ax-work-head h3 { margin: 0; }\n .ax-work-foot { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 8px; }\n select { border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n input[type=\"number\"] { width: 72px; border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n #ax-loop-panel[hidden] { display: none; }\n #ax-offline { border-color: var(--color-danger, #ef4444); margin-bottom: 14px; }\n</style>\n<section class=\"panel\" id=\"ax-offline\" hidden><h2>AX bridge unavailable</h2><p class=\"small\">This board is not running as an AX-enabled canvas node, so its controls are inert.</p></section>\n<section class=\"two\">\n <div class=\"panel\">\n <h2>Give the agent a task</h2>\n <p class=\"small\">Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt.</p>\n <div class=\"ax-field\"><input type=\"text\" id=\"ax-task-title\" placeholder=\"What should the agent do?\"></div>\n <div class=\"ax-field\"><textarea id=\"ax-task-detail\" placeholder=\"Optional detail\" style=\"min-height:64px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-add-task\">Add task</button><span class=\"small\" id=\"ax-task-status\"></span></div>\n <div class=\"ax-row\" style=\"margin-top:10px\">\n <span class=\"small\">or run it as a bounded loop:</span>\n <label class=\"small\">max runs <input type=\"number\" id=\"ax-loop-runs\" min=\"1\" max=\"20\" step=\"1\" value=\"3\"></label>\n <button type=\"button\" id=\"ax-start-loop\">Start loop</button>\n </div>\n </div>\n <div class=\"panel\">\n <h2>Steer</h2>\n <div class=\"ax-field\"><textarea id=\"ax-steer-message\" placeholder=\"Prioritize the auth refactor\" style=\"min-height:92px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-send-steer\">Queue steering message</button></div>\n <p class=\"small\">Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent.</p>\n <p class=\"small\" id=\"ax-steer-status\"></p>\n </div>\n</section>\n<section class=\"panel\" id=\"ax-loop-panel\" hidden style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2 id=\"ax-loop-title\">Loop</h2><span class=\"badge info\" id=\"ax-loop-run\">run 0 of 0</span></div>\n <p class=\"small\">Each run is its own work item. When you mark the current run <strong>done</strong>, the surface opens the next run and steers again — until the cap or Stop.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-stop-loop\">Stop loop</button><span class=\"small\" id=\"ax-loop-status\"></span></div>\n <p class=\"small\">Loop control lives in this panel, so reloading it stops the loop — the work items stay as the durable record.</p>\n</section>\n<section class=\"panel\" style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2>Work</h2><span class=\"small\" id=\"ax-work-counts\"></span></div>\n <div id=\"ax-work-list\"></div>\n <p class=\"small\" id=\"ax-work-status\"></p>\n</section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"note\": \"Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt.\",\n \"defaultRuns\": 3\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst AX_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'];\nconst AX_MAX_RUNS = 20;\n\nconst el = (id) => document.getElementById(id);\nconst axFlashTimers = {};\nfunction flash(id, message) {\n const target = el(id);\n if (!target) return;\n target.textContent = message;\n if (axFlashTimers[id]) clearTimeout(axFlashTimers[id]);\n axFlashTimers[id] = setTimeout(() => { target.textContent = ''; }, 4000);\n}\nfunction axWorkItems() {\n const state = window.PMX_AX && window.PMX_AX.state;\n return state && Array.isArray(state.workItems) ? state.workItems : [];\n}\nasync function axEmit(type, payload) {\n if (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') {\n return { ok: false, code: 'ax-bridge-missing', error: 'AX bridge is not available on this surface.' };\n }\n return await window.PMX_AX.emit(type, payload);\n}\nfunction axFailure(result) {\n return (result && (result.error || result.code)) || 'unknown error';\n}\nfunction statusTone(status) {\n if (status === 'done') return 'ok';\n if (status === 'blocked' || status === 'cancelled') return 'danger';\n if (status === 'in-progress') return 'warn';\n return 'info';\n}\nfunction elem(tag, className, textContent) {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (textContent !== undefined) node.textContent = textContent;\n return node;\n}\nasync function sendSteer() {\n const message = el('ax-steer-message').value.trim();\n if (!message) { flash('ax-steer-status', 'Write a steering message first.'); return; }\n const result = await axEmit('ax.steer', { message });\n if (result && result.ok) {\n el('ax-steer-message').value = '';\n flash('ax-steer-status', 'Queued for the agent\\'s next turn ✓');\n } else {\n flash('ax-steer-status', 'Failed: ' + axFailure(result));\n }\n}\n\n// Loop control is in-memory only (the surface is sandboxed: storage APIs throw).\nlet axLoop = null;\nlet axAdvancing = false;\n\nfunction renderLoop() {\n const panel = el('ax-loop-panel');\n panel.hidden = !axLoop;\n if (!axLoop) return;\n el('ax-loop-title').textContent = axLoop.baseTitle;\n el('ax-loop-run').textContent = 'run ' + axLoop.run + ' of ' + axLoop.maxRuns;\n}\nasync function openRun(session, run) {\n const result = await axEmit('ax.work.create', {\n title: session.baseTitle + ' — run ' + run + '/' + session.maxRuns,\n ...(session.detail ? { detail: session.detail } : {}),\n });\n // The human may have stopped the loop while this was in flight.\n if (axLoop !== session) return false;\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-loop-status', 'Could not open run ' + run + ': ' + axFailure(result));\n return false;\n }\n session.run = run;\n session.workItemId = result.primitive.id;\n renderLoop();\n axRenderSurface();\n await axEmit('ax.steer', {\n message:\n 'Loop run ' + run + ' of ' + session.maxRuns + ' for \"' + session.baseTitle +\n '\". Mark work item ' + session.workItemId + ' done when this run is complete.',\n });\n return true;\n}\n/**\n * Start a bounded loop. NEVER auto-started — a human control only. Returns the\n * clamped run count on success (0 on failure) so the surface can sync its inputs;\n * `onStarted` fires the moment run 1 is open, before the loop's own bookkeeping.\n */\nasync function axStartLoop(baseTitle, detail, requested, onStarted) {\n if (axLoop) return 0;\n const maxRuns = Math.min(AX_MAX_RUNS, Math.max(1, Math.round(Number.isFinite(requested) ? requested : 3)));\n const session = { baseTitle: baseTitle, detail: detail, maxRuns: maxRuns, run: 0, workItemId: null };\n axLoop = session;\n renderLoop();\n if (!(await openRun(session, 1))) {\n if (axLoop === session) { axLoop = null; renderLoop(); }\n return 0;\n }\n if (onStarted) onStarted(maxRuns);\n flash('ax-loop-status', requested > AX_MAX_RUNS ? 'Capped at ' + AX_MAX_RUNS + ' runs.' : 'Loop started.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop started: ' + baseTitle + ' (max ' + maxRuns + ' runs)',\n });\n return maxRuns;\n}\nasync function stopLoop() {\n const session = axLoop;\n if (!session) return;\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop stopped at run ' + session.run + ' of ' + session.maxRuns + '.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop stopped by human: ' + session.baseTitle + ' at run ' + session.run + ' of ' + session.maxRuns,\n });\n}\n/**\n * Advance when the CURRENT run's work item reaches done, observed from live AX\n * state. Double-advance guards: an in-flight flag, and clearing workItemId before\n * awaiting so a repeated state push cannot match the same finished run twice.\n */\nasync function maybeAdvanceLoop() {\n const session = axLoop;\n if (!session || axAdvancing || !session.workItemId) return;\n const current = axWorkItems().find((item) => item.id === session.workItemId);\n if (!current || current.status !== 'done') return;\n if (session.run >= session.maxRuns) {\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop finished — ' + session.maxRuns + ' of ' + session.maxRuns + ' runs done.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop complete: ' + session.baseTitle + ' (' + session.maxRuns + ' runs)',\n });\n return;\n }\n axAdvancing = true;\n session.workItemId = null;\n try {\n if (!(await openRun(session, session.run + 1)) && axLoop === session) { axLoop = null; renderLoop(); }\n } finally {\n axAdvancing = false;\n }\n}\n\nfunction applyAxState() {\n axRenderSurface();\n renderLoop();\n void maybeAdvanceLoop();\n}\n\n// ── render (live AX state only — no local mirror of the work list) ──\nfunction renderWork() {\n const items = axWorkItems();\n const list = el('ax-work-list');\n list.textContent = '';\n el('ax-work-counts').textContent = items.length\n ? AX_STATUSES.map((s) => s + ' ' + items.filter((item) => item.status === s).length).join(' · ')\n : '';\n if (items.length === 0) {\n list.appendChild(elem('p', 'muted', 'No work items yet — add a task above and it shows up here.'));\n return;\n }\n for (const item of items) {\n const row = elem('article', 'card ax-work-row');\n row.setAttribute('data-work-id', item.id);\n const head = elem('div', 'ax-work-head');\n head.appendChild(elem('h3', '', item.title));\n head.appendChild(elem('span', 'badge ' + statusTone(item.status), item.status));\n row.appendChild(head);\n if (item.detail) row.appendChild(elem('p', 'small', item.detail));\n const foot = elem('div', 'ax-work-foot');\n const label = elem('label', 'small', 'Status ');\n const select = document.createElement('select');\n select.setAttribute('data-status-for', item.id);\n for (const status of AX_STATUSES) {\n const option = document.createElement('option');\n option.value = status;\n option.textContent = status;\n if (status === item.status) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => setWorkStatus(item.id, select.value));\n label.appendChild(select);\n foot.appendChild(label);\n if (item.agentId) foot.appendChild(elem('span', 'small', 'agent: ' + item.agentId));\n if (axLoop && axLoop.workItemId === item.id) foot.appendChild(elem('span', 'badge info', 'current run'));\n row.appendChild(foot);\n list.appendChild(row);\n }\n}\nfunction axRenderSurface() { renderWork(); }\n\n// ── actions ──\nasync function addTask() {\n const title = el('ax-task-title').value.trim();\n if (!title) { flash('ax-task-status', 'Give the task a title first.'); return; }\n const detail = el('ax-task-detail').value.trim();\n const result = await axEmit('ax.work.create', detail ? { title, detail } : { title });\n if (result && result.ok) {\n el('ax-task-title').value = '';\n el('ax-task-detail').value = '';\n flash('ax-task-status', 'Task queued ✓');\n } else {\n flash('ax-task-status', 'Failed: ' + axFailure(result));\n }\n}\nasync function setWorkStatus(id, status) {\n const result = await axEmit('ax.work.update', { id, status });\n flash('ax-work-status', result && result.ok ? 'Status updated ✓' : 'Update failed: ' + axFailure(result));\n}\nasync function startLoop() {\n if (axLoop) { flash('ax-task-status', 'A loop is already running — stop it first.'); return; }\n const title = el('ax-task-title').value.trim();\n if (!title) { flash('ax-task-status', 'Give the task a title first.'); return; }\n await axStartLoop(title, el('ax-task-detail').value.trim(), Number(el('ax-loop-runs').value), (maxRuns) => {\n el('ax-task-title').value = '';\n el('ax-task-detail').value = '';\n el('ax-loop-runs').value = String(maxRuns);\n });\n}\n\nel('ax-add-task').addEventListener('click', addTask);\nel('ax-start-loop').addEventListener('click', startLoop);\nel('ax-stop-loop').addEventListener('click', stopLoop);\nel('ax-send-steer').addEventListener('click', sendSteer);\nwindow.__pmxGetCopyJson = () => ({\n ...PMX_DATA,\n workItems: axWorkItems(),\n loop: axLoop ? { title: axLoop.baseTitle, run: axLoop.run, maxRuns: axLoop.maxRuns } : null,\n});\nif (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') el('ax-offline').hidden = false;\n// Seeded snapshot first, then every live push. A loop NEVER auto-starts on load.\nwindow.addEventListener('pmx-ax-update', applyAxState);\napplyAxState();\n</script>\n</body>\n</html>",
|
|
5434
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Agent board</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / ax-board</div><h1>Agent board</h1><p>Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops.</p></div>\n </header>\n <style>\n .ax-field { display: grid; gap: 6px; margin-bottom: 10px; }\n .ax-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-work-row { margin-bottom: 10px; padding: 12px 14px; }\n .ax-work-head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }\n .ax-work-head h3 { margin: 0; }\n .ax-work-foot { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 8px; }\n select { border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n input[type=\"number\"] { width: 72px; border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n #ax-loop-panel[hidden] { display: none; }\n #ax-offline { border-color: var(--color-danger, #ef4444); margin-bottom: 14px; }\n</style>\n<section class=\"panel\" id=\"ax-offline\" hidden><h2>AX bridge unavailable</h2><p class=\"small\">This board is not running as an AX-enabled canvas node, so its controls are inert.</p></section>\n<section class=\"two\">\n <div class=\"panel\">\n <h2>Give the agent a task</h2>\n <p class=\"small\">Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt.</p>\n <div class=\"ax-field\"><input type=\"text\" id=\"ax-task-title\" placeholder=\"What should the agent do?\"></div>\n <div class=\"ax-field\"><textarea id=\"ax-task-detail\" placeholder=\"Optional detail\" style=\"min-height:64px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-add-task\">Add task</button><span class=\"small\" id=\"ax-task-status\"></span></div>\n <div class=\"ax-row\" style=\"margin-top:10px\">\n <span class=\"small\">or run it as a bounded loop:</span>\n <label class=\"small\">max runs <input type=\"number\" id=\"ax-loop-runs\" min=\"1\" max=\"20\" step=\"1\" value=\"3\"></label>\n <button type=\"button\" id=\"ax-start-loop\">Start loop</button>\n </div>\n </div>\n <div class=\"panel\">\n <h2>Steer</h2>\n <div class=\"ax-field\"><textarea id=\"ax-steer-message\" placeholder=\"Prioritize the auth refactor\" style=\"min-height:92px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-send-steer\">Queue steering message</button></div>\n <p class=\"small\">Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent.</p>\n <p class=\"small\" id=\"ax-steer-status\"></p>\n </div>\n</section>\n<section class=\"panel\" id=\"ax-loop-panel\" hidden style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2 id=\"ax-loop-title\">Loop</h2><span class=\"badge info\" id=\"ax-loop-run\">run 0 of 0</span></div>\n <p class=\"small\">Each run is its own work item. When you mark the current run <strong>done</strong>, the surface opens the next run and steers again — until the cap or Stop.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-stop-loop\">Stop loop</button><span class=\"small\" id=\"ax-loop-status\"></span></div>\n <p class=\"small\">Loop control lives in this panel, so reloading it stops the loop — the work items stay as the durable record.</p>\n</section>\n<section class=\"panel\" style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2>Work</h2><span class=\"small\" id=\"ax-work-counts\"></span></div>\n <div id=\"ax-work-list\"></div>\n <p class=\"small\" id=\"ax-work-status\"></p>\n</section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"note\": \"Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt.\",\n \"defaultRuns\": 3\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst AX_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'];\nconst AX_MAX_RUNS = 20;\n\nconst el = (id) => document.getElementById(id);\nconst axFlashTimers = {};\nfunction flash(id, message) {\n const target = el(id);\n if (!target) return;\n target.textContent = message;\n if (axFlashTimers[id]) clearTimeout(axFlashTimers[id]);\n axFlashTimers[id] = setTimeout(() => { target.textContent = ''; }, 4000);\n}\nfunction axWorkItems() {\n const state = window.PMX_AX && window.PMX_AX.state;\n return state && Array.isArray(state.workItems) ? state.workItems : [];\n}\nasync function axEmit(type, payload) {\n if (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') {\n return { ok: false, code: 'ax-bridge-missing', error: 'AX bridge is not available on this surface.' };\n }\n return await window.PMX_AX.emit(type, payload);\n}\nfunction axFailure(result) {\n return (result && (result.error || result.code)) || 'unknown error';\n}\nfunction statusTone(status) {\n if (status === 'done') return 'ok';\n if (status === 'blocked' || status === 'cancelled') return 'danger';\n if (status === 'in-progress') return 'warn';\n return 'info';\n}\nfunction elem(tag, className, textContent) {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (textContent !== undefined) node.textContent = textContent;\n return node;\n}\nasync function sendSteer() {\n const message = el('ax-steer-message').value.trim();\n if (!message) { flash('ax-steer-status', 'Write a steering message first.'); return; }\n const result = await axEmit('ax.steer', { message });\n if (result && result.ok) {\n el('ax-steer-message').value = '';\n flash('ax-steer-status', 'Queued for the agent\\'s next turn ✓');\n } else {\n flash('ax-steer-status', 'Failed: ' + axFailure(result));\n }\n}\n\n// Loop control is in-memory only (the surface is sandboxed: storage APIs throw).\nlet axLoop = null;\nlet axAdvancing = false;\n\nfunction renderLoop() {\n const panel = el('ax-loop-panel');\n panel.hidden = !axLoop;\n if (!axLoop) return;\n el('ax-loop-title').textContent = axLoop.baseTitle;\n el('ax-loop-run').textContent = 'run ' + axLoop.run + ' of ' + axLoop.maxRuns;\n}\nasync function openRun(session, run) {\n const result = await axEmit('ax.work.create', {\n title: session.baseTitle + ' — run ' + run + '/' + session.maxRuns,\n ...(session.detail ? { detail: session.detail } : {}),\n });\n // The human may have stopped the loop while this was in flight.\n if (axLoop !== session) return false;\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-loop-status', 'Could not open run ' + run + ': ' + axFailure(result));\n return false;\n }\n session.run = run;\n session.workItemId = result.primitive.id;\n renderLoop();\n axRenderSurface();\n await axEmit('ax.steer', {\n message:\n 'Loop run ' + run + ' of ' + session.maxRuns + ' for \"' + session.baseTitle +\n '\". Mark work item ' + session.workItemId + ' done when this run is complete.',\n });\n return true;\n}\n/**\n * Start a bounded loop. NEVER auto-started — a human control only. Returns the\n * clamped run count on success (0 on failure) so the surface can sync its inputs;\n * `onStarted` fires the moment run 1 is open, before the loop's own bookkeeping.\n */\nasync function axStartLoop(baseTitle, detail, requested, onStarted) {\n if (axLoop) return 0;\n const maxRuns = Math.min(AX_MAX_RUNS, Math.max(1, Math.round(Number.isFinite(requested) ? requested : 3)));\n const session = { baseTitle: baseTitle, detail: detail, maxRuns: maxRuns, run: 0, workItemId: null };\n axLoop = session;\n renderLoop();\n if (!(await openRun(session, 1))) {\n if (axLoop === session) { axLoop = null; renderLoop(); }\n return 0;\n }\n if (onStarted) onStarted(maxRuns);\n flash('ax-loop-status', requested > AX_MAX_RUNS ? 'Capped at ' + AX_MAX_RUNS + ' runs.' : 'Loop started.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop started: ' + baseTitle + ' (max ' + maxRuns + ' runs)',\n });\n return maxRuns;\n}\nasync function stopLoop() {\n const session = axLoop;\n if (!session) return;\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop stopped at run ' + session.run + ' of ' + session.maxRuns + '.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop stopped by human: ' + session.baseTitle + ' at run ' + session.run + ' of ' + session.maxRuns,\n });\n}\n/**\n * Advance when the CURRENT run's work item reaches done, observed from live AX\n * state. Double-advance guards: an in-flight flag, and clearing workItemId before\n * awaiting so a repeated state push cannot match the same finished run twice.\n */\nasync function maybeAdvanceLoop() {\n const session = axLoop;\n if (!session || axAdvancing || !session.workItemId) return;\n const current = axWorkItems().find((item) => item.id === session.workItemId);\n if (!current || current.status !== 'done') return;\n if (session.run >= session.maxRuns) {\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop finished — ' + session.maxRuns + ' of ' + session.maxRuns + ' runs done.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop complete: ' + session.baseTitle + ' (' + session.maxRuns + ' runs)',\n });\n return;\n }\n axAdvancing = true;\n session.workItemId = null;\n try {\n if (!(await openRun(session, session.run + 1)) && axLoop === session) { axLoop = null; renderLoop(); }\n } finally {\n axAdvancing = false;\n }\n}\n\nfunction applyAxState() {\n axRenderSurface();\n renderLoop();\n void maybeAdvanceLoop();\n}\n\n// ── render (live AX state only — no local mirror of the work list) ──\nfunction renderWork() {\n const items = axWorkItems();\n const list = el('ax-work-list');\n list.textContent = '';\n el('ax-work-counts').textContent = items.length\n ? AX_STATUSES.map((s) => s + ' ' + items.filter((item) => item.status === s).length).join(' · ')\n : '';\n if (items.length === 0) {\n list.appendChild(elem('p', 'muted', 'No work items yet — add a task above and it shows up here.'));\n return;\n }\n for (const item of items) {\n const row = elem('article', 'card ax-work-row');\n row.setAttribute('data-work-id', item.id);\n const head = elem('div', 'ax-work-head');\n head.appendChild(elem('h3', '', item.title));\n head.appendChild(elem('span', 'badge ' + statusTone(item.status), item.status));\n row.appendChild(head);\n if (item.detail) row.appendChild(elem('p', 'small', item.detail));\n const foot = elem('div', 'ax-work-foot');\n const label = elem('label', 'small', 'Status ');\n const select = document.createElement('select');\n select.setAttribute('data-status-for', item.id);\n for (const status of AX_STATUSES) {\n const option = document.createElement('option');\n option.value = status;\n option.textContent = status;\n if (status === item.status) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => setWorkStatus(item.id, select.value));\n label.appendChild(select);\n foot.appendChild(label);\n if (item.agentId) foot.appendChild(elem('span', 'small', 'agent: ' + item.agentId));\n if (axLoop && axLoop.workItemId === item.id) foot.appendChild(elem('span', 'badge info', 'current run'));\n row.appendChild(foot);\n list.appendChild(row);\n }\n}\nfunction axRenderSurface() { renderWork(); }\n\n// ── actions ──\nasync function addTask() {\n const title = el('ax-task-title').value.trim();\n if (!title) { flash('ax-task-status', 'Give the task a title first.'); return; }\n const detail = el('ax-task-detail').value.trim();\n const result = await axEmit('ax.work.create', detail ? { title, detail } : { title });\n if (result && result.ok) {\n el('ax-task-title').value = '';\n el('ax-task-detail').value = '';\n flash('ax-task-status', 'Task queued ✓');\n } else {\n flash('ax-task-status', 'Failed: ' + axFailure(result));\n }\n}\nasync function setWorkStatus(id, status) {\n const result = await axEmit('ax.work.update', { id, status });\n flash('ax-work-status', result && result.ok ? 'Status updated ✓' : 'Update failed: ' + axFailure(result));\n}\nasync function startLoop() {\n if (axLoop) { flash('ax-task-status', 'A loop is already running — stop it first.'); return; }\n const title = el('ax-task-title').value.trim();\n if (!title) { flash('ax-task-status', 'Give the task a title first.'); return; }\n await axStartLoop(title, el('ax-task-detail').value.trim(), Number(el('ax-loop-runs').value), (maxRuns) => {\n el('ax-task-title').value = '';\n el('ax-task-detail').value = '';\n el('ax-loop-runs').value = String(maxRuns);\n });\n}\n\nel('ax-add-task').addEventListener('click', addTask);\nel('ax-start-loop').addEventListener('click', startLoop);\nel('ax-stop-loop').addEventListener('click', stopLoop);\nel('ax-send-steer').addEventListener('click', sendSteer);\nwindow.__pmxGetCopyJson = () => ({\n ...PMX_DATA,\n workItems: axWorkItems(),\n loop: axLoop ? { title: axLoop.baseTitle, run: axLoop.run, maxRuns: axLoop.maxRuns } : null,\n});\nif (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') el('ax-offline').hidden = false;\n// Seeded snapshot first, then every live push. A loop NEVER auto-starts on load.\nwindow.addEventListener('pmx-ax-update', applyAxState);\napplyAxState();\n</script>\n</body>\n</html>",
|
|
5430
5435
|
"htmlPrimitive": "ax-board",
|
|
5431
5436
|
"primitiveData": {
|
|
5432
5437
|
"note": "Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt.",
|
|
@@ -5444,7 +5449,7 @@
|
|
|
5444
5449
|
},
|
|
5445
5450
|
"title": "Agent board",
|
|
5446
5451
|
"strictSize": true,
|
|
5447
|
-
"contentSummary": "Agent board PMX HTML primitive / ax-board Agent board Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops.
|
|
5452
|
+
"contentSummary": "Agent board PMX HTML primitive / ax-board Agent board Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops. AX bridge unavailable This board is not running as an AX-enabled canvas node, so its controls are inert. Give the agent a task Tasks queued here land in the canvas AX work queue, so the agent picks them up without another prompt. Add task or run it as a bounded loop: max runs Start loop Steer Queue steering message Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent. Loop run 0 of 0 Each run is its own work item. When you mark the current run done , the surface opens the next run and steers again — until the cap or Stop. Stop loop Loop control lives in this panel, so reloading it stops the loop — the work items stay as the durable record. Work",
|
|
5448
5453
|
"agentSummary": "Live agent work board: hand the agent tasks, watch status, steer, and run bounded task loops."
|
|
5449
5454
|
},
|
|
5450
5455
|
"createdBy": {
|
|
@@ -5482,7 +5487,7 @@
|
|
|
5482
5487
|
"collapsed": false,
|
|
5483
5488
|
"pinned": false,
|
|
5484
5489
|
"data": {
|
|
5485
|
-
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Release flow</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: start; margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } header.hero, .two, .three { grid-template-columns: 1fr; } .actions { justify-content: flex-start; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / ax-flow</div><h1>Release flow</h1><p>Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.</p></div>\n <div class=\"actions\"><button type=\"button\" data-copy-json>Copy JSON</button><button type=\"button\" data-copy-prompt>Copy prompt</button></div>\n </header>\n <style>\n .ax-field { display: grid; gap: 6px; margin-bottom: 10px; }\n .ax-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-work-row { margin-bottom: 10px; padding: 12px 14px; }\n .ax-work-head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }\n .ax-work-head h3 { margin: 0; }\n .ax-work-foot { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 8px; }\n select { border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n input[type=\"number\"] { width: 72px; border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n #ax-loop-panel[hidden] { display: none; }\n #ax-offline { border-color: var(--color-danger, #ef4444); margin-bottom: 14px; }\n</style>\n<style>\n /* One left-to-right track that scrolls rather than wrapping: a wrapped row\n leaves a \"→\" pointing at nothing at the end of every row. */\n .ax-flow-scroll { overflow-x: auto; padding: 2px 2px 6px; }\n /* fit-content: the track fills the panel when the steps fit, and grows to the\n steps' minimum width (scrolling) when they don't — so the loop rail always\n spans exactly the flow. */\n .ax-flow-wrap { position: relative; width: fit-content; min-width: 100%; }\n .ax-flow-wrap.looping { padding-bottom: 54px; }\n .ax-flow-diagram { display: flex; align-items: stretch; }\n .ax-flow-step { flex: 1 1 200px; min-width: 200px; max-width: 360px; border: 1px solid var(--color-border, #263241); border-left-width: 4px; border-radius: 16px; padding: 12px 14px; background: var(--color-surface, #17202b); }\n .ax-flow-step h3 { margin: 6px 0 6px; font-size: 14px; }\n .ax-flow-step p { margin: 0 0 10px; }\n .ax-flow-step.tone-ok { border-left-color: var(--color-success, #22c55e); }\n .ax-flow-step.tone-warn { border-left-color: var(--color-warning, #eab308); }\n .ax-flow-step.tone-danger { border-left-color: var(--color-danger, #ef4444); }\n .ax-flow-step.tone-info { border-left-color: var(--color-accent, #4a9eff); }\n .ax-flow-step.tone-idle { border-left-color: var(--color-border, #263241); }\n .ax-flow-num { font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--color-text-muted, #7e8a97); }\n .ax-flow-foot { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-flow-arrow { align-self: center; padding: 0 7px; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .badge.idle { color: var(--color-text-muted, #7e8a97); background: transparent; border: 1px solid var(--color-border, #263241); }\n .ax-flow-rail { position: absolute; left: 22px; right: 22px; bottom: 14px; height: 30px; border: 2px dashed var(--color-accent, #4a9eff); border-top: none; border-radius: 0 0 18px 18px; pointer-events: none; }\n .ax-flow-rail::before { content: '▲'; position: absolute; left: -8px; top: -14px; font-size: 12px; line-height: 1; color: var(--color-accent, #4a9eff); }\n .ax-flow-rail-label { position: absolute; left: 50%; bottom: -9px; transform: translateX(-50%); background: var(--color-bg, #0b0f14); padding: 0 8px; }\n .ax-flow-wrap:not(.looping) .ax-flow-rail { display: none; }\n</style>\n<section class=\"panel\" id=\"ax-offline\" hidden><h2>AX bridge unavailable</h2><p class=\"small\">This flow is not running as an AX-enabled canvas node, so its controls are inert.</p></section>\n<section class=\"panel\">\n <div class=\"ax-work-head\"><h2>Release flow</h2><span class=\"small\" id=\"ax-flow-counts\"></span></div>\n <p class=\"small\">Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes.</p>\n <div class=\"ax-flow-scroll\">\n <div class=\"ax-flow-wrap looping\" id=\"ax-flow-wrap\">\n <div class=\"ax-flow-diagram\" id=\"ax-flow-diagram\"></div>\n <div class=\"ax-flow-rail\"><span class=\"badge info ax-flow-rail-label\">loops back to step 1 · max 3 runs</span></div>\n </div>\n </div>\n</section>\n<section class=\"two\" style=\"margin-top:14px\">\n <div class=\"panel\">\n <h2>Run the flow</h2>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-flow-queue-all\">Queue every step</button><span class=\"small\" id=\"ax-flow-status\"></span></div>\n <p class=\"small\">A step is queued once — after that its status control advances it (todo → in-progress → done).</p>\n <div class=\"ax-row\" style=\"margin-top:10px\">\n <span class=\"small\">or run the whole flow as a bounded loop:</span>\n <label class=\"small\">max runs <input type=\"number\" id=\"ax-loop-runs\" min=\"1\" max=\"20\" step=\"1\" value=\"3\"></label>\n <button type=\"button\" id=\"ax-start-loop\">Start loop</button>\n </div>\n </div>\n <div class=\"panel\">\n <h2>Steer</h2>\n <div class=\"ax-field\"><textarea id=\"ax-steer-message\" placeholder=\"Prioritize the auth refactor\" style=\"min-height:92px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-send-steer\">Queue steering message</button></div>\n <p class=\"small\">Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent.</p>\n <p class=\"small\" id=\"ax-steer-status\"></p>\n </div>\n</section>\n<section class=\"panel\" id=\"ax-loop-panel\" hidden style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2 id=\"ax-loop-title\">Loop</h2><span class=\"badge info\" id=\"ax-loop-run\">run 0 of 0</span></div>\n <p class=\"small\">Each run is its own work item. When you mark the current run <strong>done</strong>, the surface opens the next run and steers again — until the cap or Stop.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-stop-loop\">Stop loop</button><span class=\"small\" id=\"ax-loop-status\"></span></div>\n <p class=\"small\">Loop control lives in this panel, so reloading it stops the loop — the work items stay as the durable record.</p>\n</section>\n<section class=\"panel\" style=\"margin-top:14px\">\n <h2>Materialize</h2>\n <p class=\"small\">Lays this flow out on the canvas: one markdown node per step, joined in order by flow edges, plus a dashed loop-back edge. Each step node gets its own work item, so its status chip tracks the agent.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-flow-materialize\">Materialize to board</button><span class=\"small\" id=\"ax-flow-materialize-status\"></span></div>\n <p class=\"small\">Re-materializing REPLACES the nodes this panel put on the board — it never stacks a second copy. The work items it already created stay as the durable record.</p>\n</section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"note\": \"Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes.\",\n \"steps\": [\n {\n \"title\": \"Cut the branch\",\n \"detail\": \"Bump the version and write the changelog entry.\"\n },\n {\n \"title\": \"Run the gates\",\n \"detail\": \"typecheck, unit, e2e, lint — all green before anything else.\"\n },\n {\n \"title\": \"Publish\",\n \"detail\": \"Tag, push, npm publish, then smoke the published package.\"\n },\n {\n \"title\": \"Report\",\n \"detail\": \"What shipped, what is still open, what to watch.\"\n }\n ],\n \"loop\": {\n \"enabled\": true,\n \"maxRuns\": 3\n }\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\ndocument.querySelector('[data-copy-json]')?.addEventListener('click', () => {\n const payload = typeof window.__pmxGetCopyJson === 'function' ? window.__pmxGetCopyJson() : PMX_DATA;\n copyText(JSON.stringify(payload, null, 2));\n});\ndocument.querySelector('[data-copy-prompt]')?.addEventListener('click', () => copyText('Use this PMX Canvas HTML primitive output as context:\\n\\n' + JSON.stringify(PMX_DATA, null, 2)));\n\nconst AX_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'];\nconst AX_MAX_RUNS = 20;\n\nconst el = (id) => document.getElementById(id);\nconst axFlashTimers = {};\nfunction flash(id, message) {\n const target = el(id);\n if (!target) return;\n target.textContent = message;\n if (axFlashTimers[id]) clearTimeout(axFlashTimers[id]);\n axFlashTimers[id] = setTimeout(() => { target.textContent = ''; }, 4000);\n}\nfunction axWorkItems() {\n const state = window.PMX_AX && window.PMX_AX.state;\n return state && Array.isArray(state.workItems) ? state.workItems : [];\n}\nasync function axEmit(type, payload) {\n if (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') {\n return { ok: false, code: 'ax-bridge-missing', error: 'AX bridge is not available on this surface.' };\n }\n return await window.PMX_AX.emit(type, payload);\n}\nfunction axFailure(result) {\n return (result && (result.error || result.code)) || 'unknown error';\n}\nfunction statusTone(status) {\n if (status === 'done') return 'ok';\n if (status === 'blocked' || status === 'cancelled') return 'danger';\n if (status === 'in-progress') return 'warn';\n return 'info';\n}\nfunction elem(tag, className, textContent) {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (textContent !== undefined) node.textContent = textContent;\n return node;\n}\nasync function sendSteer() {\n const message = el('ax-steer-message').value.trim();\n if (!message) { flash('ax-steer-status', 'Write a steering message first.'); return; }\n const result = await axEmit('ax.steer', { message });\n if (result && result.ok) {\n el('ax-steer-message').value = '';\n flash('ax-steer-status', 'Queued for the agent\\'s next turn ✓');\n } else {\n flash('ax-steer-status', 'Failed: ' + axFailure(result));\n }\n}\n\n// Loop control is in-memory only (the surface is sandboxed: storage APIs throw).\nlet axLoop = null;\nlet axAdvancing = false;\n\nfunction renderLoop() {\n const panel = el('ax-loop-panel');\n panel.hidden = !axLoop;\n if (!axLoop) return;\n el('ax-loop-title').textContent = axLoop.baseTitle;\n el('ax-loop-run').textContent = 'run ' + axLoop.run + ' of ' + axLoop.maxRuns;\n}\nasync function openRun(session, run) {\n const result = await axEmit('ax.work.create', {\n title: session.baseTitle + ' — run ' + run + '/' + session.maxRuns,\n ...(session.detail ? { detail: session.detail } : {}),\n });\n // The human may have stopped the loop while this was in flight.\n if (axLoop !== session) return false;\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-loop-status', 'Could not open run ' + run + ': ' + axFailure(result));\n return false;\n }\n session.run = run;\n session.workItemId = result.primitive.id;\n renderLoop();\n axRenderSurface();\n await axEmit('ax.steer', {\n message:\n 'Loop run ' + run + ' of ' + session.maxRuns + ' for \"' + session.baseTitle +\n '\". Mark work item ' + session.workItemId + ' done when this run is complete.',\n });\n return true;\n}\n/**\n * Start a bounded loop. NEVER auto-started — a human control only. Returns the\n * clamped run count on success (0 on failure) so the surface can sync its inputs;\n * `onStarted` fires the moment run 1 is open, before the loop's own bookkeeping.\n */\nasync function axStartLoop(baseTitle, detail, requested, onStarted) {\n if (axLoop) return 0;\n const maxRuns = Math.min(AX_MAX_RUNS, Math.max(1, Math.round(Number.isFinite(requested) ? requested : 3)));\n const session = { baseTitle: baseTitle, detail: detail, maxRuns: maxRuns, run: 0, workItemId: null };\n axLoop = session;\n renderLoop();\n if (!(await openRun(session, 1))) {\n if (axLoop === session) { axLoop = null; renderLoop(); }\n return 0;\n }\n if (onStarted) onStarted(maxRuns);\n flash('ax-loop-status', requested > AX_MAX_RUNS ? 'Capped at ' + AX_MAX_RUNS + ' runs.' : 'Loop started.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop started: ' + baseTitle + ' (max ' + maxRuns + ' runs)',\n });\n return maxRuns;\n}\nasync function stopLoop() {\n const session = axLoop;\n if (!session) return;\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop stopped at run ' + session.run + ' of ' + session.maxRuns + '.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop stopped by human: ' + session.baseTitle + ' at run ' + session.run + ' of ' + session.maxRuns,\n });\n}\n/**\n * Advance when the CURRENT run's work item reaches done, observed from live AX\n * state. Double-advance guards: an in-flight flag, and clearing workItemId before\n * awaiting so a repeated state push cannot match the same finished run twice.\n */\nasync function maybeAdvanceLoop() {\n const session = axLoop;\n if (!session || axAdvancing || !session.workItemId) return;\n const current = axWorkItems().find((item) => item.id === session.workItemId);\n if (!current || current.status !== 'done') return;\n if (session.run >= session.maxRuns) {\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop finished — ' + session.maxRuns + ' of ' + session.maxRuns + ' runs done.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop complete: ' + session.baseTitle + ' (' + session.maxRuns + ' runs)',\n });\n return;\n }\n axAdvancing = true;\n session.workItemId = null;\n try {\n if (!(await openRun(session, session.run + 1)) && axLoop === session) { axLoop = null; renderLoop(); }\n } finally {\n axAdvancing = false;\n }\n}\n\nfunction applyAxState() {\n axRenderSurface();\n renderLoop();\n void maybeAdvanceLoop();\n}\n\nconst AX_FLOW_TITLE = \"Release flow\";\nconst AX_FLOW_STEPS = Array.isArray(PMX_DATA.steps) ? PMX_DATA.steps : [];\nconst AX_FLOW_LOOP = PMX_DATA.loop || {};\n// Step index → the id of the work item bound to that step (created here, or\n// adopted from a materialize). Matching is by ID ONLY: work-item titles are not\n// unique, so a title match would happily bind the wrong item.\nconst axFlowWorkIds = AX_FLOW_STEPS.map(() => null);\n\nfunction stepWorkItem(index) {\n const id = axFlowWorkIds[index];\n return id ? axWorkItems().find((item) => item.id === id) || null : null;\n}\nfunction stepTone(item) {\n return item ? statusTone(item.status) : 'idle';\n}\n\n// ── render (live AX state only — no local mirror of step status) ──\nfunction renderFlow() {\n const host = el('ax-flow-diagram');\n host.textContent = '';\n let bound = 0;\n let done = 0;\n AX_FLOW_STEPS.forEach((step, index) => {\n if (index > 0) host.appendChild(elem('div', 'ax-flow-arrow', '→'));\n const item = stepWorkItem(index);\n if (item) bound += 1;\n if (item && item.status === 'done') done += 1;\n const card = elem('article', 'ax-flow-step tone-' + stepTone(item));\n card.setAttribute('data-step', String(index + 1));\n const head = elem('div', 'ax-work-head');\n head.appendChild(elem('span', 'ax-flow-num', 'step ' + (index + 1)));\n head.appendChild(elem('span', 'badge ' + stepTone(item), item ? item.status : 'not queued'));\n card.appendChild(head);\n card.appendChild(elem('h3', '', step.title));\n if (step.detail) card.appendChild(elem('p', 'small', step.detail));\n const foot = elem('div', 'ax-flow-foot');\n if (item) {\n const label = elem('label', 'small', 'status ');\n const select = document.createElement('select');\n select.setAttribute('data-status-for', item.id);\n for (const status of AX_STATUSES) {\n const option = document.createElement('option');\n option.value = status;\n option.textContent = status;\n if (status === item.status) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => setStepStatus(index, select.value));\n label.appendChild(select);\n foot.appendChild(label);\n } else {\n const button = elem('button', '', 'Queue step');\n button.type = 'button';\n button.setAttribute('data-queue-step', String(index + 1));\n button.addEventListener('click', () => queueStep(index));\n foot.appendChild(button);\n }\n if (axLoop && item && axLoop.workItemId === item.id) foot.appendChild(elem('span', 'badge info', 'current run'));\n card.appendChild(foot);\n host.appendChild(card);\n });\n el('ax-flow-counts').textContent =\n AX_FLOW_STEPS.length + ' steps · ' + bound + ' queued · ' + done + ' done' +\n (AX_FLOW_LOOP.enabled ? ' · loops' : '');\n}\nfunction axRenderSurface() { renderFlow(); }\n\n// ── actions ──\nasync function queueStep(index) {\n const step = AX_FLOW_STEPS[index];\n if (!step || axFlowWorkIds[index]) return false;\n const result = await axEmit('ax.work.create', {\n title: AX_FLOW_TITLE + ' — ' + (index + 1) + '. ' + step.title,\n ...(step.detail ? { detail: step.detail } : {}),\n });\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-flow-status', 'Step ' + (index + 1) + ' failed: ' + axFailure(result));\n return false;\n }\n axFlowWorkIds[index] = result.primitive.id;\n renderFlow();\n flash('ax-flow-status', 'Step ' + (index + 1) + ' queued ✓');\n return true;\n}\nasync function queueAllSteps() {\n for (let index = 0; index < AX_FLOW_STEPS.length; index++) {\n if (!axFlowWorkIds[index] && !(await queueStep(index))) return;\n }\n flash('ax-flow-status', 'Every step is queued ✓');\n}\nasync function setStepStatus(index, status) {\n const id = axFlowWorkIds[index];\n if (!id) return;\n const result = await axEmit('ax.work.update', { id, status });\n flash(\n 'ax-flow-status',\n result && result.ok ? 'Step ' + (index + 1) + ' → ' + status + ' ✓' : 'Update failed: ' + axFailure(result),\n );\n}\nasync function startFlowLoop() {\n if (axLoop) { flash('ax-flow-status', 'A loop is already running — stop it first.'); return; }\n await axStartLoop(AX_FLOW_TITLE, AX_FLOW_STEPS.map((step, i) => (i + 1) + '. ' + step.title).join('\\n'),\n Number(el('ax-loop-runs').value), (maxRuns) => { el('ax-loop-runs').value = String(maxRuns); });\n}\n/**\n * Hand the flow to the server, which owns the whole materialized shape. We send\n * TEXT ONLY — no node type, geometry, or node data — and adopt the work-item ids\n * it links to each step node so this panel and the board show the same status.\n */\nasync function materializeFlow() {\n const result = await axEmit('ax.flow.materialize', {\n title: AX_FLOW_TITLE,\n steps: AX_FLOW_STEPS.map((step) => (step.detail ? { title: step.title, detail: step.detail } : { title: step.title })),\n loop: { enabled: AX_FLOW_LOOP.enabled === true, maxRuns: Number(el('ax-loop-runs').value) || 3 },\n });\n if (!result || !result.ok || !result.primitive) {\n flash('ax-flow-materialize-status', 'Failed: ' + axFailure(result));\n return;\n }\n for (const step of result.primitive.steps) axFlowWorkIds[step.index - 1] = step.workItemId;\n renderFlow();\n flash(\n 'ax-flow-materialize-status',\n result.primitive.steps.length + ' step nodes on the board ✓' +\n (result.primitive.replacedNodeCount ? ' (replaced ' + result.primitive.replacedNodeCount + ')' : ''),\n );\n}\n\nel('ax-flow-queue-all').addEventListener('click', queueAllSteps);\nel('ax-flow-materialize').addEventListener('click', materializeFlow);\nel('ax-start-loop').addEventListener('click', startFlowLoop);\nel('ax-stop-loop').addEventListener('click', stopLoop);\nel('ax-send-steer').addEventListener('click', sendSteer);\nwindow.__pmxGetCopyJson = () => ({\n ...PMX_DATA,\n steps: AX_FLOW_STEPS.map((step, index) => {\n const item = stepWorkItem(index);\n return { ...step, workItemId: axFlowWorkIds[index], status: item ? item.status : null };\n }),\n loop: axLoop ? { title: axLoop.baseTitle, run: axLoop.run, maxRuns: axLoop.maxRuns } : null,\n});\nif (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') el('ax-offline').hidden = false;\n// Seeded snapshot first, then every live push. A loop NEVER auto-starts on load.\nwindow.addEventListener('pmx-ax-update', applyAxState);\napplyAxState();\n</script>\n</body>\n</html>",
|
|
5490
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Release flow</title>\n<style>\n :root { color-scheme: dark light; }\n * { box-sizing: border-box; }\n body { margin: 0; padding: 22px; background: radial-gradient(circle at top left, color-mix(in srgb, var(--color-accent, #4a9eff) 18%, transparent), transparent 34rem), var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); }\n .shell { max-width: 1160px; margin: 0 auto; }\n header.hero { margin-bottom: 20px; }\n .kicker { color: var(--color-accent, #4a9eff); text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 800; }\n h1 { margin: 5px 0 8px; font-size: clamp(28px, 4vw, 46px); line-height: .95; letter-spacing: -.04em; }\n h2 { margin: 0 0 12px; font-size: 19px; letter-spacing: -.02em; }\n h3 { margin: 0 0 8px; font-size: 15px; }\n p { color: var(--color-text-secondary, #aeb8c2); margin: 0 0 12px; }\n button { border: 1px solid var(--color-border, #263241); background: var(--color-panel, #111821); color: var(--color-text, #e8edf2); border-radius: 999px; padding: 8px 12px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--color-accent, #4a9eff); transform: translateY(-1px); }\n table { width: 100%; border-collapse: collapse; color: var(--color-text-secondary, #aeb8c2); font-size: 13px; }\n th, td { text-align: left; border-bottom: 1px solid var(--color-border, #263241); padding: 9px 8px; vertical-align: top; }\n th { color: var(--color-text, #e8edf2); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }\n .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(235px, 1fr)); gap: 14px; }\n .two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }\n .three { display: grid; grid-template-columns: 1.1fr 1fr .9fr; gap: 14px; align-items: start; }\n .card, details, .panel { border: 1px solid var(--color-border, #263241); background: color-mix(in srgb, var(--color-panel, #111821) 88%, transparent); border-radius: 18px; padding: 16px; box-shadow: 0 18px 50px rgba(0, 0, 0, .18); }\n .card.emphasis { border-color: color-mix(in srgb, var(--color-accent, #4a9eff) 55%, var(--color-border, #263241)); }\n .sticky { position: sticky; top: 14px; }\n .metric { min-height: 104px; display: grid; align-content: space-between; }\n .metric strong { font-size: clamp(24px, 4vw, 42px); letter-spacing: -.04em; }\n .muted { color: var(--color-text-secondary, #aeb8c2); }\n .small { font-size: 12px; color: var(--color-text-muted, #7e8a97); }\n ul { padding-left: 18px; margin: 8px 0 0; color: var(--color-text-secondary, #aeb8c2); }\n li + li { margin-top: 5px; }\n pre { white-space: pre-wrap; overflow: auto; margin: 10px 0 0; padding: 12px; border-radius: 12px; background: #05070a; border: 1px solid color-mix(in srgb, var(--color-border, #263241) 75%, black); color: #d7eadb; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.45; }\n .badge { display: inline-flex; align-items: center; border-radius: 999px; padding: 3px 8px; font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }\n .badge.ok { color: #062b18; background: var(--color-success, #22c55e); }\n .badge.warn { color: #2b1a02; background: var(--color-warning, #eab308); }\n .badge.danger { color: white; background: var(--color-danger, #ef4444); }\n .badge.info { color: #051a33; background: var(--color-accent, #4a9eff); }\n .timeline { position: relative; display: grid; gap: 12px; }\n .timeline .step { display: grid; grid-template-columns: 34px 1fr; gap: 12px; align-items: start; }\n .dot { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--color-accent, #4a9eff); color: #06111f; font-weight: 900; }\n .flow { display: flex; gap: 10px; align-items: stretch; overflow-x: auto; padding-bottom: 4px; }\n .flow-node { min-width: 180px; border-radius: 16px; border: 1px solid var(--color-border, #263241); padding: 14px; background: var(--color-surface, #17202b); }\n .arrow { align-self: center; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }\n .swatch { width: 44px; height: 44px; border-radius: 13px; border: 1px solid rgba(255,255,255,.22); }\n textarea, input[type=\"text\"] { width: 100%; border: 1px solid var(--color-border, #263241); border-radius: 14px; padding: 12px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n textarea { min-height: 220px; resize: vertical; font-family: var(--font-mono, ui-monospace, monospace); }\n .columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; align-items: start; }\n .column { min-height: 220px; border: 1px dashed var(--color-border, #263241); border-radius: 18px; padding: 12px; background: color-mix(in srgb, var(--color-panel, #111821) 70%, transparent); }\n .ticket { cursor: grab; margin: 10px 0; }\n .ticket:active { cursor: grabbing; }\n .slide { min-height: 390px; display: none; align-content: center; }\n .slide.active { display: grid; }\n .slide h2 { font-size: clamp(30px, 5vw, 58px); line-height: .95; }\n .preview { white-space: pre-wrap; min-height: 220px; }\n .figure svg { width: 100%; min-height: 220px; border-radius: 14px; background: color-mix(in srgb, var(--color-bg, #0b0f14) 86%, white); border: 1px solid var(--color-border, #263241); }\n @media (max-width: 760px) { body { padding: 14px; } .two, .three { grid-template-columns: 1fr; } .sticky { position: static; } }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <header class=\"hero\">\n <div><div class=\"kicker\">PMX HTML primitive / ax-flow</div><h1>Release flow</h1><p>Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.</p></div>\n </header>\n <style>\n .ax-field { display: grid; gap: 6px; margin-bottom: 10px; }\n .ax-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-work-row { margin-bottom: 10px; padding: 12px 14px; }\n .ax-work-head { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; }\n .ax-work-head h3 { margin: 0; }\n .ax-work-foot { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 8px; }\n select { border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n input[type=\"number\"] { width: 72px; border: 1px solid var(--color-border, #263241); border-radius: 999px; padding: 6px 10px; background: var(--color-bg, #0b0f14); color: var(--color-text, #e8edf2); font: inherit; }\n #ax-loop-panel[hidden] { display: none; }\n #ax-offline { border-color: var(--color-danger, #ef4444); margin-bottom: 14px; }\n</style>\n<style>\n /* One left-to-right track that scrolls rather than wrapping: a wrapped row\n leaves a \"→\" pointing at nothing at the end of every row. */\n .ax-flow-scroll { overflow-x: auto; padding: 2px 2px 6px; }\n /* fit-content: the track fills the panel when the steps fit, and grows to the\n steps' minimum width (scrolling) when they don't — so the loop rail always\n spans exactly the flow. */\n .ax-flow-wrap { position: relative; width: fit-content; min-width: 100%; }\n .ax-flow-wrap.looping { padding-bottom: 54px; }\n .ax-flow-diagram { display: flex; align-items: stretch; }\n .ax-flow-step { flex: 1 1 200px; min-width: 200px; max-width: 360px; border: 1px solid var(--color-border, #263241); border-left-width: 4px; border-radius: 16px; padding: 12px 14px; background: var(--color-surface, #17202b); }\n .ax-flow-step h3 { margin: 6px 0 6px; font-size: 14px; }\n .ax-flow-step p { margin: 0 0 10px; }\n .ax-flow-step.tone-ok { border-left-color: var(--color-success, #22c55e); }\n .ax-flow-step.tone-warn { border-left-color: var(--color-warning, #eab308); }\n .ax-flow-step.tone-danger { border-left-color: var(--color-danger, #ef4444); }\n .ax-flow-step.tone-info { border-left-color: var(--color-accent, #4a9eff); }\n .ax-flow-step.tone-idle { border-left-color: var(--color-border, #263241); }\n .ax-flow-num { font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--color-text-muted, #7e8a97); }\n .ax-flow-foot { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n .ax-flow-arrow { align-self: center; padding: 0 7px; color: var(--color-accent, #4a9eff); font-weight: 900; }\n .badge.idle { color: var(--color-text-muted, #7e8a97); background: transparent; border: 1px solid var(--color-border, #263241); }\n .ax-flow-rail { position: absolute; left: 22px; right: 22px; bottom: 14px; height: 30px; border: 2px dashed var(--color-accent, #4a9eff); border-top: none; border-radius: 0 0 18px 18px; pointer-events: none; }\n .ax-flow-rail::before { content: '▲'; position: absolute; left: -8px; top: -14px; font-size: 12px; line-height: 1; color: var(--color-accent, #4a9eff); }\n .ax-flow-rail-label { position: absolute; left: 50%; bottom: -9px; transform: translateX(-50%); background: var(--color-bg, #0b0f14); padding: 0 8px; }\n .ax-flow-wrap:not(.looping) .ax-flow-rail { display: none; }\n</style>\n<section class=\"panel\" id=\"ax-offline\" hidden><h2>AX bridge unavailable</h2><p class=\"small\">This flow is not running as an AX-enabled canvas node, so its controls are inert.</p></section>\n<section class=\"panel\">\n <div class=\"ax-work-head\"><h2>Release flow</h2><span class=\"small\" id=\"ax-flow-counts\"></span></div>\n <p class=\"small\">Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes.</p>\n <div class=\"ax-flow-scroll\">\n <div class=\"ax-flow-wrap looping\" id=\"ax-flow-wrap\">\n <div class=\"ax-flow-diagram\" id=\"ax-flow-diagram\"></div>\n <div class=\"ax-flow-rail\"><span class=\"badge info ax-flow-rail-label\">loops back to step 1 · max 3 runs</span></div>\n </div>\n </div>\n</section>\n<section class=\"two\" style=\"margin-top:14px\">\n <div class=\"panel\">\n <h2>Run the flow</h2>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-flow-queue-all\">Queue every step</button><span class=\"small\" id=\"ax-flow-status\"></span></div>\n <p class=\"small\">A step is queued once — after that its status control advances it (todo → in-progress → done).</p>\n <div class=\"ax-row\" style=\"margin-top:10px\">\n <span class=\"small\">or run the whole flow as a bounded loop:</span>\n <label class=\"small\">max runs <input type=\"number\" id=\"ax-loop-runs\" min=\"1\" max=\"20\" step=\"1\" value=\"3\"></label>\n <button type=\"button\" id=\"ax-start-loop\">Start loop</button>\n </div>\n </div>\n <div class=\"panel\">\n <h2>Steer</h2>\n <div class=\"ax-field\"><textarea id=\"ax-steer-message\" placeholder=\"Prioritize the auth refactor\" style=\"min-height:92px\"></textarea></div>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-send-steer\">Queue steering message</button></div>\n <p class=\"small\">Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent.</p>\n <p class=\"small\" id=\"ax-steer-status\"></p>\n </div>\n</section>\n<section class=\"panel\" id=\"ax-loop-panel\" hidden style=\"margin-top:14px\">\n <div class=\"ax-work-head\"><h2 id=\"ax-loop-title\">Loop</h2><span class=\"badge info\" id=\"ax-loop-run\">run 0 of 0</span></div>\n <p class=\"small\">Each run is its own work item. When you mark the current run <strong>done</strong>, the surface opens the next run and steers again — until the cap or Stop.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-stop-loop\">Stop loop</button><span class=\"small\" id=\"ax-loop-status\"></span></div>\n <p class=\"small\">Loop control lives in this panel, so reloading it stops the loop — the work items stay as the durable record.</p>\n</section>\n<section class=\"panel\" style=\"margin-top:14px\">\n <h2>Materialize</h2>\n <p class=\"small\">Lays this flow out on the canvas: one markdown node per step, joined in order by flow edges, plus a dashed loop-back edge. Each step node gets its own work item, so its status chip tracks the agent.</p>\n <div class=\"ax-row\"><button type=\"button\" id=\"ax-flow-materialize\">Materialize to board</button><span class=\"small\" id=\"ax-flow-materialize-status\"></span></div>\n <p class=\"small\">Re-materializing REPLACES the nodes this panel put on the board — it never stacks a second copy. The work items it already created stay as the durable record.</p>\n</section>\n</div>\n<script type=\"application/json\" id=\"pmx-data\">{\n \"note\": \"Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes.\",\n \"steps\": [\n {\n \"title\": \"Cut the branch\",\n \"detail\": \"Bump the version and write the changelog entry.\"\n },\n {\n \"title\": \"Run the gates\",\n \"detail\": \"typecheck, unit, e2e, lint — all green before anything else.\"\n },\n {\n \"title\": \"Publish\",\n \"detail\": \"Tag, push, npm publish, then smoke the published package.\"\n },\n {\n \"title\": \"Report\",\n \"detail\": \"What shipped, what is still open, what to watch.\"\n }\n ],\n \"loop\": {\n \"enabled\": true,\n \"maxRuns\": 3\n }\n}</script>\n<script>\nconst PMX_DATA = JSON.parse(document.getElementById('pmx-data').textContent);\nwindow.__pmxGetCopyJson = () => PMX_DATA;\nfunction fallbackCopy(text) {\n const el = document.createElement('textarea');\n el.value = text;\n el.setAttribute('readonly', '');\n el.style.position = 'fixed';\n el.style.left = '-9999px';\n document.body.appendChild(el);\n el.select();\n document.execCommand('copy');\n el.remove();\n}\nasync function copyText(text) {\n if (navigator.clipboard && window.isSecureContext) await navigator.clipboard.writeText(text);\n else fallbackCopy(text);\n}\n\nconst AX_STATUSES = ['todo', 'in-progress', 'blocked', 'done', 'cancelled'];\nconst AX_MAX_RUNS = 20;\n\nconst el = (id) => document.getElementById(id);\nconst axFlashTimers = {};\nfunction flash(id, message) {\n const target = el(id);\n if (!target) return;\n target.textContent = message;\n if (axFlashTimers[id]) clearTimeout(axFlashTimers[id]);\n axFlashTimers[id] = setTimeout(() => { target.textContent = ''; }, 4000);\n}\nfunction axWorkItems() {\n const state = window.PMX_AX && window.PMX_AX.state;\n return state && Array.isArray(state.workItems) ? state.workItems : [];\n}\nasync function axEmit(type, payload) {\n if (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') {\n return { ok: false, code: 'ax-bridge-missing', error: 'AX bridge is not available on this surface.' };\n }\n return await window.PMX_AX.emit(type, payload);\n}\nfunction axFailure(result) {\n return (result && (result.error || result.code)) || 'unknown error';\n}\nfunction statusTone(status) {\n if (status === 'done') return 'ok';\n if (status === 'blocked' || status === 'cancelled') return 'danger';\n if (status === 'in-progress') return 'warn';\n return 'info';\n}\nfunction elem(tag, className, textContent) {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (textContent !== undefined) node.textContent = textContent;\n return node;\n}\nasync function sendSteer() {\n const message = el('ax-steer-message').value.trim();\n if (!message) { flash('ax-steer-status', 'Write a steering message first.'); return; }\n const result = await axEmit('ax.steer', { message });\n if (result && result.ok) {\n el('ax-steer-message').value = '';\n flash('ax-steer-status', 'Queued for the agent\\'s next turn ✓');\n } else {\n flash('ax-steer-status', 'Failed: ' + axFailure(result));\n }\n}\n\n// Loop control is in-memory only (the surface is sandboxed: storage APIs throw).\nlet axLoop = null;\nlet axAdvancing = false;\n\nfunction renderLoop() {\n const panel = el('ax-loop-panel');\n panel.hidden = !axLoop;\n if (!axLoop) return;\n el('ax-loop-title').textContent = axLoop.baseTitle;\n el('ax-loop-run').textContent = 'run ' + axLoop.run + ' of ' + axLoop.maxRuns;\n}\nasync function openRun(session, run) {\n const result = await axEmit('ax.work.create', {\n title: session.baseTitle + ' — run ' + run + '/' + session.maxRuns,\n ...(session.detail ? { detail: session.detail } : {}),\n });\n // The human may have stopped the loop while this was in flight.\n if (axLoop !== session) return false;\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-loop-status', 'Could not open run ' + run + ': ' + axFailure(result));\n return false;\n }\n session.run = run;\n session.workItemId = result.primitive.id;\n renderLoop();\n axRenderSurface();\n await axEmit('ax.steer', {\n message:\n 'Loop run ' + run + ' of ' + session.maxRuns + ' for \"' + session.baseTitle +\n '\". Mark work item ' + session.workItemId + ' done when this run is complete.',\n });\n return true;\n}\n/**\n * Start a bounded loop. NEVER auto-started — a human control only. Returns the\n * clamped run count on success (0 on failure) so the surface can sync its inputs;\n * `onStarted` fires the moment run 1 is open, before the loop's own bookkeeping.\n */\nasync function axStartLoop(baseTitle, detail, requested, onStarted) {\n if (axLoop) return 0;\n const maxRuns = Math.min(AX_MAX_RUNS, Math.max(1, Math.round(Number.isFinite(requested) ? requested : 3)));\n const session = { baseTitle: baseTitle, detail: detail, maxRuns: maxRuns, run: 0, workItemId: null };\n axLoop = session;\n renderLoop();\n if (!(await openRun(session, 1))) {\n if (axLoop === session) { axLoop = null; renderLoop(); }\n return 0;\n }\n if (onStarted) onStarted(maxRuns);\n flash('ax-loop-status', requested > AX_MAX_RUNS ? 'Capped at ' + AX_MAX_RUNS + ' runs.' : 'Loop started.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop started: ' + baseTitle + ' (max ' + maxRuns + ' runs)',\n });\n return maxRuns;\n}\nasync function stopLoop() {\n const session = axLoop;\n if (!session) return;\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop stopped at run ' + session.run + ' of ' + session.maxRuns + '.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop stopped by human: ' + session.baseTitle + ' at run ' + session.run + ' of ' + session.maxRuns,\n });\n}\n/**\n * Advance when the CURRENT run's work item reaches done, observed from live AX\n * state. Double-advance guards: an in-flight flag, and clearing workItemId before\n * awaiting so a repeated state push cannot match the same finished run twice.\n */\nasync function maybeAdvanceLoop() {\n const session = axLoop;\n if (!session || axAdvancing || !session.workItemId) return;\n const current = axWorkItems().find((item) => item.id === session.workItemId);\n if (!current || current.status !== 'done') return;\n if (session.run >= session.maxRuns) {\n axLoop = null;\n renderLoop();\n axRenderSurface();\n flash('ax-loop-status', 'Loop finished — ' + session.maxRuns + ' of ' + session.maxRuns + ' runs done.');\n await axEmit('ax.event.record', {\n kind: 'steering',\n summary: 'Loop complete: ' + session.baseTitle + ' (' + session.maxRuns + ' runs)',\n });\n return;\n }\n axAdvancing = true;\n session.workItemId = null;\n try {\n if (!(await openRun(session, session.run + 1)) && axLoop === session) { axLoop = null; renderLoop(); }\n } finally {\n axAdvancing = false;\n }\n}\n\nfunction applyAxState() {\n axRenderSurface();\n renderLoop();\n void maybeAdvanceLoop();\n}\n\nconst AX_FLOW_TITLE = \"Release flow\";\nconst AX_FLOW_STEPS = Array.isArray(PMX_DATA.steps) ? PMX_DATA.steps : [];\nconst AX_FLOW_LOOP = PMX_DATA.loop || {};\n// Step index → the id of the work item bound to that step (created here, or\n// adopted from a materialize). Matching is by ID ONLY: work-item titles are not\n// unique, so a title match would happily bind the wrong item.\nconst axFlowWorkIds = AX_FLOW_STEPS.map(() => null);\n\nfunction stepWorkItem(index) {\n const id = axFlowWorkIds[index];\n return id ? axWorkItems().find((item) => item.id === id) || null : null;\n}\nfunction stepTone(item) {\n return item ? statusTone(item.status) : 'idle';\n}\n\n// ── render (live AX state only — no local mirror of step status) ──\nfunction renderFlow() {\n const host = el('ax-flow-diagram');\n host.textContent = '';\n let bound = 0;\n let done = 0;\n AX_FLOW_STEPS.forEach((step, index) => {\n if (index > 0) host.appendChild(elem('div', 'ax-flow-arrow', '→'));\n const item = stepWorkItem(index);\n if (item) bound += 1;\n if (item && item.status === 'done') done += 1;\n const card = elem('article', 'ax-flow-step tone-' + stepTone(item));\n card.setAttribute('data-step', String(index + 1));\n const head = elem('div', 'ax-work-head');\n head.appendChild(elem('span', 'ax-flow-num', 'step ' + (index + 1)));\n head.appendChild(elem('span', 'badge ' + stepTone(item), item ? item.status : 'not queued'));\n card.appendChild(head);\n card.appendChild(elem('h3', '', step.title));\n if (step.detail) card.appendChild(elem('p', 'small', step.detail));\n const foot = elem('div', 'ax-flow-foot');\n if (item) {\n const label = elem('label', 'small', 'status ');\n const select = document.createElement('select');\n select.setAttribute('data-status-for', item.id);\n for (const status of AX_STATUSES) {\n const option = document.createElement('option');\n option.value = status;\n option.textContent = status;\n if (status === item.status) option.selected = true;\n select.appendChild(option);\n }\n select.addEventListener('change', () => setStepStatus(index, select.value));\n label.appendChild(select);\n foot.appendChild(label);\n } else {\n const button = elem('button', '', 'Queue step');\n button.type = 'button';\n button.setAttribute('data-queue-step', String(index + 1));\n button.addEventListener('click', () => queueStep(index));\n foot.appendChild(button);\n }\n if (axLoop && item && axLoop.workItemId === item.id) foot.appendChild(elem('span', 'badge info', 'current run'));\n card.appendChild(foot);\n host.appendChild(card);\n });\n el('ax-flow-counts').textContent =\n AX_FLOW_STEPS.length + ' steps · ' + bound + ' queued · ' + done + ' done' +\n (AX_FLOW_LOOP.enabled ? ' · loops' : '');\n}\nfunction axRenderSurface() { renderFlow(); }\n\n// ── actions ──\nasync function queueStep(index) {\n const step = AX_FLOW_STEPS[index];\n if (!step || axFlowWorkIds[index]) return false;\n const result = await axEmit('ax.work.create', {\n title: AX_FLOW_TITLE + ' — ' + (index + 1) + '. ' + step.title,\n ...(step.detail ? { detail: step.detail } : {}),\n });\n if (!result || !result.ok || !result.primitive || !result.primitive.id) {\n flash('ax-flow-status', 'Step ' + (index + 1) + ' failed: ' + axFailure(result));\n return false;\n }\n axFlowWorkIds[index] = result.primitive.id;\n renderFlow();\n flash('ax-flow-status', 'Step ' + (index + 1) + ' queued ✓');\n return true;\n}\nasync function queueAllSteps() {\n for (let index = 0; index < AX_FLOW_STEPS.length; index++) {\n if (!axFlowWorkIds[index] && !(await queueStep(index))) return;\n }\n flash('ax-flow-status', 'Every step is queued ✓');\n}\nasync function setStepStatus(index, status) {\n const id = axFlowWorkIds[index];\n if (!id) return;\n const result = await axEmit('ax.work.update', { id, status });\n flash(\n 'ax-flow-status',\n result && result.ok ? 'Step ' + (index + 1) + ' → ' + status + ' ✓' : 'Update failed: ' + axFailure(result),\n );\n}\nasync function startFlowLoop() {\n if (axLoop) { flash('ax-flow-status', 'A loop is already running — stop it first.'); return; }\n await axStartLoop(AX_FLOW_TITLE, AX_FLOW_STEPS.map((step, i) => (i + 1) + '. ' + step.title).join('\\n'),\n Number(el('ax-loop-runs').value), (maxRuns) => { el('ax-loop-runs').value = String(maxRuns); });\n}\n/**\n * Hand the flow to the server, which owns the whole materialized shape. We send\n * TEXT ONLY — no node type, geometry, or node data — and adopt the work-item ids\n * it links to each step node so this panel and the board show the same status.\n */\nasync function materializeFlow() {\n const result = await axEmit('ax.flow.materialize', {\n title: AX_FLOW_TITLE,\n steps: AX_FLOW_STEPS.map((step) => (step.detail ? { title: step.title, detail: step.detail } : { title: step.title })),\n loop: { enabled: AX_FLOW_LOOP.enabled === true, maxRuns: Number(el('ax-loop-runs').value) || 3 },\n });\n if (!result || !result.ok || !result.primitive) {\n flash('ax-flow-materialize-status', 'Failed: ' + axFailure(result));\n return;\n }\n for (const step of result.primitive.steps) axFlowWorkIds[step.index - 1] = step.workItemId;\n renderFlow();\n flash(\n 'ax-flow-materialize-status',\n result.primitive.steps.length + ' step nodes on the board ✓' +\n (result.primitive.replacedNodeCount ? ' (replaced ' + result.primitive.replacedNodeCount + ')' : ''),\n );\n}\n\nel('ax-flow-queue-all').addEventListener('click', queueAllSteps);\nel('ax-flow-materialize').addEventListener('click', materializeFlow);\nel('ax-start-loop').addEventListener('click', startFlowLoop);\nel('ax-stop-loop').addEventListener('click', stopLoop);\nel('ax-send-steer').addEventListener('click', sendSteer);\nwindow.__pmxGetCopyJson = () => ({\n ...PMX_DATA,\n steps: AX_FLOW_STEPS.map((step, index) => {\n const item = stepWorkItem(index);\n return { ...step, workItemId: axFlowWorkIds[index], status: item ? item.status : null };\n }),\n loop: axLoop ? { title: axLoop.baseTitle, run: axLoop.run, maxRuns: axLoop.maxRuns } : null,\n});\nif (!window.PMX_AX || typeof window.PMX_AX.emit !== 'function') el('ax-offline').hidden = false;\n// Seeded snapshot first, then every live push. A loop NEVER auto-starts on load.\nwindow.addEventListener('pmx-ax-update', applyAxState);\napplyAxState();\n</script>\n</body>\n</html>",
|
|
5486
5491
|
"htmlPrimitive": "ax-flow",
|
|
5487
5492
|
"primitiveData": {
|
|
5488
5493
|
"note": "Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes.",
|
|
@@ -5528,8 +5533,8 @@
|
|
|
5528
5533
|
"axflow-primitive-ax-flow-step-3",
|
|
5529
5534
|
"axflow-primitive-ax-flow-step-4"
|
|
5530
5535
|
],
|
|
5531
|
-
"contentSummary": "Release flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.
|
|
5532
|
-
"agentSummary": "HTML primitive: ax-flow\nLive task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.\nRelease flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.
|
|
5536
|
+
"contentSummary": "Release flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board. AX bridge unavailable This flow is not running as an AX-enabled canvas node, so its controls are inert. Release flow Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes. loops back to step 1 · max 3 runs Run the flow Queue every step A step is queued once — after that its status control advances it (todo → in-progress → done). or run the whole flow as a bounded loop: max runs Start loop Steer Queue steering message Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent. Loop run 0 of 0 Each run is its own work item. When you mark the current run done , the surface opens the next run and steers again — unt...",
|
|
5537
|
+
"agentSummary": "HTML primitive: ax-flow\nLive task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.\nRelease flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board. AX bridge unavailable This flow is not running as an AX-enabled canvas node, so its controls are inert. Release flow Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes. loops back to step 1 · max 3 runs Run the flow Queue every step A step is queued once — after that its status control advances it (todo → in-progress → done). or run the whole flow as a bounded loop: max runs Start loop Steer Queue steering message Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent. Loop run 0 of 0 Each run is its own work item. When you mark the current run done , the surface opens the next run and steers again — unt..."
|
|
5533
5538
|
},
|
|
5534
5539
|
"createdBy": {
|
|
5535
5540
|
"actor": "agent",
|
|
@@ -5545,7 +5550,7 @@
|
|
|
5545
5550
|
"freshnessStartedAt": "2026-08-01T09:00:00.000Z",
|
|
5546
5551
|
"kind": "html",
|
|
5547
5552
|
"title": "Release flow",
|
|
5548
|
-
"content": "HTML primitive: ax-flow\nLive task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.\nRelease flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.
|
|
5553
|
+
"content": "HTML primitive: ax-flow\nLive task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board.\nRelease flow PMX HTML primitive / ax-flow Release flow Live task-flow control panel: a step diagram with an explicit loop-back, per-step status, and a one-click materialize onto the board. AX bridge unavailable This flow is not running as an AX-enabled canvas node, so its controls are inert. Release flow Each step is a canvas AX work item. Materialize drops the same flow onto the board as real nodes. loops back to step 1 · max 3 runs Run the flow Queue every step A step is queued once — after that its status control advances it (todo → in-progress → done). or run the whole flow as a bounded loop: max runs Start loop Steer Queue steering message Queued for the agent's next turn — steering is recorded on the timeline, it does not wake a running agent. Loop run 0 of 0 Each run is its own work item. When you mark the current run done , the surface opens the next run and steers again — unt...",
|
|
5549
5554
|
"path": null,
|
|
5550
5555
|
"url": null,
|
|
5551
5556
|
"surfaceUrl": "/api/canvas/surface/primitive-ax-flow",
|
|
@@ -5556,7 +5561,7 @@
|
|
|
5556
5561
|
"type": "markdown",
|
|
5557
5562
|
"position": {
|
|
5558
5563
|
"x": 0,
|
|
5559
|
-
"y":
|
|
5564
|
+
"y": 4410
|
|
5560
5565
|
},
|
|
5561
5566
|
"size": {
|
|
5562
5567
|
"width": 1240,
|
|
@@ -5566,6 +5571,7 @@
|
|
|
5566
5571
|
"collapsed": false,
|
|
5567
5572
|
"pinned": false,
|
|
5568
5573
|
"data": {
|
|
5574
|
+
"display": "heading",
|
|
5569
5575
|
"title": "⑧ Agent at work",
|
|
5570
5576
|
"content": "A task caught mid-flight: one work item per step, status chips on the nodes, controls that steer the agent.",
|
|
5571
5577
|
"strictSize": true,
|
|
@@ -5596,10 +5602,10 @@
|
|
|
5596
5602
|
"type": "markdown",
|
|
5597
5603
|
"position": {
|
|
5598
5604
|
"x": 0,
|
|
5599
|
-
"y":
|
|
5605
|
+
"y": 4610
|
|
5600
5606
|
},
|
|
5601
5607
|
"size": {
|
|
5602
|
-
"width":
|
|
5608
|
+
"width": 440,
|
|
5603
5609
|
"height": 240
|
|
5604
5610
|
},
|
|
5605
5611
|
"zIndex": 1,
|
|
@@ -5680,11 +5686,11 @@
|
|
|
5680
5686
|
"id": "axflow-primitive-ax-flow-step-2",
|
|
5681
5687
|
"type": "markdown",
|
|
5682
5688
|
"position": {
|
|
5683
|
-
"x":
|
|
5684
|
-
"y":
|
|
5689
|
+
"x": 488,
|
|
5690
|
+
"y": 4610
|
|
5685
5691
|
},
|
|
5686
5692
|
"size": {
|
|
5687
|
-
"width":
|
|
5693
|
+
"width": 440,
|
|
5688
5694
|
"height": 240
|
|
5689
5695
|
},
|
|
5690
5696
|
"zIndex": 1,
|
|
@@ -5730,11 +5736,11 @@
|
|
|
5730
5736
|
"id": "axflow-primitive-ax-flow-step-3",
|
|
5731
5737
|
"type": "markdown",
|
|
5732
5738
|
"position": {
|
|
5733
|
-
"x":
|
|
5734
|
-
"y":
|
|
5739
|
+
"x": 976,
|
|
5740
|
+
"y": 4610
|
|
5735
5741
|
},
|
|
5736
5742
|
"size": {
|
|
5737
|
-
"width":
|
|
5743
|
+
"width": 440,
|
|
5738
5744
|
"height": 240
|
|
5739
5745
|
},
|
|
5740
5746
|
"zIndex": 1,
|
|
@@ -5780,11 +5786,11 @@
|
|
|
5780
5786
|
"id": "axflow-primitive-ax-flow-step-4",
|
|
5781
5787
|
"type": "markdown",
|
|
5782
5788
|
"position": {
|
|
5783
|
-
"x":
|
|
5784
|
-
"y":
|
|
5789
|
+
"x": 1464,
|
|
5790
|
+
"y": 4610
|
|
5785
5791
|
},
|
|
5786
5792
|
"size": {
|
|
5787
|
-
"width":
|
|
5793
|
+
"width": 440,
|
|
5788
5794
|
"height": 240
|
|
5789
5795
|
},
|
|
5790
5796
|
"zIndex": 1,
|
|
@@ -5830,8 +5836,8 @@
|
|
|
5830
5836
|
"id": "agent-note",
|
|
5831
5837
|
"type": "markdown",
|
|
5832
5838
|
"position": {
|
|
5833
|
-
"x":
|
|
5834
|
-
"y":
|
|
5839
|
+
"x": 1952,
|
|
5840
|
+
"y": 4610
|
|
5835
5841
|
},
|
|
5836
5842
|
"size": {
|
|
5837
5843
|
"width": 520,
|