pmx-canvas 0.9.3 → 0.11.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 (237) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +9418 -2016
  3. package/CHANGELOG.md +54 -1
  4. package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
  5. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
  6. package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
  7. package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
  8. package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
  9. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
  10. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
  11. package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
  12. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
  13. package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
  14. package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
  15. package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
  16. package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
  17. package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
  18. package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
  19. package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
  20. package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
  21. package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
  22. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
  23. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
  24. package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
  25. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
  26. package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
  27. package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
  28. package/dist/canvas/chunks/index-194m3hc3.js +2 -0
  29. package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
  30. package/dist/canvas/chunks/index-353m7b0p.js +2 -0
  31. package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
  32. package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
  33. package/dist/canvas/chunks/index-6ceya556.js +332 -0
  34. package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
  35. package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
  36. package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
  37. package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
  38. package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
  39. package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
  40. package/dist/canvas/chunks/index-nprm96xp.js +16 -0
  41. package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
  42. package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
  43. package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
  44. package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
  45. package/dist/canvas/chunks/index-s4j678gj.js +2 -0
  46. package/dist/canvas/chunks/index-tb1wf525.js +179 -0
  47. package/dist/canvas/chunks/index-vv56h019.js +2 -0
  48. package/dist/canvas/chunks/index-w2mra17t.js +207 -0
  49. package/dist/canvas/chunks/index-wgv7en21.js +232 -0
  50. package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
  51. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
  52. package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
  53. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
  54. package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
  55. package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
  56. package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
  57. package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
  58. package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
  59. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
  60. package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
  61. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
  62. package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
  63. package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
  64. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
  65. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
  66. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
  67. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
  68. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
  69. package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
  70. package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
  71. package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
  72. package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
  73. package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
  74. package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
  75. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
  76. package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
  77. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
  78. package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
  79. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
  80. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
  81. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
  82. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
  83. package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
  84. package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
  85. package/dist/canvas/global.css +389 -229
  86. package/dist/canvas/index.js +141 -211
  87. package/dist/canvas/mcp-app.html +4205 -554
  88. package/dist/canvas/mermaid-entry.js +338 -338
  89. package/dist/canvas/surface-theme.css +6 -6
  90. package/dist/json-render/index.css +1 -1
  91. package/dist/json-render/index.js +49 -49
  92. package/dist/types/client/canvas/BarHint.d.ts +5 -3
  93. package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
  94. package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
  95. package/dist/types/client/canvas/auto-fit.d.ts +4 -5
  96. package/dist/types/client/mermaid-entry.d.ts +1 -2
  97. package/dist/types/client/mermaid-render.d.ts +1 -0
  98. package/dist/types/client/nodes/BoardNode.d.ts +1 -1
  99. package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
  100. package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
  101. package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
  102. package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
  103. package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
  104. package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
  105. package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
  106. package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
  107. package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
  108. package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
  109. package/dist/types/client/state/canvas-store.d.ts +3 -1
  110. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  111. package/dist/types/client/state/presentation.d.ts +17 -6
  112. package/dist/types/json-render/server.d.ts +2 -1
  113. package/dist/types/mcp/canvas-access.d.ts +1 -12
  114. package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
  115. package/dist/types/mcp/server.d.ts +2 -0
  116. package/dist/types/server/ax-wait.d.ts +1 -0
  117. package/dist/types/server/canvas-operations.d.ts +1 -0
  118. package/dist/types/server/canvas-state.d.ts +2 -0
  119. package/dist/types/server/index.d.ts +11 -6
  120. package/dist/types/server/operations/http.d.ts +10 -0
  121. package/dist/types/server/operations/index.d.ts +2 -2
  122. package/dist/types/server/operations/invoker.d.ts +4 -3
  123. package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
  124. package/dist/types/server/operations/mcp.d.ts +2 -0
  125. package/dist/types/server/operations/ops/batch.d.ts +1 -0
  126. package/dist/types/server/operations/ops/boards.d.ts +0 -1
  127. package/dist/types/server/operations/registry.d.ts +2 -2
  128. package/dist/types/server/operations/types.d.ts +12 -2
  129. package/dist/types/server/server.d.ts +2 -0
  130. package/dist/types/server/tour-control.d.ts +13 -3
  131. package/dist/types/server/webpage-node.d.ts +1 -0
  132. package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
  133. package/dist/types/shared/tour.d.ts +8 -0
  134. package/docs/api-reference.md +2 -2
  135. package/docs/cli.md +1 -1
  136. package/docs/design/Chrome.dc.html +32 -6
  137. package/docs/design/Types1.dc.html +3 -2
  138. package/docs/design/Types2.dc.html +1 -1
  139. package/docs/design/Types3.dc.html +1 -1
  140. package/docs/design/Zoom.dc.html +37 -60
  141. package/docs/design/canvas.json +1 -1
  142. package/docs/design.md +169 -4
  143. package/docs/http-api.md +82 -1
  144. package/docs/mcp.md +101 -21
  145. package/docs/node-types.md +38 -3
  146. package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
  147. package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
  148. package/docs/product-vision-2026-09.md +9 -0
  149. package/docs/sdk.md +5 -2
  150. package/package.json +2 -2
  151. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  152. package/plugins/pmx-canvas/plugin.json +1 -1
  153. package/plugins/pmx-canvas/runtime.json +1 -1
  154. package/plugins/pmx-canvas/tools.json +9418 -2016
  155. package/skills/pmx-canvas/SKILL.md +19 -21
  156. package/skills/pmx-canvas/references/full-reference.md +44 -6
  157. package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
  158. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
  159. package/src/client/canvas/BarHint.tsx +6 -3
  160. package/src/client/canvas/CanvasNode.tsx +34 -47
  161. package/src/client/canvas/CanvasViewport.tsx +6 -7
  162. package/src/client/canvas/ContextMenu.tsx +63 -3
  163. package/src/client/canvas/EdgeLayer.tsx +365 -169
  164. package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
  165. package/src/client/canvas/Presentation.tsx +53 -3
  166. package/src/client/canvas/ToolRail.tsx +2 -0
  167. package/src/client/canvas/TopBar.tsx +24 -9
  168. package/src/client/canvas/auto-fit.ts +9 -11
  169. package/src/client/canvas/canvas-area.ts +1 -1
  170. package/src/client/canvas/use-focus-trap.ts +6 -1
  171. package/src/client/mermaid-entry.ts +6 -20
  172. package/src/client/mermaid-render.ts +39 -0
  173. package/src/client/nodes/AxStepControls.tsx +45 -69
  174. package/src/client/nodes/BoardNode.tsx +65 -9
  175. package/src/client/nodes/ContextNode.tsx +88 -396
  176. package/src/client/nodes/ExtAppFrame.tsx +283 -1462
  177. package/src/client/nodes/FileNode.tsx +92 -81
  178. package/src/client/nodes/HtmlNode.tsx +8 -36
  179. package/src/client/nodes/LedgerNode.tsx +31 -17
  180. package/src/client/nodes/MarkdownNode.tsx +8 -17
  181. package/src/client/nodes/McpAppNode.tsx +48 -154
  182. package/src/client/nodes/MermaidNode.tsx +80 -69
  183. package/src/client/nodes/PromptNode.tsx +12 -14
  184. package/src/client/nodes/ResponseNode.tsx +8 -6
  185. package/src/client/nodes/StatusNode.tsx +22 -7
  186. package/src/client/nodes/StatusSummary.tsx +5 -3
  187. package/src/client/nodes/TraceNode.tsx +8 -8
  188. package/src/client/nodes/ViewerFrame.tsx +262 -0
  189. package/src/client/nodes/ViewerHost.tsx +41 -0
  190. package/src/client/nodes/WebpageNode.tsx +22 -24
  191. package/src/client/nodes/iframe-document-url.ts +20 -27
  192. package/src/client/nodes/primitive-copy.ts +97 -0
  193. package/src/client/nodes/use-iframe-content-height.ts +34 -10
  194. package/src/client/nodes/use-surface-frame.ts +22 -11
  195. package/src/client/nodes/viewer-runtime-state.ts +41 -35
  196. package/src/client/state/canvas-store.ts +18 -12
  197. package/src/client/state/intent-bridge.ts +1 -0
  198. package/src/client/state/presentation.ts +135 -15
  199. package/src/client/state/sse-bridge.ts +18 -19
  200. package/src/client/theme/global.css +389 -229
  201. package/src/client/theme/surface-theme.css +6 -6
  202. package/src/json-render/charts/tufte-components.tsx +44 -2
  203. package/src/json-render/renderer/index.css +62 -2
  204. package/src/json-render/renderer/index.tsx +31 -7
  205. package/src/json-render/server.ts +3 -2
  206. package/src/mcp/canvas-access.ts +6 -47
  207. package/src/mcp/resource-subscriptions.ts +181 -0
  208. package/src/mcp/server.ts +185 -219
  209. package/src/server/ax-wait.ts +21 -6
  210. package/src/server/board-export.ts +35 -24
  211. package/src/server/canvas-geometry.ts +1 -0
  212. package/src/server/canvas-operations.ts +6 -2
  213. package/src/server/canvas-state.ts +5 -0
  214. package/src/server/demo-state.json +106 -100
  215. package/src/server/html-primitives.ts +3 -9
  216. package/src/server/html-surface.ts +8 -0
  217. package/src/server/index.ts +30 -12
  218. package/src/server/operations/http.ts +69 -21
  219. package/src/server/operations/index.ts +8 -1
  220. package/src/server/operations/invoker.ts +53 -5
  221. package/src/server/operations/mcp-contracts.ts +354 -0
  222. package/src/server/operations/mcp.ts +153 -18
  223. package/src/server/operations/ops/ax-await.ts +18 -10
  224. package/src/server/operations/ops/ax-read.ts +2 -0
  225. package/src/server/operations/ops/ax-timeline.ts +2 -2
  226. package/src/server/operations/ops/batch.ts +27 -8
  227. package/src/server/operations/ops/boards.ts +1 -3
  228. package/src/server/operations/ops/nodes.ts +18 -3
  229. package/src/server/operations/ops/viewport.ts +11 -9
  230. package/src/server/operations/registry.ts +24 -4
  231. package/src/server/operations/types.ts +16 -2
  232. package/src/server/server.ts +17 -59
  233. package/src/server/tour-control.ts +66 -6
  234. package/src/server/webpage-node.ts +9 -4
  235. package/src/shared/ax-surface-protocol.ts +0 -11
  236. package/src/shared/content-height-reporter.ts +4 -3
  237. package/src/shared/tour.ts +8 -0
@@ -61,15 +61,7 @@ export function normalizeContextCardDisplay(card: ContextCard): ContextCardDispl
61
61
  : card.state === 'invalid'
62
62
  ? 'Invalid'
63
63
  : 'Loaded';
64
- return {
65
- title,
66
- summary,
67
- pathDisplay,
68
- category,
69
- sourceKind,
70
- status,
71
- required: card.required === true,
72
- };
64
+ return { title, summary, pathDisplay, category, sourceKind, status, required: card.required === true };
73
65
  }
74
66
 
75
67
  function asTrimmedString(value: unknown): string {
@@ -85,17 +77,9 @@ function stripHtmlToText(value: string): string {
85
77
 
86
78
  export function normalizeContextNodeFallback(nodeData: Record<string, unknown>): ContextNodeFallbackDisplay | null {
87
79
  const title = asTrimmedString(nodeData.title);
88
- const content = asTrimmedString(nodeData.content);
89
- const rendered = stripHtmlToText(asTrimmedString(nodeData.rendered));
90
- const summary = content || rendered;
80
+ const summary = asTrimmedString(nodeData.content) || stripHtmlToText(asTrimmedString(nodeData.rendered));
91
81
  const path = asTrimmedString(nodeData.path);
92
-
93
- if (!title && !summary && !path) return null;
94
- return {
95
- title: title || 'Context',
96
- summary,
97
- path,
98
- };
82
+ return title || summary || path ? { title: title || 'Context', summary, path } : null;
99
83
  }
100
84
 
101
85
  export function normalizePinnedContextDisplay(node: CanvasNodeState): PinnedContextDisplay {
@@ -106,13 +90,12 @@ export function normalizePinnedContextDisplay(node: CanvasNodeState): PinnedCont
106
90
  asTrimmedString(node.data.description) ||
107
91
  asTrimmedString(node.data.pageTitle) ||
108
92
  '';
109
- const path = asTrimmedString(node.data.path) || asTrimmedString(node.data.url);
110
93
  return {
111
94
  id: node.id,
112
95
  title,
113
96
  summary,
114
97
  kind: TYPE_LABELS[node.type] ?? node.type,
115
- path,
98
+ path: asTrimmedString(node.data.path) || asTrimmedString(node.data.url),
116
99
  };
117
100
  }
118
101
 
@@ -123,13 +106,6 @@ function formatTokens(n: number | null): string {
123
106
  return `${Math.round(n)}`;
124
107
  }
125
108
 
126
- function usageBarColor(utilization: number): string {
127
- if (utilization >= 0.95) return 'var(--c-danger)';
128
- if (utilization >= 0.85) return 'var(--c-warn)';
129
- if (utilization >= 0.7) return 'var(--c-warn)';
130
- return 'var(--c-accent)';
131
- }
132
-
133
109
  export function ContextNode({
134
110
  node,
135
111
  expanded = false,
@@ -141,41 +117,57 @@ export function ContextNode({
141
117
  }) {
142
118
  const cards = (node.data.cards as ContextCard[]) ?? [];
143
119
  const auxTabs = (node.data.auxTabs as Array<{ id: string; url: string; reason?: string }>) ?? [];
144
- const pinnedContext = pinnedNodes.map(normalizePinnedContextDisplay);
145
- const hasPinnedContext = pinnedContext.length > 0;
146
- const currentTokens = typeof node.data.currentTokens === 'number' ? node.data.currentTokens : null;
147
- const tokenLimit = typeof node.data.tokenLimit === 'number' ? node.data.tokenLimit : null;
148
- const utilization = typeof node.data.utilization === 'number' ? node.data.utilization : null;
149
- const messagesLength = typeof node.data.messagesLength === 'number' ? node.data.messagesLength : null;
150
- const percent = utilization !== null ? Math.max(0, Math.min(100, Math.round(utilization * 100))) : null;
151
- const barColor = usageBarColor(utilization ?? 0);
152
- const fallback =
153
- !hasPinnedContext && cards.length === 0 && auxTabs.length === 0 ? normalizeContextNodeFallback(node.data) : null;
154
-
155
- const openCard = async (card: ContextCard): Promise<void> => {
156
- const path = typeof card.path === 'string' ? card.path.trim() : '';
157
- if (!path) return;
158
- await openWorkbenchFile(path);
159
- };
160
-
120
+ const pinned = pinnedNodes.map(normalizePinnedContextDisplay);
121
+ const fallback = !pinned.length && !cards.length && !auxTabs.length ? normalizeContextNodeFallback(node.data) : null;
122
+ const current = typeof node.data.currentTokens === 'number' ? node.data.currentTokens : null;
123
+ const limit = typeof node.data.tokenLimit === 'number' ? node.data.tokenLimit : null;
124
+ const percent =
125
+ typeof node.data.utilization === 'number' ? Math.max(0, Math.min(100, Math.round(node.data.utilization * 100))) : 0;
126
+ const rows = pinned.length
127
+ ? pinned.map((item) => ({
128
+ ...item,
129
+ tags: item.kind,
130
+ state: 'Pinned',
131
+ openPath: item.path.startsWith('/') ? item.path : '',
132
+ }))
133
+ : cards.map((card) => {
134
+ const item = normalizeContextCardDisplay(card);
135
+ return {
136
+ ...item,
137
+ id: card.key ?? item.pathDisplay ?? item.title,
138
+ path: item.pathDisplay,
139
+ openPath: card.path ?? '',
140
+ state: item.status,
141
+ tags: [item.category, item.sourceKind, item.required ? 'Required' : ''].filter(Boolean).join(' · '),
142
+ };
143
+ });
161
144
  return (
162
- <div
163
- style={{
164
- display: 'flex',
165
- flexDirection: 'column',
166
- gap: expanded ? '10px' : '6px',
167
- fontSize: expanded ? '14px' : '12px',
168
- maxWidth: expanded ? '760px' : undefined,
169
- margin: expanded ? '0 auto' : undefined,
170
- width: expanded ? '100%' : undefined,
171
- padding: expanded ? '8px 0' : undefined,
172
- }}
173
- >
174
- {/* AX: focus the agent on this context node */}
175
- <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
145
+ <div class={`context-node${expanded ? ' context-node-expanded' : ''}`}>
146
+ <div class="context-node-toolbar">
147
+ {limit !== null && limit > 0 && (
148
+ <span class="context-usage">
149
+ <span
150
+ class="context-usage-track"
151
+ role="meter"
152
+ aria-label="Token usage"
153
+ aria-valuemin={0}
154
+ aria-valuemax={100}
155
+ aria-valuenow={percent}
156
+ >
157
+ <span
158
+ style={{
159
+ width: `${percent}%`,
160
+ background: percent >= 95 ? 'var(--c-danger)' : percent >= 70 ? 'var(--c-warn)' : 'var(--c-pin)',
161
+ }}
162
+ />
163
+ </span>
164
+ {percent}% · {formatTokens(current)} / {formatTokens(limit)} tokens
165
+ {typeof node.data.messagesLength === 'number' && <> · {node.data.messagesLength} messages</>}
166
+ </span>
167
+ )}
176
168
  <BarHint
177
169
  label="Point the agent at this node"
178
- body="Sets it as the agent's current AX focus, so the agent pulls it into context to work on next — a one-click alternative to pinning."
170
+ body="Sets it as the agent's current AX focus."
179
171
  align="end"
180
172
  fitWithin=".node-content"
181
173
  >
@@ -183,8 +175,8 @@ export function ContextNode({
183
175
  type="button"
184
176
  class="ax-node-action"
185
177
  style={axNodeActionButtonStyle}
186
- onClick={(e) => {
187
- e.stopPropagation();
178
+ onClick={(event) => {
179
+ event.stopPropagation();
188
180
  void runNodeAxInteraction(node, 'ax.focus.set', undefined, 'Focus set');
189
181
  }}
190
182
  >
@@ -192,356 +184,56 @@ export function ContextNode({
192
184
  </button>
193
185
  </BarHint>
194
186
  </div>
195
-
196
- {tokenLimit !== null && tokenLimit > 0 && (
197
- <div style={{ marginBottom: '8px' }}>
198
- <div
199
- style={{
200
- fontSize: '10px',
201
- fontWeight: 600,
202
- color: 'var(--c-muted)',
203
- textTransform: 'uppercase',
204
- letterSpacing: '0.04em',
205
- marginBottom: '6px',
206
- }}
207
- >
208
- Token Usage
209
- </div>
210
- <div
211
- style={{
212
- height: '6px',
213
- background: 'var(--c-surface-hover)',
214
- borderRadius: '3px',
215
- overflow: 'hidden',
216
- marginBottom: '4px',
217
- }}
218
- >
219
- <div
220
- style={{
221
- height: '100%',
222
- width: `${Math.min(100, (utilization ?? 0) * 100)}%`,
223
- background: barColor,
224
- borderRadius: '3px',
225
- transition: 'width 0.3s ease',
226
- }}
227
- />
228
- </div>
229
- <div style={{ fontSize: '11px', color: 'var(--c-muted)' }}>
230
- {percent}% — {formatTokens(currentTokens)} / {formatTokens(tokenLimit)} tokens
231
- {messagesLength !== null && <> · {messagesLength} messages</>}
232
- </div>
187
+ {rows.length > 0 && (
188
+ <div class="context-node-label">
189
+ {pinned.length ? 'Pinned context' : 'Context'} ({rows.length})
233
190
  </div>
234
191
  )}
235
-
236
- {hasPinnedContext && (
237
- <div>
238
- <div
239
- style={{
240
- fontSize: '10px',
241
- fontWeight: 600,
242
- color: 'var(--c-muted)',
243
- textTransform: 'uppercase',
244
- letterSpacing: '0.04em',
245
- marginBottom: '6px',
246
- }}
247
- >
248
- Pinned Context ({pinnedContext.length})
192
+ {rows.map((row) => (
193
+ <div class="context-row" key={row.id}>
194
+ <div class="context-row-title">
195
+ <strong>{row.title}</strong>
196
+ <span class={['Missing', 'Stale', 'Invalid'].includes(row.state) ? 'context-row-warning' : ''}>
197
+ {row.state === 'Loaded' ? '✓ ' : row.state === 'Pinned' ? '⊙ ' : '⚠ '}
198
+ {row.state}
199
+ </span>
249
200
  </div>
250
- {pinnedContext.map((display) => (
251
- <div
252
- key={display.id}
253
- style={{
254
- padding: '6px 8px',
255
- background: 'var(--c-surface-subtle)',
256
- borderRadius: '6px',
257
- marginBottom: '4px',
258
- borderLeft: '2px solid var(--c-accent)',
259
- }}
260
- >
261
- <div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>{display.title}</div>
262
- {display.summary && (
263
- <div
264
- style={{
265
- color: 'var(--c-muted)',
266
- fontSize: '10px',
267
- lineHeight: 1.45,
268
- marginBottom: '4px',
269
- whiteSpace: 'pre-wrap',
270
- }}
271
- >
272
- {display.summary}
273
- </div>
274
- )}
275
- <div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap', marginTop: '4px' }}>
276
- <span
277
- style={{
278
- fontSize: '9px',
279
- padding: '1px 4px',
280
- background: 'var(--c-surface-hover)',
281
- color: 'var(--c-text-soft)',
282
- borderRadius: '3px',
283
- display: 'inline-block',
284
- }}
285
- >
286
- {display.kind}
287
- </span>
288
- </div>
289
- {display.path && (
290
- <div style={{ marginTop: '6px' }}>
291
- <div
292
- style={{
293
- color: 'var(--c-dim)',
294
- fontSize: '10px',
295
- wordBreak: 'break-all',
296
- marginBottom: '6px',
297
- }}
298
- >
299
- {display.path}
300
- </div>
301
- {display.path.startsWith('/') && (
302
- <button
303
- type="button"
304
- onClick={() => void openWorkbenchFile(display.path)}
305
- style={{
306
- padding: '4px 8px',
307
- fontSize: '10px',
308
- background: 'color-mix(in srgb, var(--c-accent) 12%, transparent)',
309
- border: '1px solid color-mix(in srgb, var(--c-accent) 25%, transparent)',
310
- borderRadius: '4px',
311
- color: 'var(--c-text-soft)',
312
- cursor: 'pointer',
313
- }}
314
- >
315
- Open in canvas
316
- </button>
317
- )}
318
- </div>
319
- )}
320
- </div>
321
- ))}
322
- </div>
323
- )}
324
-
325
- {!hasPinnedContext && cards.length > 0 && (
326
- <div>
327
- <div
328
- style={{
329
- fontSize: '10px',
330
- fontWeight: 600,
331
- color: 'var(--c-muted)',
332
- textTransform: 'uppercase',
333
- letterSpacing: '0.04em',
334
- marginBottom: '6px',
335
- }}
336
- >
337
- Context ({cards.length})
338
- </div>
339
- {cards.map((card) => {
340
- const display = normalizeContextCardDisplay(card);
341
- return (
342
- <div
343
- key={card.key ?? display.pathDisplay ?? display.title}
344
- style={{
345
- padding: '6px 8px',
346
- background: 'var(--c-surface-subtle)',
347
- borderRadius: '6px',
348
- marginBottom: '4px',
349
- borderLeft: '2px solid var(--c-accent)',
350
- }}
351
- >
352
- <div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>{display.title}</div>
353
- <div
354
- style={{
355
- color: 'var(--c-muted)',
356
- fontSize: '10px',
357
- lineHeight: 1.45,
358
- marginBottom: '4px',
359
- }}
360
- >
361
- {display.summary}
362
- </div>
363
- {display.pathDisplay && (
364
- <div style={{ color: 'var(--c-dim)', fontSize: '10px', wordBreak: 'break-all' }}>
365
- {display.pathDisplay}
366
- </div>
367
- )}
368
- <div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap', marginTop: '4px' }}>
369
- <span
370
- style={{
371
- fontSize: '9px',
372
- padding: '1px 4px',
373
- background: 'color-mix(in srgb, var(--c-accent) 10%, transparent)',
374
- color: 'var(--c-accent)',
375
- borderRadius: '3px',
376
- display: 'inline-block',
377
- }}
378
- >
379
- {display.status}
380
- </span>
381
- {display.category && (
382
- <span
383
- style={{
384
- fontSize: '9px',
385
- padding: '1px 4px',
386
- background: 'var(--c-surface-hover)',
387
- color: 'var(--c-text-soft)',
388
- borderRadius: '3px',
389
- display: 'inline-block',
390
- }}
391
- >
392
- {display.category}
393
- </span>
394
- )}
395
- <span
396
- style={{
397
- fontSize: '9px',
398
- padding: '1px 4px',
399
- background: 'var(--c-surface-hover)',
400
- color: 'var(--c-text-soft)',
401
- borderRadius: '3px',
402
- display: 'inline-block',
403
- }}
404
- >
405
- {display.sourceKind}
406
- </span>
407
- {display.required && (
408
- <span
409
- style={{
410
- fontSize: '9px',
411
- padding: '1px 4px',
412
- background: 'color-mix(in srgb, var(--c-accent) 12%, transparent)',
413
- color: 'var(--c-accent)',
414
- borderRadius: '3px',
415
- display: 'inline-block',
416
- }}
417
- >
418
- Required
419
- </span>
420
- )}
421
- </div>
422
- {card.path && (
423
- <div style={{ marginTop: '6px' }}>
424
- <button
425
- type="button"
426
- onClick={() => void openCard(card)}
427
- style={{
428
- padding: '4px 8px',
429
- fontSize: '10px',
430
- background: 'color-mix(in srgb, var(--c-accent) 12%, transparent)',
431
- border: '1px solid color-mix(in srgb, var(--c-accent) 25%, transparent)',
432
- borderRadius: '4px',
433
- color: 'var(--c-text-soft)',
434
- cursor: 'pointer',
435
- }}
436
- >
437
- Open in canvas
438
- </button>
439
- </div>
440
- )}
441
- </div>
442
- );
443
- })}
201
+ <div class="context-row-meta">{row.tags}</div>
202
+ {row.summary && <div class="context-row-summary">{row.summary}</div>}
203
+ {row.path && <div class="context-row-meta">{row.path}</div>}
204
+ {row.openPath && (
205
+ <button type="button" class="context-row-open" onClick={() => void openWorkbenchFile(row.openPath)}>
206
+ Open in canvas
207
+ </button>
208
+ )}
444
209
  </div>
445
- )}
446
-
447
- {!hasPinnedContext && auxTabs.length > 0 && (
448
- <div>
449
- <div
450
- style={{
451
- fontSize: '10px',
452
- fontWeight: 600,
453
- color: 'var(--c-muted)',
454
- textTransform: 'uppercase',
455
- letterSpacing: '0.04em',
456
- marginBottom: '6px',
457
- marginTop: cards.length > 0 ? '8px' : 0,
458
- }}
459
- >
460
- References ({auxTabs.length})
461
- </div>
210
+ ))}
211
+ {!pinned.length && auxTabs.length > 0 && (
212
+ <>
213
+ <div class="context-node-label">References ({auxTabs.length})</div>
462
214
  {auxTabs.map((tab) => (
463
- <a
464
- key={tab.id}
465
- href={tab.url}
466
- target="_blank"
467
- rel="noopener noreferrer"
468
- style={{
469
- display: 'block',
470
- padding: '4px 8px',
471
- color: 'var(--c-accent)',
472
- fontSize: '11px',
473
- textDecoration: 'none',
474
- borderRadius: '4px',
475
- marginBottom: '2px',
476
- wordBreak: 'break-all',
477
- }}
478
- >
215
+ <a class="context-row" key={tab.id} href={tab.url} target="_blank" rel="noopener noreferrer">
479
216
  {tab.url}
480
- {tab.reason && (
481
- <span style={{ color: 'var(--c-dim)', fontSize: '10px', marginLeft: '6px' }}>({tab.reason})</span>
482
- )}
217
+ {tab.reason && <span class="context-row-meta"> ({tab.reason})</span>}
483
218
  </a>
484
219
  ))}
485
- </div>
220
+ </>
486
221
  )}
487
-
488
222
  {fallback && (
489
- <div
490
- style={{
491
- padding: '8px 10px',
492
- background: 'var(--c-surface-subtle)',
493
- borderRadius: '6px',
494
- borderLeft: '2px solid var(--c-accent)',
495
- }}
496
- >
497
- <div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '4px' }}>{fallback.title}</div>
498
- {fallback.summary && (
499
- <div
500
- style={{
501
- color: 'var(--c-text-soft)',
502
- fontSize: '11px',
503
- lineHeight: 1.5,
504
- whiteSpace: 'pre-wrap',
505
- }}
506
- >
507
- {fallback.summary}
508
- </div>
509
- )}
223
+ <div class="context-row">
224
+ <strong>{fallback.title}</strong>
225
+ {fallback.summary && <div class="context-row-summary">{fallback.summary}</div>}
510
226
  {fallback.path && (
511
- <div style={{ marginTop: '6px' }}>
512
- <div
513
- style={{
514
- color: 'var(--c-dim)',
515
- fontSize: '10px',
516
- wordBreak: 'break-all',
517
- marginBottom: '6px',
518
- }}
519
- >
520
- {fallback.path}
521
- </div>
522
- <button
523
- type="button"
524
- onClick={() => void openWorkbenchFile(fallback.path)}
525
- style={{
526
- padding: '4px 8px',
527
- fontSize: '10px',
528
- background: 'color-mix(in srgb, var(--c-accent) 12%, transparent)',
529
- border: '1px solid color-mix(in srgb, var(--c-accent) 25%, transparent)',
530
- borderRadius: '4px',
531
- color: 'var(--c-text-soft)',
532
- cursor: 'pointer',
533
- }}
534
- >
227
+ <>
228
+ <div class="context-row-meta">{fallback.path}</div>
229
+ <button type="button" class="context-row-open" onClick={() => void openWorkbenchFile(fallback.path)}>
535
230
  Open in canvas
536
231
  </button>
537
- </div>
232
+ </>
538
233
  )}
539
234
  </div>
540
235
  )}
541
-
542
- {!hasPinnedContext && !fallback && cards.length === 0 && auxTabs.length === 0 && (
543
- <div style={{ color: 'var(--c-dim)', fontStyle: 'italic' }}>No context loaded</div>
544
- )}
236
+ {!rows.length && !auxTabs.length && !fallback && <div class="context-row-meta">No context loaded</div>}
545
237
  </div>
546
238
  );
547
239
  }