yaml-flow 5.1.0 → 5.2.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 (105) hide show
  1. package/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +42 -20
  2. package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
  3. package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
  4. package/browser/board-livegraph-engine.js.map +1 -0
  5. package/browser/live-cards.js +362 -38
  6. package/browser/live-cards.schema.json +20 -4
  7. package/dist/board-livegraph-runtime/index.cjs +210 -14
  8. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  9. package/dist/board-livegraph-runtime/index.d.cts +49 -5
  10. package/dist/board-livegraph-runtime/index.d.ts +49 -5
  11. package/dist/board-livegraph-runtime/index.js +209 -15
  12. package/dist/board-livegraph-runtime/index.js.map +1 -1
  13. package/dist/card-compute/index.cjs +63 -7
  14. package/dist/card-compute/index.cjs.map +1 -1
  15. package/dist/card-compute/index.d.cts +2 -2
  16. package/dist/card-compute/index.d.ts +2 -2
  17. package/dist/card-compute/index.js +63 -7
  18. package/dist/card-compute/index.js.map +1 -1
  19. package/dist/cli/board-live-cards-cli.cjs +664 -75
  20. package/dist/cli/board-live-cards-cli.cjs.map +1 -1
  21. package/dist/cli/board-live-cards-cli.d.cts +33 -5
  22. package/dist/cli/board-live-cards-cli.d.ts +33 -5
  23. package/dist/cli/board-live-cards-cli.js +661 -76
  24. package/dist/cli/board-live-cards-cli.js.map +1 -1
  25. package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
  26. package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
  27. package/dist/continuous-event-graph/index.cjs +47 -14
  28. package/dist/continuous-event-graph/index.cjs.map +1 -1
  29. package/dist/continuous-event-graph/index.d.cts +9 -9
  30. package/dist/continuous-event-graph/index.d.ts +9 -9
  31. package/dist/continuous-event-graph/index.js +47 -14
  32. package/dist/continuous-event-graph/index.js.map +1 -1
  33. package/dist/event-graph/index.cjs +29 -12
  34. package/dist/event-graph/index.cjs.map +1 -1
  35. package/dist/event-graph/index.d.cts +5 -5
  36. package/dist/event-graph/index.d.ts +5 -5
  37. package/dist/event-graph/index.js +29 -12
  38. package/dist/event-graph/index.js.map +1 -1
  39. package/dist/index.cjs +93 -20
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +7 -7
  42. package/dist/index.d.ts +7 -7
  43. package/dist/index.js +93 -20
  44. package/dist/index.js.map +1 -1
  45. package/dist/inference/index.cjs +29 -12
  46. package/dist/inference/index.cjs.map +1 -1
  47. package/dist/inference/index.d.cts +2 -2
  48. package/dist/inference/index.d.ts +2 -2
  49. package/dist/inference/index.js +29 -12
  50. package/dist/inference/index.js.map +1 -1
  51. package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
  52. package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
  53. package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
  54. package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
  55. package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
  56. package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
  57. package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
  58. package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
  59. package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
  60. package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
  61. package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
  62. package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
  63. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
  64. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
  65. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
  66. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
  67. package/examples/example-board/agent-instructions-cardlayout.md +28 -0
  68. package/examples/example-board/agent-instructions.md +603 -0
  69. package/examples/example-board/cards/card-concentration.json +42 -0
  70. package/examples/example-board/cards/card-market-prices.json +51 -0
  71. package/examples/example-board/cards/card-portfolio-action.json +19 -0
  72. package/examples/example-board/cards/card-portfolio-risks.json +19 -0
  73. package/examples/example-board/cards/card-portfolio-value.json +62 -0
  74. package/examples/example-board/cards/card-portfolio.json +44 -0
  75. package/examples/example-board/demo-chat-handler.js +373 -33
  76. package/examples/example-board/demo-server-config.json +0 -2
  77. package/examples/example-board/demo-server.js +46 -7
  78. package/examples/example-board/demo-shell-browser.html +75 -207
  79. package/examples/example-board/demo-shell-with-server.html +14 -9
  80. package/examples/example-board/demo-shell.html +1 -1
  81. package/examples/example-board/demo-task-executor.js +259 -41
  82. package/package.json +6 -2
  83. package/schema/live-cards.schema.json +20 -4
  84. package/browser/board-livegraph-runtime.js.map +0 -1
  85. package/examples/example-board/board.yaml +0 -23
  86. package/examples/example-board/bootstrap_payload.json +0 -1
  87. package/examples/example-board/cards/card-chain-region-alert.json +0 -39
  88. package/examples/example-board/cards/card-chain-region-totals.json +0 -26
  89. package/examples/example-board/cards/card-chain-top-region.json +0 -24
  90. package/examples/example-board/cards/card-ex-actions.json +0 -32
  91. package/examples/example-board/cards/card-ex-chart.json +0 -30
  92. package/examples/example-board/cards/card-ex-filter.json +0 -36
  93. package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
  94. package/examples/example-board/cards/card-ex-form.json +0 -91
  95. package/examples/example-board/cards/card-ex-list.json +0 -22
  96. package/examples/example-board/cards/card-ex-markdown.json +0 -17
  97. package/examples/example-board/cards/card-ex-metric.json +0 -19
  98. package/examples/example-board/cards/card-ex-narrative.json +0 -36
  99. package/examples/example-board/cards/card-ex-source-http.json +0 -28
  100. package/examples/example-board/cards/card-ex-source.json +0 -21
  101. package/examples/example-board/cards/card-ex-status.json +0 -35
  102. package/examples/example-board/cards/card-ex-table.json +0 -30
  103. package/examples/example-board/cards/card-ex-todo.json +0 -29
  104. package/examples/example-board/mock.db +0 -15
  105. package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
@@ -4,15 +4,29 @@ import http from 'node:http';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { createRequire } from 'node:module';
7
8
 
8
9
  import {
9
10
  createMultiBoardServerRuntime,
10
11
  createRuntimeRequestDispatcher,
11
12
  isRuntimeRoute,
12
- } from './reusable-server-runtime.js';
13
+ } from 'yaml-flow/board-livecards-server-runtime';
13
14
 
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
17
+ const _require = createRequire(import.meta.url);
18
+
19
+ function resolveYamlFlowDir() {
20
+ try {
21
+ return path.dirname(_require.resolve('yaml-flow/package.json'));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ const _yamlFlowDir = resolveYamlFlowDir();
28
+ const _pkgCliJs = _yamlFlowDir ? path.join(_yamlFlowDir, 'board-live-cards-cli.js') : null;
29
+ const _pkgStepMachineCli = _yamlFlowDir ? path.join(_yamlFlowDir, 'step-machine-cli.js') : null;
16
30
 
17
31
  function loadServerConfig() {
18
32
  const configPath = path.join(__dirname, 'demo-server-config.json');
@@ -32,10 +46,11 @@ function resolveFromConfig(configValue) {
32
46
  }
33
47
 
34
48
  const serverConfig = loadServerConfig();
35
- const configuredCliJs = resolveFromConfig(serverConfig.boardLiveCardsCliJs);
49
+ const configuredCliJs = resolveFromConfig(serverConfig.boardLiveCardsCliJs) || _pkgCliJs;
36
50
  const configuredTaskExecutorPath = resolveFromConfig(serverConfig.taskExecutorPath || serverConfig.demoTaskExecutorPath);
37
- const configuredStepMachineCliPath = resolveFromConfig(serverConfig.stepMachineCliPath);
51
+ const configuredStepMachineCliPath = resolveFromConfig(serverConfig.stepMachineCliPath) || _pkgStepMachineCli;
38
52
  const configuredChatHandlerPath = resolveFromConfig(serverConfig.chatHandlerPath);
53
+ const configuredInferenceAdapterPath = resolveFromConfig(serverConfig.inferenceAdapterPath);
39
54
 
40
55
  if (!process.env.BOARD_LIVE_CARDS_CLI_JS && configuredCliJs) {
41
56
  process.env.BOARD_LIVE_CARDS_CLI_JS = configuredCliJs;
@@ -46,8 +61,13 @@ if (!process.env.DEMO_STEP_MACHINE_CLI_PATH && configuredStepMachineCliPath) {
46
61
  if (!process.env.DEMO_CHAT_HANDLER_PATH && configuredChatHandlerPath) {
47
62
  process.env.DEMO_CHAT_HANDLER_PATH = configuredChatHandlerPath;
48
63
  }
64
+ if (!process.env.DEMO_INFERENCE_ADAPTER_PATH && configuredInferenceAdapterPath) {
65
+ process.env.DEMO_INFERENCE_ADAPTER_PATH = configuredInferenceAdapterPath;
66
+ }
49
67
 
50
68
  const PORT = Number(process.env.DEMO_SERVER_PORT || serverConfig.port || 7799);
69
+ const RESET_ON_START = process.argv.includes('--reset');
70
+
51
71
  const CORS_HEADERS = {
52
72
  'Access-Control-Allow-Origin': '*',
53
73
  'Access-Control-Allow-Headers': 'content-type,x-file-name',
@@ -56,12 +76,26 @@ const CORS_HEADERS = {
56
76
 
57
77
  const runtime = createMultiBoardServerRuntime({
58
78
  apiBasePath: '/api/boards',
79
+ defaultCardsDir: path.join(__dirname, 'cards'),
59
80
  defaultTaskExecutorPath: process.env.DEMO_TASK_EXECUTOR_PATH || configuredTaskExecutorPath || path.join(__dirname, 'demo-task-executor.js'),
60
81
  defaultStepMachineCliPath: process.env.DEMO_STEP_MACHINE_CLI_PATH || configuredStepMachineCliPath,
61
82
  defaultChatHandlerPath: process.env.DEMO_CHAT_HANDLER_PATH || configuredChatHandlerPath || path.join(__dirname, 'demo-chat-handler.js'),
83
+ defaultInferenceAdapterPath: process.env.DEMO_INFERENCE_ADAPTER_PATH || configuredInferenceAdapterPath || null,
62
84
  boardLiveCardsCliJs: process.env.BOARD_LIVE_CARDS_CLI_JS || configuredCliJs,
63
85
  });
64
86
 
87
+ function resetRuntime() {
88
+ const setupDir = runtime.setupDir;
89
+ if (fs.existsSync(setupDir)) {
90
+ fs.rmSync(setupDir, { recursive: true, force: true });
91
+ console.log(`[demo-server] reset: wiped ${setupDir}`);
92
+ }
93
+ }
94
+
95
+ if (RESET_ON_START) {
96
+ resetRuntime();
97
+ }
98
+
65
99
  const dispatch = createRuntimeRequestDispatcher(runtime);
66
100
 
67
101
  // Board-id segment regex: /api/boards/:boardId/...
@@ -74,11 +108,16 @@ function jsonReply(res, status, payload) {
74
108
  }
75
109
 
76
110
  async function handleDemoSetup(req, res, boardId) {
77
- const url = new URL(req.url, 'http://localhost');
78
- const reset = String(url.searchParams.get('reset') || '').toLowerCase() === 'true';
79
111
  try {
80
- const result = runtime.performDemoSetup(boardId, reset);
81
- jsonReply(res, 200, result);
112
+ const { service, boardRoot } = runtime.requireBoardService(boardId);
113
+ let setupPerformed = false;
114
+
115
+ if (!fs.existsSync(path.join(boardRoot, 'surface', 'tmp-cards'))) {
116
+ service.ensureDemoSetup();
117
+ setupPerformed = true;
118
+ }
119
+
120
+ jsonReply(res, 200, { ok: true, setupPerformed });
82
121
  } catch (err) {
83
122
  jsonReply(res, err.statusCode || 500, { error: String((err && err.message) || err) });
84
123
  }
@@ -6,10 +6,9 @@
6
6
  <title>Example Board Demo (Browser Runtime)</title>
7
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
8
  <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/card-compute.js" onerror="this.onerror=null;this.src='../../browser/card-compute.js';"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/live-cards.js" onerror="this.onerror=null;this.src='../../browser/live-cards.js';"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/yaml-flow@latest/browser/board-livegraph-runtime.js" onerror="this.onerror=null;this.src='../../browser/board-livegraph-runtime.js';"></script>
12
- <script src="./reusable-runtime-artifacts-adapter.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/card-compute.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/live-cards.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livegraph-engine.js"></script>
13
12
  </head>
14
13
  <body class="bg-light">
15
14
  <div class="container-fluid py-3">
@@ -63,50 +62,44 @@
63
62
  console.error(message);
64
63
  }
65
64
 
66
- // Get LocalStorageService from board-livegraph-runtime export
65
+ // Get exports from board-livegraph-engine
67
66
  const LocalStorageService = (window.BoardLiveGraph && window.BoardLiveGraph.LocalStorageService);
68
- if (!LocalStorageService) {
69
- failInit('LocalStorageService not loaded. Run "npm run build:browser" first.');
67
+ const buildLiveCardModelsFromArtifacts = (window.BoardLiveGraph && window.BoardLiveGraph.buildLiveCardModelsFromArtifacts);
68
+ const buildBrowserArtifactsFromRuntime = (window.BoardLiveGraph && window.BoardLiveGraph.buildBrowserArtifactsFromRuntime);
69
+ if (!LocalStorageService || !buildLiveCardModelsFromArtifacts || !buildBrowserArtifactsFromRuntime) {
70
+ failInit('board-livegraph-engine.js not loaded. Run "npm run build:browser" first.');
70
71
  return;
71
72
  }
72
73
 
73
74
  // ========================================================================
74
- // Demo Setup
75
+ // Demo Setup — Portfolio Domain
75
76
  // ========================================================================
76
77
  const CARD_FILES = [
77
- 'card-ex-source.json',
78
- 'card-ex-source-http.json',
79
- 'card-ex-filter.json',
80
- 'card-ex-metric.json',
81
- 'card-ex-list.json',
82
- 'card-ex-chart.json',
83
- 'card-ex-table.json',
84
- 'card-ex-status.json',
85
- 'card-ex-markdown.json',
86
- 'card-ex-narrative.json',
87
- 'card-ex-todo.json',
88
- 'card-ex-form.json',
89
- 'card-ex-filtered-by-preference.json',
90
- 'card-ex-actions.json',
91
- 'card-chain-region-totals.json',
92
- 'card-chain-top-region.json',
93
- 'card-chain-region-alert.json',
78
+ 'card-portfolio.json',
79
+ 'card-market-prices.json',
80
+ 'card-portfolio-value.json',
81
+ 'card-concentration.json',
82
+ 'card-portfolio-risks.json',
83
+ 'card-portfolio-action.json',
94
84
  ];
95
85
 
96
- const ORDER_SEED = [
97
- { id: 'ORD-1001', product: 'Widget A', quantity: 3, amount: 12400, region: 'North' },
98
- { id: 'ORD-1002', product: 'Widget B', quantity: 2, amount: 8700, region: 'South' },
99
- { id: 'ORD-1003', product: 'Widget A', quantity: 4, amount: 15200, region: 'East' },
100
- { id: 'ORD-1004', product: 'Widget C', quantity: 1, amount: 6300, region: 'West' },
101
- { id: 'ORD-1005', product: 'Widget B', quantity: 2, amount: 9100, region: 'North' },
102
- { id: 'ORD-1006', product: 'Widget C', quantity: 3, amount: 9800, region: 'South' },
103
- ];
104
-
105
- const PRICE_SEED = [
106
- { product: 'Widget A', price: 4133.33, currency: 'USD' },
107
- { product: 'Widget B', price: 4450.0, currency: 'USD' },
108
- { product: 'Widget C', price: 3266.67, currency: 'USD' },
109
- ];
86
+ const QUOTES_SEED = {
87
+ quoteResponse: {
88
+ result: [
89
+ { symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 180, regularMarketChange: 2.5, regularMarketChangePercent: 1.41 },
90
+ { symbol: 'MSFT', shortName: 'Microsoft', regularMarketPrice: 420, regularMarketChange: -1.2, regularMarketChangePercent: -0.28 },
91
+ { symbol: 'GOOGL', shortName: 'Alphabet', regularMarketPrice: 165, regularMarketChange: 0.8, regularMarketChangePercent: 0.49 },
92
+ { symbol: 'TSLA', shortName: 'Tesla', regularMarketPrice: 250, regularMarketChange: -5.0, regularMarketChangePercent: -1.96 },
93
+ ],
94
+ },
95
+ };
96
+
97
+ const MOCK_ANALYSIS = {
98
+ mix: '- AAPL dominates at 36%\n- Tech-heavy portfolio',
99
+ pnl: '- Best: MSFT +35%\n- Worst: GOOGL -41%',
100
+ risks: '- AAPL: earnings risk\n- TSLA: high volatility',
101
+ action: '- Trim GOOGL — below cost basis and deteriorating momentum',
102
+ };
110
103
 
111
104
  const clone = (x) => JSON.parse(JSON.stringify(x));
112
105
  const nowIso = () => new Date().toISOString();
@@ -135,10 +128,6 @@
135
128
  localStorage.setItem(mockFsKey(cardId, dirName), JSON.stringify(Array.isArray(items) ? items : []));
136
129
  }
137
130
 
138
- function clearMockDir(cardId, dirName) {
139
- localStorage.removeItem(mockFsKey(cardId, dirName));
140
- }
141
-
142
131
  function toFileMetadataOnly(entry) {
143
132
  if (!entry || typeof entry !== 'object') return null;
144
133
  return {
@@ -155,8 +144,7 @@
155
144
  const input = String(name || '').trim();
156
145
  if (!input) return 'upload.bin';
157
146
  const parts = input.split(/[/\\]/);
158
- const base = parts[parts.length - 1] || 'upload.bin';
159
- return base;
147
+ return parts[parts.length - 1] || 'upload.bin';
160
148
  }
161
149
 
162
150
  function normalizeStem(rawStem) {
@@ -209,13 +197,11 @@
209
197
  keepExt = '';
210
198
  stemBudget = maxLen - prefix.length;
211
199
  }
212
-
213
200
  const stem = stemNorm.slice(0, Math.max(1, stemBudget));
214
201
  let candidate = `${prefix}${stem}${keepExt}`;
215
202
  if (candidate.length > maxLen) {
216
203
  candidate = candidate.slice(0, maxLen).replace(/\.$/, '');
217
204
  }
218
-
219
205
  if (!names.includes(candidate)) return candidate;
220
206
  serial += 1;
221
207
  }
@@ -236,9 +222,7 @@
236
222
  }
237
223
 
238
224
  function readPersistedFileMetadata(cardId) {
239
- return readMockDir(cardId, 'files')
240
- .map(toFileMetadataOnly)
241
- .filter(Boolean);
225
+ return readMockDir(cardId, 'files').map(toFileMetadataOnly).filter(Boolean);
242
226
  }
243
227
 
244
228
  function readPersistedChatMessages(cardId) {
@@ -262,8 +246,7 @@
262
246
  let binary = '';
263
247
  const chunkSize = 0x8000;
264
248
  for (let i = 0; i < bytes.length; i += chunkSize) {
265
- const chunk = bytes.subarray(i, i + chunkSize);
266
- binary += String.fromCharCode.apply(null, chunk);
249
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize));
267
250
  }
268
251
  return btoa(binary);
269
252
  }
@@ -282,12 +265,8 @@
282
265
  path: `${cardId}/files/${storedName}`,
283
266
  uploaded_at: now,
284
267
  };
285
-
286
268
  const filesDir = readMockDir(cardId, 'files');
287
- filesDir.push({
288
- ...metadata,
289
- content_base64: await fileToBase64(fileLike),
290
- });
269
+ filesDir.push({ ...metadata, content_base64: await fileToBase64(fileLike) });
291
270
  writeMockDir(cardId, 'files', filesDir);
292
271
  return metadata;
293
272
  }
@@ -303,9 +282,8 @@
303
282
  path: fileLike.path || `${cardId}/files/${storedName}`,
304
283
  uploaded_at: fileLike.uploaded_at || now,
305
284
  };
306
-
307
285
  const filesDir = readMockDir(cardId, 'files');
308
- const knownPaths = new Set(filesDir.map((entry) => entry && entry.path).filter(Boolean));
286
+ const knownPaths = new Set(filesDir.map((e) => e && e.path).filter(Boolean));
309
287
  if (!knownPaths.has(metadata.path)) {
310
288
  filesDir.push(metadata);
311
289
  writeMockDir(cardId, 'files', filesDir);
@@ -313,32 +291,13 @@
313
291
  return metadata;
314
292
  }
315
293
 
316
- if (typeof fileLike === 'string') {
317
- const displayName = normalizeDisplayFileName(fileLike);
318
- const storedName = buildStoredFileName(cardId, displayName);
319
- const metadata = {
320
- name: displayName,
321
- stored_name: storedName,
322
- size: null,
323
- mime_type: 'application/octet-stream',
324
- path: `${cardId}/files/${storedName}`,
325
- uploaded_at: now,
326
- };
327
- const filesDir = readMockDir(cardId, 'files');
328
- filesDir.push(metadata);
329
- writeMockDir(cardId, 'files', filesDir);
330
- return metadata;
331
- }
332
-
333
294
  return null;
334
295
  }
335
296
 
336
297
  function appendChatRecord(cardId, record) {
337
298
  const chatsDir = readMockDir(cardId, 'chats');
338
299
  const next = { ...(record || {}) };
339
- if (!next.path) {
340
- next.path = nextChatPath(cardId, next.role || 'system');
341
- }
300
+ if (!next.path) next.path = nextChatPath(cardId, next.role || 'system');
342
301
  chatsDir.push(next);
343
302
  writeMockDir(cardId, 'chats', chatsDir);
344
303
  }
@@ -348,11 +307,9 @@
348
307
  if (!el) return;
349
308
 
350
309
  const tasks = runtimeState && runtimeState.state && runtimeState.state.tasks
351
- ? runtimeState.state.tasks
352
- : {};
310
+ ? runtimeState.state.tasks : {};
353
311
  const availableOutputs = runtimeState && runtimeState.state && Array.isArray(runtimeState.state.availableOutputs)
354
- ? runtimeState.state.availableOutputs
355
- : [];
312
+ ? runtimeState.state.availableOutputs : [];
356
313
 
357
314
  const byStatus = {};
358
315
  for (const task of Object.values(tasks)) {
@@ -361,38 +318,20 @@
361
318
  }
362
319
 
363
320
  const cardRuntimeById = artifacts && artifacts.cardRuntimeById ? artifacts.cardRuntimeById : {};
364
- const metric = cardRuntimeById['card-ex-metric'] && cardRuntimeById['card-ex-metric'].computed_values
365
- ? cardRuntimeById['card-ex-metric'].computed_values.totalRevenue
366
- : undefined;
367
- const topProducts = cardRuntimeById['card-ex-list'] && cardRuntimeById['card-ex-list'].computed_values
368
- ? cardRuntimeById['card-ex-list'].computed_values.topProducts
369
- : undefined;
370
- const regionCounts = cardRuntimeById['card-ex-chart'] && cardRuntimeById['card-ex-chart'].computed_values
371
- ? cardRuntimeById['card-ex-chart'].computed_values.regionCounts
372
- : undefined;
373
- const filteredOrders = cardRuntimeById['card-ex-table'] && cardRuntimeById['card-ex-table'].computed_values
374
- ? cardRuntimeById['card-ex-table'].computed_values.filtered
375
- : undefined;
376
-
377
- const watched = {
378
- orders: availableOutputs.includes('orders'),
379
- prices: availableOutputs.includes('prices'),
380
- selections: availableOutputs.includes('selections'),
381
- formToken: availableOutputs.includes('card-ex-form'),
382
- };
383
321
 
384
- const lines = [
322
+ const portfolioValue = cardRuntimeById['card-portfolio-value'] && cardRuntimeById['card-portfolio-value'].computed_values;
323
+ const totalValue = portfolioValue && portfolioValue.totalValue;
324
+ const totalGain = portfolioValue && portfolioValue.totalGain;
325
+ const positions = portfolioValue && portfolioValue.positions;
326
+
327
+ el.textContent = [
385
328
  `updatedAt: ${new Date().toISOString()}`,
386
329
  `statusCounts: ${JSON.stringify(byStatus)}`,
387
330
  `availableOutputs(${availableOutputs.length}): ${availableOutputs.slice().sort().join(', ')}`,
388
- `watchedTokens: ${JSON.stringify(watched)}`,
389
- `card-ex-metric.totalRevenue: ${JSON.stringify(metric)}`,
390
- `card-ex-list.topProducts.length: ${Array.isArray(topProducts) ? topProducts.length : 'n/a'}`,
391
- `card-ex-chart.regionCounts.length: ${Array.isArray(regionCounts) ? regionCounts.length : 'n/a'}`,
392
- `card-ex-table.filtered.length: ${Array.isArray(filteredOrders) ? filteredOrders.length : 'n/a'}`,
393
- ];
394
-
395
- el.textContent = lines.join('\n');
331
+ `card-portfolio-value.totalValue: ${JSON.stringify(totalValue)}`,
332
+ `card-portfolio-value.totalGain: ${JSON.stringify(totalGain)}`,
333
+ `card-portfolio-value.positions.length: ${Array.isArray(positions) ? positions.length : 'n/a'}`,
334
+ ].join('\n');
396
335
  }
397
336
 
398
337
  function hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById) {
@@ -403,44 +342,30 @@
403
342
  const artifact = cardRuntimeById[card.id];
404
343
  if (!artifact || typeof artifact !== 'object') return false;
405
344
  if (card.sources && card.sources.length > 0) {
406
- if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources))) {
407
- return false;
408
- }
345
+ if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources))) return false;
409
346
  }
410
347
  if (card.requires && card.requires.length > 0) {
411
- if (!(artifact.requires && typeof artifact.requires === 'object' && !Array.isArray(artifact.requires))) {
412
- return false;
413
- }
348
+ if (!(artifact.requires && typeof artifact.requires === 'object' && !Array.isArray(artifact.requires))) return false;
414
349
  }
415
350
  return true;
416
351
  });
417
352
  }
418
353
 
419
- function parseBoardMeta(yamlText) {
420
- const nameMatch = yamlText.match(/^name:\s*(.+)$/m);
421
- const descMatch = yamlText.match(/^desc:\s*(.+)$/m);
422
- return {
423
- name: nameMatch ? nameMatch[1].trim() : 'Example Board',
424
- desc: descMatch ? descMatch[1].trim() : '',
425
- };
426
- }
427
-
428
354
  async function loadBoardFromFiles() {
429
- const [boardYaml, ...cards] = await Promise.all([
430
- fetch('./board.yaml').then(r => r.text()),
431
- ...CARD_FILES.map(name => fetch(`./cards/${name}`).then(r => r.json())),
432
- ]);
355
+ const boardMeta = {
356
+ name: 'Portfolio Tracker',
357
+ desc: 'Track your stock portfolio with live market prices and AI-powered risk analysis.',
358
+ };
359
+ const cards = await Promise.all(
360
+ CARD_FILES.map(name => fetch(`./cards/${name}`).then(r => r.json()))
361
+ );
433
362
 
434
- const boardMeta = parseBoardMeta(boardYaml);
435
363
  for (const c of cards) {
436
364
  c.card_data = c.card_data || {};
437
365
  const persistedFiles = readPersistedFileMetadata(c.id);
438
366
  if (persistedFiles.length > 0) c.card_data.files = persistedFiles;
439
-
440
367
  const persistedChats = readPersistedChatMessages(c.id);
441
368
  if (persistedChats.length > 0) c.card_data.messages = persistedChats;
442
-
443
- // Persist card definition to localStorage (mirrors server's tmp/cards/<id>.json)
444
369
  LocalStorageService.writeCard(c.id, c);
445
370
  }
446
371
 
@@ -448,22 +373,19 @@
448
373
  }
449
374
 
450
375
  function makeMockSourceServer() {
451
- let orderRows = clone(ORDER_SEED);
452
- let priceRows = clone(PRICE_SEED);
376
+ let quoteData = clone(QUOTES_SEED);
377
+ let mockAnalysis = clone(MOCK_ANALYSIS);
453
378
 
454
379
  return {
455
380
  fetchSource: async function (card, sourceDef) {
456
- const mockKey = sourceDef && typeof sourceDef.mock === 'string' ? sourceDef.mock : '';
457
- const script = String(sourceDef && sourceDef.script || sourceDef && sourceDef.cli || '').toLowerCase();
458
- if (mockKey === 'orders') return clone(orderRows);
459
- if (mockKey === 'prices') return clone(priceRows);
460
- if (card.id === 'card-ex-source' || script.includes('fetch-orders')) return clone(orderRows);
461
- if (card.id === 'card-ex-source-http' || script.includes('fetch-prices')) return clone(priceRows);
381
+ // chartApi source with mock="quotes" return mock quote data
382
+ if (sourceDef && sourceDef.mock === 'quotes') return clone(quoteData);
383
+ // copilot source return mock analysis object
384
+ if (sourceDef && sourceDef.copilot) return clone(mockAnalysis);
462
385
  return null;
463
386
  },
464
- setDatasets: function (nextOrders, nextPrices) {
465
- if (Array.isArray(nextOrders)) orderRows = clone(nextOrders);
466
- if (Array.isArray(nextPrices)) priceRows = clone(nextPrices);
387
+ setQuotes: function (nextQuotes) {
388
+ if (nextQuotes && typeof nextQuotes === 'object') quoteData = clone(nextQuotes);
467
389
  },
468
390
  };
469
391
  }
@@ -498,24 +420,10 @@
498
420
  return `${hh}:${mm}:${ss}`;
499
421
  }
500
422
 
501
- function compactFiles(files) {
502
- if (!Array.isArray(files)) return [];
503
- return files
504
- .map((f) => {
505
- if (!f) return null;
506
- if (typeof f === 'string') return { name: f };
507
- if (typeof f === 'object' && typeof f.name === 'string') return { name: f.name, size: f.size || null };
508
- return null;
509
- })
510
- .filter(Boolean);
511
- }
512
-
513
423
  async function handleActionInBrowserRuntime(id, actionType, payload) {
514
424
  const node = nodesById[id];
515
425
  if (!node) return;
516
-
517
426
  const now = nowIso();
518
- const cardData = node.card_data || {};
519
427
 
520
428
  if (actionType === 'chat-send') {
521
429
  const text = payload && typeof payload.text === 'string' ? payload.text.trim() : '';
@@ -528,25 +436,18 @@
528
436
  if (persisted) fileMetas.push(persisted);
529
437
  }
530
438
 
531
- appendChatRecord(id, {
532
- at: now,
533
- role: 'user',
534
- text,
535
- files: fileMetas,
536
- });
439
+ appendChatRecord(id, { at: now, role: 'user', text, files: fileMetas });
537
440
 
538
441
  fileMetas.forEach((fileMeta) => {
539
442
  if (!fileMeta || !fileMeta.stored_name) return;
540
- const display = fileMeta.name || 'file';
541
443
  appendChatRecord(id, {
542
444
  at: now,
543
445
  role: 'system',
544
- text: `File ${display} uploaded as ${fileMeta.stored_name}.`,
446
+ text: `File ${fileMeta.name || 'file'} uploaded as ${fileMeta.stored_name}.`,
545
447
  files: [],
546
448
  });
547
449
  });
548
450
 
549
- // Keep chat render state in sync with local-storage-backed chat entries.
550
451
  runtime.patchCardState(id, {
551
452
  messages: readPersistedChatMessages(id),
552
453
  lastInteractionAt: now,
@@ -557,7 +458,6 @@
557
458
  if (actionType === 'action') {
558
459
  const buttonId = payload && typeof payload.buttonId === 'string' ? payload.buttonId : '';
559
460
  if (!buttonId) return;
560
-
561
461
  runtime.patchCardState(id, {
562
462
  lastAction: { buttonId, at: now },
563
463
  lastActionText: `${buttonId} @ ${toIsoTimeTag(now)}`,
@@ -601,6 +501,7 @@
601
501
 
602
502
  const cardDefinitions = loaded.cards;
603
503
  const cardDefsById = Object.fromEntries(cardDefinitions.map(c => [c.id, c]));
504
+
604
505
  const deepSet = (obj, path, value) => {
605
506
  if (!path) return;
606
507
  const parts = String(path).split('.');
@@ -624,11 +525,10 @@
624
525
  },
625
526
  });
626
527
 
627
- // Restore previous runtime artifacts from localStorage (mirrors reading runtime-out/ from disk on server)
528
+ // Restore previous runtime artifacts from localStorage
628
529
  const cardRuntimeById = LocalStorageService.readAllComputedArtifacts(cardDefinitions.map(c => c.id));
629
530
  let statusSnapshot = LocalStorageService.readStatusSnapshot();
630
531
 
631
- // Build initial renderable nodes from artifacts (same pattern as server shell)
632
532
  const initialArtifacts = statusSnapshot && Object.keys(cardRuntimeById).length > 0 && hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById)
633
533
  ? { cardDefinitions, statusSnapshot, cardRuntimeById }
634
534
  : buildBrowserArtifactsFromRuntime({
@@ -644,8 +544,6 @@
644
544
  const engine = LiveCard.init({
645
545
  resolve: (id) => nodesById[id],
646
546
  onPatchState: (id, patch) => {
647
- // Form submissions pass { fieldValues: {...} } from LiveCard form elements.
648
- // Persist those values in localStorage card at the card's writeTo path.
649
547
  if (patch && Object.keys(patch).length === 1 && patch.fieldValues) {
650
548
  const cardDef = cardDefsById[id];
651
549
  let writeTo = null;
@@ -657,43 +555,21 @@
657
555
  }
658
556
  }
659
557
  }
660
-
661
558
  const statePatch = {};
662
559
  if (typeof writeTo === 'string' && writeTo.startsWith('card_data.')) {
663
560
  deepSet(statePatch, writeTo.slice('card_data.'.length), patch.fieldValues);
664
561
  } else {
665
562
  Object.assign(statePatch, patch.fieldValues);
666
563
  }
667
-
668
- // Apply patch to card definition in memory and in localStorage
669
564
  const cardInMem = cardDefsById[id];
670
565
  if (cardInMem) {
671
566
  deepSet(cardInMem, 'card_data', { ...cardInMem.card_data, ...statePatch });
672
567
  LocalStorageService.writeCard(id, cardInMem);
673
568
  }
674
-
675
- // Also patch the runtime (which may be used by compute)
676
569
  runtime.patchCardState(id, statePatch);
677
570
  return;
678
571
  }
679
572
 
680
- // Auto-commit: file-upload element reports staged files via _stagedFiles.
681
- // Persist metadata immediately and update the rendered list — no commit button needed.
682
- if (patch && Array.isArray(patch._stagedFiles) && patch._stagedFiles.length > 0) {
683
- const now = nowIso();
684
- const existing = readPersistedFileMetadata(id);
685
- const seen = new Set(existing.map((f) => f && f.name ? f.name : String(f)));
686
- for (const item of patch._stagedFiles) {
687
- if (!item || typeof item.name !== 'string') continue;
688
- if (seen.has(item.name)) continue;
689
- existing.push(persistStagedFileMetadata(id, item));
690
- seen.add(item.name);
691
- }
692
- writeStoredFiles(id, existing);
693
- runtime.patchCardState(id, { files: existing, _stagedFiles: [] });
694
- return;
695
- }
696
-
697
573
  runtime.patchCardState(id, patch || {});
698
574
  },
699
575
  onRefresh: (id) => runtime.retrigger(id),
@@ -701,8 +577,7 @@
701
577
  void handleActionInBrowserRuntime(id, actionType, payload || {});
702
578
  },
703
579
  getChatMessages: (id) => {
704
- const items = readPersistedChatMessages(id);
705
- return items.map((m) => ({
580
+ return readPersistedChatMessages(id).map((m) => ({
706
581
  role: m && typeof m.role === 'string' ? m.role : 'system',
707
582
  text: m && typeof m.text === 'string' ? m.text : '',
708
583
  files: Array.isArray(m && m.files) ? m.files : [],
@@ -718,7 +593,6 @@
718
593
 
719
594
  runtimeUnsub = runtime.subscribe(function () {
720
595
  const runtimeState = runtime.getState();
721
- // Build runtime artifacts (mirrors what CLI does after compute)
722
596
  const artifacts = buildBrowserArtifactsFromRuntime({
723
597
  boardPath: 'browser',
724
598
  cardDefinitions,
@@ -726,28 +600,23 @@
726
600
  graphState: runtimeState,
727
601
  });
728
602
 
729
- // Persist to localStorage (mirrors CLI writing to runtime-out/)
730
- for (const [id, artifact] of Object.entries(artifacts.cardRuntimeById)) {
603
+ for (const [, artifact] of Object.entries(artifacts.cardRuntimeById)) {
731
604
  LocalStorageService.writeComputedArtifact(artifact);
732
605
  }
733
606
  LocalStorageService.writeStatusSnapshot(artifacts.statusSnapshot);
734
607
 
735
- // Rebuild renderable nodes from persisted artifacts (same pattern as server shell)
736
608
  syncBoardNodes(buildLiveCardModelsFromArtifacts(artifacts));
737
-
738
- // Keep a compact runtime snapshot visible for debugging token flow issues.
739
609
  renderDebugPanel(runtimeState, artifacts);
740
610
  });
741
611
 
742
612
  runtime.push({ type: 'inject-tokens', tokens: [], timestamp: nowIso() });
743
- // Force a full recompute pass to recover from stale/incomplete localStorage artifacts.
744
613
  runtime.retriggerAll();
745
614
 
746
615
  window.demoLiveGraph = {
747
616
  mode: 'browser',
748
617
  runtime,
749
- setDatasets: function (nextOrders, nextPrices) {
750
- mockServer.setDatasets(nextOrders, nextPrices);
618
+ setQuotes: function (nextQuotes) {
619
+ mockServer.setQuotes(nextQuotes);
751
620
  runtime.retriggerAll();
752
621
  },
753
622
  clearLocalStorage: function () {
@@ -786,7 +655,6 @@
786
655
  setDebugPanelVisibility(this.checked);
787
656
  });
788
657
 
789
- // Keep debug UI out of the way unless Dev Mode is explicitly enabled.
790
658
  setDebugPanelVisibility(document.getElementById('devModeToggle').checked);
791
659
  }
792
660