pmx-canvas 0.10.0 → 0.12.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/extension.mjs +3 -1
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +688 -128
- package/CHANGELOG.md +69 -0
- package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
- package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
- package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
- package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1785 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
- package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
- package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-cwtsds5d.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-b4xc9tpz.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-ahp3yxxs.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-nappnay7.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-a428syda.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-nspsqebj.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-8y6177fh.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-vym028wn.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-jddqb8sy.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-bjwbpkrn.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-j6ph6hcx.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-p09w8jej.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-j975ycq5.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-sxp6hjn8.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-4tv230y2.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-mqgmnw9g.js +106 -0
- package/dist/canvas/chunks/index-037z5dz2.js +232 -0
- package/dist/canvas/chunks/index-081n3har.js +207 -0
- package/dist/canvas/chunks/index-5n03dmb9.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-60pgzdqe.js +157 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
- package/dist/canvas/chunks/index-8rjes9r6.js +2 -0
- package/dist/canvas/chunks/index-9022d1n9.js +16 -0
- package/dist/canvas/chunks/index-9b02042t.js +2 -0
- package/dist/canvas/chunks/index-ap4q4vez.js +2 -0
- package/dist/canvas/chunks/index-dyp2nhd3.js +2 -0
- package/dist/canvas/chunks/index-frvta4tm.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-h9ycj8n2.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-mergmj97.js +2 -0
- package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
- package/dist/canvas/chunks/index-nz8qbd1p.js +2 -0
- package/dist/canvas/chunks/index-qhfrazv3.js +2 -0
- package/dist/canvas/chunks/index-v119nznk.js +4 -0
- package/dist/canvas/chunks/index-vpt37m3d.js +89 -0
- package/dist/canvas/chunks/index-y9ma6azy.js +2 -0
- package/dist/canvas/chunks/index-zx0j3a2z.js +179 -0
- package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-qv97qq6a.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-sv67zats.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-k2pfam6p.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-83r0fdce.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-vg2newm1.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-k2h7zc5n.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-hg169p1v.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-bxtvsq0b.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-njyhcp4p.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-2z6v09gm.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-15mzam0e.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-rbshyd2c.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-sg26xwts.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-6eahp98e.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-ze5db10t.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-vnp7jyy8.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-8nxg8wqt.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-nj921e7s.js +7 -0
- package/dist/canvas/global.css +483 -230
- package/dist/canvas/index.js +158 -219
- package/dist/canvas/mcp-app.html +4301 -548
- 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/BoardMoveDialog.d.ts +2 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
- 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/PromptNode.d.ts +1 -0
- 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/WorkCard.d.ts +7 -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-ax-surface-bridge.d.ts +1 -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 +18 -1
- package/dist/types/client/state/human-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +5 -2
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/client/state/relation-store.d.ts +11 -0
- package/dist/types/client/types.d.ts +1 -1
- package/dist/types/json-render/catalog.d.ts +2 -2
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/server/agent-presence.d.ts +1 -0
- package/dist/types/server/ax-interaction.d.ts +1 -0
- package/dist/types/server/ax-state-manager.d.ts +10 -2
- package/dist/types/server/board-local.d.ts +4 -0
- package/dist/types/server/board-move.d.ts +38 -0
- package/dist/types/server/canvas-db.d.ts +12 -2
- package/dist/types/server/canvas-operations.d.ts +5 -6
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +86 -20
- package/dist/types/server/card-relocation.d.ts +6 -0
- package/dist/types/server/context-reads.d.ts +3 -1
- package/dist/types/server/human-presence.d.ts +1 -1
- package/dist/types/server/index.d.ts +26 -10
- package/dist/types/server/intent-registry.d.ts +8 -4
- package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
- package/dist/types/server/mutation-history.d.ts +7 -5
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/trace-manager.d.ts +1 -0
- package/dist/types/shared/ax-kinds.d.ts +6 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +8 -9
- package/dist/types/shared/human-presence.d.ts +1 -0
- package/dist/types/shared/lifecycle-status.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/RELEASE.md +21 -14
- package/docs/agent-plugin.md +16 -2
- package/docs/amp-plugin.md +271 -0
- package/docs/cli.md +42 -3
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/GuestName.dc.html +50 -0
- package/docs/design/PluginLogo.svg +7 -0
- 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/WorkCards.dc.html +39 -0
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +17 -1
- package/docs/design/plugin-logo.png +0 -0
- package/docs/design.md +280 -12
- package/docs/evals/agents-md-sections.md +38 -0
- package/docs/http-api.md +179 -11
- package/docs/mcp.md +69 -9
- package/docs/node-types.md +109 -4
- package/docs/plans/plan-009-tech-debt-backlog.md +3 -3
- package/docs/plans/plan-019-post-0.9-roadmap.md +327 -31
- package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
- package/docs/product-vision-2026-09.md +42 -17
- package/docs/sdk.md +18 -0
- package/package.json +7 -4
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +2 -2
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +688 -128
- package/skills/data-analysis/SKILL.md +13 -26
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +3 -0
- package/skills/pmx-canvas/SKILL.md +232 -849
- package/skills/pmx-canvas/evals/evals.json +26 -3
- package/skills/pmx-canvas/references/full-reference.md +89 -24
- package/skills/pmx-canvas/references/html-primitives.md +7 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
- package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/skills/pmx-canvas-testing/SKILL.md +7 -5
- package/skills/published-consumer-e2e/SKILL.md +25 -8
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -77
- package/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/web-artifacts-builder/SKILL.md +6 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
- package/src/cli/commands/apps.ts +1 -0
- package/src/cli/commands/boards.ts +30 -0
- package/src/client/App.tsx +20 -4
- package/src/client/canvas/AttentionHistory.tsx +10 -2
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/BoardMoveDialog.tsx +225 -0
- package/src/client/canvas/CanvasNode.tsx +79 -49
- package/src/client/canvas/CanvasViewport.tsx +42 -14
- package/src/client/canvas/ContextMenu.tsx +97 -15
- package/src/client/canvas/EdgeEditor.tsx +149 -0
- package/src/client/canvas/EdgeLayer.tsx +441 -174
- package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -2
- package/src/client/canvas/GroupNavigation.tsx +85 -0
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/RelationsPanel.tsx +9 -0
- package/src/client/canvas/SelectionBar.tsx +21 -0
- package/src/client/canvas/SessionPanel.tsx +35 -1
- package/src/client/canvas/TextPrompt.tsx +18 -1
- package/src/client/canvas/TopBar.tsx +54 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +2 -2
- 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 +302 -1465
- 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 +14 -24
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +44 -17
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +34 -7
- package/src/client/nodes/StatusSummary.tsx +16 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +273 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/WorkCard.tsx +175 -0
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
- 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/attention-bridge.ts +5 -0
- package/src/client/state/attention-store.ts +1 -0
- package/src/client/state/canvas-store.ts +202 -26
- package/src/client/state/human-store.ts +22 -2
- package/src/client/state/intent-bridge.ts +20 -5
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/relation-store.ts +127 -1
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +483 -230
- package/src/client/theme/surface-theme.css +6 -6
- package/src/client/types.ts +1 -0
- 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/server.ts +13 -3
- package/src/server/agent-presence.ts +43 -23
- package/src/server/ax-flow-loop.ts +6 -0
- package/src/server/ax-gate-ttl.ts +8 -1
- package/src/server/ax-interaction.ts +16 -10
- package/src/server/ax-state-manager.ts +17 -4
- package/src/server/ax-wait.ts +20 -6
- package/src/server/board-export.ts +54 -30
- package/src/server/board-local.ts +20 -0
- package/src/server/board-move.ts +297 -0
- package/src/server/canvas-db.ts +249 -158
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +139 -109
- package/src/server/canvas-schema.ts +21 -0
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +797 -159
- package/src/server/card-relocation.ts +53 -0
- package/src/server/code-graph.ts +4 -3
- package/src/server/context-reads.ts +83 -55
- package/src/server/demo-state.json +140 -114
- package/src/server/file-watcher.ts +30 -10
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/human-presence.ts +18 -5
- package/src/server/index.ts +54 -24
- package/src/server/intent-registry.ts +43 -5
- package/src/server/knowledge-lifecycle.ts +32 -9
- package/src/server/lifecycle-status.ts +29 -1
- package/src/server/mcp-app-runtime.ts +26 -7
- package/src/server/mutation-history.ts +37 -8
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/mcp-contracts.ts +32 -2
- package/src/server/operations/mcp.ts +36 -9
- package/src/server/operations/ops/annotation.ts +22 -1
- package/src/server/operations/ops/ax-await.ts +1 -0
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/batch.ts +25 -11
- package/src/server/operations/ops/boards.ts +45 -6
- package/src/server/operations/ops/nodes.ts +70 -0
- package/src/server/operations/ops/query.ts +32 -3
- package/src/server/operations/ops/snapshots.ts +1 -0
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +49 -4
- package/src/server/server.ts +69 -75
- package/src/server/tour-control.ts +97 -12
- package/src/server/trace-manager.ts +4 -0
- package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
- package/src/shared/agent-context.ts +7 -1
- package/src/shared/auto-arrange.ts +5 -4
- package/src/shared/ax-kinds.ts +13 -0
- package/src/shared/ax-surface-protocol.ts +20 -9
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/human-presence.ts +1 -0
- package/src/shared/lifecycle-status.ts +2 -0
- package/src/shared/tour.ts +8 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
|
3
2
|
import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
4
3
|
import { canvasThemeScheme } from '../../shared/themes.js';
|
|
5
4
|
import type { CanvasNodeState } from '../types';
|
|
6
5
|
import { axSurfaceState, canvasTheme } from '../state/canvas-store';
|
|
7
|
-
import {
|
|
8
|
-
import { iframeMode } from '../state/iframe-mode';
|
|
6
|
+
import { RefreshingViewerFrame } from './ViewerFrame';
|
|
9
7
|
import { shouldContentFitIframeNode } from '../canvas/auto-fit';
|
|
10
8
|
import { ExtAppFrame } from './ExtAppFrame';
|
|
11
9
|
import { useAxSurfaceBridge } from './use-ax-surface-bridge';
|
|
@@ -16,6 +14,7 @@ import { isHostedWorkbench } from '../state/workbench-transport';
|
|
|
16
14
|
|
|
17
15
|
function withViewerParams(
|
|
18
16
|
url: string,
|
|
17
|
+
theme: string,
|
|
19
18
|
expanded: boolean,
|
|
20
19
|
specVersion?: number,
|
|
21
20
|
axToken?: string,
|
|
@@ -28,9 +27,7 @@ function withViewerParams(
|
|
|
28
27
|
try {
|
|
29
28
|
const hosted = isHostedWorkbench() && url.startsWith('/');
|
|
30
29
|
const resolved = new URL(url, hosted ? 'https://pmx.invalid' : window.location.origin);
|
|
31
|
-
|
|
32
|
-
// themes (sepia → light, midnight/arctic/ember/forest → dark) to a scheme.
|
|
33
|
-
resolved.searchParams.set('theme', canvasThemeScheme(canvasTheme.value));
|
|
30
|
+
resolved.searchParams.set('theme', theme);
|
|
34
31
|
if (expanded) resolved.searchParams.set('display', 'expanded');
|
|
35
32
|
// Streaming json-render nodes bump specVersion as patches accumulate; including
|
|
36
33
|
// it in the src reloads the iframe so it re-reads the latest accumulated spec.
|
|
@@ -61,139 +58,6 @@ export function isSameOriginFrameDocumentUrl(url: string, origin = window.locati
|
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
type ViewerFrameSource = { src?: string; srcdoc?: string };
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Keep the last painted viewer in front while its replacement navigates. Viewer
|
|
68
|
-
* specs are immutable documents, so updates normally change `?v=` and reload the
|
|
69
|
-
* iframe; putting the new document behind the old one conceals Chromium's white
|
|
70
|
-
* navigation paint without suppressing rendering. Wait for restore and render commit,
|
|
71
|
-
* then two animation frames before the old viewer is removed.
|
|
72
|
-
*/
|
|
73
|
-
export function RefreshingViewerFrame({
|
|
74
|
-
source,
|
|
75
|
-
iframeRef,
|
|
76
|
-
onLoad,
|
|
77
|
-
onDocumentLoad,
|
|
78
|
-
title,
|
|
79
|
-
className = 'mcp-app-frame',
|
|
80
|
-
sandbox = 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox',
|
|
81
|
-
allow = 'clipboard-read; clipboard-write',
|
|
82
|
-
tabIndex,
|
|
83
|
-
}: {
|
|
84
|
-
source: ViewerFrameSource;
|
|
85
|
-
iframeRef: RefObject<HTMLIFrameElement>;
|
|
86
|
-
onLoad: () => void;
|
|
87
|
-
onDocumentLoad?: (frame: HTMLIFrameElement, signal: AbortSignal) => void | Promise<boolean | 'cancelled' | void>;
|
|
88
|
-
title: string;
|
|
89
|
-
className?: string;
|
|
90
|
-
sandbox?: string;
|
|
91
|
-
allow?: string;
|
|
92
|
-
tabIndex?: number;
|
|
93
|
-
}) {
|
|
94
|
-
const sourceKey = source.src ?? source.srcdoc ?? '';
|
|
95
|
-
const [painted, setPainted] = useState({ key: sourceKey, source, attempt: 0 });
|
|
96
|
-
const [pending, setPending] = useState<{ key: string; source: ViewerFrameSource; attempt: number } | null>(null);
|
|
97
|
-
const promotionRef = useRef(0);
|
|
98
|
-
const readiness = useRef(new Set<AbortController>());
|
|
99
|
-
|
|
100
|
-
useLayoutEffect(() => {
|
|
101
|
-
promotionRef.current += 1;
|
|
102
|
-
for (const controller of readiness.current) controller.abort();
|
|
103
|
-
readiness.current.clear();
|
|
104
|
-
if (!sourceKey || sourceKey === painted.key) {
|
|
105
|
-
setPending(null);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (!painted.key) {
|
|
109
|
-
setPainted({ key: sourceKey, source, attempt: 0 });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setPending({ key: sourceKey, source, attempt: 0 });
|
|
113
|
-
}, [sourceKey, painted.key]);
|
|
114
|
-
|
|
115
|
-
useEffect(
|
|
116
|
-
() => () => {
|
|
117
|
-
promotionRef.current += 1;
|
|
118
|
-
for (const controller of readiness.current) controller.abort();
|
|
119
|
-
readiness.current.clear();
|
|
120
|
-
},
|
|
121
|
-
[],
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const frame = (entry: { key: string; source: ViewerFrameSource; attempt: number }, isPending: boolean) => (
|
|
125
|
-
<iframe
|
|
126
|
-
key={`${entry.key}:${entry.attempt}`}
|
|
127
|
-
ref={isPending ? undefined : iframeRef}
|
|
128
|
-
{...entry.source}
|
|
129
|
-
class={className}
|
|
130
|
-
sandbox={sandbox}
|
|
131
|
-
allow={allow}
|
|
132
|
-
tabIndex={tabIndex}
|
|
133
|
-
inert={isPending}
|
|
134
|
-
aria-hidden={isPending ? true : undefined}
|
|
135
|
-
loading={iframeMode.value === 'srcdoc' ? undefined : 'lazy'}
|
|
136
|
-
onLoad={async (event) => {
|
|
137
|
-
// Seed runtime state while a replacement is still behind the painted
|
|
138
|
-
// frame, and wait until that document has committed its rendered UI.
|
|
139
|
-
const token = isPending ? ++promotionRef.current : promotionRef.current;
|
|
140
|
-
const loadedFrame = event.currentTarget;
|
|
141
|
-
const controller = new AbortController();
|
|
142
|
-
readiness.current.add(controller);
|
|
143
|
-
const ready = onDocumentLoad?.(loadedFrame, controller.signal);
|
|
144
|
-
const loaded = ready ? await ready : undefined;
|
|
145
|
-
readiness.current.delete(controller);
|
|
146
|
-
if (promotionRef.current !== token || loaded === 'cancelled') return;
|
|
147
|
-
if (loaded === false) {
|
|
148
|
-
if (entry.attempt === 0) {
|
|
149
|
-
const retry = { ...entry, attempt: 1 };
|
|
150
|
-
if (isPending) setPending(retry);
|
|
151
|
-
else setPainted(retry);
|
|
152
|
-
} else {
|
|
153
|
-
if (isPending) setPending(null);
|
|
154
|
-
showToast(
|
|
155
|
-
'remove',
|
|
156
|
-
'Viewer could not load',
|
|
157
|
-
`${isPending ? 'Keeping the last view. ' : ''}Reload to retry.`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (!isPending) {
|
|
163
|
-
onLoad();
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
requestAnimationFrame(() =>
|
|
167
|
-
requestAnimationFrame(() => {
|
|
168
|
-
if (promotionRef.current !== token) return;
|
|
169
|
-
iframeRef.current = loadedFrame;
|
|
170
|
-
setPainted(entry);
|
|
171
|
-
setPending(null);
|
|
172
|
-
onLoad();
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
}}
|
|
176
|
-
style={{
|
|
177
|
-
position: 'absolute',
|
|
178
|
-
inset: 0,
|
|
179
|
-
width: '100%',
|
|
180
|
-
height: '100%',
|
|
181
|
-
visibility: 'visible',
|
|
182
|
-
zIndex: isPending ? 0 : 1,
|
|
183
|
-
background: 'var(--c-panel-soft)',
|
|
184
|
-
}}
|
|
185
|
-
title={title}
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<div class="mcp-app-frame-stack">
|
|
191
|
-
{frame(painted, false)}
|
|
192
|
-
{pending && frame(pending, true)}
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
61
|
export function McpAppNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
198
62
|
if (node.data.mode === 'ext-app') {
|
|
199
63
|
return <ExtAppFrame node={node} expanded={expanded} />;
|
|
@@ -223,7 +87,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
223
87
|
// overlay frame (fill-down), not sit at its intrinsic in-canvas height.
|
|
224
88
|
const contentFit = shouldContentFitIframeNode(node) && !expanded;
|
|
225
89
|
const frameToken = useMemo(() => (contentFit ? `frame-${crypto.randomUUID()}` : ''), [contentFit]);
|
|
226
|
-
useIframeContentHeight(node, iframeRef, frameToken);
|
|
90
|
+
const measureContent = useIframeContentHeight(node, iframeRef, frameToken);
|
|
227
91
|
|
|
228
92
|
// AX emits forwarded by the json-render viewer — the shared sandboxed-surface
|
|
229
93
|
// trust boundary (M2).
|
|
@@ -257,15 +121,38 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
257
121
|
() => (isJsonViewer ? `ui-${crypto.randomUUID()}` : ''),
|
|
258
122
|
[isJsonViewer, specVersion, node.data.url],
|
|
259
123
|
);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
124
|
+
const theme = canvasTheme.value;
|
|
125
|
+
const urlTheme = isJsonViewer ? null : canvasThemeScheme(theme);
|
|
126
|
+
const pushTheme = () => {
|
|
127
|
+
if (!isJsonViewer) return;
|
|
128
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
129
|
+
{
|
|
130
|
+
source: HTML_SURFACE_PUSH_SOURCE,
|
|
131
|
+
type: 'theme-update',
|
|
132
|
+
token: stateToken,
|
|
133
|
+
theme: node.data.theme ?? theme,
|
|
134
|
+
title: node.data.title,
|
|
135
|
+
},
|
|
136
|
+
'*',
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
useEffect(pushTheme, [theme, stateToken, node.data.theme, node.data.title]);
|
|
140
|
+
// Only JSON/graph understand theme messages and retain drafts across themes.
|
|
141
|
+
// Generic viewers consume a dark/light query and navigate when it changes.
|
|
142
|
+
const url = useMemo(
|
|
143
|
+
() =>
|
|
144
|
+
withViewerParams(
|
|
145
|
+
(node.data.url as string) || '',
|
|
146
|
+
urlTheme ?? theme,
|
|
147
|
+
expanded,
|
|
148
|
+
specVersion,
|
|
149
|
+
axToken || undefined,
|
|
150
|
+
isAxViewer ? node.id : undefined,
|
|
151
|
+
frameToken || undefined,
|
|
152
|
+
contentFit,
|
|
153
|
+
stateToken || undefined,
|
|
154
|
+
),
|
|
155
|
+
[node.data.url, urlTheme, expanded, specVersion, axToken, node.id, frameToken, contentFit, stateToken],
|
|
269
156
|
);
|
|
270
157
|
// src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
|
|
271
158
|
// External viewer URLs always stay src — the hook passes them through.
|
|
@@ -295,7 +182,9 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
295
182
|
>
|
|
296
183
|
{url}
|
|
297
184
|
</a>
|
|
298
|
-
{sourceServer &&
|
|
185
|
+
{sourceServer && (
|
|
186
|
+
<div style={{ color: 'var(--c-dim)', fontSize: 'var(--fs-meta)' }}>Source: {sourceServer}</div>
|
|
187
|
+
)}
|
|
299
188
|
</div>
|
|
300
189
|
);
|
|
301
190
|
}
|
|
@@ -313,7 +202,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
313
202
|
<div
|
|
314
203
|
style={{
|
|
315
204
|
padding: '4px 8px',
|
|
316
|
-
fontSize: '
|
|
205
|
+
fontSize: 'var(--fs-meta)',
|
|
317
206
|
background: 'color-mix(in srgb, var(--c-warn) 10%, transparent)',
|
|
318
207
|
color: 'var(--c-warn)',
|
|
319
208
|
borderBottom: '1px solid color-mix(in srgb, var(--c-warn) 15%, transparent)',
|
|
@@ -328,11 +217,16 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
328
217
|
<RefreshingViewerFrame
|
|
329
218
|
source={surfaceFrame}
|
|
330
219
|
iframeRef={iframeRef}
|
|
220
|
+
allow="clipboard-read; clipboard-write"
|
|
331
221
|
onDocumentLoad={(frame, signal) => {
|
|
332
222
|
if (stateToken && frame.contentWindow)
|
|
333
|
-
return restoreViewerRuntimeState(node
|
|
223
|
+
return restoreViewerRuntimeState(node, stateToken, frame.contentWindow, signal);
|
|
224
|
+
}}
|
|
225
|
+
onLoad={() => {
|
|
226
|
+
measureContent();
|
|
227
|
+
pushAxState();
|
|
228
|
+
pushTheme();
|
|
334
229
|
}}
|
|
335
|
-
onLoad={pushAxState}
|
|
336
230
|
title={`MCP App: ${sourceServer}`}
|
|
337
231
|
/>
|
|
338
232
|
</div>
|
|
@@ -1,82 +1,93 @@
|
|
|
1
|
-
import { useEffect,
|
|
2
|
-
import {
|
|
3
|
-
import { canvasTheme } from '../state/canvas-store';
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { renderMermaid } from '../mermaid-render';
|
|
3
|
+
import { canvasTheme, nodes, updateNode } from '../state/canvas-store';
|
|
4
|
+
import { pushCanvasUpdate } from '../state/intent-bridge';
|
|
5
|
+
import { computeContentGrowHeight, contentFitPosition } from '../canvas/auto-fit';
|
|
4
6
|
import type { CanvasNodeState } from '../types';
|
|
5
|
-
import { nodeSurfaceUrl, surfaceContentHash } from './surface-url';
|
|
6
|
-
import { useIframeContentHeight } from './use-iframe-content-height';
|
|
7
|
-
import { useSurfaceFrame } from './use-surface-frame';
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* Mermaid diagram node — a display-only iframe surface. The server renders the
|
|
11
|
-
* surface document (escaped diagram source + /canvas/mermaid-entry.js) at
|
|
12
|
-
* /api/canvas/surface/:id; the iframe and "Open as site" share that one render
|
|
13
|
-
* path, exactly like html nodes. Minimal subset of HtmlNode: theme push and
|
|
14
|
-
* content-height growth, no AX bridge, no presentation mode.
|
|
15
|
-
*/
|
|
16
8
|
export function MermaidNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
|
|
21
|
-
// Per-mount nonce for the content-height reporter (node grows to fit content).
|
|
22
|
-
const frameToken = useMemo(() => `frame-${crypto.randomUUID()}`, []);
|
|
9
|
+
const container = useRef<HTMLDivElement>(null);
|
|
10
|
+
const [error, setError] = useState('');
|
|
11
|
+
const [attempt, setAttempt] = useState(0);
|
|
23
12
|
const source = typeof node.data.content === 'string' ? node.data.content : '';
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const surfaceSrc = useMemo(
|
|
31
|
-
() => (source ? nodeSurfaceUrl(node.id, { theme, themeToken, v, frameToken }) : ''),
|
|
32
|
-
[source, themeToken, v, node.id, frameToken],
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
|
|
36
|
-
const surfaceFrame = useSurfaceFrame(surfaceSrc);
|
|
13
|
+
const theme = canvasTheme.value;
|
|
14
|
+
const fit = node.data.fit !== 'none';
|
|
15
|
+
// Fit is layout, not content: it resizes the painted SVG in place. Re-rendering
|
|
16
|
+
// on a fit change replaced the element and left a detached SVG mid-update.
|
|
17
|
+
const fitRef = useRef(fit);
|
|
18
|
+
fitRef.current = fit;
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
const fitDiagram = () => {
|
|
21
|
+
const element = container.current;
|
|
22
|
+
const svg = element?.querySelector('svg');
|
|
23
|
+
const box = svg?.viewBox.baseVal;
|
|
24
|
+
if (!element || !svg || !box?.width || !box.height) return;
|
|
25
|
+
const scale = fitRef.current ? Math.min(1, element.clientWidth / box.width, element.clientHeight / box.height) : 1;
|
|
26
|
+
svg.style.maxWidth = 'none';
|
|
27
|
+
svg.style.width = `${box.width * scale}px`;
|
|
28
|
+
svg.style.height = `${box.height * scale}px`;
|
|
29
|
+
svg.style.margin = fitRef.current ? '0 auto' : '0';
|
|
30
|
+
};
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
32
|
+
const growToDiagram = () => {
|
|
33
|
+
const element = container.current;
|
|
34
|
+
const box = element?.querySelector('svg')?.viewBox.baseVal;
|
|
35
|
+
const current = nodes.value.get(node.id);
|
|
36
|
+
if (expanded || !element || !box?.width || !current) return;
|
|
37
|
+
const naturalHeight = box.height * (fitRef.current ? Math.min(1, element.clientWidth / box.width) : 1);
|
|
38
|
+
const card = element.closest<HTMLElement>('.canvas-node')!;
|
|
39
|
+
const height = computeContentGrowHeight(current, naturalHeight, card.offsetHeight - element.clientHeight);
|
|
40
|
+
if (height === null) return;
|
|
41
|
+
const position = contentFitPosition(current, height, Array.from(nodes.value.values()));
|
|
42
|
+
const patch = { size: { width: current.size.width, height }, position };
|
|
43
|
+
updateNode(node.id, patch);
|
|
44
|
+
void pushCanvasUpdate([{ id: node.id, ...patch }], { recordHistory: false });
|
|
52
45
|
};
|
|
53
46
|
|
|
54
|
-
useEffect(
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let cancelled = false;
|
|
49
|
+
const element = container.current;
|
|
50
|
+
if (!element || !source) return;
|
|
51
|
+
const observer = new ResizeObserver(fitDiagram);
|
|
52
|
+
observer.observe(element);
|
|
53
|
+
const render = async () => {
|
|
54
|
+
try {
|
|
55
|
+
const svg = await renderMermaid(source);
|
|
56
|
+
if (cancelled) return;
|
|
57
|
+
element.innerHTML = svg;
|
|
58
|
+
setError('');
|
|
59
|
+
element.querySelector('svg')?.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
60
|
+
fitDiagram();
|
|
61
|
+
growToDiagram();
|
|
62
|
+
} catch (failure) {
|
|
63
|
+
if (!cancelled) setError(failure instanceof Error ? failure.message : String(failure));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
void render();
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
};
|
|
71
|
+
}, [source, theme, expanded, attempt]);
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
// Same commit as data-fit, so the attribute and the painted size never disagree.
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
fitDiagram();
|
|
76
|
+
growToDiagram();
|
|
77
|
+
}, [fit]);
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
// (see HtmlNode for the full rationale); the surface route sends a matching
|
|
62
|
-
// CSP sandbox header.
|
|
79
|
+
if (!source) return <div class="viewer-placeholder">No diagram source set</div>;
|
|
63
80
|
return (
|
|
64
|
-
<
|
|
65
|
-
ref={
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
border: 'none',
|
|
76
|
-
background: 'var(--c-bg)',
|
|
77
|
-
borderRadius: '6px',
|
|
78
|
-
display: 'block',
|
|
79
|
-
}}
|
|
80
|
-
/>
|
|
81
|
+
<div class="mermaid-viewer" data-fit={fit ? 'contain' : 'none'}>
|
|
82
|
+
<div ref={container} class="mermaid-diagram" />
|
|
83
|
+
{error && (
|
|
84
|
+
<div class="viewer-fallback" role="status">
|
|
85
|
+
<span>{error}</span>
|
|
86
|
+
<button type="button" onClick={() => setAttempt(attempt + 1)}>
|
|
87
|
+
Retry
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
81
92
|
);
|
|
82
93
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
2
2
|
import { IconClose } from '../icons';
|
|
3
|
+
import { activeBoardId } from '../state/boards-store';
|
|
3
4
|
import { updateNodeData } from '../state/canvas-store';
|
|
4
5
|
import { fetchSlashCommands, renderMarkdown, submitCanvasPrompt, submitThreadReply } from '../state/intent-bridge';
|
|
5
6
|
import type { CanvasNodeState } from '../types';
|
|
6
7
|
import { sanitizeMarkdownHtml } from './markdown-sanitize';
|
|
7
8
|
|
|
9
|
+
// Multiple inline/focus renderers may own inputs for the same qualified card.
|
|
10
|
+
const unsentInputs = new Set<{ boardId: string | null; nodeId: string; draft: string; reply: string }>();
|
|
11
|
+
export function hasUnsavedPrompt(boardId: string, nodeId: string): boolean {
|
|
12
|
+
return [...unsentInputs].some((input) => input.boardId === boardId && input.nodeId === nodeId);
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
// Cached slash commands — fetched once on first use.
|
|
9
16
|
let cachedCommands: Array<{ name: string; description: string }> | null = null;
|
|
10
17
|
async function getCommands() {
|
|
@@ -57,7 +64,7 @@ function ContextBadge({ count }: { count: number }) {
|
|
|
57
64
|
padding: '4px 8px',
|
|
58
65
|
fontSize: '11px',
|
|
59
66
|
color: 'var(--c-accent)',
|
|
60
|
-
background: '
|
|
67
|
+
background: 'color-mix(in srgb, var(--c-pin) 8%, transparent)',
|
|
61
68
|
borderRadius: 'var(--radius-sm)',
|
|
62
69
|
flexShrink: 0,
|
|
63
70
|
}}
|
|
@@ -75,7 +82,7 @@ function ErrorBanner({ message, onDismiss }: { message: string | null; onDismiss
|
|
|
75
82
|
padding: '4px 8px',
|
|
76
83
|
fontSize: '11px',
|
|
77
84
|
color: 'var(--c-danger)',
|
|
78
|
-
background: '
|
|
85
|
+
background: 'color-mix(in srgb, var(--c-danger) 8%, transparent)',
|
|
79
86
|
borderRadius: 'var(--radius-sm)',
|
|
80
87
|
display: 'flex',
|
|
81
88
|
alignItems: 'center',
|
|
@@ -117,8 +124,30 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
117
124
|
const isStreaming = threadStatus === 'streaming';
|
|
118
125
|
const isAnswered = threadStatus === 'answered';
|
|
119
126
|
|
|
120
|
-
const [draft,
|
|
121
|
-
const [replyDraft,
|
|
127
|
+
const [draft, updateDraft] = useState('');
|
|
128
|
+
const [replyDraft, updateReplyDraft] = useState('');
|
|
129
|
+
const boardId = activeBoardId.value;
|
|
130
|
+
const input = useMemo(() => ({ boardId, nodeId: node.id, draft: '', reply: '' }), [boardId, node.id]);
|
|
131
|
+
const remember = () => {
|
|
132
|
+
if (input.draft || input.reply) unsentInputs.add(input);
|
|
133
|
+
else unsentInputs.delete(input);
|
|
134
|
+
};
|
|
135
|
+
const setDraft = (text: string) => {
|
|
136
|
+
input.draft = text;
|
|
137
|
+
remember();
|
|
138
|
+
updateDraft(text);
|
|
139
|
+
};
|
|
140
|
+
const setReplyDraft = (text: string) => {
|
|
141
|
+
input.reply = text;
|
|
142
|
+
remember();
|
|
143
|
+
updateReplyDraft(text);
|
|
144
|
+
};
|
|
145
|
+
useEffect(
|
|
146
|
+
() => () => {
|
|
147
|
+
unsentInputs.delete(input);
|
|
148
|
+
},
|
|
149
|
+
[input],
|
|
150
|
+
);
|
|
122
151
|
const [error, setError] = useState<string | null>(null);
|
|
123
152
|
const [renderedTurns, setRenderedTurns] = useState<Map<number, string>>(new Map());
|
|
124
153
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -312,7 +341,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
312
341
|
style={{
|
|
313
342
|
flex: 1,
|
|
314
343
|
resize: 'none',
|
|
315
|
-
background: '
|
|
344
|
+
background: 'var(--c-input-bg)',
|
|
316
345
|
border: '1px solid var(--c-line)',
|
|
317
346
|
borderRadius: 'var(--radius-sm)',
|
|
318
347
|
color: 'var(--c-text)',
|
|
@@ -324,7 +353,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
324
353
|
}}
|
|
325
354
|
/>
|
|
326
355
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
327
|
-
<
|
|
356
|
+
<kbd class="prompt-key-hint">{'\u2318'}+Enter to send</kbd>
|
|
328
357
|
<button
|
|
329
358
|
type="button"
|
|
330
359
|
onClick={handleSubmit}
|
|
@@ -377,10 +406,9 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
377
406
|
>
|
|
378
407
|
<span
|
|
379
408
|
style={{
|
|
380
|
-
fontSize: '
|
|
381
|
-
textTransform: 'uppercase',
|
|
409
|
+
fontSize: 'var(--fs-meta)',
|
|
382
410
|
fontWeight: 600,
|
|
383
|
-
color: isPending ? 'var(--c-
|
|
411
|
+
color: isPending ? 'var(--c-agent)' : isAnswered ? 'var(--c-ok)' : 'var(--c-muted)',
|
|
384
412
|
}}
|
|
385
413
|
>
|
|
386
414
|
{isPending ? 'Sending…' : isAnswered ? 'Answered' : legacyStatus}
|
|
@@ -481,13 +509,12 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
481
509
|
>
|
|
482
510
|
<span
|
|
483
511
|
style={{
|
|
484
|
-
fontSize: '
|
|
485
|
-
textTransform: 'uppercase',
|
|
512
|
+
fontSize: 'var(--fs-meta)',
|
|
486
513
|
fontWeight: 600,
|
|
487
514
|
color: isStreaming
|
|
488
|
-
? 'var(--c-
|
|
515
|
+
? 'var(--c-agent)'
|
|
489
516
|
: isPending
|
|
490
|
-
? 'var(--c-
|
|
517
|
+
? 'var(--c-agent)'
|
|
491
518
|
: isAnswered
|
|
492
519
|
? 'var(--c-ok)'
|
|
493
520
|
: 'var(--c-muted)',
|
|
@@ -495,7 +522,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
495
522
|
>
|
|
496
523
|
{isStreaming ? 'Streaming…' : isPending ? 'Sending…' : isAnswered ? 'Answered' : threadStatus}
|
|
497
524
|
</span>
|
|
498
|
-
<span style={{ fontSize: '
|
|
525
|
+
<span style={{ fontSize: 'var(--fs-meta)', color: 'var(--c-muted)' }}>
|
|
499
526
|
{turns.length} turn{turns.length !== 1 ? 's' : ''}
|
|
500
527
|
</span>
|
|
501
528
|
</div>
|
|
@@ -513,7 +540,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
513
540
|
style={{
|
|
514
541
|
width: '100%',
|
|
515
542
|
resize: 'none',
|
|
516
|
-
background: '
|
|
543
|
+
background: 'var(--c-input-bg)',
|
|
517
544
|
border: '1px solid var(--c-line)',
|
|
518
545
|
borderRadius: 'var(--radius-sm)',
|
|
519
546
|
color: 'var(--c-text)',
|
|
@@ -532,7 +559,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
532
559
|
marginTop: '4px',
|
|
533
560
|
}}
|
|
534
561
|
>
|
|
535
|
-
<
|
|
562
|
+
<kbd class="prompt-key-hint">{'\u2318'}+Enter</kbd>
|
|
536
563
|
<button
|
|
537
564
|
type="button"
|
|
538
565
|
onClick={handleReplySubmit}
|
|
@@ -49,13 +49,16 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
51
|
<div class="response-node-inner" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
52
|
+
<div class="thread-turn-role" style={{ color: 'var(--c-agent)' }}>
|
|
53
|
+
✦ Agent
|
|
54
|
+
</div>
|
|
52
55
|
{/* Streaming indicator */}
|
|
53
56
|
{isStreaming && (
|
|
54
57
|
<div
|
|
55
58
|
class="response-streaming-bar"
|
|
56
59
|
style={{
|
|
57
60
|
height: '2px',
|
|
58
|
-
background: 'linear-gradient(90deg, transparent, var(--c-
|
|
61
|
+
background: 'linear-gradient(90deg, transparent, var(--c-agent), transparent)',
|
|
59
62
|
animation: 'response-stream-pulse 1.5s ease-in-out infinite',
|
|
60
63
|
borderRadius: '1px',
|
|
61
64
|
marginBottom: '4px',
|
|
@@ -100,10 +103,9 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
100
103
|
>
|
|
101
104
|
<span
|
|
102
105
|
style={{
|
|
103
|
-
fontSize: '
|
|
104
|
-
textTransform: 'uppercase',
|
|
106
|
+
fontSize: 'var(--fs-meta)',
|
|
105
107
|
fontWeight: 600,
|
|
106
|
-
color: isStreaming ? 'var(--c-
|
|
108
|
+
color: isStreaming ? 'var(--c-agent)' : isComplete ? 'var(--c-ok)' : 'var(--c-muted)',
|
|
107
109
|
}}
|
|
108
110
|
>
|
|
109
111
|
{isStreaming ? 'Streaming…' : isComplete ? 'Complete' : status}
|
|
@@ -115,8 +117,8 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
115
117
|
style={{
|
|
116
118
|
padding: '3px 10px',
|
|
117
119
|
fontSize: '11px',
|
|
118
|
-
background: '
|
|
119
|
-
border: '1px solid
|
|
120
|
+
background: 'var(--c-panel-soft)',
|
|
121
|
+
border: '1px solid var(--c-line)',
|
|
120
122
|
borderRadius: 'var(--radius-sm)',
|
|
121
123
|
color: 'var(--c-accent)',
|
|
122
124
|
cursor: 'pointer',
|