gloomberb 0.5.0 → 0.6.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 (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -13
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +13 -4
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +56 -8
  87. package/src/components/layout/shell.test.tsx +322 -19
  88. package/src/components/layout/shell.tsx +1010 -366
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +150 -0
  180. package/src/plugins/builtin/chat-controller.ts +88 -16
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +373 -172
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +186 -6
  440. package/src/utils/api-client.ts +433 -33
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -3,9 +3,12 @@ import { existsSync, rmSync } from "fs";
3
3
  import { tmpdir } from "os";
4
4
  import { join } from "path";
5
5
  import { AppPersistence } from "../data/app-persistence";
6
- import { ProviderRouter } from "./provider-router";
6
+ import { AssetDataRouter } from "./provider-router";
7
+ import { assetDataProvider } from "../capabilities";
7
8
  import type { BrokerAdapter } from "../types/broker";
8
9
  import type { DataProvider, QuoteSubscriptionTarget } from "../types/data-provider";
10
+ import type { CapabilityRouteSource } from "../types/capability-route-source";
11
+ import type { NewsArticle } from "../news/types";
9
12
  import { cloneLayout, CURRENT_CONFIG_VERSION, DEFAULT_LAYOUT } from "../types/config";
10
13
 
11
14
  const originalConsoleError = console.error;
@@ -46,9 +49,6 @@ const fallbackProvider: DataProvider = {
46
49
  async search() {
47
50
  return [];
48
51
  },
49
- async getNews() {
50
- return [];
51
- },
52
52
  async getArticleSummary() {
53
53
  return null;
54
54
  },
@@ -57,9 +57,146 @@ const fallbackProvider: DataProvider = {
57
57
  },
58
58
  };
59
59
 
60
- describe("ProviderRouter", () => {
60
+ function makeArticle(id: string): NewsArticle {
61
+ return {
62
+ id,
63
+ title: id,
64
+ url: `https://example.com/${id}`,
65
+ source: "Test",
66
+ publishedAt: new Date(),
67
+ topic: "general",
68
+ topics: ["general"],
69
+ sectors: [],
70
+ categories: [],
71
+ tickers: [],
72
+ scores: {
73
+ importance: 50,
74
+ urgency: 0,
75
+ marketImpact: 50,
76
+ novelty: 0,
77
+ confidence: 0,
78
+ },
79
+ importance: 50,
80
+ isBreaking: false,
81
+ isDeveloping: false,
82
+ };
83
+ }
84
+
85
+ describe("AssetDataRouter", () => {
86
+ test("routes market calls only through sources with market capability", async () => {
87
+ const newsOnlySource: CapabilityRouteSource = {
88
+ id: "news-only",
89
+ name: "News Only",
90
+ priority: 1,
91
+ news: {
92
+ fetchNews: async () => {
93
+ throw new Error("news-only source should not handle market requests");
94
+ },
95
+ },
96
+ };
97
+ const marketSource: CapabilityRouteSource = {
98
+ id: "market-source",
99
+ name: "Market Source",
100
+ priority: 10,
101
+ market: {
102
+ ...fallbackProvider,
103
+ id: "market-source",
104
+ name: "Market Source",
105
+ async getQuote(symbol) {
106
+ return {
107
+ symbol,
108
+ providerId: "market-source",
109
+ price: 250,
110
+ currency: "USD",
111
+ change: 1,
112
+ changePercent: 0.4,
113
+ lastUpdated: Date.now(),
114
+ };
115
+ },
116
+ },
117
+ };
118
+
119
+ const router = new AssetDataRouter(fallbackProvider, [newsOnlySource, marketSource]);
120
+
121
+ const quote = await router.getQuote("AAPL", "NASDAQ");
122
+
123
+ expect(quote.providerId).toBe("market-source");
124
+ expect(quote.price).toBe(250);
125
+ });
126
+
127
+ test("routes news calls only through sources with news capability", async () => {
128
+ const article = makeArticle("source-news");
129
+ const marketOnlySource: CapabilityRouteSource = {
130
+ id: "market-only",
131
+ name: "Market Only",
132
+ priority: 1,
133
+ market: {
134
+ ...fallbackProvider,
135
+ id: "market-only",
136
+ name: "Market Only",
137
+ },
138
+ };
139
+ const newsSource: CapabilityRouteSource = {
140
+ id: "news-source",
141
+ name: "News Source",
142
+ priority: 10,
143
+ news: {
144
+ fetchNews: async () => [article],
145
+ },
146
+ };
147
+ const router = new AssetDataRouter(null, [marketOnlySource, newsSource]);
148
+
149
+ const articles = await router.getNews({ feed: "latest", limit: 10 });
150
+
151
+ expect(articles).toEqual([article]);
152
+ });
153
+
154
+ test("falls back to later providers when holder data is empty", async () => {
155
+ const cloudProvider: DataProvider = {
156
+ ...fallbackProvider,
157
+ id: "cloud",
158
+ name: "Cloud",
159
+ priority: 100,
160
+ async getHolders(symbol) {
161
+ return {
162
+ providerId: "cloud",
163
+ symbol,
164
+ holders: [],
165
+ };
166
+ },
167
+ };
168
+ const yahooProvider: DataProvider = {
169
+ ...fallbackProvider,
170
+ id: "yahoo",
171
+ name: "Yahoo",
172
+ priority: 1000,
173
+ async getHolders(symbol) {
174
+ return {
175
+ providerId: "yahoo",
176
+ symbol,
177
+ currency: "USD",
178
+ holders: [{
179
+ providerId: "yahoo",
180
+ ownerType: "institution",
181
+ name: "Vanguard Group Inc",
182
+ shares: 1_200_000_000,
183
+ value: 250_000_000_000,
184
+ percentHeld: 0.08,
185
+ changePercent: 0.02,
186
+ }],
187
+ };
188
+ },
189
+ };
190
+
191
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
192
+ const holders = await router.getHolders("AAPL", "NASDAQ");
193
+
194
+ expect(holders.providerId).toBe("yahoo");
195
+ expect(holders.holders[0]?.name).toBe("Vanguard Group Inc");
196
+ });
197
+
61
198
  test("prefers broker quotes over fallback quotes", async () => {
62
- const router = new ProviderRouter(fallbackProvider);
199
+ const router = new AssetDataRouter(fallbackProvider);
63
200
  const broker: BrokerAdapter = {
64
201
  id: "ibkr",
65
202
  name: "IBKR",
@@ -84,7 +221,7 @@ describe("ProviderRouter", () => {
84
221
 
85
222
  router.attachRegistry({
86
223
  brokers: new Map([["ibkr", broker]]),
87
- dataProviders: new Map(),
224
+ getEnabledCapabilities: () => [],
88
225
  } as any);
89
226
  router.setConfigAccessor(() => ({
90
227
  dataDir: "",
@@ -120,7 +257,7 @@ describe("ProviderRouter", () => {
120
257
  });
121
258
 
122
259
  test("prefers the requested broker instance in search results", async () => {
123
- const router = new ProviderRouter(fallbackProvider);
260
+ const router = new AssetDataRouter(fallbackProvider);
124
261
  const broker: BrokerAdapter = {
125
262
  id: "ibkr",
126
263
  name: "IBKR",
@@ -149,7 +286,7 @@ describe("ProviderRouter", () => {
149
286
 
150
287
  router.attachRegistry({
151
288
  brokers: new Map([["ibkr", broker]]),
152
- dataProviders: new Map(),
289
+ getEnabledCapabilities: () => [],
153
290
  } as any);
154
291
  router.setConfigAccessor(() => ({
155
292
  dataDir: "",
@@ -239,11 +376,11 @@ describe("ProviderRouter", () => {
239
376
  }];
240
377
  },
241
378
  };
242
- const router = new ProviderRouter(fallbackProvider, [provider]);
379
+ const router = new AssetDataRouter(fallbackProvider, [provider]);
243
380
 
244
381
  router.attachRegistry({
245
382
  brokers: new Map([["ibkr", broker]]),
246
- dataProviders: new Map(),
383
+ getEnabledCapabilities: () => [],
247
384
  } as any);
248
385
  router.setConfigAccessor(() => ({
249
386
  dataDir: "",
@@ -280,46 +417,47 @@ describe("ProviderRouter", () => {
280
417
  expect(results[0]?.brokerContract?.localSymbol).toBe("AAPL");
281
418
  });
282
419
 
283
- test("ignores disabled plugin providers when the registry filters them out", async () => {
284
- const disabledProvider: DataProvider = {
285
- id: "gloomberb-cloud",
420
+ test("ignores disabled plugin sources when the registry filters them out", async () => {
421
+ const router = new AssetDataRouter(fallbackProvider);
422
+ router.attachRegistry({
423
+ brokers: new Map(),
424
+ getEnabledCapabilities: () => [],
425
+ } as any);
426
+
427
+ const quote = await router.getQuote("AAPL", "NASDAQ");
428
+ expect(quote.price).toBe(100);
429
+ expect(quote.providerId).toBeUndefined();
430
+ });
431
+
432
+ test("routes through registered asset-data capabilities", async () => {
433
+ const cloudProvider: DataProvider = {
434
+ ...fallbackProvider,
435
+ id: "cloud",
286
436
  name: "Cloud",
287
437
  priority: 10,
288
- async getTickerFinancials() {
289
- throw new Error("disabled provider should not be used");
290
- },
291
438
  async getQuote() {
292
- throw new Error("disabled provider should not be used");
293
- },
294
- async getExchangeRate() {
295
- throw new Error("disabled provider should not be used");
296
- },
297
- async search() {
298
- throw new Error("disabled provider should not be used");
299
- },
300
- async getNews() {
301
- throw new Error("disabled provider should not be used");
302
- },
303
- async getArticleSummary() {
304
- throw new Error("disabled provider should not be used");
305
- },
306
- async getPriceHistory() {
307
- throw new Error("disabled provider should not be used");
439
+ return {
440
+ symbol: "AAPL",
441
+ providerId: "cloud",
442
+ price: 125,
443
+ currency: "USD",
444
+ change: 1,
445
+ changePercent: 0.8,
446
+ lastUpdated: Date.now(),
447
+ };
308
448
  },
309
449
  };
310
-
311
- const router = new ProviderRouter(fallbackProvider);
450
+ const router = new AssetDataRouter(fallbackProvider);
312
451
  router.attachRegistry({
313
452
  brokers: new Map(),
314
- dataProviders: new Map([["gloomberb-cloud", disabledProvider]]),
315
- getEnabledDataProviders() {
316
- return [];
317
- },
453
+ getEnabledCapabilities: (kind?: string) => (
454
+ kind === "asset-data" ? [assetDataProvider(cloudProvider)] : []
455
+ ),
318
456
  } as any);
319
457
 
320
458
  const quote = await router.getQuote("AAPL", "NASDAQ");
321
- expect(quote.price).toBe(100);
322
- expect(quote.providerId).toBeUndefined();
459
+ expect(quote.price).toBe(125);
460
+ expect(quote.providerId).toBe("cloud");
323
461
  });
324
462
 
325
463
  test("prefers provider quote streams when one is available", () => {
@@ -373,10 +511,10 @@ describe("ProviderRouter", () => {
373
511
  },
374
512
  };
375
513
 
376
- const router = new ProviderRouter(fallbackProvider, [streamingProvider]);
514
+ const router = new AssetDataRouter(fallbackProvider, [streamingProvider]);
377
515
  router.attachRegistry({
378
516
  brokers: new Map([["ibkr", broker]]),
379
- dataProviders: new Map(),
517
+ getEnabledCapabilities: () => [],
380
518
  } as any);
381
519
  router.setConfigAccessor(() => ({
382
520
  dataDir: "",
@@ -476,10 +614,10 @@ describe("ProviderRouter", () => {
476
614
  },
477
615
  };
478
616
 
479
- const router = new ProviderRouter(fallbackProvider, [streamingProvider]);
617
+ const router = new AssetDataRouter(fallbackProvider, [streamingProvider]);
480
618
  router.attachRegistry({
481
619
  brokers: new Map([["ibkr", broker]]),
482
- dataProviders: new Map(),
620
+ getEnabledCapabilities: () => [],
483
621
  } as any);
484
622
  router.setConfigAccessor(() => ({
485
623
  dataDir: "",
@@ -538,7 +676,7 @@ describe("ProviderRouter", () => {
538
676
  const dbPath = createTempDbPath("cache-merge");
539
677
  const persistence = new AppPersistence(dbPath);
540
678
  const providerCalls = { broker: 0, fallback: 0 };
541
- const router = new ProviderRouter({
679
+ const router = new AssetDataRouter({
542
680
  ...fallbackProvider,
543
681
  async getTickerFinancials() {
544
682
  providerCalls.fallback += 1;
@@ -581,7 +719,7 @@ describe("ProviderRouter", () => {
581
719
 
582
720
  router.attachRegistry({
583
721
  brokers: new Map([["ibkr", broker]]),
584
- dataProviders: new Map(),
722
+ getEnabledCapabilities: () => [],
585
723
  } as any);
586
724
  router.setConfigAccessor(() => ({
587
725
  dataDir: "",
@@ -633,7 +771,7 @@ describe("ProviderRouter", () => {
633
771
  });
634
772
 
635
773
  test("keeps broker price authority when a fallback quote is off by a likely 100x unit mismatch", async () => {
636
- const router = new ProviderRouter({
774
+ const router = new AssetDataRouter({
637
775
  ...fallbackProvider,
638
776
  async getTickerFinancials() {
639
777
  return {
@@ -648,7 +786,7 @@ describe("ProviderRouter", () => {
648
786
  change: -0.021,
649
787
  changePercent: -7.89,
650
788
  lastUpdated: Date.now(),
651
- dataSource: "yahoo",
789
+ dataSource: "delayed",
652
790
  },
653
791
  fundamentals: { revenue: 1000 },
654
792
  };
@@ -686,7 +824,7 @@ describe("ProviderRouter", () => {
686
824
 
687
825
  router.attachRegistry({
688
826
  brokers: new Map([["ibkr", broker]]),
689
- dataProviders: new Map(),
827
+ getEnabledCapabilities: () => [],
690
828
  } as any);
691
829
  router.setConfigAccessor(() => ({
692
830
  dataDir: "",
@@ -729,7 +867,7 @@ describe("ProviderRouter", () => {
729
867
  });
730
868
 
731
869
  test("preserves fallback profile data when broker already has fundamentals", async () => {
732
- const router = new ProviderRouter({
870
+ const router = new AssetDataRouter({
733
871
  ...fallbackProvider,
734
872
  async getTickerFinancials() {
735
873
  return {
@@ -766,7 +904,7 @@ describe("ProviderRouter", () => {
766
904
 
767
905
  router.attachRegistry({
768
906
  brokers: new Map([["ibkr", broker]]),
769
- dataProviders: new Map(),
907
+ getEnabledCapabilities: () => [],
770
908
  } as any);
771
909
  router.setConfigAccessor(() => ({
772
910
  dataDir: "",
@@ -857,7 +995,7 @@ describe("ProviderRouter", () => {
857
995
  },
858
996
  };
859
997
 
860
- const router = new ProviderRouter(yahooProvider, [cloudProvider]);
998
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
861
999
  const merged = await router.getTickerFinancials("AAPL", "NASDAQ");
862
1000
 
863
1001
  expect(merged.quote?.price).toBe(125);
@@ -924,13 +1062,13 @@ describe("ProviderRouter", () => {
924
1062
  },
925
1063
  };
926
1064
 
927
- const seedRouter = new ProviderRouter(yahooProvider, [cloudProvider], persistence.resources);
1065
+ const seedRouter = new AssetDataRouter(yahooProvider, [cloudProvider], persistence.resources);
928
1066
  const seeded = await seedRouter.getTickerFinancials("AAPL", "NASDAQ");
929
1067
  expect(seeded.quote?.marketCap).toBe(2_000_000_000);
930
1068
 
931
1069
  let cloudCalls = 0;
932
1070
  let yahooCalls = 0;
933
- const cachedRouter = new ProviderRouter({
1071
+ const cachedRouter = new AssetDataRouter({
934
1072
  ...yahooProvider,
935
1073
  async getTickerFinancials() {
936
1074
  yahooCalls += 1;
@@ -1011,7 +1149,7 @@ describe("ProviderRouter", () => {
1011
1149
  recentTickers: [],
1012
1150
  };
1013
1151
 
1014
- const seedRouter = new ProviderRouter({
1152
+ const seedRouter = new AssetDataRouter({
1015
1153
  ...fallbackProvider,
1016
1154
  async getTickerFinancials() {
1017
1155
  return {
@@ -1023,7 +1161,7 @@ describe("ProviderRouter", () => {
1023
1161
  }, [], persistence.resources);
1024
1162
  seedRouter.attachRegistry({
1025
1163
  brokers: new Map([["ibkr", broker]]),
1026
- dataProviders: new Map(),
1164
+ getEnabledCapabilities: () => [],
1027
1165
  } as any);
1028
1166
  seedRouter.setConfigAccessor(() => config);
1029
1167
 
@@ -1033,7 +1171,7 @@ describe("ProviderRouter", () => {
1033
1171
  });
1034
1172
  expect(seeded.profile).toBeUndefined();
1035
1173
 
1036
- const refreshedRouter = new ProviderRouter({
1174
+ const refreshedRouter = new AssetDataRouter({
1037
1175
  ...fallbackProvider,
1038
1176
  async getTickerFinancials() {
1039
1177
  return {
@@ -1050,7 +1188,7 @@ describe("ProviderRouter", () => {
1050
1188
  }, [], persistence.resources);
1051
1189
  refreshedRouter.attachRegistry({
1052
1190
  brokers: new Map([["ibkr", broker]]),
1053
- dataProviders: new Map(),
1191
+ getEnabledCapabilities: () => [],
1054
1192
  } as any);
1055
1193
  refreshedRouter.setConfigAccessor(() => config);
1056
1194
 
@@ -1067,7 +1205,7 @@ describe("ProviderRouter", () => {
1067
1205
  test("does not reuse another broker instance's financials when a specific instance is requested", async () => {
1068
1206
  const dbPath = createTempDbPath("instance-scoped-financials");
1069
1207
  const persistence = new AppPersistence(dbPath);
1070
- const router = new ProviderRouter({
1208
+ const router = new AssetDataRouter({
1071
1209
  ...fallbackProvider,
1072
1210
  async getTickerFinancials() {
1073
1211
  return {
@@ -1082,7 +1220,7 @@ describe("ProviderRouter", () => {
1082
1220
  change: 0,
1083
1221
  changePercent: 0,
1084
1222
  lastUpdated: Date.now(),
1085
- dataSource: "yahoo",
1223
+ dataSource: "delayed",
1086
1224
  },
1087
1225
  };
1088
1226
  },
@@ -1121,7 +1259,7 @@ describe("ProviderRouter", () => {
1121
1259
 
1122
1260
  router.attachRegistry({
1123
1261
  brokers: new Map([["ibkr", broker]]),
1124
- dataProviders: new Map(),
1262
+ getEnabledCapabilities: () => [],
1125
1263
  } as any);
1126
1264
  router.setConfigAccessor(() => ({
1127
1265
  dataDir: "",
@@ -1235,13 +1373,14 @@ describe("ProviderRouter", () => {
1235
1373
  priceHistory: [],
1236
1374
  quote: {
1237
1375
  symbol: "IQE.L",
1376
+ providerId: "yahoo",
1238
1377
  price: 0.231,
1239
1378
  currency: "GBP",
1240
1379
  change: -0.014,
1241
1380
  changePercent: -5.71,
1242
1381
  previousClose: 0.245,
1243
1382
  lastUpdated: now,
1244
- dataSource: "yahoo",
1383
+ dataSource: "delayed",
1245
1384
  },
1246
1385
  fundamentals: {
1247
1386
  revenue: 1000,
@@ -1271,7 +1410,7 @@ describe("ProviderRouter", () => {
1271
1410
  throw new Error("should not fetch cloud financials");
1272
1411
  },
1273
1412
  };
1274
- const router = new ProviderRouter(yahooProvider, [cloudProvider], persistence.resources);
1413
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider], persistence.resources);
1275
1414
 
1276
1415
  const financials = await router.getTickerFinancials("IQE", "LSE", {
1277
1416
  instrument: {
@@ -1356,7 +1495,7 @@ describe("ProviderRouter", () => {
1356
1495
  exchangeName: "FWB2",
1357
1496
  listingExchangeName: "FWB2",
1358
1497
  providerId: "yahoo",
1359
- dataSource: "yahoo",
1498
+ dataSource: "delayed",
1360
1499
  },
1361
1500
  },
1362
1501
  {
@@ -1365,7 +1504,7 @@ describe("ProviderRouter", () => {
1365
1504
  },
1366
1505
  );
1367
1506
 
1368
- const router = new ProviderRouter({
1507
+ const router = new AssetDataRouter({
1369
1508
  ...fallbackProvider,
1370
1509
  id: "yahoo",
1371
1510
  name: "Yahoo",
@@ -1398,7 +1537,7 @@ describe("ProviderRouter", () => {
1398
1537
  test("drops cached premarket cloud quotes that lack an active-session price", () => {
1399
1538
  const dbPath = createTempDbPath("stale-cloud-premarket-cache");
1400
1539
  const persistence = new AppPersistence(dbPath);
1401
- const now = Date.parse("2026-04-08T10:55:00Z");
1540
+ const now = Date.now();
1402
1541
 
1403
1542
  persistence.resources.set(
1404
1543
  {
@@ -1457,7 +1596,7 @@ describe("ProviderRouter", () => {
1457
1596
  exchangeName: "NASDAQ",
1458
1597
  listingExchangeName: "NASDAQ",
1459
1598
  providerId: "yahoo",
1460
- dataSource: "yahoo",
1599
+ dataSource: "delayed",
1461
1600
  },
1462
1601
  },
1463
1602
  {
@@ -1466,7 +1605,7 @@ describe("ProviderRouter", () => {
1466
1605
  },
1467
1606
  );
1468
1607
 
1469
- const router = new ProviderRouter({
1608
+ const router = new AssetDataRouter({
1470
1609
  ...fallbackProvider,
1471
1610
  id: "yahoo",
1472
1611
  name: "Yahoo",
@@ -1503,7 +1642,7 @@ describe("ProviderRouter", () => {
1503
1642
  throw new Error('[404] {"chart":{"result":null,"error":{"code":"Not Found","description":"No data found, symbol may be delisted"}}}');
1504
1643
  },
1505
1644
  };
1506
- const router = new ProviderRouter(fallbackProvider, [noisyProvider]);
1645
+ const router = new AssetDataRouter(fallbackProvider, [noisyProvider]);
1507
1646
  const logged: unknown[][] = [];
1508
1647
  console.error = (...args: unknown[]) => {
1509
1648
  logged.push(args);
@@ -1538,11 +1677,11 @@ describe("ProviderRouter", () => {
1538
1677
  },
1539
1678
  };
1540
1679
 
1541
- const seedRouter = new ProviderRouter(yahooProvider, [cloudProvider], persistence.resources);
1680
+ const seedRouter = new AssetDataRouter(yahooProvider, [cloudProvider], persistence.resources);
1542
1681
  const seeded = await seedRouter.getPriceHistory("AAPL", "NASDAQ", "1Y");
1543
1682
  expect(seeded[0]?.close).toBe(101);
1544
1683
 
1545
- const cachedRouter = new ProviderRouter(yahooProvider, [cloudProvider], persistence.resources);
1684
+ const cachedRouter = new AssetDataRouter(yahooProvider, [cloudProvider], persistence.resources);
1546
1685
  const cached = await cachedRouter.getPriceHistory("AAPL", "NASDAQ", "1Y");
1547
1686
  expect(cached[0]?.close).toBe(101);
1548
1687
 
@@ -1550,7 +1689,7 @@ describe("ProviderRouter", () => {
1550
1689
  });
1551
1690
 
1552
1691
  test("sorts reversed chart history into chronological order", async () => {
1553
- const router = new ProviderRouter({
1692
+ const router = new AssetDataRouter({
1554
1693
  ...fallbackProvider,
1555
1694
  id: "cloud",
1556
1695
  name: "Cloud",
@@ -1590,7 +1729,7 @@ describe("ProviderRouter", () => {
1590
1729
  );
1591
1730
 
1592
1731
  let providerCalls = 0;
1593
- const router = new ProviderRouter({
1732
+ const router = new AssetDataRouter({
1594
1733
  ...fallbackProvider,
1595
1734
  id: "yahoo",
1596
1735
  name: "Yahoo",
@@ -1615,7 +1754,7 @@ describe("ProviderRouter", () => {
1615
1754
  const dbPath = createTempDbPath("forced-financial-refresh");
1616
1755
  const persistence = new AppPersistence(dbPath);
1617
1756
 
1618
- const seedRouter = new ProviderRouter({
1757
+ const seedRouter = new AssetDataRouter({
1619
1758
  ...fallbackProvider,
1620
1759
  async getTickerFinancials() {
1621
1760
  return {
@@ -1636,7 +1775,7 @@ describe("ProviderRouter", () => {
1636
1775
  await seedRouter.getTickerFinancials("AAPL", "NASDAQ");
1637
1776
 
1638
1777
  let providerCalls = 0;
1639
- const refreshRouter = new ProviderRouter({
1778
+ const refreshRouter = new AssetDataRouter({
1640
1779
  ...fallbackProvider,
1641
1780
  async getTickerFinancials() {
1642
1781
  providerCalls += 1;
@@ -1669,7 +1808,7 @@ describe("ProviderRouter", () => {
1669
1808
  const dbPath = createTempDbPath("stale-chart-refresh");
1670
1809
  const persistence = new AppPersistence(dbPath);
1671
1810
 
1672
- const seedRouter = new ProviderRouter({
1811
+ const seedRouter = new AssetDataRouter({
1673
1812
  ...fallbackProvider,
1674
1813
  async getPriceHistory() {
1675
1814
  return [{ date: new Date("2026-03-27T00:00:00Z"), close: 101 }];
@@ -1682,7 +1821,7 @@ describe("ProviderRouter", () => {
1682
1821
  .run(Date.now() - 1, "market", "price-history", "AAPL");
1683
1822
 
1684
1823
  let providerCalls = 0;
1685
- const refreshRouter = new ProviderRouter({
1824
+ const refreshRouter = new AssetDataRouter({
1686
1825
  ...fallbackProvider,
1687
1826
  async getPriceHistory() {
1688
1827
  providerCalls += 1;
@@ -1718,7 +1857,7 @@ describe("ProviderRouter", () => {
1718
1857
  },
1719
1858
  };
1720
1859
 
1721
- const router = new ProviderRouter(yahooProvider, [cloudProvider]);
1860
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
1722
1861
  const history = await router.getPriceHistoryForResolution("AAPL", "NASDAQ", "1Y", "1d");
1723
1862
 
1724
1863
  expect(history[0]?.close).toBe(102);
@@ -1744,7 +1883,7 @@ describe("ProviderRouter", () => {
1744
1883
  },
1745
1884
  };
1746
1885
 
1747
- const router = new ProviderRouter(yahooProvider, [cloudProvider]);
1886
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
1748
1887
  expect(await router.getChartResolutionCapabilities("AAPL", "NASDAQ")).toEqual(["1d", "1wk"]);
1749
1888
  });
1750
1889
 
@@ -1768,7 +1907,7 @@ describe("ProviderRouter", () => {
1768
1907
  },
1769
1908
  };
1770
1909
 
1771
- const router = new ProviderRouter(yahooProvider, [cloudProvider]);
1910
+ const router = new AssetDataRouter(yahooProvider, [cloudProvider]);
1772
1911
  const history = await router.getDetailedPriceHistory(
1773
1912
  "AAPL",
1774
1913
  "NASDAQ",