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 { scheduleConfigSave } from "../state/config-save-scheduler";
2
+ import { stableStringify } from "../remote/revision";
2
3
  import type { AppConfig, BrokerInstanceConfig } from "../types/config";
3
4
  import type { PricePoint, TickerFinancials } from "../types/financials";
4
5
  import type { Portfolio, TickerMetadata, TickerPosition, TickerRecord, Watchlist } from "../types/ticker";
@@ -11,7 +12,18 @@ import {
11
12
  computeWeightedPortfolioReturns,
12
13
  type WeightedReturnSeries,
13
14
  } from "../plugins/builtin/analytics/metrics";
14
- import { getSyncedProfileAnalytics } from "./profile-analytics";
15
+ import {
16
+ getSyncedProfileAnalytics,
17
+ setSyncedProfileAnalytics,
18
+ } from "./profile-analytics";
19
+ import {
20
+ addLegacyBuiltinDisabledPluginAliases,
21
+ addLegacyBuiltinPaneStatePluginAliases,
22
+ addLegacyBuiltinPluginOwnerAliases,
23
+ normalizeBuiltinDisabledPluginIds,
24
+ normalizeBuiltinPaneStatePluginOwners,
25
+ normalizeBuiltinPluginStateMap,
26
+ } from "../plugins/ownership";
15
27
 
16
28
  const SENSITIVE_KEY_PATTERN = /(token|secret|password|credential|private|api[_-]?key|access[_-]?key|refresh[_-]?key|session|cookie|dataDir|path|directory|localPath)/i;
17
29
 
@@ -151,12 +163,19 @@ function collectCoreConfigPayload(config: AppConfig) {
151
163
  portfolios: config.portfolios.map(sanitizePortfolio),
152
164
  watchlists: config.watchlists.map(sanitizeWatchlist),
153
165
  layout: config.layout,
154
- layouts: config.layouts,
166
+ layouts: config.layouts.map((savedLayout) => (
167
+ savedLayout.paneState
168
+ ? {
169
+ ...savedLayout,
170
+ paneState: addLegacyBuiltinPaneStatePluginAliases(savedLayout.paneState),
171
+ }
172
+ : savedLayout
173
+ )),
155
174
  activeLayoutIndex: config.activeLayoutIndex,
156
175
  brokerInstances: config.brokerInstances.map(sanitizeBrokerInstance),
157
- disabledPlugins: config.disabledPlugins,
176
+ disabledPlugins: addLegacyBuiltinDisabledPluginAliases(config.disabledPlugins),
158
177
  disabledSources: config.disabledSources,
159
- pluginConfig: config.pluginConfig,
178
+ pluginConfig: addLegacyBuiltinPluginOwnerAliases(config.pluginConfig),
160
179
  theme: config.theme,
161
180
  chartPreferences: config.chartPreferences,
162
181
  valueFlashingEnabled: config.valueFlashingEnabled,
@@ -295,32 +314,88 @@ function collectCoreCollectionsPayload(
295
314
  };
296
315
  }
297
316
 
298
- function mergeConfigPayload(config: AppConfig, payload: unknown): AppConfig | null {
317
+ function hydrateProfileAnalytics(payload: Record<string, unknown>): void {
318
+ if (!isPlainObject(payload.analyticsByPortfolio)) return;
319
+ for (const [portfolioId, analytics] of Object.entries(payload.analyticsByPortfolio)) {
320
+ if (!isPlainObject(analytics)) continue;
321
+ setSyncedProfileAnalytics(portfolioId, {
322
+ oneYearReturn: typeof analytics.oneYearReturn === "number" ? analytics.oneYearReturn : null,
323
+ spyBeta: typeof analytics.spyBeta === "number" ? analytics.spyBeta : null,
324
+ });
325
+ }
326
+ }
327
+
328
+ function valuesEqual(left: unknown, right: unknown): boolean {
329
+ return stableStringify(left) === stableStringify(right);
330
+ }
331
+
332
+ function isPluginStateMap(value: unknown): value is Record<string, Record<string, unknown>> {
333
+ return isPlainObject(value) && Object.values(value).every(isPlainObject);
334
+ }
335
+
336
+ function normalizeSyncedLayouts(layouts: AppConfig["layouts"]): AppConfig["layouts"] {
337
+ return layouts.map((savedLayout) => {
338
+ if (!isPlainObject(savedLayout)) return savedLayout;
339
+ if (!isPluginStateMap(savedLayout.paneState)) return savedLayout;
340
+ return {
341
+ ...savedLayout,
342
+ paneState: normalizeBuiltinPaneStatePluginOwners(savedLayout.paneState),
343
+ };
344
+ });
345
+ }
346
+
347
+ function mergeConfigPayload(
348
+ config: AppConfig,
349
+ payload: unknown,
350
+ baselineConfig: AppConfig = config,
351
+ ): AppConfig | null {
299
352
  if (!isPlainObject(payload)) return null;
300
353
  const next: AppConfig = { ...config };
354
+ const canApply = <K extends keyof AppConfig>(key: K) => (
355
+ valuesEqual(config[key], baselineConfig[key])
356
+ );
301
357
  const assign = <K extends keyof AppConfig>(key: K) => {
302
- if (key in payload) {
358
+ if (key in payload && canApply(key)) {
303
359
  next[key] = payload[key as string] as AppConfig[K];
304
360
  }
305
361
  };
306
362
 
307
363
  assign("baseCurrency");
308
364
  assign("refreshIntervalMinutes");
309
- assign("layout");
310
- assign("layouts");
311
- assign("activeLayoutIndex");
312
- assign("disabledPlugins");
365
+ assign("portfolios");
366
+ assign("watchlists");
313
367
  assign("disabledSources");
314
- assign("pluginConfig");
315
368
  assign("theme");
316
369
  assign("chartPreferences");
317
370
  assign("valueFlashingEnabled");
318
371
  assign("recentTickers");
319
372
  assign("onboardingComplete");
320
373
 
321
- if (Array.isArray(payload.portfolios)) next.portfolios = payload.portfolios as Portfolio[];
322
- if (Array.isArray(payload.watchlists)) next.watchlists = payload.watchlists as Watchlist[];
323
- if (Array.isArray(payload.brokerInstances)) {
374
+ if (
375
+ canApply("disabledPlugins")
376
+ && Array.isArray(payload.disabledPlugins)
377
+ && payload.disabledPlugins.every((pluginId) => typeof pluginId === "string")
378
+ ) {
379
+ next.disabledPlugins = normalizeBuiltinDisabledPluginIds(payload.disabledPlugins);
380
+ }
381
+ if (canApply("pluginConfig") && isPluginStateMap(payload.pluginConfig)) {
382
+ next.pluginConfig = normalizeBuiltinPluginStateMap(payload.pluginConfig);
383
+ }
384
+
385
+ const layoutStateUntouched = config.layout === baselineConfig.layout
386
+ && config.layouts === baselineConfig.layouts
387
+ && config.activeLayoutIndex === baselineConfig.activeLayoutIndex;
388
+ if (
389
+ layoutStateUntouched
390
+ && ["layout", "layouts", "activeLayoutIndex"].every((key) => key in payload)
391
+ && Array.isArray(payload.layouts)
392
+ ) {
393
+ next.layout = payload.layout as unknown as AppConfig["layout"];
394
+ next.layouts = normalizeSyncedLayouts(payload.layouts as AppConfig["layouts"]);
395
+ next.activeLayoutIndex = payload.activeLayoutIndex as number;
396
+ }
397
+
398
+ if (Array.isArray(payload.brokerInstances) && canApply("brokerInstances")) {
324
399
  const incoming = payload.brokerInstances as Array<Partial<BrokerInstanceConfig>>;
325
400
  const existingById = new Map(config.brokerInstances.map((instance) => [instance.id, instance]));
326
401
  next.brokerInstances = incoming.map((instance) => {
@@ -343,9 +418,9 @@ export const coreConfigSyncContributor: SyncContributor = {
343
418
  id: "core.config",
344
419
  schemaVersion: 1,
345
420
  collect: ({ state }) => collectCoreConfigPayload(state.config),
346
- apply: (payload, { state, dispatch }) => {
347
- const nextConfig = mergeConfigPayload(state.config, payload);
348
- if (!nextConfig) return;
421
+ apply: (payload, { baselineState, state, dispatch }) => {
422
+ const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config);
423
+ if (!nextConfig || valuesEqual(nextConfig, state.config)) return;
349
424
  dispatch({ type: "SET_CONFIG", config: nextConfig });
350
425
  scheduleConfigSave(nextConfig);
351
426
  },
@@ -360,18 +435,16 @@ export const coreCollectionsSyncContributor: SyncContributor = {
360
435
  state.financials,
361
436
  state.exchangeRates,
362
437
  ),
363
- apply: async (payload, { state, dispatch, tickerRepository }) => {
438
+ apply: async (payload, { getState, isCurrent, dispatch, tickerRepository }) => {
364
439
  if (!isPlainObject(payload)) return;
365
- const nextConfig = { ...state.config };
366
- if (Array.isArray(payload.portfolios)) nextConfig.portfolios = payload.portfolios as Portfolio[];
367
- if (Array.isArray(payload.watchlists)) nextConfig.watchlists = payload.watchlists as Watchlist[];
368
-
369
- const nextTickers = new Map(state.tickers);
440
+ hydrateProfileAnalytics(payload);
441
+ const incomingRecords: TickerRecord[] = [];
370
442
  const rawTickers = Array.isArray(payload.tickers) ? payload.tickers : [];
371
443
  for (const rawTicker of rawTickers) {
444
+ if (!isCurrent()) return;
372
445
  if (!isPlainObject(rawTicker)) continue;
373
446
  const current = typeof rawTicker.ticker === "string"
374
- ? nextTickers.get(rawTicker.ticker)
447
+ ? getState().tickers.get(rawTicker.ticker)
375
448
  : null;
376
449
  const metadata = hydrateTickerMetadata({
377
450
  ...current?.metadata,
@@ -380,12 +453,15 @@ export const coreCollectionsSyncContributor: SyncContributor = {
380
453
  });
381
454
  const record: TickerRecord = { metadata };
382
455
  await tickerRepository.saveTicker(record);
383
- nextTickers.set(metadata.ticker, record);
456
+ incomingRecords.push(record);
384
457
  }
385
458
 
386
- dispatch({ type: "SET_CONFIG", config: nextConfig });
459
+ if (!isCurrent() || incomingRecords.length === 0) return;
460
+ const nextTickers = new Map(getState().tickers);
461
+ for (const record of incomingRecords) {
462
+ nextTickers.set(record.metadata.ticker, record);
463
+ }
387
464
  dispatch({ type: "SET_TICKERS", tickers: nextTickers });
388
- scheduleConfigSave(nextConfig);
389
465
  },
390
466
  };
391
467
 
@@ -397,4 +473,5 @@ export const __syncContributorInternalsForTests = {
397
473
  sanitizeUnknown,
398
474
  collectCoreConfigPayload,
399
475
  collectCoreCollectionsPayload,
476
+ mergeConfigPayload,
400
477
  };
package/src/sync/react.ts CHANGED
@@ -6,6 +6,7 @@ import { cloudSyncController } from "./controller";
6
6
 
7
7
  interface CloudSyncRuntimeOptions {
8
8
  state: AppState;
9
+ getState: () => AppState;
9
10
  dispatch: Dispatch<AppAction>;
10
11
  tickerRepository: TickerRepository;
11
12
  pluginRegistry: PluginRegistry;
@@ -14,28 +15,25 @@ interface CloudSyncRuntimeOptions {
14
15
 
15
16
  export function useCloudSyncRuntime({
16
17
  state,
18
+ getState,
17
19
  dispatch,
18
20
  tickerRepository,
19
21
  pluginRegistry,
20
22
  initialized,
21
23
  }: CloudSyncRuntimeOptions): void {
22
24
  useEffect(() => {
23
- cloudSyncController.setRuntime({
24
- state,
25
+ return cloudSyncController.setRuntime({
26
+ getState,
25
27
  dispatch,
26
28
  tickerRepository,
27
29
  getContributors: () => pluginRegistry.getEnabledSyncContributors(),
28
30
  getTransport: () => pluginRegistry.getActiveSyncTransport(),
29
31
  });
30
- }, [dispatch, pluginRegistry, state, tickerRepository]);
31
-
32
- useEffect(() => () => {
33
- cloudSyncController.clearRuntime();
34
- }, []);
32
+ }, [dispatch, getState, pluginRegistry, tickerRepository]);
35
33
 
36
34
  useEffect(() => {
37
35
  if (!initialized) return;
38
- void cloudSyncController.pullLatest();
36
+ void cloudSyncController.requestSync({ reason: "startup" });
39
37
  }, [initialized, pluginRegistry]);
40
38
 
41
39
  useEffect(() => {
package/src/sync/types.ts CHANGED
@@ -25,7 +25,10 @@ export interface SyncCollectContext {
25
25
 
26
26
  export interface SyncApplyContext {
27
27
  snapshot: SyncSnapshot;
28
+ baselineState: AppState;
28
29
  state: AppState;
30
+ getState: () => AppState;
31
+ isCurrent: () => boolean;
29
32
  dispatch: Dispatch<AppAction>;
30
33
  tickerRepository: TickerRepository;
31
34
  }
@@ -0,0 +1,158 @@
1
+ import type { ResolvedSeries, TimeSeriesPoint } from "./types";
2
+
3
+ export interface AlignmentOptions {
4
+ mode?: "union" | "intersection";
5
+ timeline?: readonly Date[];
6
+ carryForward?: boolean;
7
+ maxCarryMilliseconds?: number;
8
+ start?: Date;
9
+ end?: Date;
10
+ }
11
+
12
+ export interface AlignedSeriesValue {
13
+ point: TimeSeriesPoint;
14
+ value: number | null;
15
+ carried: boolean;
16
+ }
17
+
18
+ export interface AlignedTimeSeriesRow {
19
+ date: Date;
20
+ values: Record<string, AlignedSeriesValue | null>;
21
+ }
22
+
23
+ function finiteNumber(value: unknown): value is number {
24
+ return typeof value === "number" && Number.isFinite(value);
25
+ }
26
+
27
+ export function scalarPointValue(point: TimeSeriesPoint): number | null {
28
+ return finiteNumber(point.value)
29
+ ? point.value
30
+ : finiteNumber(point.close)
31
+ ? point.close
32
+ : null;
33
+ }
34
+
35
+ function sortedUniquePoints(points: readonly TimeSeriesPoint[]): TimeSeriesPoint[] {
36
+ const byTime = new Map<number, TimeSeriesPoint>();
37
+ for (const point of points) {
38
+ const time = point.date.getTime();
39
+ if (Number.isFinite(time)) byTime.set(time, point);
40
+ }
41
+ return [...byTime.values()].sort((left, right) => left.date.getTime() - right.date.getTime());
42
+ }
43
+
44
+ export function effectiveTimeSeriesPointTime(point: TimeSeriesPoint): number {
45
+ const pointTime = point.date.getTime();
46
+ const availableTime = point.availableAt?.getTime();
47
+ return finiteNumber(availableTime) ? Math.max(pointTime, availableTime) : pointTime;
48
+ }
49
+
50
+ /**
51
+ * Aligns heterogeneous observations on one timeline. Step series carry their
52
+ * last known value forward only after `availableAt`; values are never filled
53
+ * backward before the first public observation.
54
+ */
55
+ export function alignTimeSeries(
56
+ sourceSeries: readonly ResolvedSeries[],
57
+ options: AlignmentOptions = {},
58
+ ): AlignedTimeSeriesRow[] {
59
+ const series = sourceSeries.map((entry) => ({ ...entry, points: sortedUniquePoints(entry.points) }));
60
+ const start = options.start?.getTime() ?? Number.NEGATIVE_INFINITY;
61
+ const end = options.end?.getTime() ?? Number.POSITIVE_INFINITY;
62
+ const timeline = new Set<number>();
63
+ for (const date of options.timeline ?? []) {
64
+ const time = date.getTime();
65
+ if (Number.isFinite(time) && time >= start && time <= end) timeline.add(time);
66
+ }
67
+ if (!options.timeline) {
68
+ for (const entry of series) {
69
+ for (const point of entry.points) {
70
+ const time = point.date.getTime();
71
+ if (time >= start && time <= end) timeline.add(time);
72
+ const effectiveTime = effectiveTimeSeriesPointTime(point);
73
+ if (effectiveTime !== time && effectiveTime >= start && effectiveTime <= end) {
74
+ timeline.add(effectiveTime);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ const exactMaps = series.map((entry) => (
81
+ new Map(entry.points.map((point) => [effectiveTimeSeriesPointTime(point), point]))
82
+ ));
83
+ const sortedTimes = [...timeline].sort((left, right) => left - right);
84
+ const rows: AlignedTimeSeriesRow[] = [];
85
+ for (const time of sortedTimes) {
86
+ const values: Record<string, AlignedSeriesValue | null> = {};
87
+ series.forEach((entry, seriesIndex) => {
88
+ const exact = exactMaps[seriesIndex]!.get(time);
89
+ if (exact) {
90
+ values[entry.id] = { point: exact, value: scalarPointValue(exact), carried: false };
91
+ return;
92
+ }
93
+ const allowCarry = options.carryForward ?? entry.interpolation === "step-after";
94
+ if (!allowCarry) {
95
+ values[entry.id] = null;
96
+ return;
97
+ }
98
+
99
+ let previous: TimeSeriesPoint | null = null;
100
+ let previousEligibleAt = Number.NEGATIVE_INFINITY;
101
+ for (const point of entry.points) {
102
+ const eligibleAt = effectiveTimeSeriesPointTime(point);
103
+ if (eligibleAt <= time && eligibleAt >= previousEligibleAt) {
104
+ previous = point;
105
+ previousEligibleAt = eligibleAt;
106
+ }
107
+ }
108
+ if (!previous) {
109
+ values[entry.id] = null;
110
+ return;
111
+ }
112
+ const age = time - previousEligibleAt;
113
+ if (options.maxCarryMilliseconds !== undefined && age > options.maxCarryMilliseconds) {
114
+ values[entry.id] = null;
115
+ return;
116
+ }
117
+ values[entry.id] = {
118
+ point: previous,
119
+ value: scalarPointValue(previous),
120
+ carried: true,
121
+ };
122
+ });
123
+ if (options.mode !== "intersection" || series.every((entry) => values[entry.id] !== null)) {
124
+ rows.push({ date: new Date(time), values });
125
+ }
126
+ }
127
+ return rows;
128
+ }
129
+
130
+ export function firstCommonValueDate(series: readonly ResolvedSeries[]): Date | null {
131
+ const row = alignTimeSeries(series, { mode: "intersection" }).find((entry) => (
132
+ series.every(({ id }) => finiteNumber(entry.values[id]?.value))
133
+ ));
134
+ return row ? new Date(row.date) : null;
135
+ }
136
+
137
+ /** Returns visible points and optionally the prior step anchor needed to draw continuity. */
138
+ export function clipSeriesToWindow(
139
+ series: ResolvedSeries,
140
+ start: Date,
141
+ end: Date,
142
+ includeStepAnchor = true,
143
+ ): ResolvedSeries {
144
+ const startTime = start.getTime();
145
+ const endTime = end.getTime();
146
+ const sorted = sortedUniquePoints(series.points);
147
+ const visible = sorted.filter((point) => {
148
+ const time = point.date.getTime();
149
+ return time >= startTime && time <= endTime;
150
+ });
151
+ if (includeStepAnchor && series.interpolation === "step-after") {
152
+ const anchor = [...sorted].reverse().find((point) => (
153
+ effectiveTimeSeriesPointTime(point) <= startTime && point.date.getTime() < startTime
154
+ ));
155
+ if (anchor) visible.unshift(anchor);
156
+ }
157
+ return { ...series, points: visible };
158
+ }
@@ -0,0 +1,49 @@
1
+ import type { TimeSeriesPoint } from "./types";
2
+
3
+ export interface FredObservationLike {
4
+ date: string;
5
+ value: string | number | null;
6
+ realtime_start?: string;
7
+ realtimeStart?: string;
8
+ }
9
+
10
+ export interface FredExtractionOptions {
11
+ timestampMode?: "available-at" | "period-end";
12
+ providerId?: string;
13
+ }
14
+
15
+ function parsedDate(value: unknown): Date | null {
16
+ if (typeof value !== "string") return null;
17
+ const date = new Date(value);
18
+ return Number.isFinite(date.getTime()) ? date : null;
19
+ }
20
+
21
+ /** Converts FRED observations or vintage observations without coupling to a network client. */
22
+ export function extractFredSeries(
23
+ observations: readonly FredObservationLike[],
24
+ options: FredExtractionOptions = {},
25
+ ): TimeSeriesPoint[] {
26
+ const points: TimeSeriesPoint[] = [];
27
+ for (const observation of observations) {
28
+ const observedAt = parsedDate(observation.date);
29
+ const numericValue = typeof observation.value === "number"
30
+ ? observation.value
31
+ : typeof observation.value === "string" && observation.value.trim() !== ""
32
+ ? Number(observation.value)
33
+ : Number.NaN;
34
+ if (!observedAt || !Number.isFinite(numericValue)) continue;
35
+ const availableAt = parsedDate(observation.realtime_start ?? observation.realtimeStart);
36
+ points.push({
37
+ date: options.timestampMode !== "period-end" && availableAt ? availableAt : observedAt,
38
+ observedAt,
39
+ availableAt: availableAt ?? undefined,
40
+ value: numericValue,
41
+ periodLabel: observation.date,
42
+ provenance: {
43
+ providerId: options.providerId ?? "fred",
44
+ quality: "reported",
45
+ },
46
+ });
47
+ }
48
+ return points.sort((left, right) => left.date.getTime() - right.date.getTime());
49
+ }