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.
Files changed (343) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +3 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +688 -128
  4. package/CHANGELOG.md +69 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +251 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1785 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +251 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/abnfDiagram-N423BO3Z-cwtsds5d.js +1 -0
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/architectureDiagram-T3A2C74G-b4xc9tpz.js +36 -0
  35. package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-ahp3yxxs.js +132 -0
  36. package/dist/canvas/chunks/c4Diagram-5PPSVZJV-nappnay7.js +10 -0
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/cose-bilkent-JH36ORCC-a428syda.js +1 -0
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-nspsqebj.js +62 -0
  42. package/dist/canvas/chunks/dagre-VZM6K2ZE-8y6177fh.js +4 -0
  43. package/dist/canvas/chunks/diagram-7IWD3JNH-vym028wn.js +30 -0
  44. package/dist/canvas/chunks/diagram-B4RE2ZJO-jddqb8sy.js +3 -0
  45. package/dist/canvas/chunks/diagram-LBJQPF4R-bjwbpkrn.js +24 -0
  46. package/dist/canvas/chunks/diagram-Q27KOJAE-j6ph6hcx.js +24 -0
  47. package/dist/canvas/chunks/diagram-UB23O5K3-p09w8jej.js +41 -0
  48. package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-j975ycq5.js +1 -0
  49. package/dist/canvas/chunks/erDiagram-JOGREHBK-sxp6hjn8.js +85 -0
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-4tv230y2.js +292 -0
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-mqgmnw9g.js +106 -0
  55. package/dist/canvas/chunks/index-037z5dz2.js +232 -0
  56. package/dist/canvas/chunks/index-081n3har.js +207 -0
  57. package/dist/canvas/chunks/index-5n03dmb9.js +2 -0
  58. package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
  59. package/dist/canvas/chunks/index-60pgzdqe.js +157 -0
  60. package/dist/canvas/chunks/index-6ceya556.js +332 -0
  61. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  62. package/dist/canvas/chunks/index-8rjes9r6.js +2 -0
  63. package/dist/canvas/chunks/index-9022d1n9.js +16 -0
  64. package/dist/canvas/chunks/index-9b02042t.js +2 -0
  65. package/dist/canvas/chunks/index-ap4q4vez.js +2 -0
  66. package/dist/canvas/chunks/index-dyp2nhd3.js +2 -0
  67. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  68. package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
  69. package/dist/canvas/chunks/index-h9ycj8n2.js +2 -0
  70. package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
  71. package/dist/canvas/chunks/index-mergmj97.js +2 -0
  72. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  73. package/dist/canvas/chunks/index-nz8qbd1p.js +2 -0
  74. package/dist/canvas/chunks/index-qhfrazv3.js +2 -0
  75. package/dist/canvas/chunks/index-v119nznk.js +4 -0
  76. package/dist/canvas/chunks/index-vpt37m3d.js +89 -0
  77. package/dist/canvas/chunks/index-y9ma6azy.js +2 -0
  78. package/dist/canvas/chunks/index-zx0j3a2z.js +179 -0
  79. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  80. package/dist/canvas/chunks/infoDiagram-6WML65LV-qv97qq6a.js +2 -0
  81. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  82. package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-sv67zats.js +139 -0
  83. package/dist/canvas/chunks/kanban-definition-27J2QSJJ-k2pfam6p.js +89 -0
  84. package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
  85. package/dist/canvas/chunks/mermaid.core-83r0fdce.js +12 -0
  86. package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-vg2newm1.js +96 -0
  87. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  88. package/dist/canvas/chunks/pegDiagram-VL7TDLO6-k2h7zc5n.js +1 -0
  89. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  90. package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-hg169p1v.js +39 -0
  91. package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-bxtvsq0b.js +7 -0
  92. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  93. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  94. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  95. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  96. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  97. package/dist/canvas/chunks/railroadDiagram-AXF67PYL-njyhcp4p.js +1 -0
  98. package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-2z6v09gm.js +84 -0
  99. package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-15mzam0e.js +40 -0
  100. package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-rbshyd2c.js +162 -0
  101. package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-sg26xwts.js +1 -0
  102. package/dist/canvas/chunks/stateDiagram-OKZ733FA-6eahp98e.js +1 -0
  103. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  104. package/dist/canvas/chunks/swimlanes-SLNWSIFB-ze5db10t.js +1 -0
  105. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  106. package/dist/canvas/chunks/timeline-definition-Z64GVDOM-vnp7jyy8.js +120 -0
  107. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  108. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  109. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  110. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  111. package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-8nxg8wqt.js +78 -0
  112. package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-nj921e7s.js +7 -0
  113. package/dist/canvas/global.css +483 -230
  114. package/dist/canvas/index.js +158 -219
  115. package/dist/canvas/mcp-app.html +4301 -548
  116. package/dist/canvas/mermaid-entry.js +338 -338
  117. package/dist/canvas/surface-theme.css +6 -6
  118. package/dist/json-render/index.css +1 -1
  119. package/dist/json-render/index.js +49 -49
  120. package/dist/types/client/canvas/BarHint.d.ts +5 -3
  121. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  122. package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
  123. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  124. package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
  125. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  126. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  127. package/dist/types/client/canvas/auto-fit.d.ts +4 -5
  128. package/dist/types/client/mermaid-entry.d.ts +1 -2
  129. package/dist/types/client/mermaid-render.d.ts +1 -0
  130. package/dist/types/client/nodes/BoardNode.d.ts +1 -1
  131. package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
  132. package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
  133. package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
  134. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  135. package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
  136. package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
  137. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  138. package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
  139. package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
  140. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  141. package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
  142. package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
  143. package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
  144. package/dist/types/client/state/canvas-store.d.ts +18 -1
  145. package/dist/types/client/state/human-store.d.ts +2 -0
  146. package/dist/types/client/state/intent-bridge.d.ts +5 -2
  147. package/dist/types/client/state/presentation.d.ts +17 -6
  148. package/dist/types/client/state/relation-store.d.ts +11 -0
  149. package/dist/types/client/types.d.ts +1 -1
  150. package/dist/types/json-render/catalog.d.ts +2 -2
  151. package/dist/types/json-render/server.d.ts +2 -1
  152. package/dist/types/server/agent-presence.d.ts +1 -0
  153. package/dist/types/server/ax-interaction.d.ts +1 -0
  154. package/dist/types/server/ax-state-manager.d.ts +10 -2
  155. package/dist/types/server/board-local.d.ts +4 -0
  156. package/dist/types/server/board-move.d.ts +38 -0
  157. package/dist/types/server/canvas-db.d.ts +12 -2
  158. package/dist/types/server/canvas-operations.d.ts +5 -6
  159. package/dist/types/server/canvas-provenance.d.ts +1 -1
  160. package/dist/types/server/canvas-serialization.d.ts +1 -0
  161. package/dist/types/server/canvas-state.d.ts +86 -20
  162. package/dist/types/server/card-relocation.d.ts +6 -0
  163. package/dist/types/server/context-reads.d.ts +3 -1
  164. package/dist/types/server/human-presence.d.ts +1 -1
  165. package/dist/types/server/index.d.ts +26 -10
  166. package/dist/types/server/intent-registry.d.ts +8 -4
  167. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  168. package/dist/types/server/mutation-history.d.ts +7 -5
  169. package/dist/types/server/operations/ops/boards.d.ts +0 -1
  170. package/dist/types/server/operations/registry.d.ts +2 -0
  171. package/dist/types/server/tour-control.d.ts +13 -3
  172. package/dist/types/server/trace-manager.d.ts +1 -0
  173. package/dist/types/shared/ax-kinds.d.ts +6 -0
  174. package/dist/types/shared/ax-surface-protocol.d.ts +8 -9
  175. package/dist/types/shared/human-presence.d.ts +1 -0
  176. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  177. package/dist/types/shared/tour.d.ts +8 -0
  178. package/docs/RELEASE.md +21 -14
  179. package/docs/agent-plugin.md +16 -2
  180. package/docs/amp-plugin.md +271 -0
  181. package/docs/cli.md +42 -3
  182. package/docs/design/Chrome.dc.html +32 -6
  183. package/docs/design/GuestName.dc.html +50 -0
  184. package/docs/design/PluginLogo.svg +7 -0
  185. package/docs/design/Types1.dc.html +3 -2
  186. package/docs/design/Types2.dc.html +1 -1
  187. package/docs/design/Types3.dc.html +1 -1
  188. package/docs/design/WorkCards.dc.html +39 -0
  189. package/docs/design/Zoom.dc.html +37 -60
  190. package/docs/design/canvas.json +17 -1
  191. package/docs/design/plugin-logo.png +0 -0
  192. package/docs/design.md +280 -12
  193. package/docs/evals/agents-md-sections.md +38 -0
  194. package/docs/http-api.md +179 -11
  195. package/docs/mcp.md +69 -9
  196. package/docs/node-types.md +109 -4
  197. package/docs/plans/plan-009-tech-debt-backlog.md +3 -3
  198. package/docs/plans/plan-019-post-0.9-roadmap.md +327 -31
  199. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  200. package/docs/product-vision-2026-09.md +42 -17
  201. package/docs/sdk.md +18 -0
  202. package/package.json +7 -4
  203. package/plugins/pmx-canvas/assets/preview.png +0 -0
  204. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
  205. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  206. package/plugins/pmx-canvas/plugin.json +2 -2
  207. package/plugins/pmx-canvas/runtime.json +1 -1
  208. package/plugins/pmx-canvas/tools.json +688 -128
  209. package/skills/data-analysis/SKILL.md +13 -26
  210. package/skills/frontend-design/LICENSE.txt +177 -0
  211. package/skills/frontend-design/SKILL.md +3 -0
  212. package/skills/pmx-canvas/SKILL.md +232 -849
  213. package/skills/pmx-canvas/evals/evals.json +26 -3
  214. package/skills/pmx-canvas/references/full-reference.md +89 -24
  215. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  216. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  217. package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
  218. package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
  219. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  220. package/skills/published-consumer-e2e/SKILL.md +25 -8
  221. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -77
  222. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  223. package/skills/web-artifacts-builder/SKILL.md +6 -0
  224. package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
  225. package/src/cli/commands/apps.ts +1 -0
  226. package/src/cli/commands/boards.ts +30 -0
  227. package/src/client/App.tsx +20 -4
  228. package/src/client/canvas/AttentionHistory.tsx +10 -2
  229. package/src/client/canvas/BarHint.tsx +6 -3
  230. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  231. package/src/client/canvas/CanvasNode.tsx +79 -49
  232. package/src/client/canvas/CanvasViewport.tsx +42 -14
  233. package/src/client/canvas/ContextMenu.tsx +97 -15
  234. package/src/client/canvas/EdgeEditor.tsx +149 -0
  235. package/src/client/canvas/EdgeLayer.tsx +441 -174
  236. package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -2
  237. package/src/client/canvas/GroupNavigation.tsx +85 -0
  238. package/src/client/canvas/Presentation.tsx +53 -3
  239. package/src/client/canvas/RelationsPanel.tsx +9 -0
  240. package/src/client/canvas/SelectionBar.tsx +21 -0
  241. package/src/client/canvas/SessionPanel.tsx +35 -1
  242. package/src/client/canvas/TextPrompt.tsx +18 -1
  243. package/src/client/canvas/TopBar.tsx +54 -9
  244. package/src/client/canvas/auto-fit.ts +9 -11
  245. package/src/client/canvas/canvas-area.ts +2 -2
  246. package/src/client/canvas/use-focus-trap.ts +6 -1
  247. package/src/client/mermaid-entry.ts +6 -20
  248. package/src/client/mermaid-render.ts +39 -0
  249. package/src/client/nodes/AxStepControls.tsx +45 -69
  250. package/src/client/nodes/BoardNode.tsx +65 -9
  251. package/src/client/nodes/ContextNode.tsx +88 -396
  252. package/src/client/nodes/ExtAppFrame.tsx +302 -1465
  253. package/src/client/nodes/FileNode.tsx +92 -81
  254. package/src/client/nodes/HtmlNode.tsx +8 -36
  255. package/src/client/nodes/LedgerNode.tsx +31 -17
  256. package/src/client/nodes/MarkdownNode.tsx +14 -24
  257. package/src/client/nodes/McpAppNode.tsx +48 -154
  258. package/src/client/nodes/MermaidNode.tsx +80 -69
  259. package/src/client/nodes/PromptNode.tsx +44 -17
  260. package/src/client/nodes/ResponseNode.tsx +8 -6
  261. package/src/client/nodes/StatusNode.tsx +34 -7
  262. package/src/client/nodes/StatusSummary.tsx +16 -3
  263. package/src/client/nodes/TraceNode.tsx +8 -8
  264. package/src/client/nodes/ViewerFrame.tsx +273 -0
  265. package/src/client/nodes/ViewerHost.tsx +41 -0
  266. package/src/client/nodes/WebpageNode.tsx +22 -24
  267. package/src/client/nodes/WorkCard.tsx +175 -0
  268. package/src/client/nodes/iframe-document-url.ts +20 -27
  269. package/src/client/nodes/primitive-copy.ts +97 -0
  270. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  271. package/src/client/nodes/use-iframe-content-height.ts +34 -10
  272. package/src/client/nodes/use-surface-frame.ts +22 -11
  273. package/src/client/nodes/viewer-runtime-state.ts +41 -35
  274. package/src/client/state/attention-bridge.ts +5 -0
  275. package/src/client/state/attention-store.ts +1 -0
  276. package/src/client/state/canvas-store.ts +202 -26
  277. package/src/client/state/human-store.ts +22 -2
  278. package/src/client/state/intent-bridge.ts +20 -5
  279. package/src/client/state/presentation.ts +135 -15
  280. package/src/client/state/relation-store.ts +127 -1
  281. package/src/client/state/sse-bridge.ts +18 -19
  282. package/src/client/theme/global.css +483 -230
  283. package/src/client/theme/surface-theme.css +6 -6
  284. package/src/client/types.ts +1 -0
  285. package/src/json-render/charts/tufte-components.tsx +44 -2
  286. package/src/json-render/renderer/index.css +62 -2
  287. package/src/json-render/renderer/index.tsx +31 -7
  288. package/src/json-render/server.ts +3 -2
  289. package/src/mcp/server.ts +13 -3
  290. package/src/server/agent-presence.ts +43 -23
  291. package/src/server/ax-flow-loop.ts +6 -0
  292. package/src/server/ax-gate-ttl.ts +8 -1
  293. package/src/server/ax-interaction.ts +16 -10
  294. package/src/server/ax-state-manager.ts +17 -4
  295. package/src/server/ax-wait.ts +20 -6
  296. package/src/server/board-export.ts +54 -30
  297. package/src/server/board-local.ts +20 -0
  298. package/src/server/board-move.ts +297 -0
  299. package/src/server/canvas-db.ts +249 -158
  300. package/src/server/canvas-geometry.ts +1 -0
  301. package/src/server/canvas-operations.ts +139 -109
  302. package/src/server/canvas-schema.ts +21 -0
  303. package/src/server/canvas-serialization.ts +2 -0
  304. package/src/server/canvas-state.ts +797 -159
  305. package/src/server/card-relocation.ts +53 -0
  306. package/src/server/code-graph.ts +4 -3
  307. package/src/server/context-reads.ts +83 -55
  308. package/src/server/demo-state.json +140 -114
  309. package/src/server/file-watcher.ts +30 -10
  310. package/src/server/html-primitives.ts +3 -9
  311. package/src/server/html-surface.ts +8 -0
  312. package/src/server/human-presence.ts +18 -5
  313. package/src/server/index.ts +54 -24
  314. package/src/server/intent-registry.ts +43 -5
  315. package/src/server/knowledge-lifecycle.ts +32 -9
  316. package/src/server/lifecycle-status.ts +29 -1
  317. package/src/server/mcp-app-runtime.ts +26 -7
  318. package/src/server/mutation-history.ts +37 -8
  319. package/src/server/operations/composites.ts +6 -3
  320. package/src/server/operations/mcp-contracts.ts +32 -2
  321. package/src/server/operations/mcp.ts +36 -9
  322. package/src/server/operations/ops/annotation.ts +22 -1
  323. package/src/server/operations/ops/ax-await.ts +1 -0
  324. package/src/server/operations/ops/ax-timeline.ts +28 -2
  325. package/src/server/operations/ops/batch.ts +25 -11
  326. package/src/server/operations/ops/boards.ts +45 -6
  327. package/src/server/operations/ops/nodes.ts +70 -0
  328. package/src/server/operations/ops/query.ts +32 -3
  329. package/src/server/operations/ops/snapshots.ts +1 -0
  330. package/src/server/operations/ops/viewport.ts +11 -9
  331. package/src/server/operations/registry.ts +49 -4
  332. package/src/server/server.ts +69 -75
  333. package/src/server/tour-control.ts +97 -12
  334. package/src/server/trace-manager.ts +4 -0
  335. package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
  336. package/src/shared/agent-context.ts +7 -1
  337. package/src/shared/auto-arrange.ts +5 -4
  338. package/src/shared/ax-kinds.ts +13 -0
  339. package/src/shared/ax-surface-protocol.ts +20 -9
  340. package/src/shared/content-height-reporter.ts +4 -3
  341. package/src/shared/human-presence.ts +1 -0
  342. package/src/shared/lifecycle-status.ts +2 -0
  343. 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: { node: CanvasNodeState; onContextMenu?: (e: MouseEvent, nodeId: string) => void }) {
109
- return next.node !== this.props.node || next.onContextMenu !== this.props.onContextMenu;
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
- <CanvasNode node={node} onContextMenu={onContextMenu}>
116
- {renderNodeContent(node)}
117
- </CanvasNode>
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(hiddenByCollapsedGroup.value.keys()),
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
- {/* D4: CSS matrix(a,b,c,d,tx,ty) — scale uniformly (a=d=scale, b=c=0)
877
- then translate (tx=v.x, ty=v.y). transformOrigin: '0 0' ensures
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={nodes} edges={edges} part="lines" />
898
- <EdgeLayer nodes={nodes} edges={edges} part="pills" />
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 key={node.id} node={node} onContextMenu={onNodeContextMenu} />
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 { activeBoard, boardIncarnation } from '../state/boards-store';
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
- setMenu({ kind: 'node', x: e.clientX, y: e.clientY, nodeId });
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
- items.push({
527
- label: 'Rename…',
528
- action: () => {
529
- void askText('Rename node', 'Node title', {
530
- initial: typeof node.data.title === 'string' ? node.data.title : '',
531
- confirm: 'Save',
532
- }).then((title) => {
533
- if (!title?.trim()) return;
534
- void updateNodeFromClient(node.id, { title: title.trim() });
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
+ }