gloomberb 0.9.9 β†’ 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/README.md +41 -3
  2. package/README.zh-CN.md +294 -0
  3. package/package.json +10 -4
  4. package/patches/@opentui%2Fcore@0.3.2.patch +26 -69
  5. package/src/api-client/auth.ts +2 -0
  6. package/src/api-client/index.test.ts +4 -0
  7. package/src/api-client/types.ts +3 -0
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -0
  9. package/src/app/pane-template-dialog-wizard.test.tsx +36 -0
  10. package/src/app/pane-template-dialog-wizard.tsx +10 -3
  11. package/src/app/runtime/desktop-deeplink.test.ts +15 -0
  12. package/src/app/runtime/desktop-deeplink.ts +21 -6
  13. package/src/app.tsx +40 -33
  14. package/src/capabilities/service-capabilities.ts +7 -2
  15. package/src/cli/commands/automation.test.ts +202 -0
  16. package/src/cli/commands/automation.ts +153 -36
  17. package/src/cli/commands/market.ts +1 -14
  18. package/src/cli/commands/system.ts +4 -1
  19. package/src/cli/context.ts +1 -1
  20. package/src/cli/desktop-pane-shot.ts +65 -4
  21. package/src/cli/entry.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +532 -0
  23. package/src/cli/pane-functions/catalog.ts +39 -4
  24. package/src/cli/pane-functions/data.test.ts +17 -0
  25. package/src/cli/pane-functions/data.ts +55 -13
  26. package/src/cli/pane-functions/discovery.ts +36 -9
  27. package/src/cli/pane-functions/index.test.ts +104 -1
  28. package/src/cli/pane-functions/index.ts +64 -5
  29. package/src/cli/pane-functions/options.ts +21 -6
  30. package/src/cli/pane-functions/report.ts +735 -72
  31. package/src/cli/pane-functions/resolver.ts +70 -6
  32. package/src/cli/pane-functions/screenshot.test.ts +431 -0
  33. package/src/cli/pane-functions/screenshot.ts +952 -8
  34. package/src/components/chart/composite/column-layout.ts +215 -0
  35. package/src/components/chart/composite/composite-chart.test.tsx +630 -0
  36. package/src/components/chart/composite/composite-chart.tsx +1148 -0
  37. package/src/components/chart/composite/format.test.ts +137 -0
  38. package/src/components/chart/composite/format.ts +135 -0
  39. package/src/components/chart/composite/index.ts +38 -0
  40. package/src/components/chart/composite/interactions.test.ts +109 -0
  41. package/src/components/chart/composite/interactions.ts +247 -0
  42. package/src/components/chart/composite/price-series.test.ts +39 -0
  43. package/src/components/chart/composite/price-series.ts +78 -0
  44. package/src/components/chart/composite/rasterizer.ts +359 -0
  45. package/src/components/chart/composite/renderers.test.ts +610 -0
  46. package/src/components/chart/composite/scene.test.ts +464 -0
  47. package/src/components/chart/composite/scene.ts +553 -0
  48. package/src/components/chart/composite/text-renderer.ts +285 -0
  49. package/src/components/chart/composite/time-scale.test.ts +88 -0
  50. package/src/components/chart/composite/time-scale.ts +217 -0
  51. package/src/components/chart/composite/types.ts +141 -0
  52. package/src/components/chart/core/data.ts +81 -5
  53. package/src/components/chart/core/index.test.ts +128 -43
  54. package/src/components/chart/core/resolution.ts +0 -6
  55. package/src/components/chart/native/chart-rasterizer.ts +1 -3
  56. package/src/components/chart/native/kitty/index.test.ts +99 -120
  57. package/src/components/chart/native/kitty/manager.ts +3 -0
  58. package/src/components/chart/native/kitty/protocol.ts +4 -1
  59. package/src/components/chart/native/raster/crosshair.ts +79 -46
  60. package/src/components/chart/native/raster/types.ts +2 -11
  61. package/src/components/chart/native/surface/manager.ts +48 -3
  62. package/src/components/chart/price-axis-labels.tsx +18 -4
  63. package/src/components/command-bar/commands/direct/index.ts +25 -0
  64. package/src/components/command-bar/commands/registry.ts +9 -0
  65. package/src/components/command-bar/helpers.test.ts +4 -1
  66. package/src/components/command-bar/list/view.tsx +18 -6
  67. package/src/components/command-bar/multi-select-picker.tsx +6 -5
  68. package/src/components/command-bar/pane-settings.test.ts +141 -0
  69. package/src/components/command-bar/pane-settings.ts +61 -1
  70. package/src/components/command-bar/panel/body.tsx +7 -6
  71. package/src/components/command-bar/routes/root/command-items.ts +4 -4
  72. package/src/components/command-bar/routes/root/results.ts +10 -1
  73. package/src/components/command-bar/routes/root/selection.test.ts +54 -0
  74. package/src/components/command-bar/routes/root/selection.ts +13 -0
  75. package/src/components/command-bar/routing/actions.ts +18 -2
  76. package/src/components/command-bar/routing/list-state.ts +2 -1
  77. package/src/components/command-bar/surface/index.test.tsx +36 -4
  78. package/src/components/command-bar/surface/pane.test.tsx +93 -0
  79. package/src/components/command-bar/theme-picker.tsx +2 -1
  80. package/src/components/command-bar/view-model.ts +19 -20
  81. package/src/components/command-bar/workflow/body.tsx +2 -1
  82. package/src/components/command-bar/workflow/field-row.tsx +3 -2
  83. package/src/components/command-bar/workflow/fields.ts +4 -2
  84. package/src/components/command-bar/workflow/ops.test.ts +161 -0
  85. package/src/components/command-bar/workflow/ops.ts +20 -50
  86. package/src/components/command-bar/workflow/route-actions.test.ts +34 -0
  87. package/src/components/command-bar/workflow/route-actions.ts +4 -0
  88. package/src/components/command-bar/workflow/types.ts +1 -0
  89. package/src/components/feed-data-table/stack-view.tsx +6 -5
  90. package/src/components/index.ts +13 -1
  91. package/src/components/layout/detached-pane-shell.tsx +2 -1
  92. package/src/components/layout/floating-pane.tsx +3 -0
  93. package/src/components/layout/header.tsx +15 -8
  94. package/src/components/layout/pane/footer/index.test.tsx +22 -3
  95. package/src/components/layout/pane/footer/index.tsx +3 -1
  96. package/src/components/layout/pane/footer/model.ts +9 -2
  97. package/src/components/layout/pane/header.tsx +4 -6
  98. package/src/components/layout/pane/index.tsx +3 -0
  99. package/src/components/layout/pane/sidebar.test.tsx +100 -0
  100. package/src/components/layout/pane/sidebar.tsx +239 -0
  101. package/src/components/layout/pane/title.ts +4 -3
  102. package/src/components/layout/shell/action-menu-overlay.tsx +5 -3
  103. package/src/components/layout/shell/drag/runtime.ts +2 -0
  104. package/src/components/layout/shell/menu.test.ts +19 -0
  105. package/src/components/layout/shell/menu.ts +8 -5
  106. package/src/components/layout/shell/native/pointer-runtime.ts +4 -1
  107. package/src/components/layout/shell/pane/layers.tsx +2 -2
  108. package/src/components/layout/status-bar.tsx +8 -7
  109. package/src/components/layout/window-controls.tsx +4 -3
  110. package/src/components/metric-treemap/index.tsx +3 -2
  111. package/src/components/onboarding/onboarding-steps.tsx +30 -29
  112. package/src/components/onboarding/onboarding-wizard.tsx +5 -4
  113. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +15 -12
  114. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +17 -16
  115. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +6 -5
  116. package/src/components/onboarding/portfolio-step/choose-panel.tsx +5 -4
  117. package/src/components/pane-settings-dialog/desktop.tsx +25 -3
  118. package/src/components/pane-settings-dialog/field-dialogs.tsx +5 -4
  119. package/src/components/pane-settings-dialog/tui.tsx +11 -9
  120. package/src/components/pane-settings-dialog/value.ts +8 -5
  121. package/src/components/pane-settings-dialog.test.tsx +235 -0
  122. package/src/components/pane-settings-dialog.tsx +24 -0
  123. package/src/components/pane-template-wizard.tsx +6 -2
  124. package/src/components/ticker/list-table-view.tsx +3 -2
  125. package/src/components/ui/button.tsx +3 -1
  126. package/src/components/ui/choice-dialog.tsx +6 -1
  127. package/src/components/ui/confirm-dialog.tsx +3 -2
  128. package/src/components/ui/fields.tsx +10 -1
  129. package/src/components/ui/frame.tsx +4 -1
  130. package/src/components/ui/index.ts +5 -0
  131. package/src/components/ui/inline-quick-add.tsx +104 -0
  132. package/src/components/ui/list-view.tsx +5 -4
  133. package/src/components/ui/loading.tsx +2 -1
  134. package/src/components/ui/multi-select/dialog.tsx +188 -44
  135. package/src/components/ui/popover.tsx +10 -0
  136. package/src/components/ui/shortcut-hint.tsx +2 -0
  137. package/src/components/ui/status.tsx +4 -3
  138. package/src/components/ui/tabs.tsx +19 -8
  139. package/src/components/ui/toggle.tsx +44 -3
  140. package/src/components/ui/ui.test.tsx +67 -5
  141. package/src/core/app-services.ts +4 -6
  142. package/src/core/state/session-persistence.ts +3 -2
  143. package/src/data/config/chart-settings.ts +712 -0
  144. package/src/data/config/layout.ts +24 -5
  145. package/src/data/config/store/index.test.ts +377 -14
  146. package/src/data/config/store/normalize.ts +79 -65
  147. package/src/data/fred-series.ts +137 -0
  148. package/src/i18n/index.test.ts +114 -0
  149. package/src/i18n/index.ts +147 -0
  150. package/src/i18n/ja.ts +1013 -0
  151. package/src/i18n/ko.ts +1011 -0
  152. package/src/i18n/languages.test.ts +31 -0
  153. package/src/i18n/languages.ts +72 -0
  154. package/src/i18n/zh-cn.ts +1013 -0
  155. package/src/i18n/zh-tw.ts +1013 -0
  156. package/src/plugins/builtin/account-management/footer.ts +3 -2
  157. package/src/plugins/builtin/account-management/form-components.tsx +3 -2
  158. package/src/plugins/builtin/account-management/keyboard.ts +6 -0
  159. package/src/plugins/builtin/account-management/model.ts +25 -21
  160. package/src/plugins/builtin/account-management/navigation.test.ts +27 -0
  161. package/src/plugins/builtin/account-management/navigation.ts +24 -0
  162. package/src/plugins/builtin/account-management/pane.tsx +120 -85
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +10 -9
  164. package/src/plugins/builtin/ai/agent-history.ts +139 -0
  165. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +133 -51
  166. package/src/plugins/builtin/ai/ask-ai.test.tsx +83 -22
  167. package/src/plugins/builtin/ai/index.test.ts +302 -0
  168. package/src/plugins/builtin/ai/index.tsx +374 -40
  169. package/src/plugins/builtin/ai/pane-settings.test.ts +155 -0
  170. package/src/plugins/builtin/ai/pane-settings.ts +319 -0
  171. package/src/plugins/builtin/ai/pi/credential-store.test.ts +80 -0
  172. package/src/plugins/builtin/ai/pi/credential-store.ts +138 -0
  173. package/src/plugins/builtin/ai/pi/file-store.ts +138 -0
  174. package/src/plugins/builtin/ai/pi/host.test.ts +500 -0
  175. package/src/plugins/builtin/ai/pi/host.ts +564 -0
  176. package/src/plugins/builtin/ai/pi/index.ts +5 -0
  177. package/src/plugins/builtin/ai/pi/models-store.test.ts +44 -0
  178. package/src/plugins/builtin/ai/pi/models-store.ts +141 -0
  179. package/src/plugins/builtin/ai/pi/providers.ts +68 -0
  180. package/src/plugins/builtin/ai/pi/runtime.test.ts +240 -0
  181. package/src/plugins/builtin/ai/pi/runtime.ts +635 -0
  182. package/src/plugins/builtin/ai/providers.test.ts +68 -0
  183. package/src/plugins/builtin/ai/providers.ts +163 -38
  184. package/src/plugins/builtin/ai/runner-selection.test.ts +174 -0
  185. package/src/plugins/builtin/ai/runner-selection.ts +193 -0
  186. package/src/plugins/builtin/ai/runner-selector.tsx +153 -0
  187. package/src/plugins/builtin/ai/runner.test.ts +198 -0
  188. package/src/plugins/builtin/ai/runner.ts +276 -73
  189. package/src/plugins/builtin/ai/screener/contract.test.ts +26 -73
  190. package/src/plugins/builtin/ai/screener/contract.ts +14 -52
  191. package/src/plugins/builtin/ai/screener/editor-runtime.ts +55 -5
  192. package/src/plugins/builtin/ai/screener/editor.tsx +42 -36
  193. package/src/plugins/builtin/ai/screener/footer.ts +82 -67
  194. package/src/plugins/builtin/ai/screener/keyboard.ts +27 -11
  195. package/src/plugins/builtin/ai/screener/model.test.ts +24 -0
  196. package/src/plugins/builtin/ai/screener/model.ts +9 -3
  197. package/src/plugins/builtin/ai/screener/pane.test.tsx +142 -55
  198. package/src/plugins/builtin/ai/screener/pane.tsx +95 -97
  199. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -41
  200. package/src/plugins/builtin/ai/screener/runner.ts +41 -25
  201. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +2 -2
  202. package/src/plugins/builtin/ai/use-runtime-providers.ts +28 -0
  203. package/src/plugins/builtin/ai/workspace/model.test.ts +227 -0
  204. package/src/plugins/builtin/ai/workspace/model.ts +308 -0
  205. package/src/plugins/builtin/ai/workspace/pane.test.tsx +197 -0
  206. package/src/plugins/builtin/ai/workspace/pane.tsx +896 -0
  207. package/src/plugins/builtin/analytics/index.test.tsx +3 -3
  208. package/src/plugins/builtin/analytics/index.tsx +3 -8
  209. package/src/plugins/builtin/broker-manager/detail.tsx +25 -24
  210. package/src/plugins/builtin/broker-manager/index.test.tsx +1 -1
  211. package/src/plugins/builtin/broker-manager/index.tsx +9 -11
  212. package/src/plugins/builtin/broker-manager/model.ts +11 -10
  213. package/src/plugins/builtin/broker-manager/pane-actions.tsx +22 -21
  214. package/src/plugins/builtin/broker-manager/table.ts +9 -8
  215. package/src/plugins/builtin/changelog/index.tsx +3 -8
  216. package/src/plugins/builtin/chart-composer/chart-spec.ts +89 -0
  217. package/src/plugins/builtin/chart-composer/cli-options.ts +98 -0
  218. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +120 -0
  219. package/src/plugins/builtin/chart-composer/editor.test.tsx +332 -0
  220. package/src/plugins/builtin/chart-composer/editor.tsx +1107 -0
  221. package/src/plugins/builtin/chart-composer/index.tsx +228 -0
  222. package/src/plugins/builtin/chart-composer/pane.tsx +645 -0
  223. package/src/plugins/builtin/chart-composer/presets.test.ts +561 -0
  224. package/src/plugins/builtin/chart-composer/presets.ts +759 -0
  225. package/src/plugins/builtin/chart-composer/quick-add.test.tsx +245 -0
  226. package/src/plugins/builtin/chart-composer/quick-add.tsx +409 -0
  227. package/src/plugins/builtin/chart-composer/semantic.ts +86 -0
  228. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +65 -0
  229. package/src/plugins/builtin/chart-composer/series-catalog.ts +295 -0
  230. package/src/plugins/builtin/chart-composer/settings.test.ts +163 -0
  231. package/src/plugins/builtin/chart-composer/settings.ts +397 -0
  232. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +44 -0
  233. package/src/plugins/builtin/chart-composer/shortcuts.ts +35 -0
  234. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +103 -0
  235. package/src/plugins/builtin/chat/channel-labels.ts +5 -4
  236. package/src/plugins/builtin/chat/channels.ts +12 -11
  237. package/src/plugins/builtin/chat/chat.test.tsx +32 -0
  238. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  239. package/src/plugins/builtin/chat/content/composer.tsx +9 -8
  240. package/src/plugins/builtin/chat/content/index.tsx +36 -1
  241. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +8 -7
  242. package/src/plugins/builtin/chat/content/transcript.tsx +9 -3
  243. package/src/plugins/builtin/chat/controller/utils.ts +10 -9
  244. package/src/plugins/builtin/chat/message/desktop.tsx +17 -10
  245. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +122 -3
  246. package/src/plugins/builtin/chat/message/profile-popover.tsx +38 -1
  247. package/src/plugins/builtin/chat/message/terminal.tsx +19 -10
  248. package/src/plugins/builtin/chat/pane-state.test.ts +31 -0
  249. package/src/plugins/builtin/chat/pane-state.ts +30 -0
  250. package/src/plugins/builtin/chat/pane.tsx +27 -4
  251. package/src/plugins/builtin/chat/profile-popover.ts +62 -5
  252. package/src/plugins/builtin/chat/sidebar.test.tsx +4 -7
  253. package/src/plugins/builtin/chat/sidebar.tsx +107 -168
  254. package/src/plugins/builtin/chat/test-harness.tsx +6 -0
  255. package/src/plugins/builtin/cloud/auth-actions.tsx +5 -4
  256. package/src/plugins/builtin/cloud/plugin.tsx +150 -127
  257. package/src/plugins/builtin/composite-plugins.ts +71 -0
  258. package/src/plugins/builtin/correlation/index.test.tsx +4 -4
  259. package/src/plugins/builtin/correlation/index.tsx +3 -8
  260. package/src/plugins/builtin/earnings/index.tsx +37 -21
  261. package/src/plugins/builtin/earnings/model.test.ts +52 -0
  262. package/src/plugins/builtin/earnings/model.ts +32 -0
  263. package/src/plugins/builtin/econ/detail-view.tsx +17 -4
  264. package/src/plugins/builtin/econ/fred-cache.test.ts +57 -11
  265. package/src/plugins/builtin/econ/index.tsx +16 -20
  266. package/src/plugins/builtin/fear-greed/index.tsx +2 -8
  267. package/src/plugins/builtin/fx-matrix/index.tsx +3 -8
  268. package/src/plugins/builtin/help/components.tsx +5 -4
  269. package/src/plugins/builtin/help/index.tsx +14 -17
  270. package/src/plugins/builtin/holders/index.tsx +2 -8
  271. package/src/plugins/builtin/insider/index.tsx +2 -7
  272. package/src/plugins/builtin/kelly-sizer/index.test.tsx +2 -2
  273. package/src/plugins/builtin/kelly-sizer/index.tsx +3 -7
  274. package/src/plugins/builtin/kelly-sizer/test-support.tsx +3 -3
  275. package/src/plugins/builtin/layout-manager/index.tsx +3 -7
  276. package/src/plugins/builtin/market-heatmap/index.tsx +3 -8
  277. package/src/plugins/builtin/market-movers/index.tsx +3 -8
  278. package/src/plugins/builtin/news/index.tsx +12 -16
  279. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +3 -3
  280. package/src/plugins/builtin/news/wire/index.tsx +83 -73
  281. package/src/plugins/builtin/options/index.tsx +2 -8
  282. package/src/plugins/builtin/plugin-module.test.ts +89 -0
  283. package/src/plugins/builtin/plugin-module.ts +116 -0
  284. package/src/plugins/builtin/portfolio-list/header.tsx +4 -3
  285. package/src/plugins/builtin/portfolio-list/index.test.ts +17 -0
  286. package/src/plugins/builtin/portfolio-list/index.tsx +3 -5
  287. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +3 -3
  288. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +53 -53
  289. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  290. package/src/plugins/builtin/portfolio-list/summary/index.tsx +4 -3
  291. package/src/plugins/builtin/research/index.tsx +2 -8
  292. package/src/plugins/builtin/sec/index.tsx +2 -8
  293. package/src/plugins/builtin/sectors/index.tsx +3 -8
  294. package/src/plugins/builtin/thirteenf/index.tsx +3 -8
  295. package/src/plugins/builtin/thirteenf/pane.test.tsx +1 -1
  296. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +49 -23
  297. package/src/plugins/builtin/ticker-detail/index.test.tsx +15 -136
  298. package/src/plugins/builtin/ticker-detail/index.tsx +2 -93
  299. package/src/plugins/builtin/ticker-detail/overview/components.tsx +6 -5
  300. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -17
  301. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -3
  302. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +1 -1
  303. package/src/plugins/builtin/ticker-detail/settings.ts +3 -50
  304. package/src/plugins/builtin/ticker-research-backend-plugin.ts +17 -0
  305. package/src/plugins/builtin/ticker-research-plugin.ts +27 -0
  306. package/src/plugins/builtin/tv/channels.ts +27 -0
  307. package/src/plugins/builtin/tv/index.tsx +40 -0
  308. package/src/plugins/builtin/tv/pane.tsx +286 -0
  309. package/src/plugins/builtin/tv/youtube-stream.ts +100 -0
  310. package/src/plugins/builtin/world-indices/index.tsx +3 -8
  311. package/src/plugins/builtin/yield-curve/index.tsx +8 -8
  312. package/src/plugins/catalog-backend.test.ts +21 -0
  313. package/src/plugins/catalog-backend.ts +42 -0
  314. package/src/plugins/catalog-ui.ts +6 -14
  315. package/src/plugins/ibkr/instance-selection.test.ts +0 -1
  316. package/src/plugins/ownership.ts +143 -0
  317. package/src/plugins/prediction-markets/backend-plugin.ts +19 -0
  318. package/src/plugins/prediction-markets/chart.test.tsx +7 -5
  319. package/src/plugins/prediction-markets/chart.tsx +25 -7
  320. package/src/plugins/registry/index.test.ts +51 -0
  321. package/src/plugins/registry/index.ts +8 -0
  322. package/src/plugins/registry/pane-settings.ts +1 -1
  323. package/src/plugins/runtime/index.test.tsx +2 -2
  324. package/src/remote/app-host.tsx +16 -3
  325. package/src/remote/controller.test.ts +102 -1
  326. package/src/remote/controller.ts +122 -0
  327. package/src/remote/types.ts +11 -0
  328. package/src/renderers/electrobun/bun/core-capabilities.ts +123 -27
  329. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +5 -1
  330. package/src/renderers/electrobun/bun/desktop/initialization.ts +5 -1
  331. package/src/renderers/electrobun/bun/desktop/media.ts +15 -0
  332. package/src/renderers/electrobun/bun/desktop/window-style.ts +4 -1
  333. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +32 -1
  334. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +10 -0
  335. package/src/renderers/electrobun/bun/index.ts +5 -0
  336. package/src/renderers/electrobun/bun/tui-entry.ts +1 -6
  337. package/src/renderers/electrobun/view/ai-host.ts +124 -19
  338. package/src/renderers/electrobun/view/backend-rpc.ts +4 -0
  339. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +151 -39
  340. package/src/renderers/electrobun/view/desktop/popover.tsx +166 -0
  341. package/src/renderers/electrobun/view/dialog-host.test.ts +25 -0
  342. package/src/renderers/electrobun/view/dialog-host.tsx +75 -12
  343. package/src/renderers/electrobun/view/host/box.tsx +5 -0
  344. package/src/renderers/electrobun/view/host/chart-surface.tsx +10 -9
  345. package/src/renderers/electrobun/view/host/input.tsx +9 -19
  346. package/src/renderers/electrobun/view/host/media-surface.tsx +170 -0
  347. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  348. package/src/renderers/electrobun/view/host/style.ts +1 -1
  349. package/src/renderers/electrobun/view/host/tabs.tsx +4 -3
  350. package/src/renderers/electrobun/view/main.tsx +10 -3
  351. package/src/renderers/electrobun/view/styles.css +17 -0
  352. package/src/renderers/electrobun/view/ui-host.tsx +7 -0
  353. package/src/renderers/opentui/chart-surface.test.tsx +52 -6
  354. package/src/renderers/opentui/chart-surface.tsx +95 -11
  355. package/src/renderers/opentui/dialog-host.tsx +36 -5
  356. package/src/renderers/opentui/host.tsx +39 -0
  357. package/src/renderers/opentui/start.tsx +30 -0
  358. package/src/renderers/opentui/ui-host.tsx +2 -0
  359. package/src/sources/provider-router/index.test.ts +43 -0
  360. package/src/sources/provider-router/index.ts +4 -0
  361. package/src/sources/provider-router/supplemental.ts +46 -0
  362. package/src/sources/sec-edgar.test.ts +203 -2
  363. package/src/sources/sec-edgar.ts +70 -15
  364. package/src/state/app/context/index.tsx +13 -1
  365. package/src/state/app/context/selector.test.tsx +62 -1
  366. package/src/state/session-persistence.test.ts +13 -2
  367. package/src/sync/controller.test.ts +190 -3
  368. package/src/sync/controller.ts +76 -45
  369. package/src/sync/core-contributors.test.ts +121 -0
  370. package/src/sync/core-contributors.ts +104 -27
  371. package/src/sync/react.ts +6 -8
  372. package/src/sync/types.ts +3 -0
  373. package/src/time-series/alignment.ts +158 -0
  374. package/src/time-series/economic.ts +49 -0
  375. package/src/time-series/field-catalog.ts +306 -0
  376. package/src/time-series/fundamentals.test.ts +505 -0
  377. package/src/time-series/fundamentals.ts +842 -0
  378. package/src/time-series/hooks.ts +45 -0
  379. package/src/time-series/index.ts +14 -0
  380. package/src/time-series/live-quotes.test.ts +219 -0
  381. package/src/time-series/live-quotes.ts +203 -0
  382. package/src/time-series/market-resolution.ts +53 -0
  383. package/src/time-series/market.ts +125 -0
  384. package/src/time-series/reporting.ts +98 -0
  385. package/src/time-series/resolve.test.ts +1281 -0
  386. package/src/time-series/resolve.ts +783 -0
  387. package/src/time-series/spec-studies.test.ts +400 -0
  388. package/src/time-series/spec.ts +487 -0
  389. package/src/time-series/studies.ts +563 -0
  390. package/src/time-series/transforms.test.ts +105 -0
  391. package/src/time-series/transforms.ts +152 -0
  392. package/src/time-series/types.ts +162 -0
  393. package/src/time-series/use-chart-resolution.ts +166 -0
  394. package/src/types/config.ts +9 -11
  395. package/src/types/desktop-window.ts +1 -0
  396. package/src/types/financials.ts +4 -0
  397. package/src/types/media.ts +17 -0
  398. package/src/types/plugin.ts +32 -1
  399. package/src/ui/context-menu.tsx +15 -3
  400. package/src/ui/dialog.tsx +14 -2
  401. package/src/ui/host.tsx +40 -1
  402. package/src/ui/index.tsx +9 -0
  403. package/src/utils/exchanges.test.ts +13 -1
  404. package/src/utils/exchanges.ts +10 -0
  405. package/src/utils/financial-statements.test.ts +138 -0
  406. package/src/utils/financial-statements.ts +115 -8
  407. package/src/utils/format.test.ts +11 -1
  408. package/src/utils/format.ts +7 -0
  409. package/src/version.ts +1 -1
  410. package/src/components/chart/bar-chart-renderer.test.ts +0 -233
  411. package/src/components/chart/bar-chart-renderer.ts +0 -445
  412. package/src/components/chart/comparison/data.test.ts +0 -159
  413. package/src/components/chart/comparison/data.ts +0 -302
  414. package/src/components/chart/comparison/renderer.test.ts +0 -187
  415. package/src/components/chart/comparison/renderer.ts +0 -282
  416. package/src/components/chart/comparison/scene.ts +0 -178
  417. package/src/components/chart/comparison-stock-chart/axis-width.ts +0 -99
  418. package/src/components/chart/comparison-stock-chart/canvas.ts +0 -148
  419. package/src/components/chart/comparison-stock-chart/controls.ts +0 -252
  420. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -101
  421. package/src/components/chart/comparison-stock-chart/interactions.ts +0 -208
  422. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +0 -62
  423. package/src/components/chart/comparison-stock-chart/layout.tsx +0 -155
  424. package/src/components/chart/comparison-stock-chart/legend.tsx +0 -74
  425. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +0 -89
  426. package/src/components/chart/comparison-stock-chart/render-data.ts +0 -227
  427. package/src/components/chart/comparison-stock-chart/render-output.ts +0 -140
  428. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +0 -128
  429. package/src/components/chart/comparison-stock-chart/sources.ts +0 -51
  430. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +0 -159
  431. package/src/components/chart/comparison-stock-chart/toolbar.tsx +0 -117
  432. package/src/components/chart/comparison-stock-chart/types.ts +0 -22
  433. package/src/components/chart/comparison-stock-chart/view.tsx +0 -572
  434. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +0 -239
  435. package/src/components/chart/comparison-stock-chart.tsx +0 -24
  436. package/src/components/chart/core/axis-measure.ts +0 -33
  437. package/src/components/chart/core/controller.test.ts +0 -157
  438. package/src/components/chart/core/controller.ts +0 -349
  439. package/src/components/chart/core/indicator-selector.tsx +0 -75
  440. package/src/components/chart/core/keyboard.ts +0 -24
  441. package/src/components/chart/core/pane-settings.test.ts +0 -52
  442. package/src/components/chart/core/pane-settings.ts +0 -141
  443. package/src/components/chart/core/scroll.test.ts +0 -64
  444. package/src/components/chart/core/scroll.ts +0 -82
  445. package/src/components/chart/core/window-viewport.test.ts +0 -69
  446. package/src/components/chart/core/window-viewport.ts +0 -162
  447. package/src/components/chart/display-cursor.ts +0 -153
  448. package/src/components/chart/indicators/bands.test.ts +0 -36
  449. package/src/components/chart/indicators/bands.ts +0 -29
  450. package/src/components/chart/indicators/moving-averages.test.ts +0 -39
  451. package/src/components/chart/indicators/moving-averages.ts +0 -42
  452. package/src/components/chart/indicators/options.test.ts +0 -32
  453. package/src/components/chart/indicators/options.ts +0 -107
  454. package/src/components/chart/indicators/oscillators.test.ts +0 -58
  455. package/src/components/chart/indicators/oscillators.ts +0 -92
  456. package/src/components/chart/native/canvas-bitmaps.ts +0 -140
  457. package/src/components/chart/native/raster/comparison-chart.ts +0 -162
  458. package/src/components/chart/native/surface/hooks.ts +0 -144
  459. package/src/components/chart/native/surface/rendering.ts +0 -211
  460. package/src/components/chart/static/bar-chart-surface.test.tsx +0 -101
  461. package/src/components/chart/static/bar-chart-surface.tsx +0 -292
  462. package/src/components/chart/stock-chart/auto.ts +0 -216
  463. package/src/components/chart/stock-chart/axis-width.ts +0 -146
  464. package/src/components/chart/stock-chart/bitmaps.ts +0 -22
  465. package/src/components/chart/stock-chart/canvas.ts +0 -202
  466. package/src/components/chart/stock-chart/controls.tsx +0 -149
  467. package/src/components/chart/stock-chart/cursor.ts +0 -60
  468. package/src/components/chart/stock-chart/data-runtime.ts +0 -128
  469. package/src/components/chart/stock-chart/footer.test.ts +0 -65
  470. package/src/components/chart/stock-chart/footer.ts +0 -303
  471. package/src/components/chart/stock-chart/geometry.ts +0 -160
  472. package/src/components/chart/stock-chart/index.test.ts +0 -532
  473. package/src/components/chart/stock-chart/index.tsx +0 -29
  474. package/src/components/chart/stock-chart/indicators.ts +0 -226
  475. package/src/components/chart/stock-chart/interaction-runtime.ts +0 -44
  476. package/src/components/chart/stock-chart/interactions.ts +0 -300
  477. package/src/components/chart/stock-chart/keyboard.ts +0 -373
  478. package/src/components/chart/stock-chart/layout-metrics.ts +0 -62
  479. package/src/components/chart/stock-chart/layout.tsx +0 -177
  480. package/src/components/chart/stock-chart/native-surfaces.ts +0 -121
  481. package/src/components/chart/stock-chart/presentation.ts +0 -67
  482. package/src/components/chart/stock-chart/renderer-switch.test.tsx +0 -263
  483. package/src/components/chart/stock-chart/rendering/bounds.ts +0 -116
  484. package/src/components/chart/stock-chart/rendering/candidates.ts +0 -145
  485. package/src/components/chart/stock-chart/rendering/data.ts +0 -370
  486. package/src/components/chart/stock-chart/rendering/invalidation.ts +0 -68
  487. package/src/components/chart/stock-chart/rendering/output.ts +0 -304
  488. package/src/components/chart/stock-chart/rendering/planning.ts +0 -122
  489. package/src/components/chart/stock-chart/rendering/projection.test.tsx +0 -98
  490. package/src/components/chart/stock-chart/rendering/projection.ts +0 -112
  491. package/src/components/chart/stock-chart/rendering/state.test.ts +0 -70
  492. package/src/components/chart/stock-chart/rendering/state.ts +0 -283
  493. package/src/components/chart/stock-chart/requests.ts +0 -337
  494. package/src/components/chart/stock-chart/resolution-support.ts +0 -96
  495. package/src/components/chart/stock-chart/resolved/runtime.ts +0 -289
  496. package/src/components/chart/stock-chart/resolved.tsx +0 -359
  497. package/src/components/chart/stock-chart/settings.ts +0 -81
  498. package/src/components/chart/stock-chart/surface-runtime.ts +0 -62
  499. package/src/components/chart/stock-chart/types.ts +0 -25
  500. package/src/components/chart/stock-chart/view.tsx +0 -155
  501. package/src/components/chart/stock-chart/viewport/effects.ts +0 -293
  502. package/src/components/chart/stock-chart/viewport/index.ts +0 -144
  503. package/src/components/chart/stock-chart/viewport/runtime.ts +0 -375
  504. package/src/components/chart/time-axis-label.tsx +0 -175
  505. package/src/plugins/builtin/ai/gloomberb-cli.ts +0 -71
  506. package/src/plugins/builtin/ai/screener/action-bar.tsx +0 -58
  507. package/src/plugins/builtin/comparison-chart/index.test.tsx +0 -848
  508. package/src/plugins/builtin/comparison-chart/index.tsx +0 -194
  509. package/src/plugins/builtin/econ/fred-cache.ts +0 -106
  510. package/src/plugins/builtin/plugin-groups.ts +0 -130
  511. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +0 -91
  512. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -100
  513. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +0 -296
  514. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +0 -62
  515. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +0 -608
  516. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +0 -65
  517. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +0 -58
  518. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +0 -138
  519. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +0 -220
@@ -1,4 +1,5 @@
1
1
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, type ReactNode } from "react";
2
+ import { t, tf } from "../i18n";
2
3
  import { getSharedRegistry, type PluginRegistry } from "../plugins/registry";
3
4
  import type { TickerFinancials } from "../types/financials";
4
5
  import { TICKER_RESEARCH_PANE_ID } from "../types/config";
@@ -225,12 +226,12 @@ export function tickerContextMenuItems({
225
226
  const items: ContextMenuItem[] = [
226
227
  {
227
228
  id: "ticker:open",
228
- label: `Open ${symbol} Ticker Research`,
229
+ label: tf("Open {symbol} Ticker Research", { symbol }),
229
230
  onSelect: () => (openTicker ? openTicker(symbol) : registry?.navigateTicker(symbol)),
230
231
  },
231
232
  {
232
233
  id: "ticker:pin-floating",
233
- label: `Open ${symbol} in New Floating Ticker Research`,
234
+ label: tf("Open {symbol} in New Floating Ticker Research", { symbol }),
234
235
  onSelect: () => registry?.pinTicker(symbol, { floating: true, paneType: TICKER_RESEARCH_PANE_ID, forceNewPane: true }),
235
236
  },
236
237
  {
@@ -283,6 +284,17 @@ export function tickerContextMenuItems({
283
284
  return items;
284
285
  }
285
286
 
287
+ function translateContextMenuItems(items: ContextMenuItem[]): ContextMenuItem[] {
288
+ return items.map((item) => {
289
+ if (item.type === "divider" || item.type === "role") return item;
290
+ return {
291
+ ...item,
292
+ label: t(item.label),
293
+ submenu: item.submenu ? translateContextMenuItems(item.submenu) : item.submenu,
294
+ };
295
+ });
296
+ }
297
+
286
298
  export function ContextMenuProvider({
287
299
  pluginRegistry,
288
300
  children,
@@ -314,7 +326,7 @@ export function ContextMenuProvider({
314
326
 
315
327
  event?.preventDefault?.();
316
328
  event?.stopPropagation?.();
317
- return renderer.showContextMenu(withSafeActions(items, handleActionError));
329
+ return renderer.showContextMenu(withSafeActions(translateContextMenuItems(items), handleActionError));
318
330
  }, [handleActionError, nativeSupported, registry, renderer]);
319
331
 
320
332
  useEffect(() => {
package/src/ui/dialog.tsx CHANGED
@@ -18,6 +18,8 @@ export interface DialogApi {
18
18
  interface DialogContextValue {
19
19
  dialog: DialogApi;
20
20
  isOpen: boolean;
21
+ dialogId?: string;
22
+ keyboardEnabled: boolean;
21
23
  }
22
24
 
23
25
  const DialogContext = createContext<DialogContextValue | null>(null);
@@ -25,14 +27,18 @@ const DialogContext = createContext<DialogContextValue | null>(null);
25
27
  export function DialogHostProvider({
26
28
  dialog,
27
29
  isOpen,
30
+ dialogId,
31
+ keyboardEnabled = true,
28
32
  children,
29
33
  }: {
30
34
  dialog: DialogApi;
31
35
  isOpen: boolean;
36
+ dialogId?: string;
37
+ keyboardEnabled?: boolean;
32
38
  children: ReactNode;
33
39
  }) {
34
40
  return (
35
- <DialogContext value={{ dialog, isOpen }}>
41
+ <DialogContext value={{ dialog, isOpen, dialogId, keyboardEnabled }}>
36
42
  {children}
37
43
  </DialogContext>
38
44
  );
@@ -54,5 +60,11 @@ export function useDialogKeyboard(
54
60
  handler: (event: KeyEventLike) => void,
55
61
  options?: ShortcutOptions | string,
56
62
  ): void {
57
- useShortcut(handler, typeof options === "string" ? { scope: options } : options);
63
+ const context = useContext(DialogContext);
64
+ const resolved = typeof options === "string" ? { scope: options } : options;
65
+ useShortcut(handler, {
66
+ ...resolved,
67
+ enabled: (resolved?.enabled ?? true) && (context?.keyboardEnabled ?? true),
68
+ scope: resolved?.scope ?? context?.dialogId,
69
+ });
58
70
  }
package/src/ui/host.tsx CHANGED
@@ -1,6 +1,7 @@
1
- import { createContext, useContext, type ComponentType, type ReactNode } from "react";
1
+ import { createContext, useContext, type ComponentType, type ReactNode, type Ref } from "react";
2
2
  import type { ContextMenuItem } from "../types/context-menu";
3
3
  import type { AppNotificationRequest } from "../types/plugin";
4
+ import type { LiveStreamResolveRequest, ResolvedLiveStream } from "../types/media";
4
5
  import type { AsciiFontName } from "./ascii-font";
5
6
 
6
7
  export const TextAttributes = {
@@ -56,6 +57,8 @@ export interface ChartCrosshairOverlay {
56
57
  pixelX: number;
57
58
  pixelY: number;
58
59
  color: string;
60
+ /** Per-series dots on the cursor column, in bitmap pixels. */
61
+ markers?: readonly { pixelY: number; color: string }[];
59
62
  }
60
63
 
61
64
  interface TextEditBuffer {
@@ -105,6 +108,7 @@ export interface ScrollBoxRenderable {
105
108
  scrollTop: number;
106
109
  scrollLeft?: number;
107
110
  scrollHeight: number;
111
+ scrollWidth?: number;
108
112
  scrollTopPx?: number;
109
113
  scrollLeftPx?: number;
110
114
  scrollHeightPx?: number;
@@ -125,6 +129,7 @@ export interface InputRenderable {
125
129
  cursorOffset?: number;
126
130
  setCursorOffset?(offset: number): void;
127
131
  focus?(): void;
132
+ blur?(): void;
128
133
  }
129
134
 
130
135
  export interface TextareaRenderable extends InputRenderable {
@@ -214,6 +219,23 @@ export interface ImageSurfaceProps extends BoxProps {
214
219
  alt?: string;
215
220
  objectFit?: "contain" | "cover";
216
221
  }
222
+ export interface MediaSurfaceProps extends BoxProps {
223
+ src?: string;
224
+ title?: string;
225
+ poster?: string;
226
+ autoPlay?: boolean;
227
+ muted?: boolean;
228
+ mediaHandleRef?: Ref<MediaSurfaceHandle>;
229
+ onPlaybackStateChange?: (state: "idle" | "loading" | "playing" | "paused" | "error") => void;
230
+ onMutedChange?: (muted: boolean) => void;
231
+ onError?: (message: string) => void;
232
+ }
233
+ export interface MediaSurfaceHandle {
234
+ play(): Promise<void>;
235
+ pause(): void;
236
+ toggle(): Promise<void>;
237
+ toggleMuted(): boolean;
238
+ }
217
239
  interface SpinnerMarkProps extends BoxProps {
218
240
  name?: string;
219
241
  color?: string;
@@ -257,6 +279,7 @@ export interface HostTabsProps {
257
279
  activeValue: string | null;
258
280
  onSelect: (value: string) => void;
259
281
  compact?: boolean;
282
+ dense?: boolean;
260
283
  variant?: "underline" | "pill" | "bare";
261
284
  closeMode?: "active" | "always";
262
285
  addLabel?: string;
@@ -277,6 +300,18 @@ export interface HostCheckboxProps {
277
300
  variant?: "default" | "desktop";
278
301
  }
279
302
 
303
+ export interface HostPopoverProps {
304
+ open: boolean;
305
+ onOpenChange(open: boolean): void;
306
+ trigger: ReactNode;
307
+ children: ReactNode;
308
+ anchorPoint?: { x: number; y: number } | null;
309
+ placement?: "bottom-start" | "bottom-end";
310
+ minWidth?: number | string;
311
+ maxWidth?: number | string;
312
+ label?: string;
313
+ }
314
+
280
315
  export interface UiHost {
281
316
  kind?: "opentui" | "desktop-web";
282
317
  capabilities?: {
@@ -302,6 +337,7 @@ export interface UiHost {
302
337
  Textarea: ComponentType<TextareaProps>;
303
338
  ChartSurface: ComponentType<ChartSurfaceProps>;
304
339
  ImageSurface: ComponentType<ImageSurfaceProps>;
340
+ MediaSurface: ComponentType<MediaSurfaceProps>;
305
341
  SpinnerMark: ComponentType<SpinnerMarkProps>;
306
342
  AsciiText: ComponentType<AsciiTextProps>;
307
343
  Button?: ComponentType<any>;
@@ -313,6 +349,7 @@ export interface UiHost {
313
349
  PageStackView?: ComponentType<any>;
314
350
  Tabs?: ComponentType<HostTabsProps>;
315
351
  Checkbox?: ComponentType<HostCheckboxProps>;
352
+ Popover?: ComponentType<HostPopoverProps>;
316
353
  DataTable?: ComponentType<any>;
317
354
  createSyntaxStyle?(): SyntaxStyleLike;
318
355
  colorFromHex?(hex: string): unknown;
@@ -329,6 +366,8 @@ export interface RendererHost {
329
366
  supportsNativeDesktopNotifications?: boolean;
330
367
  notify(notification: AppNotificationRequest): void;
331
368
  showContextMenu?(items: ContextMenuItem[]): Promise<boolean>;
369
+ playTerminalMedia?(url: string, title?: string, options?: { muted?: boolean }): Promise<void>;
370
+ resolveLiveStream?(request: LiveStreamResolveRequest): Promise<ResolvedLiveStream>;
332
371
  }
333
372
 
334
373
  interface UiHostContextValue {
package/src/ui/index.tsx CHANGED
@@ -37,8 +37,11 @@ export type {
37
37
  BoxRenderable,
38
38
  ChartSurfaceProps,
39
39
  HostCheckboxProps,
40
+ HostPopoverProps,
40
41
  Highlight,
41
42
  ImageSurfaceProps,
43
+ MediaSurfaceHandle,
44
+ MediaSurfaceProps,
42
45
  InputRenderable,
43
46
  NativeCursorState,
44
47
  NativeRendererHost,
@@ -283,6 +286,12 @@ export const ImageSurface = forwardRef<any, ComponentProps<UiHost["ImageSurface"
283
286
  });
284
287
  ImageSurface.displayName = "ImageSurface";
285
288
 
289
+ export const MediaSurface = forwardRef<any, ComponentProps<UiHost["MediaSurface"]>>((props, ref) => {
290
+ const { MediaSurface: HostMediaSurface } = useUiHost();
291
+ return createElement(HostMediaSurface as any, { ...props, ref });
292
+ });
293
+ MediaSurface.displayName = "MediaSurface";
294
+
286
295
  export const SpinnerMark = forwardRef<any, ComponentProps<UiHost["SpinnerMark"]>>((props, ref) => {
287
296
  const { SpinnerMark: HostSpinnerMark } = useUiHost();
288
297
  return createElement(HostSpinnerMark as any, { ...props, ref });
@@ -1,5 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { canonicalExchange, CANONICAL_EXCHANGE_ALIASES, EXCHANGE_TIME_ZONES, resolveExchangeTimeZone } from "./exchanges";
2
+ import {
3
+ canonicalExchange,
4
+ CANONICAL_EXCHANGE_ALIASES,
5
+ EXCHANGE_TIME_ZONES,
6
+ parsePublicTickerKey,
7
+ publicTickerKey,
8
+ resolveExchangeTimeZone,
9
+ } from "./exchanges";
3
10
 
4
11
  describe("exchange metadata", () => {
5
12
  test("has a valid timezone for every canonical exchange", () => {
@@ -23,4 +30,9 @@ describe("exchange metadata", () => {
23
30
  expect(resolveExchangeTimeZone("TSE")).toBe("America/Toronto");
24
31
  expect(resolveExchangeTimeZone("AEB")).toBe("Europe/Amsterdam");
25
32
  });
33
+
34
+ test("round-trips public exchange-qualified ticker keys", () => {
35
+ expect(publicTickerKey("3hnx", "LSE")).toBe("3HNX:XLON");
36
+ expect(parsePublicTickerKey("3hnx:xlon")).toEqual({ symbol: "3HNX", exchange: "LSE" });
37
+ });
26
38
  });
@@ -238,6 +238,16 @@ export function publicExchange(value?: string): string {
238
238
  return PUBLIC_EXCHANGE_ALIASES[canonical] ?? canonical;
239
239
  }
240
240
 
241
+ export function parsePublicTickerKey(value: string): { symbol: string; exchange?: string } {
242
+ const normalized = normalizeSymbol(value);
243
+ const separator = normalized.lastIndexOf(":");
244
+ if (separator <= 0 || separator === normalized.length - 1) return { symbol: normalized };
245
+ return {
246
+ symbol: normalized.slice(0, separator),
247
+ exchange: canonicalExchange(normalized.slice(separator + 1)),
248
+ };
249
+ }
250
+
241
251
  export function canonicalTickerKey(symbol: string, exchange?: string): string {
242
252
  const normalizedSymbol = normalizeSymbol(symbol);
243
253
  const canonical = canonicalExchange(exchange);
@@ -0,0 +1,138 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mergeFinancialStatementRows } from "./financial-statements";
3
+
4
+ describe("mergeFinancialStatementRows", () => {
5
+ test("preserves per-field availability across providers", () => {
6
+ const [merged] = mergeFinancialStatementRows(
7
+ [{
8
+ date: "2025-12-31",
9
+ totalRevenue: 120,
10
+ availableAt: "2026-02-10",
11
+ fieldAvailability: { totalRevenue: "2026-02-10" },
12
+ }],
13
+ [{
14
+ date: "2025-12-31",
15
+ grossProfit: 72,
16
+ availableAt: "2026-02-12",
17
+ fieldAvailability: { grossProfit: "2026-02-12" },
18
+ }],
19
+ );
20
+
21
+ expect(merged).toMatchObject({
22
+ availableAt: "2026-02-12",
23
+ totalRevenue: 120,
24
+ grossProfit: 72,
25
+ fieldAvailability: {
26
+ totalRevenue: "2026-02-10",
27
+ grossProfit: "2026-02-12",
28
+ },
29
+ });
30
+ });
31
+
32
+ test("never attaches fallback provenance to a different primary value", () => {
33
+ const [merged] = mergeFinancialStatementRows(
34
+ [{
35
+ date: "2025-12-31",
36
+ totalRevenue: 120,
37
+ availableAt: "2026-03-15",
38
+ }],
39
+ [{
40
+ date: "2025-12-31",
41
+ totalRevenue: 100,
42
+ fieldAvailability: { totalRevenue: "2026-02-01" },
43
+ }],
44
+ );
45
+
46
+ expect(merged).toMatchObject({
47
+ totalRevenue: 120,
48
+ availableAt: "2026-03-15",
49
+ fieldAvailability: { totalRevenue: "2026-03-15" },
50
+ });
51
+ });
52
+
53
+ test("copies fallback availability only when the retained value matches", () => {
54
+ const [matching] = mergeFinancialStatementRows(
55
+ [{ date: "2025-12-31", totalRevenue: 100 }],
56
+ [{
57
+ date: "2025-12-31",
58
+ totalRevenue: 100,
59
+ fieldAvailability: { totalRevenue: "2026-02-01" },
60
+ }],
61
+ );
62
+ const [different] = mergeFinancialStatementRows(
63
+ [{ date: "2025-12-31", totalRevenue: 120 }],
64
+ [{
65
+ date: "2025-12-31",
66
+ totalRevenue: 100,
67
+ fieldAvailability: { totalRevenue: "2026-02-01" },
68
+ }],
69
+ );
70
+
71
+ expect(matching?.fieldAvailability?.totalRevenue).toBe("2026-02-01");
72
+ expect(different?.fieldAvailability?.totalRevenue).toBeUndefined();
73
+ expect(different?.availableAt).toBeUndefined();
74
+ });
75
+
76
+ test("coalesces calendar-normalized and issuer fiscal period ends", () => {
77
+ const merged = mergeFinancialStatementRows(
78
+ [
79
+ { date: "2025-06-30", totalRevenue: 94_036 },
80
+ { date: "2025-09-30", totalRevenue: 102_466 },
81
+ ],
82
+ [
83
+ {
84
+ date: "2025-06-28",
85
+ totalRevenue: 94_036,
86
+ fieldAvailability: { totalRevenue: "2025-08-01" },
87
+ },
88
+ {
89
+ date: "2025-09-27",
90
+ totalRevenue: 102_466,
91
+ fieldAvailability: { totalRevenue: "2025-10-31" },
92
+ },
93
+ ],
94
+ );
95
+
96
+ expect(merged).toEqual([
97
+ {
98
+ date: "2025-06-28",
99
+ availableAt: "2025-08-01",
100
+ totalRevenue: 94_036,
101
+ fieldAvailability: { totalRevenue: "2025-08-01" },
102
+ },
103
+ {
104
+ date: "2025-09-27",
105
+ availableAt: "2025-10-31",
106
+ totalRevenue: 102_466,
107
+ fieldAvailability: { totalRevenue: "2025-10-31" },
108
+ },
109
+ ]);
110
+ });
111
+
112
+ test("does not coalesce statement rows outside the fiscal-close tolerance", () => {
113
+ const merged = mergeFinancialStatementRows(
114
+ [{ date: "2025-06-30", totalRevenue: 94_036 }],
115
+ [{
116
+ date: "2025-07-15",
117
+ totalRevenue: 94_036,
118
+ fieldAvailability: { totalRevenue: "2025-08-01" },
119
+ }],
120
+ );
121
+
122
+ expect(merged.map((row) => row.date)).toEqual(["2025-06-30", "2025-07-15"]);
123
+ });
124
+
125
+ test("retains a primary fiscal close when only the fallback is calendar-normalized", () => {
126
+ const merged = mergeFinancialStatementRows(
127
+ [{
128
+ date: "2025-06-28",
129
+ totalRevenue: 94_036,
130
+ fieldAvailability: { totalRevenue: "2025-08-01" },
131
+ }],
132
+ [{ date: "2025-06-30", totalRevenue: 94_036 }],
133
+ );
134
+
135
+ expect(merged).toHaveLength(1);
136
+ expect(merged[0]?.date).toBe("2025-06-28");
137
+ });
138
+ });
@@ -1,5 +1,71 @@
1
1
  import type { FinancialStatement } from "../types/financials";
2
2
 
3
+ const STATEMENT_METADATA_KEYS = new Set(["date", "availableAt", "fieldAvailability"]);
4
+ const NEARBY_PERIOD_END_MS = 7 * 24 * 60 * 60 * 1_000;
5
+
6
+ function metricKeys(...rows: Array<FinancialStatement | undefined>): string[] {
7
+ return [...new Set(rows.flatMap((row) => Object.keys(row ?? {})))]
8
+ .filter((key) => !STATEMENT_METADATA_KEYS.has(key));
9
+ }
10
+
11
+ function metricValue(row: FinancialStatement | undefined, key: string): unknown {
12
+ return (row as unknown as Record<string, unknown> | undefined)?.[key];
13
+ }
14
+
15
+ function hasMetricValue(row: FinancialStatement | undefined, key: string): boolean {
16
+ return typeof metricValue(row, key) === "number";
17
+ }
18
+
19
+ function hasAvailabilityEvidence(row: FinancialStatement | undefined): boolean {
20
+ return !!row?.availableAt || Object.keys(row?.fieldAvailability ?? {}).length > 0;
21
+ }
22
+
23
+ function canonicalStatementDate(
24
+ primary: FinancialStatement,
25
+ fallback: FinancialStatement | undefined,
26
+ ): string {
27
+ if (!fallback) return primary.date;
28
+ return hasAvailabilityEvidence(fallback) && !hasAvailabilityEvidence(primary)
29
+ ? fallback.date
30
+ : primary.date;
31
+ }
32
+
33
+ function statementDateTime(row: FinancialStatement): number | null {
34
+ const time = Date.parse(`${row.date}T00:00:00Z`);
35
+ return Number.isFinite(time) ? time : null;
36
+ }
37
+
38
+ export function areNearbyFinancialPeriodEnds(
39
+ left: string | Date,
40
+ right: string | Date,
41
+ ): boolean {
42
+ const leftTime = left instanceof Date ? left.getTime() : Date.parse(`${left}T00:00:00Z`);
43
+ const rightTime = right instanceof Date ? right.getTime() : Date.parse(`${right}T00:00:00Z`);
44
+ return Number.isFinite(leftTime)
45
+ && Number.isFinite(rightTime)
46
+ && Math.abs(leftTime - rightTime) <= NEARBY_PERIOD_END_MS;
47
+ }
48
+
49
+ function matchFallbackRow(
50
+ primary: FinancialStatement,
51
+ fallbackRows: FinancialStatement[],
52
+ usedFallbackRows: Set<FinancialStatement>,
53
+ ): FinancialStatement | undefined {
54
+ const exact = fallbackRows.find((row) => row.date === primary.date && !usedFallbackRows.has(row));
55
+ if (exact) return exact;
56
+ const primaryTime = statementDateTime(primary);
57
+ if (primaryTime === null) return undefined;
58
+ return fallbackRows
59
+ .flatMap((row) => {
60
+ if (usedFallbackRows.has(row)) return [];
61
+ const fallbackTime = statementDateTime(row);
62
+ if (fallbackTime === null) return [];
63
+ const distance = Math.abs(fallbackTime - primaryTime);
64
+ return areNearbyFinancialPeriodEnds(primary.date, row.date) ? [{ row, distance }] : [];
65
+ })
66
+ .sort((left, right) => left.distance - right.distance || left.row.date.localeCompare(right.row.date))[0]?.row;
67
+ }
68
+
3
69
  export function mergeFinancialStatementRows(
4
70
  primaryRows: FinancialStatement[],
5
71
  fallbackRows: FinancialStatement[],
@@ -7,16 +73,57 @@ export function mergeFinancialStatementRows(
7
73
  if (primaryRows.length === 0) return fallbackRows;
8
74
  if (fallbackRows.length === 0) return primaryRows;
9
75
 
10
- const fallbackByDate = new Map(fallbackRows.map((row) => [row.date, row]));
11
- const primaryDates = new Set(primaryRows.map((row) => row.date));
12
- const mergedRows = primaryRows.map((row) => ({
13
- ...fallbackByDate.get(row.date),
14
- ...row,
15
- date: row.date,
16
- }));
76
+ const usedFallbackRows = new Set<FinancialStatement>();
77
+ const mergedRows = primaryRows.map((row) => {
78
+ const fallback = matchFallbackRow(row, fallbackRows, usedFallbackRows);
79
+ if (fallback) usedFallbackRows.add(fallback);
80
+ const merged = {
81
+ ...fallback,
82
+ ...row,
83
+ // Prefer the date backed by filing provenance. Generic provider merges
84
+ // otherwise retain their primary provider's period identity.
85
+ date: canonicalStatementDate(row, fallback),
86
+ } as FinancialStatement;
87
+ const fieldAvailability: Record<string, string> = {};
88
+ const keys = metricKeys(row, fallback);
89
+
90
+ for (const key of keys) {
91
+ const primaryHasValue = hasMetricValue(row, key);
92
+ const fallbackHasValue = hasMetricValue(fallback, key);
93
+ if (!primaryHasValue && fallbackHasValue) {
94
+ (merged as unknown as Record<string, unknown>)[key] = metricValue(fallback, key);
95
+ }
96
+
97
+ if (primaryHasValue) {
98
+ const primaryAvailability = row.fieldAvailability?.[key] ?? row.availableAt;
99
+ const valuesMatch = fallbackHasValue && Object.is(metricValue(row, key), metricValue(fallback, key));
100
+ const availability = primaryAvailability
101
+ ?? (valuesMatch ? fallback?.fieldAvailability?.[key] ?? fallback?.availableAt : undefined);
102
+ if (availability) fieldAvailability[key] = availability;
103
+ } else if (fallbackHasValue) {
104
+ const availability = fallback?.fieldAvailability?.[key] ?? fallback?.availableAt;
105
+ if (availability) fieldAvailability[key] = availability;
106
+ }
107
+ }
108
+
109
+ // A fallback row-level date cannot safely date a different primary value.
110
+ // Retained fallback fields still carry their own per-field provenance.
111
+ const primaryHasMetrics = keys.some((key) => hasMetricValue(row, key));
112
+ const retainedMetricKeys = keys.filter((key) => hasMetricValue(merged, key));
113
+ const completeFieldAvailability = retainedMetricKeys.length > 0
114
+ && retainedMetricKeys.every((key) => !!fieldAvailability[key]);
115
+ const availableAt = completeFieldAvailability
116
+ ? Object.values(fieldAvailability).sort().at(-1)
117
+ : row.availableAt ?? (!primaryHasMetrics ? fallback?.availableAt : undefined);
118
+ if (availableAt) merged.availableAt = availableAt;
119
+ else delete merged.availableAt;
120
+ if (Object.keys(fieldAvailability).length > 0) merged.fieldAvailability = fieldAvailability;
121
+ else delete merged.fieldAvailability;
122
+ return merged;
123
+ });
17
124
 
18
125
  for (const row of fallbackRows) {
19
- if (!primaryDates.has(row.date)) mergedRows.push(row);
126
+ if (!usedFallbackRows.has(row)) mergedRows.push(row);
20
127
  }
21
128
 
22
129
  return mergedRows.sort((left, right) => left.date.localeCompare(right.date));
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { displayWidth, formatTimeAgo, padTo } from "./format";
2
+ import { displayWidth, formatTimeAgo, padTo, truncateToDisplayWidth } from "./format";
3
3
  import { normalizeTimestamp } from "./timestamp";
4
4
 
5
5
  describe("formatTimeAgo", () => {
@@ -29,3 +29,13 @@ describe("padTo", () => {
29
29
  expect(padTo("πŸ‡ΊπŸ‡Έ CPI", 6)).toBe("πŸ‡ΊπŸ‡Έ CPI");
30
30
  });
31
31
  });
32
+
33
+ describe("truncateToDisplayWidth", () => {
34
+ test("preserves grapheme clusters and stays within the terminal cell budget", () => {
35
+ expect(displayWidth("πŸ“ˆ")).toBe(2);
36
+ expect(displayWidth("e\u0301")).toBe(1);
37
+ expect(displayWidth("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦")).toBe(2);
38
+ expect(truncateToDisplayWidth("ζŠ•θ΅„η»„εˆεˆ†ζžι’ζΏ", 9)).toBe("ζŠ•θ΅„η»„...");
39
+ expect(displayWidth(truncateToDisplayWidth("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ family", 7))).toBeLessThanOrEqual(7);
40
+ });
41
+ });
@@ -186,6 +186,13 @@ function truncateToWidth(value: string, width: number): string {
186
186
  return output;
187
187
  }
188
188
 
189
+ export function truncateToDisplayWidth(value: string, width: number): string {
190
+ if (width <= 0) return "";
191
+ if (displayWidth(value) <= width) return value;
192
+ if (width <= 3) return ".".repeat(width);
193
+ return `${truncateToWidth(value, width - 3)}...`;
194
+ }
195
+
189
196
  /** Pad/truncate a string to a fixed display width */
190
197
  export function padTo(str: string, width: number, align: "left" | "right" | "center" = "left"): string {
191
198
  const clipped = displayWidth(str) > width ? truncateToWidth(str, width) : str;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.9.9";
1
+ export const VERSION = "0.10.0";