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
@@ -0,0 +1,134 @@
1
+ import { type ReactNode } from "react";
2
+ import { Box, Text } from "../../../../ui";
3
+ import { Button, Checkbox } from "../../../../components/ui";
4
+ import { NativeSelect } from "../../../../components/ui/native-select";
5
+ import { colors } from "../../../../theme/colors";
6
+ import type {
7
+ SeriesEditorActionModel,
8
+ SeriesEditorFieldId,
9
+ SeriesEditorFieldModel,
10
+ } from "./model";
11
+
12
+ function DesktopEditorField({
13
+ label,
14
+ children,
15
+ width = "calc(50% - 6px)",
16
+ }: {
17
+ label: string;
18
+ children: ReactNode;
19
+ width?: string;
20
+ }) {
21
+ return (
22
+ <Box flexDirection="column" width={width} minWidth="220px" style={{ gap: 4 }}>
23
+ <Text fg={colors.textDim} style={{ fontWeight: 600 }}>{label}</Text>
24
+ {children}
25
+ </Box>
26
+ );
27
+ }
28
+
29
+ const SECURITY_FIELD_ORDER: SeriesEditorFieldId[] = [
30
+ "style",
31
+ "transform",
32
+ "axis",
33
+ "period",
34
+ "timing",
35
+ "panel",
36
+ "scale",
37
+ "visibility",
38
+ ];
39
+
40
+ const NON_SECURITY_FIELD_ORDER: SeriesEditorFieldId[] = [
41
+ "style",
42
+ "transform",
43
+ "axis",
44
+ "visibility",
45
+ "panel",
46
+ "scale",
47
+ ];
48
+
49
+ export function DesktopSeriesEditorFields({ fields }: { fields: SeriesEditorFieldModel[] }) {
50
+ const fieldById = new Map(fields.map((field) => [field.id, field]));
51
+ const isSecurity = fieldById.has("period");
52
+ const orderedFields = (isSecurity ? SECURITY_FIELD_ORDER : NON_SECURITY_FIELD_ORDER)
53
+ .map((id) => fieldById.get(id))
54
+ .filter((field): field is SeriesEditorFieldModel => !!field);
55
+
56
+ return (
57
+ <Box
58
+ flexDirection="row"
59
+ flexWrap="wrap"
60
+ width="100%"
61
+ style={{ columnGap: 12, rowGap: 10 }}
62
+ >
63
+ {orderedFields.map((field) => (
64
+ <DesktopEditorField
65
+ key={field.id}
66
+ label={field.label}
67
+ width={field.id === "visibility" && isSecurity ? "100%" : undefined}
68
+ >
69
+ {field.kind === "visibility" ? (
70
+ <Box height="28px" justifyContent="center">
71
+ <Checkbox
72
+ label="Show series"
73
+ checked={field.value === "shown"}
74
+ variant="desktop"
75
+ onChange={(checked) => field.onChange(checked ? "shown" : "hidden")}
76
+ />
77
+ </Box>
78
+ ) : field.kind === "panel" ? (
79
+ <Box flexDirection="row" width="100%" style={{ gap: 6 }}>
80
+ <Box flexGrow={1} minWidth={0}>
81
+ <NativeSelect
82
+ value={field.value}
83
+ options={field.options}
84
+ width="100%"
85
+ onChange={field.onChange}
86
+ />
87
+ </Box>
88
+ <Button label="New Panel" onPress={field.onAddPanel} />
89
+ </Box>
90
+ ) : (
91
+ <NativeSelect
92
+ value={field.value}
93
+ options={field.options}
94
+ width="100%"
95
+ onChange={field.onChange}
96
+ />
97
+ )}
98
+ </DesktopEditorField>
99
+ ))}
100
+ </Box>
101
+ );
102
+ }
103
+
104
+ export function DesktopSeriesEditorActions({ actions }: { actions: SeriesEditorActionModel[] }) {
105
+ const seriesActions = actions.filter((action) => action.group === "series");
106
+ const confirmActions = actions.filter((action) => action.group === "confirm");
107
+ return (
108
+ <Box flexDirection="row" gap={1} width="100%" style={{ gap: 6, paddingTop: 2 }}>
109
+ <Box flexDirection="row" style={{ gap: 6 }}>
110
+ {seriesActions.map((action) => (
111
+ <Button
112
+ key={action.id}
113
+ label={action.label}
114
+ variant={action.variant}
115
+ disabled={action.disabled}
116
+ onPress={action.onPress}
117
+ />
118
+ ))}
119
+ </Box>
120
+ <Box flexGrow={1} />
121
+ <Box flexDirection="row" style={{ gap: 6 }}>
122
+ {confirmActions.map((action) => (
123
+ <Button
124
+ key={action.id}
125
+ label={action.label}
126
+ variant={action.variant}
127
+ disabled={action.disabled}
128
+ onPress={action.onPress}
129
+ />
130
+ ))}
131
+ </Box>
132
+ </Box>
133
+ );
134
+ }
@@ -0,0 +1,85 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { appendChartSeries, buildFundamentalChartPreset, buildPriceChartPreset } from "../presets";
3
+ import {
4
+ buildSeriesEditorActions,
5
+ buildSeriesEditorFields,
6
+ type SeriesEditorActionHandlers,
7
+ type SeriesEditorFieldHandlers,
8
+ } from "./model";
9
+
10
+ const fieldHandlers: SeriesEditorFieldHandlers = {
11
+ setStyle() {},
12
+ setTransform() {},
13
+ setAxis() {},
14
+ setVisibility() {},
15
+ setPanel() {},
16
+ addPanel() {},
17
+ setScale() {},
18
+ setPeriod() {},
19
+ setTimestampMode() {},
20
+ };
21
+
22
+ const actionHandlers: SeriesEditorActionHandlers = {
23
+ add() {},
24
+ remove() {},
25
+ moveUp() {},
26
+ moveDown() {},
27
+ cancel() {},
28
+ save() {},
29
+ };
30
+
31
+ describe("series editor model", () => {
32
+ test("builds the full fundamental field sequence and guards the final visible series", () => {
33
+ const draft = buildFundamentalChartPreset(["AAPL"]);
34
+ const fields = buildSeriesEditorFields({
35
+ draft,
36
+ selected: draft.series[0]!,
37
+ handlers: fieldHandlers,
38
+ });
39
+
40
+ expect(fields.map((field) => field.id)).toEqual([
41
+ "style",
42
+ "transform",
43
+ "axis",
44
+ "visibility",
45
+ "panel",
46
+ "scale",
47
+ "period",
48
+ "timing",
49
+ ]);
50
+ expect(fields.find((field) => field.id === "visibility")?.options).toEqual([
51
+ { value: "shown", label: "Shown" },
52
+ { value: "hidden", label: "Hidden", disabled: true },
53
+ ]);
54
+ });
55
+
56
+ test("keeps series actions aligned with selection and final-series boundaries", () => {
57
+ const single = buildPriceChartPreset("AAPL");
58
+ const singleActions = buildSeriesEditorActions({
59
+ draft: single,
60
+ selected: single.series[0]!,
61
+ selectedIndex: 0,
62
+ handlers: actionHandlers,
63
+ });
64
+ expect(singleActions.filter((action) => action.disabled).map((action) => action.id)).toEqual([
65
+ "remove",
66
+ "move-up",
67
+ "move-down",
68
+ ]);
69
+
70
+ const appended = appendChartSeries(single, {
71
+ kind: "security",
72
+ symbol: "MSFT",
73
+ fieldId: "market.ohlcv",
74
+ }).spec;
75
+ const appendedActions = buildSeriesEditorActions({
76
+ draft: appended,
77
+ selected: appended.series[1]!,
78
+ selectedIndex: 1,
79
+ handlers: actionHandlers,
80
+ });
81
+ expect(appendedActions.filter((action) => action.disabled).map((action) => action.id)).toEqual([
82
+ "move-down",
83
+ ]);
84
+ });
85
+ });
@@ -0,0 +1,292 @@
1
+ import { isFundamentalFieldId } from "../../../../time-series/field-catalog";
2
+ import type {
3
+ ChartSeriesSpec,
4
+ ChartSpec,
5
+ PanelScale,
6
+ SeriesAxis,
7
+ SeriesPeriod,
8
+ SeriesStyle,
9
+ SeriesTimestampMode,
10
+ SeriesTransform,
11
+ } from "../../../../time-series/types";
12
+ import { canToggleChartSeries, MAX_CHART_COMPOSER_SERIES } from "../chart-spec";
13
+ import {
14
+ defaultFinancialTimestampMode,
15
+ getCompatibleSeriesStyles,
16
+ getCompatibleSeriesTransforms,
17
+ } from "../presets";
18
+
19
+ export type SeriesEditorFieldId =
20
+ | "style"
21
+ | "transform"
22
+ | "axis"
23
+ | "visibility"
24
+ | "panel"
25
+ | "scale"
26
+ | "period"
27
+ | "timing";
28
+
29
+ export type SeriesEditorFocus = "add" | "series" | "source" | SeriesEditorFieldId;
30
+
31
+ export interface SeriesEditorOption {
32
+ value: string;
33
+ label: string;
34
+ disabled?: boolean;
35
+ }
36
+
37
+ export interface SeriesEditorFieldModel {
38
+ id: SeriesEditorFieldId;
39
+ label: string;
40
+ kind: "select" | "visibility" | "panel";
41
+ value: string;
42
+ options: SeriesEditorOption[];
43
+ onChange(value: string): void;
44
+ onAddPanel?: () => void;
45
+ }
46
+
47
+ export interface SeriesEditorActionModel {
48
+ id: "add" | "remove" | "move-up" | "move-down" | "cancel" | "save";
49
+ label: string;
50
+ group: "series" | "confirm";
51
+ variant?: "danger" | "ghost" | "primary";
52
+ shortcut?: string;
53
+ disabled?: boolean;
54
+ onPress(): void;
55
+ }
56
+
57
+ const AXES: SeriesAxis[] = ["auto", "left", "right"];
58
+ const MARKET_PERIODS: SeriesPeriod[] = ["auto", "daily", "weekly", "monthly", "quarterly", "annual"];
59
+ const FINANCIAL_PERIODS: SeriesPeriod[] = ["auto", "quarterly", "annual", "ttm"];
60
+ const TIMING_OPTIONS: Array<{ value: SeriesTimestampMode; label: string }> = [
61
+ { value: "available-at", label: "Available Date" },
62
+ { value: "period-end", label: "Period End" },
63
+ ];
64
+
65
+ export function titleCaseSeriesEditorValue(value: string): string {
66
+ return value
67
+ .replaceAll("-", " ")
68
+ .replace(/\b\w/g, (character) => character.toUpperCase());
69
+ }
70
+
71
+ function seriesFieldId(series: ChartSeriesSpec): string {
72
+ return series.source.kind === "security" ? series.source.fieldId : series.source.seriesId;
73
+ }
74
+
75
+ function compatiblePeriods(series: ChartSeriesSpec): SeriesPeriod[] {
76
+ if (series.source.kind !== "security") return [];
77
+ return series.source.fieldId.startsWith("market.") ? MARKET_PERIODS : FINANCIAL_PERIODS;
78
+ }
79
+
80
+ export function supportsSeriesTimestampMode(series: ChartSeriesSpec | null): boolean {
81
+ return !!series
82
+ && series.source.kind === "security"
83
+ && isFundamentalFieldId(series.source.fieldId);
84
+ }
85
+
86
+ export function getSeriesEditorFieldIds(selected: ChartSeriesSpec | null): SeriesEditorFieldId[] {
87
+ if (!selected) return [];
88
+ return [
89
+ "style",
90
+ "transform",
91
+ "axis",
92
+ "visibility",
93
+ "panel",
94
+ "scale",
95
+ ...(selected.source.kind === "security" ? ["period" as const] : []),
96
+ ...(supportsSeriesTimestampMode(selected) ? ["timing" as const] : []),
97
+ ];
98
+ }
99
+
100
+ export function getSeriesTimestampMode(series: ChartSeriesSpec): SeriesTimestampMode {
101
+ return series.source.kind === "security"
102
+ ? series.source.timestampMode
103
+ ?? defaultFinancialTimestampMode(series.source.fieldId)
104
+ ?? "available-at"
105
+ : "available-at";
106
+ }
107
+
108
+ export interface SeriesEditorFieldHandlers {
109
+ setStyle(value: SeriesStyle): void;
110
+ setTransform(value: SeriesTransform): void;
111
+ setAxis(value: SeriesAxis): void;
112
+ setVisibility(visible: boolean): void;
113
+ setPanel(panelId: string): void;
114
+ addPanel(): void;
115
+ setScale(scale: PanelScale): void;
116
+ setPeriod(period: SeriesPeriod): void;
117
+ setTimestampMode(mode: SeriesTimestampMode): void;
118
+ }
119
+
120
+ export function buildSeriesEditorFields({
121
+ draft,
122
+ selected,
123
+ handlers,
124
+ }: {
125
+ draft: ChartSpec;
126
+ selected: ChartSeriesSpec | null;
127
+ handlers: SeriesEditorFieldHandlers;
128
+ }): SeriesEditorFieldModel[] {
129
+ if (!selected) return [];
130
+ const fieldId = seriesFieldId(selected);
131
+ const selectedPanel = draft.panels.find((panel) => panel.id === selected.panelId);
132
+ const fields: SeriesEditorFieldModel[] = [
133
+ {
134
+ id: "style",
135
+ label: "Style",
136
+ kind: "select",
137
+ value: selected.style,
138
+ options: getCompatibleSeriesStyles(fieldId).map((value) => ({
139
+ value,
140
+ label: titleCaseSeriesEditorValue(value),
141
+ })),
142
+ onChange: (value) => handlers.setStyle(value as SeriesStyle),
143
+ },
144
+ {
145
+ id: "transform",
146
+ label: "Transform",
147
+ kind: "select",
148
+ value: selected.transform,
149
+ options: getCompatibleSeriesTransforms(fieldId).map((value) => ({
150
+ value,
151
+ label: value === "index100" ? "Index 100" : value.toUpperCase(),
152
+ })),
153
+ onChange: (value) => handlers.setTransform(value as SeriesTransform),
154
+ },
155
+ {
156
+ id: "axis",
157
+ label: "Axis",
158
+ kind: "select",
159
+ value: selected.axis,
160
+ options: AXES.map((value) => ({ value, label: titleCaseSeriesEditorValue(value) })),
161
+ onChange: (value) => handlers.setAxis(value as SeriesAxis),
162
+ },
163
+ {
164
+ id: "visibility",
165
+ label: "Visibility",
166
+ kind: "visibility",
167
+ value: selected.visible === false ? "hidden" : "shown",
168
+ options: [
169
+ { value: "shown", label: "Shown" },
170
+ {
171
+ value: "hidden",
172
+ label: "Hidden",
173
+ disabled: selected.visible !== false && !canToggleChartSeries(draft, selected.id),
174
+ },
175
+ ],
176
+ onChange: (value) => handlers.setVisibility(value !== "hidden"),
177
+ },
178
+ {
179
+ id: "panel",
180
+ label: "Panel",
181
+ kind: "panel",
182
+ value: selected.panelId,
183
+ options: draft.panels.map((panel) => ({
184
+ value: panel.id,
185
+ label: panel.label ?? titleCaseSeriesEditorValue(panel.id),
186
+ })),
187
+ onChange: handlers.setPanel,
188
+ onAddPanel: handlers.addPanel,
189
+ },
190
+ {
191
+ id: "scale",
192
+ label: `Scale (${selectedPanel?.label ?? selected.panelId})`,
193
+ kind: "select",
194
+ value: selectedPanel?.scale ?? "linear",
195
+ options: [
196
+ { value: "linear", label: "Linear" },
197
+ { value: "log", label: "Log" },
198
+ ],
199
+ onChange: (value) => handlers.setScale(value as PanelScale),
200
+ },
201
+ ...(selected.source.kind === "security" ? [{
202
+ id: "period" as const,
203
+ label: "Period",
204
+ kind: "select" as const,
205
+ value: selected.source.period ?? "auto",
206
+ options: compatiblePeriods(selected).map((value) => ({
207
+ value,
208
+ label: value === "ttm" ? "TTM" : titleCaseSeriesEditorValue(value),
209
+ })),
210
+ onChange: (value: string) => handlers.setPeriod(value as SeriesPeriod),
211
+ }] : []),
212
+ ...(supportsSeriesTimestampMode(selected) ? [{
213
+ id: "timing" as const,
214
+ label: "Timing",
215
+ kind: "select" as const,
216
+ value: getSeriesTimestampMode(selected),
217
+ options: TIMING_OPTIONS,
218
+ onChange: (value: string) => handlers.setTimestampMode(value as SeriesTimestampMode),
219
+ }] : []),
220
+ ];
221
+ const fieldsById = new Map(fields.map((field) => [field.id, field]));
222
+ return getSeriesEditorFieldIds(selected).map((id) => fieldsById.get(id)!);
223
+ }
224
+
225
+ export interface SeriesEditorActionHandlers {
226
+ add(): void;
227
+ remove(): void;
228
+ moveUp(): void;
229
+ moveDown(): void;
230
+ cancel(): void;
231
+ save(): void;
232
+ }
233
+
234
+ export function buildSeriesEditorActions({
235
+ draft,
236
+ selected,
237
+ selectedIndex,
238
+ handlers,
239
+ }: {
240
+ draft: ChartSpec;
241
+ selected: ChartSeriesSpec | null;
242
+ selectedIndex: number;
243
+ handlers: SeriesEditorActionHandlers;
244
+ }): SeriesEditorActionModel[] {
245
+ return [
246
+ {
247
+ id: "add",
248
+ label: "Add Series",
249
+ group: "series",
250
+ shortcut: "A",
251
+ disabled: draft.series.length >= MAX_CHART_COMPOSER_SERIES,
252
+ onPress: handlers.add,
253
+ },
254
+ {
255
+ id: "remove",
256
+ label: "Remove",
257
+ group: "series",
258
+ variant: "danger",
259
+ shortcut: "D",
260
+ disabled: !selected || draft.series.length <= 1,
261
+ onPress: handlers.remove,
262
+ },
263
+ {
264
+ id: "move-up",
265
+ label: "Move Up",
266
+ group: "series",
267
+ disabled: selectedIndex <= 0,
268
+ onPress: handlers.moveUp,
269
+ },
270
+ {
271
+ id: "move-down",
272
+ label: "Move Down",
273
+ group: "series",
274
+ disabled: selectedIndex < 0 || selectedIndex >= draft.series.length - 1,
275
+ onPress: handlers.moveDown,
276
+ },
277
+ {
278
+ id: "cancel",
279
+ label: "Cancel",
280
+ group: "confirm",
281
+ variant: "ghost",
282
+ onPress: handlers.cancel,
283
+ },
284
+ {
285
+ id: "save",
286
+ label: "Save",
287
+ group: "confirm",
288
+ variant: "primary",
289
+ onPress: handlers.save,
290
+ },
291
+ ];
292
+ }
@@ -0,0 +1,108 @@
1
+ import { type ReactNode } from "react";
2
+ import { Box, Text } from "../../../../ui";
3
+ import { SegmentedControl } from "../../../../components";
4
+ import { Button } from "../../../../components/ui";
5
+ import { colors } from "../../../../theme/colors";
6
+ import type {
7
+ SeriesEditorActionModel,
8
+ SeriesEditorFieldModel,
9
+ SeriesEditorFocus,
10
+ } from "./model";
11
+
12
+ function TuiEditorField({
13
+ label,
14
+ focused,
15
+ onFocus,
16
+ children,
17
+ }: {
18
+ label: string;
19
+ focused: boolean;
20
+ onFocus: () => void;
21
+ children: ReactNode;
22
+ }) {
23
+ return (
24
+ <Box
25
+ flexDirection="column"
26
+ width="100%"
27
+ overflow="hidden"
28
+ onMouseDown={onFocus}
29
+ >
30
+ <Text fg={focused ? colors.textBright : colors.textDim}>
31
+ {focused ? `› ${label}` : label}
32
+ </Text>
33
+ {children}
34
+ </Box>
35
+ );
36
+ }
37
+
38
+ export function OpenTuiSeriesEditorFields({
39
+ fields,
40
+ keyboardFocus,
41
+ dialogId,
42
+ onFocus,
43
+ }: {
44
+ fields: SeriesEditorFieldModel[];
45
+ keyboardFocus: SeriesEditorFocus;
46
+ dialogId?: string;
47
+ onFocus: (focus: SeriesEditorFocus) => void;
48
+ }) {
49
+ return (
50
+ <>
51
+ {fields.map((field) => {
52
+ const focused = keyboardFocus === field.id;
53
+ return (
54
+ <TuiEditorField
55
+ key={field.id}
56
+ label={field.label}
57
+ focused={focused}
58
+ onFocus={() => onFocus(field.id)}
59
+ >
60
+ {field.kind === "panel" ? (
61
+ <Box flexDirection="row" width="100%" minWidth={0} gap={1} overflow="hidden">
62
+ <Box flexGrow={1} minWidth={0} overflow="hidden">
63
+ <SegmentedControl
64
+ options={field.options}
65
+ value={field.value}
66
+ onChange={field.onChange}
67
+ focused={focused}
68
+ shortcutScope={dialogId}
69
+ width="100%"
70
+ wrap
71
+ />
72
+ </Box>
73
+ <Button label="New Panel" shortcut="N" onPress={field.onAddPanel} />
74
+ </Box>
75
+ ) : (
76
+ <SegmentedControl
77
+ options={field.options}
78
+ value={field.value}
79
+ onChange={field.onChange}
80
+ focused={focused}
81
+ shortcutScope={dialogId}
82
+ width="100%"
83
+ wrap
84
+ />
85
+ )}
86
+ </TuiEditorField>
87
+ );
88
+ })}
89
+ </>
90
+ );
91
+ }
92
+
93
+ export function OpenTuiSeriesEditorActions({ actions }: { actions: SeriesEditorActionModel[] }) {
94
+ return (
95
+ <Box flexDirection="row" gap={1} width="100%">
96
+ {actions.map((action) => (
97
+ <Button
98
+ key={action.id}
99
+ label={action.label}
100
+ shortcut={action.shortcut}
101
+ variant={action.variant}
102
+ disabled={action.disabled}
103
+ onPress={action.onPress}
104
+ />
105
+ ))}
106
+ </Box>
107
+ );
108
+ }