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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { signal } from '@preact/signals';
|
|
2
|
+
import { useState } from 'preact/hooks';
|
|
3
|
+
import { RELATION_KINDS, relationKind } from '../../shared/relations.js';
|
|
4
|
+
import { activeBoard, activeBoardId, boardIncarnation } from '../state/boards-store';
|
|
5
|
+
import { edges, selectedEdgeId, viewport } from '../state/canvas-store';
|
|
6
|
+
import { removeEdgeFromClient, updateEdgeFromClient } from '../state/intent-bridge';
|
|
7
|
+
import type { CanvasEdge } from '../types';
|
|
8
|
+
import { canvasArea } from './canvas-area';
|
|
9
|
+
|
|
10
|
+
export const edgeEditorAnchor = signal<{ id: string; incarnation: number; point: { x: number; y: number } } | null>(
|
|
11
|
+
null,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/** Like EdgeHint, the editor lives in App's screen layer, outside the transformed world. */
|
|
15
|
+
export function SelectedEdgeEditor() {
|
|
16
|
+
const anchor = edgeEditorAnchor.value;
|
|
17
|
+
const edge = anchor ? edges.value.get(anchor.id) : null;
|
|
18
|
+
return anchor && edge && anchor.id === selectedEdgeId.value && anchor.incarnation === boardIncarnation ? (
|
|
19
|
+
<EdgeEditor key={`${anchor.incarnation}:${edge.id}`} edge={edge} point={anchor.point} />
|
|
20
|
+
) : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Screen-sized controls anchored to the selected relation, not scaled with its SVG. */
|
|
24
|
+
export function EdgeEditor({ edge, point }: { edge: CanvasEdge; point: { x: number; y: number } }) {
|
|
25
|
+
const [field, setField] = useState<'label' | 'reason' | 'kind' | null>(null);
|
|
26
|
+
const [value, setValue] = useState('');
|
|
27
|
+
const [pending, setPending] = useState(false);
|
|
28
|
+
const [error, setError] = useState('');
|
|
29
|
+
const [incarnation] = useState(boardIncarnation);
|
|
30
|
+
const [boardId] = useState(activeBoardId.value);
|
|
31
|
+
const edit = (next: NonNullable<typeof field>) => {
|
|
32
|
+
setField(next);
|
|
33
|
+
setValue(next === 'kind' ? relationKind(edge) : (edge[next] ?? ''));
|
|
34
|
+
setError('');
|
|
35
|
+
};
|
|
36
|
+
const save = async (patch: Parameters<typeof updateEdgeFromClient>[1] | null) => {
|
|
37
|
+
if (pending || incarnation !== boardIncarnation) return;
|
|
38
|
+
setPending(true);
|
|
39
|
+
const result = patch
|
|
40
|
+
? await updateEdgeFromClient(edge.id, patch, boardId)
|
|
41
|
+
: await removeEdgeFromClient(edge.id, boardId);
|
|
42
|
+
if (incarnation !== boardIncarnation) return;
|
|
43
|
+
setPending(false);
|
|
44
|
+
if (result.ok) {
|
|
45
|
+
setField(null);
|
|
46
|
+
setError('');
|
|
47
|
+
if (!patch && selectedEdgeId.value === edge.id) selectedEdgeId.value = null;
|
|
48
|
+
} else setError('Change refused. Your draft is still here.');
|
|
49
|
+
};
|
|
50
|
+
const area = canvasArea();
|
|
51
|
+
const view = viewport.value;
|
|
52
|
+
const width = Math.min(320, area.width - 24);
|
|
53
|
+
const x = Math.max(
|
|
54
|
+
area.left + 12,
|
|
55
|
+
Math.min(area.left + area.width - width - 12, area.left + point.x * view.scale + view.x - width / 2),
|
|
56
|
+
);
|
|
57
|
+
const y = Math.max(
|
|
58
|
+
area.top + 8,
|
|
59
|
+
Math.min(area.top + area.height - (field ? 180 : 60), area.top + point.y * view.scale + view.y + 20),
|
|
60
|
+
);
|
|
61
|
+
const kinds = [
|
|
62
|
+
...new Set(
|
|
63
|
+
[...RELATION_KINDS, ...(activeBoard()?.relationVocabulary ?? [])]
|
|
64
|
+
.map((item) => item.kind)
|
|
65
|
+
.concat(relationKind(edge)),
|
|
66
|
+
),
|
|
67
|
+
];
|
|
68
|
+
return (
|
|
69
|
+
<form
|
|
70
|
+
class="edge-editor"
|
|
71
|
+
aria-label="Edit relation"
|
|
72
|
+
style={{ left: x, top: y, width }}
|
|
73
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
74
|
+
onKeyDown={(event) => {
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
if (event.key === 'Escape' && !pending) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
setField(null);
|
|
79
|
+
selectedEdgeId.value = null;
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
onSubmit={(event) => {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
if (field) void save({ [field]: value });
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<fieldset disabled={pending}>
|
|
88
|
+
<div class="edge-editor-tools">
|
|
89
|
+
<select
|
|
90
|
+
aria-label="Relation kind"
|
|
91
|
+
value={relationKind(edge)}
|
|
92
|
+
onChange={(event) => {
|
|
93
|
+
const kind = event.currentTarget.value;
|
|
94
|
+
if (kind === '__custom') edit('kind');
|
|
95
|
+
else {
|
|
96
|
+
setField('kind');
|
|
97
|
+
setValue(kind);
|
|
98
|
+
void save({ kind });
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{kinds.map((kind) => (
|
|
103
|
+
<option key={kind} value={kind}>
|
|
104
|
+
{kind}
|
|
105
|
+
</option>
|
|
106
|
+
))}
|
|
107
|
+
<option value="__custom">Custom…</option>
|
|
108
|
+
</select>
|
|
109
|
+
<button type="button" onClick={() => edit('label')}>
|
|
110
|
+
Label
|
|
111
|
+
</button>
|
|
112
|
+
<button type="button" onClick={() => edit('reason')}>
|
|
113
|
+
Why
|
|
114
|
+
</button>
|
|
115
|
+
<button type="button" onClick={() => void save(null)}>
|
|
116
|
+
Delete
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
{field && (
|
|
120
|
+
<div class="edge-editor-field">
|
|
121
|
+
<label>
|
|
122
|
+
{field === 'reason' ? 'Why this relation?' : field === 'label' ? 'Label' : 'Custom kind'}
|
|
123
|
+
<input
|
|
124
|
+
autoFocus
|
|
125
|
+
value={value}
|
|
126
|
+
required={field === 'kind'}
|
|
127
|
+
maxLength={field === 'kind' ? 80 : field === 'reason' ? 4000 : 1000}
|
|
128
|
+
onInput={(event) => setValue(event.currentTarget.value)}
|
|
129
|
+
/>
|
|
130
|
+
</label>
|
|
131
|
+
<div>
|
|
132
|
+
<button type="submit">Save</button>{' '}
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
onClick={() => {
|
|
136
|
+
setField(null);
|
|
137
|
+
setError('');
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
Cancel
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
{error && <p role="alert">{error}</p>}
|
|
146
|
+
</fieldset>
|
|
147
|
+
</form>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -7,15 +7,23 @@ import {
|
|
|
7
7
|
selectedEdgeId,
|
|
8
8
|
viewport,
|
|
9
9
|
visibleNodeFor,
|
|
10
|
+
groupContents,
|
|
10
11
|
} from '../state/canvas-store';
|
|
11
12
|
import { linkMarks, writerName } from '../state/context-status-store';
|
|
12
13
|
import { HUMAN_STARTED_SESSION_LABEL } from '../../shared/agent-presence';
|
|
13
14
|
import { activeSession } from '../state/presence-store';
|
|
14
|
-
import { activeBoard } from '../state/boards-store';
|
|
15
|
+
import { activeBoard, boardIncarnation } from '../state/boards-store';
|
|
15
16
|
import { reportClientViewportSize } from '../state/intent-bridge';
|
|
16
17
|
import { relationDefinition, relationKind, relationText } from '../../shared/relations.js';
|
|
17
18
|
import type { CanvasEdge, CanvasNodeState } from '../types';
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
dependencyAnalysis,
|
|
21
|
+
relationWarning,
|
|
22
|
+
relationFilter,
|
|
23
|
+
relationFocus,
|
|
24
|
+
relationHighlight,
|
|
25
|
+
} from '../state/relation-store';
|
|
26
|
+
import { edgeEditorAnchor } from './EdgeEditor';
|
|
19
27
|
|
|
20
28
|
/** Filter only the kinds on this board; shared presets are managed in Home. */
|
|
21
29
|
export function RelationLegend() {
|
|
@@ -71,6 +79,17 @@ export function RelationLegend() {
|
|
|
71
79
|
</button>
|
|
72
80
|
);
|
|
73
81
|
})}
|
|
82
|
+
{dependencyAnalysis.value.hasDependencies && (
|
|
83
|
+
<>
|
|
84
|
+
<span class={dependencyAnalysis.value.cycles ? 'relation-warning' : ''}>
|
|
85
|
+
{dependencyAnalysis.value.cycles ? '⚠ ' : ''}
|
|
86
|
+
{dependencyAnalysis.value.cycles} dependency cycles
|
|
87
|
+
</span>
|
|
88
|
+
{dependencyAnalysis.value.focused && (
|
|
89
|
+
<span>Change selection → {dependencyAnalysis.value.affected.size} affected</span>
|
|
90
|
+
)}
|
|
91
|
+
</>
|
|
92
|
+
)}
|
|
74
93
|
</div>
|
|
75
94
|
);
|
|
76
95
|
}
|
|
@@ -312,6 +331,7 @@ function edgeLayout(
|
|
|
312
331
|
scale: number,
|
|
313
332
|
obstacles: LabelObstacle[],
|
|
314
333
|
textWidth: (text: string, weight?: number) => number,
|
|
334
|
+
parallelCount: number,
|
|
315
335
|
) {
|
|
316
336
|
const dx = end.x - start.x;
|
|
317
337
|
const dy = end.y - start.y;
|
|
@@ -334,7 +354,16 @@ function edgeLayout(
|
|
|
334
354
|
const zoom = edgeZoom.value;
|
|
335
355
|
const marks = linkMarks(edge);
|
|
336
356
|
// Reasons belong in the hint/inspector, not a long pill covering both endpoints.
|
|
337
|
-
const
|
|
357
|
+
const baseLabel = edge.label ?? edge.kind;
|
|
358
|
+
const label = parallelCount > 1 ? `${baseLabel ?? relationKind(edge)} · ×${parallelCount}` : baseLabel;
|
|
359
|
+
const warning = [
|
|
360
|
+
dependencyAnalysis.value.cycleEdges.has(edge.id) ? 'Cycle' : '',
|
|
361
|
+
relationWarning(edge).length ? 'Source needs review' : '',
|
|
362
|
+
]
|
|
363
|
+
.filter(Boolean)
|
|
364
|
+
.join(' · ');
|
|
365
|
+
const warningText = warning ? `⚠ ${warning}` : '';
|
|
366
|
+
const warningWidth = warning ? textWidth(warningText, 500) + 12 : 0;
|
|
338
367
|
const showLabel = !!label && zoom >= 0.35;
|
|
339
368
|
// Pill layout in label units (× labelScale): the label, then a hairline and
|
|
340
369
|
// "not seen by …", which keeps only its glyph on a short line or below 60% zoom.
|
|
@@ -346,12 +375,12 @@ function edgeLayout(
|
|
|
346
375
|
// A short line: the full pill plus some line on either side would not fit.
|
|
347
376
|
const notSeenWords = marks.notSeen && zoom >= 0.6 && dist >= (fullWidth + 60) * labelScale;
|
|
348
377
|
const notSeenWidth = marks.notSeen ? (labelWidth ? 12 : 0) + (notSeenWords ? wordedNotSeenWidth : 12) : 0;
|
|
349
|
-
const pillWidth = pad * 2 + labelWidth + notSeenWidth;
|
|
378
|
+
const pillWidth = pad * 2 + labelWidth + notSeenWidth + warningWidth;
|
|
350
379
|
const labelX = -pillWidth / 2 + pad;
|
|
351
380
|
const notSeenX = labelX + labelWidth + (labelWidth ? 12 : 0);
|
|
352
|
-
const hasPill = showLabel || marks.notSeen;
|
|
353
|
-
const curveMid =
|
|
354
|
-
const mid =
|
|
381
|
+
const hasPill = showLabel || marks.notSeen || warningWidth > 0;
|
|
382
|
+
const curveMid = bezierMidpoint(start.x, start.y, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
|
|
383
|
+
const mid = hasPill ? placeLabel(curveMid, pillWidth * labelScale, 20 * labelScale, obstacles, 4 * scale) : null;
|
|
355
384
|
if (mid) {
|
|
356
385
|
obstacles.push({
|
|
357
386
|
x: mid.x - (pillWidth / 2) * labelScale,
|
|
@@ -377,6 +406,8 @@ function edgeLayout(
|
|
|
377
406
|
notSeenX,
|
|
378
407
|
curveMid,
|
|
379
408
|
mid,
|
|
409
|
+
warningText,
|
|
410
|
+
warningX: labelX + labelWidth + notSeenWidth + 6,
|
|
380
411
|
};
|
|
381
412
|
}
|
|
382
413
|
|
|
@@ -406,12 +437,15 @@ function EdgePath({ edge, fromNode, toNode, layout, focused, dimmed, selected, s
|
|
|
406
437
|
// that LANDED on an edge must not immediately undo itself.
|
|
407
438
|
e.stopPropagation();
|
|
408
439
|
selectedEdgeId.value = edge.id;
|
|
440
|
+
edgeHint.value = null;
|
|
409
441
|
};
|
|
410
442
|
const showHint = (anchor: Element, point?: { x: number; y: number }) => {
|
|
443
|
+
if (selectedEdgeId.value === edge.id) return;
|
|
411
444
|
const text = [
|
|
412
445
|
`${String(fromNode.data.title ?? fromNode.id)} → ${String(toNode.data.title ?? toNode.id)}`,
|
|
413
446
|
relationText(edge),
|
|
414
447
|
linkHint(edge, marks.notSeen),
|
|
448
|
+
...relationWarning(edge),
|
|
415
449
|
]
|
|
416
450
|
.filter(Boolean)
|
|
417
451
|
.join('\n');
|
|
@@ -511,6 +545,17 @@ function EdgePath({ edge, fromNode, toNode, layout, focused, dimmed, selected, s
|
|
|
511
545
|
)}
|
|
512
546
|
</g>
|
|
513
547
|
)}
|
|
548
|
+
{layout.warningText && (
|
|
549
|
+
<text
|
|
550
|
+
class="edge-label"
|
|
551
|
+
x={layout.warningX * labelScale}
|
|
552
|
+
text-anchor="start"
|
|
553
|
+
dominant-baseline="central"
|
|
554
|
+
fill="var(--c-warn)"
|
|
555
|
+
>
|
|
556
|
+
{layout.warningText}
|
|
557
|
+
</text>
|
|
558
|
+
)}
|
|
514
559
|
</g>
|
|
515
560
|
);
|
|
516
561
|
}
|
|
@@ -615,7 +660,17 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
|
|
|
615
660
|
return Math.ceil(Math.max(metrics.width, metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight));
|
|
616
661
|
};
|
|
617
662
|
const nodeMap = nodes.value;
|
|
618
|
-
const
|
|
663
|
+
const inside = groupContents.value;
|
|
664
|
+
const edgeList = Array.from(edges.value.values()).filter(
|
|
665
|
+
(edge) => !inside || inside.has(edge.from) || inside.has(edge.to),
|
|
666
|
+
);
|
|
667
|
+
const resolve = (id: string) => {
|
|
668
|
+
const visible = visibleNodeFor(id);
|
|
669
|
+
return nodeMap.get(visible?.id ?? id) ?? nodeMap.get(id);
|
|
670
|
+
};
|
|
671
|
+
const pairs = new Map<string, number>();
|
|
672
|
+
const pairKey = (edge: CanvasEdge) => JSON.stringify([edge.from, edge.to].sort());
|
|
673
|
+
for (const edge of edgeList) if (!edge.toBoardId) pairs.set(pairKey(edge), (pairs.get(pairKey(edge)) ?? 0) + 1);
|
|
619
674
|
const scale = edgeChromeScale(edgeZoom.value);
|
|
620
675
|
|
|
621
676
|
// Allocate a port to every incident link, including parallel and reverse links.
|
|
@@ -623,8 +678,8 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
|
|
|
623
678
|
const sides = new Map<string, Array<{ anchor: Anchor; node: CanvasNodeState; order: number; id: string }>>();
|
|
624
679
|
for (const edge of edgeList) {
|
|
625
680
|
if (edge.toBoardId) continue;
|
|
626
|
-
const from =
|
|
627
|
-
const to =
|
|
681
|
+
const from = resolve(edge.from);
|
|
682
|
+
const to = resolve(edge.to);
|
|
628
683
|
if (!from || !to || from.id === to.id) continue;
|
|
629
684
|
const start = computeAnchor(from, to);
|
|
630
685
|
const end = computeAnchor(to, from);
|
|
@@ -657,7 +712,7 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
|
|
|
657
712
|
const obstacles: LabelObstacle[] = [];
|
|
658
713
|
const portSize = 32 / edgeZoom.value;
|
|
659
714
|
for (const node of nodeMap.values()) {
|
|
660
|
-
if (
|
|
715
|
+
if (resolve(node.id)?.id !== node.id) continue;
|
|
661
716
|
const frame = node.type === 'group' && !node.collapsed;
|
|
662
717
|
const { x, y } = node.position;
|
|
663
718
|
const { width, height } = node.size;
|
|
@@ -681,14 +736,30 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
|
|
|
681
736
|
const layouts = new Map<string, ReturnType<typeof edgeLayout>>();
|
|
682
737
|
for (const edge of edgeList) {
|
|
683
738
|
const anchors = ports.get(edge.id);
|
|
684
|
-
if (anchors)
|
|
739
|
+
if (anchors)
|
|
740
|
+
layouts.set(
|
|
741
|
+
edge.id,
|
|
742
|
+
edgeLayout(edge, anchors.start, anchors.end, scale, obstacles, textWidth, pairs.get(pairKey(edge)) ?? 1),
|
|
743
|
+
);
|
|
685
744
|
}
|
|
686
|
-
return { nodeMap, edgeList, layouts, scale };
|
|
745
|
+
return { nodeMap, edgeList, layouts, scale, resolve };
|
|
687
746
|
}),
|
|
688
747
|
[nodes, edges, textContext, font],
|
|
689
748
|
);
|
|
690
749
|
|
|
691
|
-
const { nodeMap, edgeList, layouts, scale } = geometry.value;
|
|
750
|
+
const { nodeMap, edgeList, layouts, scale, resolve } = geometry.value;
|
|
751
|
+
const selected = selectedEdgeId.value;
|
|
752
|
+
const layout = selected ? layouts.get(selected) : null;
|
|
753
|
+
const point = layout?.mid ?? layout?.curveMid;
|
|
754
|
+
useLayoutEffect(() => {
|
|
755
|
+
edgeEditorAnchor.value = selected && point ? { id: selected, incarnation: boardIncarnation, point } : null;
|
|
756
|
+
}, [selected, point?.x, point?.y, boardIncarnation]);
|
|
757
|
+
useEffect(
|
|
758
|
+
() => () => {
|
|
759
|
+
edgeEditorAnchor.value = null;
|
|
760
|
+
},
|
|
761
|
+
[],
|
|
762
|
+
);
|
|
692
763
|
const focusId = relationFocus.value;
|
|
693
764
|
const hasFocus = focusId !== null;
|
|
694
765
|
const searchSet = searchHighlightIds.value;
|
|
@@ -758,8 +829,8 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
|
|
|
758
829
|
// Remote endpoints are navigable links, never a same-ID card on this board.
|
|
759
830
|
if (edge.toBoardId) return null;
|
|
760
831
|
// An endpoint hidden behind a collapsed group's chip is drawn to the chip.
|
|
761
|
-
const fromNode =
|
|
762
|
-
const toNode =
|
|
832
|
+
const fromNode = resolve(edge.from);
|
|
833
|
+
const toNode = resolve(edge.to);
|
|
763
834
|
const layout = layouts.get(edge.id);
|
|
764
835
|
if (!fromNode || !toNode || !layout) return null;
|
|
765
836
|
const isConnected = hasFocus && (edge.from === focusId || edge.to === focusId);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
2
2
|
import { BarHint } from './BarHint';
|
|
3
|
+
import { AttentionHistory } from './AttentionHistory';
|
|
4
|
+
import { AttentionToast } from './AttentionToast';
|
|
3
5
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
4
6
|
import { ContextNode } from '../nodes/ContextNode';
|
|
5
7
|
import { DiffNode } from '../nodes/DiffNode';
|
|
@@ -397,6 +399,8 @@ export function ExpandedNodeOverlay() {
|
|
|
397
399
|
{pendingClose ? 'Saving edits…' : 'esc or click outside to close'}
|
|
398
400
|
</span>
|
|
399
401
|
</div>
|
|
402
|
+
<AttentionToast />
|
|
403
|
+
<AttentionHistory />
|
|
400
404
|
{canPresent && presenting && (
|
|
401
405
|
<div
|
|
402
406
|
ref={presentationOverlayRef}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { activeBoard } from '../state/boards-store';
|
|
2
|
+
import { useLayoutEffect, useRef } from 'preact/hooks';
|
|
3
|
+
import { reportClientViewportSize } from '../state/intent-bridge';
|
|
4
|
+
import { focusNode, groupContents, groupPath, groupViewNodes, leaveGroup, nodes } from '../state/canvas-store';
|
|
5
|
+
|
|
6
|
+
export function GroupBreadcrumbs() {
|
|
7
|
+
const path = groupPath.value;
|
|
8
|
+
const ref = useRef<HTMLElement>(null);
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
const element = ref.current;
|
|
11
|
+
if (!element) return;
|
|
12
|
+
const resize = new ResizeObserver(() => void reportClientViewportSize());
|
|
13
|
+
resize.observe(element);
|
|
14
|
+
return () => resize.disconnect();
|
|
15
|
+
}, [path.length > 0]);
|
|
16
|
+
if (!path.length) return null;
|
|
17
|
+
return (
|
|
18
|
+
<nav
|
|
19
|
+
ref={ref}
|
|
20
|
+
class="group-breadcrumbs"
|
|
21
|
+
aria-label="Group navigation"
|
|
22
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
23
|
+
>
|
|
24
|
+
<button type="button" onClick={() => leaveGroup(0)}>
|
|
25
|
+
{activeBoard()?.name ?? 'Board'}
|
|
26
|
+
</button>
|
|
27
|
+
{path.map((visit, index) => (
|
|
28
|
+
<span key={visit.id}>
|
|
29
|
+
<span aria-hidden="true"> / </span>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
aria-current={index === path.length - 1 ? 'location' : undefined}
|
|
33
|
+
disabled={index === path.length - 1}
|
|
34
|
+
onClick={() => leaveGroup(index + 1)}
|
|
35
|
+
>
|
|
36
|
+
{String(nodes.value.get(visit.id)?.data.title ?? 'Group')}
|
|
37
|
+
</button>
|
|
38
|
+
</span>
|
|
39
|
+
))}
|
|
40
|
+
<button type="button" onClick={() => leaveGroup()} aria-label="Leave group">
|
|
41
|
+
Esc · goes up
|
|
42
|
+
</button>
|
|
43
|
+
</nav>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function GroupOutsideLinks() {
|
|
48
|
+
const inside = groupContents.value;
|
|
49
|
+
if (!inside) return null;
|
|
50
|
+
const outside = [...groupViewNodes.value.values()].filter((node) => !inside.has(node.id));
|
|
51
|
+
const children = [...nodes.value.values()].filter((node) => inside.has(node.id));
|
|
52
|
+
const root = nodes.value.get(groupPath.value.at(-1)!.id)!;
|
|
53
|
+
const left = children.length ? Math.min(...children.map((node) => node.position.x)) - 24 : root.position.x;
|
|
54
|
+
const top = children.length ? Math.min(...children.map((node) => node.position.y)) - 40 : root.position.y;
|
|
55
|
+
const right = children.length
|
|
56
|
+
? Math.max(...children.map((node) => node.position.x + node.size.width)) + 24
|
|
57
|
+
: left + 320;
|
|
58
|
+
const bottom = children.length
|
|
59
|
+
? Math.max(...children.map((node) => node.position.y + node.size.height)) + 24
|
|
60
|
+
: top + 120;
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<div class="group-inside-frame" style={{ left, top, width: right - left, height: bottom - top }}>
|
|
64
|
+
<span>Inside {String(root.data.title ?? 'Group')}</span>
|
|
65
|
+
</div>
|
|
66
|
+
{outside.map((node, index) => (
|
|
67
|
+
<div key={node.id} class="group-outside-endpoint" style={{ left: node.position.x, top: node.position.y }}>
|
|
68
|
+
{index === 0 && <span class="group-outside-label">Outside</span>}
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class="group-outside-stub"
|
|
72
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
73
|
+
onClick={(event) => {
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
focusNode(node.id, { recordHistory: false, persistStacking: false });
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
↗ {String(node.data.title ?? node.type)}
|
|
79
|
+
<span>{node.type} · Show on canvas</span>
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -4,6 +4,7 @@ import { relationDefinition, relationSourceEnd } from '../../shared/relations.js
|
|
|
4
4
|
import { activeBoardId, boardList, openMemoryTarget } from '../state/boards-store';
|
|
5
5
|
import { edges, nodes } from '../state/canvas-store';
|
|
6
6
|
import { requestJson } from '../state/intent-bridge';
|
|
7
|
+
import { showRelationsOnCanvas } from '../state/relation-store';
|
|
7
8
|
|
|
8
9
|
/** The expanded card's evidence and uses, from the same graph agents navigate. */
|
|
9
10
|
export function RelationsPanel({ nodeId }: { nodeId: string }) {
|
|
@@ -50,6 +51,14 @@ export function RelationsPanel({ nodeId }: { nodeId: string }) {
|
|
|
50
51
|
return (
|
|
51
52
|
<aside class="relations-panel" aria-label="Relations">
|
|
52
53
|
<h3>Relations</h3>
|
|
54
|
+
<div class="relation-navigation">
|
|
55
|
+
<button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'lineage')}>
|
|
56
|
+
Show lineage
|
|
57
|
+
</button>
|
|
58
|
+
<button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'connections')}>
|
|
59
|
+
Show on canvas
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
53
62
|
{!graph && <p role="status">{unavailable ? 'Relations unavailable. Reopen to retry.' : 'Loading relations…'}</p>}
|
|
54
63
|
{graph && relations.length === 0 && <p>No connections yet.</p>}
|
|
55
64
|
{relations.map((edge, index) => {
|
|
@@ -13,6 +13,7 @@ import { createEdgeFromClient, createGroupFromClient, removeNodeFromClient } fro
|
|
|
13
13
|
import { sessionActive } from '../state/presence-store';
|
|
14
14
|
import { BarHint } from './BarHint';
|
|
15
15
|
import { useChromeHeight } from './use-chrome-height';
|
|
16
|
+
import { openBoardMove } from './BoardMoveDialog';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Floating bottom-center bar for a multi-selection (rail-chrome-v2 phase 7,
|
|
@@ -174,6 +175,26 @@ export function SelectionBar() {
|
|
|
174
175
|
</BarHint>
|
|
175
176
|
</>
|
|
176
177
|
)}
|
|
178
|
+
<BarHint label="Move to new board" side="up">
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
class="selection-bar-icon"
|
|
182
|
+
aria-label="Move to new board"
|
|
183
|
+
onClick={() => openBoardMove([...selectedNodeIds.value])}
|
|
184
|
+
>
|
|
185
|
+
<svg
|
|
186
|
+
width="16"
|
|
187
|
+
height="16"
|
|
188
|
+
viewBox="0 0 16 16"
|
|
189
|
+
fill="none"
|
|
190
|
+
stroke="currentColor"
|
|
191
|
+
stroke-width="1.5"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
>
|
|
194
|
+
<path d="M6 3H2v10h12V9M8 8l6-6M9 2h5v5" />
|
|
195
|
+
</svg>
|
|
196
|
+
</button>
|
|
197
|
+
</BarHint>
|
|
177
198
|
<BarHint label="Pin to agent context" side="up">
|
|
178
199
|
<button type="button" class="selection-bar-action is-pin" onClick={handlePinContext}>
|
|
179
200
|
<span aria-hidden="true">✦</span>
|
|
@@ -4,7 +4,9 @@ import { IconChevronRight, IconUndo } from '../icons';
|
|
|
4
4
|
import { useNow } from './use-now';
|
|
5
5
|
import { agentPhaseLabel } from '../../shared/agent-presence.js';
|
|
6
6
|
import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
|
|
7
|
-
import { focusNode, selectedNodeIds } from '../state/canvas-store';
|
|
7
|
+
import { focusNode, nodes, selectedNodeIds } from '../state/canvas-store';
|
|
8
|
+
import { activeBoardId } from '../state/boards-store';
|
|
9
|
+
import { createNodeFromClient } from '../state/intent-bridge';
|
|
8
10
|
import { activeSession, agentPresences, attachedSessions, writerColor } from '../state/presence-store';
|
|
9
11
|
import {
|
|
10
12
|
type ApprovalGateView,
|
|
@@ -96,6 +98,35 @@ function clock(iso: string): string {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
function WorkItemRow({ item }: { item: WorkItemView }) {
|
|
101
|
+
const [busy, setBusy] = useState(false);
|
|
102
|
+
const [pending, setPending] = useState<{ boardId: string; nodeId: string } | null>(null);
|
|
103
|
+
const boardId = activeBoardId.value;
|
|
104
|
+
const boardNodes = nodes.value;
|
|
105
|
+
const presentation = [...nodes.value.values()].find(
|
|
106
|
+
(node) => node.type === 'status' && node.data.workItemId === item.id,
|
|
107
|
+
);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!pending) return;
|
|
110
|
+
if (boardId === pending.boardId && !boardNodes.has(pending.nodeId)) return;
|
|
111
|
+
if (boardId === pending.boardId) focusNode(pending.nodeId);
|
|
112
|
+
setPending(null);
|
|
113
|
+
setBusy(false);
|
|
114
|
+
}, [pending, boardId, boardNodes]);
|
|
115
|
+
const show = async () => {
|
|
116
|
+
if (presentation) return focusNode(presentation.id);
|
|
117
|
+
if (!boardId || busy) return;
|
|
118
|
+
setBusy(true);
|
|
119
|
+
const result = await createNodeFromClient({
|
|
120
|
+
type: 'status',
|
|
121
|
+
data: { workItemId: item.id },
|
|
122
|
+
width: 360,
|
|
123
|
+
height: 200,
|
|
124
|
+
expectedBoardId: boardId,
|
|
125
|
+
});
|
|
126
|
+
if (result.ok && result.id && activeBoardId.value === boardId) {
|
|
127
|
+
setPending({ boardId, nodeId: result.id });
|
|
128
|
+
} else setBusy(false);
|
|
129
|
+
};
|
|
99
130
|
const glyph = WORK_GLYPH[item.status] ?? 'queued';
|
|
100
131
|
const nodeId = item.nodeIds[0];
|
|
101
132
|
return (
|
|
@@ -106,6 +137,9 @@ function WorkItemRow({ item }: { item: WorkItemView }) {
|
|
|
106
137
|
{item.title}
|
|
107
138
|
</button>
|
|
108
139
|
{item.detail && <div class="session-item-detail">{item.detail}</div>}
|
|
140
|
+
<button type="button" class="session-show-work" disabled={busy} onClick={() => void show()}>
|
|
141
|
+
{busy ? 'Showing…' : presentation ? 'Show card' : 'Show on board'}
|
|
142
|
+
</button>
|
|
109
143
|
</div>
|
|
110
144
|
<span class="session-item-status">{WORK_STATUS_TEXT[item.status] ?? glyph}</span>
|
|
111
145
|
<span class="session-item-time">{clock(item.updatedAt)}</span>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal } from '@preact/signals';
|
|
2
2
|
import { useEffect, useRef } from 'preact/hooks';
|
|
3
|
+
import { useFocusTrap } from './use-focus-trap';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* In-canvas replacement for `window.prompt`, which embedded browser panes
|
|
@@ -9,6 +10,8 @@ import { useEffect, useRef } from 'preact/hooks';
|
|
|
9
10
|
interface TextPromptRequest {
|
|
10
11
|
title: string;
|
|
11
12
|
placeholder: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
maxLength?: number;
|
|
12
15
|
/** Prefilled (and pre-selected) value — edit flows start from the current text. */
|
|
13
16
|
initial: string;
|
|
14
17
|
/** Submitting an empty field resolves '' instead of null — "clear the value" flows. */
|
|
@@ -25,7 +28,7 @@ let promptSeq = 0;
|
|
|
25
28
|
export function askText(
|
|
26
29
|
title: string,
|
|
27
30
|
placeholder: string,
|
|
28
|
-
opts: { initial?: string; allowEmpty?: boolean; confirm?: string } = {},
|
|
31
|
+
opts: { initial?: string; allowEmpty?: boolean; confirm?: string; description?: string; maxLength?: number } = {},
|
|
29
32
|
): Promise<string | null> {
|
|
30
33
|
return new Promise((resolve) => {
|
|
31
34
|
textPromptRequest.value?.resolve(null);
|
|
@@ -34,6 +37,8 @@ export function askText(
|
|
|
34
37
|
seq: promptSeq,
|
|
35
38
|
title,
|
|
36
39
|
placeholder,
|
|
40
|
+
description: opts.description,
|
|
41
|
+
maxLength: opts.maxLength,
|
|
37
42
|
initial: opts.initial ?? '',
|
|
38
43
|
allowEmpty: opts.allowEmpty ?? false,
|
|
39
44
|
confirm: opts.confirm ?? 'Add',
|
|
@@ -51,6 +56,8 @@ function settle(value: string | null): void {
|
|
|
51
56
|
export function TextPrompt() {
|
|
52
57
|
const request = textPromptRequest.value;
|
|
53
58
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
59
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
60
|
+
useFocusTrap(dialogRef, request !== null, { initial: inputRef });
|
|
54
61
|
|
|
55
62
|
useEffect(() => {
|
|
56
63
|
if (!request) return;
|
|
@@ -78,13 +85,21 @@ export function TextPrompt() {
|
|
|
78
85
|
return (
|
|
79
86
|
<div class="text-prompt-backdrop" onPointerDown={() => settle(null)}>
|
|
80
87
|
<div
|
|
88
|
+
ref={dialogRef}
|
|
81
89
|
class="text-prompt"
|
|
82
90
|
role="dialog"
|
|
91
|
+
aria-modal="true"
|
|
83
92
|
aria-label={request.title}
|
|
93
|
+
aria-describedby={request.description ? 'text-prompt-description' : undefined}
|
|
84
94
|
data-testid="text-prompt"
|
|
85
95
|
onPointerDown={(e) => e.stopPropagation()}
|
|
86
96
|
>
|
|
87
97
|
<div class="text-prompt-title">{request.title}</div>
|
|
98
|
+
{request.description && (
|
|
99
|
+
<div id="text-prompt-description" class="text-prompt-description">
|
|
100
|
+
{request.description}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
88
103
|
<input
|
|
89
104
|
// A fresh element per ask (so defaultValue applies) focused in the
|
|
90
105
|
// ref callback, which Preact runs synchronously during commit — the
|
|
@@ -101,6 +116,8 @@ export function TextPrompt() {
|
|
|
101
116
|
type="text"
|
|
102
117
|
defaultValue={request.initial}
|
|
103
118
|
placeholder={request.placeholder}
|
|
119
|
+
aria-label={request.title}
|
|
120
|
+
maxLength={request.maxLength}
|
|
104
121
|
onKeyDown={(e) => {
|
|
105
122
|
// Contained: the global shortcut layer must not see these.
|
|
106
123
|
if (e.key === 'Enter') {
|