yaml-flow 5.4.2 → 7.0.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 (252) hide show
  1. package/board-live-cards-cli.js +6 -6
  2. package/browser/asset-integrity.json +10 -0
  3. package/browser/board-livecards-client.js +2 -0
  4. package/browser/board-livecards-client.js.map +1 -0
  5. package/browser/board-livecards-localstorage.js +10 -0
  6. package/browser/board-livecards-localstorage.js.map +1 -0
  7. package/browser/board-livegraph-engine.js +2 -1676
  8. package/browser/board-livegraph-engine.js.map +1 -1
  9. package/browser/card-compute.js +28 -28
  10. package/browser/compute-jsonata.js +5 -0
  11. package/browser/compute-jsonata.js.map +1 -0
  12. package/browser/live-cards.js +561 -129
  13. package/browser/live-cards.schema.json +418 -132
  14. package/card-store.js +37 -0
  15. package/dist/batch/index.cjs +1 -108
  16. package/dist/batch/index.cjs.map +1 -1
  17. package/dist/batch/index.js +1 -106
  18. package/dist/batch/index.js.map +1 -1
  19. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  20. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  21. package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
  22. package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
  23. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  24. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  25. package/dist/board-livegraph-runtime/index.d.cts +12 -11
  26. package/dist/board-livegraph-runtime/index.d.ts +12 -11
  27. package/dist/board-livegraph-runtime/index.js +2 -1662
  28. package/dist/board-livegraph-runtime/index.js.map +1 -1
  29. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
  30. package/dist/card-compute/index.cjs +9 -7159
  31. package/dist/card-compute/index.cjs.map +1 -1
  32. package/dist/card-compute/index.d.cts +27 -1
  33. package/dist/card-compute/index.d.ts +27 -1
  34. package/dist/card-compute/index.js +9 -7145
  35. package/dist/card-compute/index.js.map +1 -1
  36. package/dist/card-compute/jsonata-sync.cjs +7623 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
  38. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  39. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
  40. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
  41. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
  42. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  43. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  44. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  45. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  46. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  47. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  48. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  49. package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
  50. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  51. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  52. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  53. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  54. package/dist/cli/node/artifacts-store-cli.js +11 -0
  55. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  56. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  57. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  58. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  59. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  60. package/dist/cli/node/board-live-cards-cli.js +15 -0
  61. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  62. package/dist/cli/node/card-store-cli.cjs +8 -0
  63. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  64. package/dist/cli/node/card-store-cli.d.cts +15 -0
  65. package/dist/cli/node/card-store-cli.d.ts +15 -0
  66. package/dist/cli/node/card-store-cli.js +8 -0
  67. package/dist/cli/node/card-store-cli.js.map +1 -0
  68. package/dist/cli/node/execution-adapter.cjs +3 -0
  69. package/dist/cli/node/execution-adapter.cjs.map +1 -0
  70. package/dist/cli/node/execution-adapter.d.cts +174 -0
  71. package/dist/cli/node/execution-adapter.d.ts +174 -0
  72. package/dist/cli/node/execution-adapter.js +3 -0
  73. package/dist/cli/node/execution-adapter.js.map +1 -0
  74. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  75. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  76. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  77. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  78. package/dist/cli/node/fs-board-adapter.js +14 -0
  79. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  80. package/dist/cli/node/jsonata-sync.cjs +7623 -0
  81. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  82. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  83. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  84. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  85. package/dist/cli/node/source-cli-task-executor.js +11 -0
  86. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  87. package/dist/config/index.cjs +1 -79
  88. package/dist/config/index.cjs.map +1 -1
  89. package/dist/config/index.js +1 -76
  90. package/dist/config/index.js.map +1 -1
  91. package/dist/continuous-event-graph/index.cjs +2 -2129
  92. package/dist/continuous-event-graph/index.cjs.map +1 -1
  93. package/dist/continuous-event-graph/index.d.cts +81 -5
  94. package/dist/continuous-event-graph/index.d.ts +81 -5
  95. package/dist/continuous-event-graph/index.js +2 -2088
  96. package/dist/continuous-event-graph/index.js.map +1 -1
  97. package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
  98. package/dist/event-graph/index.cjs +22 -8292
  99. package/dist/event-graph/index.cjs.map +1 -1
  100. package/dist/event-graph/index.js +22 -8237
  101. package/dist/event-graph/index.js.map +1 -1
  102. package/dist/execution-refs.cjs +3 -0
  103. package/dist/execution-refs.cjs.map +1 -0
  104. package/dist/execution-refs.d.cts +260 -0
  105. package/dist/execution-refs.d.ts +260 -0
  106. package/dist/execution-refs.js +3 -0
  107. package/dist/execution-refs.js.map +1 -0
  108. package/dist/index.cjs +29 -13221
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +2 -4
  111. package/dist/index.d.ts +2 -4
  112. package/dist/index.js +29 -13112
  113. package/dist/index.js.map +1 -1
  114. package/dist/inference/index.cjs +5 -617
  115. package/dist/inference/index.cjs.map +1 -1
  116. package/dist/inference/index.js +5 -610
  117. package/dist/inference/index.js.map +1 -1
  118. package/dist/jsonata-sync.cjs +7623 -0
  119. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  120. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  121. package/dist/server-runtime/index.cjs +9 -0
  122. package/dist/server-runtime/index.cjs.map +1 -0
  123. package/dist/server-runtime/index.d.cts +31 -0
  124. package/dist/server-runtime/index.d.ts +31 -0
  125. package/dist/server-runtime/index.js +9 -0
  126. package/dist/server-runtime/index.js.map +1 -0
  127. package/dist/server-runtime/jsonata-sync.cjs +7623 -0
  128. package/dist/step-machine/index.cjs +11 -7129
  129. package/dist/step-machine/index.cjs.map +1 -1
  130. package/dist/step-machine/index.js +11 -7113
  131. package/dist/step-machine/index.js.map +1 -1
  132. package/dist/step-machine-public/index.cjs +2 -0
  133. package/dist/step-machine-public/index.cjs.map +1 -0
  134. package/dist/step-machine-public/index.d.cts +159 -0
  135. package/dist/step-machine-public/index.d.ts +159 -0
  136. package/dist/step-machine-public/index.js +2 -0
  137. package/dist/step-machine-public/index.js.map +1 -0
  138. package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
  139. package/dist/storage-refs.cjs +10 -0
  140. package/dist/storage-refs.cjs.map +1 -0
  141. package/dist/storage-refs.d.cts +93 -0
  142. package/dist/storage-refs.d.ts +93 -0
  143. package/dist/storage-refs.js +10 -0
  144. package/dist/storage-refs.js.map +1 -0
  145. package/dist/stores/file.cjs +1 -114
  146. package/dist/stores/file.cjs.map +1 -1
  147. package/dist/stores/file.js +1 -112
  148. package/dist/stores/file.js.map +1 -1
  149. package/dist/stores/index.cjs +1 -231
  150. package/dist/stores/index.cjs.map +1 -1
  151. package/dist/stores/index.js +1 -227
  152. package/dist/stores/index.js.map +1 -1
  153. package/dist/stores/localStorage.cjs +1 -76
  154. package/dist/stores/localStorage.cjs.map +1 -1
  155. package/dist/stores/localStorage.js +1 -74
  156. package/dist/stores/localStorage.js.map +1 -1
  157. package/dist/stores/memory.cjs +1 -47
  158. package/dist/stores/memory.cjs.map +1 -1
  159. package/dist/stores/memory.js +1 -45
  160. package/dist/stores/memory.js.map +1 -1
  161. package/dist/types-B1ZRa4aI.d.ts +147 -0
  162. package/dist/types-BxEFcVK9.d.cts +147 -0
  163. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +291 -0
  164. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  165. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  166. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
  167. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  168. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
  169. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
  170. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
  171. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
  172. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +366 -0
  173. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  174. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  175. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
  176. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
  177. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
  178. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
  179. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
  180. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
  181. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
  182. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +9 -8
  183. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
  184. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
  185. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +107 -0
  186. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
  187. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
  188. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
  189. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  190. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  191. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  192. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  193. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
  194. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  195. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
  196. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  197. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  198. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
  199. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
  200. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
  201. package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
  202. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
  203. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
  204. package/examples/example-board/agent-instructions.md +11 -5
  205. package/examples/example-board/cards/_index.json +47 -0
  206. package/examples/example-board/cards/card-market-prices.json +33 -9
  207. package/examples/example-board/cards/card-my-identity.json +30 -6
  208. package/examples/example-board/cards/card-portfolio-action.json +24 -6
  209. package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
  210. package/examples/example-board/cards/card-portfolio-risks.json +24 -6
  211. package/examples/example-board/cards/card-portfolio-value.json +38 -10
  212. package/examples/example-board/cards/card-portfolio.json +57 -13
  213. package/examples/example-board/cards/card-rebalance-impact.json +22 -6
  214. package/examples/example-board/cards/card-rebalance-sim.json +66 -15
  215. package/examples/example-board/demo-chat-handler.js +14 -4
  216. package/examples/example-board/demo-server-config.json +1 -0
  217. package/examples/example-board/demo-server.js +366 -68
  218. package/examples/example-board/demo-shell-localstorage.html +774 -0
  219. package/examples/example-board/demo-shell-with-server.html +20 -37
  220. package/examples/example-board/demo-shell.html +5 -4
  221. package/examples/example-board/demo-task-executor.js +273 -275
  222. package/examples/index.html +0 -14
  223. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  224. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  225. package/package.json +46 -8
  226. package/schema/live-cards.schema.json +418 -132
  227. package/step-machine-cli.js +43 -310
  228. package/board-livecards-server-runtime.js +0 -1574
  229. package/browser/board-livecards-runtime-client.js +0 -263
  230. package/dist/cli/board-live-cards-cli.cjs +0 -10650
  231. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  232. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  233. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  234. package/dist/cli/board-live-cards-cli.js +0 -10598
  235. package/dist/cli/board-live-cards-cli.js.map +0 -1
  236. package/dist/journal-9HEgs7dU.d.ts +0 -28
  237. package/dist/journal-B-JCfQnh.d.cts +0 -28
  238. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  239. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  240. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  241. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  242. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  243. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  244. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  245. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  246. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  247. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  248. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  249. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
  250. package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
  251. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
  252. package/examples/example-board/demo-shell-browser.html +0 -674
@@ -1,17 +1,19 @@
1
1
  // live-cards.js — LiveCards v3: Node-based Board/Canvas engine
2
2
  //
3
3
  // Schema: Each node has { id } required; all else optional.
4
- // id, meta, card_data, requires, provides, source_defs, compute, view
5
- // Nodes with view render as cards; nodes with source_defs but no view render as source pills in canvas.
6
- // compute[] ordered array of { bindTo, expr } JSONata steps writes to node.computed_values (ephemeral)
7
- // source_defs[] open objects: only bindTo + outputFile matter to the engine; all other fields are
8
- // passed verbatim to the board's task-executor (--in JSON). Users define their own
9
- // shape (kind, url, mailbox, channel, model, ...) per executor.
10
- // requires[] — upstream node IDs; engine subscribes automatically
4
+ // id, meta, card_data, requires, provides, view
5
+ // Nodes with view render as cards; nodes with no view but with source_defs declared on the
6
+ // underlying card definition render as source pills in canvas mode (source_defs are runtime-only;
7
+ // they are not interpreted here).
8
+ // requires[] upstream provider tokens; engine subscribes automatically
11
9
  // provides[] — [{ bindTo, src }] explicit downstream token bindings
10
+ // computed_values — derived values produced by the runtime; rendered as-is, never recomputed here
11
+ //
12
+ // Rendering contract: this module renders derived state only. View bind paths resolve to one of
13
+ // card_data | requires | computed_values | runtime_state. Raw fetched-source payloads stay in the
14
+ // runtime and never reach the Board.
12
15
  //
13
16
  // Uses Bootstrap 5 for layout/forms, optional Chart.js for charts.
14
- // Uses CardCompute (card-compute.js) for declarative compute expressions.
15
17
  //
16
18
  // API:
17
19
  // const engine = LiveCard.init({ resolve, onPatch, onPatchState, onRefresh, onAction, getChatMessages, markdown, sanitize, chartLib });
@@ -24,8 +26,15 @@
24
26
  // engine.appendChatMessage(nodeId, role, text)
25
27
  // engine.registerRenderer(name, fn)
26
28
  //
27
- // const board = LiveCard.Board(engine, el, { nodes, positions?, mode, canvas })
28
- // board.setMode('board'|'canvas'), board.autoLayout(), board.add(node), board.remove(id)
29
+ // Reactive board (preferred): state in, view out. No destructive re-renders.
30
+ // const board = LiveCard.Board(engine, el, { initialState, getNodeIds, selectNode, mode?, canvas? });
31
+ // board.setState(nextState) — diff vs prev; per-node updates only
32
+ // board.destroy()
33
+ //
34
+ // Imperative core (advanced): direct node-list manipulation.
35
+ // const core = LiveCard.BoardCore(engine, el, { nodes, positions?, mode, canvas });
36
+ // core.add(node), core.remove(id), core.reorder(ids), core.updateNode(id, model)
37
+ // core.setMode('board'|'canvas'), core.setDevMode(flag), core.autoLayout(), core.clear(), core.destroy()
29
38
 
30
39
  // eslint-disable-next-line no-unused-vars
31
40
  var LiveCard = (function () {
@@ -98,6 +107,20 @@ var LiveCard = (function () {
98
107
  .lc-gandalf-caret:hover { opacity:1; }
99
108
  .lc-gandalf-card.lc-collapsed .lc-gandalf-caret { transform:rotate(-90deg); }
100
109
  .lc-gandalf-card.lc-collapsed .card-body { display:none !important; }
110
+ .lc-token-row { display:flex; flex-wrap:wrap; gap:0.35rem; padding:0.2rem 0.5rem; background:transparent; align-items:center; justify-content:center; min-height:0; }
111
+ .lc-token-row-requires { border-bottom:none; padding-bottom:0.1rem; }
112
+ .lc-token-row-provides { border-top:none; padding-top:0.1rem; }
113
+ .lc-token-gem { display:inline-block; width:10px; height:10px; border-radius:50%; cursor:default; transition:transform .15s, box-shadow .15s; position:relative; }
114
+ .lc-token-gem:hover { transform:scale(1.5); box-shadow:0 0 4px rgba(0,0,0,0.3); z-index:5; }
115
+ .lc-token-gem-requires { background:var(--bs-secondary,#6c757d); border:1.5px solid var(--bs-secondary,#6c757d); }
116
+ .lc-token-gem-requires.lc-token-available { background:var(--bs-success,#198754); border-color:var(--bs-success,#198754); }
117
+ .lc-token-gem-provides { background:var(--bs-secondary,#6c757d); border:1.5px solid var(--bs-secondary,#6c757d); }
118
+ .lc-token-gem-provides.lc-token-available { background:var(--bs-success,#198754); border-color:var(--bs-success,#198754); }
119
+ .lc-running { animation:lc-running-pulse 2s ease-in-out infinite; position:relative; }
120
+ .lc-running::before { content:''; position:absolute; inset:-2px; border-radius:inherit; background:linear-gradient(90deg,transparent,rgba(13,110,253,.45),rgba(102,16,242,.4),rgba(13,110,253,.45),transparent); background-size:300% 100%; animation:lc-running-shimmer 2s linear infinite; z-index:-1; pointer-events:none; }
121
+ @keyframes lc-running-pulse { 0%,100%{ box-shadow:0 0 4px rgba(13,110,253,.15); } 50%{ box-shadow:0 0 14px 3px rgba(13,110,253,.35); } }
122
+ @keyframes lc-running-shimmer { 0%{ background-position:100% 0; } 100%{ background-position:-100% 0; } }
123
+ .lc-running .card-header { border-bottom-color:rgba(13,110,253,.35); }
101
124
  @media (max-width:576px) {
102
125
  .lc-metric-value { font-size:1.5rem; }
103
126
  .lc-chart-wrap { min-height:150px; }
@@ -216,6 +239,30 @@ var LiveCard = (function () {
216
239
  const _formState = {}; // stateKey → { baseValues, journal }
217
240
  const _notesState = {}; // stateKey → { baseContent, journal|null }
218
241
  const _todoState = {}; // stateKey → { currentState, pending } for todo dirty tracking
242
+
243
+ /**
244
+ * Overlay a "Saving…" spinner over `el` while a patch is in-flight.
245
+ * The overlay is removed automatically on the next SSE re-render because
246
+ * every editable renderer does `el.innerHTML = …` on refresh.
247
+ */
248
+ function _showSavingOverlay(el) {
249
+ // Ensure the container is a positioned ancestor so the overlay can fill it.
250
+ if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
251
+ const overlay = document.createElement('div');
252
+ overlay.className = 'lc-saving-overlay';
253
+ overlay.setAttribute('aria-live', 'polite');
254
+ overlay.style.cssText = [
255
+ 'position:absolute', 'inset:0',
256
+ 'background:rgba(255,255,255,0.78)',
257
+ 'display:flex', 'align-items:center', 'justify-content:center',
258
+ 'gap:0.5rem', 'z-index:20', 'border-radius:inherit',
259
+ 'pointer-events:all', // blocks all clicks on underlying inputs
260
+ ].join(';');
261
+ overlay.innerHTML =
262
+ '<span class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></span>' +
263
+ '<span class="text-primary fw-medium small">Saving…</span>';
264
+ el.appendChild(overlay);
265
+ }
219
266
  const _renderers = {}; // kind → fn
220
267
  const _nodeEls = {}; // nodeId → { container, resultEl, uid }
221
268
  const _chatModal = {
@@ -261,11 +308,6 @@ var LiveCard = (function () {
261
308
  return _cleanup[id];
262
309
  }
263
310
 
264
- function _runCompute() {
265
- // Runtime payload is authoritative; UI never recomputes derived values.
266
- return Promise.resolve();
267
- }
268
-
269
311
  function _ensureChatModal() {
270
312
  if (_chatModal.backdrop) return;
271
313
 
@@ -753,11 +795,9 @@ var LiveCard = (function () {
753
795
  const ns = {
754
796
  card: node && node.card ? node.card : {},
755
797
  card_data: node && node.card_data ? node.card_data : {},
756
- fetched_sources: node && node.fetched_sources ? node.fetched_sources : {},
757
798
  requires: node && node.requires ? node.requires : {},
758
799
  computed_values: node && node.computed_values ? node.computed_values : {},
759
800
  runtime_state: node && node.runtime_state ? node.runtime_state : {},
760
- data_objects: node && node.data_objects ? node.data_objects : {},
761
801
  };
762
802
 
763
803
  if (!Object.prototype.hasOwnProperty.call(ns, root)) return undefined;
@@ -781,7 +821,29 @@ var LiveCard = (function () {
781
821
  const requires = (node && node.card && Array.isArray(node.card.requires)) ? node.card.requires : [];
782
822
  if (!requires.length) return;
783
823
  const cleanup = _getCleanup(node.id);
784
- cleanup.unsubs = requires.map(upId => subscribe(upId, () => {
824
+
825
+ // Resolve required tokens to upstream node IDs via provides declarations.
826
+ // Build a token→nodeId map from all nodes the engine knows about.
827
+ const tokenMap = {};
828
+ const allNodeIds = Object.keys(_subs).concat(Object.keys(_nodeEls));
829
+ allNodeIds.forEach(function(nid) {
830
+ const n = cfg.resolve(nid);
831
+ if (!n || !n.card) return;
832
+ var provides = (Array.isArray(n.card.provides) && n.card.provides.length)
833
+ ? n.card.provides.map(function(p) { return typeof p === 'string' ? p : (p.bindTo || p); })
834
+ : [n.id];
835
+ provides.forEach(function(tok) { tokenMap[tok] = n.id; });
836
+ });
837
+
838
+ // Subscribe to each upstream provider node (deduplicated)
839
+ const seen = {};
840
+ const upIds = [];
841
+ requires.forEach(function(token) {
842
+ var srcId = tokenMap[token] || token; // fallback: treat token as nodeId
843
+ if (!seen[srcId]) { seen[srcId] = true; upIds.push(srcId); }
844
+ });
845
+
846
+ cleanup.unsubs = upIds.map(upId => subscribe(upId, () => {
785
847
  const info = _nodeEls[node.id];
786
848
  if (!info || !info.resultEl) return;
787
849
  const updated = cfg.resolve(node.id);
@@ -1145,6 +1207,7 @@ var LiveCard = (function () {
1145
1207
  const nextValues = getEffectiveValues();
1146
1208
  cfg.onPatchState(node.id, { fieldValues: nextValues });
1147
1209
  btn.textContent = 'Saving...';
1210
+ _showSavingOverlay(el);
1148
1211
  }, { signal });
1149
1212
 
1150
1213
  discardBtn.addEventListener('click', () => {
@@ -1223,6 +1286,7 @@ var LiveCard = (function () {
1223
1286
  const nextValue = textarea.value;
1224
1287
  cfg.onPatchState(node.id, { notes: nextValue });
1225
1288
  saveBtn.textContent = 'Saving...';
1289
+ _showSavingOverlay(el);
1226
1290
  }, { signal });
1227
1291
 
1228
1292
  discardBtn.addEventListener('click', () => {
@@ -1307,6 +1371,7 @@ var LiveCard = (function () {
1307
1371
  cfg.onPatchState(node.id, { fieldValues: rows });
1308
1372
  const saveBtn = el.querySelector('.lc-et-save');
1309
1373
  if (saveBtn) saveBtn.textContent = 'Saving...';
1374
+ _showSavingOverlay(el);
1310
1375
  }
1311
1376
 
1312
1377
  function commitDiscard() {
@@ -1925,11 +1990,11 @@ var LiveCard = (function () {
1925
1990
  //
1926
1991
  // Usage:
1927
1992
  // { "kind": "ref",
1928
- // "data": { "bind": "fetched_sources.rebalance.proposed_trades",
1993
+ // "data": { "bind": "computed_values.proposed_trades",
1929
1994
  // "viewBind": "card_data.display_mode",
1930
1995
  // "fallbackKind": "table" } }
1931
1996
  //
1932
- // viewBind can point to any namespace: card_data, fetched_sources, requires, computed_values.
1997
+ // viewBind can point to any namespace: card_data, requires, computed_values, runtime_state.
1933
1998
  // If the resolved view object contains a "bind" sub-path, that overrides data.bind.
1934
1999
  const _REF_KIND_WHITELIST = new Set([
1935
2000
  'table','editable-table','chart','metric','list','badge',
@@ -2144,7 +2209,7 @@ var LiveCard = (function () {
2144
2209
  if (node.card_data && node.card_data.status === 'error' && node.card_data.error) {
2145
2210
  resultEl.innerHTML = `<div class="text-danger small fw-semibold">Refresh failed</div><pre class="text-muted small mt-1" style="white-space:pre-wrap">${_esc(node.card_data.error)}</pre>`;
2146
2211
  } else {
2147
- _runCompute(node).then(function () { _renderElements(node, resultEl); });
2212
+ _renderElements(node, resultEl);
2148
2213
  }
2149
2214
 
2150
2215
  // ---- Wire refresh ----
@@ -2239,7 +2304,7 @@ var LiveCard = (function () {
2239
2304
  if (node.card_data.status === 'error' && node.card_data.error) {
2240
2305
  info.resultEl.innerHTML = `<div class="text-danger small fw-semibold">Refresh failed</div><pre class="text-muted small mt-1" style="white-space:pre-wrap">${_esc(node.card_data.error)}</pre>`;
2241
2306
  } else {
2242
- _runCompute(node).then(function () { _renderElements(node, info.resultEl); });
2307
+ _renderElements(node, info.resultEl);
2243
2308
  }
2244
2309
  }
2245
2310
 
@@ -2318,10 +2383,11 @@ var LiveCard = (function () {
2318
2383
  }
2319
2384
 
2320
2385
  // ===========================================================================
2321
- // Board — grid (board) and DAG (canvas) modes
2386
+ // BoardCoreimperative grid (board) and DAG (canvas) modes.
2387
+ // Most callers should use Board (reactive wrapper) instead.
2322
2388
  // ===========================================================================
2323
2389
 
2324
- function Board(engine, containerEl, opts) {
2390
+ function BoardCore(engine, containerEl, opts) {
2325
2391
  opts = opts || {};
2326
2392
  const mode = { current: opts.mode || 'board' };
2327
2393
  const devMode = { current: opts.devMode || false };
@@ -2351,6 +2417,17 @@ var LiveCard = (function () {
2351
2417
  };
2352
2418
  const ac = new AbortController();
2353
2419
  const signal = ac.signal;
2420
+ const _edges = []; // LeaderLine instances for canvas edges
2421
+
2422
+ // Edge style config (from canvas opts)
2423
+ const edgeOpts = co.edgeStyle || {};
2424
+ const edgeCfg = {
2425
+ color: edgeOpts.color || 'rgba(108, 117, 125, 0.6)',
2426
+ size: edgeOpts.size || 2,
2427
+ dash: edgeOpts.dash !== false,
2428
+ animation: edgeOpts.animation !== false,
2429
+ endPlug: edgeOpts.endPlug || 'arrow1',
2430
+ };
2354
2431
 
2355
2432
  // DOM containers
2356
2433
  const root = document.createElement('div');
@@ -2362,20 +2439,18 @@ var LiveCard = (function () {
2362
2439
 
2363
2440
  const canvasEl = document.createElement('div');
2364
2441
  canvasEl.className = 'lc-canvas';
2365
- const canvasHeight = co.height || '600px';
2366
- const canvasOverflow = co.overflow || 'auto';
2367
- canvasEl.style.cssText = 'position:relative;overflow:' + canvasOverflow + ';width:100%;height:' + canvasHeight + ';';
2442
+ canvasEl.style.cssText = 'position:relative;overflow:auto;width:100%;';
2368
2443
  const canvasInner = document.createElement('div');
2369
2444
  canvasInner.className = 'lc-canvas-inner';
2370
- canvasInner.style.cssText = 'position:absolute;top:0;left:0;transform-origin:0 0;';
2445
+ canvasInner.style.cssText = 'position:relative;transform-origin:0 0;min-width:100%;min-height:100%;';
2371
2446
  canvasEl.appendChild(canvasInner);
2372
2447
 
2373
2448
  // SVG overlay for edges
2374
2449
  const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2375
2450
  svgEl.setAttribute('class', 'lc-canvas-edges');
2376
- svgEl.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible;';
2451
+ svgEl.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden;z-index:0;';
2377
2452
  const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
2378
- defs.innerHTML = '<marker id="lc-arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="var(--bs-secondary,#6c757d)"/></marker>';
2453
+ defs.innerHTML = '<marker id="lc-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 1 L 8 5 L 0 9 z" fill="rgba(108,117,125,0.55)"/></marker>';
2379
2454
  svgEl.appendChild(defs);
2380
2455
  canvasInner.appendChild(svgEl);
2381
2456
 
@@ -2384,10 +2459,16 @@ var LiveCard = (function () {
2384
2459
  const s = document.createElement('style');
2385
2460
  s.id = 'lc-board-css';
2386
2461
  s.textContent = `
2387
- .lc-canvas-card { position:absolute; min-width:${cvs.minWidth}px; max-width:${cvs.maxWidth}px; cursor:grab; user-select:none; z-index:1; }
2462
+ .lc-canvas-card { position:absolute; min-width:${cvs.minWidth}px; cursor:grab; user-select:none; z-index:1; }
2388
2463
  .lc-canvas-card.lc-dragging { cursor:grabbing; z-index:10; box-shadow:0 8px 24px rgba(0,0,0,0.18)!important; }
2389
- .lc-canvas-card .card-body { max-height:${cvs.cardMaxH}px; overflow:auto; }
2390
- .lc-canvas-edges line { stroke:var(--bs-secondary,#6c757d); stroke-width:1.5; }
2464
+ .lc-canvas-card .card-body { overflow:hidden; }
2465
+ .lc-canvas-card.lc-resizing { cursor:nwse-resize; z-index:10; }
2466
+ .lc-resize-handle { position:absolute; bottom:0; right:0; width:14px; height:14px; cursor:nwse-resize; z-index:2; opacity:0.4; transition:opacity .15s; }
2467
+ .lc-resize-handle:hover { opacity:1; }
2468
+ .lc-resize-handle::after { content:''; position:absolute; bottom:3px; right:3px; width:8px; height:8px; border-right:2px solid var(--bs-secondary,#6c757d); border-bottom:2px solid var(--bs-secondary,#6c757d); }
2469
+ .lc-canvas-edges path.lc-edge-path { stroke:rgba(100,140,200,0.5); stroke-width:2; fill:none; stroke-linecap:round; }
2470
+ .lc-canvas-edges line { stroke:rgba(100,140,200,0.4); stroke-width:2; }
2471
+ @keyframes lc-edge-flow { to { stroke-dashoffset:-10; } }
2391
2472
  .lc-source-node { position:absolute; cursor:grab; user-select:none; z-index:1; }
2392
2473
  .lc-source-node.lc-dragging { cursor:grabbing; z-index:10; }
2393
2474
  `;
@@ -2422,6 +2503,51 @@ var LiveCard = (function () {
2422
2503
  return (node && node.card && Array.isArray(node.card.requires)) ? node.card.requires : [];
2423
2504
  }
2424
2505
 
2506
+ /**
2507
+ * Returns tokens this node provides.
2508
+ * Explicit: card.provides[].bindTo
2509
+ * Implicit default: the node's own id (if no provides declared)
2510
+ */
2511
+ function _getProvides(node) {
2512
+ if (!node || !node.card) return [node ? node.id : ''];
2513
+ if (Array.isArray(node.card.provides) && node.card.provides.length > 0) {
2514
+ return node.card.provides.map(function(p) { return (typeof p === 'string') ? p : (p.bindTo || p); });
2515
+ }
2516
+ // Default: node provides a token equal to its own id
2517
+ return [node.id];
2518
+ }
2519
+
2520
+ /**
2521
+ * Build token → provider nodeId map from all nodes in the board.
2522
+ * Called before drawing edges so we can resolve requires tokens → source nodes.
2523
+ */
2524
+ function _buildTokenMap() {
2525
+ var map = {};
2526
+ nodeList.forEach(function(node) {
2527
+ _getProvides(node).forEach(function(token) {
2528
+ map[token] = node.id;
2529
+ });
2530
+ });
2531
+ return map;
2532
+ }
2533
+
2534
+ /**
2535
+ * Resolve required tokens to provider node IDs.
2536
+ * Returns deduplicated array of source node IDs for a given consumer node.
2537
+ */
2538
+ function _resolveEdgeSources(node, tokenMap) {
2539
+ var sources = [];
2540
+ var seen = {};
2541
+ _getRequires(node).forEach(function(token) {
2542
+ var srcId = tokenMap[token];
2543
+ if (srcId && !seen[srcId]) {
2544
+ seen[srcId] = true;
2545
+ sources.push(srcId);
2546
+ }
2547
+ });
2548
+ return sources;
2549
+ }
2550
+
2425
2551
  function _showCardInspector(node) {
2426
2552
  const modal = document.createElement('div');
2427
2553
  modal.className = 'modal d-block';
@@ -2447,32 +2573,9 @@ var LiveCard = (function () {
2447
2573
 
2448
2574
  const cardSection = document.createElement('div');
2449
2575
  cardSection.className = 'mb-4';
2450
- cardSection.innerHTML = '<h6 class="fw-semibold mb-2">Card Object (Editable)</h6>';
2451
-
2452
- const editableCardObject = JSON.parse(JSON.stringify((node && node.card) ? node.card : {}));
2453
-
2454
- const editor = document.createElement('textarea');
2455
- editor.className = 'form-control form-control-sm font-monospace';
2456
- editor.rows = 16;
2457
- editor.style.whiteSpace = 'pre';
2458
- editor.value = JSON.stringify(editableCardObject, null, 2);
2459
-
2460
- const editorHint = document.createElement('div');
2461
- editorHint.className = 'small text-muted mt-2';
2462
- editorHint.textContent = 'Edit JSON and click Submit to apply updates to this card.';
2463
-
2464
- const editorError = document.createElement('div');
2465
- editorError.className = 'small text-danger mt-1 d-none';
2466
-
2467
- const submitBtn = document.createElement('button');
2468
- submitBtn.type = 'button';
2469
- submitBtn.className = 'btn btn-primary btn-sm mb-2';
2470
- submitBtn.textContent = 'Submit';
2471
-
2472
- cardSection.appendChild(submitBtn);
2473
- cardSection.appendChild(editor);
2474
- cardSection.appendChild(editorHint);
2475
- cardSection.appendChild(editorError);
2576
+ cardSection.innerHTML = '<h6 class="fw-semibold mb-2">Card Definition (Read-only)</h6>';
2577
+ const cardDef = (node && node.card) ? node.card : {};
2578
+ cardSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(cardDef, null, 2))}</pre>`;
2476
2579
  body.appendChild(cardSection);
2477
2580
 
2478
2581
  const computedSection = document.createElement('div');
@@ -2482,13 +2585,6 @@ var LiveCard = (function () {
2482
2585
  computedSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(computedValues, null, 2))}</pre>`;
2483
2586
  body.appendChild(computedSection);
2484
2587
 
2485
- const sourcesSection = document.createElement('div');
2486
- sourcesSection.className = 'mb-4';
2487
- sourcesSection.innerHTML = '<h6 class="fw-semibold mb-2">Fetched Sources (Read-only)</h6>';
2488
- const sourcesData = node.fetched_sources || {};
2489
- sourcesSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(sourcesData, null, 2))}</pre>`;
2490
- body.appendChild(sourcesSection);
2491
-
2492
2588
  const requiresSection = document.createElement('div');
2493
2589
  requiresSection.className = 'mb-4';
2494
2590
  requiresSection.innerHTML = '<h6 class="fw-semibold mb-2">Requires (Read-only)</h6>';
@@ -2511,43 +2607,6 @@ var LiveCard = (function () {
2511
2607
  closeBtn.textContent = 'Close';
2512
2608
  closeBtn.addEventListener('click', closeModal);
2513
2609
 
2514
- submitBtn.addEventListener('click', function () {
2515
- editorError.classList.add('d-none');
2516
- editorError.textContent = '';
2517
- try {
2518
- const parsed = JSON.parse(editor.value);
2519
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
2520
- throw new Error('Card Object must be a JSON object.');
2521
- }
2522
- if (parsed.id && parsed.id !== node.id) {
2523
- throw new Error('Changing card id is not supported in the inspector.');
2524
- }
2525
-
2526
- const fixedId = node.id;
2527
- const preservedRuntime = {
2528
- card_data: node.card_data,
2529
- fetched_sources: node.fetched_sources,
2530
- requires: node.requires,
2531
- computed_values: node.computed_values,
2532
- runtime_state: node.runtime_state,
2533
- data_objects: node.data_objects,
2534
- };
2535
- node.card = parsed;
2536
- node.id = fixedId;
2537
- Object.assign(node, preservedRuntime);
2538
-
2539
- engine.notify(node.id, { inspector: 'card-object-updated' });
2540
- _render();
2541
-
2542
- submitBtn.textContent = '✓ Saved';
2543
- setTimeout(function () { submitBtn.textContent = 'Submit'; }, 1200);
2544
- closeModal();
2545
- } catch (err) {
2546
- editorError.textContent = 'Invalid JSON: ' + String((err && err.message) || err);
2547
- editorError.classList.remove('d-none');
2548
- }
2549
- });
2550
-
2551
2610
  footer.appendChild(closeBtn);
2552
2611
  content.appendChild(header);
2553
2612
  content.appendChild(body);
@@ -2562,8 +2621,9 @@ var LiveCard = (function () {
2562
2621
  const card = node && node.card ? node.card : {};
2563
2622
  const isSimulation = card.meta && card.meta.simulation === true;
2564
2623
  const isGandalfCard = card.meta && card.meta._gandalfCard === true;
2624
+ const isRunning = node && node.runtime_state && node.runtime_state.task_status === 'running';
2565
2625
  const extraClass = isSimulation ? ' lc-simulation-card' : (isGandalfCard ? ' lc-gandalf-card' : '');
2566
- wrap.className = 'card shadow-sm h-100' + extraClass;
2626
+ wrap.className = 'card shadow-sm h-100' + extraClass + (isRunning ? ' lc-running' : '');
2567
2627
  const header = document.createElement('div');
2568
2628
  header.className = 'card-header d-flex align-items-center gap-2 py-2';
2569
2629
  const title = (card.meta && card.meta.title) || node.id;
@@ -2639,8 +2699,44 @@ var LiveCard = (function () {
2639
2699
 
2640
2700
  const body = document.createElement('div');
2641
2701
  body.className = 'card-body p-2';
2702
+
2703
+ // Token gem rows — requires gems above header, provides gems below body
2704
+ const requiresTokens = (card.requires && Array.isArray(card.requires)) ? card.requires : [];
2705
+ const providesTokens = (Array.isArray(card.provides) && card.provides.length)
2706
+ ? card.provides.map(function(p) { return typeof p === 'string' ? p : (p.bindTo || p); })
2707
+ : [node.id];
2708
+
2709
+ // Requires gems — top of card (above header)
2710
+ if (requiresTokens.length) {
2711
+ const reqRow = document.createElement('div');
2712
+ reqRow.className = 'lc-token-row lc-token-row-requires';
2713
+ requiresTokens.forEach(function(token) {
2714
+ const gem = document.createElement('span');
2715
+ gem.className = 'lc-token-gem lc-token-gem-requires';
2716
+ gem.dataset.token = token;
2717
+ gem.title = token;
2718
+ reqRow.appendChild(gem);
2719
+ });
2720
+ wrap.appendChild(reqRow);
2721
+ }
2722
+
2642
2723
  wrap.appendChild(header);
2643
2724
  wrap.appendChild(body);
2725
+
2726
+ // Provides gems — bottom of card (below body)
2727
+ if (providesTokens.length) {
2728
+ const provRow = document.createElement('div');
2729
+ provRow.className = 'lc-token-row lc-token-row-provides';
2730
+ providesTokens.forEach(function(token) {
2731
+ const gem = document.createElement('span');
2732
+ gem.className = 'lc-token-gem lc-token-gem-provides';
2733
+ gem.dataset.token = token;
2734
+ gem.title = token;
2735
+ provRow.appendChild(gem);
2736
+ });
2737
+ wrap.appendChild(provRow);
2738
+ }
2739
+
2644
2740
  return { wrap, header, body };
2645
2741
  }
2646
2742
 
@@ -2661,7 +2757,23 @@ var LiveCard = (function () {
2661
2757
 
2662
2758
  // ---- Board mode ----
2663
2759
 
2760
+ // Compute canvas inner size from card positions + padding
2761
+ function _fitCanvasToContent() {
2762
+ var pad = 100;
2763
+ var maxR = 0, maxB = 0;
2764
+ canvasInner.querySelectorAll('.lc-canvas-card,.lc-source-node').forEach(function(el) {
2765
+ var r = el.offsetLeft + el.offsetWidth;
2766
+ var b = el.offsetTop + el.offsetHeight;
2767
+ if (r > maxR) maxR = r;
2768
+ if (b > maxB) maxB = b;
2769
+ });
2770
+ canvasInner.style.width = (maxR + pad) + 'px';
2771
+ canvasInner.style.height = (maxB + pad) + 'px';
2772
+ }
2773
+
2664
2774
  function _renderBoard() {
2775
+ _destroyEdges();
2776
+ document.body.style.overflow = '';
2665
2777
  root.innerHTML = '';
2666
2778
  root.appendChild(gridEl);
2667
2779
  gridEl.innerHTML = '';
@@ -2684,6 +2796,7 @@ var LiveCard = (function () {
2684
2796
  nodeMap[node.id] = { node, colEl: col, bodyEl: body };
2685
2797
  engine.render(node, body, { showChat });
2686
2798
  });
2799
+ _updateTokenAvailability();
2687
2800
  }
2688
2801
 
2689
2802
  // ---- Canvas mode ----
@@ -2692,26 +2805,116 @@ var LiveCard = (function () {
2692
2805
  canvasInner.style.transform = `translate(${cvs.panX}px,${cvs.panY}px) scale(${cvs.zoom})`;
2693
2806
  }
2694
2807
 
2808
+ /**
2809
+ * Update token badge availability: a provides badge turns green when the
2810
+ * node has data; a requires badge turns green when the upstream provider
2811
+ * has data for that token.
2812
+ */
2813
+ function _updateTokenAvailability() {
2814
+ var tokenMap = _buildTokenMap();
2815
+ // A node "has data" when card_data or computed_values is non-empty, or status is fresh/completed.
2816
+ var nodeHasData = {};
2817
+ nodeList.forEach(function(node) {
2818
+ var cd = node.card_data || (node.card && node.card.card_data);
2819
+ var cv = node.computed_values;
2820
+ var status = cd && cd.status;
2821
+ var hasOutput = (cd && Object.keys(cd).length > 0) || (cv && Object.keys(cv).length > 0);
2822
+ nodeHasData[node.id] = hasOutput || status === 'fresh' || status === 'completed';
2823
+ });
2824
+
2825
+ // Update all gem elements in root container
2826
+ var allGems = root.querySelectorAll('.lc-token-gem');
2827
+ allGems.forEach(function(gem) {
2828
+ var token = gem.dataset.token;
2829
+ if (!token) return;
2830
+ if (gem.classList.contains('lc-token-gem-provides')) {
2831
+ // The provides gem: green if this node has data
2832
+ var nodeEl = gem.closest('[data-node-id]');
2833
+ var nId = nodeEl && nodeEl.dataset.nodeId;
2834
+ gem.classList.toggle('lc-token-available', !!(nId && nodeHasData[nId]));
2835
+ } else if (gem.classList.contains('lc-token-gem-requires')) {
2836
+ // The requires gem: green if the upstream provider for this token has data
2837
+ var srcId = tokenMap[token];
2838
+ gem.classList.toggle('lc-token-available', !!(srcId && nodeHasData[srcId]));
2839
+ }
2840
+ });
2841
+ }
2842
+
2843
+ function _destroyEdges() {
2844
+ _edges.forEach(function(line) { try { line.remove(); } catch(e) { /* noop */ } });
2845
+ _edges.length = 0;
2846
+ }
2847
+
2848
+ function _repositionEdges() {
2849
+ _edges.forEach(function(line) { try { line.position(); } catch(e) { /* noop */ } });
2850
+ }
2851
+
2695
2852
  function _drawEdges() {
2696
- svgEl.querySelectorAll('line').forEach(l => l.remove());
2853
+ _destroyEdges();
2854
+ svgEl.querySelectorAll('line,path').forEach(function(el) { el.remove(); });
2697
2855
  if (!cvs.edges) return;
2698
2856
 
2699
- nodeList.forEach(node => {
2700
- _getRequires(node).forEach(srcId => {
2701
- const srcInfo = nodeMap[srcId];
2702
- const tgtInfo = nodeMap[node.id];
2703
- if (!srcInfo || !tgtInfo) return;
2704
- const sEl = srcInfo.colEl;
2705
- const tEl = tgtInfo.colEl;
2706
- const sx = sEl.offsetLeft + sEl.offsetWidth;
2707
- const sy = sEl.offsetTop + sEl.offsetHeight / 2;
2708
- const tx = tEl.offsetLeft;
2709
- const ty = tEl.offsetTop + tEl.offsetHeight / 2;
2710
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
2711
- line.setAttribute('x1', sx); line.setAttribute('y1', sy);
2712
- line.setAttribute('x2', tx); line.setAttribute('y2', ty);
2713
- line.setAttribute('marker-end', 'url(#lc-arrow)');
2714
- svgEl.appendChild(line);
2857
+ // Build token → provider nodeId map
2858
+ var tokenMap = _buildTokenMap();
2859
+
2860
+ // SVG edges — rendered behind cards (z-index:0) for a clean look
2861
+ nodeList.forEach(function(node) {
2862
+ var tgtInfo = nodeMap[node.id];
2863
+ if (!tgtInfo || !tgtInfo.colEl) return;
2864
+ _getRequires(node).forEach(function(token) {
2865
+ var srcId = tokenMap[token];
2866
+ if (!srcId) return;
2867
+ var srcInfo = nodeMap[srcId];
2868
+ if (!srcInfo || !srcInfo.colEl) return;
2869
+ // Locate gems; fall back to card element if gem not found
2870
+ var srcGem = srcInfo.colEl.querySelector('.lc-token-gem-provides[data-token="' + token + '"]');
2871
+ var tgtGem = tgtInfo.colEl.querySelector('.lc-token-gem-requires[data-token="' + token + '"]');
2872
+ var sx, sy, tx, ty;
2873
+ var innerRect = canvasInner.getBoundingClientRect();
2874
+ if (srcGem) {
2875
+ var srcRect = srcGem.getBoundingClientRect();
2876
+ sx = (srcRect.left + srcRect.width / 2 - innerRect.left) / cvs.zoom;
2877
+ sy = (srcRect.bottom - innerRect.top) / cvs.zoom;
2878
+ } else {
2879
+ var sEl = srcInfo.colEl;
2880
+ sx = sEl.offsetLeft + sEl.offsetWidth / 2;
2881
+ sy = sEl.offsetTop + sEl.offsetHeight;
2882
+ }
2883
+ if (tgtGem) {
2884
+ var tgtRect = tgtGem.getBoundingClientRect();
2885
+ tx = (tgtRect.left + tgtRect.width / 2 - innerRect.left) / cvs.zoom;
2886
+ ty = (tgtRect.top - innerRect.top) / cvs.zoom;
2887
+ } else {
2888
+ var tEl = tgtInfo.colEl;
2889
+ tx = tEl.offsetLeft + tEl.offsetWidth / 2;
2890
+ ty = tEl.offsetTop;
2891
+ }
2892
+ // Route bezier curves around cards — offset control points outward
2893
+ var dx = tx - sx;
2894
+ var dy = ty - sy;
2895
+ var dist = Math.sqrt(dx * dx + dy * dy);
2896
+ var cpLen = Math.max(40, Math.min(dist * 0.4, 120));
2897
+ // Determine if src is roughly above, below, left, or right of target
2898
+ var absDx = Math.abs(dx);
2899
+ var absDy = Math.abs(dy);
2900
+ var cp1x, cp1y, cp2x, cp2y;
2901
+ if (absDy > absDx * 0.4) {
2902
+ // Mostly vertical — curve control points go straight down/up
2903
+ cp1x = sx; cp1y = sy + cpLen;
2904
+ cp2x = tx; cp2y = ty - cpLen;
2905
+ } else {
2906
+ // Mostly horizontal — swing control points outward to avoid overlapping cards
2907
+ var sideSign = dx > 0 ? 1 : -1;
2908
+ cp1x = sx + sideSign * cpLen; cp1y = sy + cpLen * 0.5;
2909
+ cp2x = tx - sideSign * cpLen; cp2y = ty - cpLen * 0.5;
2910
+ }
2911
+ var d = 'M ' + sx + ' ' + sy + ' C ' + cp1x + ' ' + cp1y + ', ' + cp2x + ' ' + cp2y + ', ' + tx + ' ' + ty;
2912
+ var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2913
+ path.setAttribute('d', d);
2914
+ path.setAttribute('fill', 'none');
2915
+ path.setAttribute('marker-end', 'url(#lc-arrow)');
2916
+ path.classList.add('lc-edge-path');
2917
+ svgEl.appendChild(path);
2715
2918
  });
2716
2919
  });
2717
2920
  }
@@ -2736,7 +2939,8 @@ var LiveCard = (function () {
2736
2939
  const dy = (e.clientY - startY) / cvs.zoom;
2737
2940
  el.style.left = (origX + dx) + 'px';
2738
2941
  el.style.top = (origY + dy) + 'px';
2739
- _drawEdges();
2942
+ if (_edges.length) _repositionEdges();
2943
+ else _drawEdges();
2740
2944
  }, { signal });
2741
2945
 
2742
2946
  el.addEventListener('pointerup', () => {
@@ -2755,15 +2959,81 @@ var LiveCard = (function () {
2755
2959
  node.card.view.layout.canvas.y = y;
2756
2960
  }
2757
2961
  engine.notify(node.id);
2758
- _drawEdges();
2962
+ _fitCanvasToContent();
2963
+ if (_edges.length) _repositionEdges();
2964
+ else _drawEdges();
2965
+ }, { signal });
2966
+ }
2967
+
2968
+ function _makeResizable(el, node) {
2969
+ const handle = document.createElement('div');
2970
+ handle.className = 'lc-resize-handle';
2971
+ el.appendChild(handle);
2972
+ el.style.overflow = 'visible';
2973
+
2974
+ let resizing = false, startX, startY, origW, origH;
2975
+
2976
+ handle.addEventListener('pointerdown', function(e) {
2977
+ if (e.button !== 0) return;
2978
+ e.stopPropagation();
2979
+ e.preventDefault();
2980
+ resizing = true;
2981
+ el.classList.add('lc-resizing');
2982
+ handle.setPointerCapture(e.pointerId);
2983
+ startX = e.clientX;
2984
+ startY = e.clientY;
2985
+ origW = el.offsetWidth;
2986
+ origH = el.offsetHeight;
2987
+ }, { signal });
2988
+
2989
+ handle.addEventListener('pointermove', function(e) {
2990
+ if (!resizing) return;
2991
+ const dw = (e.clientX - startX) / cvs.zoom;
2992
+ const dh = (e.clientY - startY) / cvs.zoom;
2993
+ const newW = Math.max(cvs.minWidth, origW + dw);
2994
+ const newH = Math.max(80, origH + dh);
2995
+ el.style.width = newW + 'px';
2996
+ el.style.height = newH + 'px';
2997
+ if (_edges.length) _repositionEdges();
2998
+ else _drawEdges();
2999
+ }, { signal });
3000
+
3001
+ handle.addEventListener('pointerup', function() {
3002
+ if (!resizing) return;
3003
+ resizing = false;
3004
+ el.classList.remove('lc-resizing');
3005
+ const w = el.offsetWidth;
3006
+ const h = el.offsetHeight;
3007
+ // Snap to grid
3008
+ const sw = cvs.snap > 1 ? Math.round(w / cvs.snap) * cvs.snap : w;
3009
+ const sh = cvs.snap > 1 ? Math.round(h / cvs.snap) * cvs.snap : h;
3010
+ el.style.width = sw + 'px';
3011
+ el.style.height = sh + 'px';
3012
+ // Persist dimensions
3013
+ _positions[node.id] = Object.assign(_positions[node.id] || {}, { w: sw, h: sh });
3014
+ if (node.card && node.card.view) {
3015
+ if (!node.card.view.layout) node.card.view.layout = {};
3016
+ if (!node.card.view.layout.canvas) node.card.view.layout.canvas = {};
3017
+ node.card.view.layout.canvas.w = sw;
3018
+ node.card.view.layout.canvas.h = sh;
3019
+ }
3020
+ engine.notify(node.id);
3021
+ _fitCanvasToContent();
3022
+ if (_edges.length) _repositionEdges();
3023
+ else _drawEdges();
2759
3024
  }, { signal });
2760
3025
  }
2761
3026
 
2762
3027
  function _renderCanvas() {
3028
+ _destroyEdges();
3029
+ document.body.style.overflow = 'hidden';
2763
3030
  root.innerHTML = '';
2764
3031
  root.appendChild(canvasEl);
3032
+ // Fill remaining viewport height
3033
+ var top = canvasEl.getBoundingClientRect().top;
3034
+ canvasEl.style.height = co.height || ('calc(100vh - ' + top + 'px)');
2765
3035
  canvasInner.querySelectorAll('.lc-canvas-card,.lc-source-node').forEach(el => el.remove());
2766
- svgEl.querySelectorAll('line').forEach(l => l.remove());
3036
+ svgEl.querySelectorAll('line,path').forEach(function(el) { el.remove(); });
2767
3037
  _initPositions();
2768
3038
  _applyTransform();
2769
3039
 
@@ -2788,6 +3058,7 @@ var LiveCard = (function () {
2788
3058
  el.style.left = pos.x + 'px';
2789
3059
  el.style.top = pos.y + 'px';
2790
3060
  if (pos.w) el.style.width = pos.w + 'px';
3061
+ if (pos.h) el.style.height = pos.h + 'px';
2791
3062
 
2792
3063
  const { wrap, body } = _buildCardWrapper(node);
2793
3064
  while (wrap.firstChild) el.appendChild(wrap.firstChild);
@@ -2798,10 +3069,20 @@ var LiveCard = (function () {
2798
3069
  nodeMap[node.id] = { node, colEl: el, bodyEl: body };
2799
3070
  engine.render(node, body, { showChat: false });
2800
3071
  _makeDraggable(el, node);
3072
+ _makeResizable(el, node);
2801
3073
  }
2802
3074
  });
2803
3075
 
2804
- _drawEdges();
3076
+ _updateTokenAvailability();
3077
+
3078
+ // Fit canvas to content then draw edges
3079
+ requestAnimationFrame(function() {
3080
+ _fitCanvasToContent();
3081
+ _drawEdges();
3082
+ });
3083
+
3084
+ // Reposition LeaderLine edges on scroll
3085
+ canvasEl.addEventListener('scroll', function() { _repositionEdges(); }, { signal, passive: true });
2805
3086
 
2806
3087
  // Pan: middle-click or Ctrl+drag on background
2807
3088
  let panning = false, panStartX, panStartY, panOrigX, panOrigY;
@@ -2819,6 +3100,7 @@ var LiveCard = (function () {
2819
3100
  cvs.panX = panOrigX + (e.clientX - panStartX);
2820
3101
  cvs.panY = panOrigY + (e.clientY - panStartY);
2821
3102
  _applyTransform();
3103
+ _repositionEdges();
2822
3104
  }, { signal });
2823
3105
  canvasEl.addEventListener('pointerup', () => { panning = false; }, { signal });
2824
3106
 
@@ -2829,6 +3111,7 @@ var LiveCard = (function () {
2829
3111
  const delta = e.deltaY > 0 ? 0.9 : 1.1;
2830
3112
  cvs.zoom = Math.min(cvs.zoomMax, Math.max(cvs.zoomMin, cvs.zoom * delta));
2831
3113
  _applyTransform();
3114
+ _repositionEdges();
2832
3115
  }, { signal, passive: false });
2833
3116
  }
2834
3117
 
@@ -2840,11 +3123,12 @@ var LiveCard = (function () {
2840
3123
  // ---- Auto-layout (topological L → R) ----
2841
3124
 
2842
3125
  function autoLayout() {
3126
+ const tokenMap = _buildTokenMap();
2843
3127
  const incoming = {};
2844
3128
  const levels = {};
2845
3129
  nodeList.forEach(n => { incoming[n.id] = []; levels[n.id] = 0; });
2846
3130
  nodeList.forEach(n => {
2847
- _getRequires(n).forEach(srcId => {
3131
+ _resolveEdgeSources(n, tokenMap).forEach(srcId => {
2848
3132
  if (incoming[n.id]) incoming[n.id].push(srcId);
2849
3133
  });
2850
3134
  });
@@ -2907,9 +3191,44 @@ var LiveCard = (function () {
2907
3191
  _render();
2908
3192
  }
2909
3193
 
2910
- function refresh() { _render(); }
3194
+ /**
3195
+ * Per-node update: replace runtime fields on the existing node object in place
3196
+ * and re-render only that node's body. Outer wrapper is rebuilt to pick up
3197
+ * status/badges, but the surrounding column element is reused so layout is stable.
3198
+ * Editable element state is preserved via journal overlays keyed by nodeId:bindPath.
3199
+ */
3200
+ function updateNode(id, model) {
3201
+ const entry = nodeMap[id];
3202
+ if (!entry) throw new Error('updateNode: unknown node id ' + id);
3203
+ const node = entry.node;
3204
+ if (model && typeof model === 'object') {
3205
+ if (model.card !== undefined) node.card = model.card;
3206
+ if (model.card_data !== undefined) node.card_data = model.card_data;
3207
+ if (model.requires !== undefined) node.requires = model.requires;
3208
+ if (model.computed_values !== undefined) node.computed_values = model.computed_values;
3209
+ if (model.runtime_state !== undefined) node.runtime_state = model.runtime_state;
3210
+ }
3211
+ engine.destroy(id);
3212
+ if (mode.current === 'board') {
3213
+ const colEl = entry.colEl;
3214
+ colEl.innerHTML = '';
3215
+ const built = _buildCardWrapper(node);
3216
+ colEl.appendChild(built.wrap);
3217
+ nodeMap[id] = { node, colEl, bodyEl: built.body };
3218
+ engine.render(node, built.body, { showChat });
3219
+ } else {
3220
+ const el = entry.colEl;
3221
+ el.innerHTML = '';
3222
+ const built = _buildCardWrapper(node);
3223
+ while (built.wrap.firstChild) el.appendChild(built.wrap.firstChild);
3224
+ nodeMap[id] = { node, colEl: el, bodyEl: built.body };
3225
+ engine.render(node, built.body, { showChat: false });
3226
+ }
3227
+ _updateTokenAvailability();
3228
+ }
2911
3229
 
2912
3230
  function clear() {
3231
+ _destroyEdges();
2913
3232
  engine.destroyAll();
2914
3233
  nodeList.length = 0;
2915
3234
  Object.keys(nodeMap).forEach(k => delete nodeMap[k]);
@@ -2929,6 +3248,8 @@ var LiveCard = (function () {
2929
3248
  }
2930
3249
 
2931
3250
  function destroy() {
3251
+ _destroyEdges();
3252
+ document.body.style.overflow = '';
2932
3253
  ac.abort();
2933
3254
  engine.destroyAll();
2934
3255
  nodeList.length = 0;
@@ -2947,7 +3268,7 @@ var LiveCard = (function () {
2947
3268
  add,
2948
3269
  remove,
2949
3270
  reorder,
2950
- refresh,
3271
+ updateNode,
2951
3272
  clear,
2952
3273
  setMode,
2953
3274
  setDevMode,
@@ -2960,9 +3281,120 @@ var LiveCard = (function () {
2960
3281
  };
2961
3282
  }
2962
3283
 
3284
+ // ===========================================================================
3285
+ // Board — reactive host. State in, view out. No destructive re-renders.
3286
+ // ===========================================================================
3287
+
3288
+ function Board(engine, containerEl, opts) {
3289
+ opts = opts || {};
3290
+ const initialState = opts.initialState;
3291
+ const getNodeIds = opts.getNodeIds;
3292
+ const selectNode = opts.selectNode;
3293
+ if (typeof getNodeIds !== 'function' || typeof selectNode !== 'function') {
3294
+ throw new Error('LiveCard.Board requires getNodeIds and selectNode functions');
3295
+ }
3296
+
3297
+ let state = initialState;
3298
+ const prevModelsById = {};
3299
+ const prevFingerprintsById = {};
3300
+
3301
+ function _stableStringify(v) {
3302
+ if (v == null || typeof v !== 'object') return JSON.stringify(v);
3303
+ if (Array.isArray(v)) return '[' + v.map(_stableStringify).join(',') + ']';
3304
+ const keys = Object.keys(v).sort();
3305
+ return '{' + keys.map(k => JSON.stringify(k) + ':' + _stableStringify(v[k])).join(',') + '}';
3306
+ }
3307
+
3308
+ function _modelFingerprint(model) {
3309
+ if (!model || typeof model !== 'object') return 'null';
3310
+ return _stableStringify({
3311
+ card: model.card,
3312
+ card_data: model.card_data,
3313
+ requires: model.requires,
3314
+ computed_values: model.computed_values,
3315
+ runtime_state: model.runtime_state,
3316
+ });
3317
+ }
3318
+
3319
+ const initialIds = getNodeIds(state);
3320
+ const initialNodes = initialIds.map(id => {
3321
+ const m = selectNode(state, id);
3322
+ prevModelsById[id] = m;
3323
+ prevFingerprintsById[id] = _modelFingerprint(m);
3324
+ return m;
3325
+ });
3326
+
3327
+ const coreOpts = {};
3328
+ Object.keys(opts).forEach(k => {
3329
+ if (k === 'initialState' || k === 'getNodeIds' || k === 'selectNode' || k === 'nodes') return;
3330
+ coreOpts[k] = opts[k];
3331
+ });
3332
+ coreOpts.nodes = initialNodes;
3333
+
3334
+ const core = BoardCore(engine, containerEl, coreOpts);
3335
+
3336
+ function _changed(prevFingerprint, nextFingerprint) {
3337
+ return prevFingerprint !== nextFingerprint;
3338
+ }
3339
+
3340
+ function setState(nextStateOrUpdater) {
3341
+ const nextState = (typeof nextStateOrUpdater === 'function')
3342
+ ? nextStateOrUpdater(state)
3343
+ : nextStateOrUpdater;
3344
+ if (nextState === undefined) return;
3345
+
3346
+ state = nextState;
3347
+ const nextIds = getNodeIds(state);
3348
+ const nextSet = new Set(nextIds);
3349
+
3350
+ // Removals
3351
+ Object.keys(prevModelsById).forEach(id => {
3352
+ if (!nextSet.has(id)) {
3353
+ core.remove(id);
3354
+ delete prevModelsById[id];
3355
+ delete prevFingerprintsById[id];
3356
+ }
3357
+ });
3358
+
3359
+ // Additions and per-node updates
3360
+ nextIds.forEach(id => {
3361
+ const next = selectNode(state, id);
3362
+ const prev = prevModelsById[id];
3363
+ const nextFingerprint = _modelFingerprint(next);
3364
+ const prevFingerprint = prevFingerprintsById[id];
3365
+ if (!prev) {
3366
+ core.add(next);
3367
+ } else if (_changed(prevFingerprint, nextFingerprint)) {
3368
+ core.updateNode(id, next);
3369
+ }
3370
+ prevModelsById[id] = next;
3371
+ prevFingerprintsById[id] = nextFingerprint;
3372
+ });
3373
+
3374
+ // Reorder if id sequence differs
3375
+ const currentOrder = core.nodes.map(n => n.id);
3376
+ const orderDiffers = nextIds.length !== currentOrder.length
3377
+ || nextIds.some((id, i) => id !== currentOrder[i]);
3378
+ if (orderDiffers) core.reorder(nextIds);
3379
+ }
3380
+
3381
+ function destroy() {
3382
+ Object.keys(prevModelsById).forEach(k => delete prevModelsById[k]);
3383
+ Object.keys(prevFingerprintsById).forEach(k => delete prevFingerprintsById[k]);
3384
+ core.destroy();
3385
+ }
3386
+
3387
+ return {
3388
+ setState,
3389
+ destroy,
3390
+ core,
3391
+ get state() { return state; },
3392
+ };
3393
+ }
3394
+
2963
3395
  // ===========================================================================
2964
3396
  // Module export
2965
3397
  // ===========================================================================
2966
3398
 
2967
- return { init, Board };
3399
+ return { init, Board, BoardCore };
2968
3400
  })();