yaml-flow 5.2.5 → 5.2.8

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 (84) hide show
  1. package/README.md +6 -6
  2. package/board-livecards-server-runtime.js +260 -35
  3. package/browser/board-livegraph-engine.js +57 -32
  4. package/browser/board-livegraph-engine.js.map +1 -1
  5. package/browser/card-compute.js +17 -17
  6. package/browser/live-cards.js +139 -12
  7. package/browser/live-cards.schema.json +14 -9
  8. package/dist/board-livegraph-runtime/index.cjs +57 -32
  9. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  10. package/dist/board-livegraph-runtime/index.d.cts +1 -1
  11. package/dist/board-livegraph-runtime/index.d.ts +1 -1
  12. package/dist/board-livegraph-runtime/index.js +57 -32
  13. package/dist/board-livegraph-runtime/index.js.map +1 -1
  14. package/dist/card-compute/index.cjs +96 -38
  15. package/dist/card-compute/index.cjs.map +1 -1
  16. package/dist/card-compute/index.d.cts +13 -8
  17. package/dist/card-compute/index.d.ts +13 -8
  18. package/dist/card-compute/index.js +96 -38
  19. package/dist/card-compute/index.js.map +1 -1
  20. package/dist/cli/board-live-cards-cli.cjs +7200 -201
  21. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  22. package/dist/cli/board-live-cards-cli.d.cts +6 -6
  23. package/dist/cli/board-live-cards-cli.d.ts +6 -6
  24. package/dist/cli/board-live-cards-cli.js +7199 -201
  25. package/dist/cli/board-live-cards-cli.js.map +1 -1
  26. package/dist/continuous-event-graph/index.cjs +55 -30
  27. package/dist/continuous-event-graph/index.cjs.map +1 -1
  28. package/dist/continuous-event-graph/index.d.cts +2 -2
  29. package/dist/continuous-event-graph/index.d.ts +2 -2
  30. package/dist/continuous-event-graph/index.js +55 -30
  31. package/dist/continuous-event-graph/index.js.map +1 -1
  32. package/dist/index.cjs +121 -53
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +121 -53
  37. package/dist/index.js.map +1 -1
  38. package/dist/{live-cards-bridge-CeNxiVcm.d.ts → live-cards-bridge-EQjytzI_.d.ts} +10 -5
  39. package/dist/{live-cards-bridge-z_rJCSbi.d.cts → live-cards-bridge-x5XREkXm.d.cts} +10 -5
  40. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
  41. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +1 -1
  42. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +1 -1
  43. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
  44. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +2 -2
  45. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +1 -1
  46. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +10 -10
  47. package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  48. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
  49. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  50. package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +2 -2
  51. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
  52. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
  53. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
  54. package/examples/example-board/agent-instructions-cardlayout.md +1 -1
  55. package/examples/example-board/agent-instructions.md +271 -45
  56. package/examples/example-board/cards/card-concentration.json +8 -5
  57. package/examples/example-board/cards/card-market-prices.json +14 -9
  58. package/examples/example-board/cards/card-my-identity.json +28 -0
  59. package/examples/example-board/cards/card-portfolio-value.json +1 -1
  60. package/examples/example-board/cards/card-portfolio.json +1 -1
  61. package/examples/example-board/cards/card-rebalance-impact.json +65 -0
  62. package/examples/example-board/cards/card-rebalance-sim.json +57 -0
  63. package/examples/example-board/demo-chat-handler.js +2 -1
  64. package/examples/example-board/demo-server-config.json +6 -1
  65. package/examples/example-board/demo-server.js +79 -8
  66. package/examples/example-board/demo-shell-browser.html +6 -6
  67. package/examples/example-board/demo-shell-with-server.html +4 -4
  68. package/examples/example-board/demo-task-executor.js +436 -246
  69. package/examples/example-board/scripts/copilot_wrapper.bat +157 -0
  70. package/examples/example-board/scripts/copilot_wrapper_helper.ps1 +190 -0
  71. package/examples/example-board/scripts/workiq_wrapper.mjs +66 -0
  72. package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +5 -5
  73. package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +3 -3
  74. package/examples/npm-libs/event-graph/research-pipeline.ts +5 -5
  75. package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +9 -9
  76. package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
  77. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +1 -1
  78. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
  79. package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
  80. package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +1 -1
  81. package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +1 -1
  82. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +1 -1
  83. package/package.json +2 -2
  84. package/schema/live-cards.schema.json +14 -9
@@ -126,7 +126,7 @@
126
126
 
127
127
  var VALID_ELEMENT_KINDS = ['metric','table','chart','form','filter','list','notes','todo','alert','narrative','badge','text','markdown','custom'];
128
128
  var VALID_STATUSES = ['fresh','stale','loading','error'];
129
- var ALLOWED_KEYS = ['id','meta','requires','provides','view','card_data','compute','sources'];
129
+ var ALLOWED_KEYS = ['id','meta','requires','provides','view','card_data','compute','source_defs'];
130
130
 
131
131
  function validateNode(node) {
132
132
  var errors = [];
@@ -191,17 +191,17 @@
191
191
  }
192
192
  }
193
193
 
194
- // sources
195
- if (node.sources != null) {
196
- if (!Array.isArray(node.sources)) {
197
- errors.push('sources: must be an array');
194
+ // source_defs
195
+ if (node.source_defs != null) {
196
+ if (!Array.isArray(node.source_defs)) {
197
+ errors.push('source_defs: must be an array');
198
198
  } else {
199
- node.sources.forEach(function (src, i) {
200
- if (!src || typeof src !== 'object' || Array.isArray(src)) errors.push('sources[' + i + ']: must be an object');
201
- else if (typeof src.bindTo !== 'string' || !src.bindTo) errors.push('sources[' + i + ']: missing required "bindTo" property');
199
+ node.source_defs.forEach(function (src, i) {
200
+ if (!src || typeof src !== 'object' || Array.isArray(src)) errors.push('source_defs[' + i + ']: must be an object');
201
+ else if (typeof src.bindTo !== 'string' || !src.bindTo) errors.push('source_defs[' + i + ']: missing required "bindTo" property');
202
202
  else {
203
- if (src.outputFile != null && typeof src.outputFile !== 'string') errors.push('sources[' + i + ']: outputFile must be a string');
204
- if (src.optional != null && typeof src.optional !== 'boolean') errors.push('sources[' + i + ']: optional must be a boolean');
203
+ if (src.outputFile != null && typeof src.outputFile !== 'string') errors.push('source_defs[' + i + ']: outputFile must be a string');
204
+ if (src.optional != null && typeof src.optional !== 'boolean') errors.push('source_defs[' + i + ']: optional must be a boolean');
205
205
  }
206
206
  });
207
207
  }
@@ -231,18 +231,18 @@
231
231
  }
232
232
 
233
233
  /**
234
- * Enrich sources with execution context for template interpolation and prompt rendering.
235
- * Pure function: no side effects, returns new enriched sources array.
234
+ * Enrich source_defs with execution context for template interpolation and prompt rendering.
235
+ * Pure function: no side effects, returns new enriched source_defs array.
236
236
  *
237
- * @param {Array} sources - Array of source definitions
237
+ * @param {Array} source_defs - Array of source definitions
238
238
  * @param {Object} context - Execution context containing requires, sourcesData, computed_values
239
- * @returns {Array} New array of sources with _requires, _sourcesData, _computed_values attached
239
+ * @returns {Array} New array of source_defs with _requires, _sourcesData, _computed_values attached
240
240
  */
241
- function enrichSources(sources, context) {
242
- if (!sources || sources.length === 0) return [];
241
+ function enrichSources(source_defs, context) {
242
+ if (!source_defs || source_defs.length === 0) return [];
243
243
  context = context || {};
244
244
 
245
- return sources.map(function (src) {
245
+ return source_defs.map(function (src) {
246
246
  return Object.assign({}, src, {
247
247
  _requires: context.requires || {},
248
248
  _sourcesData: context.sourcesData || {},
@@ -1,10 +1,10 @@
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, sources, compute, view
5
- // Nodes with view render as cards; nodes with sources but no view render as source pills in canvas.
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
6
  // compute[] — ordered array of { bindTo, expr } JSONata steps → writes to node.computed_values (ephemeral)
7
- // sources[] — open objects: only bindTo + outputFile matter to the engine; all other fields are
7
+ // source_defs[] — open objects: only bindTo + outputFile matter to the engine; all other fields are
8
8
  // passed verbatim to the board's task-executor (--in JSON). Users define their own
9
9
  // shape (kind, url, mailbox, channel, model, ...) per executor.
10
10
  // requires[] — upstream node IDs; engine subscribes automatically
@@ -89,6 +89,15 @@ var LiveCard = (function () {
89
89
  .lc-files-modal-backdrop .modal-dialog { max-height:90vh; }
90
90
  .lc-files-modal-backdrop .modal-content { display:flex; flex-direction:column; max-height:90vh; }
91
91
  .lc-files-modal-backdrop .modal-body { overflow-y:auto; flex:1; min-height:200px; padding:1rem; }
92
+ .lc-simulation-card { background:#fdf6ec; border-color:#e0c97f !important; }
93
+ .lc-simulation-card .card-header { background:#faecc8; border-bottom-color:#e0c97f; }
94
+ .lc-gandalf-card { background:#eef4ff; border-color:#6ea4e0 !important; }
95
+ .lc-gandalf-card .card-header { background:#d7e8fa; border-bottom-color:#6ea4e0; cursor:pointer; user-select:none; }
96
+ .lc-gandalf-card .card-header:hover { background:#c8dcf5; }
97
+ .lc-gandalf-caret { transition:transform .2s; display:inline-flex; align-items:center; margin-left:auto; opacity:.6; flex-shrink:0; cursor:pointer; padding:2px; }
98
+ .lc-gandalf-caret:hover { opacity:1; }
99
+ .lc-gandalf-card.lc-collapsed .lc-gandalf-caret { transform:rotate(-90deg); }
100
+ .lc-gandalf-card.lc-collapsed .card-body { display:none !important; }
92
101
  @media (max-width:576px) {
93
102
  .lc-metric-value { font-size:1.5rem; }
94
103
  .lc-chart-wrap { min-height:150px; }
@@ -1847,6 +1856,68 @@ var LiveCard = (function () {
1847
1856
  };
1848
1857
  }
1849
1858
 
1859
+ // ---- ref ----
1860
+ // Indirection element: resolves a bind path to get the view definition,
1861
+ // then dispatches to the real renderer. The resolved value may be:
1862
+ // - a string → treated directly as the element kind ("table", "chart", etc.)
1863
+ // - an object → { kind, label, data: { columns, chartType, chartOptions, writeTo } }
1864
+ // merged with static elemDef (static fields win for protection)
1865
+ // - null/undefined → falls back to elemDef.data.fallbackKind or shape-inferred kind
1866
+ //
1867
+ // Allowed kinds from resolved value (whitelist, unknown → "table"):
1868
+ // table, editable-table, chart, metric, list, badge, text, narrative, markdown
1869
+ //
1870
+ // Usage:
1871
+ // { "kind": "ref",
1872
+ // "data": { "bind": "fetched_sources.rebalance.proposed_trades",
1873
+ // "viewBind": "card_data.display_mode",
1874
+ // "fallbackKind": "table" } }
1875
+ //
1876
+ // viewBind can point to any namespace: card_data, fetched_sources, requires, computed_values.
1877
+ // If the resolved view object contains a "bind" sub-path, that overrides data.bind.
1878
+ const _REF_KIND_WHITELIST = new Set([
1879
+ 'table','editable-table','chart','metric','list','badge',
1880
+ 'text','narrative','markdown','form','filter','todo','alert',
1881
+ ]);
1882
+ function _renderRef(data, el, elemDef, node) {
1883
+ const ed = elemDef.data || {};
1884
+
1885
+ // Resolve the view hint
1886
+ const viewRaw = ed.viewBind ? _resolveBind(node, ed.viewBind) : undefined;
1887
+
1888
+ let resolvedKind, resolvedExtra;
1889
+ if (typeof viewRaw === 'string' && viewRaw) {
1890
+ resolvedKind = viewRaw;
1891
+ resolvedExtra = {};
1892
+ } else if (viewRaw && typeof viewRaw === 'object' && !Array.isArray(viewRaw)) {
1893
+ resolvedKind = typeof viewRaw.kind === 'string' ? viewRaw.kind : undefined;
1894
+ resolvedExtra = viewRaw.data && typeof viewRaw.data === 'object' ? viewRaw.data : {};
1895
+ }
1896
+
1897
+ // Validate kind against whitelist; fall back to shape inference
1898
+ if (!resolvedKind || !_REF_KIND_WHITELIST.has(resolvedKind)) {
1899
+ resolvedKind = ed.fallbackKind && _REF_KIND_WHITELIST.has(ed.fallbackKind)
1900
+ ? ed.fallbackKind
1901
+ : (Array.isArray(data) ? 'table' : typeof data === 'string' ? 'text' : 'narrative');
1902
+ }
1903
+
1904
+ // Build effective elemDef: resolved hints first, static elemDef fields override (card author wins)
1905
+ const mergedData = Object.assign({}, resolvedExtra, ed);
1906
+ delete mergedData.viewBind;
1907
+ delete mergedData.fallbackKind;
1908
+
1909
+ // If the resolved hint provided its own bind path, honour it (but static ed.bind still wins)
1910
+ if (!mergedData.bind && resolvedExtra.bind) mergedData.bind = resolvedExtra.bind;
1911
+
1912
+ const effectiveElemDef = Object.assign({}, elemDef, { kind: resolvedKind }, { data: mergedData });
1913
+
1914
+ // Re-resolve data using effective bind (may have changed)
1915
+ const effectiveData = mergedData.bind ? _resolveBind(node, mergedData.bind) : data;
1916
+
1917
+ const renderer = _renderers[resolvedKind] || _renderers.table;
1918
+ renderer(effectiveData, el, effectiveElemDef, node);
1919
+ }
1920
+
1850
1921
  // ---- Register built-in renderers ----
1851
1922
 
1852
1923
  _renderers.table = _renderTable;
@@ -1867,6 +1938,7 @@ var LiveCard = (function () {
1867
1938
  _renderers['file-upload'] = _renderFileUpload;
1868
1939
  _renderers['chat'] = _renderChatEl;
1869
1940
  _renderers.actions = _renderActions;
1941
+ _renderers.ref = _renderRef;
1870
1942
 
1871
1943
  // ===========================================================================
1872
1944
  // _renderElements — render all view.elements for a card node
@@ -2453,26 +2525,75 @@ var LiveCard = (function () {
2453
2525
 
2454
2526
  function _buildCardWrapper(node) {
2455
2527
  const wrap = document.createElement('div');
2456
- wrap.className = 'card shadow-sm h-100';
2528
+ const card = node && node.card ? node.card : {};
2529
+ const isSimulation = card.meta && card.meta.simulation === true;
2530
+ const isGandalfCard = card.meta && card.meta._gandalfCard === true;
2531
+ const extraClass = isSimulation ? ' lc-simulation-card' : (isGandalfCard ? ' lc-gandalf-card' : '');
2532
+ wrap.className = 'card shadow-sm h-100' + extraClass;
2457
2533
  const header = document.createElement('div');
2458
2534
  header.className = 'card-header d-flex align-items-center gap-2 py-2';
2459
- const card = node && node.card ? node.card : {};
2460
2535
  const title = (card.meta && card.meta.title) || node.id;
2461
2536
  const tags = (card.meta && card.meta.tags) || [];
2462
2537
  let badgeHtml = '';
2463
- if ((card.sources && card.sources.length) && !card.view) {
2464
- var src = card.sources[0] || {};
2538
+ if ((card.source_defs && card.source_defs.length) && !card.view) {
2539
+ var src = card.source_defs[0] || {};
2465
2540
  badgeHtml = '<span class="badge bg-info text-dark ms-auto">' + _esc(src.kind || 'source') + '</span>';
2466
2541
  } else if (tags.length) {
2467
2542
  badgeHtml = tags.map(t => '<span class="badge bg-secondary ms-1">' + _esc(t) + '</span>').join('');
2468
2543
  }
2469
2544
  header.innerHTML = '<strong class="small">' + _esc(title) + '</strong>' + badgeHtml;
2470
-
2545
+
2546
+ // Gandalf cards: collapsible via caret — caret gets its own click listener,
2547
+ // header is left alone for dragging in canvas mode.
2548
+ if (isGandalfCard) {
2549
+ const caret = document.createElement('span');
2550
+ caret.className = 'lc-gandalf-caret';
2551
+ caret.title = 'Collapse / expand';
2552
+ caret.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
2553
+ header.appendChild(caret);
2554
+
2555
+ const storageKey = 'lc-gandalf-collapsed:' + (node.id || title);
2556
+ if (sessionStorage.getItem(storageKey) === '1') {
2557
+ wrap.classList.add('lc-collapsed');
2558
+ header.dataset.gandalfCollapsed = '1';
2559
+ }
2560
+
2561
+ caret.addEventListener('click', function(e) {
2562
+ e.stopPropagation();
2563
+ const cardEl = caret.closest('.lc-gandalf-card') || wrap;
2564
+ cardEl.classList.toggle('lc-collapsed');
2565
+ sessionStorage.setItem(storageKey, cardEl.classList.contains('lc-collapsed') ? '1' : '0');
2566
+ });
2567
+ caret.addEventListener('pointerdown', e => e.stopPropagation()); // prevent drag start
2568
+ }
2569
+ if (isSimulation) {
2570
+ const simBtns = document.createElement('span');
2571
+ simBtns.className = 'd-inline-flex align-items-center gap-1 ms-auto';
2572
+
2573
+ const pinBtn = document.createElement('button');
2574
+ pinBtn.className = 'btn btn-sm btn-outline-success lc-sim-pin';
2575
+ pinBtn.style.cssText = 'padding: 2px 6px;';
2576
+ pinBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 2h6l-1 7h-4L9 2z"/><path d="M6 17h12l-2-4H8L6 17z"/></svg>';
2577
+ pinBtn.title = 'Pin this simulation card';
2578
+ pinBtn.dataset.nodeId = node.id;
2579
+
2580
+ const discardBtn = document.createElement('button');
2581
+ discardBtn.className = 'btn btn-sm btn-outline-danger lc-sim-discard';
2582
+ discardBtn.style.cssText = 'padding: 2px 6px;';
2583
+ discardBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
2584
+ discardBtn.title = 'Discard this simulation card';
2585
+ discardBtn.dataset.nodeId = node.id;
2586
+
2587
+ simBtns.appendChild(pinBtn);
2588
+ simBtns.appendChild(discardBtn);
2589
+ header.appendChild(simBtns);
2590
+ }
2591
+
2471
2592
  // Add dev mode code icon button if devMode is enabled
2472
2593
  if (devMode.current) {
2473
2594
  const codeBtn = document.createElement('button');
2474
2595
  codeBtn.className = 'btn btn-sm btn-outline-secondary';
2475
- codeBtn.style.cssText = 'padding: 2px 6px; margin-left: auto;';
2596
+ codeBtn.style.cssText = 'padding: 2px 6px;' + (isSimulation ? '' : ' margin-left: auto;');
2476
2597
  codeBtn.innerHTML = '&lt;/&gt;';
2477
2598
  codeBtn.title = 'Inspect card data';
2478
2599
  codeBtn.addEventListener('click', function(e) {
@@ -2495,7 +2616,7 @@ var LiveCard = (function () {
2495
2616
  const status = (node.card_data && node.card_data.status) || 'fresh';
2496
2617
  const card = node && node.card ? node.card : {};
2497
2618
  const title = (card.meta && card.meta.title) || node.id;
2498
- const kind = (card.sources && card.sources[0] && card.sources[0].kind) || 'source';
2619
+ const kind = (card.source_defs && card.source_defs[0] && card.source_defs[0].kind) || 'source';
2499
2620
  el.innerHTML = `<div class="lc-source-pill shadow-sm">
2500
2621
  ${_statusDot(status)}
2501
2622
  <span class="fw-medium">${_esc(title)}</span>
@@ -2615,7 +2736,7 @@ var LiveCard = (function () {
2615
2736
  nodeList.forEach(node => {
2616
2737
  const pos = _positions[node.id] || { x: 0, y: 0 };
2617
2738
 
2618
- if ((!node.card || !node.card.view) && (node.card && node.card.sources && node.card.sources.length)) {
2739
+ if ((!node.card || !node.card.view) && (node.card && node.card.source_defs && node.card.source_defs.length)) {
2619
2740
  const el = _buildSourcePill(node);
2620
2741
  el.dataset.nodeId = node.id;
2621
2742
  el.style.left = pos.x + 'px';
@@ -2625,7 +2746,10 @@ var LiveCard = (function () {
2625
2746
  _makeDraggable(el, node);
2626
2747
  } else {
2627
2748
  const el = document.createElement('div');
2628
- el.className = 'lc-canvas-card card shadow-sm';
2749
+ const isSimCanvas = node.card && node.card.meta && node.card.meta.simulation === true;
2750
+ const isGandalfCanvas = node.card && node.card.meta && node.card.meta._gandalfCard === true;
2751
+ const canvasExtra = isSimCanvas ? ' lc-simulation-card' : (isGandalfCanvas ? ' lc-gandalf-card' : '');
2752
+ el.className = 'lc-canvas-card card shadow-sm' + canvasExtra;
2629
2753
  el.dataset.nodeId = node.id;
2630
2754
  el.style.left = pos.x + 'px';
2631
2755
  el.style.top = pos.y + 'px';
@@ -2633,6 +2757,9 @@ var LiveCard = (function () {
2633
2757
 
2634
2758
  const { wrap, body } = _buildCardWrapper(node);
2635
2759
  while (wrap.firstChild) el.appendChild(wrap.firstChild);
2760
+ // Re-apply collapsed state: in canvas mode el is the card container, not wrap
2761
+ const movedHeader = el.querySelector('.card-header');
2762
+ if (movedHeader && movedHeader.dataset.gandalfCollapsed === '1') el.classList.add('lc-collapsed');
2636
2763
  canvasInner.appendChild(el);
2637
2764
  nodeMap[node.id] = { node, colEl: el, bodyEl: body };
2638
2765
  engine.render(node, body, { showNotes: false, showChat: false });
@@ -80,10 +80,10 @@
80
80
  "type": "array",
81
81
  "items": {
82
82
  "type": "object",
83
- "required": ["bindTo", "src"],
83
+ "required": ["bindTo", "ref"],
84
84
  "properties": {
85
85
  "bindTo": { "type": "string", "description": "Token name published downstream" },
86
- "src": { "type": "string", "description": "Path to read value from (card_data.*, requires.*, fetched_sources.*, computed_values.*)" }
86
+ "ref": { "type": "string", "description": "Path to read value from (card_data.*, requires.*, fetched_sources.*, computed_values.*)" }
87
87
  }
88
88
  },
89
89
  "description": "Explicit bindings exposing computed or card_data values downstream as named tokens"
@@ -100,14 +100,19 @@
100
100
  },
101
101
 
102
102
  "source_def": {
103
- "description": "One source entry. The engine requires 'bindTo' (compute namespace key) and 'outputFile' (delivery signal path). bindTo and outputFile must be unique across all sources in a card. Every other property is yours — add whatever your task-executor needs: kind, url, headers, mailbox, channel, model, query, etc. The full object is passed verbatim as the --in JSON to the executor.",
103
+ "description": "One source entry. The engine requires 'bindTo' (compute namespace key) and 'outputFile' (delivery signal path). bindTo and outputFile must be unique across all source_defs in a card. Every other property is yours — add whatever your task-executor needs: kind, url, headers, mailbox, channel, model, query, etc. The full object is passed verbatim as the --in JSON to the executor.",
104
104
  "type": "object",
105
105
  "required": ["bindTo", "outputFile"],
106
106
  "additionalProperties": true,
107
107
  "properties": {
108
108
  "bindTo": { "type": "string", "description": "Key under fetched_sources.* available in compute expressions" },
109
109
  "outputFile": { "type": "string", "description": "Board-relative path the executor writes its JSON result to. Presence of this file signals delivery." },
110
- "optionalForCompletionGating": { "type": "boolean", "default": false, "description": "When true this source does not gate card completion. Default false when absent, so sources are completion-gating by default." },
110
+ "projections": {
111
+ "type": "object",
112
+ "description": "Named data projections from card_data or requires, evaluated before the executor is called. Each key is a ref name; each value is a JSONata expression rooted at card_data or requires. The resolved values are passed to the executor as _projections. fetched_sources, computed_values, and source_defs are not accessible here — sources run before those exist.",
113
+ "additionalProperties": { "type": "string" }
114
+ },
115
+ "optionalForCompletionGating": { "type": "boolean", "default": false, "description": "When true this source does not gate card completion. Default false when absent, so source_defs are completion-gating by default." },
111
116
  "timeout": { "type": "integer", "minimum": 0, "default": 120000, "description": "Executor/script timeout in ms. Default: 120 000 (2 min)." },
112
117
  "script": { "type": "string", "description": "Legacy direct-run: shell command executed when no .task-executor is registered. stdout is captured as the result." }
113
118
  }
@@ -215,7 +220,7 @@
215
220
  },
216
221
 
217
222
  "title": "LiveCard",
218
- "description": "A unified card node. Behavior depends on which sections are present (sources, compute, view, etc.)",
223
+ "description": "A unified card node. Behavior depends on which sections are present (source_defs, compute, view, etc.)",
219
224
  "type": "object",
220
225
  "required": ["id"],
221
226
  "additionalProperties": false,
@@ -258,22 +263,22 @@
258
263
  },
259
264
  "llm_task_completion_inference": {
260
265
  "type": "object",
261
- "description": "Runtime state written by the inference adapter (advanced/undocumented). Prefer the standard sources → compute → provides pattern for LLM-based signals.",
266
+ "description": "Runtime state written by the inference adapter (advanced/undocumented). Prefer the standard source_defs → compute → provides pattern for LLM-based signals.",
262
267
  "properties": {
263
268
  "inferenceRequested": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request was initiated" },
264
269
  "inferenceCompletedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the latest inference request completed" },
265
270
  "isTaskCompleted": { "type": "boolean", "description": "Whether the task is considered complete by the adapter" },
266
271
  "reasoning": { "type": "string", "description": "Explanation of completion decision" },
267
- "evidence": { "type": "array", "description": "Supporting evidence from sources/compute" }
272
+ "evidence": { "type": "array", "description": "Supporting evidence from source_defs/compute" }
268
273
  },
269
274
  "additionalProperties": true
270
275
  }
271
276
  },
272
277
  "additionalProperties": true
273
278
  },
274
- "sources": {
279
+ "source_defs": {
275
280
  "type": "array",
276
- "description": "Source entries. Each entry is passed verbatim to the board's .task-executor (registered via init --task-executor) as the --in JSON file. The executor fetches/generates the data and writes JSON to --out. If no executor is registered, the built-in executor runs the entry's 'cli' command directly. Sources gate completion by default. Set optionalForCompletionGating: true for enrichment-only sources that should not block task-completed.",
281
+ "description": "Source entries. Each entry is passed verbatim to the board's .task-executor (registered via init --task-executor) as the --in JSON file. The executor fetches/generates the data and writes JSON to --out. If no executor is registered, the built-in executor runs the entry's 'cli' command directly. Sources gate completion by default. Set optionalForCompletionGating: true for enrichment-only source_defs that should not block task-completed.",
277
282
  "items": { "$ref": "#/definitions/source_def" }
278
283
  },
279
284
  "compute": {
@@ -83,7 +83,7 @@ var VALID_ELEMENT_KINDS = /* @__PURE__ */ new Set([
83
83
  "markdown",
84
84
  "custom"
85
85
  ]);
86
- var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "sources"]);
86
+ var ALLOWED_KEYS = /* @__PURE__ */ new Set(["id", "meta", "requires", "provides", "view", "card_data", "compute", "source_defs"]);
87
87
  function validateNode(node) {
88
88
  const errors = [];
89
89
  if (!node || typeof node !== "object" || Array.isArray(node)) {
@@ -109,15 +109,15 @@ function validateNode(node) {
109
109
  if (n.requires != null && !Array.isArray(n.requires)) errors.push("requires: must be an array of strings");
110
110
  if (n.provides != null) {
111
111
  if (!Array.isArray(n.provides)) {
112
- errors.push("provides: must be an array of { bindTo, src } bindings");
112
+ errors.push("provides: must be an array of { bindTo, ref } bindings");
113
113
  } else {
114
114
  n.provides.forEach((p, i) => {
115
115
  if (!p || typeof p !== "object" || Array.isArray(p)) {
116
- errors.push(`provides[${i}]: must be an object with bindTo and src`);
116
+ errors.push(`provides[${i}]: must be an object with bindTo and ref`);
117
117
  } else {
118
118
  const b = p;
119
119
  if (typeof b.bindTo !== "string" || !b.bindTo) errors.push(`provides[${i}]: missing required "bindTo" string`);
120
- if (typeof b.src !== "string" || !b.src) errors.push(`provides[${i}]: missing required "src" string`);
120
+ if (typeof b.ref !== "string" || !b.ref) errors.push(`provides[${i}]: missing required "ref" string`);
121
121
  }
122
122
  });
123
123
  }
@@ -137,35 +137,35 @@ function validateNode(node) {
137
137
  });
138
138
  }
139
139
  }
140
- if (n.sources != null) {
141
- if (!Array.isArray(n.sources)) {
142
- errors.push("sources: must be an array");
140
+ if (n.source_defs != null) {
141
+ if (!Array.isArray(n.source_defs)) {
142
+ errors.push("source_defs: must be an array");
143
143
  } else {
144
144
  const bindTos = /* @__PURE__ */ new Set();
145
145
  const outputFiles = /* @__PURE__ */ new Set();
146
- n.sources.forEach((src, i) => {
146
+ n.source_defs.forEach((src, i) => {
147
147
  if (!src || typeof src !== "object" || Array.isArray(src)) {
148
- errors.push(`sources[${i}]: must be an object`);
148
+ errors.push(`source_defs[${i}]: must be an object`);
149
149
  } else {
150
150
  const s = src;
151
151
  if (typeof s.bindTo !== "string" || !s.bindTo) {
152
- errors.push(`sources[${i}]: missing required "bindTo" property`);
152
+ errors.push(`source_defs[${i}]: missing required "bindTo" property`);
153
153
  } else {
154
154
  if (bindTos.has(s.bindTo)) {
155
- errors.push(`sources[${i}]: bindTo "${s.bindTo}" is not unique across sources`);
155
+ errors.push(`source_defs[${i}]: bindTo "${s.bindTo}" is not unique across source_defs`);
156
156
  }
157
157
  bindTos.add(s.bindTo);
158
158
  }
159
159
  if (typeof s.outputFile !== "string" || !s.outputFile) {
160
- errors.push(`sources[${i}]: missing required "outputFile" property`);
160
+ errors.push(`source_defs[${i}]: missing required "outputFile" property`);
161
161
  } else {
162
162
  if (outputFiles.has(s.outputFile)) {
163
- errors.push(`sources[${i}]: outputFile "${s.outputFile}" is not unique across sources`);
163
+ errors.push(`source_defs[${i}]: outputFile "${s.outputFile}" is not unique across source_defs`);
164
164
  }
165
165
  outputFiles.add(s.outputFile);
166
166
  }
167
167
  if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== "boolean") {
168
- errors.push(`sources[${i}]: optionalForCompletionGating must be a boolean`);
168
+ errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);
169
169
  }
170
170
  }
171
171
  });
@@ -200,14 +200,29 @@ function validateNode(node) {
200
200
  }
201
201
  return { ok: errors.length === 0, errors };
202
202
  }
203
- function enrichSources(sources, context) {
204
- if (!sources || sources.length === 0) return [];
205
- return sources.map((src) => ({
206
- ...src,
207
- _requires: context.requires ?? {},
208
- _sourcesData: context.sourcesData ?? {},
209
- _computed_values: context.computed_values ?? {}
210
- }));
203
+ async function enrichSources(source_defs, context) {
204
+ if (!source_defs || source_defs.length === 0) return [];
205
+ const evalCtx = {
206
+ card_data: context.card_data ?? {},
207
+ requires: context.requires ?? {}
208
+ };
209
+ return Promise.all(
210
+ source_defs.map(async (src) => {
211
+ const _projections = {};
212
+ if (src.projections && typeof src.projections === "object" && !Array.isArray(src.projections)) {
213
+ for (const [key, expr] of Object.entries(src.projections)) {
214
+ if (typeof expr === "string" && expr.trim().length > 0) {
215
+ try {
216
+ _projections[key] = await jsonata2__default.default(expr).evaluate(evalCtx);
217
+ } catch {
218
+ _projections[key] = void 0;
219
+ }
220
+ }
221
+ }
222
+ }
223
+ return { ...src, _projections };
224
+ })
225
+ );
211
226
  }
212
227
  var CardCompute = {
213
228
  run,
@@ -908,6 +923,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
908
923
  const inputQueue = new MemoryJournal();
909
924
  let live = "state" in configOrLive && "config" in configOrLive ? configOrLive : createLiveGraph(configOrLive, executionId);
910
925
  let disposed = false;
926
+ const pendingHandlers = /* @__PURE__ */ new Set();
911
927
  const handlers = new Map(Object.entries(initialHandlers));
912
928
  const internalJournal = new MemoryJournal();
913
929
  let draining = false;
@@ -950,7 +966,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
950
966
  const taskState = live.state.tasks[taskName];
951
967
  if (!taskState || taskState.status !== "running") continue;
952
968
  const callbackToken = encodeCallbackToken(taskName);
953
- runPipeline(taskName, callbackToken, update).catch((error) => {
969
+ const p = runPipeline(taskName, callbackToken, update).catch((error) => {
954
970
  if (disposed) return;
955
971
  internalJournal.append({
956
972
  type: "task-failed",
@@ -959,7 +975,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
959
975
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
960
976
  });
961
977
  drain();
978
+ }).finally(() => {
979
+ pendingHandlers.delete(p);
962
980
  });
981
+ pendingHandlers.add(p);
963
982
  }
964
983
  }
965
984
  }
@@ -1019,7 +1038,7 @@ function createReactiveGraph(configOrLive, options, executionId) {
1019
1038
  });
1020
1039
  drain();
1021
1040
  const callbackToken = encodeCallbackToken(taskName);
1022
- runPipeline(taskName, callbackToken).catch((error) => {
1041
+ const p = runPipeline(taskName, callbackToken).catch((error) => {
1023
1042
  if (disposed) return;
1024
1043
  internalJournal.append({
1025
1044
  type: "task-failed",
@@ -1028,7 +1047,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
1028
1047
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1029
1048
  });
1030
1049
  drain();
1050
+ }).finally(() => {
1051
+ pendingHandlers.delete(p);
1031
1052
  });
1053
+ pendingHandlers.add(p);
1032
1054
  }
1033
1055
  return {
1034
1056
  push(event) {
@@ -1142,7 +1164,10 @@ function createReactiveGraph(configOrLive, options, executionId) {
1142
1164
  getSchedule() {
1143
1165
  return schedule(live);
1144
1166
  },
1145
- dispose() {
1167
+ async dispose(options2) {
1168
+ if (options2?.wait && pendingHandlers.size > 0) {
1169
+ await Promise.allSettled([...pendingHandlers]);
1170
+ }
1146
1171
  disposed = true;
1147
1172
  }
1148
1173
  };
@@ -1164,7 +1189,7 @@ function toTaskConfig(card) {
1164
1189
  function buildTokenProviders(cards) {
1165
1190
  const tokenToCardId = /* @__PURE__ */ new Map();
1166
1191
  for (const [cardId, card] of cards.entries()) {
1167
- const bindings = card.provides && card.provides.length > 0 ? card.provides : [{ bindTo: cardId, src: "card_data" }];
1192
+ const bindings = card.provides && card.provides.length > 0 ? card.provides : [{ bindTo: cardId, ref: "card_data" }];
1168
1193
  for (const binding of bindings) tokenToCardId.set(binding.bindTo, cardId);
1169
1194
  }
1170
1195
  return tokenToCardId;
@@ -1310,14 +1335,14 @@ function createBoardLiveGraphRuntime(input, options = {}) {
1310
1335
  requiresData[token] = providesData2[token];
1311
1336
  }
1312
1337
  const sourcesData = {};
1313
- if (card.sources && card.sources.length > 0) {
1338
+ if (card.source_defs && card.source_defs.length > 0) {
1314
1339
  const adapter = sourceAdapters[cardId] ?? defaultSourceAdapter;
1315
1340
  const fetched = taskExecutor ? await taskExecutor({ card, input: inputArgs }) : adapter ? await adapter({ card, input: inputArgs }) : void 0;
1316
1341
  if (fetched && typeof fetched === "object") {
1317
- for (const src of card.sources) {
1342
+ for (const src of card.source_defs) {
1318
1343
  if (Object.prototype.hasOwnProperty.call(fetched, src.bindTo)) {
1319
1344
  sourcesData[src.bindTo] = fetched[src.bindTo];
1320
- } else if (card.sources.length === 1) {
1345
+ } else if (card.source_defs.length === 1) {
1321
1346
  sourcesData[src.bindTo] = fetched;
1322
1347
  }
1323
1348
  }
@@ -1327,7 +1352,7 @@ function createBoardLiveGraphRuntime(input, options = {}) {
1327
1352
  id: card.id,
1328
1353
  card_data: deepClone(card.card_data ?? {}),
1329
1354
  requires: requiresData,
1330
- sources: card.sources,
1355
+ source_defs: card.source_defs,
1331
1356
  compute: card.compute
1332
1357
  };
1333
1358
  computeNode._sourcesData = sourcesData;
@@ -1336,8 +1361,8 @@ function createBoardLiveGraphRuntime(input, options = {}) {
1336
1361
  }
1337
1362
  const providesData = {};
1338
1363
  if (card.provides && card.provides.length > 0) {
1339
- for (const { bindTo, src } of card.provides) {
1340
- providesData[bindTo] = CardCompute.resolve(computeNode, src);
1364
+ for (const { bindTo, ref } of card.provides) {
1365
+ providesData[bindTo] = CardCompute.resolve(computeNode, ref);
1341
1366
  }
1342
1367
  } else {
1343
1368
  providesData[card.id] = {