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
@@ -0,0 +1,774 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Example Board Demo (LocalStorage Runtime)</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg" />
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
9
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@7.0.0/browser/compute-jsonata.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/leader-line/leader-line.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@7.0.0/browser/live-cards.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/yaml-flow@7.0.0/browser/board-livecards-localstorage.js"></script>
16
+ </head>
17
+ <body class="bg-light">
18
+ <div class="container-fluid py-3">
19
+ <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
20
+ <div>
21
+ <h1 class="h4 mb-0" id="boardTitle">Example Board (LocalStorage Runtime)</h1>
22
+ <div class="small text-muted" id="boardDesc"></div>
23
+ </div>
24
+ <div class="d-flex align-items-center gap-2">
25
+ <button class="btn btn-sm btn-outline-primary" id="modeBoard">Board</button>
26
+ <button class="btn btn-sm btn-outline-primary" id="modeCanvas">Canvas</button>
27
+ <button class="btn btn-sm btn-outline-secondary" id="autoLayout">Auto Layout</button>
28
+ <button class="btn btn-sm btn-outline-danger" id="resetDemo">Reset Demo</button>
29
+ <div class="form-check ms-2">
30
+ <input class="form-check-input" type="checkbox" id="devModeToggle" checked />
31
+ <label class="form-check-label" for="devModeToggle">Dev Mode</label>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="alert alert-info small py-2 mb-3">
37
+ LocalStorage runtime mode: board state persisted in browser localStorage via
38
+ <code>board-livecards-localstorage</code> + <code>server-runtime</code>.
39
+ Source fetches are done by a demo task executor in this HTML using source_def url/url-list and
40
+ reporting results via <code>reportSourceFetched</code>.
41
+ </div>
42
+
43
+ <div id="boardRoot">
44
+ <div class="d-flex align-items-center justify-content-center" style="height: 72vh;">
45
+ <div class="text-center">
46
+ <div class="spinner-border mb-3" role="status">
47
+ <span class="visually-hidden">Loading...</span>
48
+ </div>
49
+ <p class="text-muted">Initializing board...</p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <script>
56
+ (function () {
57
+ 'use strict';
58
+
59
+ var buildBoardState = window.BoardLiveCardsLocalStorage && window.BoardLiveCardsLocalStorage.buildBoardState;
60
+ var applyNotification = window.BoardLiveCardsLocalStorage && window.BoardLiveCardsLocalStorage.applyNotification;
61
+ var createBoardLiveCardsLocalStorage = window.BoardLiveCardsLocalStorage && window.BoardLiveCardsLocalStorage.create;
62
+
63
+ if (!buildBoardState || !applyNotification || !createBoardLiveCardsLocalStorage) {
64
+ document.getElementById('boardRoot').innerHTML =
65
+ '<div class="alert alert-danger">Required scripts not loaded. Run <code>npm run build:browser</code> first.</div>';
66
+ return;
67
+ }
68
+
69
+ var NAMESPACE = 'demo-board';
70
+ var currentMode = 'canvas';
71
+ var board = null;
72
+ var stateRef = { current: null }; // read accessor for resolve(); board.setState owns the state
73
+ var app = null;
74
+ var pendingNotifications = []; // buffer for notifications arriving before board is ready
75
+
76
+ function clone(x) { return JSON.parse(JSON.stringify(x)); }
77
+ function nowIso() { return new Date().toISOString(); }
78
+
79
+ var INLINE_CARDS = [
80
+ {
81
+ id: 'card-portfolio',
82
+ meta: {
83
+ title: 'My Portfolio',
84
+ tags: ['portfolio'],
85
+ desc: 'Manage your holdings inline. Changes propagate downstream.',
86
+ },
87
+ provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
88
+ compute: [],
89
+ view: {
90
+ elements: [
91
+ {
92
+ kind: 'editable-table',
93
+ label: 'Holdings',
94
+ data: {
95
+ bind: 'card_data.holdings',
96
+ writeTo: 'card_data.holdings',
97
+ columns: ['ticker', 'quantity', 'cost_basis'],
98
+ schema: { properties: { quantity: { type: 'number' }, cost_basis: { type: 'number' } } },
99
+ },
100
+ },
101
+ ],
102
+ layout: { board: { col: 4, order: 1 }, canvas: { x: 20, y: 50, w: 340, h: 330 } },
103
+ features: { refresh: true, chat: true },
104
+ },
105
+ card_data: {
106
+ holdings: [
107
+ { ticker: 'AAPL', quantity: 10, cost_basis: 150 },
108
+ { ticker: 'MSFT', quantity: 5, cost_basis: 310 },
109
+ { ticker: 'GOOGL', quantity: 2, cost_basis: 280 },
110
+ { ticker: 'TSLA', quantity: 3, cost_basis: 200 },
111
+ ],
112
+ },
113
+ },
114
+ {
115
+ id: 'card-market-prices',
116
+ meta: {
117
+ title: 'Market Prices',
118
+ tags: ['prices', 'market'],
119
+ desc: 'Fetches prices from Yahoo Finance v8 API using source_def url-list.',
120
+ },
121
+ requires: ['holdings'],
122
+ source_defs: [
123
+ {
124
+ bindTo: 'quotes',
125
+ outputFile: 'market-prices-quotes.json',
126
+ projections: {
127
+ url_list: "requires.holdings.ticker.('https://query1.finance.yahoo.com/v8/finance/chart/' & $ & '?interval=1d&range=1d')",
128
+ },
129
+ 'url-list': {
130
+ headers: { 'User-Agent': 'Mozilla/5.0 (compatible; portfolio-tracker-demo/1.0)' },
131
+ cacheTimeout: 300,
132
+ },
133
+ },
134
+ ],
135
+ compute: [
136
+ {
137
+ bindTo: 'normalizedQuotes',
138
+ expr: '{ "quoteResponse": { "result": $map(fetched_sources.quotes, function($r) { ($m := $r.chart.result[0].meta; $prev := $m.chartPreviousClose; $chg := $m.regularMarketPrice - $prev; { "symbol": $m.symbol, "shortName": $m.shortName ? $m.shortName : $m.longName, "regularMarketPrice": $m.regularMarketPrice, "regularMarketChange": $chg, "regularMarketChangePercent": $chg / $prev * 100 }) }), "error": null } }',
139
+ },
140
+ {
141
+ bindTo: 'prices',
142
+ expr: '$map(computed_values.normalizedQuotes.quoteResponse.result, function($q) { {"ticker": $q.symbol, "name": $q.shortName, "price": $round($q.regularMarketPrice, 2), "change": $round($q.regularMarketChange, 2), "chg_pct": $round($q.regularMarketChangePercent, 2)} })',
143
+ },
144
+ ],
145
+ provides: [{ bindTo: 'quotes', ref: 'computed_values.normalizedQuotes' }],
146
+ view: {
147
+ elements: [
148
+ {
149
+ kind: 'table',
150
+ label: 'Prices',
151
+ data: { bind: 'computed_values.prices', columns: ['ticker', 'name', 'price', 'change', 'chg_pct'], sortable: true },
152
+ },
153
+ ],
154
+ layout: { board: { col: 4, order: 2 }, canvas: { x: 400, y: 50, w: 400, h: 340 } },
155
+ features: { refresh: true },
156
+ },
157
+ card_data: {},
158
+ },
159
+ {
160
+ id: 'card-portfolio-value',
161
+ meta: {
162
+ title: 'Portfolio Value',
163
+ tags: ['portfolio', 'value'],
164
+ desc: 'Calculates value and P&L from holdings + quotes.',
165
+ },
166
+ requires: ['holdings', 'quotes'],
167
+ provides: [{ bindTo: 'positions', ref: 'computed_values.positions' }],
168
+ compute: [
169
+ {
170
+ bindTo: 'positions',
171
+ expr: '($hMap := $merge(requires.holdings.{ticker: $}); $map(requires.quotes.quoteResponse.result, function($q) { ($h := $lookup($hMap, $q.symbol); $qty := $h.quantity; $cb := $h.cost_basis; $val := $round($q.regularMarketPrice * $qty, 2); $cost := $round($cb * $qty, 2); $chg := $round($q.regularMarketChange * $qty, 2); {"ticker": $q.symbol, "quantity": $qty, "cost_basis": $cb, "price": $round($q.regularMarketPrice, 2), "value": $val, "gain_$": $round($val - $cost, 2), "gain_%": $round(($val - $cost) / $cost * 100, 2), "chg_$": $chg, "chg_pct": $round($q.regularMarketChangePercent, 2)}) }))',
172
+ },
173
+ {
174
+ bindTo: 'totalValue',
175
+ expr: '$round($sum(computed_values.positions.value), 2)',
176
+ },
177
+ {
178
+ bindTo: 'gainers',
179
+ expr: '$count($filter(computed_values.positions, function($p){ $p.chg_pct > 0 }))',
180
+ },
181
+ {
182
+ bindTo: 'losers',
183
+ expr: '$count($filter(computed_values.positions, function($p){ $p.chg_pct < 0 }))',
184
+ },
185
+ {
186
+ bindTo: 'gainersLosers',
187
+ expr: "computed_values.gainers & ' up · ' & computed_values.losers & ' down'",
188
+ },
189
+ ],
190
+ view: {
191
+ elements: [
192
+ { kind: 'metric', label: 'Portfolio Value ($)', data: { bind: 'computed_values.totalValue' } },
193
+ { kind: 'text', data: { bind: 'computed_values.gainersLosers' } },
194
+ { kind: 'table', label: 'Positions', data: { bind: 'computed_values.positions', columns: ['ticker', 'value', 'gain_$', 'gain_%'], sortable: true } },
195
+ ],
196
+ layout: { board: { col: 4, order: 3 }, canvas: { x: 840, y: 50, w: 420, h: 380 } },
197
+ features: { chat: true },
198
+ },
199
+ card_data: {},
200
+ },
201
+ {
202
+ id: 'card-portfolio-intelligence',
203
+ meta: {
204
+ title: 'Portfolio Intelligence',
205
+ tags: ['portfolio', 'analysis'],
206
+ desc: 'Derived analysis from computed positions.',
207
+ },
208
+ requires: ['positions'],
209
+ source_defs: [
210
+ {
211
+ bindTo: 'analysis',
212
+ outputFile: 'card-concentration-analysis.json',
213
+ kind: 'mock-llm-random',
214
+ prompt: 'Analyze this portfolio JSON and provide concise mix, pnl, risks, and action insights: {{positions_json}}',
215
+ projections: {
216
+ positions_json: 'requires.positions',
217
+ },
218
+ },
219
+ ],
220
+ compute: [
221
+ { bindTo: 'intel', expr: 'fetched_sources.analysis.intel ? fetched_sources.analysis.intel : (fetched_sources.analysis.mix ? fetched_sources.analysis.mix : fetched_sources.analysis.response)' },
222
+ { bindTo: 'risk_signal', expr: 'fetched_sources.analysis.risk_signal ? fetched_sources.analysis.risk_signal : fetched_sources.analysis.risks' },
223
+ { bindTo: 'action_signal', expr: 'fetched_sources.analysis.action_signal ? fetched_sources.analysis.action_signal : (fetched_sources.analysis.action ? fetched_sources.analysis.action : fetched_sources.analysis.response)' },
224
+ ],
225
+ provides: [
226
+ { bindTo: 'intel', ref: 'computed_values.intel' },
227
+ { bindTo: 'risk_signal', ref: 'computed_values.risk_signal' },
228
+ { bindTo: 'action_signal', ref: 'computed_values.action_signal' },
229
+ ],
230
+ view: {
231
+ elements: [
232
+ { kind: 'markdown', label: 'Intel', data: { bind: 'computed_values.intel' } },
233
+ ],
234
+ layout: { board: { col: 3, order: 4 }, canvas: { x: 1300, y: 50, w: 340, h: 200 } },
235
+ features: { refresh: true, chat: true },
236
+ },
237
+ card_data: {},
238
+ },
239
+ {
240
+ id: 'card-portfolio-risks',
241
+ meta: { title: 'Risk Watch', tags: ['portfolio', 'risk'], desc: 'Risk signals from Portfolio Intelligence.' },
242
+ requires: ['risk_signal'],
243
+ view: {
244
+ elements: [{ kind: 'markdown', data: { bind: 'requires.risk_signal' } }],
245
+ layout: { board: { col: 3, order: 5 }, canvas: { x: 1300, y: 270, w: 340, h: 220 } },
246
+ },
247
+ card_data: {},
248
+ },
249
+ {
250
+ id: 'card-portfolio-action',
251
+ meta: { title: 'Action Signal', tags: ['portfolio', 'action'], desc: 'Action recommendation from Portfolio Intelligence.' },
252
+ requires: ['action_signal'],
253
+ view: {
254
+ elements: [{ kind: 'markdown', data: { bind: 'requires.action_signal' } }],
255
+ layout: { board: { col: 4, order: 6 }, canvas: { x: 1660, y: 50, w: 300, h: 160 } },
256
+ },
257
+ card_data: {},
258
+ },
259
+ ];
260
+
261
+ function hasPersistedBoardState() {
262
+ var key = NAMESPACE + ':card-store:kv:_index';
263
+ try { return localStorage.getItem(key) !== null; } catch { return false; }
264
+ }
265
+
266
+ // ── Task executor helpers (protocol-native) ─────────────────────────────
267
+
268
+ var TASK_CACHE_KEY = NAMESPACE + ':task-executor-cache';
269
+
270
+ function readTaskCache() {
271
+ try {
272
+ var raw = localStorage.getItem(TASK_CACHE_KEY);
273
+ return raw ? JSON.parse(raw) : {};
274
+ } catch (_) { return {}; }
275
+ }
276
+
277
+ function writeTaskCache(cache) {
278
+ try { localStorage.setItem(TASK_CACHE_KEY, JSON.stringify(cache)); } catch (_) {}
279
+ }
280
+
281
+ var TASK_EXECUTOR_CAPABILITIES = {
282
+ version: '1.0',
283
+ executor: 'portfolio-tracker-browser-task-executor',
284
+ subcommands: ['run-source-fetch', 'describe-capabilities'],
285
+ sourceKinds: {
286
+ 'mock-llm-random': {
287
+ description: 'Return a pre-cooked LLM-like response after a random delay based on source_def.prompt.',
288
+ inputSchema: {
289
+ kind: { type: 'string', required: true, description: 'Must be "mock-llm-random".' },
290
+ prompt: { type: 'string', required: true, description: 'Prompt text used to pick a pre-cooked response.' },
291
+ bindTo: { type: 'string', required: true, description: 'Output token for fetched source.' },
292
+ outputFile: { type: 'string', required: false, description: 'Optional output artifact filename.' },
293
+ },
294
+ outputShape: '{ response: string, latency_ms: number, prompt_echo: string, intel?: string, risk_signal?: string, action_signal?: string }',
295
+ },
296
+ mock: {
297
+ description: 'Return static in-page mock data by source_def.mock key.',
298
+ inputSchema: {
299
+ mock: { type: 'string', required: true, description: 'Key in in-page MOCK_DB.' },
300
+ bindTo: { type: 'string', required: true, description: 'Output token for fetched source.' },
301
+ outputFile: { type: 'string', required: false, description: 'Optional output artifact filename.' },
302
+ },
303
+ outputShape: 'any JSON value from MOCK_DB[mock].',
304
+ },
305
+ url: {
306
+ description: 'Fetch JSON from a single URL described by source_def.url.url.',
307
+ inputSchema: {
308
+ url: { type: 'object', required: true, description: 'Object containing url string and optional headers/args.' },
309
+ bindTo: { type: 'string', required: true, description: 'Output token for fetched source.' },
310
+ outputFile: { type: 'string', required: false, description: 'Optional output artifact filename.' },
311
+ },
312
+ outputShape: 'JSON object from fetched URL.',
313
+ },
314
+ 'url-list': {
315
+ description: 'Fetch JSON from each resolved URL in source_def._projections.url_list.',
316
+ inputSchema: {
317
+ 'url-list': { type: 'object', required: true, description: 'Object with optional request headers.' },
318
+ bindTo: { type: 'string', required: true, description: 'Output token for fetched source.' },
319
+ outputFile: { type: 'string', required: false, description: 'Optional output artifact filename.' },
320
+ _projections: { type: 'object', required: true, description: 'Must include resolved url_list array.' },
321
+ },
322
+ outputShape: 'JSON array, one element per fetched URL.',
323
+ },
324
+ },
325
+ };
326
+
327
+ var MOCK_DB = {
328
+ analysis: {
329
+ intel: '- Local demo mode: using mock analysis source.\n- Provide a custom executor for copilot/workiq in CLI hosts.',
330
+ risk_signal: '- TSLA: elevated volatility.\n- Portfolio concentration is moderate in tech names.',
331
+ action_signal: '- Hold positions; rebalance only if single-name concentration crosses 35%.',
332
+ // Back-compat aliases for persisted old card definitions / cached payloads.
333
+ mix: '- Local demo mode: using mock analysis source.\n- Provide a custom executor for copilot/workiq in CLI hosts.',
334
+ risks: '- TSLA: elevated volatility.\n- Portfolio concentration is moderate in tech names.',
335
+ action: '- Hold positions; rebalance only if single-name concentration crosses 35%.',
336
+ },
337
+ };
338
+
339
+ function interpolateTemplate(template, values) {
340
+ return String(template || '').replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, function (_m, key) {
341
+ var v = values && Object.prototype.hasOwnProperty.call(values, key) ? values[key] : '';
342
+ if (v === null || v === undefined) return '';
343
+ if (typeof v === 'object') {
344
+ try { return JSON.stringify(v); } catch (_e) { return String(v); }
345
+ }
346
+ return String(v);
347
+ });
348
+ }
349
+
350
+ function resolveUrlsForSourceDef(sourceDef) {
351
+ if (sourceDef.url && sourceDef.url.url) {
352
+ var context = Object.assign({}, sourceDef._projections || {}, (sourceDef.url.args || {}));
353
+ return [interpolateTemplate(sourceDef.url.url, context)];
354
+ }
355
+ if (sourceDef['url-list']) {
356
+ return Array.isArray(sourceDef._projections && sourceDef._projections.url_list)
357
+ ? sourceDef._projections.url_list
358
+ : [];
359
+ }
360
+ return [];
361
+ }
362
+
363
+ var DEFAULT_QUOTES = {
364
+ AAPL: { symbol: 'AAPL', longName: 'Apple Inc.', regularMarketPrice: 290, regularMarketChange: 0.8, regularMarketChangePercent: 0.28 },
365
+ MSFT: { symbol: 'MSFT', longName: 'Microsoft Corp.', regularMarketPrice: 420, regularMarketChange: -0.6, regularMarketChangePercent: -0.14 },
366
+ GOOGL: { symbol: 'GOOGL', longName: 'Alphabet Inc.', regularMarketPrice: 395, regularMarketChange: 1.2, regularMarketChangePercent: 0.30 },
367
+ TSLA: { symbol: 'TSLA', longName: 'Tesla Inc.', regularMarketPrice: 399, regularMarketChange: -2.1, regularMarketChangePercent: -0.52 },
368
+ };
369
+
370
+ function buildDefaultYahooQuoteResponse(url) {
371
+ var m = String(url || '').match(/\/chart\/([^?]+)/);
372
+ var symbol = m ? decodeURIComponent(m[1]).toUpperCase() : 'UNKNOWN';
373
+ var quote = DEFAULT_QUOTES[symbol] || {
374
+ symbol: symbol,
375
+ longName: symbol,
376
+ regularMarketPrice: 150,
377
+ regularMarketChange: 0,
378
+ regularMarketChangePercent: 0,
379
+ };
380
+ return {
381
+ chart: {
382
+ result: [{ meta: quote }],
383
+ error: null,
384
+ },
385
+ };
386
+ }
387
+
388
+ function buildSyntheticYahooQuoteResponse(url) {
389
+ var m = String(url || '').match(/\/chart\/([^?]+)/);
390
+ var symbol = m ? decodeURIComponent(m[1]).toUpperCase() : 'UNKNOWN';
391
+ var seed = symbol.split('').reduce(function (s, ch) { return s + ch.charCodeAt(0); }, 0);
392
+ var price = 90 + (seed % 260);
393
+ var change = (((seed % 21) - 10) / 10);
394
+ var changePct = price ? (change / price) * 100 : 0;
395
+ return {
396
+ chart: {
397
+ result: [{
398
+ meta: {
399
+ symbol: symbol,
400
+ longName: symbol,
401
+ regularMarketPrice: Number(price.toFixed(2)),
402
+ regularMarketChange: Number(change.toFixed(2)),
403
+ regularMarketChangePercent: Number(changePct.toFixed(2)),
404
+ },
405
+ }],
406
+ error: null,
407
+ },
408
+ };
409
+ }
410
+
411
+ function hasValidYahooMeta(payload) {
412
+ return !!(
413
+ payload
414
+ && payload.chart
415
+ && Array.isArray(payload.chart.result)
416
+ && payload.chart.result[0]
417
+ && payload.chart.result[0].meta
418
+ && typeof payload.chart.result[0].meta.symbol === 'string'
419
+ );
420
+ }
421
+
422
+ function normalizeYahooQuotePayload(url, payload) {
423
+ return hasValidYahooMeta(payload) ? payload : buildDefaultYahooQuoteResponse(url);
424
+ }
425
+
426
+ function normalizeYahooQuoteList(urls, list) {
427
+ var src = Array.isArray(list) ? list : [];
428
+ return urls.map(function (u, idx) {
429
+ return normalizeYahooQuotePayload(u, src[idx]);
430
+ });
431
+ }
432
+
433
+ function pickRandomItem(arr) {
434
+ if (!Array.isArray(arr) || arr.length === 0) return '';
435
+ return arr[Math.floor(Math.random() * arr.length)];
436
+ }
437
+
438
+ function resolveMockLlmResponse(prompt) {
439
+ var p = String(prompt || '').toLowerCase();
440
+ if (p.indexOf('portfolio') >= 0 || p.indexOf('holding') >= 0 || p.indexOf('ticker') >= 0) {
441
+ return pickRandomItem([
442
+ 'Portfolio summary: concentration is moderate; rebalance only if a single position exceeds 35%.',
443
+ 'Portfolio insight: momentum is mixed today, with gains led by high-beta positions.',
444
+ 'Portfolio note: maintain allocation discipline and review downside risk before adding exposure.',
445
+ ]);
446
+ }
447
+ if (p.indexOf('risk') >= 0 || p.indexOf('volatility') >= 0 || p.indexOf('drawdown') >= 0) {
448
+ return pickRandomItem([
449
+ 'Risk view: near-term volatility is elevated; consider tighter stop-loss thresholds.',
450
+ 'Risk view: correlation risk is rising; diversify across non-overlapping factors.',
451
+ 'Risk view: downside tails are widening; reduce leverage and preserve optionality.',
452
+ ]);
453
+ }
454
+ if (p.indexOf('action') >= 0 || p.indexOf('recommend') >= 0 || p.indexOf('next step') >= 0) {
455
+ return pickRandomItem([
456
+ 'Suggested action: hold current positions and reassess after the next session close.',
457
+ 'Suggested action: trim top concentration by 5% and rotate into lower-volatility names.',
458
+ 'Suggested action: stage entries over multiple intervals instead of a single fill.',
459
+ ]);
460
+ }
461
+ return pickRandomItem([
462
+ 'Mock LLM response: scenario remains neutral with balanced upside and downside signals.',
463
+ 'Mock LLM response: wait for confirmation before changing allocation materially.',
464
+ 'Mock LLM response: maintain risk controls and monitor regime shift indicators.',
465
+ ]);
466
+ }
467
+
468
+ function buildMockLlmAnalysis(prompt) {
469
+ var p = String(prompt || '').toLowerCase();
470
+ if (p.indexOf('portfolio') >= 0 || p.indexOf('positions') >= 0 || p.indexOf('ticker') >= 0) {
471
+ return {
472
+ intel: pickRandomItem([
473
+ '- Largest exposure remains concentrated in mega-cap tech names.\n- Diversification is moderate across current holdings.',
474
+ '- Portfolio mix is growth-tilted with moderate single-name concentration.\n- P&L contribution is uneven across current positions.',
475
+ ]),
476
+ risk_signal: pickRandomItem([
477
+ '- Primary risk is concentration and correlation during volatility spikes.\n- Monitor drawdown risk in top-weight positions.',
478
+ '- Risk posture is medium-high due to momentum clustering.\n- Downside sensitivity increases in gap-down sessions.',
479
+ ]),
480
+ action_signal: pickRandomItem([
481
+ '- Hold core positions and rebalance only if top holding breaches concentration threshold.',
482
+ '- Trim strongest outperformer modestly and redistribute into lower-volatility exposure.',
483
+ ]),
484
+ };
485
+ }
486
+
487
+ var generic = resolveMockLlmResponse(prompt);
488
+ return {
489
+ intel: generic,
490
+ risk_signal: generic,
491
+ action_signal: generic,
492
+ };
493
+ }
494
+
495
+ async function delayedMockLlmResult(prompt) {
496
+ var delayMs = 1000 + Math.floor(Math.random() * 2001);
497
+ await new Promise(function (resolve) { setTimeout(resolve, delayMs); });
498
+ var analysis = buildMockLlmAnalysis(prompt);
499
+ return {
500
+ response: analysis.intel || resolveMockLlmResponse(prompt),
501
+ latency_ms: delayMs,
502
+ prompt_echo: String(prompt || ''),
503
+ intel: analysis.intel,
504
+ risk_signal: analysis.risk_signal,
505
+ action_signal: analysis.action_signal,
506
+ };
507
+ }
508
+
509
+ /** Attempt a real network fetch for a single URL. Returns null on failure. */
510
+ function fetchUrlFromNetwork(url, headers) {
511
+ return fetch(url, { method: 'GET', headers: headers || {} })
512
+ .then(function (resp) { if (!resp.ok) throw new Error('HTTP ' + resp.status); return resp.json(); })
513
+ .catch(function () { return null; });
514
+ }
515
+
516
+
517
+ async function bootstrap() {
518
+ var initCards = hasPersistedBoardState() ? [] : clone(INLINE_CARDS);
519
+
520
+ // Task executor executes only declared source kinds.
521
+ // No shell-side cache or computed-value synthesis.
522
+ app = createBoardLiveCardsLocalStorage(NAMESPACE, {
523
+ cards: initCards,
524
+ taskExecutor: async function (_ref, args) {
525
+ if (args && (args.subcommand === 'describe-capabilities' || args.command === 'describe-capabilities')) {
526
+ return { dispatched: true, data: clone(TASK_EXECUTOR_CAPABILITIES) };
527
+ }
528
+
529
+ var sourceDef = args && args.source_def;
530
+ if (!sourceDef || !sourceDef.bindTo) return { dispatched: true };
531
+
532
+ var callbackToken = args.callback && args.callback.token;
533
+ if (!callbackToken) return { dispatched: true };
534
+
535
+ // Extract cardId from callback token
536
+ var cardId = 'unknown';
537
+ try {
538
+ var decoded = JSON.parse(atob(callbackToken.replace(/-/g, '+').replace(/_/g, '/')));
539
+ cardId = decoded.cid || 'unknown';
540
+ } catch (_) {}
541
+
542
+ var bindTo = sourceDef.bindTo;
543
+ var urls = resolveUrlsForSourceDef(sourceDef);
544
+ var headers = sourceDef.url && sourceDef.url.headers
545
+ ? sourceDef.url.headers
546
+ : (sourceDef['url-list'] && sourceDef['url-list'].headers ? sourceDef['url-list'].headers : {});
547
+
548
+ var resultValue = null;
549
+ if (sourceDef.kind === 'mock-llm-random') {
550
+ if (typeof sourceDef.prompt !== 'string' || sourceDef.prompt.trim() === '') {
551
+ app.reportSourceFetchFailure(callbackToken, 'mock-llm-random requires non-empty prompt');
552
+ return { dispatched: false, error: 'mock-llm-random requires non-empty prompt' };
553
+ }
554
+ var resolvedPrompt = interpolateTemplate(sourceDef.prompt, sourceDef._projections || {});
555
+ resultValue = await delayedMockLlmResult(resolvedPrompt);
556
+ } else if (sourceDef.mock) {
557
+ resultValue = MOCK_DB[sourceDef.mock];
558
+ if (resultValue === undefined) {
559
+ app.reportSourceFetchFailure(callbackToken, 'mock key not found: ' + sourceDef.mock);
560
+ return { dispatched: false, error: 'mock key not found: ' + sourceDef.mock };
561
+ }
562
+ /* Yahoo Finance url/url-list branch disabled — re-enable to fetch live quotes.
563
+ } else if (sourceDef.url || sourceDef['url-list']) {
564
+ if (!Array.isArray(urls) || urls.length === 0) {
565
+ app.reportSourceFetchFailure(callbackToken, 'no resolved URLs for source_def');
566
+ return { dispatched: false, error: 'no resolved URLs for source_def' };
567
+ }
568
+ var requestKey = JSON.stringify(urls);
569
+ var cache = readTaskCache();
570
+ if (!cache.completed) cache.completed = {};
571
+ if (!cache.completed[cardId]) cache.completed[cardId] = {};
572
+
573
+ var cachedEntry = cache.completed[cardId][bindTo];
574
+ var cachedValue = null;
575
+ if (cachedEntry && typeof cachedEntry === 'object' && !Array.isArray(cachedEntry)
576
+ && Object.prototype.hasOwnProperty.call(cachedEntry, '_requestKey')
577
+ && Object.prototype.hasOwnProperty.call(cachedEntry, 'value')) {
578
+ if (cachedEntry._requestKey === requestKey) cachedValue = cachedEntry.value;
579
+ }
580
+
581
+ if (cachedValue === null) {
582
+ var seeded = sourceDef.url
583
+ ? buildDefaultYahooQuoteResponse(urls[0])
584
+ : urls.map(function (u) { return buildDefaultYahooQuoteResponse(u); });
585
+ cache.completed[cardId][bindTo] = { _requestKey: requestKey, value: clone(seeded) };
586
+ writeTaskCache(cache);
587
+ cachedValue = seeded;
588
+ }
589
+
590
+ // Ensure cached payload shape always matches quote compute expectations.
591
+ cachedValue = sourceDef.url
592
+ ? normalizeYahooQuotePayload(urls[0], cachedValue)
593
+ : normalizeYahooQuoteList(urls, cachedValue);
594
+
595
+ resultValue = cachedValue;
596
+
597
+ // Fire-and-forget refresh: updates localStorage cache for next invocation.
598
+ Promise.all(urls.map(function (u) { return fetchUrlFromNetwork(u, headers); }))
599
+ .then(function (networkResults) {
600
+ var hasAny = networkResults.some(function (r) { return r !== null; });
601
+ if (!hasAny) return;
602
+
603
+ var freshCache = readTaskCache();
604
+ if (!freshCache.completed) freshCache.completed = {};
605
+ if (!freshCache.completed[cardId]) freshCache.completed[cardId] = {};
606
+
607
+ var currentEntry = freshCache.completed[cardId][bindTo];
608
+ var currentValue = (currentEntry && currentEntry.value !== undefined) ? currentEntry.value : cachedValue;
609
+ var nextValue;
610
+
611
+ if (sourceDef.url) {
612
+ nextValue = networkResults[0] !== null
613
+ ? normalizeYahooQuotePayload(urls[0], networkResults[0])
614
+ : normalizeYahooQuotePayload(urls[0], currentValue);
615
+ } else {
616
+ var base = normalizeYahooQuoteList(urls, currentValue);
617
+ networkResults.forEach(function (res, idx) {
618
+ if (res !== null) base[idx] = normalizeYahooQuotePayload(urls[idx], res);
619
+ });
620
+ nextValue = base;
621
+ }
622
+
623
+ freshCache.completed[cardId][bindTo] = { _requestKey: requestKey, value: clone(nextValue) };
624
+ writeTaskCache(freshCache);
625
+ })
626
+ .catch(function () { keep cache as-is on refresh failure });
627
+ */
628
+ } else if (sourceDef.url || sourceDef['url-list']) {
629
+ // Seeded synthetic quotes; no network fetch.
630
+ if (!Array.isArray(urls) || urls.length === 0) {
631
+ app.reportSourceFetchFailure(callbackToken, 'no resolved URLs for source_def');
632
+ return { dispatched: false, error: 'no resolved URLs for source_def' };
633
+ }
634
+ resultValue = sourceDef.url
635
+ ? buildDefaultYahooQuoteResponse(urls[0])
636
+ : urls.map(function (u) { return buildDefaultYahooQuoteResponse(u); });
637
+ } else {
638
+ app.reportSourceFetchFailure(callbackToken, 'unsupported source kind in browser executor');
639
+ return { dispatched: false, error: 'unsupported source kind in browser executor' };
640
+ }
641
+
642
+ var blobKey = cardId + '/' + (sourceDef.outputFile || bindTo + '.json');
643
+ var blobRef = app.writeMemoryBlob(blobKey, JSON.stringify(resultValue));
644
+ app.reportSourceFetched(callbackToken, blobRef);
645
+
646
+ return { dispatched: true };
647
+ },
648
+ onWarn: function (msg) { console.warn('[localstorage-runtime]', msg); },
649
+ onBoardChange: function (event) {
650
+ if (!event || !Array.isArray(event.notifications) || event.notifications.length === 0) return;
651
+ if (!board) {
652
+ // Board not yet created — buffer for replay after initialization.
653
+ pendingNotifications.push.apply(pendingNotifications, event.notifications);
654
+ return;
655
+ }
656
+ if (board.core && board.core.devMode) {
657
+ console.log('[notifications]', event.notifications);
658
+ }
659
+ board.setState(function (prev) {
660
+ var next = applyNotification(prev, event.notifications, function () {
661
+ return app.getState();
662
+ });
663
+ stateRef.current = next;
664
+ return next;
665
+ });
666
+ },
667
+ });
668
+
669
+ document.getElementById('boardTitle').textContent = 'Portfolio Tracker (LocalStorage Runtime)';
670
+ document.getElementById('boardDesc').textContent =
671
+ 'Cards are inline in HTML. Runtime state is persisted in localStorage.';
672
+
673
+ await app.bootstrap();
674
+
675
+ // Build initial state: apply any notifications buffered during bootstrap
676
+ // (the persisted-state snapshot from publishPersistedStateSnapshot), then
677
+ // fall back to a direct getState() read for fresh starts where no snapshot
678
+ // was published.
679
+ stateRef.current = pendingNotifications.length > 0
680
+ ? applyNotification(buildBoardState(null, null), pendingNotifications, function () { return app.getState(); })
681
+ : buildBoardState(app.getState(), null);
682
+ pendingNotifications = [];
683
+
684
+ var engine = LiveCard.init({
685
+ resolve: function (id) { return stateRef.current && stateRef.current.modelsById[id]; },
686
+ chartLib: (typeof Chart !== 'undefined') ? Chart : null,
687
+ markdown: (typeof marked !== 'undefined') ? function (t) { return marked.parse(t); } : null,
688
+ sanitize: (typeof DOMPurify !== 'undefined') ? function (t) { return DOMPurify.sanitize(t); } : null,
689
+ onPatchState: async function (id, patch) {
690
+ await app.patchCard(id, patch || {});
691
+ },
692
+ onRefresh: async function (id) {
693
+ await app.patchCard(id, {});
694
+ },
695
+ onAction: async function (id, actionType, payload) {
696
+ await app.applyCardAction(id, actionType, payload || {});
697
+ },
698
+ getChatMessages: function (id) {
699
+ return app.readChatRecords(id).map(function (m) {
700
+ return {
701
+ role: m && typeof m.role === 'string' ? m.role : 'system',
702
+ text: m && typeof m.text === 'string' ? m.text : '',
703
+ files: Array.isArray(m && m.files) ? m.files : [],
704
+ };
705
+ });
706
+ },
707
+ });
708
+
709
+ document.getElementById('boardRoot').innerHTML = '';
710
+
711
+ board = LiveCard.Board(engine, document.getElementById('boardRoot'), {
712
+ initialState: stateRef.current,
713
+ getNodeIds: function (s) { return s.cardIds; },
714
+ selectNode: function (s, id) { return s.modelsById[id]; },
715
+ mode: currentMode,
716
+ canvas: {},
717
+ });
718
+
719
+ if (board && document.getElementById('devModeToggle').checked) {
720
+ board.core.setDevMode(true);
721
+ }
722
+
723
+ document.getElementById('modeBoard').addEventListener('click', function () {
724
+ currentMode = 'board';
725
+ if (board) board.core.setMode('board');
726
+ });
727
+
728
+ document.getElementById('modeCanvas').addEventListener('click', function () {
729
+ currentMode = 'canvas';
730
+ if (board) board.core.setMode('canvas');
731
+ });
732
+
733
+ document.getElementById('autoLayout').addEventListener('click', function () {
734
+ currentMode = 'canvas';
735
+ if (board) {
736
+ board.core.setMode('canvas');
737
+ board.core.autoLayout();
738
+ }
739
+ });
740
+
741
+ document.getElementById('devModeToggle').addEventListener('change', function () {
742
+ if (board) board.core.setDevMode(this.checked);
743
+ });
744
+ }
745
+
746
+ function resetDemoStorage() {
747
+ localStorage.clear();
748
+ }
749
+
750
+ function showResetOverlay() {
751
+ var overlay = document.createElement('div');
752
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.55);';
753
+ overlay.innerHTML = '<div style="color:#fff;font-size:2rem;font-weight:600;letter-spacing:0.05em;">Resetting\u2026</div>';
754
+ document.body.appendChild(overlay);
755
+ }
756
+
757
+ document.getElementById('resetDemo').addEventListener('click', function () {
758
+ showResetOverlay();
759
+ setTimeout(function () {
760
+ resetDemoStorage();
761
+ window.location.reload();
762
+ }, 400);
763
+ });
764
+
765
+ bootstrap().catch(function (err) {
766
+ console.error(err);
767
+ document.getElementById('boardRoot').innerHTML =
768
+ '<div class="alert alert-danger">Failed to start LocalStorage runtime demo: ' +
769
+ String(err && err.message || err) + '</div>';
770
+ });
771
+ })();
772
+ </script>
773
+ </body>
774
+ </html>