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,5 +1,7 @@
|
|
|
1
1
|
import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
2
2
|
import { BarHint } from './BarHint';
|
|
3
|
+
import { AttentionHistory } from './AttentionHistory';
|
|
4
|
+
import { AttentionToast } from './AttentionToast';
|
|
3
5
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
4
6
|
import { ContextNode } from '../nodes/ContextNode';
|
|
5
7
|
import { DiffNode } from '../nodes/DiffNode';
|
|
@@ -242,8 +244,7 @@ export function ExpandedNodeOverlay() {
|
|
|
242
244
|
};
|
|
243
245
|
}, [handlePresentationKeyDown, presentationExitToken, presenting]);
|
|
244
246
|
|
|
245
|
-
//
|
|
246
|
-
// world element is unmounted while expanded and remounted on close.
|
|
247
|
+
// Restore to the same spatial card at every zoom.
|
|
247
248
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
248
249
|
const restoreToNode = useCallback(
|
|
249
250
|
() => (nodeId ? document.querySelector<HTMLElement>(`.canvas-node[data-node-id="${CSS.escape(nodeId)}"]`) : null),
|
|
@@ -398,6 +399,8 @@ export function ExpandedNodeOverlay() {
|
|
|
398
399
|
{pendingClose ? 'Saving edits…' : 'esc or click outside to close'}
|
|
399
400
|
</span>
|
|
400
401
|
</div>
|
|
402
|
+
<AttentionToast />
|
|
403
|
+
<AttentionHistory />
|
|
401
404
|
{canPresent && presenting && (
|
|
402
405
|
<div
|
|
403
406
|
ref={presentationOverlayRef}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { activeBoard } from '../state/boards-store';
|
|
2
|
+
import { useLayoutEffect, useRef } from 'preact/hooks';
|
|
3
|
+
import { reportClientViewportSize } from '../state/intent-bridge';
|
|
4
|
+
import { focusNode, groupContents, groupPath, groupViewNodes, leaveGroup, nodes } from '../state/canvas-store';
|
|
5
|
+
|
|
6
|
+
export function GroupBreadcrumbs() {
|
|
7
|
+
const path = groupPath.value;
|
|
8
|
+
const ref = useRef<HTMLElement>(null);
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
const element = ref.current;
|
|
11
|
+
if (!element) return;
|
|
12
|
+
const resize = new ResizeObserver(() => void reportClientViewportSize());
|
|
13
|
+
resize.observe(element);
|
|
14
|
+
return () => resize.disconnect();
|
|
15
|
+
}, [path.length > 0]);
|
|
16
|
+
if (!path.length) return null;
|
|
17
|
+
return (
|
|
18
|
+
<nav
|
|
19
|
+
ref={ref}
|
|
20
|
+
class="group-breadcrumbs"
|
|
21
|
+
aria-label="Group navigation"
|
|
22
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
23
|
+
>
|
|
24
|
+
<button type="button" onClick={() => leaveGroup(0)}>
|
|
25
|
+
{activeBoard()?.name ?? 'Board'}
|
|
26
|
+
</button>
|
|
27
|
+
{path.map((visit, index) => (
|
|
28
|
+
<span key={visit.id}>
|
|
29
|
+
<span aria-hidden="true"> / </span>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
aria-current={index === path.length - 1 ? 'location' : undefined}
|
|
33
|
+
disabled={index === path.length - 1}
|
|
34
|
+
onClick={() => leaveGroup(index + 1)}
|
|
35
|
+
>
|
|
36
|
+
{String(nodes.value.get(visit.id)?.data.title ?? 'Group')}
|
|
37
|
+
</button>
|
|
38
|
+
</span>
|
|
39
|
+
))}
|
|
40
|
+
<button type="button" onClick={() => leaveGroup()} aria-label="Leave group">
|
|
41
|
+
Esc · goes up
|
|
42
|
+
</button>
|
|
43
|
+
</nav>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function GroupOutsideLinks() {
|
|
48
|
+
const inside = groupContents.value;
|
|
49
|
+
if (!inside) return null;
|
|
50
|
+
const outside = [...groupViewNodes.value.values()].filter((node) => !inside.has(node.id));
|
|
51
|
+
const children = [...nodes.value.values()].filter((node) => inside.has(node.id));
|
|
52
|
+
const root = nodes.value.get(groupPath.value.at(-1)!.id)!;
|
|
53
|
+
const left = children.length ? Math.min(...children.map((node) => node.position.x)) - 24 : root.position.x;
|
|
54
|
+
const top = children.length ? Math.min(...children.map((node) => node.position.y)) - 40 : root.position.y;
|
|
55
|
+
const right = children.length
|
|
56
|
+
? Math.max(...children.map((node) => node.position.x + node.size.width)) + 24
|
|
57
|
+
: left + 320;
|
|
58
|
+
const bottom = children.length
|
|
59
|
+
? Math.max(...children.map((node) => node.position.y + node.size.height)) + 24
|
|
60
|
+
: top + 120;
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<div class="group-inside-frame" style={{ left, top, width: right - left, height: bottom - top }}>
|
|
64
|
+
<span>Inside {String(root.data.title ?? 'Group')}</span>
|
|
65
|
+
</div>
|
|
66
|
+
{outside.map((node, index) => (
|
|
67
|
+
<div key={node.id} class="group-outside-endpoint" style={{ left: node.position.x, top: node.position.y }}>
|
|
68
|
+
{index === 0 && <span class="group-outside-label">Outside</span>}
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="group-outside-stub"
|
|
72
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
73
|
+
onClick={(event) => {
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
focusNode(node.id, { recordHistory: false, persistStacking: false });
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
↗ {String(node.data.title ?? node.type)}
|
|
79
|
+
<span>{node.type} · Show on canvas</span>
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -4,6 +4,7 @@ import { canvasArea } from './canvas-area';
|
|
|
4
4
|
import { cancelViewportAnimation, hasInitialServerLayout, viewport } from '../state/canvas-store';
|
|
5
5
|
import {
|
|
6
6
|
endPresentation,
|
|
7
|
+
presentationCursor,
|
|
7
8
|
presentationError,
|
|
8
9
|
presenting,
|
|
9
10
|
recordingCamera,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
stepTour,
|
|
12
13
|
} from '../state/presentation';
|
|
13
14
|
import { usePanZoom } from './use-pan-zoom';
|
|
15
|
+
import { BarHint } from './BarHint';
|
|
14
16
|
|
|
15
17
|
declare global {
|
|
16
18
|
interface Window {
|
|
@@ -25,6 +27,9 @@ declare global {
|
|
|
25
27
|
|
|
26
28
|
export function Presentation() {
|
|
27
29
|
const active = presenting.value;
|
|
30
|
+
const cursor = presentationCursor.value;
|
|
31
|
+
const canBack = cursor !== null && cursor.index > 0;
|
|
32
|
+
const canNext = cursor !== null && cursor.index < cursor.total - 1;
|
|
28
33
|
const ready = hasInitialServerLayout.value;
|
|
29
34
|
const moveCamera = (camera: Camera) => {
|
|
30
35
|
cancelViewportAnimation();
|
|
@@ -89,9 +94,54 @@ export function Presentation() {
|
|
|
89
94
|
}}
|
|
90
95
|
onContextMenu={(event) => event.preventDefault()}
|
|
91
96
|
>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
<div class="presentation-controls" role="group" aria-label="Presentation controls">
|
|
98
|
+
<BarHint
|
|
99
|
+
id="presentation-back-hint"
|
|
100
|
+
label="Previous stop"
|
|
101
|
+
shortcut="← / ↑"
|
|
102
|
+
fitWithin=".presentation-navigation"
|
|
103
|
+
>
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
class="presentation-step"
|
|
107
|
+
aria-describedby="presentation-back-hint"
|
|
108
|
+
aria-disabled={!canBack}
|
|
109
|
+
onClick={() => {
|
|
110
|
+
if (canBack) void stepTour({ step: 'previous' });
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
Back
|
|
114
|
+
</button>
|
|
115
|
+
</BarHint>
|
|
116
|
+
<BarHint
|
|
117
|
+
id="presentation-next-hint"
|
|
118
|
+
label="Next stop"
|
|
119
|
+
shortcut="→ / ↓ / Space"
|
|
120
|
+
fitWithin=".presentation-navigation"
|
|
121
|
+
>
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
class="presentation-step"
|
|
125
|
+
aria-describedby="presentation-next-hint"
|
|
126
|
+
aria-disabled={!canNext}
|
|
127
|
+
onClick={() => {
|
|
128
|
+
if (canNext) void stepTour({ step: 'next' });
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
Next
|
|
132
|
+
</button>
|
|
133
|
+
</BarHint>
|
|
134
|
+
<BarHint id="presentation-exit-hint" label="Exit presentation" shortcut="Esc" align="end">
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
class="presentation-exit"
|
|
138
|
+
aria-describedby="presentation-exit-hint"
|
|
139
|
+
onClick={endPresentation}
|
|
140
|
+
>
|
|
141
|
+
Exit presentation
|
|
142
|
+
</button>
|
|
143
|
+
</BarHint>
|
|
144
|
+
</div>
|
|
95
145
|
</div>
|
|
96
146
|
)}
|
|
97
147
|
{presentationError.value && (
|
|
@@ -4,6 +4,7 @@ import { relationDefinition, relationSourceEnd } from '../../shared/relations.js
|
|
|
4
4
|
import { activeBoardId, boardList, openMemoryTarget } from '../state/boards-store';
|
|
5
5
|
import { edges, nodes } from '../state/canvas-store';
|
|
6
6
|
import { requestJson } from '../state/intent-bridge';
|
|
7
|
+
import { showRelationsOnCanvas } from '../state/relation-store';
|
|
7
8
|
|
|
8
9
|
/** The expanded card's evidence and uses, from the same graph agents navigate. */
|
|
9
10
|
export function RelationsPanel({ nodeId }: { nodeId: string }) {
|
|
@@ -50,6 +51,14 @@ export function RelationsPanel({ nodeId }: { nodeId: string }) {
|
|
|
50
51
|
return (
|
|
51
52
|
<aside class="relations-panel" aria-label="Relations">
|
|
52
53
|
<h3>Relations</h3>
|
|
54
|
+
<div class="relation-navigation">
|
|
55
|
+
<button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'lineage')}>
|
|
56
|
+
Show lineage
|
|
57
|
+
</button>
|
|
58
|
+
<button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'connections')}>
|
|
59
|
+
Show on canvas
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
53
62
|
{!graph && <p role="status">{unavailable ? 'Relations unavailable. Reopen to retry.' : 'Loading relations…'}</p>}
|
|
54
63
|
{graph && relations.length === 0 && <p>No connections yet.</p>}
|
|
55
64
|
{relations.map((edge, index) => {
|
|
@@ -13,6 +13,7 @@ import { createEdgeFromClient, createGroupFromClient, removeNodeFromClient } fro
|
|
|
13
13
|
import { sessionActive } from '../state/presence-store';
|
|
14
14
|
import { BarHint } from './BarHint';
|
|
15
15
|
import { useChromeHeight } from './use-chrome-height';
|
|
16
|
+
import { openBoardMove } from './BoardMoveDialog';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Floating bottom-center bar for a multi-selection (rail-chrome-v2 phase 7,
|
|
@@ -174,6 +175,26 @@ export function SelectionBar() {
|
|
|
174
175
|
</BarHint>
|
|
175
176
|
</>
|
|
176
177
|
)}
|
|
178
|
+
<BarHint label="Move to new board" side="up">
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
class="selection-bar-icon"
|
|
182
|
+
aria-label="Move to new board"
|
|
183
|
+
onClick={() => openBoardMove([...selectedNodeIds.value])}
|
|
184
|
+
>
|
|
185
|
+
<svg
|
|
186
|
+
width="16"
|
|
187
|
+
height="16"
|
|
188
|
+
viewBox="0 0 16 16"
|
|
189
|
+
fill="none"
|
|
190
|
+
stroke="currentColor"
|
|
191
|
+
stroke-width="1.5"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
>
|
|
194
|
+
<path d="M6 3H2v10h12V9M8 8l6-6M9 2h5v5" />
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
</BarHint>
|
|
177
198
|
<BarHint label="Pin to agent context" side="up">
|
|
178
199
|
<button type="button" class="selection-bar-action is-pin" onClick={handlePinContext}>
|
|
179
200
|
<span aria-hidden="true">✦</span>
|
|
@@ -4,7 +4,9 @@ import { IconChevronRight, IconUndo } from '../icons';
|
|
|
4
4
|
import { useNow } from './use-now';
|
|
5
5
|
import { agentPhaseLabel } from '../../shared/agent-presence.js';
|
|
6
6
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
7
|
-
import { focusNode, selectedNodeIds } from '../state/canvas-store';
|
|
7
|
+
import { focusNode, nodes, selectedNodeIds } from '../state/canvas-store';
|
|
8
|
+
import { activeBoardId } from '../state/boards-store';
|
|
9
|
+
import { createNodeFromClient } from '../state/intent-bridge';
|
|
8
10
|
import { activeSession, agentPresences, attachedSessions, writerColor } from '../state/presence-store';
|
|
9
11
|
import {
|
|
10
12
|
type ApprovalGateView,
|
|
@@ -96,6 +98,35 @@ function clock(iso: string): string {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
function WorkItemRow({ item }: { item: WorkItemView }) {
|
|
101
|
+
const [busy, setBusy] = useState(false);
|
|
102
|
+
const [pending, setPending] = useState<{ boardId: string; nodeId: string } | null>(null);
|
|
103
|
+
const boardId = activeBoardId.value;
|
|
104
|
+
const boardNodes = nodes.value;
|
|
105
|
+
const presentation = [...nodes.value.values()].find(
|
|
106
|
+
(node) => node.type === 'status' && node.data.workItemId === item.id,
|
|
107
|
+
);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!pending) return;
|
|
110
|
+
if (boardId === pending.boardId && !boardNodes.has(pending.nodeId)) return;
|
|
111
|
+
if (boardId === pending.boardId) focusNode(pending.nodeId);
|
|
112
|
+
setPending(null);
|
|
113
|
+
setBusy(false);
|
|
114
|
+
}, [pending, boardId, boardNodes]);
|
|
115
|
+
const show = async () => {
|
|
116
|
+
if (presentation) return focusNode(presentation.id);
|
|
117
|
+
if (!boardId || busy) return;
|
|
118
|
+
setBusy(true);
|
|
119
|
+
const result = await createNodeFromClient({
|
|
120
|
+
type: 'status',
|
|
121
|
+
data: { workItemId: item.id },
|
|
122
|
+
width: 360,
|
|
123
|
+
height: 200,
|
|
124
|
+
expectedBoardId: boardId,
|
|
125
|
+
});
|
|
126
|
+
if (result.ok && result.id && activeBoardId.value === boardId) {
|
|
127
|
+
setPending({ boardId, nodeId: result.id });
|
|
128
|
+
} else setBusy(false);
|
|
129
|
+
};
|
|
99
130
|
const glyph = WORK_GLYPH[item.status] ?? 'queued';
|
|
100
131
|
const nodeId = item.nodeIds[0];
|
|
101
132
|
return (
|
|
@@ -106,6 +137,9 @@ function WorkItemRow({ item }: { item: WorkItemView }) {
|
|
|
106
137
|
{item.title}
|
|
107
138
|
</button>
|
|
108
139
|
{item.detail && <div class="session-item-detail">{item.detail}</div>}
|
|
140
|
+
<button type="button" class="session-show-work" disabled={busy} onClick={() => void show()}>
|
|
141
|
+
{busy ? 'Showing…' : presentation ? 'Show card' : 'Show on board'}
|
|
142
|
+
</button>
|
|
109
143
|
</div>
|
|
110
144
|
<span class="session-item-status">{WORK_STATUS_TEXT[item.status] ?? glyph}</span>
|
|
111
145
|
<span class="session-item-time">{clock(item.updatedAt)}</span>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@preact/signals';
|
|
2
2
|
import { useEffect, useRef } from 'preact/hooks';
|
|
3
|
+
import { useFocusTrap } from './use-focus-trap';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* In-canvas replacement for `window.prompt`, which embedded browser panes
|
|
@@ -9,6 +10,8 @@ import { useEffect, useRef } from 'preact/hooks';
|
|
|
9
10
|
interface TextPromptRequest {
|
|
10
11
|
title: string;
|
|
11
12
|
placeholder: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
maxLength?: number;
|
|
12
15
|
/** Prefilled (and pre-selected) value — edit flows start from the current text. */
|
|
13
16
|
initial: string;
|
|
14
17
|
/** Submitting an empty field resolves '' instead of null — "clear the value" flows. */
|
|
@@ -25,7 +28,7 @@ let promptSeq = 0;
|
|
|
25
28
|
export function askText(
|
|
26
29
|
title: string,
|
|
27
30
|
placeholder: string,
|
|
28
|
-
opts: { initial?: string; allowEmpty?: boolean; confirm?: string } = {},
|
|
31
|
+
opts: { initial?: string; allowEmpty?: boolean; confirm?: string; description?: string; maxLength?: number } = {},
|
|
29
32
|
): Promise<string | null> {
|
|
30
33
|
return new Promise((resolve) => {
|
|
31
34
|
textPromptRequest.value?.resolve(null);
|
|
@@ -34,6 +37,8 @@ export function askText(
|
|
|
34
37
|
seq: promptSeq,
|
|
35
38
|
title,
|
|
36
39
|
placeholder,
|
|
40
|
+
description: opts.description,
|
|
41
|
+
maxLength: opts.maxLength,
|
|
37
42
|
initial: opts.initial ?? '',
|
|
38
43
|
allowEmpty: opts.allowEmpty ?? false,
|
|
39
44
|
confirm: opts.confirm ?? 'Add',
|
|
@@ -51,6 +56,8 @@ function settle(value: string | null): void {
|
|
|
51
56
|
export function TextPrompt() {
|
|
52
57
|
const request = textPromptRequest.value;
|
|
53
58
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
59
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
60
|
+
useFocusTrap(dialogRef, request !== null, { initial: inputRef });
|
|
54
61
|
|
|
55
62
|
useEffect(() => {
|
|
56
63
|
if (!request) return;
|
|
@@ -78,13 +85,21 @@ export function TextPrompt() {
|
|
|
78
85
|
return (
|
|
79
86
|
<div class="text-prompt-backdrop" onPointerDown={() => settle(null)}>
|
|
80
87
|
<div
|
|
88
|
+
ref={dialogRef}
|
|
81
89
|
class="text-prompt"
|
|
82
90
|
role="dialog"
|
|
91
|
+
aria-modal="true"
|
|
83
92
|
aria-label={request.title}
|
|
93
|
+
aria-describedby={request.description ? 'text-prompt-description' : undefined}
|
|
84
94
|
data-testid="text-prompt"
|
|
85
95
|
onPointerDown={(e) => e.stopPropagation()}
|
|
86
96
|
>
|
|
87
97
|
<div class="text-prompt-title">{request.title}</div>
|
|
98
|
+
{request.description && (
|
|
99
|
+
<div id="text-prompt-description" class="text-prompt-description">
|
|
100
|
+
{request.description}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
88
103
|
<input
|
|
89
104
|
// A fresh element per ask (so defaultValue applies) focused in the
|
|
90
105
|
// ref callback, which Preact runs synchronously during commit — the
|
|
@@ -101,6 +116,8 @@ export function TextPrompt() {
|
|
|
101
116
|
type="text"
|
|
102
117
|
defaultValue={request.initial}
|
|
103
118
|
placeholder={request.placeholder}
|
|
119
|
+
aria-label={request.title}
|
|
120
|
+
maxLength={request.maxLength}
|
|
104
121
|
onKeyDown={(e) => {
|
|
105
122
|
// Contained: the global shortcut layer must not see these.
|
|
106
123
|
if (e.key === 'Enter') {
|
|
@@ -38,6 +38,18 @@ import {
|
|
|
38
38
|
} from '../state/session-store';
|
|
39
39
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
40
40
|
import { agentSessionsEnabled } from '../state/workspace-settings';
|
|
41
|
+
import { humanName, setHumanName } from '../state/human-store';
|
|
42
|
+
import { tabAgentKey } from '../state/intent-bridge';
|
|
43
|
+
import { HUMAN_NAME_MAX_LENGTH } from '../../shared/human-presence.js';
|
|
44
|
+
import { askText } from './TextPrompt';
|
|
45
|
+
|
|
46
|
+
const PRESENT_DESCRIPTION = (
|
|
47
|
+
<span style={{ whiteSpace: 'pre-line' }}>
|
|
48
|
+
{
|
|
49
|
+
'A presentation follows either a saved tour or the board’s groups.\n\nSaved tour: ask your agent to define the stops and their order. Each stop can focus on a card, a group, a rectangular area, or a specific camera position.\n\nNo saved tour: every group becomes a stop, ordered top-to-bottom, then left-to-right. It doesn’t automatically visit every card.'
|
|
50
|
+
}
|
|
51
|
+
</span>
|
|
52
|
+
);
|
|
41
53
|
|
|
42
54
|
/**
|
|
43
55
|
* The attached session's chip (rail-chrome-v2 phase 3): phase-colored, with a
|
|
@@ -481,12 +493,16 @@ export function TopBar() {
|
|
|
481
493
|
)}
|
|
482
494
|
{activeBoardId.value && (
|
|
483
495
|
<BarHint
|
|
496
|
+
id="present-hint"
|
|
484
497
|
label="Present board"
|
|
485
|
-
body=
|
|
498
|
+
body={PRESENT_DESCRIPTION}
|
|
499
|
+
align="end"
|
|
500
|
+
fitWithin="body"
|
|
486
501
|
>
|
|
487
502
|
<button
|
|
488
503
|
type="button"
|
|
489
504
|
class="present-button"
|
|
505
|
+
aria-describedby="present-hint"
|
|
490
506
|
onClick={() => {
|
|
491
507
|
void startPresentation();
|
|
492
508
|
}}
|
|
@@ -524,6 +540,32 @@ export function TopBar() {
|
|
|
524
540
|
<TopBarMore boardOpen={!!activeBoardId.value} onFit={handleFit} />
|
|
525
541
|
</>
|
|
526
542
|
)}
|
|
543
|
+
{!isHostedWorkbench() && !tabAgentKey && (
|
|
544
|
+
<span class="human-name-control">
|
|
545
|
+
<BarHint label="Your name" body={`Shown on your cursor: ${humanName.value}`} align="end">
|
|
546
|
+
<button
|
|
547
|
+
type="button"
|
|
548
|
+
class="top-bar-btn human-name-button"
|
|
549
|
+
aria-label={`Your name: ${humanName.value}`}
|
|
550
|
+
aria-haspopup="dialog"
|
|
551
|
+
onClick={async (event) => {
|
|
552
|
+
const button = event.currentTarget;
|
|
553
|
+
const name = await askText('Your name on this canvas', 'e.g. Alex', {
|
|
554
|
+
initial: humanName.value,
|
|
555
|
+
confirm: 'Save name',
|
|
556
|
+
maxLength: HUMAN_NAME_MAX_LENGTH,
|
|
557
|
+
description:
|
|
558
|
+
'Shown on your cursor. Self-reported, not a verified identity or an access grant. Saved in this browser.',
|
|
559
|
+
});
|
|
560
|
+
if (name !== null) setHumanName(name);
|
|
561
|
+
button.focus({ preventScroll: true });
|
|
562
|
+
}}
|
|
563
|
+
>
|
|
564
|
+
{writerInitial(humanName.value)}
|
|
565
|
+
</button>
|
|
566
|
+
</BarHint>
|
|
567
|
+
</span>
|
|
568
|
+
)}
|
|
527
569
|
</div>
|
|
528
570
|
</div>
|
|
529
571
|
);
|
|
@@ -592,14 +634,17 @@ function TopBarMore({ boardOpen, onFit }: { boardOpen: boolean; onFit: () => voi
|
|
|
592
634
|
</button>
|
|
593
635
|
)}
|
|
594
636
|
{boardOpen && (
|
|
595
|
-
<
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
637
|
+
<BarHint id="present-menu-hint" label="Present board" body={PRESENT_DESCRIPTION}>
|
|
638
|
+
<button
|
|
639
|
+
type="button"
|
|
640
|
+
role="menuitem"
|
|
641
|
+
class="toolbar-menu-item"
|
|
642
|
+
aria-describedby="present-menu-hint"
|
|
643
|
+
onClick={run(() => void startPresentation())}
|
|
644
|
+
>
|
|
645
|
+
Present board
|
|
646
|
+
</button>
|
|
647
|
+
</BarHint>
|
|
603
648
|
)}
|
|
604
649
|
<button type="button" role="menuitem" class="toolbar-menu-item" onClick={run(() => zoomByFactor(1.25))}>
|
|
605
650
|
Zoom in
|
|
@@ -7,12 +7,6 @@ export const AUTO_FIT_MAX_HEIGHT = 600;
|
|
|
7
7
|
// Iframe surfaces (charts/dashboards/rich html) can legitimately need more room
|
|
8
8
|
// than a text node, so they grow to a higher ceiling before scrolling.
|
|
9
9
|
export const AUTO_FIT_MAX_HEIGHT_IFRAME = 1400;
|
|
10
|
-
// `.node-body` adds 12px padding top+bottom around an iframe surface (global.css).
|
|
11
|
-
// The bridge reports the iframe's OWN document scrollHeight, so the grow target
|
|
12
|
-
// must add titlebar + this body padding or the node settles ~24px short and the
|
|
13
|
-
// surface shows a residual inner scrollbar. (The DOM auto-fit above doesn't need
|
|
14
|
-
// this: body.scrollHeight already includes the body's padding.)
|
|
15
|
-
export const AUTO_FIT_BODY_PADDING = 24;
|
|
16
10
|
|
|
17
11
|
/** Node types the DOM auto-fit can't measure: iframe-backed surfaces (html/
|
|
18
12
|
* json-render/graph/mcp-app), where the body's scrollHeight equals the iframe
|
|
@@ -77,13 +71,17 @@ export function shouldContentFitIframeNode(node: CanvasNodeState): boolean {
|
|
|
77
71
|
/**
|
|
78
72
|
* Grow-only target height from a surface-reported content height. Returns null
|
|
79
73
|
* when the node is exempt, the report is non-positive, or the node already fits
|
|
80
|
-
* (so it never shrinks
|
|
81
|
-
*
|
|
82
|
-
*
|
|
74
|
+
* (so it never shrinks). Callers measure the actual chrome in world pixels,
|
|
75
|
+
* including zoom-compensated headers and any captions. A fixed padding allowance
|
|
76
|
+
* makes a full-height viewer grow again on every content-height report.
|
|
83
77
|
*/
|
|
84
|
-
export function computeContentGrowHeight(
|
|
78
|
+
export function computeContentGrowHeight(
|
|
79
|
+
node: CanvasNodeState,
|
|
80
|
+
contentHeight: number,
|
|
81
|
+
chromeHeight: number,
|
|
82
|
+
): number | null {
|
|
85
83
|
if (!shouldContentFitIframeNode(node) || !Number.isFinite(contentHeight) || contentHeight <= 0) return null;
|
|
86
|
-
const want = Math.min(contentHeight +
|
|
84
|
+
const want = Math.min(Math.ceil(contentHeight + chromeHeight), AUTO_FIT_MAX_HEIGHT_IFRAME);
|
|
87
85
|
return want > node.size.height + 8 ? want : null;
|
|
88
86
|
}
|
|
89
87
|
|
|
@@ -55,12 +55,12 @@ export interface CanvasFitInsets {
|
|
|
55
55
|
* both (0.5.0 Amp report, finding A; architecture rule 9's trap).
|
|
56
56
|
*/
|
|
57
57
|
export function canvasFitInsets(): CanvasFitInsets {
|
|
58
|
-
let top =
|
|
58
|
+
let top = 44;
|
|
59
59
|
let bottom = 16;
|
|
60
60
|
try {
|
|
61
61
|
const area = canvasArea();
|
|
62
62
|
const areaBottom = area.top + area.height;
|
|
63
|
-
for (const selector of ['.lifecycle-filters', '.relation-legend']) {
|
|
63
|
+
for (const selector of ['.lifecycle-filters', '.relation-legend', '.group-breadcrumbs']) {
|
|
64
64
|
const el = document.querySelector(selector);
|
|
65
65
|
if (!(el instanceof HTMLElement) || !el.isConnected) continue;
|
|
66
66
|
const rect = el.getBoundingClientRect();
|
|
@@ -71,7 +71,12 @@ export function useFocusTrap(
|
|
|
71
71
|
// from (focus mode unmounts the node while expanded) names the element
|
|
72
72
|
// to return to, which may only exist after the next frame.
|
|
73
73
|
if (restoreTo) {
|
|
74
|
-
requestAnimationFrame(() =>
|
|
74
|
+
requestAnimationFrame(() => {
|
|
75
|
+
// A newly opened menu owns focus before this deferred return runs.
|
|
76
|
+
if (document.activeElement === document.body || root.contains(document.activeElement)) {
|
|
77
|
+
restoreTo()?.focus();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
75
80
|
} else if (previous && document.contains(previous) && previous !== document.body) {
|
|
76
81
|
previous.focus();
|
|
77
82
|
}
|
|
@@ -5,16 +5,10 @@
|
|
|
5
5
|
* HTML-escaped diagram source in `<pre class="mermaid-source">`; this script
|
|
6
6
|
* reads it back via textContent, renders it to SVG, and re-renders when the
|
|
7
7
|
* parent canvas live-switches the theme (the theme bridge toggles the document's
|
|
8
|
-
* `data-theme` attribute).
|
|
9
|
-
* canvas boot lean — only mermaid-node surfaces pay for the library.
|
|
8
|
+
* `data-theme` attribute). Standalone views and exports share the canvas renderer.
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
import
|
|
13
|
-
import { canvasThemeScheme } from '../shared/themes.js';
|
|
14
|
-
|
|
15
|
-
function documentScheme(): 'dark' | 'light' {
|
|
16
|
-
return canvasThemeScheme(document.documentElement.getAttribute('data-theme'));
|
|
17
|
-
}
|
|
11
|
+
import { renderMermaid } from './mermaid-render.js';
|
|
18
12
|
|
|
19
13
|
function showError(container: HTMLElement, message: string): void {
|
|
20
14
|
container.innerHTML = '';
|
|
@@ -65,18 +59,14 @@ function fitDiagram(container: HTMLElement): void {
|
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
async function renderDiagram(source: string, container: HTMLElement): Promise<void> {
|
|
68
|
-
const
|
|
69
|
-
mermaid.initialize({
|
|
70
|
-
startOnLoad: false,
|
|
71
|
-
theme: scheme === 'dark' ? 'dark' : 'default',
|
|
72
|
-
securityLevel: 'strict',
|
|
73
|
-
});
|
|
74
|
-
renderSeq += 1;
|
|
62
|
+
const sequence = ++renderSeq;
|
|
75
63
|
try {
|
|
76
|
-
const
|
|
64
|
+
const svg = await renderMermaid(source);
|
|
65
|
+
if (sequence !== renderSeq) return;
|
|
77
66
|
container.innerHTML = svg;
|
|
78
67
|
fitDiagram(container);
|
|
79
68
|
} catch (error) {
|
|
69
|
+
if (sequence !== renderSeq) return;
|
|
80
70
|
// Never leave a blank/black frame: show the parse/render error instead.
|
|
81
71
|
showError(container, error instanceof Error ? error.message : String(error));
|
|
82
72
|
}
|
|
@@ -98,11 +88,7 @@ function boot(): void {
|
|
|
98
88
|
void renderDiagram(source, container);
|
|
99
89
|
// Live theme switching: the parent's theme bridge toggles data-theme on the
|
|
100
90
|
// root element; re-render so the diagram palette follows the canvas theme.
|
|
101
|
-
let lastScheme = documentScheme();
|
|
102
91
|
const observer = new MutationObserver(() => {
|
|
103
|
-
const scheme = documentScheme();
|
|
104
|
-
if (scheme === lastScheme) return;
|
|
105
|
-
lastScheme = scheme;
|
|
106
92
|
void renderDiagram(source, container);
|
|
107
93
|
});
|
|
108
94
|
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
|
|
3
|
+
// Mermaid configuration is global: serialize configuration with each render.
|
|
4
|
+
let rendering: Promise<unknown> = Promise.resolve();
|
|
5
|
+
|
|
6
|
+
export function renderMermaid(source: string): Promise<string> {
|
|
7
|
+
const result = rendering.then(async () => {
|
|
8
|
+
// Diagram-only dependency: the SPA splits this module; self-contained MCP
|
|
9
|
+
// documents defer its initialization without requiring external assets.
|
|
10
|
+
const { default: mermaid } = await import('mermaid');
|
|
11
|
+
const style = getComputedStyle(document.documentElement);
|
|
12
|
+
const token = (name: string) => style.getPropertyValue(name).trim();
|
|
13
|
+
mermaid.initialize({
|
|
14
|
+
startOnLoad: false,
|
|
15
|
+
securityLevel: 'strict',
|
|
16
|
+
theme: 'base',
|
|
17
|
+
fontFamily: token('--font-ui'),
|
|
18
|
+
themeVariables: {
|
|
19
|
+
primaryColor: token('--c-panel-soft'),
|
|
20
|
+
primaryTextColor: token('--c-text'),
|
|
21
|
+
primaryBorderColor: token('--c-line'),
|
|
22
|
+
lineColor: token('--c-muted'),
|
|
23
|
+
secondaryColor: token('--c-panel'),
|
|
24
|
+
tertiaryColor: token('--c-panel'),
|
|
25
|
+
edgeLabelBackground: token('--c-panel'),
|
|
26
|
+
fontSize: '12px',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const { svg } = await mermaid.render(`mermaid-${crypto.randomUUID()}`, source);
|
|
30
|
+
return DOMPurify.sanitize(svg, {
|
|
31
|
+
ADD_TAGS: ['foreignObject'],
|
|
32
|
+
ADD_ATTR: ['dominant-baseline'],
|
|
33
|
+
HTML_INTEGRATION_POINTS: { foreignobject: true },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// A malformed diagram must not prevent the next diagram from rendering.
|
|
37
|
+
rendering = result.catch(() => {});
|
|
38
|
+
return result;
|
|
39
|
+
}
|