pmx-canvas 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/extension.mjs +3 -1
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +688 -128
- package/CHANGELOG.md +69 -0
- package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
- package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
- package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
- package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
- package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1785 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +251 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
- package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
- package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
- package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-cwtsds5d.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-b4xc9tpz.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-ahp3yxxs.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-nappnay7.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-a428syda.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-nspsqebj.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-8y6177fh.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-vym028wn.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-jddqb8sy.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-bjwbpkrn.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-j6ph6hcx.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-p09w8jej.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-j975ycq5.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-sxp6hjn8.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-4tv230y2.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-mqgmnw9g.js +106 -0
- package/dist/canvas/chunks/index-037z5dz2.js +232 -0
- package/dist/canvas/chunks/index-081n3har.js +207 -0
- package/dist/canvas/chunks/index-5n03dmb9.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-60pgzdqe.js +157 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
- package/dist/canvas/chunks/index-8rjes9r6.js +2 -0
- package/dist/canvas/chunks/index-9022d1n9.js +16 -0
- package/dist/canvas/chunks/index-9b02042t.js +2 -0
- package/dist/canvas/chunks/index-ap4q4vez.js +2 -0
- package/dist/canvas/chunks/index-dyp2nhd3.js +2 -0
- package/dist/canvas/chunks/index-frvta4tm.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-h9ycj8n2.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-mergmj97.js +2 -0
- package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
- package/dist/canvas/chunks/index-nz8qbd1p.js +2 -0
- package/dist/canvas/chunks/index-qhfrazv3.js +2 -0
- package/dist/canvas/chunks/index-v119nznk.js +4 -0
- package/dist/canvas/chunks/index-vpt37m3d.js +89 -0
- package/dist/canvas/chunks/index-y9ma6azy.js +2 -0
- package/dist/canvas/chunks/index-zx0j3a2z.js +179 -0
- package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-qv97qq6a.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-sv67zats.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-k2pfam6p.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-83r0fdce.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-vg2newm1.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-k2h7zc5n.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-hg169p1v.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-bxtvsq0b.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-njyhcp4p.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-2z6v09gm.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-15mzam0e.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-rbshyd2c.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-sg26xwts.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-6eahp98e.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-ze5db10t.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-vnp7jyy8.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-8nxg8wqt.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-nj921e7s.js +7 -0
- package/dist/canvas/global.css +483 -230
- package/dist/canvas/index.js +158 -219
- package/dist/canvas/mcp-app.html +4301 -548
- package/dist/canvas/mermaid-entry.js +338 -338
- package/dist/canvas/surface-theme.css +6 -6
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +49 -49
- package/dist/types/client/canvas/BarHint.d.ts +5 -3
- package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
- package/dist/types/client/canvas/auto-fit.d.ts +4 -5
- package/dist/types/client/mermaid-entry.d.ts +1 -2
- package/dist/types/client/mermaid-render.d.ts +1 -0
- package/dist/types/client/nodes/BoardNode.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
- package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
- package/dist/types/client/nodes/PromptNode.d.ts +1 -0
- package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
- package/dist/types/client/nodes/WorkCard.d.ts +7 -0
- package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
- package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
- package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +18 -1
- package/dist/types/client/state/human-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +5 -2
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/client/state/relation-store.d.ts +11 -0
- package/dist/types/client/types.d.ts +1 -1
- package/dist/types/json-render/catalog.d.ts +2 -2
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/server/agent-presence.d.ts +1 -0
- package/dist/types/server/ax-interaction.d.ts +1 -0
- package/dist/types/server/ax-state-manager.d.ts +10 -2
- package/dist/types/server/board-local.d.ts +4 -0
- package/dist/types/server/board-move.d.ts +38 -0
- package/dist/types/server/canvas-db.d.ts +12 -2
- package/dist/types/server/canvas-operations.d.ts +5 -6
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +86 -20
- package/dist/types/server/card-relocation.d.ts +6 -0
- package/dist/types/server/context-reads.d.ts +3 -1
- package/dist/types/server/human-presence.d.ts +1 -1
- package/dist/types/server/index.d.ts +26 -10
- package/dist/types/server/intent-registry.d.ts +8 -4
- package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
- package/dist/types/server/mutation-history.d.ts +7 -5
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/trace-manager.d.ts +1 -0
- package/dist/types/shared/ax-kinds.d.ts +6 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +8 -9
- package/dist/types/shared/human-presence.d.ts +1 -0
- package/dist/types/shared/lifecycle-status.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/RELEASE.md +21 -14
- package/docs/agent-plugin.md +16 -2
- package/docs/amp-plugin.md +271 -0
- package/docs/cli.md +42 -3
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/GuestName.dc.html +50 -0
- package/docs/design/PluginLogo.svg +7 -0
- package/docs/design/Types1.dc.html +3 -2
- package/docs/design/Types2.dc.html +1 -1
- package/docs/design/Types3.dc.html +1 -1
- package/docs/design/WorkCards.dc.html +39 -0
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +17 -1
- package/docs/design/plugin-logo.png +0 -0
- package/docs/design.md +280 -12
- package/docs/evals/agents-md-sections.md +38 -0
- package/docs/http-api.md +179 -11
- package/docs/mcp.md +69 -9
- package/docs/node-types.md +109 -4
- package/docs/plans/plan-009-tech-debt-backlog.md +3 -3
- package/docs/plans/plan-019-post-0.9-roadmap.md +327 -31
- package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
- package/docs/product-vision-2026-09.md +42 -17
- package/docs/sdk.md +18 -0
- package/package.json +7 -4
- package/plugins/pmx-canvas/assets/preview.png +0 -0
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +2 -2
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +688 -128
- package/skills/data-analysis/SKILL.md +13 -26
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +3 -0
- package/skills/pmx-canvas/SKILL.md +232 -849
- package/skills/pmx-canvas/evals/evals.json +26 -3
- package/skills/pmx-canvas/references/full-reference.md +89 -24
- package/skills/pmx-canvas/references/html-primitives.md +7 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
- package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
- package/skills/pmx-canvas-testing/SKILL.md +7 -5
- package/skills/published-consumer-e2e/SKILL.md +25 -8
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -77
- package/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/web-artifacts-builder/SKILL.md +6 -0
- package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
- package/src/cli/commands/apps.ts +1 -0
- package/src/cli/commands/boards.ts +30 -0
- package/src/client/App.tsx +20 -4
- package/src/client/canvas/AttentionHistory.tsx +10 -2
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/BoardMoveDialog.tsx +225 -0
- package/src/client/canvas/CanvasNode.tsx +79 -49
- package/src/client/canvas/CanvasViewport.tsx +42 -14
- package/src/client/canvas/ContextMenu.tsx +97 -15
- package/src/client/canvas/EdgeEditor.tsx +149 -0
- package/src/client/canvas/EdgeLayer.tsx +441 -174
- package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -2
- package/src/client/canvas/GroupNavigation.tsx +85 -0
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/RelationsPanel.tsx +9 -0
- package/src/client/canvas/SelectionBar.tsx +21 -0
- package/src/client/canvas/SessionPanel.tsx +35 -1
- package/src/client/canvas/TextPrompt.tsx +18 -1
- package/src/client/canvas/TopBar.tsx +54 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +2 -2
- package/src/client/canvas/use-focus-trap.ts +6 -1
- package/src/client/mermaid-entry.ts +6 -20
- package/src/client/mermaid-render.ts +39 -0
- package/src/client/nodes/AxStepControls.tsx +45 -69
- package/src/client/nodes/BoardNode.tsx +65 -9
- package/src/client/nodes/ContextNode.tsx +88 -396
- package/src/client/nodes/ExtAppFrame.tsx +302 -1465
- package/src/client/nodes/FileNode.tsx +92 -81
- package/src/client/nodes/HtmlNode.tsx +8 -36
- package/src/client/nodes/LedgerNode.tsx +31 -17
- package/src/client/nodes/MarkdownNode.tsx +14 -24
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +44 -17
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +34 -7
- package/src/client/nodes/StatusSummary.tsx +16 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +273 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/WorkCard.tsx +175 -0
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
- package/src/client/nodes/use-iframe-content-height.ts +34 -10
- package/src/client/nodes/use-surface-frame.ts +22 -11
- package/src/client/nodes/viewer-runtime-state.ts +41 -35
- package/src/client/state/attention-bridge.ts +5 -0
- package/src/client/state/attention-store.ts +1 -0
- package/src/client/state/canvas-store.ts +202 -26
- package/src/client/state/human-store.ts +22 -2
- package/src/client/state/intent-bridge.ts +20 -5
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/relation-store.ts +127 -1
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +483 -230
- package/src/client/theme/surface-theme.css +6 -6
- package/src/client/types.ts +1 -0
- package/src/json-render/charts/tufte-components.tsx +44 -2
- package/src/json-render/renderer/index.css +62 -2
- package/src/json-render/renderer/index.tsx +31 -7
- package/src/json-render/server.ts +3 -2
- package/src/mcp/server.ts +13 -3
- package/src/server/agent-presence.ts +43 -23
- package/src/server/ax-flow-loop.ts +6 -0
- package/src/server/ax-gate-ttl.ts +8 -1
- package/src/server/ax-interaction.ts +16 -10
- package/src/server/ax-state-manager.ts +17 -4
- package/src/server/ax-wait.ts +20 -6
- package/src/server/board-export.ts +54 -30
- package/src/server/board-local.ts +20 -0
- package/src/server/board-move.ts +297 -0
- package/src/server/canvas-db.ts +249 -158
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +139 -109
- package/src/server/canvas-schema.ts +21 -0
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +797 -159
- package/src/server/card-relocation.ts +53 -0
- package/src/server/code-graph.ts +4 -3
- package/src/server/context-reads.ts +83 -55
- package/src/server/demo-state.json +140 -114
- package/src/server/file-watcher.ts +30 -10
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/human-presence.ts +18 -5
- package/src/server/index.ts +54 -24
- package/src/server/intent-registry.ts +43 -5
- package/src/server/knowledge-lifecycle.ts +32 -9
- package/src/server/lifecycle-status.ts +29 -1
- package/src/server/mcp-app-runtime.ts +26 -7
- package/src/server/mutation-history.ts +37 -8
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/mcp-contracts.ts +32 -2
- package/src/server/operations/mcp.ts +36 -9
- package/src/server/operations/ops/annotation.ts +22 -1
- package/src/server/operations/ops/ax-await.ts +1 -0
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/batch.ts +25 -11
- package/src/server/operations/ops/boards.ts +45 -6
- package/src/server/operations/ops/nodes.ts +70 -0
- package/src/server/operations/ops/query.ts +32 -3
- package/src/server/operations/ops/snapshots.ts +1 -0
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +49 -4
- package/src/server/server.ts +69 -75
- package/src/server/tour-control.ts +97 -12
- package/src/server/trace-manager.ts +4 -0
- package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
- package/src/shared/agent-context.ts +7 -1
- package/src/shared/auto-arrange.ts +5 -4
- package/src/shared/ax-kinds.ts +13 -0
- package/src/shared/ax-surface-protocol.ts +20 -9
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/human-presence.ts +1 -0
- package/src/shared/lifecycle-status.ts +2 -0
- package/src/shared/tour.ts +8 -0
|
@@ -17,6 +17,7 @@ import { HtmlNode } from '../nodes/HtmlNode';
|
|
|
17
17
|
import { PromptNode } from '../nodes/PromptNode';
|
|
18
18
|
import { ResponseNode } from '../nodes/ResponseNode';
|
|
19
19
|
import { TraceNode } from '../nodes/TraceNode';
|
|
20
|
+
import { ViewerHost } from '../nodes/ViewerHost';
|
|
20
21
|
import {
|
|
21
22
|
activeNodeId,
|
|
22
23
|
cancelViewportAnimation,
|
|
@@ -36,6 +37,9 @@ import {
|
|
|
36
37
|
dragDropTarget,
|
|
37
38
|
hiddenByCollapsedGroup,
|
|
38
39
|
canvasTool,
|
|
40
|
+
groupContents,
|
|
41
|
+
groupViewNodes,
|
|
42
|
+
canvasInteractionEpoch,
|
|
39
43
|
} from '../state/canvas-store';
|
|
40
44
|
import { createEdgeFromClient, createNodeFromClient } from '../state/intent-bridge';
|
|
41
45
|
import type { AnnotationTool, CanvasAnnotation, CanvasNodeState } from '../types';
|
|
@@ -49,6 +53,7 @@ import { ScopeFenceLayer } from './ScopeFenceLayer';
|
|
|
49
53
|
import { CanvasNode } from './CanvasNode';
|
|
50
54
|
import { BoardNode } from '../nodes/BoardNode';
|
|
51
55
|
import { EdgeLayer } from './EdgeLayer';
|
|
56
|
+
import { GroupOutsideLinks } from './GroupNavigation';
|
|
52
57
|
import { AnnotationLayer } from './AnnotationLayer';
|
|
53
58
|
import { activeGuides } from './snap-guides';
|
|
54
59
|
import { usePanZoom } from './use-pan-zoom';
|
|
@@ -103,18 +108,30 @@ function renderNodeContent(node: CanvasNodeState) {
|
|
|
103
108
|
*/
|
|
104
109
|
class WorldNode extends Component<{
|
|
105
110
|
node: CanvasNodeState;
|
|
111
|
+
hidden: boolean;
|
|
106
112
|
onContextMenu?: (e: MouseEvent, nodeId: string) => void;
|
|
107
113
|
}> {
|
|
108
|
-
shouldComponentUpdate(next: {
|
|
109
|
-
|
|
114
|
+
shouldComponentUpdate(next: {
|
|
115
|
+
node: CanvasNodeState;
|
|
116
|
+
hidden: boolean;
|
|
117
|
+
onContextMenu?: (e: MouseEvent, nodeId: string) => void;
|
|
118
|
+
}) {
|
|
119
|
+
return (
|
|
120
|
+
next.node !== this.props.node ||
|
|
121
|
+
next.hidden !== this.props.hidden ||
|
|
122
|
+
next.onContextMenu !== this.props.onContextMenu
|
|
123
|
+
);
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
render() {
|
|
113
|
-
const { node, onContextMenu } = this.props;
|
|
127
|
+
const { node, hidden, onContextMenu } = this.props;
|
|
128
|
+
const viewer = ['html', 'mcp-app', 'json-render', 'graph', 'webpage', 'mermaid'].includes(node.type);
|
|
114
129
|
return (
|
|
115
|
-
<
|
|
116
|
-
{
|
|
117
|
-
|
|
130
|
+
<div style={{ display: hidden ? 'none' : 'contents' }}>
|
|
131
|
+
<CanvasNode node={node} onContextMenu={onContextMenu}>
|
|
132
|
+
{viewer ? <ViewerHost node={node}>{renderNodeContent(node)}</ViewerHost> : renderNodeContent(node)}
|
|
133
|
+
</CanvasNode>
|
|
134
|
+
</div>
|
|
118
135
|
);
|
|
119
136
|
}
|
|
120
137
|
}
|
|
@@ -570,6 +587,7 @@ export function CanvasViewport({
|
|
|
570
587
|
// Find intersecting nodes (AABB intersection)
|
|
571
588
|
const hits: string[] = [];
|
|
572
589
|
for (const node of nodes.value.values()) {
|
|
590
|
+
if (groupContents.value && !groupContents.value.has(node.id)) continue;
|
|
573
591
|
const nx = node.position.x;
|
|
574
592
|
const ny = node.position.y;
|
|
575
593
|
if (nx + node.size.width > worldMinX && nx < worldMaxX && ny + node.size.height > worldMinY && ny < worldMaxY) {
|
|
@@ -620,6 +638,7 @@ export function CanvasViewport({
|
|
|
620
638
|
let hit: string | null = null;
|
|
621
639
|
for (const node of nodes.value.values()) {
|
|
622
640
|
if (node.id === exceptId || node.type === 'group') continue;
|
|
641
|
+
if (groupContents.value && !groupContents.value.has(node.id)) continue;
|
|
623
642
|
if (
|
|
624
643
|
wx >= node.position.x &&
|
|
625
644
|
wx <= node.position.x + node.size.width &&
|
|
@@ -805,11 +824,17 @@ export function CanvasViewport({
|
|
|
805
824
|
// reorder DOM children when bringToFront() changes zIndex, causing browsers to
|
|
806
825
|
// detach/reattach iframe elements (which forces them to reload/reconnect).
|
|
807
826
|
// Group nodes render first (behind) so they serve as visual containers.
|
|
827
|
+
const mounted = useRef({ epoch: canvasInteractionEpoch.value, ids: new Set<string>() });
|
|
828
|
+
if (mounted.current.epoch !== canvasInteractionEpoch.value)
|
|
829
|
+
mounted.current = { epoch: canvasInteractionEpoch.value, ids: new Set() };
|
|
830
|
+
for (const id of mounted.current.ids) if (!nodes.value.has(id)) mounted.current.ids.delete(id);
|
|
831
|
+
const collapsed = hiddenByCollapsedGroup.value;
|
|
808
832
|
const worldNodes = getRenderableWorldNodes(
|
|
809
833
|
nodes.value.values(),
|
|
810
834
|
expandedNodeId.value,
|
|
811
|
-
new Set(
|
|
835
|
+
new Set([...collapsed.keys()].filter((id) => !mounted.current.ids.has(id))),
|
|
812
836
|
);
|
|
837
|
+
for (const node of worldNodes) mounted.current.ids.add(node.id);
|
|
813
838
|
const lowestZ = worldNodes.reduce((lowest, node) => Math.min(lowest, Math.floor(node.zIndex)), 0);
|
|
814
839
|
const highestZ = worldNodes.reduce((highest, node) => Math.max(highest, Math.ceil(node.zIndex)), 0);
|
|
815
840
|
// Keep group-body hit order without changing the frames' visual stacking.
|
|
@@ -873,9 +898,8 @@ export function CanvasViewport({
|
|
|
873
898
|
: 'default',
|
|
874
899
|
}}
|
|
875
900
|
>
|
|
876
|
-
{/*
|
|
877
|
-
|
|
878
|
-
the scale pivot is the top-left corner of the world layer. */}
|
|
901
|
+
{/* Scale the complete spatial world proportionally, including iframe
|
|
902
|
+
contents. CSS zoom changes embedded viewport semantics in WebKit. */}
|
|
879
903
|
<div
|
|
880
904
|
ref={worldRef}
|
|
881
905
|
class="canvas-world"
|
|
@@ -894,9 +918,8 @@ export function CanvasViewport({
|
|
|
894
918
|
<IntentLayer />
|
|
895
919
|
<AgentPresenceLayer />
|
|
896
920
|
<HumanPresenceLayer />
|
|
897
|
-
<EdgeLayer nodes={
|
|
898
|
-
<
|
|
899
|
-
<EdgeLayer nodes={nodes} edges={edges} part="pill-hits" />
|
|
921
|
+
<EdgeLayer nodes={groupViewNodes} edges={edges} />
|
|
922
|
+
<GroupOutsideLinks />
|
|
900
923
|
{draggingEdge.value && (
|
|
901
924
|
<div
|
|
902
925
|
class="edge-hint-pill"
|
|
@@ -931,7 +954,12 @@ export function CanvasViewport({
|
|
|
931
954
|
<AnnotationLayer annotations={Array.from(annotations.value.values())} />
|
|
932
955
|
{draftAnnotation && draftAnnotation.points.length >= 2 && <AnnotationLayer annotations={[draftAnnotation]} />}
|
|
933
956
|
{worldNodes.map((node) => (
|
|
934
|
-
<WorldNode
|
|
957
|
+
<WorldNode
|
|
958
|
+
key={node.id}
|
|
959
|
+
node={node}
|
|
960
|
+
hidden={collapsed.has(node.id) || (groupContents.value !== null && !groupContents.value.has(node.id))}
|
|
961
|
+
onContextMenu={onNodeContextMenu}
|
|
962
|
+
/>
|
|
935
963
|
))}
|
|
936
964
|
{/* Snap alignment guide lines */}
|
|
937
965
|
{activeGuides.value && (
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ComponentChildren } from 'preact';
|
|
2
2
|
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
3
|
-
import { useCallback, useLayoutEffect, useState } from 'preact/hooks';
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
4
4
|
import {
|
|
5
5
|
contextPinnedNodeIds,
|
|
6
6
|
edges,
|
|
7
7
|
expandNode,
|
|
8
8
|
focusNode,
|
|
9
9
|
nodes,
|
|
10
|
+
openGroup,
|
|
10
11
|
pendingConnection,
|
|
11
12
|
persistLayout,
|
|
12
13
|
removeNode,
|
|
@@ -28,8 +29,10 @@ import {
|
|
|
28
29
|
updateNodeFromClient,
|
|
29
30
|
} from '../state/intent-bridge';
|
|
30
31
|
import { canOpenAsSite, openNodeAsSite } from '../nodes/surface-url';
|
|
32
|
+
import { canCopyPrimitiveJson, copyPrimitiveJson } from '../nodes/primitive-copy';
|
|
31
33
|
import { askText } from './TextPrompt';
|
|
32
|
-
import {
|
|
34
|
+
import { openBoardMove } from './BoardMoveDialog';
|
|
35
|
+
import { activeBoard, boardIncarnation, setBoardReadme } from '../state/boards-store';
|
|
33
36
|
import { openLifecycle } from '../state/knowledge-store';
|
|
34
37
|
import { RELATION_KINDS, relationKind } from '../../shared/relations.js';
|
|
35
38
|
import { EXPANDABLE_TYPES } from '../types';
|
|
@@ -62,7 +65,9 @@ export function useContextMenu() {
|
|
|
62
65
|
const openNodeMenu = useCallback((e: MouseEvent, nodeId: string) => {
|
|
63
66
|
e.preventDefault();
|
|
64
67
|
e.stopPropagation();
|
|
65
|
-
|
|
68
|
+
const trigger = e.detail === 0 && e.target instanceof Element ? e.target.closest('button') : null;
|
|
69
|
+
const bounds = trigger?.getBoundingClientRect();
|
|
70
|
+
setMenu({ kind: 'node', x: bounds?.left ?? e.clientX, y: bounds?.bottom ?? e.clientY, nodeId });
|
|
66
71
|
}, []);
|
|
67
72
|
|
|
68
73
|
const openCanvasMenu = useCallback((e: MouseEvent, canvasX: number, canvasY: number) => {
|
|
@@ -113,6 +118,21 @@ const GROUP_COLOR_PRESETS = [
|
|
|
113
118
|
] as const;
|
|
114
119
|
|
|
115
120
|
export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
121
|
+
const root = useRef<HTMLDivElement>(null);
|
|
122
|
+
useLayoutEffect(() => {
|
|
123
|
+
const element = root.current;
|
|
124
|
+
const trigger = document.activeElement;
|
|
125
|
+
element?.querySelector<HTMLButtonElement>('button:not(:disabled)')?.focus();
|
|
126
|
+
return () => {
|
|
127
|
+
if (element?.contains(document.activeElement) && trigger instanceof HTMLElement && trigger.isConnected) {
|
|
128
|
+
// Card controls hide when the menu takes focus. Reveal the opener
|
|
129
|
+
// through its card before restoring focus to the button itself.
|
|
130
|
+
trigger.closest<HTMLElement>('.canvas-node')?.focus({ preventScroll: true });
|
|
131
|
+
trigger.focus({ preventScroll: true });
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, [menu]);
|
|
135
|
+
|
|
116
136
|
let items: MenuItem[];
|
|
117
137
|
if (menu.kind === 'node') {
|
|
118
138
|
const node = nodes.value.get(menu.nodeId);
|
|
@@ -215,6 +235,9 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
215
235
|
|
|
216
236
|
return (
|
|
217
237
|
<div
|
|
238
|
+
ref={root}
|
|
239
|
+
role="menu"
|
|
240
|
+
aria-label={menu.kind === 'node' ? 'Node actions' : menu.kind === 'edge' ? 'Edge actions' : 'Canvas actions'}
|
|
218
241
|
class="context-menu"
|
|
219
242
|
style={{
|
|
220
243
|
position: 'fixed',
|
|
@@ -224,6 +247,27 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
224
247
|
}}
|
|
225
248
|
onPointerDown={(e) => e.stopPropagation()}
|
|
226
249
|
onClick={(e) => e.stopPropagation()}
|
|
250
|
+
onBlur={(e) => {
|
|
251
|
+
if (e.relatedTarget instanceof Node && !e.currentTarget.contains(e.relatedTarget)) onClose();
|
|
252
|
+
}}
|
|
253
|
+
onKeyDown={(e) => {
|
|
254
|
+
e.stopPropagation();
|
|
255
|
+
if (e.key === 'Escape') {
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
onClose();
|
|
258
|
+
} else if (e.target instanceof HTMLButtonElement && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
const controls = [...e.currentTarget.querySelectorAll<HTMLElement>('button:not(:disabled), input, select')];
|
|
261
|
+
const index = controls.indexOf(e.target);
|
|
262
|
+
const next =
|
|
263
|
+
e.key === 'Home'
|
|
264
|
+
? 0
|
|
265
|
+
: e.key === 'End'
|
|
266
|
+
? controls.length - 1
|
|
267
|
+
: (index + (e.key === 'ArrowUp' ? -1 : 1) + controls.length) % controls.length;
|
|
268
|
+
controls[next]?.focus();
|
|
269
|
+
}
|
|
270
|
+
}}
|
|
227
271
|
>
|
|
228
272
|
{items.map((item) => {
|
|
229
273
|
const baseKey = item.separator
|
|
@@ -251,6 +295,8 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
251
295
|
<button
|
|
252
296
|
key={itemKey}
|
|
253
297
|
type="button"
|
|
298
|
+
role="menuitem"
|
|
299
|
+
disabled={!item.action}
|
|
254
300
|
class={`context-menu-item${item.danger ? ' is-danger' : ''}`}
|
|
255
301
|
onClick={() => {
|
|
256
302
|
item.action?.();
|
|
@@ -424,6 +470,23 @@ async function createPromptedNode(opts: {
|
|
|
424
470
|
|
|
425
471
|
function buildCanvasMenuItems(canvasX: number, canvasY: number): MenuItem[] {
|
|
426
472
|
return [
|
|
473
|
+
{
|
|
474
|
+
label: 'New work card',
|
|
475
|
+
action: () => {
|
|
476
|
+
const width = 360;
|
|
477
|
+
const height = 200;
|
|
478
|
+
const position = centeredPosition(canvasX, canvasY, width, height);
|
|
479
|
+
void createNodeFromClient({
|
|
480
|
+
type: 'status',
|
|
481
|
+
title: 'New work card',
|
|
482
|
+
data: { workState: 'todo' },
|
|
483
|
+
x: position.x,
|
|
484
|
+
y: position.y,
|
|
485
|
+
width,
|
|
486
|
+
height,
|
|
487
|
+
});
|
|
488
|
+
},
|
|
489
|
+
},
|
|
427
490
|
{
|
|
428
491
|
label: 'New note',
|
|
429
492
|
action: () => {
|
|
@@ -523,18 +586,19 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
|
|
|
523
586
|
action: () => focusNode(node.id),
|
|
524
587
|
});
|
|
525
588
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
589
|
+
if (!(node.type === 'status' && typeof node.data.workItemId === 'string'))
|
|
590
|
+
items.push({
|
|
591
|
+
label: 'Rename…',
|
|
592
|
+
action: () => {
|
|
593
|
+
void askText('Rename node', 'Node title', {
|
|
594
|
+
initial: typeof node.data.title === 'string' ? node.data.title : '',
|
|
595
|
+
confirm: 'Save',
|
|
596
|
+
}).then((title) => {
|
|
597
|
+
if (!title?.trim()) return;
|
|
598
|
+
void updateNodeFromClient(node.id, { title: title.trim() });
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
});
|
|
538
602
|
|
|
539
603
|
// Expand into full-viewport overlay for focused work
|
|
540
604
|
if (EXPANDABLE_TYPES.has(node.type)) {
|
|
@@ -622,6 +686,22 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
|
|
|
622
686
|
items.push({ separator: true });
|
|
623
687
|
|
|
624
688
|
// Type-specific
|
|
689
|
+
if (canCopyPrimitiveJson(node)) {
|
|
690
|
+
items.push({ label: 'Copy JSON', action: () => void copyPrimitiveJson(node.id) });
|
|
691
|
+
}
|
|
692
|
+
if (node.type === 'markdown') {
|
|
693
|
+
items.push({
|
|
694
|
+
label: node.data.display === 'heading' ? 'Display as card' : 'Display as heading',
|
|
695
|
+
action: () =>
|
|
696
|
+
void updateNodeFromClient(node.id, { data: { display: node.data.display === 'heading' ? 'card' : 'heading' } }),
|
|
697
|
+
});
|
|
698
|
+
const board = activeBoard();
|
|
699
|
+
if (board)
|
|
700
|
+
items.push({
|
|
701
|
+
label: board.readmeNodeId === node.id ? 'Remove as README' : 'Set as README',
|
|
702
|
+
action: () => void setBoardReadme(board.id, board.readmeNodeId === node.id ? null : node.id),
|
|
703
|
+
});
|
|
704
|
+
}
|
|
625
705
|
if ((node.type === 'markdown' || node.type === 'file' || node.type === 'image') && localPath) {
|
|
626
706
|
items.push({
|
|
627
707
|
label: 'Open in browser',
|
|
@@ -697,6 +777,8 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
|
|
|
697
777
|
if (node.type === 'group') {
|
|
698
778
|
const childIds = (node.data.children as string[]) ?? [];
|
|
699
779
|
items.push({ separator: true });
|
|
780
|
+
items.push({ label: 'Open group', action: () => openGroup(node.id) });
|
|
781
|
+
items.push({ label: 'Move to new board', action: () => openBoardMove([node.id]) });
|
|
700
782
|
items.push({
|
|
701
783
|
render: (onClose) => renderGroupColorSection(node, onClose),
|
|
702
784
|
});
|
|
@@ -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
|
+
}
|