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
package/src/client/App.tsx
CHANGED
|
@@ -37,7 +37,10 @@ import { ShortcutOverlay } from './canvas/ShortcutOverlay';
|
|
|
37
37
|
import { SnapshotPanel } from './canvas/SnapshotPanel';
|
|
38
38
|
import { promptedCreate, ToolRail } from './canvas/ToolRail';
|
|
39
39
|
import { EdgeHint, RelationLegend } from './canvas/EdgeLayer';
|
|
40
|
+
import { SelectedEdgeEditor } from './canvas/EdgeEditor';
|
|
41
|
+
import { GroupBreadcrumbs } from './canvas/GroupNavigation';
|
|
40
42
|
import { TextPrompt } from './canvas/TextPrompt';
|
|
43
|
+
import { BoardMoveDialog } from './canvas/BoardMoveDialog';
|
|
41
44
|
import { TopBar } from './canvas/TopBar';
|
|
42
45
|
import {
|
|
43
46
|
activeNodeId,
|
|
@@ -62,6 +65,9 @@ import {
|
|
|
62
65
|
walkGraph,
|
|
63
66
|
zoomByFactor,
|
|
64
67
|
groupsOfSelection,
|
|
68
|
+
groupPath,
|
|
69
|
+
groupViewNodes,
|
|
70
|
+
leaveGroup,
|
|
65
71
|
} from './state/canvas-store';
|
|
66
72
|
import { connectSSE } from './state/sse-bridge';
|
|
67
73
|
import { intents } from './state/intent-store';
|
|
@@ -247,7 +253,10 @@ export function App() {
|
|
|
247
253
|
if (target?.closest('[role="menu"]')) return;
|
|
248
254
|
const inDialog = target?.closest('[role="dialog"]');
|
|
249
255
|
// Chrome owns its arrow keys and Tab order, rather than board navigation.
|
|
250
|
-
if (
|
|
256
|
+
if (
|
|
257
|
+
(e.key === 'Tab' || e.key.startsWith('Arrow')) &&
|
|
258
|
+
target?.closest('.tool-rail, .top-bar, .group-breadcrumbs, .group-outside-stub')
|
|
259
|
+
) {
|
|
251
260
|
return;
|
|
252
261
|
}
|
|
253
262
|
|
|
@@ -292,6 +301,10 @@ export function App() {
|
|
|
292
301
|
} else if (e.key === 'Escape') {
|
|
293
302
|
if (!isSelectionEscape(e)) return;
|
|
294
303
|
e.preventDefault();
|
|
304
|
+
if (groupPath.value.length && !menu) {
|
|
305
|
+
leaveGroup();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
295
308
|
if (relationFilter.value?.boardId === activeBoardId.value) relationFilter.value = null;
|
|
296
309
|
lifecycleFilter.value = 'All';
|
|
297
310
|
clearSelection();
|
|
@@ -436,6 +449,7 @@ export function App() {
|
|
|
436
449
|
>
|
|
437
450
|
<Presentation />
|
|
438
451
|
<EdgeHint />
|
|
452
|
+
<SelectedEdgeEditor />
|
|
439
453
|
<ToolRail
|
|
440
454
|
minimapVisible={minimapVisible}
|
|
441
455
|
onToggleMinimap={handleToggleMinimap}
|
|
@@ -461,11 +475,12 @@ export function App() {
|
|
|
461
475
|
annotationTool={annotationTool}
|
|
462
476
|
/>
|
|
463
477
|
)}
|
|
464
|
-
<AttentionToast />
|
|
478
|
+
{(onHome || !expandedNodeId.value) && <AttentionToast />}
|
|
465
479
|
<KnowledgeSync />
|
|
466
480
|
{!onHome && <LifecycleFilters />}
|
|
467
481
|
{!onHome && <RelationLegend />}
|
|
468
|
-
<
|
|
482
|
+
{!onHome && <GroupBreadcrumbs />}
|
|
483
|
+
{(onHome || !expandedNodeId.value) && <AttentionHistory />}
|
|
469
484
|
<ActivityFeed />
|
|
470
485
|
<WritersSheet />
|
|
471
486
|
{onHome ? (
|
|
@@ -480,6 +495,7 @@ export function App() {
|
|
|
480
495
|
{!onHome && sessionActive.value && !isHostedWorkbench() && <CommandBar />}
|
|
481
496
|
{!onHome && <SessionReceipt onOpenSnapshots={() => setSnapshotOpen(true)} />}
|
|
482
497
|
<TextPrompt />
|
|
498
|
+
<BoardMoveDialog />
|
|
483
499
|
{!browsingMemory.value && <ExportDialog />}
|
|
484
500
|
{!onHome && expandedNodeId.value && <ExpandedNodeOverlay />}
|
|
485
501
|
<SnapshotPanel
|
|
@@ -490,7 +506,7 @@ export function App() {
|
|
|
490
506
|
{minimapVisible && !onHome && (
|
|
491
507
|
<Minimap
|
|
492
508
|
viewport={viewport}
|
|
493
|
-
nodes={
|
|
509
|
+
nodes={groupViewNodes}
|
|
494
510
|
edges={edges}
|
|
495
511
|
onNavigate={handleMinimapNavigate}
|
|
496
512
|
containerWidth={area.width}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'preact/hooks';
|
|
1
2
|
import {
|
|
2
3
|
attentionHistory,
|
|
3
4
|
attentionHistoryOpen,
|
|
@@ -20,13 +21,19 @@ function handleOpenUpdates(): void {
|
|
|
20
21
|
|
|
21
22
|
export function AttentionHistory() {
|
|
22
23
|
const entries = attentionHistory.value;
|
|
23
|
-
if (entries.length === 0) return null;
|
|
24
|
-
|
|
25
24
|
const isOpen = attentionHistoryOpen.value;
|
|
26
25
|
const unread = attentionHistoryUnread.value;
|
|
26
|
+
const toggleRef = useRef<HTMLButtonElement>(null);
|
|
27
|
+
const wasOpen = useRef(isOpen);
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
if (isOpen !== wasOpen.current) toggleRef.current?.focus({ preventScroll: true });
|
|
30
|
+
wasOpen.current = isOpen;
|
|
31
|
+
}, [isOpen]);
|
|
32
|
+
if (entries.length === 0) return null;
|
|
27
33
|
if (!isOpen) {
|
|
28
34
|
return (
|
|
29
35
|
<button
|
|
36
|
+
ref={toggleRef}
|
|
30
37
|
type="button"
|
|
31
38
|
class="attention-history-tab"
|
|
32
39
|
onClick={handleOpenUpdates}
|
|
@@ -67,6 +74,7 @@ export function AttentionHistory() {
|
|
|
67
74
|
<span class="attention-history-subtitle">Focus and meaning shifts</span>
|
|
68
75
|
</div>
|
|
69
76
|
<button
|
|
77
|
+
ref={toggleRef}
|
|
70
78
|
type="button"
|
|
71
79
|
class="attention-history-close"
|
|
72
80
|
onClick={closeAttentionHistory}
|
|
@@ -20,6 +20,7 @@ export function BarPortal({ children }: { children: VNode }) {
|
|
|
20
20
|
* `title` hints, which are delay-gated and never render in embedded panes.
|
|
21
21
|
*/
|
|
22
22
|
export function BarHint({
|
|
23
|
+
id,
|
|
23
24
|
label,
|
|
24
25
|
shortcut,
|
|
25
26
|
body,
|
|
@@ -29,10 +30,12 @@ export function BarHint({
|
|
|
29
30
|
fitWithin,
|
|
30
31
|
children,
|
|
31
32
|
}: {
|
|
33
|
+
/** Optional description target for the control's aria-describedby. */
|
|
34
|
+
id?: string;
|
|
32
35
|
label: string;
|
|
33
36
|
shortcut?: string;
|
|
34
|
-
/**
|
|
35
|
-
body?:
|
|
37
|
+
/** Explanatory text, optionally split into readable paragraphs. */
|
|
38
|
+
body?: ComponentChildren;
|
|
36
39
|
align?: 'start' | 'center' | 'end';
|
|
37
40
|
/** Which way the tooltip opens. Bars at the bottom of the region open 'up'. */
|
|
38
41
|
side?: 'down' | 'up';
|
|
@@ -73,7 +76,7 @@ export function BarHint({
|
|
|
73
76
|
onFocusIn={fitWithin ? fit : undefined}
|
|
74
77
|
>
|
|
75
78
|
{children}
|
|
76
|
-
<span class="toolbar-tooltip" role="tooltip">
|
|
79
|
+
<span id={id} class="toolbar-tooltip" role="tooltip">
|
|
77
80
|
<span class="toolbar-tooltip-label">{label}</span>
|
|
78
81
|
{body && <span class="toolbar-tooltip-body">{body}</span>}
|
|
79
82
|
{shortcut && (
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { signal } from '@preact/signals';
|
|
2
|
+
import { useRef, useState } from 'preact/hooks';
|
|
3
|
+
import type { BoardMoveInput, BoardMovePreview, BoardMoveResult } from '../../server/board-move.js';
|
|
4
|
+
import { activeBoardId, boardIncarnation, claimNavigation, openBoard } from '../state/boards-store';
|
|
5
|
+
import { nodes } from '../state/canvas-store';
|
|
6
|
+
import { requestJson } from '../state/intent-bridge';
|
|
7
|
+
import { hasUnsavedMarkdown } from '../nodes/MarkdownNode';
|
|
8
|
+
import { hasUnsavedPrompt } from '../nodes/PromptNode';
|
|
9
|
+
import { hasUnsavedWorkCard } from '../nodes/WorkCard';
|
|
10
|
+
import { useFocusTrap } from './use-focus-trap';
|
|
11
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
12
|
+
|
|
13
|
+
type MoveSelection = {
|
|
14
|
+
sourceBoardId: string;
|
|
15
|
+
nodeIds: string[];
|
|
16
|
+
requestId: string;
|
|
17
|
+
incarnation: number;
|
|
18
|
+
canNavigate: () => boolean;
|
|
19
|
+
};
|
|
20
|
+
const selection = signal<MoveSelection | null>(null);
|
|
21
|
+
|
|
22
|
+
export function openBoardMove(nodeIds: string[]): void {
|
|
23
|
+
if (!activeBoardId.value || !nodeIds.length) return;
|
|
24
|
+
selection.value = {
|
|
25
|
+
sourceBoardId: activeBoardId.value,
|
|
26
|
+
nodeIds: [...nodeIds],
|
|
27
|
+
requestId: crypto.randomUUID(),
|
|
28
|
+
incarnation: boardIncarnation,
|
|
29
|
+
canNavigate: claimNavigation(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function BoardMoveDialog() {
|
|
34
|
+
const current = selection.value;
|
|
35
|
+
return current ? <MoveForm key={current.requestId} selected={current} /> : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function MoveForm({ selected }: { selected: MoveSelection }) {
|
|
39
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const nameRef = useRef<HTMLInputElement>(null);
|
|
41
|
+
const [name, setName] = useState('New board');
|
|
42
|
+
const [preview, setPreview] = useState<BoardMovePreview | null>(null);
|
|
43
|
+
const [input, setInput] = useState<BoardMoveInput | null>(null);
|
|
44
|
+
const [busy, setBusy] = useState(false);
|
|
45
|
+
const [attempted, setAttempted] = useState(false);
|
|
46
|
+
const [refused, setRefused] = useState(false);
|
|
47
|
+
const [error, setError] = useState('');
|
|
48
|
+
const close = () => {
|
|
49
|
+
if (selection.value === selected) selection.value = null;
|
|
50
|
+
};
|
|
51
|
+
const current = () =>
|
|
52
|
+
selection.value === selected &&
|
|
53
|
+
selected.incarnation === boardIncarnation &&
|
|
54
|
+
activeBoardId.value === selected.sourceBoardId;
|
|
55
|
+
useFocusTrap(dialogRef, true, { initial: nameRef, onEscape: close });
|
|
56
|
+
|
|
57
|
+
const unsaved = (ids: string[]) =>
|
|
58
|
+
ids.filter(
|
|
59
|
+
(id) =>
|
|
60
|
+
hasUnsavedMarkdown(selected.sourceBoardId, id) ||
|
|
61
|
+
hasUnsavedWorkCard(selected.sourceBoardId, id) ||
|
|
62
|
+
hasUnsavedPrompt(selected.sourceBoardId, id),
|
|
63
|
+
);
|
|
64
|
+
const submit = async (event: Event) => {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
if (busy || refused || !name.trim()) return;
|
|
67
|
+
if (!current()) return setError('The board changed. Close this dialog and select the cards again.');
|
|
68
|
+
setBusy(true);
|
|
69
|
+
setError('');
|
|
70
|
+
if (!preview) {
|
|
71
|
+
const board = await requestJson<{ ok: boolean; contentRevision: number } | null>(
|
|
72
|
+
'moveRevision',
|
|
73
|
+
`/api/canvas/boards/${encodeURIComponent(selected.sourceBoardId)}`,
|
|
74
|
+
null,
|
|
75
|
+
);
|
|
76
|
+
if (!current()) {
|
|
77
|
+
setBusy(false);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!board?.ok) {
|
|
81
|
+
setError('Could not read the source board. Try again.');
|
|
82
|
+
setBusy(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const next = {
|
|
86
|
+
sourceBoardId: selected.sourceBoardId,
|
|
87
|
+
nodeIds: selected.nodeIds,
|
|
88
|
+
requestId: selected.requestId,
|
|
89
|
+
name: name.trim(),
|
|
90
|
+
expectedRevision: board.contentRevision,
|
|
91
|
+
};
|
|
92
|
+
const result = await requestJson<{ ok: boolean; preview?: BoardMovePreview; error?: string } | null>(
|
|
93
|
+
'previewMove',
|
|
94
|
+
'/api/canvas/boards/move-selection',
|
|
95
|
+
null,
|
|
96
|
+
{
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/json' },
|
|
99
|
+
body: JSON.stringify({ ...next, preview: true }),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
if (current()) {
|
|
103
|
+
if (result?.ok && result.preview) {
|
|
104
|
+
setInput(next);
|
|
105
|
+
setPreview(result.preview);
|
|
106
|
+
if (unsaved(result.preview.nodeIds).length)
|
|
107
|
+
setError('Save or clear the selected cards’ drafts before moving. Close and preview again.');
|
|
108
|
+
} else setError(result?.error ?? 'Could not preview the move. Try again.');
|
|
109
|
+
}
|
|
110
|
+
} else if (input && !preview.blockers.length) {
|
|
111
|
+
if (unsaved(preview.nodeIds).length) {
|
|
112
|
+
setBusy(false);
|
|
113
|
+
return setError('Save or clear the selected cards’ drafts before moving. Close and preview again.');
|
|
114
|
+
}
|
|
115
|
+
setAttempted(true);
|
|
116
|
+
const result = await requestJson<
|
|
117
|
+
({ ok: true } & BoardMoveResult) | { ok: false; error?: string; blockers?: string[] } | null
|
|
118
|
+
>('moveSelection', '/api/canvas/boards/move-selection', null, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'Content-Type': 'application/json' },
|
|
121
|
+
body: JSON.stringify(input),
|
|
122
|
+
});
|
|
123
|
+
if (result?.ok) {
|
|
124
|
+
const navigate = current() && selected.canNavigate();
|
|
125
|
+
close();
|
|
126
|
+
if (navigate) await openBoard(result.destinationBoardId);
|
|
127
|
+
} else if (selection.value === selected) {
|
|
128
|
+
if (result) {
|
|
129
|
+
setRefused(true);
|
|
130
|
+
setError(`${result.error ?? 'Move refused.'} ${result.blockers?.join(' ') ?? ''} Close and preview again.`);
|
|
131
|
+
} else {
|
|
132
|
+
setError('No reply received. Retry uses the same request and cannot create a second board.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
setBusy(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const blocked = !!preview?.blockers.length;
|
|
140
|
+
return (
|
|
141
|
+
<div class="text-prompt-backdrop board-move-backdrop" onMouseDown={close}>
|
|
142
|
+
<div
|
|
143
|
+
ref={dialogRef}
|
|
144
|
+
class="text-prompt board-move-dialog"
|
|
145
|
+
role="dialog"
|
|
146
|
+
aria-modal="true"
|
|
147
|
+
aria-labelledby="board-move-title"
|
|
148
|
+
onMouseDown={(event) => event.stopPropagation()}
|
|
149
|
+
onKeyDown={(event) => {
|
|
150
|
+
event.stopPropagation();
|
|
151
|
+
if (event.key === 'Escape') {
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
close();
|
|
154
|
+
}
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
<form onClick={submitWithoutNavigation} onSubmit={submit}>
|
|
158
|
+
<h2 id="board-move-title">Move to new board</h2>
|
|
159
|
+
<p>Move the original cards, including group contents. Leave a portal on this board.</p>
|
|
160
|
+
<label class="board-copy-field">
|
|
161
|
+
New board name
|
|
162
|
+
<input
|
|
163
|
+
ref={nameRef}
|
|
164
|
+
class="text-prompt-input"
|
|
165
|
+
maxLength={120}
|
|
166
|
+
required
|
|
167
|
+
value={name}
|
|
168
|
+
readOnly={!!preview || busy}
|
|
169
|
+
onInput={(event) => setName(event.currentTarget.value)}
|
|
170
|
+
/>
|
|
171
|
+
</label>
|
|
172
|
+
{preview && (
|
|
173
|
+
<>
|
|
174
|
+
<p>
|
|
175
|
+
{preview.nodeIds.length} cards · {preview.pinnedNodeIds.length} pinned ·{' '}
|
|
176
|
+
{preview.affectedBoardIds.length + 1} affected boards
|
|
177
|
+
</p>
|
|
178
|
+
<ul class="board-move-cards">
|
|
179
|
+
{preview.nodeIds.map((id) => (
|
|
180
|
+
<li key={id}>{String(nodes.value.get(id)?.data.title ?? id)}</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
{preview.readmeNodeId && <p>The README moves with the cards.</p>}
|
|
184
|
+
{!!preview.liveTaskNodeIds.length && (
|
|
185
|
+
<p>{preview.liveTaskNodeIds.length} live task cards keep their original task owner.</p>
|
|
186
|
+
)}
|
|
187
|
+
<p>Content, relations and review history are preserved. Moving does not validate or read content.</p>
|
|
188
|
+
<p class="board-move-warning">
|
|
189
|
+
⚠ History boundary: pre-move Undo/Redo is cleared on affected boards. Older snapshots remain
|
|
190
|
+
inspectable, but cannot be restored across the move.
|
|
191
|
+
</p>
|
|
192
|
+
<p>Board-space annotations stay here. The new board opens after the move.</p>
|
|
193
|
+
{blocked && (
|
|
194
|
+
<div role="alert" class="board-move-warning">
|
|
195
|
+
<strong>⚠ Cannot move this selection</strong>
|
|
196
|
+
<ul>
|
|
197
|
+
{preview.blockers.map((reason) => (
|
|
198
|
+
<li key={reason}>{reason}</li>
|
|
199
|
+
))}
|
|
200
|
+
</ul>
|
|
201
|
+
<p>No cards moved. Close, resolve the listed bindings and preview again.</p>
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</>
|
|
205
|
+
)}
|
|
206
|
+
{error && (
|
|
207
|
+
<p role="alert" class="board-move-warning">
|
|
208
|
+
⚠ {error}
|
|
209
|
+
</p>
|
|
210
|
+
)}
|
|
211
|
+
<div class="text-prompt-actions">
|
|
212
|
+
<button type="button" class="text-prompt-cancel" onClick={close}>
|
|
213
|
+
{blocked || refused ? 'Close' : 'Cancel'}
|
|
214
|
+
</button>
|
|
215
|
+
{!blocked && !refused && (
|
|
216
|
+
<button type="submit" class="text-prompt-confirm" disabled={busy || !name.trim()}>
|
|
217
|
+
{busy ? 'Working…' : attempted ? 'Retry move' : preview ? 'Move and open' : 'Preview move'}
|
|
218
|
+
</button>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</form>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { boardList } from '../state/boards-store';
|
|
1
|
+
import { activeBoard, boardList } from '../state/boards-store';
|
|
2
2
|
import { cardQualification, effectiveLifecycleFilter as lifecycleFilter } from '../state/knowledge-store';
|
|
3
3
|
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
4
4
|
import { relationHighlight } from '../state/relation-store';
|
|
5
5
|
import { LifecycleFooter } from './LifecycleChrome';
|
|
6
6
|
import { BarHint } from './BarHint';
|
|
7
|
+
import { openBoardMove } from './BoardMoveDialog';
|
|
7
8
|
import { CrossBoardReferences } from './CrossBoardReferences';
|
|
8
9
|
import { BoardPinButton } from './BoardPinButton';
|
|
9
10
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
@@ -36,6 +37,8 @@ import {
|
|
|
36
37
|
updateNodeData,
|
|
37
38
|
viewport,
|
|
38
39
|
canvasTool,
|
|
40
|
+
groupContents,
|
|
41
|
+
openGroup,
|
|
39
42
|
} from '../state/canvas-store';
|
|
40
43
|
import {
|
|
41
44
|
addToGroupFromClient,
|
|
@@ -50,7 +53,7 @@ import { AxStepControls } from '../nodes/AxStepControls';
|
|
|
50
53
|
import { getNodeIcon, IconChevronRight, IconClose, IconMore, IconExpand, IconPin } from '../icons';
|
|
51
54
|
import { EXPANDABLE_TYPES, TYPE_LABELS } from '../types';
|
|
52
55
|
import type { CanvasNodeState } from '../types';
|
|
53
|
-
import {
|
|
56
|
+
import { computeAutoFitHeight, shouldAutoFitNode } from './auto-fit';
|
|
54
57
|
import { activeGuides, buildSnapCache, clearSnapCache, snapToGuides } from './snap-guides';
|
|
55
58
|
import { useNodeDrag } from './use-node-drag';
|
|
56
59
|
import { useNodeResize } from './use-node-resize';
|
|
@@ -89,44 +92,6 @@ export function nearestNodeInDirection(
|
|
|
89
92
|
return best;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
/** Fraction of a node's height the title bar may occupy before it starts
|
|
93
|
-
* crowding out the content it is labelling. */
|
|
94
|
-
const MAX_TITLEBAR_HEIGHT_RATIO = 0.4;
|
|
95
|
-
/** Fraction of a node's width the fixed title-bar chrome may occupy before it
|
|
96
|
-
* starts eating the node's NAME. */
|
|
97
|
-
const MAX_TITLEBAR_CHROME_WIDTH_RATIO = 0.55;
|
|
98
|
-
/** Unscaled width of everything in the bar that is not the title: type icon,
|
|
99
|
-
* status/type pill, the control cluster, and the gaps between them. */
|
|
100
|
-
const TITLEBAR_CHROME_BASE_WIDTH = 175;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* How much to enlarge node chrome (title bar, badges, icons) when zoomed out so
|
|
104
|
-
* it stays legible. Full inverse compensation is capped at 2.2x — but the cap
|
|
105
|
-
* alone ignored the node it is drawn on, so on SHORT nodes the growing bar ate
|
|
106
|
-
* the body: a 116px-tall section label at 46% zoom gave the title bar 72% of the
|
|
107
|
-
* node and left 14px for text that needed 41, so the markdown was simply cut off.
|
|
108
|
-
* The scale is therefore also bounded by the node's own height, keeping the bar
|
|
109
|
-
* under MAX_TITLEBAR_HEIGHT_RATIO of it. Tall nodes are unaffected (their height
|
|
110
|
-
* never binds); pass height 0 for collapsed/auto-height nodes to skip the bound.
|
|
111
|
-
*/
|
|
112
|
-
export function nodeChromeScale(viewportScale: number, nodeHeight: number, nodeWidth = 0): number {
|
|
113
|
-
if (!Number.isFinite(viewportScale) || viewportScale >= 1) return 1;
|
|
114
|
-
const zoomScale = Math.min(2.2, 1 / Math.max(viewportScale, 0.01));
|
|
115
|
-
let bounded = zoomScale;
|
|
116
|
-
if (Number.isFinite(nodeHeight) && nodeHeight > 0) {
|
|
117
|
-
bounded = Math.min(bounded, (nodeHeight * MAX_TITLEBAR_HEIGHT_RATIO) / AUTO_FIT_TITLEBAR_HEIGHT);
|
|
118
|
-
}
|
|
119
|
-
// Width matters as much as height: the icon, pill and control cluster are all
|
|
120
|
-
// flex-shrink: 0, so the NAME is the only thing that gives way. Unbounded, a
|
|
121
|
-
// 360px step node at 52% zoom spent 100 of 187 bar pixels on controls alone and
|
|
122
|
-
// the title collapsed to nothing. Bound the scale so the fixed chrome can never
|
|
123
|
-
// take more than MAX_TITLEBAR_CHROME_WIDTH_RATIO of the node.
|
|
124
|
-
if (Number.isFinite(nodeWidth) && nodeWidth > 0) {
|
|
125
|
-
bounded = Math.min(bounded, (nodeWidth * MAX_TITLEBAR_CHROME_WIDTH_RATIO) / TITLEBAR_CHROME_BASE_WIDTH);
|
|
126
|
-
}
|
|
127
|
-
return Math.max(1, bounded);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
95
|
/** A header title keeps at least this much room, or all of it when shorter (HeaderMarks.dc.html). */
|
|
131
96
|
const TITLE_MIN_WIDTH = 140;
|
|
132
97
|
|
|
@@ -217,7 +182,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
217
182
|
document.removeEventListener('keydown', escListener.current);
|
|
218
183
|
escListener.current = null;
|
|
219
184
|
}
|
|
220
|
-
persistLayout();
|
|
185
|
+
persistLayout({ raisedNodeId: node.id });
|
|
221
186
|
// Membership changes ONLY here, and only while the pill was showing.
|
|
222
187
|
const target = dragDropTarget.value;
|
|
223
188
|
endDropTracking();
|
|
@@ -308,12 +273,15 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
308
273
|
toggleSelected(node.id);
|
|
309
274
|
return;
|
|
310
275
|
}
|
|
311
|
-
bringToFront(node.id);
|
|
276
|
+
bringToFront(node.id, false);
|
|
312
277
|
// User wins (phase 8): hold the node for the drag; an agent mid-edit yields.
|
|
313
278
|
reportHumanGrab(node.id);
|
|
314
279
|
noteNearAtGrab(node.id);
|
|
315
280
|
takeOverNode(node.id, typeof node.data.title === 'string' && node.data.title ? node.data.title : node.type);
|
|
316
|
-
buildSnapCache(
|
|
281
|
+
buildSnapCache(
|
|
282
|
+
node.id,
|
|
283
|
+
[...nodes.value.values()].filter((candidate) => !groupContents.value || groupContents.value.has(candidate.id)),
|
|
284
|
+
);
|
|
317
285
|
if (node.type !== 'group') {
|
|
318
286
|
const onKey = (ev: KeyboardEvent) => {
|
|
319
287
|
if (ev.key === 'Escape') suppressDropForDrag();
|
|
@@ -359,15 +327,33 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
359
327
|
(e: KeyboardEvent) => {
|
|
360
328
|
const target = e.target as HTMLElement | null;
|
|
361
329
|
if (target && target !== e.currentTarget && target.closest('input, textarea, [contenteditable="true"]')) return;
|
|
330
|
+
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
|
|
331
|
+
const more = shellRef.current?.querySelector<HTMLButtonElement>('.node-hover-control[aria-haspopup="menu"]');
|
|
332
|
+
if (more) {
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
e.stopPropagation();
|
|
335
|
+
more.click();
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
362
339
|
const dir = ARROW_DIRS[e.key];
|
|
363
340
|
if (dir) {
|
|
364
341
|
e.preventDefault();
|
|
365
|
-
const next = nearestNodeInDirection(
|
|
342
|
+
const next = nearestNodeInDirection(
|
|
343
|
+
node,
|
|
344
|
+
dir,
|
|
345
|
+
[...nodes.value.values()].filter(
|
|
346
|
+
(candidate) => !groupContents.value || groupContents.value.has(candidate.id),
|
|
347
|
+
),
|
|
348
|
+
);
|
|
366
349
|
if (!next) return;
|
|
367
350
|
bringToFront(next.id);
|
|
368
351
|
requestAnimationFrame(() => {
|
|
369
352
|
document.querySelector<HTMLElement>(`.canvas-node[data-node-id="${CSS.escape(next.id)}"]`)?.focus();
|
|
370
353
|
});
|
|
354
|
+
} else if (e.key === 'Enter' && e.target === e.currentTarget && node.type === 'group') {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
openGroup(node.id);
|
|
371
357
|
} else if (e.key === 'Enter' && e.target === e.currentTarget && EXPANDABLE_TYPES.has(node.type)) {
|
|
372
358
|
e.preventDefault();
|
|
373
359
|
expandNode(node.id);
|
|
@@ -409,6 +395,8 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
409
395
|
if (!body) return;
|
|
410
396
|
|
|
411
397
|
const observer = new ResizeObserver(() => {
|
|
398
|
+
// Focus-mode return can remount an open work draft. Never save its temporary form height.
|
|
399
|
+
if (body.querySelector('.work-card-editor')) hasAutoFit.current = true;
|
|
412
400
|
if (hasAutoFit.current) {
|
|
413
401
|
observer.disconnect();
|
|
414
402
|
return;
|
|
@@ -445,10 +433,14 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
445
433
|
const isTrace = node.type === 'trace';
|
|
446
434
|
const isTraceRunning = isTrace && node.data.status === 'running';
|
|
447
435
|
const isGroup = node.type === 'group';
|
|
436
|
+
const isHeading = node.type === 'markdown' && node.data.display === 'heading';
|
|
437
|
+
const graphType = (node.data.graphConfig as { graphType?: string } | undefined)?.graphType;
|
|
448
438
|
const isStrictSize = node.data.strictSize === true;
|
|
449
439
|
const viewportScale = Math.max(viewport.value.scale, 0.01);
|
|
450
|
-
|
|
451
|
-
|
|
440
|
+
// Text alone may grow within the fixed header line. Enlarging the whole bar
|
|
441
|
+
// resizes embedded viewports and reflows their content while zooming.
|
|
442
|
+
const titleScale = Math.min(1.2, Math.max(1, 1 / viewportScale));
|
|
443
|
+
const chipFold = useHeaderFold(titleRef, headerChip(node, isContextPinned), title, node.size.width, titleScale);
|
|
452
444
|
|
|
453
445
|
const groupColor = isGroup && node.data.color ? (node.data.color as string) : undefined;
|
|
454
446
|
const nodeStyle: Record<string, string | number> = {
|
|
@@ -457,7 +449,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
457
449
|
width: `${node.size.width}px`,
|
|
458
450
|
height: node.collapsed ? 'auto' : `${node.size.height}px`,
|
|
459
451
|
zIndex: node.zIndex,
|
|
460
|
-
'--node-
|
|
452
|
+
'--node-title-scale': titleScale.toFixed(3),
|
|
461
453
|
'--node-port-scale': (1 / viewportScale).toFixed(3),
|
|
462
454
|
...(groupColor ? { '--group-color': groupColor } : {}),
|
|
463
455
|
};
|
|
@@ -477,6 +469,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
477
469
|
isTrace ? 'trace-node' : '',
|
|
478
470
|
isTraceRunning ? 'trace-running' : '',
|
|
479
471
|
isGroup ? 'group-node' : '',
|
|
472
|
+
isHeading ? 'heading-node' : '',
|
|
480
473
|
isStrictSize ? 'strict-size' : '',
|
|
481
474
|
isAgentMutating ? 'agent-mutating' : '',
|
|
482
475
|
sessionChromeActive.value && sessionEditedIds.value.has(node.id) ? 'session-edited' : '',
|
|
@@ -516,8 +509,12 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
516
509
|
// Collapsed group: a compact chip. Children keep their positions for restore.
|
|
517
510
|
return (
|
|
518
511
|
<div
|
|
512
|
+
ref={shellRef}
|
|
519
513
|
class={`${nodeClass} group-chip`}
|
|
520
514
|
data-node-type={node.type}
|
|
515
|
+
data-node-id={node.id}
|
|
516
|
+
tabIndex={isActive ? 0 : -1}
|
|
517
|
+
onKeyDown={handleNodeKeyDown}
|
|
521
518
|
style={{ ...nodeStyle, width: 'auto', height: 'auto' }}
|
|
522
519
|
onPointerDown={handlePointerDown}
|
|
523
520
|
onDblClick={handleDblClick}
|
|
@@ -623,7 +620,9 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
623
620
|
) : (
|
|
624
621
|
<span class="group-name">{title}</span>
|
|
625
622
|
)}
|
|
626
|
-
<span class="group-count">
|
|
623
|
+
<span class="group-count">
|
|
624
|
+
{groupChildren.length} {groupChildren.length === 1 ? 'node' : 'nodes'}
|
|
625
|
+
</span>
|
|
627
626
|
</span>
|
|
628
627
|
<span class="group-edge-spacer" />
|
|
629
628
|
<span class="group-actions" role="toolbar" aria-label={`${title} actions`}>
|
|
@@ -693,6 +692,28 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
693
692
|
</button>
|
|
694
693
|
{groupMenuOpen && (
|
|
695
694
|
<div class="group-menu" role="menu" onPointerDown={(e) => e.stopPropagation()}>
|
|
695
|
+
<button
|
|
696
|
+
type="button"
|
|
697
|
+
role="menuitem"
|
|
698
|
+
onClick={(e) => {
|
|
699
|
+
e.stopPropagation();
|
|
700
|
+
setGroupMenuOpen(false);
|
|
701
|
+
openGroup(node.id);
|
|
702
|
+
}}
|
|
703
|
+
>
|
|
704
|
+
Open group
|
|
705
|
+
</button>
|
|
706
|
+
<button
|
|
707
|
+
type="button"
|
|
708
|
+
role="menuitem"
|
|
709
|
+
onClick={(e) => {
|
|
710
|
+
e.stopPropagation();
|
|
711
|
+
setGroupMenuOpen(false);
|
|
712
|
+
openBoardMove([node.id]);
|
|
713
|
+
}}
|
|
714
|
+
>
|
|
715
|
+
Move to new board
|
|
716
|
+
</button>
|
|
696
717
|
<button
|
|
697
718
|
type="button"
|
|
698
719
|
role="menuitem"
|
|
@@ -740,7 +761,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
740
761
|
<NodeTypeIcon node={node} pinned={isContextPinned} title={title} fold={chipFold}>
|
|
741
762
|
{(() => {
|
|
742
763
|
const NodeIcon = getNodeIcon(node.type);
|
|
743
|
-
return <NodeIcon size={
|
|
764
|
+
return <NodeIcon size={14} />;
|
|
744
765
|
})()}
|
|
745
766
|
</NodeTypeIcon>
|
|
746
767
|
{/* The kind-colored icon says what type this is (rail-chrome-v2 card
|
|
@@ -766,6 +787,14 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
766
787
|
{title}
|
|
767
788
|
</span>
|
|
768
789
|
)}
|
|
790
|
+
{activeBoard()?.readmeNodeId === node.id && <span class="node-readme-mark">README</span>}
|
|
791
|
+
{node.type === 'graph' && graphType && <span class="node-header-meta">{graphType}</span>}
|
|
792
|
+
{node.type === 'file' && <div class="node-file-toolbar" data-file-toolbar={node.id} />}
|
|
793
|
+
{node.type === 'status' && typeof node.data.updatedAt === 'string' && (
|
|
794
|
+
<time class="node-header-meta" dateTime={node.data.updatedAt}>
|
|
795
|
+
{new Date(node.data.updatedAt).toLocaleTimeString()}
|
|
796
|
+
</time>
|
|
797
|
+
)}
|
|
769
798
|
<NearPinMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
770
799
|
<NodeContextMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
771
800
|
<div class="node-controls">
|
|
@@ -827,6 +856,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
827
856
|
onContextMenu?.(e, node.id);
|
|
828
857
|
}}
|
|
829
858
|
aria-label="More actions"
|
|
859
|
+
aria-haspopup="menu"
|
|
830
860
|
>
|
|
831
861
|
<IconMore size={14} />
|
|
832
862
|
</button>
|