pmx-canvas 0.11.0 → 0.12.1
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 +4 -1
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +440 -18
- package/CHANGELOG.md +51 -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 +267 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1168 -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 +1839 -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 +301 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +117 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +908 -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-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
- package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
- package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
- package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
- package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
- 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-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
- package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
- package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
- package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
- package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
- package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
- package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
- package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
- package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
- package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
- package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
- 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-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
- package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
- package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
- package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
- package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
- package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
- package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
- package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
- package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
- package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
- package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
- package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
- package/dist/canvas/chunks/index-frvta4tm.js +2 -0
- package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
- package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
- package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
- package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
- package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
- package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
- package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
- package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
- package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
- package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
- package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
- package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
- package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
- package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
- package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
- package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
- package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
- package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
- package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
- package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
- 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-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
- package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
- package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
- package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
- package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
- package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
- package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
- package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
- 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-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
- package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
- package/dist/canvas/global.css +94 -1
- package/dist/canvas/index.js +154 -145
- package/dist/canvas/mcp-app.html +600 -498
- package/dist/json-render/index.css +1 -1
- package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
- package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
- package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
- package/dist/types/client/nodes/PromptNode.d.ts +1 -0
- package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
- package/dist/types/client/nodes/WorkCard.d.ts +7 -0
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +15 -0
- package/dist/types/client/state/human-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +4 -2
- 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/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 +84 -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 +16 -5
- 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/registry.d.ts +2 -0
- 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 -0
- package/dist/types/shared/human-presence.d.ts +1 -0
- package/dist/types/shared/lifecycle-status.d.ts +2 -0
- package/docs/RELEASE.md +21 -14
- package/docs/agent-plugin.md +22 -2
- package/docs/amp-plugin.md +270 -0
- package/docs/cli.md +42 -3
- package/docs/design/GuestName.dc.html +50 -0
- package/docs/design/PluginLogo.svg +7 -0
- package/docs/design/WorkCards.dc.html +39 -0
- package/docs/design/canvas.json +16 -0
- package/docs/design/plugin-logo.png +0 -0
- package/docs/design.md +111 -8
- package/docs/evals/agents-md-sections.md +38 -0
- package/docs/http-api.md +128 -10
- package/docs/mcp.md +61 -9
- package/docs/node-types.md +71 -1
- package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
- package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
- package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
- package/docs/product-vision-2026-09.md +33 -17
- package/docs/sdk.md +18 -0
- package/package.json +6 -3
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +4 -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 +440 -18
- 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 +248 -842
- package/skills/pmx-canvas/evals/evals.json +110 -5
- package/skills/pmx-canvas/references/full-reference.md +142 -28
- package/skills/pmx-canvas/references/html-primitives.md +7 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +61 -11
- package/skills/pmx-canvas/references/tours-and-recording.md +39 -6
- package/skills/pmx-canvas/references/workbench-workflows.md +908 -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 -75
- package/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/web-artifacts-builder/SKILL.md +6 -0
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +2 -2
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +8 -8
- 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/BoardMoveDialog.tsx +225 -0
- package/src/client/canvas/CanvasNode.tsx +45 -2
- package/src/client/canvas/CanvasViewport.tsx +38 -9
- package/src/client/canvas/ContextMenu.tsx +34 -12
- package/src/client/canvas/EdgeEditor.tsx +149 -0
- package/src/client/canvas/EdgeLayer.tsx +87 -16
- package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
- package/src/client/canvas/GroupNavigation.tsx +85 -0
- 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 +30 -0
- package/src/client/canvas/canvas-area.ts +1 -1
- package/src/client/nodes/ExtAppFrame.tsx +26 -10
- package/src/client/nodes/MarkdownNode.tsx +6 -7
- package/src/client/nodes/PromptNode.tsx +32 -3
- package/src/client/nodes/StatusNode.tsx +12 -0
- package/src/client/nodes/StatusSummary.tsx +11 -0
- package/src/client/nodes/ViewerFrame.tsx +21 -10
- package/src/client/nodes/WorkCard.tsx +175 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
- 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 +185 -15
- package/src/client/state/human-store.ts +22 -2
- package/src/client/state/intent-bridge.ts +19 -5
- package/src/client/state/relation-store.ts +127 -1
- package/src/client/theme/global.css +94 -1
- package/src/client/types.ts +1 -0
- 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 +19 -6
- package/src/server/board-local.ts +20 -0
- package/src/server/board-move.ts +297 -0
- package/src/server/canvas-db.ts +262 -160
- 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 +792 -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 +35 -15
- package/src/server/file-watcher.ts +30 -10
- package/src/server/human-presence.ts +18 -5
- package/src/server/index.ts +35 -15
- 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 +15 -0
- 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 +44 -3
- 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/registry.ts +49 -4
- package/src/server/server.ts +62 -16
- package/src/server/tour-control.ts +37 -12
- package/src/server/trace-manager.ts +4 -0
- package/src/server/web-artifacts/scripts/bundle-artifact.sh +2 -2
- package/src/server/web-artifacts/scripts/init-artifact.sh +8 -8
- 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 +22 -0
- package/src/shared/human-presence.ts +1 -0
- package/src/shared/lifecycle-status.ts +2 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
- package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
- package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
- package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
- package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
- package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
- package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
- package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
- package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
- package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
- package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
- package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
- package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
|
@@ -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;
|
|
300
|
+
}
|
|
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
|
+
});
|
|
281
392
|
}
|
|
282
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>();
|
|
@@ -821,6 +949,17 @@ export function cancelViewportAnimation(): void {
|
|
|
821
949
|
}
|
|
822
950
|
}
|
|
823
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
|
+
|
|
824
963
|
// ── Persistence ───────────────────────────────────────────────
|
|
825
964
|
const STORAGE_KEY = 'pmx-canvas-layout';
|
|
826
965
|
|
|
@@ -903,7 +1042,9 @@ export function restoreLayout(): Map<string, Partial<CanvasNodeState>> | null {
|
|
|
903
1042
|
|
|
904
1043
|
// ── Fit all ───────────────────────────────────────────────────
|
|
905
1044
|
export function fitAll(containerW: number, containerH: number): void {
|
|
906
|
-
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);
|
|
907
1048
|
if (all.length === 0) return;
|
|
908
1049
|
|
|
909
1050
|
let minX = Number.POSITIVE_INFINITY;
|
|
@@ -916,6 +1057,12 @@ export function fitAll(containerW: number, containerH: number): void {
|
|
|
916
1057
|
maxX = Math.max(maxX, n.position.x + n.size.width);
|
|
917
1058
|
maxY = Math.max(maxY, n.position.y + n.size.height);
|
|
918
1059
|
}
|
|
1060
|
+
if (groupPath.value.length) {
|
|
1061
|
+
minX -= 24;
|
|
1062
|
+
minY -= 40;
|
|
1063
|
+
maxX += 24;
|
|
1064
|
+
maxY += 24;
|
|
1065
|
+
}
|
|
919
1066
|
|
|
920
1067
|
// Screen-space fit (architecture rule 9): the margins that must clear the
|
|
921
1068
|
// floating chrome are SCREEN constants. The old world-space PAD shrank with
|
|
@@ -930,17 +1077,41 @@ export function fitAll(containerW: number, containerH: number): void {
|
|
|
930
1077
|
const cx = (minX + maxX) / 2;
|
|
931
1078
|
const cy = (minY + maxY) / 2;
|
|
932
1079
|
|
|
933
|
-
animateViewport(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
+
);
|
|
938
1089
|
}
|
|
939
1090
|
|
|
940
1091
|
// ── Focus node ────────────────────────────────────────────────
|
|
941
1092
|
export function focusNode(id: string, options: { recordHistory?: boolean; persistStacking?: boolean } = {}): void {
|
|
942
1093
|
const node = nodes.value.get(id);
|
|
943
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 {
|
|
944
1115
|
const v = viewport.value;
|
|
945
1116
|
// Same screen-space frame as fitAll: viewport x/y are CONTAINER-relative
|
|
946
1117
|
// (screen = world*scale + viewport inside the canvas region), so the centre
|
|
@@ -966,12 +1137,11 @@ export function focusNode(id: string, options: { recordHistory?: boolean; persis
|
|
|
966
1137
|
300,
|
|
967
1138
|
options,
|
|
968
1139
|
);
|
|
969
|
-
bringToFront(id, options.persistStacking !== false);
|
|
970
1140
|
}
|
|
971
1141
|
|
|
972
1142
|
// ── Cycle focus ───────────────────────────────────────────────
|
|
973
1143
|
export function cycleActiveNode(direction: 1 | -1 = 1): void {
|
|
974
|
-
const all = Array.from(nodes.value.keys());
|
|
1144
|
+
const all = Array.from(nodes.value.keys()).filter((id) => !groupContents.value || groupContents.value.has(id));
|
|
975
1145
|
if (all.length === 0) return;
|
|
976
1146
|
const currentIdx = activeNodeId.value ? all.indexOf(activeNodeId.value) : -1;
|
|
977
1147
|
const nextIdx = (currentIdx + direction + all.length) % all.length;
|
|
@@ -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
|
|
|
@@ -300,11 +300,13 @@ export async function createNodeFromClient(opts: {
|
|
|
300
300
|
type?: string;
|
|
301
301
|
title?: string;
|
|
302
302
|
content?: string;
|
|
303
|
+
data?: Record<string, unknown>;
|
|
303
304
|
x?: number;
|
|
304
305
|
y?: number;
|
|
305
306
|
width?: number;
|
|
306
307
|
height?: number;
|
|
307
308
|
boardId?: string;
|
|
309
|
+
expectedBoardId?: string;
|
|
308
310
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
309
311
|
return requestJson(
|
|
310
312
|
'createNodeFromClient',
|
|
@@ -490,7 +492,7 @@ export async function reportClientViewportSize(): Promise<void> {
|
|
|
490
492
|
if (isHostedWorkbench() && !boardsLoaded.value) return;
|
|
491
493
|
const size = clientWindowSize();
|
|
492
494
|
if (!('clientWidth' in size)) return;
|
|
493
|
-
|
|
495
|
+
const init: RequestInit = {
|
|
494
496
|
method: 'POST',
|
|
495
497
|
headers: { 'Content-Type': 'application/json' },
|
|
496
498
|
body: JSON.stringify({
|
|
@@ -498,7 +500,15 @@ export async function reportClientViewportSize(): Promise<void> {
|
|
|
498
500
|
...(boardsLoaded.value ? { expectedBoardId: activeBoardId.value } : {}),
|
|
499
501
|
recordHistory: false,
|
|
500
502
|
}),
|
|
501
|
-
}
|
|
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
|
+
}
|
|
502
512
|
}
|
|
503
513
|
|
|
504
514
|
// ── Group API ─────────────────────────────────────────────────
|
|
@@ -612,6 +622,7 @@ export async function updateEdgeFromClient(
|
|
|
612
622
|
style?: string;
|
|
613
623
|
animated?: boolean;
|
|
614
624
|
},
|
|
625
|
+
expectedBoardId = activeBoardId.value,
|
|
615
626
|
): Promise<{ ok: boolean }> {
|
|
616
627
|
return requestJson(
|
|
617
628
|
'updateEdgeFromClient',
|
|
@@ -620,13 +631,16 @@ export async function updateEdgeFromClient(
|
|
|
620
631
|
{
|
|
621
632
|
method: 'PATCH',
|
|
622
633
|
headers: { 'Content-Type': 'application/json' },
|
|
623
|
-
body: JSON.stringify(patch),
|
|
634
|
+
body: JSON.stringify({ ...patch, expectedBoardId }),
|
|
624
635
|
},
|
|
625
636
|
);
|
|
626
637
|
}
|
|
627
638
|
|
|
628
639
|
/** Remove a canvas edge via the server. */
|
|
629
|
-
export async function removeEdgeFromClient(
|
|
640
|
+
export async function removeEdgeFromClient(
|
|
641
|
+
edgeId: string,
|
|
642
|
+
expectedBoardId = activeBoardId.value,
|
|
643
|
+
): Promise<{ ok: boolean }> {
|
|
630
644
|
return requestJson(
|
|
631
645
|
'removeEdgeFromClient',
|
|
632
646
|
'/api/canvas/edge',
|
|
@@ -634,7 +648,7 @@ export async function removeEdgeFromClient(edgeId: string): Promise<{ ok: boolea
|
|
|
634
648
|
{
|
|
635
649
|
method: 'DELETE',
|
|
636
650
|
headers: { 'Content-Type': 'application/json' },
|
|
637
|
-
body: JSON.stringify({ edge_id: edgeId }),
|
|
651
|
+
body: JSON.stringify({ edge_id: edgeId, expectedBoardId }),
|
|
638
652
|
},
|
|
639
653
|
);
|
|
640
654
|
}
|
|
@@ -1,9 +1,123 @@
|
|
|
1
1
|
import { computed, effect, signal } from '@preact/signals';
|
|
2
2
|
import { relationKind, relationPreset, relationSourceEnd } from '../../shared/relations.js';
|
|
3
|
+
import { memoryKey } from '../../shared/memory-graph.js';
|
|
3
4
|
import { activeBoard, activeBoardId } from './boards-store';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
activeNodeId,
|
|
7
|
+
edges,
|
|
8
|
+
nodes,
|
|
9
|
+
selectedNodeIds,
|
|
10
|
+
focusNode,
|
|
11
|
+
collapseExpandedNode,
|
|
12
|
+
canvasInteractionEpoch,
|
|
13
|
+
} from './canvas-store';
|
|
14
|
+
import { cardQualification } from './knowledge-store';
|
|
15
|
+
import type { CanvasEdge } from '../types';
|
|
5
16
|
|
|
6
17
|
export const relationFilter = signal<{ boardId: string; kind: string } | null>(null);
|
|
18
|
+
const inspection = signal<{ boardId: string; nodeId: string; mode: 'lineage' | 'connections'; epoch: number } | null>(
|
|
19
|
+
null,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export function showRelationsOnCanvas(nodeId: string, mode: 'lineage' | 'connections'): void {
|
|
23
|
+
const boardId = activeBoardId.value;
|
|
24
|
+
if (!boardId) return;
|
|
25
|
+
collapseExpandedNode();
|
|
26
|
+
selectedNodeIds.value = new Set();
|
|
27
|
+
relationFilter.value = null;
|
|
28
|
+
focusNode(nodeId, { recordHistory: false, persistStacking: false });
|
|
29
|
+
activeNodeId.value = nodeId;
|
|
30
|
+
inspection.value = { boardId, nodeId, mode, epoch: canvasInteractionEpoch.value };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
effect(() => {
|
|
34
|
+
const value = inspection.value;
|
|
35
|
+
if (
|
|
36
|
+
value &&
|
|
37
|
+
(value.boardId !== activeBoardId.value ||
|
|
38
|
+
value.nodeId !== activeNodeId.value ||
|
|
39
|
+
value.epoch !== canvasInteractionEpoch.value ||
|
|
40
|
+
relationFilter.value)
|
|
41
|
+
)
|
|
42
|
+
inspection.value = null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** Only qualified dependency/lineage sources warn; citations and illustrated loops never do. */
|
|
46
|
+
export function relationWarning(edge: CanvasEdge): string[] {
|
|
47
|
+
const boardId = activeBoardId.value;
|
|
48
|
+
const kind = relationKind(edge);
|
|
49
|
+
if (!boardId || !['depends-on', 'derived-from'].includes(kind)) return [];
|
|
50
|
+
const from = { boardId, cardId: edge.from };
|
|
51
|
+
const to = { boardId: edge.toBoardId ?? boardId, cardId: edge.to };
|
|
52
|
+
const source = relationSourceEnd(edge) === 'from' ? from : to;
|
|
53
|
+
const dependent = relationSourceEnd(edge) === 'from' ? to : from;
|
|
54
|
+
if (dependent.boardId !== boardId) return [];
|
|
55
|
+
return (
|
|
56
|
+
cardQualification(dependent.cardId)?.sourceWarnings?.[
|
|
57
|
+
JSON.stringify([boardId, edge.id, kind, memoryKey(source)])
|
|
58
|
+
] ?? []
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const dependencyAnalysis = computed(() => {
|
|
63
|
+
const links = [...edges.value.values()].filter((edge) => !edge.toBoardId && relationKind(edge) === 'depends-on');
|
|
64
|
+
const forward = new Map<string, Set<string>>();
|
|
65
|
+
const reverse = new Map<string, Set<string>>();
|
|
66
|
+
for (const edge of links) {
|
|
67
|
+
if (!forward.has(edge.from)) forward.set(edge.from, new Set());
|
|
68
|
+
if (!forward.has(edge.to)) forward.set(edge.to, new Set());
|
|
69
|
+
if (!reverse.has(edge.from)) reverse.set(edge.from, new Set());
|
|
70
|
+
if (!reverse.has(edge.to)) reverse.set(edge.to, new Set());
|
|
71
|
+
forward.get(edge.from)!.add(edge.to);
|
|
72
|
+
reverse.get(edge.to)!.add(edge.from);
|
|
73
|
+
}
|
|
74
|
+
// Iterative strongly connected components: one count per cyclic dependency cluster.
|
|
75
|
+
const visited = new Set<string>();
|
|
76
|
+
const order: string[] = [];
|
|
77
|
+
for (const root of forward.keys()) {
|
|
78
|
+
const stack: Array<[string, boolean]> = [[root, false]];
|
|
79
|
+
while (stack.length) {
|
|
80
|
+
const [id, done] = stack.pop()!;
|
|
81
|
+
if (done) {
|
|
82
|
+
order.push(id);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (visited.has(id)) continue;
|
|
86
|
+
visited.add(id);
|
|
87
|
+
stack.push([id, true]);
|
|
88
|
+
for (const next of forward.get(id) ?? []) if (!visited.has(next)) stack.push([next, false]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const assigned = new Set<string>();
|
|
92
|
+
const cyclic = new Map<string, number>();
|
|
93
|
+
let cycles = 0;
|
|
94
|
+
for (const root of order.reverse()) {
|
|
95
|
+
if (assigned.has(root)) continue;
|
|
96
|
+
const component = new Set<string>([root]);
|
|
97
|
+
assigned.add(root);
|
|
98
|
+
for (const id of component)
|
|
99
|
+
for (const next of reverse.get(id) ?? []) {
|
|
100
|
+
if (assigned.has(next)) continue;
|
|
101
|
+
assigned.add(next);
|
|
102
|
+
component.add(next);
|
|
103
|
+
}
|
|
104
|
+
if (component.size > 1 || forward.get(root)?.has(root)) {
|
|
105
|
+
cycles++;
|
|
106
|
+
for (const id of component) cyclic.set(id, cycles);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const cycleEdges = new Set(
|
|
110
|
+
links
|
|
111
|
+
.filter((edge) => cyclic.has(edge.from) && cyclic.get(edge.from) === cyclic.get(edge.to))
|
|
112
|
+
.map((edge) => edge.id),
|
|
113
|
+
);
|
|
114
|
+
const roots = new Set(selectedNodeIds.value);
|
|
115
|
+
if (activeNodeId.value) roots.add(activeNodeId.value);
|
|
116
|
+
const affected = new Set(roots);
|
|
117
|
+
for (const id of affected) for (const next of reverse.get(id) ?? []) affected.add(next);
|
|
118
|
+
for (const id of roots) affected.delete(id);
|
|
119
|
+
return { cycles, cycleEdges, affected, focused: roots.size > 0, hasDependencies: links.length > 0 };
|
|
120
|
+
});
|
|
7
121
|
|
|
8
122
|
effect(() => {
|
|
9
123
|
const filter = relationFilter.value;
|
|
@@ -40,6 +154,18 @@ export const relationHighlight = computed(() => {
|
|
|
40
154
|
cards.add(edge.from);
|
|
41
155
|
cards.add(edge.to);
|
|
42
156
|
};
|
|
157
|
+
const view = inspection.value;
|
|
158
|
+
if (view && view.boardId === activeBoardId.value && view.nodeId === focus && !kind) {
|
|
159
|
+
if (view.mode === 'connections') {
|
|
160
|
+
for (const edge of list) if (edge.from === focus || edge.to === focus) add(edge);
|
|
161
|
+
} else {
|
|
162
|
+
for (const id of cards)
|
|
163
|
+
for (const edge of list)
|
|
164
|
+
if (['derived-from', 'depends-on'].includes(relationKind(edge)) && (edge.from === id || edge.to === id))
|
|
165
|
+
add(edge);
|
|
166
|
+
}
|
|
167
|
+
return { cards, links };
|
|
168
|
+
}
|
|
43
169
|
if (!kind) for (const edge of list) if (focusedCards.has(edge.from) && focusedCards.has(edge.to)) add(edge);
|
|
44
170
|
if (kind) {
|
|
45
171
|
for (const edge of list) if (relationKind(edge) === kind) add(edge);
|