gloomberb 0.10.0 → 0.10.2

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 (353) hide show
  1. package/package.json +8 -1
  2. package/src/api-client/auth.ts +1 -0
  3. package/src/api-client/index.test.ts +359 -108
  4. package/src/api-client/index.ts +24 -13
  5. package/src/api-client/socket.ts +70 -35
  6. package/src/api-client/types.ts +1 -1
  7. package/src/app/language-observer.test.tsx +82 -0
  8. package/src/app/language-observer.tsx +13 -0
  9. package/src/app/pane-runtime/index.tsx +2 -2
  10. package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
  11. package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
  12. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  13. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
  14. package/src/app/runtime/broker-import.ts +2 -2
  15. package/src/app/runtime/plugin-bindings.ts +2 -2
  16. package/src/app/runtime/startup.ts +2 -2
  17. package/src/app.tsx +16 -7
  18. package/src/brokers/account-cache.ts +6 -6
  19. package/src/brokers/broker-ticker-sync.ts +3 -3
  20. package/src/brokers/remote-broker-adapter.ts +2 -2
  21. package/src/brokers/sync-broker-instance.ts +5 -6
  22. package/src/cli/commands/api.ts +3 -6
  23. package/src/cli/commands/automation.ts +7 -15
  24. package/src/cli/commands/market.ts +26 -61
  25. package/src/cli/commands/overview.ts +34 -45
  26. package/src/cli/commands/search.ts +25 -27
  27. package/src/cli/commands/system.ts +27 -57
  28. package/src/cli/commands/ticker.ts +62 -63
  29. package/src/cli/context.test.ts +63 -0
  30. package/src/cli/context.ts +15 -1
  31. package/src/cli/index.ts +1 -1
  32. package/src/cli/pane-functions/data.ts +2 -19
  33. package/src/cli/pane-functions/index.ts +26 -25
  34. package/src/cli/pane-functions/report.ts +1 -1
  35. package/src/cli/pane-functions/screenshot.ts +3 -3
  36. package/src/cli/scoped-context.ts +41 -0
  37. package/src/components/chart/composite/composite-chart.test.tsx +615 -18
  38. package/src/components/chart/composite/composite-chart.tsx +179 -79
  39. package/src/components/chart/composite/format.test.ts +20 -7
  40. package/src/components/chart/composite/index.ts +0 -21
  41. package/src/components/chart/composite/interactions.test.ts +90 -3
  42. package/src/components/chart/composite/interactions.ts +189 -24
  43. package/src/components/chart/composite/price-series.ts +0 -2
  44. package/src/components/chart/composite/rasterizer.ts +10 -9
  45. package/src/components/chart/composite/renderers.test.ts +108 -2
  46. package/src/components/chart/composite/scene.test.ts +36 -0
  47. package/src/components/chart/composite/scene.ts +5 -2
  48. package/src/components/chart/composite/text-renderer.ts +37 -25
  49. package/src/components/chart/composite/time-axis.test.ts +243 -0
  50. package/src/components/chart/composite/time-axis.ts +608 -0
  51. package/src/components/chart/composite/types.ts +12 -1
  52. package/src/components/chart/core/data.ts +2 -159
  53. package/src/components/chart/core/date-window.ts +6 -225
  54. package/src/components/chart/core/palette.ts +1 -12
  55. package/src/components/chart/core/pointer.ts +0 -142
  56. package/src/components/chart/core/types.ts +1 -58
  57. package/src/components/chart/cursor-motion.ts +2 -4
  58. package/src/components/chart/indicators/types.ts +0 -9
  59. package/src/components/chart/market-session.ts +0 -11
  60. package/src/components/chart/native/kitty/protocol.ts +1 -1
  61. package/src/components/chart/native/raster/layout.ts +3 -10
  62. package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
  63. package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
  64. package/src/components/chart/static/chart/surface.tsx +1 -2
  65. package/src/components/command-bar/action-runtime.ts +2 -2
  66. package/src/components/command-bar/commands/direct/index.ts +1 -1
  67. package/src/components/command-bar/panel/body.tsx +2 -2
  68. package/src/components/command-bar/panel/index.tsx +11 -9
  69. package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
  70. package/src/components/command-bar/surface/index.tsx +2 -2
  71. package/src/components/command-bar/workflow/body.tsx +3 -3
  72. package/src/components/command-bar/workflow/collection-actions.ts +2 -2
  73. package/src/components/command-bar/workflow/coordinator.ts +2 -2
  74. package/src/components/command-bar/workflow/field-row.tsx +25 -11
  75. package/src/components/command-bar/workflow/tickers.ts +2 -2
  76. package/src/components/feed-data-table/stack-view.test.tsx +28 -36
  77. package/src/components/feed-data-table/stack-view.tsx +3 -1
  78. package/src/components/layout/detached-pane-shell.tsx +6 -6
  79. package/src/components/layout/header.tsx +13 -9
  80. package/src/components/layout/pane/content.test.tsx +42 -3
  81. package/src/components/layout/pane/content.tsx +3 -1
  82. package/src/components/layout/pane/footer/index.test.tsx +45 -2
  83. package/src/components/layout/pane/footer/registration.tsx +3 -1
  84. package/src/components/layout/shell/index.tsx +1 -2
  85. package/src/components/layout/shell/native/surfaces.ts +1 -1
  86. package/src/components/layout/shell/native/window-state.ts +1 -1
  87. package/src/components/layout/status-bar.tsx +12 -8
  88. package/src/components/layout/window-controls.tsx +8 -6
  89. package/src/components/onboarding/onboarding-wizard.tsx +3 -1
  90. package/src/components/pane-settings-dialog/desktop.tsx +0 -2
  91. package/src/components/pane-settings-dialog.tsx +0 -2
  92. package/src/components/pane-template-wizard.tsx +10 -9
  93. package/src/components/price-performance.tsx +2 -113
  94. package/src/components/price-sparkline/model.ts +15 -17
  95. package/src/components/price-sparkline/view.tsx +8 -7
  96. package/src/components/speedometer-gauge/desktop.tsx +2 -1
  97. package/src/components/speedometer-gauge/index.tsx +0 -2
  98. package/src/components/speedometer-gauge/terminal.tsx +24 -5
  99. package/src/components/ticker/badge/index.tsx +1 -18
  100. package/src/components/ui/data-table/opentui/index.tsx +3 -3
  101. package/src/components/ui/frame.tsx +1 -2
  102. package/src/components/ui/page-stack-view.test.tsx +126 -0
  103. package/src/components/ui/page-stack-view.tsx +24 -12
  104. package/src/components/ui/tabs.tsx +7 -3
  105. package/src/components/ui/ui.test.tsx +23 -2
  106. package/src/components/use-external-link-footer.ts +4 -2
  107. package/src/core/app-service-ports.ts +46 -0
  108. package/src/core/app-services.test.ts +66 -0
  109. package/src/core/app-services.ts +17 -15
  110. package/src/data/config/chart-settings.ts +3 -10
  111. package/src/data/config/store/index.test.ts +99 -2
  112. package/src/data/config/store/migrations.ts +278 -0
  113. package/src/data/config/store/normalize.ts +44 -186
  114. package/src/data/resource-store.ts +73 -12
  115. package/src/data/sqlite/cache.test.ts +104 -15
  116. package/src/i18n/index.test.ts +17 -0
  117. package/src/i18n/index.ts +23 -6
  118. package/src/i18n/ja.ts +13 -0
  119. package/src/i18n/ko.ts +13 -0
  120. package/src/i18n/react.ts +6 -0
  121. package/src/i18n/zh-cn.ts +13 -0
  122. package/src/i18n/zh-tw.ts +13 -0
  123. package/src/market-data/coordinator/auxiliary.ts +7 -3
  124. package/src/market-data/coordinator/chart.ts +1 -1
  125. package/src/market-data/coordinator/index.test.ts +26 -0
  126. package/src/market-data/coordinator/index.ts +11 -5
  127. package/src/market-data/coordinator/quotes.ts +4 -40
  128. package/src/market-data/coordinator/subscriptions.test.ts +89 -1
  129. package/src/market-data/hooks.tsx +47 -1
  130. package/src/market-data/market/status.ts +6 -6
  131. package/src/market-data/performance.ts +1 -1
  132. package/src/market-data/quote-subscription-target.test.ts +37 -0
  133. package/src/market-data/quote-subscription-target.ts +60 -0
  134. package/src/market-data/quotes/freshness.ts +1 -0
  135. package/src/market-data/quotes/resolution.ts +4 -0
  136. package/src/market-data/request-types.ts +3 -3
  137. package/src/news/aggregator.test.ts +97 -1
  138. package/src/news/aggregator.ts +100 -57
  139. package/src/news/hooks.test.tsx +64 -0
  140. package/src/news/hooks.ts +18 -25
  141. package/src/plugins/builtin/account-management/footer.ts +4 -2
  142. package/src/plugins/builtin/account-management/pane.tsx +9 -4
  143. package/src/plugins/builtin/ai/agent-history.ts +11 -7
  144. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
  145. package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
  146. package/src/plugins/builtin/ai/index.test.ts +2 -2
  147. package/src/plugins/builtin/ai/index.tsx +10 -11
  148. package/src/plugins/builtin/ai/pane-settings.ts +1 -1
  149. package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
  150. package/src/plugins/builtin/ai/providers.ts +1 -9
  151. package/src/plugins/builtin/ai/runner.ts +0 -6
  152. package/src/plugins/builtin/ai/screener/footer.ts +4 -1
  153. package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
  154. package/src/plugins/builtin/ai/workspace/model.ts +2 -27
  155. package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
  156. package/src/plugins/builtin/analytics/metrics.ts +1 -7
  157. package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
  158. package/src/plugins/builtin/broker-manager/index.tsx +4 -2
  159. package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
  160. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
  161. package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
  162. package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
  163. package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
  164. package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
  165. package/src/plugins/builtin/chart-composer/index.tsx +0 -6
  166. package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
  167. package/src/plugins/builtin/chart-composer/presets.ts +1 -19
  168. package/src/plugins/builtin/chart-composer/settings.ts +1 -1
  169. package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
  171. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  172. package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
  173. package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
  174. package/src/plugins/builtin/chat/controller/session.ts +2 -0
  175. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  176. package/src/plugins/builtin/chat/controller.test.ts +73 -20
  177. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  178. package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
  179. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
  180. package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
  181. package/src/plugins/builtin/correlation/compute.ts +4 -10
  182. package/src/plugins/builtin/econ/index.tsx +17 -19
  183. package/src/plugins/builtin/holders/types.ts +0 -1
  184. package/src/plugins/builtin/insider/index.tsx +6 -31
  185. package/src/plugins/builtin/news/index.tsx +5 -7
  186. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
  187. package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
  188. package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
  189. package/src/plugins/builtin/news/wire/read-state.ts +18 -28
  190. package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
  191. package/src/plugins/builtin/options/footer.test.ts +115 -0
  192. package/src/plugins/builtin/options/footer.ts +110 -0
  193. package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
  194. package/src/plugins/builtin/options/live-quotes.ts +147 -0
  195. package/src/plugins/builtin/options/view.test.tsx +71 -1
  196. package/src/plugins/builtin/options/view.tsx +39 -24
  197. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
  198. package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
  199. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
  200. package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
  201. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
  202. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
  203. package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
  204. package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
  205. package/src/plugins/builtin/sec/filing-content.ts +94 -0
  206. package/src/plugins/builtin/sec/index.tsx +21 -104
  207. package/src/plugins/builtin/sectors/sector-model.ts +1 -7
  208. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
  209. package/src/plugins/builtin/shared/pane-footer.ts +82 -0
  210. package/src/plugins/builtin/shared/read-state.ts +64 -3
  211. package/src/plugins/builtin/substack/read-state.ts +16 -41
  212. package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
  213. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
  214. package/src/plugins/builtin/yield-curve/index.tsx +11 -9
  215. package/src/plugins/ibkr/dialogs.tsx +3 -3
  216. package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
  217. package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
  218. package/src/plugins/ibkr/gateway/history.ts +2 -2
  219. package/src/plugins/ibkr/gateway/selection.ts +2 -2
  220. package/src/plugins/ibkr/gateway/service/index.ts +2 -2
  221. package/src/plugins/ibkr/gateway/service/native.ts +2 -2
  222. package/src/plugins/plugin-persistence.ts +3 -4
  223. package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
  224. package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
  225. package/src/plugins/prediction-markets/detail/book.tsx +2 -2
  226. package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
  227. package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
  228. package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
  229. package/src/plugins/registry/context.ts +3 -4
  230. package/src/plugins/registry/index.ts +5 -6
  231. package/src/react/input.test.ts +126 -3
  232. package/src/react/input.ts +120 -1
  233. package/src/remote/app-host.tsx +2 -13
  234. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
  235. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
  236. package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
  237. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
  238. package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
  239. package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
  240. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
  241. package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
  242. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
  243. package/src/renderers/electrobun/bun/index.ts +123 -83
  244. package/src/renderers/electrobun/shared/protocol.ts +146 -7
  245. package/src/renderers/electrobun/view/app-services.ts +5 -8
  246. package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
  247. package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
  248. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
  249. package/src/renderers/electrobun/view/config-host.ts +1 -1
  250. package/src/renderers/electrobun/view/context-menu.ts +1 -17
  251. package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
  252. package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
  253. package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
  254. package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
  255. package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
  256. package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
  257. package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
  258. package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
  259. package/src/renderers/electrobun/view/host/box.tsx +0 -1
  260. package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
  261. package/src/renderers/electrobun/view/host/input.tsx +0 -1
  262. package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
  263. package/src/renderers/electrobun/view/host/native.ts +0 -1
  264. package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
  265. package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
  266. package/src/renderers/electrobun/view/host/text.tsx +0 -1
  267. package/src/renderers/electrobun/view/http-fetch.ts +3 -11
  268. package/src/renderers/electrobun/view/input-host.tsx +10 -53
  269. package/src/renderers/electrobun/view/key-event.ts +0 -1
  270. package/src/renderers/electrobun/view/main.tsx +3 -3
  271. package/src/renderers/electrobun/view/native-renderer.ts +0 -1
  272. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
  273. package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
  274. package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
  275. package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
  276. package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
  277. package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
  278. package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
  279. package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
  280. package/src/renderers/electrobun/view/toast-host.tsx +0 -1
  281. package/src/renderers/electrobun/view/ui-host.tsx +1 -2
  282. package/src/renderers/electrobun/view/update-host.ts +1 -1
  283. package/src/renderers/opentui/chart-surface.tsx +2 -1
  284. package/src/renderers/opentui/dialog-host.tsx +20 -4
  285. package/src/renderers/opentui/image/loader.ts +12 -2
  286. package/src/renderers/opentui/input-host.tsx +7 -49
  287. package/src/renderers/opentui/start.tsx +2 -0
  288. package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
  289. package/src/sources/gloomberb-cloud/index.test.ts +173 -0
  290. package/src/sources/gloomberb-cloud/index.ts +42 -20
  291. package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
  292. package/src/sources/provider-router/cache.ts +1 -1
  293. package/src/sources/provider-router/document-routes.ts +10 -5
  294. package/src/sources/provider-router/financials.test.ts +14 -0
  295. package/src/sources/provider-router/financials.ts +6 -1
  296. package/src/sources/provider-router/history.test.ts +163 -1
  297. package/src/sources/provider-router/history.ts +278 -347
  298. package/src/sources/provider-router/index.ts +2 -2
  299. package/src/sources/provider-router/routing.test.ts +39 -0
  300. package/src/sources/provider-router/routing.ts +24 -11
  301. package/src/sources/provider-router/supplemental.ts +37 -17
  302. package/src/sources/sec-edgar/content.ts +2 -12
  303. package/src/sources/sec-edgar.ts +2 -13
  304. package/src/sources/yahoo-finance/history.ts +2 -2
  305. package/src/sources/yahoo-finance/options.ts +5 -0
  306. package/src/sources/yahoo-finance.ts +2 -2
  307. package/src/state/app/bootstrap.ts +2 -2
  308. package/src/state/app/context/index.tsx +2 -2
  309. package/src/state/hooks/quote-streaming.ts +66 -1
  310. package/src/state/hooks/session-snapshot.ts +2 -2
  311. package/src/state/selectors.ts +0 -9
  312. package/src/sync/controller.test.ts +98 -0
  313. package/src/sync/controller.ts +29 -5
  314. package/src/sync/react.ts +2 -2
  315. package/src/sync/types.ts +2 -2
  316. package/src/theme/colors.test.ts +10 -0
  317. package/src/theme/colors.ts +75 -78
  318. package/src/theme/theme-context.test.tsx +67 -0
  319. package/src/theme/theme-context.tsx +9 -5
  320. package/src/tickers/open-target.ts +2 -2
  321. package/src/tickers/search/upsert.ts +2 -2
  322. package/src/time-series/alignment.ts +0 -7
  323. package/src/time-series/chart-data.ts +145 -0
  324. package/src/time-series/date-window.ts +44 -0
  325. package/src/time-series/history-window.ts +21 -0
  326. package/src/time-series/hooks.test.ts +59 -0
  327. package/src/time-series/hooks.ts +33 -12
  328. package/src/time-series/market-resolution.ts +2 -2
  329. package/src/time-series/market.ts +1 -1
  330. package/src/time-series/range.ts +9 -0
  331. package/src/{components/chart/core → time-series}/resolution.ts +3 -102
  332. package/src/time-series/resolve.test.ts +203 -1
  333. package/src/time-series/resolve.ts +96 -18
  334. package/src/time-series/spec-studies.test.ts +15 -0
  335. package/src/time-series/spec.ts +2 -2
  336. package/src/time-series/studies.ts +5 -2
  337. package/src/time-series/types.ts +3 -1
  338. package/src/time-series/use-chart-resolution.test.tsx +358 -0
  339. package/src/time-series/use-chart-resolution.ts +46 -4
  340. package/src/types/broker.ts +2 -2
  341. package/src/types/data-provider.ts +3 -3
  342. package/src/types/financials.ts +12 -0
  343. package/src/types/plugin.ts +2 -2
  344. package/src/ui/context-menu.tsx +3 -2
  345. package/src/ui/dialog.tsx +2 -2
  346. package/src/utils/html-entities.ts +3 -5
  347. package/src/utils/main-thread-monitor.ts +1 -1
  348. package/src/utils/price-history.ts +5 -5
  349. package/src/version.ts +3 -1
  350. package/src/components/chart/core/viewport.test.ts +0 -8
  351. package/src/components/chart/core/viewport.ts +0 -24
  352. package/src/time-series/index.ts +0 -14
  353. /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
@@ -29,21 +29,33 @@ export function PageStackView({
29
29
  backHint,
30
30
  }: PageStackViewProps) {
31
31
  const HostPageStackView = useUiHost().PageStackView as ComponentType<PageStackViewProps> | undefined;
32
+ const props = {
33
+ focused,
34
+ detailOpen,
35
+ onBack,
36
+ rootContent,
37
+ detailContent,
38
+ detailTitle,
39
+ backLabel,
40
+ backHint,
41
+ };
32
42
  if (HostPageStackView) {
33
- return (
34
- <HostPageStackView
35
- focused={focused}
36
- detailOpen={detailOpen}
37
- onBack={onBack}
38
- rootContent={rootContent}
39
- detailContent={detailContent}
40
- detailTitle={detailTitle}
41
- backLabel={backLabel}
42
- backHint={backHint}
43
- />
44
- );
43
+ return <HostPageStackView {...props} />;
45
44
  }
46
45
 
46
+ return <OpenTuiPageStackView {...props} />;
47
+ }
48
+
49
+ function OpenTuiPageStackView({
50
+ focused,
51
+ detailOpen,
52
+ onBack,
53
+ rootContent,
54
+ detailContent,
55
+ detailTitle,
56
+ backLabel,
57
+ backHint,
58
+ }: PageStackViewProps) {
47
59
  const goBack = useCallback((event?: {
48
60
  preventDefault?: () => void;
49
61
  stopPropagation?: () => void;
@@ -2,8 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { useShortcut } from "../../react/input";
3
3
  import { Box, ScrollBox, Text, useUiHost } from "../../ui";
4
4
  import { TextAttributes, type ScrollBoxRenderable } from "../../ui";
5
- import { colors, hoverBg } from "../../theme/colors";
5
+ import { hoverBg } from "../../theme/colors";
6
+ import { useThemeColors } from "../../theme/theme-context";
6
7
  import { t } from "../../i18n";
8
+ import { useAppLanguage } from "../../i18n/react";
7
9
  import { displayWidth } from "../../utils/format";
8
10
  import { useRemoteUiNode } from "../../remote/semantic-tree";
9
11
 
@@ -55,9 +57,11 @@ export function Tabs({
55
57
  scrollable = true,
56
58
  scrollId,
57
59
  }: TabsProps) {
60
+ const language = useAppLanguage();
61
+ const colors = useThemeColors();
58
62
  const tabs = useMemo(() => rawTabs.map((tab) => (
59
63
  { ...tab, label: t(tab.label) }
60
- )), [rawTabs]);
64
+ )), [language, rawTabs]);
61
65
  const ui = useUiHost();
62
66
  const NativeTabs = ui.Tabs;
63
67
  const navigationValueRef = useRef<string | null>(activeValue);
@@ -74,7 +78,7 @@ export function Tabs({
74
78
  activeUnderline: focused ? colors.textBright : colors.borderFocused,
75
79
  inactiveUnderline: colors.bg,
76
80
  hoverUnderline: colors.border,
77
- hoverBg: hoverBg(),
81
+ hoverBg: hoverBg(colors),
78
82
  activeBg: colors.selected,
79
83
  activePillFg: colors.selectedText,
80
84
  closeFg: colors.textMuted,
@@ -19,6 +19,7 @@ import { Tabs } from "./tabs";
19
19
  import { AppContext, PaneInstanceProvider, createInitialState } from "../../state/app/context";
20
20
  import { createDefaultConfig } from "../../types/config";
21
21
  import { DataTableView } from "../data-table/view";
22
+ import { setLanguage } from "../../i18n";
22
23
 
23
24
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
24
25
  let setListSelection: ((index: number) => void) | null = null;
@@ -277,9 +278,9 @@ async function emitKeypress(event: { name?: string; sequence?: string; ctrl?: bo
277
278
  await testSetup!.renderOnce();
278
279
  }
279
280
 
280
- afterEach(() => {
281
+ afterEach(async () => {
281
282
  if (testSetup) {
282
- testSetup.renderer.destroy();
283
+ await act(async () => testSetup?.renderer.destroy());
283
284
  testSetup = undefined;
284
285
  }
285
286
  setListSelection = null;
@@ -290,9 +291,29 @@ afterEach(() => {
290
291
  addedTab = false;
291
292
  resolvedChoice = null;
292
293
  multiSelectOpenStates = [];
294
+ setLanguage("en");
293
295
  });
294
296
 
295
297
  describe("shared UI kit", () => {
298
+ test("retranslates stable tab items when the app language changes", async () => {
299
+ const tabs = [{ label: "Open", value: "open" }];
300
+ testSetup = await testRender(
301
+ <Tabs tabs={tabs} activeValue="open" onSelect={() => {}} />,
302
+ { width: 20, height: 2 },
303
+ );
304
+
305
+ await act(async () => testSetup?.renderOnce());
306
+ expect(testSetup.captureCharFrame()).toContain("Open");
307
+
308
+ await act(async () => {
309
+ setLanguage("zh-CN");
310
+ await testSetup?.renderOnce();
311
+ await testSetup?.renderOnce();
312
+ });
313
+
314
+ expect(testSetup.captureCharFrame()).toContain("打开");
315
+ });
316
+
296
317
  test("scrolls overflowing tabs horizontally with the mouse wheel", async () => {
297
318
  testSetup = await testRender(
298
319
  <Tabs
@@ -15,6 +15,7 @@ interface UseExternalLinkFooterOptions {
15
15
  info?: PaneFooterSegment[];
16
16
  hints?: PaneHint[];
17
17
  label?: string;
18
+ showHint?: boolean;
18
19
  }
19
20
 
20
21
  const EMPTY_INFO: PaneFooterSegment[] = [];
@@ -33,6 +34,7 @@ export function useExternalLinkFooter({
33
34
  info = EMPTY_INFO,
34
35
  hints = EMPTY_HINTS,
35
36
  label = "link",
37
+ showHint = true,
36
38
  }: UseExternalLinkFooterOptions) {
37
39
  const rendererHost = useRendererHost();
38
40
  const url = normalizeUrl(rawUrl);
@@ -68,10 +70,10 @@ export function useExternalLinkFooter({
68
70
  info: [...info, ...linkInfo],
69
71
  hints: [
70
72
  ...hints,
71
- ...(url ? [{ id: "open", key: "o", label: "pen", onPress: openUrl }] : []),
73
+ ...(url && showHint ? [{ id: "open", key: "o", label: "pen", onPress: openUrl }] : []),
72
74
  ],
73
75
  };
74
- }, [hints, info, label, openUrl, sourceLabel, url]);
76
+ }, [hints, info, label, openUrl, showHint, sourceLabel, url]);
75
77
 
76
78
  usePaneFooter(registrationId, () => footer, [footer]);
77
79
 
@@ -0,0 +1,46 @@
1
+ import type { PluginStateStore } from "../data/plugin-state-store";
2
+ import type { ResourceStore } from "../data/resource-store";
3
+ import type { SessionStore } from "../data/session-store";
4
+ import type { TickerRepository } from "../data/ticker-repository";
5
+ import type { MarketDataCoordinator } from "../market-data/coordinator";
6
+ import type { PluginRegistry } from "../plugins/registry";
7
+ import type { AppConfig } from "../types/config";
8
+ import type { DataProvider } from "../types/data-provider";
9
+ import type { GloomPlugin } from "../types/plugin";
10
+
11
+ export type AppPluginStateStorePort = Pick<
12
+ PluginStateStore,
13
+ "get" | "set" | "delete" | "keys" | "clear"
14
+ >;
15
+
16
+ export type AppResourceStorePort = Pick<ResourceStore, "get" | "list" | "set" | "delete">;
17
+ export type AppSessionStorePort = Pick<SessionStore, "get" | "set" | "delete">;
18
+
19
+ export interface AppPersistencePort {
20
+ pluginState: AppPluginStateStorePort;
21
+ resources: AppResourceStorePort;
22
+ sessions: AppSessionStorePort;
23
+ close(): void;
24
+ }
25
+
26
+ export type AppTickerRepositoryPort = Pick<
27
+ TickerRepository,
28
+ "loadAllTickers" | "loadTicker" | "saveTicker" | "createTicker" | "deleteTicker"
29
+ >;
30
+
31
+ export interface AppRuntimeServices {
32
+ persistence: AppPersistencePort;
33
+ tickerRepository: AppTickerRepositoryPort;
34
+ dataProvider: DataProvider;
35
+ marketData: MarketDataCoordinator;
36
+ pluginRegistry: PluginRegistry;
37
+ ready: Promise<void>;
38
+ destroy(): void;
39
+ }
40
+
41
+ export interface AppServicesFactoryOptions {
42
+ config: AppConfig;
43
+ plugins: readonly GloomPlugin[];
44
+ }
45
+
46
+ export type AppServicesFactory = (options: AppServicesFactoryOptions) => AppRuntimeServices;
@@ -0,0 +1,66 @@
1
+ import { afterEach, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { newsProvider } from "../capabilities";
6
+ import type { NewsArticle } from "../news/types";
7
+ import { createDefaultConfig } from "../types/config";
8
+ import type { GloomPlugin } from "../types/plugin";
9
+ import { createAppServices, type AppServices } from "./app-services";
10
+
11
+ let services: AppServices | null = null;
12
+ let dataDir: string | null = null;
13
+
14
+ afterEach(async () => {
15
+ services?.destroy();
16
+ services = null;
17
+ if (dataDir) await rm(dataDir, { recursive: true, force: true });
18
+ dataDir = null;
19
+ });
20
+
21
+ test("the router does not re-enter the aggregate news facade", async () => {
22
+ dataDir = await mkdtemp(join(tmpdir(), "gloomberb-news-composition-"));
23
+ let fetchCount = 0;
24
+ const article: NewsArticle = {
25
+ id: "composition-story",
26
+ title: "Composition story",
27
+ url: "https://composition.example.com/story",
28
+ source: "Test",
29
+ publishedAt: new Date("2026-07-26T12:00:00.000Z"),
30
+ topic: "markets",
31
+ topics: ["markets"],
32
+ sectors: [],
33
+ categories: ["markets"],
34
+ tickers: [],
35
+ scores: { importance: 50, urgency: 0, marketImpact: 50, novelty: 0, confidence: 100 },
36
+ importance: 50,
37
+ isBreaking: false,
38
+ isDeveloping: false,
39
+ };
40
+ const plugin: GloomPlugin = {
41
+ id: "composition-news",
42
+ name: "Composition News",
43
+ version: "1.0.0",
44
+ capabilities: [newsProvider({
45
+ id: "composition-news",
46
+ name: "Composition News",
47
+ provider: {
48
+ fetchNews: async () => {
49
+ fetchCount++;
50
+ return [article];
51
+ },
52
+ },
53
+ })],
54
+ };
55
+
56
+ services = createAppServices({
57
+ config: createDefaultConfig(dataDir),
58
+ plugins: [plugin],
59
+ });
60
+ await services.ready;
61
+
62
+ const articles = await services.providerRouter.getNews({ feed: "latest", limit: 20 });
63
+
64
+ expect(fetchCount).toBe(1);
65
+ expect(articles.map((item) => item.id)).toEqual([article.id]);
66
+ });
@@ -7,16 +7,15 @@ import { setSharedNewsService } from "../news/hooks";
7
7
  import { PluginRegistry } from "../plugins/registry";
8
8
  import { AssetDataRouter } from "../sources/provider-router";
9
9
  import { assetDataProvider, newsProvider } from "../capabilities";
10
- import type { AppConfig } from "../types/config";
11
10
  import type { DataProvider } from "../types/data-provider";
12
- import type { GloomPlugin } from "../types/plugin";
13
11
  import { debugLog } from "../utils/debug-log";
14
12
  import { measurePerf, measurePerfAsync } from "../utils/perf-marks";
15
13
  import { setIbkrPortfolioPerformanceResourceStore } from "../plugins/ibkr/portfolio-performance";
14
+ import type { AppRuntimeServices, AppServicesFactoryOptions } from "./app-service-ports";
16
15
 
17
16
  const servicesLog = debugLog.createLogger("services");
18
17
 
19
- export interface AppServices {
18
+ export interface AppServices extends AppRuntimeServices {
20
19
  persistence: AppPersistence;
21
20
  tickerRepository: TickerRepository;
22
21
  providerRouter: AssetDataRouter;
@@ -31,10 +30,7 @@ export interface AppServices {
31
30
  export function createAppServices({
32
31
  config,
33
32
  plugins,
34
- }: {
35
- config: AppConfig;
36
- plugins: readonly GloomPlugin[];
37
- }): AppServices {
33
+ }: AppServicesFactoryOptions): AppServices {
38
34
  servicesLog.info("create services start", {
39
35
  pluginCount: plugins.length,
40
36
  brokerInstanceCount: config.brokerInstances.length,
@@ -49,14 +45,20 @@ export function createAppServices({
49
45
  const pluginRegistry = new PluginRegistry(dataProvider, tickerRepository, persistence);
50
46
  const newsService = new NewsService();
51
47
  pluginRegistry.capabilities.register("core", assetDataProvider(providerRouter));
52
- pluginRegistry.capabilities.register("core", newsProvider({
53
- id: "core",
54
- name: "News",
55
- provider: {
56
- fetchNews: async (query) => (await newsService.load(query)).articles,
57
- getCachedNews: (query) => newsService.getQueryState(query).articles,
58
- },
59
- }));
48
+ pluginRegistry.capabilities.register("core", {
49
+ ...newsProvider({
50
+ id: "core",
51
+ name: "News",
52
+ provider: {
53
+ fetchNews: async (query) => (await newsService.load(query)).articles,
54
+ getCachedNews: (query) => newsService.getQueryState(query).articles,
55
+ },
56
+ }),
57
+ // This capability exposes the aggregate service to remote renderers. Giving
58
+ // it the router source id prevents the router from rediscovering its own
59
+ // aggregate facade alongside the underlying plugin news sources.
60
+ sourceId: providerRouter.id,
61
+ });
60
62
 
61
63
  providerRouter.attachRegistry(pluginRegistry);
62
64
  pluginRegistry.getConfigFn = () => config;
@@ -1,4 +1,4 @@
1
- import type { ChartResolution, TimeRange } from "../../components/chart/core/types";
1
+ import type { ChartResolution, TimeRange } from "../../time-series/range";
2
2
  import type { LayoutConfig, PaneBinding } from "../../types/config";
3
3
  import type {
4
4
  ChartPanelSpec,
@@ -55,6 +55,7 @@ export interface LegacyChartIndicatorSelection {
55
55
  }
56
56
 
57
57
  export interface LegacyChartMigrationContext {
58
+ migrateLegacy?: boolean;
58
59
  defaultRenderMode?: SeriesStyle;
59
60
  indicatorSelection?: LegacyChartIndicatorSelection;
60
61
  }
@@ -362,6 +363,7 @@ export function migrateChartPaneSettings(
362
363
  settings: Record<string, unknown> | undefined,
363
364
  context: LegacyChartMigrationContext = {},
364
365
  ): Record<string, unknown> | undefined {
366
+ if (context.migrateLegacy === false) return settings;
365
367
  if (paneId === "chart-composer") {
366
368
  const retained = stripKeys(settings, [
367
369
  "chartAxisMode",
@@ -495,15 +497,6 @@ export function extractLegacyChartIndicatorSelection(
495
497
  : { ids: normalizedLegacyIndicatorIds(["volume", ...ids]), explicit: true };
496
498
  }
497
499
 
498
- export function hasLegacyChartIndicatorConfig(
499
- pluginConfig: Record<string, Record<string, unknown>>,
500
- ): boolean {
501
- return ["ticker-research", "ticker-detail", "company-research"].some((pluginId) => {
502
- const state = pluginConfig[pluginId];
503
- return hasOwn(state, "chartIndicators") || hasOwn(state, "chartIndicatorsVersion");
504
- });
505
- }
506
-
507
500
  export function stripLegacyChartPluginConfig(
508
501
  pluginConfig: Record<string, Record<string, unknown>>,
509
502
  ): Record<string, Record<string, unknown>> {
@@ -648,6 +648,28 @@ describe("loadConfig", () => {
648
648
  expect(config.disabledPlugins).toEqual(["news"]);
649
649
  });
650
650
 
651
+ test("runs the Cloud-to-Macro split only before its v15 boundary", async () => {
652
+ const beforeDir = await createTempConfigDir();
653
+ await writeConfigJson(beforeDir, createSavedConfig({
654
+ configVersion: 14,
655
+ disabledPlugins: ["gloomberb-cloud"],
656
+ disabledSources: [],
657
+ }));
658
+ const before = await loadConfig(beforeDir);
659
+ expect(before.disabledPlugins).toEqual(["gloomberb-cloud", "macro"]);
660
+ expect(before.disabledSources).toEqual(["gloomberb-cloud"]);
661
+
662
+ const atBoundaryDir = await createTempConfigDir();
663
+ await writeConfigJson(atBoundaryDir, createSavedConfig({
664
+ configVersion: 15,
665
+ disabledPlugins: ["gloomberb-cloud"],
666
+ disabledSources: [],
667
+ }));
668
+ const atBoundary = await loadConfig(atBoundaryDir);
669
+ expect(atBoundary.disabledPlugins).toEqual(["gloomberb-cloud"]);
670
+ expect(atBoundary.disabledSources).toEqual([]);
671
+ });
672
+
651
673
  test("preserves IBKR gateway configs without migration rewrites", async () => {
652
674
  const dataDir = await createTempConfigDir();
653
675
  await writeConfigJson(dataDir, createSavedConfig({
@@ -679,6 +701,7 @@ describe("loadConfig", () => {
679
701
  test("migrates legacy saved pane tab IDs and preserves focus metadata", async () => {
680
702
  const dataDir = await createTempConfigDir();
681
703
  await writeConfigJson(dataDir, createSavedConfig({
704
+ configVersion: CURRENT_CONFIG_VERSION - 1,
682
705
  layouts: [{
683
706
  name: "Chart",
684
707
  layout: DEFAULT_LAYOUT,
@@ -726,6 +749,80 @@ describe("loadConfig", () => {
726
749
  expect(persisted.layouts[0]?.activePanel).toBe("right");
727
750
  });
728
751
 
752
+ test("does not replay historical migrations for current configs or saves", async () => {
753
+ const dataDir = await createTempConfigDir();
754
+ const selectedPortfolioColumns = DEFAULT_COLUMNS.map((column) => column.id);
755
+ const currentLayout = {
756
+ ...DEFAULT_LAYOUT,
757
+ instances: DEFAULT_LAYOUT.instances.map((instance) => (
758
+ instance.instanceId === "ticker-detail:main"
759
+ ? {
760
+ ...instance,
761
+ settings: {
762
+ ...(instance.settings ?? {}),
763
+ chartRangePreset: "6M",
764
+ chartResolution: "1d",
765
+ },
766
+ }
767
+ : instance.instanceId === "portfolio-list:main"
768
+ ? {
769
+ ...instance,
770
+ settings: {
771
+ ...(instance.settings ?? {}),
772
+ columnIds: selectedPortfolioColumns,
773
+ },
774
+ }
775
+ : instance
776
+ )),
777
+ };
778
+ const legacyPaneState = {
779
+ "ticker-detail:main": {
780
+ activeTabId: "fundamental-graphs",
781
+ pluginState: {
782
+ "ticker-detail": { detailMetric: "revenue", shared: "legacy" },
783
+ "ticker-research": { shared: "canonical" },
784
+ },
785
+ },
786
+ };
787
+ const legacyPluginConfig = {
788
+ "ticker-detail": {
789
+ chartIndicators: ["sma50"],
790
+ chartIndicatorsVersion: 2,
791
+ retainedPreference: "keep",
792
+ },
793
+ };
794
+ await writeConfigJson(dataDir, createSavedConfig({
795
+ layout: currentLayout,
796
+ layouts: [{ name: "Current", layout: currentLayout, paneState: legacyPaneState }],
797
+ pluginConfig: legacyPluginConfig,
798
+ disabledPlugins: ["options", "gloomberb-cloud"],
799
+ disabledSources: [],
800
+ }));
801
+
802
+ const config = await loadConfig(dataDir);
803
+ expect(findPaneInstance(config.layout, "ticker-detail:main")?.settings).toEqual(expect.objectContaining({
804
+ chartRangePreset: "6M",
805
+ chartResolution: "1d",
806
+ }));
807
+ expect(config.layouts[0]?.paneState).toEqual(legacyPaneState);
808
+ expect(config.pluginConfig).toEqual(legacyPluginConfig);
809
+ expect(config.disabledPlugins).toEqual(["options", "gloomberb-cloud"]);
810
+ expect(findPaneInstance(config.layout, "portfolio-list:main")?.settings?.columnIds)
811
+ .toEqual(selectedPortfolioColumns);
812
+
813
+ await saveConfig(config);
814
+ const persisted = JSON.parse(await readFile(join(dataDir, "config.json"), "utf-8")) as typeof config;
815
+ expect(persisted.layouts[0]?.paneState).toEqual(legacyPaneState);
816
+ expect(persisted.pluginConfig).toEqual(legacyPluginConfig);
817
+ expect(persisted.disabledPlugins).toEqual(["options", "gloomberb-cloud"]);
818
+ expect(findPaneInstance(persisted.layout, "portfolio-list:main")?.settings?.columnIds)
819
+ .toEqual(selectedPortfolioColumns);
820
+ expect(findPaneInstance(persisted.layout, "ticker-detail:main")?.settings).toEqual(expect.objectContaining({
821
+ chartRangePreset: "6M",
822
+ chartResolution: "1d",
823
+ }));
824
+ });
825
+
729
826
  test("migrates legacy main portfolio panes to the portfolio default columns", async () => {
730
827
  const dataDir = await createTempConfigDir();
731
828
  const legacyColumnIds = DEFAULT_COLUMNS.map((column) => column.id);
@@ -757,7 +854,7 @@ describe("loadConfig", () => {
757
854
  .toEqual(DEFAULT_PORTFOLIO_COLUMN_IDS);
758
855
  });
759
856
 
760
- test("does not replay the portfolio column migration for version 19 configs", async () => {
857
+ test("does not replay the portfolio column migration at its v17 boundary", async () => {
761
858
  const dataDir = await createTempConfigDir();
762
859
  const selectedColumnIds = DEFAULT_COLUMNS.map((column) => column.id);
763
860
  const layout = {
@@ -776,7 +873,7 @@ describe("loadConfig", () => {
776
873
  };
777
874
 
778
875
  await writeConfigJson(dataDir, createSavedConfig({
779
- configVersion: 19,
876
+ configVersion: 17,
780
877
  layout,
781
878
  layouts: [{ name: "Default", layout }],
782
879
  }));