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,13 +1,15 @@
|
|
|
1
1
|
import { effect } from '@preact/signals';
|
|
2
2
|
import { JSON_VIEWER_STATE_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
3
3
|
import { canvasInteractionEpoch, nodes } from '../state/canvas-store';
|
|
4
|
+
import type { CanvasNodeState } from '../types';
|
|
5
|
+
import { VIEWER_LOAD_DEADLINE_MS } from './ViewerFrame';
|
|
4
6
|
|
|
5
7
|
type Snapshot = {
|
|
6
8
|
epoch: number;
|
|
7
9
|
version: unknown;
|
|
8
10
|
documentUrl: unknown;
|
|
9
11
|
state: Record<string, unknown> | null;
|
|
10
|
-
owners: Array<{ token: string; source: Window
|
|
12
|
+
owners: Array<{ token: string; source: Window }>;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
// Tab-local UI state only: never persist form drafts in canvas data or AX. Keep
|
|
@@ -18,11 +20,12 @@ const snapshots = new Map<string, Snapshot>();
|
|
|
18
20
|
let listening = false;
|
|
19
21
|
|
|
20
22
|
export function restoreViewerRuntimeState(
|
|
21
|
-
|
|
23
|
+
authored: CanvasNodeState,
|
|
22
24
|
token: string,
|
|
23
25
|
source: Window,
|
|
24
26
|
signal?: AbortSignal,
|
|
25
27
|
): Promise<boolean | 'cancelled'> {
|
|
28
|
+
const nodeId = authored.id;
|
|
26
29
|
if (!listening) {
|
|
27
30
|
listening = true;
|
|
28
31
|
effect(() => {
|
|
@@ -36,7 +39,6 @@ export function restoreViewerRuntimeState(
|
|
|
36
39
|
snapshot.version !== node.data.specVersion ||
|
|
37
40
|
snapshot.documentUrl !== node.data.url
|
|
38
41
|
) {
|
|
39
|
-
for (const owner of snapshot.owners) owner.cancel();
|
|
40
42
|
snapshots.delete(id);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -54,57 +56,61 @@ export function restoreViewerRuntimeState(
|
|
|
54
56
|
const ownerIndex =
|
|
55
57
|
snapshot?.owners.findIndex((owner) => owner.token === message.token && owner.source === event.source) ?? -1;
|
|
56
58
|
if (!snapshot || ownerIndex < 0) return;
|
|
57
|
-
if (message.type === 'mounted') {
|
|
58
|
-
snapshot.owners[ownerIndex]!.mounted();
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (message.type === 'ready') {
|
|
62
|
-
snapshot.owners[ownerIndex]!.ready();
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
59
|
if (message.type !== 'snapshot') return;
|
|
66
60
|
if (!message.state || typeof message.state !== 'object' || Array.isArray(message.state)) return;
|
|
67
61
|
const { ax: _ax, ...state } = message.state as Record<string, unknown>;
|
|
68
62
|
snapshot.state = state;
|
|
69
63
|
// Once the replacement emits, the retired document cannot overwrite it.
|
|
70
|
-
const retired = snapshot.owners.slice(0, ownerIndex);
|
|
71
64
|
snapshot.owners = snapshot.owners.slice(ownerIndex);
|
|
72
|
-
for (const owner of retired) owner.cancel();
|
|
73
65
|
});
|
|
74
66
|
}
|
|
75
67
|
const node = nodes.value.get(nodeId);
|
|
76
68
|
if (!node || signal?.aborted) return Promise.resolve('cancelled');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
// A retained document may load after its authored spec was replaced. It can
|
|
70
|
+
// still acknowledge paint, but cannot own or restore the new spec's drafts.
|
|
71
|
+
const current = node.data.specVersion === authored.data.specVersion && node.data.url === authored.data.url;
|
|
72
|
+
const snapshot: Snapshot | undefined = current
|
|
73
|
+
? (snapshots.get(nodeId) ?? {
|
|
74
|
+
epoch: canvasInteractionEpoch.value,
|
|
75
|
+
version: authored.data.specVersion,
|
|
76
|
+
documentUrl: authored.data.url,
|
|
77
|
+
state: null,
|
|
78
|
+
owners: [],
|
|
79
|
+
})
|
|
80
|
+
: undefined;
|
|
81
|
+
if (snapshot) snapshots.set(nodeId, snapshot);
|
|
85
82
|
const ready = new Promise<boolean | 'cancelled'>((resolve) => {
|
|
86
83
|
const finish = (loaded: boolean | 'cancelled') => {
|
|
87
84
|
clearTimeout(timer);
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
window.removeEventListener('message', receive);
|
|
86
|
+
signal?.removeEventListener('abort', cancel);
|
|
87
|
+
if (loaded !== true && snapshot) snapshot.owners = snapshot.owners.filter((entry) => entry !== owner);
|
|
90
88
|
resolve(loaded);
|
|
91
89
|
};
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
const cancel = () => finish('cancelled');
|
|
91
|
+
const receive = (event: MessageEvent) => {
|
|
92
|
+
if (
|
|
93
|
+
event.source !== source ||
|
|
94
|
+
event.data?.source !== JSON_VIEWER_STATE_SOURCE ||
|
|
95
|
+
event.data?.token !== token ||
|
|
96
|
+
event.data?.nodeId !== nodeId
|
|
97
|
+
)
|
|
98
|
+
return;
|
|
99
|
+
if (event.data.type === 'mounted') clearTimeout(timer);
|
|
100
|
+
if (event.data.type === 'ready') finish(true);
|
|
98
101
|
};
|
|
102
|
+
const owner = { token, source };
|
|
99
103
|
// A missing runtime fails, but a healthy offscreen document can wait for
|
|
100
104
|
// paint indefinitely. Its synchronous mount acknowledgement ends the deadline;
|
|
101
105
|
// only the later frame-gated ready message may promote it.
|
|
102
|
-
const timer = setTimeout(() => finish(false),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
const timer = setTimeout(() => finish(false), VIEWER_LOAD_DEADLINE_MS);
|
|
107
|
+
if (snapshot) {
|
|
108
|
+
snapshot.owners = snapshot.owners.filter((entry) => entry.source !== source).slice(-1);
|
|
109
|
+
snapshot.owners.push(owner);
|
|
110
|
+
}
|
|
111
|
+
window.addEventListener('message', receive);
|
|
112
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
107
113
|
});
|
|
108
|
-
source.postMessage({ source: JSON_VIEWER_STATE_SOURCE, type: 'restore', token, state: snapshot
|
|
114
|
+
source.postMessage({ source: JSON_VIEWER_STATE_SOURCE, type: 'restore', token, state: snapshot?.state ?? null }, '*');
|
|
109
115
|
return ready;
|
|
110
116
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type SseMessage,
|
|
10
10
|
} from '../../shared/semantic-attention.js';
|
|
11
11
|
import {
|
|
12
|
+
attentionHistoryOpen,
|
|
12
13
|
pushAttentionHistory,
|
|
13
14
|
resetAttentionState,
|
|
14
15
|
setAttentionPulse,
|
|
@@ -291,6 +292,10 @@ function entryFromEvent(event: SemanticWatchEvent): AttentionEntry | null {
|
|
|
291
292
|
}
|
|
292
293
|
|
|
293
294
|
function flushToastQueue(): void {
|
|
295
|
+
if (attentionHistoryOpen.value) {
|
|
296
|
+
toastQueue = [];
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
294
299
|
if (toastTimer !== null) return;
|
|
295
300
|
const next = toastQueue.shift() ?? null;
|
|
296
301
|
if (!next) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { batch, computed, signal } from '@preact/signals';
|
|
1
|
+
import { batch, computed, effect, signal } from '@preact/signals';
|
|
2
2
|
import {
|
|
3
3
|
isExcalidrawNode,
|
|
4
4
|
type CanvasAnnotation,
|
|
@@ -159,6 +159,7 @@ export function resetCanvasInteractionState(): void {
|
|
|
159
159
|
}
|
|
160
160
|
batch(() => {
|
|
161
161
|
canvasInteractionEpoch.value += 1;
|
|
162
|
+
groupPath.value = [];
|
|
162
163
|
contextPinsRevision++;
|
|
163
164
|
contextPinsPendingWrites = 0;
|
|
164
165
|
activeNodeId.value = null;
|
|
@@ -226,13 +227,14 @@ export function trackDragMembership(nodeId: string): void {
|
|
|
226
227
|
const cy = node.position.y + node.size.height / 2;
|
|
227
228
|
let candidate: CanvasNodeState | null = null;
|
|
228
229
|
for (const other of nodes.value.values()) {
|
|
230
|
+
if (groupContents.value && !groupContents.value.has(other.id)) continue;
|
|
229
231
|
if (other.type !== 'group' || other.id === parentId || other.collapsed) continue;
|
|
230
232
|
if (contains(other, cx, cy)) candidate = other;
|
|
231
233
|
}
|
|
232
234
|
if (!dropSuppressedForDrag) {
|
|
233
235
|
const next = candidate
|
|
234
236
|
? { nodeId, groupId: candidate.id, mode: 'add' as const }
|
|
235
|
-
: parent && fullyOutside(node, parent)
|
|
237
|
+
: parent && parent.id !== groupPath.value.at(-1)?.id && fullyOutside(node, parent)
|
|
236
238
|
? { nodeId, groupId: parent.id, mode: 'remove' as const }
|
|
237
239
|
: null;
|
|
238
240
|
const current = dragDropTarget.value;
|
|
@@ -266,9 +268,14 @@ export const hiddenByCollapsedGroup = computed(() => {
|
|
|
266
268
|
const hidden = new Map<string, string>();
|
|
267
269
|
for (const node of nodes.value.values()) {
|
|
268
270
|
if (node.type !== 'group' || !node.collapsed) continue;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
if (groupContents.value && !groupContents.value.has(node.id)) continue;
|
|
272
|
+
const pending = new Set(Array.isArray(node.data.children) ? node.data.children : []);
|
|
273
|
+
for (const childId of pending) {
|
|
274
|
+
if (typeof childId !== 'string' || childId === node.id) continue;
|
|
275
|
+
hidden.set(childId, node.id);
|
|
276
|
+
const child = nodes.value.get(childId);
|
|
277
|
+
if (child?.type === 'group' && Array.isArray(child.data.children))
|
|
278
|
+
for (const descendant of child.data.children) pending.add(descendant);
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
return hidden;
|
|
@@ -276,10 +283,131 @@ export const hiddenByCollapsedGroup = computed(() => {
|
|
|
276
283
|
|
|
277
284
|
/** The node that stands in for `id` on the canvas: itself, or its collapsed group's chip. */
|
|
278
285
|
export function visibleNodeFor(id: string): CanvasNodeState | undefined {
|
|
279
|
-
const
|
|
280
|
-
|
|
286
|
+
const visited = new Set<string>();
|
|
287
|
+
while (hiddenByCollapsedGroup.value.has(id) && !visited.has(id)) {
|
|
288
|
+
visited.add(id);
|
|
289
|
+
id = hiddenByCollapsedGroup.value.get(id)!;
|
|
290
|
+
}
|
|
291
|
+
return nodes.value.get(id);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface GroupVisit {
|
|
295
|
+
id: string;
|
|
296
|
+
parent: unknown;
|
|
297
|
+
viewport: ViewportState;
|
|
298
|
+
selection: Set<string>;
|
|
299
|
+
active: string | null;
|
|
281
300
|
}
|
|
282
301
|
|
|
302
|
+
/** Human navigation only. The canonical node/edge maps and agent reads remain whole-board. */
|
|
303
|
+
export const groupPath = signal<GroupVisit[]>([]);
|
|
304
|
+
export const groupContents = computed(() => {
|
|
305
|
+
const id = groupPath.value.at(-1)?.id;
|
|
306
|
+
if (!id) return null;
|
|
307
|
+
const ids = new Set<string>();
|
|
308
|
+
const pending = [id];
|
|
309
|
+
const visited = new Set<string>();
|
|
310
|
+
for (const parent of pending) {
|
|
311
|
+
if (visited.has(parent)) continue;
|
|
312
|
+
visited.add(parent);
|
|
313
|
+
const node = nodes.value.get(parent);
|
|
314
|
+
if (node?.type !== 'group' || !Array.isArray(node.data.children)) continue;
|
|
315
|
+
for (const child of node.data.children) {
|
|
316
|
+
if (typeof child !== 'string' || child === id || !nodes.value.has(child)) continue;
|
|
317
|
+
ids.add(child);
|
|
318
|
+
pending.push(child);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return ids;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
/** The drawing's outside endpoints are view-only stubs, never inserted into nodes. */
|
|
325
|
+
export const groupViewNodes = computed(() => {
|
|
326
|
+
const inside = groupContents.value;
|
|
327
|
+
if (!inside) return nodes.value;
|
|
328
|
+
const view = new Map([...nodes.value].filter(([id]) => inside.has(id)));
|
|
329
|
+
const outside = new Set<string>();
|
|
330
|
+
for (const edge of edges.value.values()) {
|
|
331
|
+
if (edge.toBoardId || inside.has(edge.from) === inside.has(edge.to)) continue;
|
|
332
|
+
outside.add(inside.has(edge.from) ? edge.to : edge.from);
|
|
333
|
+
}
|
|
334
|
+
const root = nodes.value.get(groupPath.value.at(-1)!.id);
|
|
335
|
+
const bounds = [...view.values()];
|
|
336
|
+
const left = bounds.length ? Math.min(...bounds.map((node) => node.position.x)) : (root?.position.x ?? 0);
|
|
337
|
+
const bottom = bounds.length
|
|
338
|
+
? Math.max(...bounds.map((node) => node.position.y + node.size.height))
|
|
339
|
+
: (root?.position.y ?? 0) + 80;
|
|
340
|
+
[...outside].sort().forEach((id, index) => {
|
|
341
|
+
const node = nodes.value.get(id);
|
|
342
|
+
if (node)
|
|
343
|
+
view.set(id, {
|
|
344
|
+
...node,
|
|
345
|
+
position: { x: left + (index % 3) * 194, y: bottom + 88 + Math.floor(index / 3) * 84 },
|
|
346
|
+
size: { width: 170, height: 60 },
|
|
347
|
+
collapsed: false,
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
return view;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
export function leaveGroup(depth = groupPath.value.length - 1): void {
|
|
354
|
+
const visit = groupPath.value[depth];
|
|
355
|
+
if (!visit) return;
|
|
356
|
+
cancelViewportAnimation();
|
|
357
|
+
batch(() => {
|
|
358
|
+
groupPath.value = groupPath.value.slice(0, depth);
|
|
359
|
+
viewport.value = visit.viewport;
|
|
360
|
+
selectedNodeIds.value = filterNodeIdSet(visit.selection, nodes.value);
|
|
361
|
+
activeNodeId.value = visit.active && nodes.value.has(visit.active) ? visit.active : null;
|
|
362
|
+
selectedEdgeId.value = null;
|
|
363
|
+
});
|
|
364
|
+
void updateViewportFromClient(visit.viewport, { recordHistory: false });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function openGroup(id: string, fit = true): void {
|
|
368
|
+
const node = nodes.value.get(id);
|
|
369
|
+
if (node?.type !== 'group' || groupPath.value.some((visit) => visit.id === id)) return;
|
|
370
|
+
if (groupContents.value && !groupContents.value.has(id)) leaveGroup(0);
|
|
371
|
+
cancelViewportAnimation();
|
|
372
|
+
batch(() => {
|
|
373
|
+
groupPath.value = [
|
|
374
|
+
...groupPath.value,
|
|
375
|
+
{
|
|
376
|
+
id,
|
|
377
|
+
parent: node.data.parentGroup,
|
|
378
|
+
viewport: { ...viewport.value },
|
|
379
|
+
selection: new Set(selectedNodeIds.value),
|
|
380
|
+
active: activeNodeId.value,
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
clearSelection();
|
|
384
|
+
activeNodeId.value = null;
|
|
385
|
+
});
|
|
386
|
+
// Wait for the breadcrumb/legend to paint before measuring their screen insets.
|
|
387
|
+
if (!fit) return;
|
|
388
|
+
afterGroupView(() => {
|
|
389
|
+
const area = canvasArea();
|
|
390
|
+
fitAll(area.width, area.height);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
effect(() => {
|
|
395
|
+
const path = groupPath.value;
|
|
396
|
+
const map = nodes.value;
|
|
397
|
+
const invalid = path.findIndex((visit) => {
|
|
398
|
+
const node = map.get(visit.id);
|
|
399
|
+
return node?.type !== 'group' || node.data.parentGroup !== visit.parent;
|
|
400
|
+
});
|
|
401
|
+
if (invalid >= 0) leaveGroup(invalid);
|
|
402
|
+
const inside = groupContents.value;
|
|
403
|
+
if (inside) {
|
|
404
|
+
const selected = selectedNodeIds.value;
|
|
405
|
+
if ([...selected].some((id) => !inside.has(id)))
|
|
406
|
+
selectedNodeIds.value = new Set([...selected].filter((id) => inside.has(id)));
|
|
407
|
+
if (activeNodeId.value && !inside.has(activeNodeId.value)) activeNodeId.value = null;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
283
411
|
/** Collapsed groups render as a chip; children keep their positions for restore. */
|
|
284
412
|
export function groupsOfSelection(): string[] {
|
|
285
413
|
const ids = new Set<string>();
|
|
@@ -637,18 +765,22 @@ export function resizeNode(id: string, size: { width: number; height: number }):
|
|
|
637
765
|
updateNode(id, { size });
|
|
638
766
|
}
|
|
639
767
|
|
|
640
|
-
export function bringToFront(id: string): void {
|
|
768
|
+
export function bringToFront(id: string, persist = true): void {
|
|
641
769
|
const existing = nodes.value.get(id);
|
|
642
770
|
if (!existing) return;
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (typeof childId === 'string' && nodes.value.has(childId)) updateNode(childId, { zIndex: maxZ++ });
|
|
649
|
-
}
|
|
771
|
+
const updates = [{ id, zIndex: maxZ++ }];
|
|
772
|
+
// A group is a frame: it must never stack above its own children.
|
|
773
|
+
if (existing.type === 'group' && Array.isArray(existing.data.children)) {
|
|
774
|
+
for (const childId of existing.data.children) {
|
|
775
|
+
if (typeof childId === 'string' && nodes.value.has(childId)) updates.push({ id: childId, zIndex: maxZ++ });
|
|
650
776
|
}
|
|
777
|
+
}
|
|
778
|
+
batch(() => {
|
|
779
|
+
for (const update of updates) updateNode(update.id, { zIndex: update.zIndex });
|
|
651
780
|
});
|
|
781
|
+
// Title-bar gestures commit stacking with geometry at pointer-up. A separate
|
|
782
|
+
// drag-start write would broadcast an old layout over the in-progress move.
|
|
783
|
+
if (persist) void pushCanvasUpdate(updates, { recordHistory: false });
|
|
652
784
|
activeNodeId.value = id;
|
|
653
785
|
}
|
|
654
786
|
|
|
@@ -817,17 +949,31 @@ export function cancelViewportAnimation(): void {
|
|
|
817
949
|
}
|
|
818
950
|
}
|
|
819
951
|
|
|
952
|
+
/** Share the camera slot so newer navigation and direct gestures cancel queued fits. */
|
|
953
|
+
function afterGroupView(action: () => void): void {
|
|
954
|
+
cancelViewportAnimation();
|
|
955
|
+
const epoch = canvasInteractionEpoch.value;
|
|
956
|
+
const path = groupPath.value;
|
|
957
|
+
animationId = requestAnimationFrame(() => {
|
|
958
|
+
animationId = null;
|
|
959
|
+
if (epoch === canvasInteractionEpoch.value && path === groupPath.value) action();
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
820
963
|
// ── Persistence ───────────────────────────────────────────────
|
|
821
964
|
const STORAGE_KEY = 'pmx-canvas-layout';
|
|
822
965
|
|
|
823
|
-
export function persistLayout(options: { recordHistory?: boolean } = {}): void {
|
|
966
|
+
export function persistLayout(options: { recordHistory?: boolean; raisedNodeId?: string } = {}): void {
|
|
824
967
|
try {
|
|
825
968
|
const allNodes = Array.from(nodes.value.values());
|
|
969
|
+
const raised = options.raisedNodeId ? nodes.value.get(options.raisedNodeId) : undefined;
|
|
970
|
+
const raisedChildren = raised?.type === 'group' && Array.isArray(raised.data.children) ? raised.data.children : [];
|
|
826
971
|
const nodeUpdates = allNodes.map((n) => ({
|
|
827
972
|
id: n.id,
|
|
828
973
|
position: n.position,
|
|
829
974
|
size: n.size,
|
|
830
975
|
collapsed: n.collapsed,
|
|
976
|
+
...(n.id === raised?.id || raisedChildren.includes(n.id) ? { zIndex: n.zIndex } : {}),
|
|
831
977
|
}));
|
|
832
978
|
const layout = {
|
|
833
979
|
viewport: viewport.value,
|
|
@@ -896,7 +1042,9 @@ export function restoreLayout(): Map<string, Partial<CanvasNodeState>> | null {
|
|
|
896
1042
|
|
|
897
1043
|
// ── Fit all ───────────────────────────────────────────────────
|
|
898
1044
|
export function fitAll(containerW: number, containerH: number): void {
|
|
899
|
-
const all = Array.from(
|
|
1045
|
+
const all = Array.from(groupViewNodes.value.values());
|
|
1046
|
+
const group = nodes.value.get(groupPath.value.at(-1)?.id ?? '');
|
|
1047
|
+
if (!all.length && group) all.push(group);
|
|
900
1048
|
if (all.length === 0) return;
|
|
901
1049
|
|
|
902
1050
|
let minX = Number.POSITIVE_INFINITY;
|
|
@@ -909,6 +1057,12 @@ export function fitAll(containerW: number, containerH: number): void {
|
|
|
909
1057
|
maxX = Math.max(maxX, n.position.x + n.size.width);
|
|
910
1058
|
maxY = Math.max(maxY, n.position.y + n.size.height);
|
|
911
1059
|
}
|
|
1060
|
+
if (groupPath.value.length) {
|
|
1061
|
+
minX -= 24;
|
|
1062
|
+
minY -= 40;
|
|
1063
|
+
maxX += 24;
|
|
1064
|
+
maxY += 24;
|
|
1065
|
+
}
|
|
912
1066
|
|
|
913
1067
|
// Screen-space fit (architecture rule 9): the margins that must clear the
|
|
914
1068
|
// floating chrome are SCREEN constants. The old world-space PAD shrank with
|
|
@@ -923,17 +1077,41 @@ export function fitAll(containerW: number, containerH: number): void {
|
|
|
923
1077
|
const cx = (minX + maxX) / 2;
|
|
924
1078
|
const cy = (minY + maxY) / 2;
|
|
925
1079
|
|
|
926
|
-
animateViewport(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1080
|
+
animateViewport(
|
|
1081
|
+
{
|
|
1082
|
+
x: insets.left + usableW / 2 - cx * scale,
|
|
1083
|
+
y: insets.top + usableH / 2 - cy * scale,
|
|
1084
|
+
scale,
|
|
1085
|
+
},
|
|
1086
|
+
300,
|
|
1087
|
+
{ recordHistory: groupPath.value.length === 0 },
|
|
1088
|
+
);
|
|
931
1089
|
}
|
|
932
1090
|
|
|
933
1091
|
// ── Focus node ────────────────────────────────────────────────
|
|
934
|
-
export function focusNode(id: string, options: { recordHistory?: boolean } = {}): void {
|
|
1092
|
+
export function focusNode(id: string, options: { recordHistory?: boolean; persistStacking?: boolean } = {}): void {
|
|
935
1093
|
const node = nodes.value.get(id);
|
|
936
1094
|
if (!node) return;
|
|
1095
|
+
cancelViewportAnimation();
|
|
1096
|
+
const previousPath = groupPath.value;
|
|
1097
|
+
if (groupContents.value && !groupContents.value.has(id)) leaveGroup(0);
|
|
1098
|
+
// Reveal collapsed targets through view state, without changing authored collapse flags.
|
|
1099
|
+
const ancestors: string[] = [];
|
|
1100
|
+
let parent = node.data.parentGroup;
|
|
1101
|
+
while (typeof parent === 'string' && !ancestors.includes(parent)) {
|
|
1102
|
+
ancestors.push(parent);
|
|
1103
|
+
parent = nodes.value.get(parent)?.data.parentGroup;
|
|
1104
|
+
}
|
|
1105
|
+
for (const ancestor of ancestors.reverse()) {
|
|
1106
|
+
if (nodes.value.get(ancestor)?.collapsed && !groupPath.value.some((visit) => visit.id === ancestor))
|
|
1107
|
+
openGroup(ancestor, false);
|
|
1108
|
+
}
|
|
1109
|
+
bringToFront(id, options.persistStacking !== false);
|
|
1110
|
+
if (groupPath.value.length || previousPath !== groupPath.value) afterGroupView(() => focusNodeCamera(node, options));
|
|
1111
|
+
else focusNodeCamera(node, options);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function focusNodeCamera(node: CanvasNodeState, options: { recordHistory?: boolean }): void {
|
|
937
1115
|
const v = viewport.value;
|
|
938
1116
|
// Same screen-space frame as fitAll: viewport x/y are CONTAINER-relative
|
|
939
1117
|
// (screen = world*scale + viewport inside the canvas region), so the centre
|
|
@@ -959,17 +1137,15 @@ export function focusNode(id: string, options: { recordHistory?: boolean } = {})
|
|
|
959
1137
|
300,
|
|
960
1138
|
options,
|
|
961
1139
|
);
|
|
962
|
-
bringToFront(id);
|
|
963
1140
|
}
|
|
964
1141
|
|
|
965
1142
|
// ── Cycle focus ───────────────────────────────────────────────
|
|
966
1143
|
export function cycleActiveNode(direction: 1 | -1 = 1): void {
|
|
967
|
-
const all = Array.from(nodes.value.keys());
|
|
1144
|
+
const all = Array.from(nodes.value.keys()).filter((id) => !groupContents.value || groupContents.value.has(id));
|
|
968
1145
|
if (all.length === 0) return;
|
|
969
1146
|
const currentIdx = activeNodeId.value ? all.indexOf(activeNodeId.value) : -1;
|
|
970
1147
|
const nextIdx = (currentIdx + direction + all.length) % all.length;
|
|
971
1148
|
const nextId = all[nextIdx];
|
|
972
|
-
bringToFront(nextId);
|
|
973
1149
|
focusNode(nextId);
|
|
974
1150
|
}
|
|
975
1151
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, signal } from '@preact/signals';
|
|
2
2
|
import {
|
|
3
3
|
HUMAN_CURSOR_THROTTLE_MS,
|
|
4
|
+
HUMAN_NAME_MAX_LENGTH,
|
|
4
5
|
type HumanPresence,
|
|
5
6
|
type HumanPresenceSnapshot,
|
|
6
7
|
} from '../../shared/human-presence.js';
|
|
@@ -34,12 +35,12 @@ export const humanClientId = sessionScoped(
|
|
|
34
35
|
|
|
35
36
|
function resolveName(): string {
|
|
36
37
|
try {
|
|
37
|
-
const fromQuery = new URLSearchParams(window.location.search).get('name')?.trim();
|
|
38
|
+
const fromQuery = new URLSearchParams(window.location.search).get('name')?.trim().slice(0, HUMAN_NAME_MAX_LENGTH);
|
|
38
39
|
if (fromQuery) {
|
|
39
40
|
window.localStorage.setItem('pmx-canvas-human-name', fromQuery);
|
|
40
41
|
return fromQuery;
|
|
41
42
|
}
|
|
42
|
-
const stored = window.localStorage.getItem('pmx-canvas-human-name')?.trim();
|
|
43
|
+
const stored = window.localStorage.getItem('pmx-canvas-human-name')?.trim().slice(0, HUMAN_NAME_MAX_LENGTH);
|
|
43
44
|
if (stored) return stored;
|
|
44
45
|
} catch {
|
|
45
46
|
// storage unavailable: fall through to a generated name
|
|
@@ -51,6 +52,25 @@ export const humanName = signal<string>(resolveName());
|
|
|
51
52
|
export const humans = signal<HumanPresence[]>([]);
|
|
52
53
|
export const otherHumans = computed(() => humans.value.filter((human) => human.clientId !== humanClientId));
|
|
53
54
|
|
|
55
|
+
/** Change the display label, never the per-tab identity or anyone's access. */
|
|
56
|
+
export function setHumanName(name: string): void {
|
|
57
|
+
const value = name.trim().slice(0, HUMAN_NAME_MAX_LENGTH);
|
|
58
|
+
if (!value) return;
|
|
59
|
+
humanName.value = value;
|
|
60
|
+
try {
|
|
61
|
+
window.localStorage.setItem('pmx-canvas-human-name', value);
|
|
62
|
+
} catch {
|
|
63
|
+
// Storage can be blocked in embedded panes; the current tab still has its name.
|
|
64
|
+
}
|
|
65
|
+
// A link's initial name must not undo an explicit rename on reload.
|
|
66
|
+
const url = new URL(window.location.href);
|
|
67
|
+
if (url.searchParams.has('name')) {
|
|
68
|
+
url.searchParams.delete('name');
|
|
69
|
+
window.history.replaceState(window.history.state, '', url);
|
|
70
|
+
}
|
|
71
|
+
post({ cursor: lastCursor, grabbingNodeId: grabbingNodeId.value });
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
/** Nodes a human took over mid-edit → that human's name, shown as the yield pill for a moment. */
|
|
55
75
|
export const yieldedNodes = signal<Map<string, string>>(new Map());
|
|
56
76
|
|
|
@@ -250,6 +250,7 @@ export async function pushCanvasUpdate(
|
|
|
250
250
|
position?: { x: number; y: number };
|
|
251
251
|
size?: { width: number; height: number };
|
|
252
252
|
collapsed?: boolean;
|
|
253
|
+
zIndex?: number;
|
|
253
254
|
}>,
|
|
254
255
|
options: { recordHistory?: boolean } = {},
|
|
255
256
|
): Promise<void> {
|
|
@@ -299,11 +300,13 @@ export async function createNodeFromClient(opts: {
|
|
|
299
300
|
type?: string;
|
|
300
301
|
title?: string;
|
|
301
302
|
content?: string;
|
|
303
|
+
data?: Record<string, unknown>;
|
|
302
304
|
x?: number;
|
|
303
305
|
y?: number;
|
|
304
306
|
width?: number;
|
|
305
307
|
height?: number;
|
|
306
308
|
boardId?: string;
|
|
309
|
+
expectedBoardId?: string;
|
|
307
310
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
308
311
|
return requestJson(
|
|
309
312
|
'createNodeFromClient',
|
|
@@ -489,7 +492,7 @@ export async function reportClientViewportSize(): Promise<void> {
|
|
|
489
492
|
if (isHostedWorkbench() && !boardsLoaded.value) return;
|
|
490
493
|
const size = clientWindowSize();
|
|
491
494
|
if (!('clientWidth' in size)) return;
|
|
492
|
-
|
|
495
|
+
const init: RequestInit = {
|
|
493
496
|
method: 'POST',
|
|
494
497
|
headers: { 'Content-Type': 'application/json' },
|
|
495
498
|
body: JSON.stringify({
|
|
@@ -497,7 +500,15 @@ export async function reportClientViewportSize(): Promise<void> {
|
|
|
497
500
|
...(boardsLoaded.value ? { expectedBoardId: activeBoardId.value } : {}),
|
|
498
501
|
recordHistory: false,
|
|
499
502
|
}),
|
|
500
|
-
}
|
|
503
|
+
};
|
|
504
|
+
try {
|
|
505
|
+
const res = await workbenchFetch('/api/canvas/viewport', withWorkbenchMarker(init));
|
|
506
|
+
// Resize observations can cross a board switch. Discard that stale report,
|
|
507
|
+
// without telling the human an edit failed; actual camera writes still report refusals.
|
|
508
|
+
if (!res.ok && res.status !== 409) reportRefusedWrite(res, init, await readJsonBody(res));
|
|
509
|
+
} catch (error) {
|
|
510
|
+
logRequestError('reportClientViewportSize', error);
|
|
511
|
+
}
|
|
501
512
|
}
|
|
502
513
|
|
|
503
514
|
// ── Group API ─────────────────────────────────────────────────
|
|
@@ -611,6 +622,7 @@ export async function updateEdgeFromClient(
|
|
|
611
622
|
style?: string;
|
|
612
623
|
animated?: boolean;
|
|
613
624
|
},
|
|
625
|
+
expectedBoardId = activeBoardId.value,
|
|
614
626
|
): Promise<{ ok: boolean }> {
|
|
615
627
|
return requestJson(
|
|
616
628
|
'updateEdgeFromClient',
|
|
@@ -619,13 +631,16 @@ export async function updateEdgeFromClient(
|
|
|
619
631
|
{
|
|
620
632
|
method: 'PATCH',
|
|
621
633
|
headers: { 'Content-Type': 'application/json' },
|
|
622
|
-
body: JSON.stringify(patch),
|
|
634
|
+
body: JSON.stringify({ ...patch, expectedBoardId }),
|
|
623
635
|
},
|
|
624
636
|
);
|
|
625
637
|
}
|
|
626
638
|
|
|
627
639
|
/** Remove a canvas edge via the server. */
|
|
628
|
-
export async function removeEdgeFromClient(
|
|
640
|
+
export async function removeEdgeFromClient(
|
|
641
|
+
edgeId: string,
|
|
642
|
+
expectedBoardId = activeBoardId.value,
|
|
643
|
+
): Promise<{ ok: boolean }> {
|
|
629
644
|
return requestJson(
|
|
630
645
|
'removeEdgeFromClient',
|
|
631
646
|
'/api/canvas/edge',
|
|
@@ -633,7 +648,7 @@ export async function removeEdgeFromClient(edgeId: string): Promise<{ ok: boolea
|
|
|
633
648
|
{
|
|
634
649
|
method: 'DELETE',
|
|
635
650
|
headers: { 'Content-Type': 'application/json' },
|
|
636
|
-
body: JSON.stringify({ edge_id: edgeId }),
|
|
651
|
+
body: JSON.stringify({ edge_id: edgeId, expectedBoardId }),
|
|
637
652
|
},
|
|
638
653
|
);
|
|
639
654
|
}
|