gloomberb 0.5.1 → 0.6.1

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 (506) hide show
  1. package/README.md +91 -11
  2. package/package.json +13 -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 +318 -158
  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 +53 -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/native/surface-visibility.ts +48 -0
  53. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  54. package/src/components/chart/static-chart-surface.tsx +191 -0
  55. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  56. package/src/components/chart/stock-chart.tsx +452 -191
  57. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  58. package/src/components/command-bar/command-bar.test.tsx +269 -20
  59. package/src/components/command-bar/command-bar.tsx +830 -353
  60. package/src/components/command-bar/command-registry.ts +10 -20
  61. package/src/components/command-bar/helpers.test.ts +1 -1
  62. package/src/components/command-bar/helpers.ts +9 -11
  63. package/src/components/command-bar/pane-template-display.ts +4 -8
  64. package/src/components/command-bar/root-shortcuts.ts +12 -11
  65. package/src/components/command-bar/view-model.test.ts +8 -42
  66. package/src/components/command-bar/view-model.ts +8 -36
  67. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  68. package/src/components/command-bar/workflow-ops.ts +40 -9
  69. package/src/components/command-bar/workflow-types.ts +2 -1
  70. package/src/components/data-table-stack-view.test.tsx +206 -0
  71. package/src/components/data-table-stack-view.tsx +152 -0
  72. package/src/components/data-table-view.test.tsx +188 -0
  73. package/src/components/data-table-view.tsx +279 -0
  74. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  75. package/src/components/feed-data-table-stack-view.tsx +365 -0
  76. package/src/components/index.ts +26 -31
  77. package/src/components/layout/detached-pane-shell.tsx +185 -0
  78. package/src/components/layout/floating-pane.tsx +87 -14
  79. package/src/components/layout/header.tsx +139 -49
  80. package/src/components/layout/pane-content.tsx +42 -0
  81. package/src/components/layout/pane-footer.test.tsx +195 -0
  82. package/src/components/layout/pane-footer.tsx +425 -0
  83. package/src/components/layout/pane-header.test.tsx +39 -0
  84. package/src/components/layout/pane-header.tsx +192 -14
  85. package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
  86. package/src/components/layout/pane-sizing.ts +9 -10
  87. package/src/components/layout/pane-title.ts +35 -0
  88. package/src/components/layout/pane.tsx +52 -11
  89. package/src/components/layout/shell.test.tsx +315 -107
  90. package/src/components/layout/shell.tsx +1010 -368
  91. package/src/components/layout/status-bar.test.tsx +17 -10
  92. package/src/components/layout/status-bar.tsx +367 -100
  93. package/src/components/layout/titlebar-overlay.ts +2 -0
  94. package/src/components/markdown-editor.tsx +29 -14
  95. package/src/components/markdown-text.tsx +25 -24
  96. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  97. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  98. package/src/components/pane-settings-dialog.tsx +521 -89
  99. package/src/components/pane-template-wizard.test.tsx +1 -1
  100. package/src/components/pane-template-wizard.tsx +38 -37
  101. package/src/components/price-selector-dialog.tsx +22 -21
  102. package/src/components/ticker-badge-list.tsx +53 -0
  103. package/src/components/ticker-badge-text.test.tsx +44 -1
  104. package/src/components/ticker-badge-text.tsx +78 -38
  105. package/src/components/ticker-badge.tsx +28 -7
  106. package/src/components/ticker-list-table-view.tsx +248 -0
  107. package/src/components/ticker-list-table.test.tsx +47 -1
  108. package/src/components/ticker-list-table.tsx +212 -41
  109. package/src/components/toggle-list.tsx +114 -8
  110. package/src/components/ui/button.tsx +24 -16
  111. package/src/components/ui/choice-dialog.tsx +89 -0
  112. package/src/components/ui/data-table.tsx +313 -114
  113. package/src/components/ui/external-link.tsx +52 -13
  114. package/src/components/ui/fields.tsx +44 -30
  115. package/src/components/ui/frame.tsx +24 -57
  116. package/src/components/ui/index.ts +15 -50
  117. package/src/components/ui/list-view.tsx +65 -25
  118. package/src/components/ui/loading.tsx +5 -60
  119. package/src/components/ui/message-composer.test.tsx +81 -0
  120. package/src/components/ui/message-composer.tsx +139 -0
  121. package/src/components/ui/multi-select-dialog.tsx +100 -32
  122. package/src/components/ui/multi-select.ts +112 -0
  123. package/src/components/ui/native-select.tsx +120 -0
  124. package/src/components/ui/page-stack-view.tsx +49 -19
  125. package/src/components/ui/remote-image.tsx +50 -0
  126. package/src/components/ui/row-value-cache.test.ts +17 -0
  127. package/src/components/ui/row-value-cache.ts +41 -0
  128. package/src/components/ui/shortcut-hint.tsx +55 -0
  129. package/src/components/ui/status.tsx +12 -67
  130. package/src/components/ui/table-layout.ts +59 -0
  131. package/src/components/ui/tabs.tsx +249 -27
  132. package/src/components/ui/toggle.tsx +19 -127
  133. package/src/components/ui/ui.test.tsx +266 -53
  134. package/src/components/use-double-click-activation.ts +7 -2
  135. package/src/components/use-external-link-footer.ts +79 -0
  136. package/src/core/app-services.ts +95 -0
  137. package/src/core/state/app-state.ts +886 -0
  138. package/src/{state → core/state}/session-persistence.ts +7 -6
  139. package/src/data/config-layout.ts +225 -0
  140. package/src/data/config-store-node.ts +390 -0
  141. package/src/data/config-store.test.ts +72 -0
  142. package/src/data/config-store.ts +35 -478
  143. package/src/data/sqlite-json.ts +2 -5
  144. package/src/data/sqlite-retry.ts +1 -0
  145. package/src/index.tsx +2 -68
  146. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  147. package/src/market-data/coordinator.test.ts +73 -4
  148. package/src/market-data/coordinator.ts +190 -24
  149. package/src/market-data/hooks.test.tsx +1 -1
  150. package/src/market-data/hooks.tsx +109 -87
  151. package/src/market-data/query-store.ts +3 -3
  152. package/src/market-data/request-types.test.ts +0 -14
  153. package/src/market-data/result-types.ts +1 -0
  154. package/src/news/aggregator.test.ts +267 -0
  155. package/src/news/aggregator.ts +452 -0
  156. package/src/news/hooks.ts +39 -0
  157. package/src/news/ticker-symbols.ts +21 -0
  158. package/src/news/types.ts +77 -0
  159. package/src/notifications/app-notifier.ts +21 -10
  160. package/src/pane-settings.test.ts +0 -2
  161. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  162. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  163. package/src/plugins/builtin/ai/providers.ts +1 -9
  164. package/src/plugins/builtin/ai/runner.ts +41 -5
  165. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  166. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  167. package/src/plugins/builtin/ai/settings.ts +0 -1
  168. package/src/plugins/builtin/ai/utils.ts +1 -71
  169. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  170. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  171. package/src/plugins/builtin/alerts/index.tsx +263 -155
  172. package/src/plugins/builtin/alerts/types.ts +7 -0
  173. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  174. package/src/plugins/builtin/analytics/index.tsx +78 -120
  175. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  176. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  177. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  178. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  179. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  180. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  181. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  182. package/src/plugins/builtin/chat-controller.ts +85 -14
  183. package/src/plugins/builtin/chat.test.tsx +283 -36
  184. package/src/plugins/builtin/chat.tsx +363 -170
  185. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  186. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  187. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  188. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  189. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  190. package/src/plugins/builtin/correlation/index.tsx +51 -46
  191. package/src/plugins/builtin/correlation/settings.ts +1 -2
  192. package/src/plugins/builtin/debug.tsx +99 -70
  193. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  194. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  195. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  196. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  197. package/src/plugins/builtin/earnings/index.tsx +187 -94
  198. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  199. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  200. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  201. package/src/plugins/builtin/econ/index.tsx +236 -421
  202. package/src/plugins/builtin/econ/types.ts +1 -0
  203. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  204. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  205. package/src/plugins/builtin/help.test.tsx +120 -60
  206. package/src/plugins/builtin/help.tsx +266 -82
  207. package/src/plugins/builtin/holders/index.test.ts +45 -0
  208. package/src/plugins/builtin/holders/index.tsx +949 -0
  209. package/src/plugins/builtin/insider/index.tsx +207 -253
  210. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  211. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  212. package/src/plugins/builtin/layout-manager.tsx +12 -40
  213. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  214. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  215. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  216. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  217. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  218. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  219. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  220. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  221. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  222. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  223. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  224. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  225. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  226. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  227. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  228. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  229. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  230. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  231. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  232. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  233. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  234. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  235. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  236. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  237. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  238. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  239. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  240. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  241. package/src/plugins/builtin/news.tsx +98 -25
  242. package/src/plugins/builtin/notes-files.ts +51 -10
  243. package/src/plugins/builtin/notes.tsx +332 -131
  244. package/src/plugins/builtin/options.test.tsx +185 -0
  245. package/src/plugins/builtin/options.tsx +346 -206
  246. package/src/plugins/builtin/plugin-groups.ts +92 -0
  247. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  248. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  249. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  250. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  251. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  252. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  253. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  254. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  255. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  256. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  257. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  258. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  259. package/src/plugins/builtin/research/index.tsx +724 -0
  260. package/src/plugins/builtin/sec.tsx +67 -19
  261. package/src/plugins/builtin/sectors/index.tsx +42 -80
  262. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  263. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  264. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  265. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  266. package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
  267. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  268. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  269. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
  270. package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
  271. package/src/plugins/builtin/ticker-surface.ts +68 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  273. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  274. package/src/plugins/builtin/yahoo.ts +55 -1
  275. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  276. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  277. package/src/plugins/catalog-ui.test.ts +24 -0
  278. package/src/plugins/catalog-ui.ts +44 -0
  279. package/src/plugins/catalog.ts +2 -50
  280. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  281. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  282. package/src/plugins/ibkr/account-cache.ts +15 -120
  283. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  284. package/src/plugins/ibkr/config.test.ts +0 -18
  285. package/src/plugins/ibkr/dialogs.tsx +14 -56
  286. package/src/plugins/ibkr/flex.test.ts +57 -2
  287. package/src/plugins/ibkr/flex.ts +116 -12
  288. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  289. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  290. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  291. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  292. package/src/plugins/ibkr/gateway-types.ts +27 -0
  293. package/src/plugins/ibkr/index.test.tsx +3 -11
  294. package/src/plugins/ibkr/index.tsx +1 -0
  295. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  296. package/src/plugins/ibkr/price-normalization.ts +1 -0
  297. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  298. package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
  299. package/src/plugins/ibkr/trade-tab.tsx +166 -117
  300. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  301. package/src/plugins/pane-manager.test.ts +3 -1
  302. package/src/plugins/pane-manager.ts +93 -19
  303. package/src/plugins/plugin-runtime.test.tsx +176 -74
  304. package/src/plugins/plugin-runtime.tsx +228 -10
  305. package/src/plugins/prediction-markets/cache.ts +49 -0
  306. package/src/plugins/prediction-markets/categories.ts +0 -10
  307. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  308. package/src/plugins/prediction-markets/chart.tsx +21 -20
  309. package/src/plugins/prediction-markets/cli.ts +1 -0
  310. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  311. package/src/plugins/prediction-markets/controller.ts +37 -108
  312. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  313. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  314. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  315. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  316. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  317. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  318. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  319. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  320. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  321. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  322. package/src/plugins/prediction-markets/launch.ts +1 -1
  323. package/src/plugins/prediction-markets/metrics.ts +25 -12
  324. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  325. package/src/plugins/prediction-markets/pane.tsx +180 -82
  326. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  327. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  328. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  329. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  330. package/src/plugins/prediction-markets/settings.ts +0 -5
  331. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  332. package/src/plugins/registry.test.ts +307 -0
  333. package/src/plugins/registry.ts +293 -82
  334. package/src/react/input.ts +61 -0
  335. package/src/react/plugins/plugin-slot.tsx +21 -0
  336. package/src/react/use-raf-callback.ts +44 -0
  337. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  338. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  339. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  340. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  341. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  342. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  343. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  344. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  345. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  346. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  347. package/src/renderers/electrobun/bun/index.ts +1149 -0
  348. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  349. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  350. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  351. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  352. package/src/renderers/electrobun/view/app-services.ts +348 -0
  353. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  354. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  355. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  356. package/src/renderers/electrobun/view/config-host.ts +35 -0
  357. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  358. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  359. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  360. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  361. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  362. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  363. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  364. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  365. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  366. package/src/renderers/electrobun/view/input-host.tsx +112 -0
  367. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  368. package/src/renderers/electrobun/view/key-event.ts +134 -0
  369. package/src/renderers/electrobun/view/main.tsx +104 -0
  370. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  372. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  373. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  374. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  375. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  376. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  377. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  378. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  379. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  380. package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
  381. package/src/renderers/electrobun/view/update-host.ts +38 -0
  382. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  383. package/src/renderers/opentui/dialog-host.tsx +56 -0
  384. package/src/renderers/opentui/host.tsx +140 -0
  385. package/src/renderers/opentui/image-loader.test.ts +25 -0
  386. package/src/renderers/opentui/image-loader.ts +105 -0
  387. package/src/renderers/opentui/image-surface.tsx +266 -0
  388. package/src/renderers/opentui/input-host.tsx +63 -0
  389. package/src/renderers/opentui/start.tsx +71 -0
  390. package/src/renderers/opentui/test-utils.tsx +76 -0
  391. package/src/renderers/opentui/toast-host.tsx +10 -0
  392. package/src/renderers/opentui/ui-host.tsx +98 -0
  393. package/src/sources/gloomberb-cloud.test.ts +288 -17
  394. package/src/sources/gloomberb-cloud.ts +176 -9
  395. package/src/sources/provider-errors.ts +1 -0
  396. package/src/sources/provider-router.test.ts +220 -81
  397. package/src/sources/provider-router.ts +294 -111
  398. package/src/sources/sec-edgar.ts +19 -6
  399. package/src/sources/yahoo-finance.test.ts +0 -5
  400. package/src/sources/yahoo-finance.ts +420 -11
  401. package/src/state/app-activity.ts +7 -7
  402. package/src/state/app-bootstrap.ts +114 -40
  403. package/src/state/app-context-selector.test.tsx +70 -1
  404. package/src/state/app-context.test.ts +71 -3
  405. package/src/state/app-context.test.tsx +14 -7
  406. package/src/state/app-context.tsx +209 -869
  407. package/src/state/config-save-scheduler.ts +35 -0
  408. package/src/state/pane-scroll-registry.tsx +254 -0
  409. package/src/state/persist-scheduler.test.ts +67 -0
  410. package/src/state/persist-scheduler.ts +69 -0
  411. package/src/state/selectors-ui.ts +55 -0
  412. package/src/state/selectors.ts +15 -71
  413. package/src/state/session-persistence.test.ts +1 -1
  414. package/src/state/ticker-refresh-queue.ts +52 -3
  415. package/src/state/use-inline-tickers.ts +18 -16
  416. package/src/state/use-quote-streaming.test.ts +0 -6
  417. package/src/state/use-quote-streaming.test.tsx +1 -1
  418. package/src/state/use-quote-streaming.ts +3 -2
  419. package/src/state/use-session-snapshot.ts +32 -17
  420. package/src/test-support/plugin-runtime.ts +149 -0
  421. package/src/theme/color-utils.ts +1 -0
  422. package/src/theme/colors.ts +16 -0
  423. package/src/types/broker.ts +16 -1
  424. package/src/types/capability-route-source.ts +23 -0
  425. package/src/types/config.test.ts +23 -0
  426. package/src/types/config.ts +145 -10
  427. package/src/types/context-menu.ts +95 -0
  428. package/src/types/data-provider.ts +34 -3
  429. package/src/types/desktop-menu.ts +13 -0
  430. package/src/types/desktop-window.ts +29 -0
  431. package/src/types/financials.ts +123 -2
  432. package/src/types/instrument.ts +1 -0
  433. package/src/types/news-source.ts +9 -0
  434. package/src/types/persistence.ts +1 -0
  435. package/src/types/plugin.ts +32 -8
  436. package/src/ui/ascii-font.ts +85 -0
  437. package/src/ui/context-menu.test.ts +94 -0
  438. package/src/ui/context-menu.tsx +433 -0
  439. package/src/ui/dialog.tsx +55 -0
  440. package/src/ui/host.tsx +364 -0
  441. package/src/ui/index.tsx +134 -0
  442. package/src/ui/toast.tsx +40 -0
  443. package/src/updater.test.ts +69 -0
  444. package/src/updater.ts +61 -18
  445. package/src/utils/api-client.test.ts +99 -9
  446. package/src/utils/api-client.ts +378 -18
  447. package/src/utils/back-navigation.ts +6 -0
  448. package/src/utils/cli-output.ts +1 -0
  449. package/src/utils/currency-units.test.ts +1 -6
  450. package/src/utils/currency-units.ts +1 -0
  451. package/src/utils/debug-log.ts +68 -0
  452. package/src/utils/dom-screenshot.ts +326 -0
  453. package/src/utils/exchange-rate-map.ts +1 -0
  454. package/src/utils/exchanges.ts +214 -0
  455. package/src/utils/format.test.ts +7 -0
  456. package/src/utils/format.ts +1 -0
  457. package/src/utils/fuzzy-search.ts +1 -0
  458. package/src/utils/html-entities.test.ts +8 -0
  459. package/src/utils/html-entities.ts +32 -0
  460. package/src/utils/http-transport.ts +11 -0
  461. package/src/utils/main-thread-monitor.ts +127 -0
  462. package/src/utils/perf-marks.ts +54 -0
  463. package/src/utils/quote-freshness.ts +27 -50
  464. package/src/utils/quote-resolution.test.ts +5 -5
  465. package/src/utils/quote-resolution.ts +0 -1
  466. package/src/utils/quote-time.ts +1 -5
  467. package/src/utils/sec.test.ts +0 -12
  468. package/src/utils/selection-clipboard.ts +14 -6
  469. package/src/utils/shortcut-labels.test.ts +20 -0
  470. package/src/utils/shortcut-labels.ts +67 -0
  471. package/src/utils/slugify.ts +1 -0
  472. package/src/utils/terminal-input-reset.ts +1 -0
  473. package/src/utils/text-wrap.ts +82 -0
  474. package/src/utils/throttled-fetch.test.ts +19 -0
  475. package/src/utils/throttled-fetch.ts +11 -9
  476. package/src/utils/ticker-list.test.ts +1 -7
  477. package/src/utils/ticker-list.ts +1 -0
  478. package/src/utils/timestamp.ts +29 -0
  479. package/src/utils/tweet-text.test.ts +13 -0
  480. package/src/utils/tweet-text.ts +18 -0
  481. package/src/utils/url.ts +11 -0
  482. package/src/version.ts +1 -1
  483. package/src/components/chart/chart-control-hint.tsx +0 -39
  484. package/src/components/detail-feed-view.test.tsx +0 -180
  485. package/src/components/detail-feed-view.tsx +0 -531
  486. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  487. package/src/components/layout/pixel-mouse.test.ts +0 -40
  488. package/src/components/spinner.tsx +0 -2
  489. package/src/components/tab-bar.tsx +0 -17
  490. package/src/components/ui/multi-select-chips.tsx +0 -69
  491. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  492. package/src/plugins/builtin/ask-ai.tsx +0 -9
  493. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  494. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  495. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  496. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  497. package/src/plugins/builtin/fred-settings.ts +0 -10
  498. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  499. package/src/plugins/builtin/options-availability.ts +0 -158
  500. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  501. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  502. package/src/plugins/prediction-markets/status.ts +0 -24
  503. package/src/plugins/prediction-markets/table.tsx +0 -87
  504. package/src/utils/quote-time.test.ts +0 -22
  505. package/src/utils/selection-clipboard.test.ts +0 -43
  506. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -0,0 +1,131 @@
1
+ import {
2
+ recordSchema,
3
+ type CapabilityManifest,
4
+ type CapabilityOperationManifest,
5
+ type CapabilityRegistryOptions,
6
+ type PluginCapability,
7
+ type RegisteredCapability,
8
+ } from "./types";
9
+
10
+ interface SubscriptionEntry {
11
+ capabilityId: string;
12
+ dispose: () => void;
13
+ }
14
+
15
+ export class CapabilityRegistry {
16
+ private readonly capabilities = new Map<string, RegisteredCapability>();
17
+ private readonly subscriptions = new Map<string, SubscriptionEntry>();
18
+ private nextSubscriptionId = 1;
19
+
20
+ constructor(private readonly options: CapabilityRegistryOptions = {}) {}
21
+
22
+ register(pluginId: string, capability: PluginCapability): () => void {
23
+ const existing = this.capabilities.get(capability.id);
24
+ if (existing) {
25
+ throw new Error(`Capability "${capability.id}" already registered by plugin "${existing.pluginId}".`);
26
+ }
27
+ this.capabilities.set(capability.id, { pluginId, capability });
28
+ return () => {
29
+ this.capabilities.delete(capability.id);
30
+ for (const [subscriptionId, entry] of this.subscriptions) {
31
+ if (entry.capabilityId === capability.id) {
32
+ entry.dispose();
33
+ this.subscriptions.delete(subscriptionId);
34
+ }
35
+ }
36
+ };
37
+ }
38
+
39
+ get(id: string): RegisteredCapability | undefined {
40
+ return this.capabilities.get(id);
41
+ }
42
+
43
+ list(kind?: string): RegisteredCapability[] {
44
+ return [...this.capabilities.values()]
45
+ .filter((entry) => !kind || entry.capability.kind === kind)
46
+ .filter((entry) => this.isEnabled(entry))
47
+ .sort((left, right) => (
48
+ (left.capability.priority ?? 1000) - (right.capability.priority ?? 1000)
49
+ || left.capability.id.localeCompare(right.capability.id)
50
+ ));
51
+ }
52
+
53
+ manifests({ rendererOnly = false }: { rendererOnly?: boolean } = {}): CapabilityManifest[] {
54
+ return this.list().map(({ capability }) => {
55
+ const operations: CapabilityOperationManifest[] = Object.entries(capability.operations)
56
+ .filter(([, operation]) => !rendererOnly || operation.rendererSafe === true)
57
+ .map(([id, operation]) => ({
58
+ id,
59
+ kind: operation.kind,
60
+ rendererSafe: operation.rendererSafe === true,
61
+ }));
62
+ return {
63
+ id: capability.id,
64
+ kind: capability.kind,
65
+ name: capability.name,
66
+ priority: capability.priority,
67
+ sourceId: capability.sourceId,
68
+ operations,
69
+ };
70
+ });
71
+ }
72
+
73
+ async invoke<T = unknown>(
74
+ capabilityId: string,
75
+ operationId: string,
76
+ payload: unknown,
77
+ options: { renderer?: boolean } = {},
78
+ ): Promise<T> {
79
+ const { capability, operation } = this.resolveOperation(capabilityId, operationId, options);
80
+ if (!operation.handler) throw new Error(`Capability operation "${capabilityId}.${operationId}" is not invokable.`);
81
+ const input = (operation.input ?? recordSchema).parse(payload);
82
+ const result = await operation.handler(input, { capability, operationId });
83
+ return (operation.output ? operation.output.parse(result) : result) as T;
84
+ }
85
+
86
+ async subscribe<T = unknown>(
87
+ capabilityId: string,
88
+ operationId: string,
89
+ payload: unknown,
90
+ emit: (event: T) => void,
91
+ options: { renderer?: boolean; subscriptionId?: string } = {},
92
+ ): Promise<string> {
93
+ const { capability, operation } = this.resolveOperation(capabilityId, operationId, options);
94
+ if (!operation.subscribe) throw new Error(`Capability operation "${capabilityId}.${operationId}" is not subscribable.`);
95
+ const input = (operation.input ?? recordSchema).parse(payload);
96
+ const subscriptionId = options.subscriptionId ?? `${capabilityId}:${operationId}:${this.nextSubscriptionId++}`;
97
+ this.unsubscribe(subscriptionId);
98
+ const dispose = await operation.subscribe(input, emit, { capability, operationId });
99
+ this.subscriptions.set(subscriptionId, { capabilityId, dispose });
100
+ return subscriptionId;
101
+ }
102
+
103
+ unsubscribe(subscriptionId: string): void {
104
+ const entry = this.subscriptions.get(subscriptionId);
105
+ if (!entry) return;
106
+ entry.dispose();
107
+ this.subscriptions.delete(subscriptionId);
108
+ }
109
+
110
+ destroy(): void {
111
+ for (const subscriptionId of [...this.subscriptions.keys()]) this.unsubscribe(subscriptionId);
112
+ this.capabilities.clear();
113
+ }
114
+
115
+ private resolveOperation(capabilityId: string, operationId: string, options: { renderer?: boolean }) {
116
+ const entry = this.capabilities.get(capabilityId);
117
+ if (!entry || !this.isEnabled(entry)) throw new Error(`Capability "${capabilityId}" is not available.`);
118
+ const operation = entry.capability.operations[operationId];
119
+ if (!operation) throw new Error(`Capability operation "${capabilityId}.${operationId}" is not available.`);
120
+ if (options.renderer && operation.rendererSafe !== true) {
121
+ throw new Error(`Capability operation "${capabilityId}.${operationId}" is not available to renderers.`);
122
+ }
123
+ return { ...entry, operation };
124
+ }
125
+
126
+ private isEnabled(entry: RegisteredCapability): boolean {
127
+ if (this.options.isPluginEnabled && !this.options.isPluginEnabled(entry.pluginId)) return false;
128
+ if (this.options.isCapabilityEnabled && !this.options.isCapabilityEnabled(entry.capability, entry.pluginId)) return false;
129
+ return entry.capability.isEnabled?.() ?? true;
130
+ }
131
+ }
@@ -0,0 +1,27 @@
1
+ import type { BrokerConnectionStatus } from "../types/broker";
2
+ import type { QuoteSubscriptionTarget } from "../types/data-provider";
3
+ import type { Quote } from "../types/financials";
4
+
5
+ export const BROKER_CAPABILITY_ID = "broker.core";
6
+ export const NOTES_FILES_CAPABILITY_ID = "notes.files";
7
+ export const AI_RUNNER_CAPABILITY_ID = "ai.runner";
8
+
9
+ export type BrokerStatusEvent = {
10
+ kind: "status";
11
+ instanceId: string;
12
+ status: BrokerConnectionStatus;
13
+ };
14
+
15
+ export type BrokerQuoteEvent = {
16
+ kind: "quote";
17
+ target: QuoteSubscriptionTarget;
18
+ quote: Quote;
19
+ };
20
+
21
+ export type BrokerRemoteEvent = BrokerStatusEvent | BrokerQuoteEvent;
22
+
23
+ export type AiRunnerEvent =
24
+ | { kind: "chunk"; output: string }
25
+ | { kind: "done"; output: string }
26
+ | { kind: "cancelled" }
27
+ | { kind: "error"; error: string };
@@ -0,0 +1,84 @@
1
+ import type { CachePolicyMap } from "../types/persistence";
2
+ import type { AssetDataProvider } from "../types/data-provider";
3
+ import type { NewsDataProvider } from "../types/capability-route-source";
4
+
5
+ export type CapabilityOperationKind = "read" | "query" | "action" | "stream";
6
+
7
+ export type CapabilityKind =
8
+ | "asset-data"
9
+ | "news"
10
+ | "plugin-service";
11
+
12
+ export interface CapabilitySchema<T = unknown> {
13
+ parse(value: unknown): T;
14
+ }
15
+
16
+ export interface CapabilityHandlerContext {
17
+ capability: PluginCapability;
18
+ operationId: string;
19
+ }
20
+
21
+ export type CapabilityStreamEmit<T = unknown> = (event: T) => void;
22
+
23
+ export interface CapabilityOperation<I = unknown, O = unknown, E = unknown> {
24
+ kind: CapabilityOperationKind;
25
+ rendererSafe?: boolean;
26
+ input?: CapabilitySchema<I>;
27
+ output?: CapabilitySchema<O>;
28
+ cachePolicy?: CachePolicyMap[keyof CachePolicyMap];
29
+ handler?: (input: I, ctx: CapabilityHandlerContext) => Promise<O> | O;
30
+ subscribe?: (input: I, emit: CapabilityStreamEmit<E>, ctx: CapabilityHandlerContext) => (() => void) | Promise<() => void>;
31
+ }
32
+
33
+ export interface PluginCapability {
34
+ readonly id: string;
35
+ readonly kind: CapabilityKind | string;
36
+ readonly name: string;
37
+ readonly priority?: number;
38
+ readonly cachePolicy?: CachePolicyMap;
39
+ readonly sourceId?: string;
40
+ readonly operations: Record<string, CapabilityOperation<any, any, any>>;
41
+ isEnabled?(): boolean;
42
+ }
43
+
44
+ export interface AssetDataCapability extends PluginCapability {
45
+ readonly kind: "asset-data";
46
+ readonly provider: AssetDataProvider;
47
+ }
48
+
49
+ export interface NewsCapability extends PluginCapability {
50
+ readonly kind: "news";
51
+ readonly provider: NewsDataProvider;
52
+ }
53
+
54
+ export interface CapabilityOperationManifest {
55
+ id: string;
56
+ kind: CapabilityOperationKind;
57
+ rendererSafe: boolean;
58
+ }
59
+
60
+ export interface CapabilityManifest {
61
+ id: string;
62
+ kind: string;
63
+ name: string;
64
+ priority?: number;
65
+ sourceId?: string;
66
+ operations: CapabilityOperationManifest[];
67
+ }
68
+
69
+ export interface CapabilityRegistryOptions {
70
+ isPluginEnabled?(pluginId: string): boolean;
71
+ isCapabilityEnabled?(capability: PluginCapability, pluginId: string): boolean;
72
+ }
73
+
74
+ export interface RegisteredCapability {
75
+ pluginId: string;
76
+ capability: PluginCapability;
77
+ }
78
+
79
+ export const recordSchema: CapabilitySchema<Record<string, unknown>> = {
80
+ parse(value) {
81
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
82
+ return value as Record<string, unknown>;
83
+ },
84
+ };
@@ -14,7 +14,8 @@ import {
14
14
  import { exchangeShortName, marketStateLabel } from "../../utils/market-status";
15
15
  import type { AppConfig } from "../../types/config";
16
16
  import type { FinancialStatement, TickerFinancials } from "../../types/financials";
17
- import type { NewsItem, SecFilingItem } from "../../types/data-provider";
17
+ import type { SecFilingItem } from "../../types/data-provider";
18
+ import type { NewsArticle } from "../../news/types";
18
19
  import type { TickerRecord } from "../../types/ticker";
19
20
  import { createBaseConverter } from "../base-converter";
20
21
  import { initMarketData } from "../context";
@@ -187,7 +188,7 @@ export async function buildTickerReport({
187
188
  config: AppConfig;
188
189
  toBase: (value: number, fromCurrency: string) => Promise<number>;
189
190
  notes?: string;
190
- recentNews?: NewsItem[];
191
+ recentNews?: NewsArticle[];
191
192
  recentSecFilings?: SecFilingItem[];
192
193
  }): Promise<string> {
193
194
  const quote = financials.quote;
@@ -391,7 +392,14 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
391
392
  const notesFiles = new NotesFiles(dataDir);
392
393
  const [notesResult, newsResult, secFilingsResult] = await Promise.allSettled([
393
394
  notesFiles.load(normalized),
394
- dataProvider.getNews(normalized, NEWS_ITEM_LIMIT, exchange || quote.exchangeName || ""),
395
+ dataProvider.getNews({
396
+ feed: "ticker",
397
+ scope: "ticker",
398
+ ticker: normalized,
399
+ exchange: exchange || quote.exchangeName || "",
400
+ tickerTier: "primary",
401
+ limit: NEWS_ITEM_LIMIT,
402
+ }),
395
403
  shouldFetchSecFilings(tickerFile, resolvedFinancials) && dataProvider.getSecFilings
396
404
  ? dataProvider.getSecFilings(normalized, SEC_FILING_LIMIT, exchange || quote.exchangeName || "")
397
405
  : Promise.resolve([]),
@@ -3,12 +3,11 @@ import { existsSync } from "fs";
3
3
  import { getDataDir, loadConfig } from "../data/config-store";
4
4
  import { AppPersistence } from "../data/app-persistence";
5
5
  import { TickerRepository } from "../data/ticker-repository";
6
- import { ProviderRouter } from "../sources/provider-router";
7
- import type { DataProvider } from "../types/data-provider";
6
+ import { AssetDataRouter } from "../sources/provider-router";
7
+ import type { PluginCapability } from "../capabilities";
8
8
  import type { AppConfig } from "../types/config";
9
9
  import type { GloomPlugin } from "../types/plugin";
10
10
  import { getLoadablePlugins } from "../plugins/catalog";
11
- import { createBaseConverter } from "./base-converter";
12
11
  import { fail } from "./errors";
13
12
  import type { ConfigContext, MarketContext } from "./types";
14
13
 
@@ -16,11 +15,19 @@ interface CliContextOptions {
16
15
  plugins?: GloomPlugin[];
17
16
  }
18
17
 
19
- function resolveCliDataProviders(config: AppConfig, plugins: GloomPlugin[]): DataProvider[] {
18
+ function resolveCliCapabilities(config: AppConfig, plugins: GloomPlugin[]): PluginCapability[] {
20
19
  const disabledPlugins = new Set(config.disabledPlugins ?? []);
20
+ const disabledSources = new Set(config.disabledSources ?? []);
21
21
  return plugins
22
- .filter((plugin) => !disabledPlugins.has(plugin.id) && !!plugin.dataProvider)
23
- .map((plugin) => plugin.dataProvider as DataProvider);
22
+ .filter((plugin) => !disabledPlugins.has(plugin.id))
23
+ .flatMap((plugin) => (
24
+ (plugin.capabilities ?? [])
25
+ .filter((capability) => {
26
+ if (capability.kind !== "asset-data" && capability.kind !== "news") return false;
27
+ const sourceId = capability.sourceId ?? capability.id;
28
+ return !disabledSources.has(sourceId);
29
+ })
30
+ ));
24
31
  }
25
32
 
26
33
  export async function loadCliConfigIfAvailable(): Promise<AppConfig | null> {
@@ -46,12 +53,11 @@ export async function initConfigData(): Promise<ConfigContext> {
46
53
  export async function initMarketData(options: CliContextOptions = {}): Promise<MarketContext> {
47
54
  const context = await initConfigData();
48
55
  const plugins = options.plugins ?? getLoadablePlugins();
49
- const dataProvider = new ProviderRouter(
50
- null,
51
- resolveCliDataProviders(context.config, plugins),
52
- context.persistence.resources,
53
- );
56
+ const capabilities = resolveCliCapabilities(context.config, plugins);
57
+ const dataProvider = new AssetDataRouter(null, [], context.persistence.resources);
58
+ dataProvider.attachRegistry({
59
+ brokers: new Map(),
60
+ getEnabledCapabilities: (kind?: string) => capabilities.filter((capability) => !kind || capability.kind === kind),
61
+ } as any);
54
62
  return { ...context, dataProvider };
55
63
  }
56
-
57
- export { createBaseConverter };
@@ -5,6 +5,7 @@ import {
5
5
  formatPercentRaw,
6
6
  } from "../utils/format";
7
7
  import { formatMarketPriceWithCurrency } from "../utils/market-format";
8
+
8
9
  export { slugifyName } from "../utils/slugify";
9
10
  import type { AppConfig } from "../types/config";
10
11
  import type { Watchlist, TickerRecord } from "../types/ticker";
package/src/cli/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AppConfig } from "../types/config";
2
2
  import { AppPersistence } from "../data/app-persistence";
3
3
  import { TickerRepository } from "../data/ticker-repository";
4
- import { ProviderRouter } from "../sources/provider-router";
4
+ import { AssetDataRouter } from "../sources/provider-router";
5
5
 
6
6
  export type ConfigContext = {
7
7
  config: AppConfig;
@@ -11,5 +11,5 @@ export type ConfigContext = {
11
11
  };
12
12
 
13
13
  export type MarketContext = ConfigContext & {
14
- dataProvider: ProviderRouter;
14
+ dataProvider: AssetDataRouter;
15
15
  };
@@ -20,7 +20,7 @@ import {
20
20
  type ViewStateWithViewport = Pick<ChartViewState, "presetRange" | "bufferRange" | "activePreset" | "resolution" | "panOffset" | "zoomLevel" | "cursorX" | "cursorY">
21
21
  | Pick<ComparisonChartViewState, "presetRange" | "bufferRange" | "activePreset" | "resolution" | "panOffset" | "zoomLevel" | "cursorX" | "cursorY">;
22
22
 
23
- export interface VisibleDateWindow {
23
+ interface VisibleDateWindow {
24
24
  start: Date | null;
25
25
  end: Date | null;
26
26
  dates: Date[];
@@ -243,26 +243,6 @@ export function clampDateWindowToBounds(
243
243
  };
244
244
  }
245
245
 
246
- export function scaleDateWindow(
247
- window: DateWindowRange | null | undefined,
248
- spanScale: number,
249
- anchorRatio: number,
250
- ): DateWindowRange | null {
251
- const normalizedWindow = normalizeDateWindowRange(window);
252
- if (!normalizedWindow) return null;
253
-
254
- const currentSpanMs = Math.max(normalizedWindow.endMs - normalizedWindow.startMs, 1);
255
- const nextSpanMs = Math.max(currentSpanMs * spanScale, 1);
256
- const ratio = clamp(anchorRatio, 0, 1);
257
- const anchorMs = normalizedWindow.startMs + (currentSpanMs * ratio);
258
- const nextStartMs = anchorMs - (nextSpanMs * ratio);
259
-
260
- return {
261
- start: new Date(nextStartMs),
262
- end: new Date(nextStartMs + nextSpanMs),
263
- };
264
- }
265
-
266
246
  export function shiftDateWindow(
267
247
  window: DateWindowRange | null | undefined,
268
248
  shiftRatio: number,
@@ -279,7 +259,7 @@ export function shiftDateWindow(
279
259
  };
280
260
  }
281
261
 
282
- export function getCanonicalVisiblePointCount(dates: readonly Date[], presetRange: TimeRange): number {
262
+ function getCanonicalVisiblePointCount(dates: readonly Date[], presetRange: TimeRange): number {
283
263
  if (dates.length === 0) return 0;
284
264
  if (presetRange === "ALL") return dates.length;
285
265
  const endDate = dates[dates.length - 1]!;
@@ -419,7 +399,7 @@ export function formatVisibleSpanLabel(window: Pick<VisibleDateWindow, "start" |
419
399
  return `view:${startLabel}-${endLabel}`;
420
400
  }
421
401
 
422
- export function isCanonicalPresetViewport(
402
+ function isCanonicalPresetViewport(
423
403
  dates: readonly Date[],
424
404
  state: Pick<ViewStateWithViewport, "activePreset" | "panOffset" | "zoomLevel" | "resolution">,
425
405
  ): boolean {
@@ -475,7 +455,7 @@ export function resolveVisibleActivePreset(
475
455
  return needsCanonicalPresetViewportReset(dates, state) ? state.activePreset : null;
476
456
  }
477
457
 
478
- export function resolvePresetSelectionWithResolution<S extends ViewStateWithViewport>(
458
+ function resolvePresetSelectionWithResolution<S extends ViewStateWithViewport>(
479
459
  state: S,
480
460
  presetRange: TimeRange,
481
461
  resolution: ManualChartResolution,
@@ -1,6 +1,5 @@
1
1
  import type { PricePoint } from "../../types/financials";
2
- import type { TimeRange, ChartRenderMode, ChartViewState, VisibleWindow } from "./chart-types";
3
- import { RANGE_DAYS } from "./chart-types";
2
+ import type { ChartRenderMode, ChartViewState, VisibleWindow } from "./chart-types";
4
3
  import { getVisiblePointCount } from "./chart-viewport";
5
4
 
6
5
  export interface ProjectedChartPoint {
@@ -40,7 +39,7 @@ function coerceDate(value: Date | string | number): Date {
40
39
  return value instanceof Date ? value : new Date(value);
41
40
  }
42
41
 
43
- export function getRequestedRenderMode(mode?: ChartRenderMode, compact = false): ChartRenderMode {
42
+ function getRequestedRenderMode(mode?: ChartRenderMode, compact = false): ChartRenderMode {
44
43
  if (compact) return "area";
45
44
  return mode ?? "area";
46
45
  }
@@ -77,16 +76,6 @@ export function resolveRenderMode(
77
76
  };
78
77
  }
79
78
 
80
- /**
81
- * Filter price history to the selected time range.
82
- */
83
- export function filterByTimeRange(history: PricePoint[], range: TimeRange): PricePoint[] {
84
- if (range === "ALL" || history.length <= RANGE_DAYS[range]) {
85
- return history;
86
- }
87
- return history.slice(-RANGE_DAYS[range]);
88
- }
89
-
90
79
  /**
91
80
  * Apply zoom and pan to get the visible data window.
92
81
  */
@@ -232,7 +221,7 @@ export function resolveStableOhlcProjectionOptions({
232
221
  * Downsample close-driven chart data to fit the target width.
233
222
  * Uses representative closes so line/area charts preserve the broad shape.
234
223
  */
235
- export function projectCloseSeries(
224
+ function projectCloseSeries(
236
225
  points: PricePoint[],
237
226
  targetWidth: number,
238
227
  ): ProjectedChartPoint[] {
@@ -1,5 +1,5 @@
1
- import { MultiSelectDialogButton } from "../ui";
2
- import { ChartControlHint } from "./chart-control-hint";
1
+ import { MultiSelectDialogButton, ShortcutHint } from "../ui";
2
+ import { usePaneHints } from "../layout/pane-footer";
3
3
  import {
4
4
  CHART_INDICATOR_OPTIONS,
5
5
  normalizeChartIndicatorSelection,
@@ -10,10 +10,28 @@ interface ChartIndicatorSelectorProps {
10
10
  selectedIds: ChartIndicatorId[];
11
11
  onChange: (selectedIds: ChartIndicatorId[]) => void;
12
12
  width: number;
13
- variant?: "button" | "hint";
13
+ variant?: "button" | "hint" | "pane-hint";
14
14
  shortcutActive?: boolean;
15
15
  }
16
16
 
17
+ function ChartIndicatorPaneHint({
18
+ disabled,
19
+ openDialog,
20
+ }: {
21
+ disabled?: boolean;
22
+ openDialog: () => void;
23
+ }) {
24
+ usePaneHints("chart-indicators", () => [{
25
+ id: "indicators",
26
+ key: "i",
27
+ label: "ndicators",
28
+ disabled,
29
+ onPress: openDialog,
30
+ }], [disabled, openDialog]);
31
+
32
+ return null;
33
+ }
34
+
17
35
  export function ChartIndicatorSelector({
18
36
  selectedIds,
19
37
  onChange,
@@ -36,11 +54,15 @@ export function ChartIndicatorSelector({
36
54
  selectedValues={selectedIds}
37
55
  onChange={(values) => onChange(normalizeChartIndicatorSelection(values))}
38
56
  idPrefix="chart-indicators"
39
- shortcutKey={variant === "hint" ? "i" : undefined}
57
+ shortcutKey={variant === "hint" || variant === "pane-hint" ? "i" : undefined}
40
58
  shortcutActive={shortcutActive}
41
- renderTrigger={variant === "hint"
59
+ renderTrigger={variant === "pane-hint"
60
+ ? ({ disabled, openDialog }) => (
61
+ <ChartIndicatorPaneHint disabled={disabled} openDialog={openDialog} />
62
+ )
63
+ : variant === "hint"
42
64
  ? ({ disabled, openDialog }) => (
43
- <ChartControlHint
65
+ <ShortcutHint
44
66
  hotkey="i"
45
67
  label="ndicators"
46
68
  disabled={disabled}
@@ -1,47 +1,34 @@
1
- import { saveConfig } from "../../data/config-store";
2
1
  import { setPaneSettings } from "../../pane-settings";
3
- import { useAppState, usePaneInstance, usePaneInstanceId } from "../../state/app-context";
2
+ import {
3
+ useAppDispatch,
4
+ useAppStateRef,
5
+ usePaneInstance,
6
+ usePaneInstanceId,
7
+ } from "../../state/app-context";
8
+ import { scheduleConfigSave } from "../../state/config-save-scheduler";
4
9
  import type { ChartResolution, TimeRange } from "./chart-types";
5
- import type { IndicatorConfig } from "./indicators/types";
6
10
 
7
11
  export function usePersistChartControlSelection(rangePresetKey: string): (
8
12
  range: TimeRange,
9
13
  resolution: ChartResolution,
10
14
  ) => void {
11
- const { state, dispatch } = useAppState();
15
+ const dispatch = useAppDispatch();
16
+ const stateRef = useAppStateRef();
12
17
  const paneId = usePaneInstanceId();
13
18
  const pane = usePaneInstance();
14
19
 
15
20
  return (range, resolution) => {
16
- const layout = setPaneSettings(state.config.layout, paneId, {
21
+ const currentState = stateRef.current;
22
+ const layout = setPaneSettings(currentState.config.layout, paneId, {
17
23
  ...(pane?.settings ?? {}),
18
24
  [rangePresetKey]: range,
19
25
  chartResolution: resolution,
20
26
  });
21
- const layouts = state.config.layouts.map((savedLayout, index) => (
22
- index === state.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
27
+ const layouts = currentState.config.layouts.map((savedLayout, index) => (
28
+ index === currentState.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
23
29
  ));
24
- const nextConfig = { ...state.config, layout, layouts };
30
+ const nextConfig = { ...currentState.config, layout, layouts };
25
31
  dispatch({ type: "SET_CONFIG", config: nextConfig });
26
- saveConfig(nextConfig).catch(() => {});
27
- };
28
- }
29
-
30
- export function usePersistIndicatorConfig(): (config: IndicatorConfig) => void {
31
- const { state, dispatch } = useAppState();
32
- const paneId = usePaneInstanceId();
33
- const pane = usePaneInstance();
34
-
35
- return (indicatorConfig) => {
36
- const layout = setPaneSettings(state.config.layout, paneId, {
37
- ...(pane?.settings ?? {}),
38
- indicators: indicatorConfig,
39
- });
40
- const layouts = state.config.layouts.map((savedLayout, index) => (
41
- index === state.config.activeLayoutIndex ? { ...savedLayout, layout } : savedLayout
42
- ));
43
- const nextConfig = { ...state.config, layout, layouts };
44
- dispatch({ type: "SET_CONFIG", config: nextConfig });
45
- saveConfig(nextConfig).catch(() => {});
32
+ scheduleConfigSave(nextConfig);
46
33
  };
47
34
  }