gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -3,6 +3,7 @@
3
3
  import { type CSSProperties } from "react";
4
4
  import { Box } from "../../ui";
5
5
  import { blendHex, colors } from "../../theme/colors";
6
+ import { WEB_CELL_HEIGHT } from "../../theme/font-scale";
6
7
 
7
8
  export type NativeSelectElement = HTMLSelectElement & { showPicker?: () => void };
8
9
 
@@ -18,6 +19,11 @@ export interface NativeSelectProps {
18
19
  options: NativeSelectOption[];
19
20
  width?: number | string;
20
21
  height?: number;
22
+ /**
23
+ * `field` is the settings-dialog control. `inline` drops the box so a dense
24
+ * pane row reads as text while staying a real, keyboard-accessible select.
25
+ */
26
+ variant?: "field" | "inline";
21
27
  includeUnsetOption?: boolean;
22
28
  selectRef?: (element: NativeSelectElement | null) => void;
23
29
  onFocus?: () => void;
@@ -41,23 +47,27 @@ export function openNativeSelect(element: NativeSelectElement | null | undefined
41
47
  export function NativeSelect({
42
48
  value,
43
49
  options,
44
- width = 184,
45
- height = 28,
50
+ width,
51
+ height,
52
+ variant = "field",
46
53
  includeUnsetOption = false,
47
54
  selectRef,
48
55
  onFocus,
49
56
  onChange,
50
57
  }: NativeSelectProps) {
58
+ const inline = variant === "inline";
51
59
  const hasCurrentValue = options.some((option) => option.value === value);
60
+ const resolvedWidth = width ?? (inline ? "auto" : 184);
61
+ const resolvedHeight = height ?? (inline ? WEB_CELL_HEIGHT : 28);
52
62
  const style: CSSProperties = {
53
- width,
54
- height,
63
+ width: resolvedWidth,
64
+ height: resolvedHeight,
55
65
  color: colors.text,
56
- backgroundColor: blendHex(colors.panel, colors.textBright, 0.06),
57
- border: `1px solid ${colors.border}`,
58
- borderRadius: 6,
59
- padding: "0 8px",
60
- boxShadow: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
66
+ backgroundColor: inline ? "transparent" : blendHex(colors.panel, colors.textBright, 0.06),
67
+ border: inline ? "none" : `1px solid ${colors.border}`,
68
+ borderRadius: inline ? 0 : 6,
69
+ padding: inline ? 0 : "0 8px",
70
+ boxShadow: inline ? "none" : `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
61
71
  cursor: "pointer",
62
72
  font: "inherit",
63
73
  letterSpacing: 0,
@@ -68,7 +78,7 @@ export function NativeSelect({
68
78
 
69
79
  return (
70
80
  <Box
71
- height={`${height}px`}
81
+ height={`${resolvedHeight}px`}
72
82
  flexDirection="row"
73
83
  alignItems="center"
74
84
  onMouseDown={(event: any) => {
@@ -0,0 +1,138 @@
1
+ import { useCallback } from "react";
2
+ import { Box, Text, TextAttributes, useUiHost } from "../../ui";
3
+ import { type PromptContext, useDialog } from "../../ui/dialog";
4
+ import { useRemoteUiNode } from "../../remote/semantic-tree";
5
+ import { colors } from "../../theme/colors";
6
+ import { ChoiceDialog } from "./choice-dialog";
7
+ import { NativeSelect } from "./native-select";
8
+
9
+ export interface SelectButtonOption<T extends string = string> {
10
+ value: T;
11
+ label: string;
12
+ /** Trigger wording when the full label is too wide for a pane header. */
13
+ short?: string;
14
+ description?: string;
15
+ disabled?: boolean;
16
+ }
17
+
18
+ export interface SelectButtonProps<T extends string = string> {
19
+ /** Names the dimension, e.g. "Prem". Shown before the value on the trigger. */
20
+ label: string;
21
+ value: T;
22
+ options: readonly SelectButtonOption<T>[];
23
+ onChange: (value: T) => void;
24
+ /** Dialog title. Defaults to `label`. */
25
+ title?: string;
26
+ disabled?: boolean;
27
+ /**
28
+ * Draws the value as active. Panes pass `value !== default` so a row of
29
+ * selects shows at a glance which ones are narrowing what you are looking at.
30
+ */
31
+ emphasized?: boolean;
32
+ idPrefix?: string;
33
+ }
34
+
35
+ /**
36
+ * Single-select control usable inside a pane body, not just the settings dialog.
37
+ * Desktop gets a real `<select>`; the terminal has no dropdown primitive, so it
38
+ * opens the standard choice dialog, which shows the full option labels instead
39
+ * of making people click a chip repeatedly to discover what the values are.
40
+ */
41
+ export function SelectButton<T extends string = string>({
42
+ label,
43
+ value,
44
+ options,
45
+ onChange,
46
+ title,
47
+ disabled = false,
48
+ emphasized = false,
49
+ idPrefix,
50
+ }: SelectButtonProps<T>) {
51
+ const isDesktopWeb = useUiHost().kind === "desktop-web";
52
+ const dialog = useDialog();
53
+ const current = options.find((option) => option.value === value);
54
+
55
+ const selectValue = useCallback((next: unknown) => {
56
+ const candidate = typeof next === "string" ? next : (next as { value?: string })?.value;
57
+ const match = options.find((option) => option.value === candidate);
58
+ if (!match || match.disabled || match.value === value) return;
59
+ onChange(match.value);
60
+ }, [onChange, options, value]);
61
+
62
+ // Same parity as Button and Tabs: the control has to be drivable without a
63
+ // mouse for remote control and UI automation.
64
+ useRemoteUiNode({
65
+ role: "select",
66
+ label,
67
+ disabled,
68
+ actions: { select: selectValue },
69
+ metadata: {
70
+ value,
71
+ options: options.map((option) => ({ value: option.value, label: option.label })),
72
+ },
73
+ });
74
+
75
+ const openDialog = useCallback((event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
76
+ event?.stopPropagation?.();
77
+ event?.preventDefault?.();
78
+ if (disabled) return;
79
+ void dialog.prompt<string>({
80
+ closeOnClickOutside: true,
81
+ content: (context: PromptContext<string>) => (
82
+ <ChoiceDialog
83
+ {...context}
84
+ title={title ?? label}
85
+ selectedChoiceId={value}
86
+ choices={options.map((option) => ({
87
+ id: option.value,
88
+ label: option.label,
89
+ description: option.description,
90
+ disabled: option.disabled,
91
+ }))}
92
+ />
93
+ ),
94
+ }).then((next) => {
95
+ if (!next) return;
96
+ const match = options.find((option) => option.value === next);
97
+ if (match && !match.disabled) onChange(match.value);
98
+ }).catch(() => {});
99
+ }, [dialog, disabled, label, onChange, options, title, value]);
100
+
101
+ if (isDesktopWeb) {
102
+ return (
103
+ <Box flexDirection="row" alignItems="center" gap={1} id={idPrefix ? `${idPrefix}:select` : undefined}>
104
+ <Text fg={colors.textMuted}>{label}</Text>
105
+ <NativeSelect
106
+ variant="inline"
107
+ value={value}
108
+ options={options.map((option) => ({
109
+ value: option.value,
110
+ label: option.label,
111
+ disabled: option.disabled,
112
+ }))}
113
+ onChange={(next) => {
114
+ if (next !== value) onChange(next as T);
115
+ }}
116
+ />
117
+ </Box>
118
+ );
119
+ }
120
+
121
+ return (
122
+ <Box
123
+ id={idPrefix ? `${idPrefix}:select` : undefined}
124
+ height={1}
125
+ flexDirection="row"
126
+ onMouseDown={openDialog}
127
+ >
128
+ <Text fg={colors.textMuted} onMouseDown={openDialog}>{`${label} `}</Text>
129
+ <Text
130
+ fg={disabled ? colors.textMuted : emphasized ? colors.textBright : colors.text}
131
+ attributes={emphasized ? TextAttributes.BOLD : undefined}
132
+ onMouseDown={openDialog}
133
+ >
134
+ {current?.short ?? current?.label ?? ""}
135
+ </Text>
136
+ </Box>
137
+ );
138
+ }
@@ -70,7 +70,7 @@ export function PaneStatusBody({
70
70
  }: PaneStatusBodyProps) {
71
71
  if (error) {
72
72
  return (
73
- <Box paddingX={1} paddingY={1}>
73
+ <Box paddingX={1} paddingY={1} data-gloom-status="error">
74
74
  <EmptyState
75
75
  title={subject ? unavailableText(subject) : error}
76
76
  message={subject ? error : undefined}
@@ -89,7 +89,7 @@ export function PaneStatusBody({
89
89
  }
90
90
  if (empty) {
91
91
  return (
92
- <Box paddingX={1} paddingY={1}>
92
+ <Box paddingX={1} paddingY={1} data-gloom-status="empty">
93
93
  <EmptyState
94
94
  title={emptyTitle ?? t("Nothing to show yet.")}
95
95
  message={emptyMessage}
@@ -177,7 +177,7 @@ export function useMeasuredTableContentWidth(
177
177
  queueMicrotask(measureContentWidth);
178
178
  }, [measureContentWidth]);
179
179
 
180
- useEffect(scheduleContentWidthMeasure);
180
+ useEffect(scheduleContentWidthMeasure, [scheduleContentWidthMeasure]);
181
181
 
182
182
  return {
183
183
  contentWidth: Math.max(tableWidth, viewportWidth),
@@ -16,21 +16,25 @@ export function useDoubleClickActivation<T>({
16
16
  onActivate?: (value: T) => void;
17
17
  }) {
18
18
  const lastClickRef = useRef<LastClickState | null>(null);
19
+ const onSelectRef = useRef(onSelect);
20
+ const onActivateRef = useRef(onActivate);
21
+ onSelectRef.current = onSelect;
22
+ onActivateRef.current = onActivate;
19
23
 
20
24
  return useCallback((targetKey: string, value: T, event?: ClickEventLike) => {
21
25
  const lastClick = lastClickRef.current;
22
26
 
23
- onSelect?.(value);
27
+ onSelectRef.current?.(value);
24
28
 
25
29
  if ((typeof event?.detail === "number" && event.detail >= 2)
26
30
  || (lastClick && lastClick.targetKey === targetKey)) {
27
31
  lastClickRef.current = null;
28
- onActivate?.(value);
32
+ onActivateRef.current?.(value);
29
33
  return;
30
34
  }
31
35
 
32
36
  lastClickRef.current = {
33
37
  targetKey,
34
38
  };
35
- }, [onActivate, onSelect]);
39
+ }, []);
36
40
  }
@@ -14,7 +14,7 @@ import { assetDataProvider, newsProvider } from "../capabilities";
14
14
  import type { DataProvider } from "../types/data-provider";
15
15
  import { debugLog } from "../utils/debug-log";
16
16
  import { measurePerf, measurePerfAsync } from "../utils/perf-marks";
17
- import { setIbkrPortfolioPerformanceResourceStore } from "../plugins/ibkr/portfolio-performance";
17
+ import { setPluginResourceStore } from "../public/broker";
18
18
  import type { AppRuntimeServices, AppServicesFactoryOptions } from "./app-service-ports";
19
19
 
20
20
  const servicesLog = debugLog.createLogger("services");
@@ -41,7 +41,7 @@ export function createAppServices({
41
41
  });
42
42
  const dbPath = join(config.dataDir, ".gloomberb-cache.db");
43
43
  const persistence = measurePerf("startup.services.persistence", () => new AppPersistence(dbPath));
44
- setIbkrPortfolioPerformanceResourceStore(persistence.resources);
44
+ setPluginResourceStore(persistence.resources);
45
45
  const tickerRepository = measurePerf("startup.services.ticker-repository", () => new TickerRepository(persistence.tickers));
46
46
  const disposeCloudConnectionSources = registerGloomCloudConnectionSources(connectionHealth);
47
47
  const providerRouter = measurePerf("startup.services.asset-data-router", () => (
@@ -105,7 +105,7 @@ export function createAppServices({
105
105
  newsService.stop();
106
106
  pluginRegistry.destroy();
107
107
  disposeCloudConnectionSources();
108
- setIbkrPortfolioPerformanceResourceStore(null);
108
+ setPluginResourceStore(null);
109
109
  persistence.close();
110
110
  },
111
111
  };
@@ -1,4 +1,4 @@
1
- import type { ChartResolution, TimeRange } from "../../time-series/range";
1
+ import { CHART_RESOLUTIONS, TIME_RANGES, type ChartResolution, type TimeRange } from "../../time-series/range";
2
2
  import type { LayoutConfig, PaneBinding } from "../../types/config";
3
3
  import type {
4
4
  ChartPanelSpec,
@@ -16,8 +16,8 @@ import {
16
16
  validateChartSpec,
17
17
  } from "../../time-series/spec";
18
18
 
19
- const CHART_RANGES = new Set<TimeRange>(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
20
- const CHART_RESOLUTIONS = new Set<ChartResolution>(["auto", "1m", "5m", "15m", "30m", "45m", "1h", "1d", "1wk", "1mo"]);
19
+ const CHART_RANGES = new Set<TimeRange>(TIME_RANGES);
20
+ const CHART_RESOLUTION_SET = new Set<ChartResolution>(CHART_RESOLUTIONS);
21
21
  const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
22
22
  const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
23
23
  "volume",
@@ -94,7 +94,7 @@ function range(value: unknown, fallback: TimeRange): TimeRange {
94
94
  }
95
95
 
96
96
  function resolution(value: unknown, fallback: ChartResolution): ChartResolution {
97
- return CHART_RESOLUTIONS.has(value as ChartResolution) ? value as ChartResolution : fallback;
97
+ return CHART_RESOLUTION_SET.has(value as ChartResolution) ? value as ChartResolution : fallback;
98
98
  }
99
99
 
100
100
  function makeSpec(
@@ -10,8 +10,10 @@ import {
10
10
  cloneLayout,
11
11
  clonePaneSettings,
12
12
  createPaneInstanceId,
13
+ getPlacedPaneInstanceIds,
13
14
  normalizePaneLayout,
14
15
  normalizePaneId,
16
+ removeUnreachablePaneInstances,
15
17
  } from "../../types/config";
16
18
  import {
17
19
  migrateChartPaneSettings,
@@ -155,11 +157,14 @@ function sanitizePaneInstances(
155
157
  placementMemory: sanitizePlacementMemory(entry.placementMemory),
156
158
  };
157
159
  });
158
- return instances.length > 0 ? instances : cloneLayout(fallback).instances;
160
+ return instances;
159
161
  }
160
162
 
161
- function getDefaultFollowSourceInstanceId(instances: PaneInstanceConfig[]): string | null {
162
- return instances.find((instance) => instance.paneId === "portfolio-list")?.instanceId ?? null;
163
+ function getDefaultFollowSourceInstanceId(layout: LayoutConfig): string | null {
164
+ const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
165
+ return layout.instances.find((instance) => (
166
+ instance.paneId === "portfolio-list" && placedPaneIds.has(instance.instanceId)
167
+ ))?.instanceId ?? null;
163
168
  }
164
169
 
165
170
  function sanitizeFloatingEntries(value: unknown, validInstanceIds: Set<string>): LayoutConfig["floating"] {
@@ -219,7 +224,7 @@ export function sanitizeLayout(
219
224
  if (!Array.isArray((value as LayoutConfig & { instances?: unknown }).instances)) {
220
225
  const layout = cloneLayout(fallback);
221
226
  return normalizePaneLayout(layout, {
222
- defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
227
+ defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
223
228
  resolveOrphanSymbol: () => null,
224
229
  });
225
230
  }
@@ -241,8 +246,9 @@ export function sanitizeLayout(
241
246
  detached,
242
247
  };
243
248
 
244
- return normalizePaneLayout(layout, {
245
- defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
249
+ const normalized = normalizePaneLayout(layout, {
250
+ defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
246
251
  resolveOrphanSymbol: () => null,
247
252
  });
253
+ return removeUnreachablePaneInstances(normalized);
248
254
  }
@@ -49,6 +49,50 @@ async function writeConfigJson(dataDir: string, config: Record<string, unknown>)
49
49
  }
50
50
 
51
51
  describe("sanitizeLayout", () => {
52
+ test("preserves an intentionally blank layout", () => {
53
+ expect(sanitizeLayout({
54
+ dockRoot: null,
55
+ instances: [],
56
+ floating: [],
57
+ detached: [],
58
+ }, DEFAULT_LAYOUT)).toEqual({
59
+ dockRoot: null,
60
+ instances: [],
61
+ floating: [],
62
+ detached: [],
63
+ });
64
+ });
65
+
66
+ test("prunes abandoned panes while retaining a hidden follow source", () => {
67
+ const layout = sanitizeLayout({
68
+ dockRoot: { kind: "pane", instanceId: "ticker-research:visible" },
69
+ instances: [
70
+ {
71
+ instanceId: "portfolio-list:source",
72
+ paneId: "portfolio-list",
73
+ binding: { kind: "none" },
74
+ },
75
+ {
76
+ instanceId: "ticker-research:visible",
77
+ paneId: "ticker-research",
78
+ binding: { kind: "follow", sourceInstanceId: "portfolio-list:source" },
79
+ },
80
+ {
81
+ instanceId: "ticker-research:closed",
82
+ paneId: "ticker-research",
83
+ binding: { kind: "fixed", symbol: "NVDA" },
84
+ },
85
+ ],
86
+ floating: [],
87
+ detached: [],
88
+ }, DEFAULT_LAYOUT);
89
+
90
+ expect(layout.instances.map((instance) => instance.instanceId)).toEqual([
91
+ "portfolio-list:source",
92
+ "ticker-research:visible",
93
+ ]);
94
+ });
95
+
52
96
  test("ships a focused Home layout with portfolio, chat, and following research", () => {
53
97
  expect(DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId)).toEqual([
54
98
  "portfolio-list:main",
@@ -318,6 +362,59 @@ describe("sanitizeLayout", () => {
318
362
  });
319
363
 
320
364
  describe("loadConfig", () => {
365
+ test("migrates unreachable pane instances and their saved state", async () => {
366
+ const dataDir = await createTempConfigDir();
367
+ const hiddenPaneId = "ticker-research:closed";
368
+ const legacyLayout = {
369
+ ...DEFAULT_LAYOUT,
370
+ instances: [
371
+ ...DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId === "chat:main"
372
+ ? {
373
+ ...instance,
374
+ placementMemory: {
375
+ docked: {
376
+ anchorInstanceId: hiddenPaneId,
377
+ position: "right" as const,
378
+ },
379
+ },
380
+ }
381
+ : instance),
382
+ {
383
+ instanceId: hiddenPaneId,
384
+ paneId: "ticker-research",
385
+ binding: { kind: "fixed" as const, symbol: "NVDA" },
386
+ placementMemory: { floating: { x: 12, y: 4, width: 90, height: 30 } },
387
+ },
388
+ ],
389
+ };
390
+ await writeConfigJson(dataDir, createSavedConfig({
391
+ configVersion: 21,
392
+ layout: legacyLayout,
393
+ layouts: [{
394
+ name: "Default",
395
+ layout: legacyLayout,
396
+ paneState: {
397
+ "chat:main": { draft: "keep" },
398
+ [hiddenPaneId]: { activeTabId: "overview" },
399
+ },
400
+ focusedPaneId: hiddenPaneId,
401
+ }],
402
+ }));
403
+
404
+ const config = await loadConfig(dataDir);
405
+
406
+ expect(config.configVersion).toBe(CURRENT_CONFIG_VERSION);
407
+ expect(config.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
408
+ expect(config.layouts[0]?.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
409
+ expect(config.layouts[0]?.paneState).toEqual({ "chat:main": { draft: "keep" } });
410
+ expect(config.layouts[0]?.focusedPaneId).toBeNull();
411
+ expect(findPaneInstance(config.layout, "chat:main")?.placementMemory?.docked).toEqual({
412
+ anchorInstanceId: undefined,
413
+ path: undefined,
414
+ position: "right",
415
+ });
416
+ });
417
+
321
418
  test("folds saved graph plugin state into composer specs and removes only chart-owned state", async () => {
322
419
  const dataDir = await createTempConfigDir();
323
420
  const legacyLayout = {
@@ -4,6 +4,7 @@ import {
4
4
  CURRENT_CONFIG_VERSION,
5
5
  DEFAULT_COLUMNS,
6
6
  DEFAULT_PORTFOLIO_COLUMN_IDS,
7
+ getPlacedPaneInstanceIds,
7
8
  type LayoutConfig,
8
9
  } from "../../../types/config";
9
10
  import {
@@ -24,6 +25,7 @@ const CLOUD_MACRO_SPLIT_CONFIG_VERSION = 15;
24
25
  const PORTFOLIO_DEFAULT_COLUMNS_CONFIG_VERSION = 17;
25
26
  const BUILTIN_OWNERSHIP_AND_CHART_CONFIG_VERSION = 20;
26
27
  const ONBOARDING_BACKFILL_CONFIG_VERSION = 21;
28
+ const UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION = 22;
27
29
 
28
30
  const LEGACY_MAIN_PORTFOLIO_COLUMN_IDS = DEFAULT_COLUMNS.map((column) => column.id);
29
31
  const PRE_SPARKLINE_PORTFOLIO_COLUMN_IDS = [
@@ -85,6 +87,11 @@ const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [
85
87
  toVersion: ONBOARDING_BACKFILL_CONFIG_VERSION,
86
88
  migrate: migrateOnboardingComplete,
87
89
  },
90
+ {
91
+ name: "prune-unreachable-pane-instances",
92
+ toVersion: UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION,
93
+ migrate: migrateUnreachablePaneInstances,
94
+ },
88
95
  ];
89
96
 
90
97
  export function migrateSavedConfig(saved: Record<string, unknown>, dataDir: string): ConfigMigrationResult {
@@ -139,6 +146,40 @@ function migrateOnboardingComplete(saved: Record<string, unknown>): Record<strin
139
146
  };
140
147
  }
141
148
 
149
+ function migrateUnreachablePaneInstances(
150
+ saved: Record<string, unknown>,
151
+ dataDir: string,
152
+ ): Record<string, unknown> {
153
+ const defaults = createDefaultConfig(dataDir);
154
+ const layout = sanitizeLayout(saved.layout, defaults.layout);
155
+ const layouts = Array.isArray(saved.layouts)
156
+ ? saved.layouts.map((entry) => {
157
+ if (!isPlainRecord(entry) || typeof entry.name !== "string") return entry;
158
+ const entryLayout = sanitizeLayout(entry.layout, layout);
159
+ const instanceIds = new Set(entryLayout.instances.map((instance) => instance.instanceId));
160
+ const placedIds = new Set(getPlacedPaneInstanceIds(entryLayout));
161
+ const paneState = isPlainRecord(entry.paneState)
162
+ ? Object.fromEntries(Object.entries(entry.paneState).filter(([instanceId]) => instanceIds.has(instanceId)))
163
+ : entry.paneState;
164
+ const focusedPaneId = typeof entry.focusedPaneId === "string" && !placedIds.has(entry.focusedPaneId)
165
+ ? null
166
+ : entry.focusedPaneId;
167
+ return {
168
+ ...entry,
169
+ layout: entryLayout,
170
+ paneState,
171
+ focusedPaneId,
172
+ };
173
+ })
174
+ : saved.layouts;
175
+
176
+ return {
177
+ ...saved,
178
+ layout,
179
+ layouts,
180
+ };
181
+ }
182
+
142
183
  function migrateCloudDefault(saved: Record<string, unknown>): Record<string, unknown> {
143
184
  return {
144
185
  ...saved,
@@ -10,6 +10,7 @@ import {
10
10
  cloneLayout,
11
11
  createDefaultConfig,
12
12
  CURRENT_CONFIG_VERSION,
13
+ getPlacedPaneInstanceIds,
13
14
  } from "../../../types/config";
14
15
  import type { Portfolio, Watchlist } from "../../../types/ticker";
15
16
  import { isLanguagePreference } from "../../../i18n/languages";
@@ -305,15 +306,16 @@ function sanitizeSavedLayouts(
305
306
  )
306
307
  .map((entry) => {
307
308
  const layout = sanitizeLayout(entry.layout, fallbackLayout);
309
+ const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
308
310
  const paneState = sanitizeSavedPaneState((entry as { paneState?: unknown }).paneState, layout);
309
311
  return {
310
312
  id: typeof entry.id === "string" ? entry.id : undefined,
311
313
  name: entry.name,
312
314
  layout,
313
315
  paneState,
314
- focusedPaneId: typeof entry.focusedPaneId === "string" || entry.focusedPaneId === null
315
- ? entry.focusedPaneId
316
- : undefined,
316
+ focusedPaneId: typeof entry.focusedPaneId === "string"
317
+ ? placedPaneIds.has(entry.focusedPaneId) ? entry.focusedPaneId : null
318
+ : entry.focusedPaneId === null ? null : undefined,
317
319
  activePanel: entry.activePanel === "right" || entry.activePanel === "left"
318
320
  ? entry.activePanel
319
321
  : undefined,
package/src/i18n/es.ts CHANGED
@@ -572,6 +572,7 @@ export const es: Record<string, string> = {
572
572
  "Open the help window": "Abrir la ventana de ayuda",
573
573
  "Set up a portfolio": "Configura una cartera",
574
574
  "How would you like to get started?": "¿Cómo quieres empezar?",
575
+ "{shortcut} to get started.": "{shortcut} para empezar.",
575
576
  "Edits later from the command bar.": "Se edita luego desde la barra de comandos.",
576
577
  "Create Manual Portfolio": "Crear cartera manual",
577
578
  "Add tickers and positions by hand": "Agrega tickers y posiciones a mano",
@@ -760,7 +761,7 @@ export const es: Record<string, string> = {
760
761
  "Verify your email to send messages.": "Verifica tu correo para enviar mensajes.",
761
762
  "Verify your email to use Cloud tweets.": "Verifica tu correo para usar Cloud tweets.",
762
763
  "Resend Verification Email": "Reenviar correo de verificación",
763
- "Ctrl+P, then Resend Verification Email": "Ctrl+P y luego Reenviar correo de verificación",
764
+ "{shortcut}, then Resend Verification Email": "{shortcut} y luego Reenviar correo de verificación",
764
765
  "editing": "editando",
765
766
  "replying to": "respondiendo a",
766
767
  "Reply": "Responder",
@@ -998,7 +999,7 @@ export const es: Record<string, string> = {
998
999
  "No watchlist tickers": "Sin tickers en seguimiento",
999
1000
  "No tickers in {name}.": "No hay tickers en {name}.",
1000
1001
  "this collection": "esta colección",
1001
- "Press Ctrl+P to add one.": "Pulsa Ctrl+P para agregar uno.",
1002
+ "Press {shortcut} to add one.": "Pulsa {shortcut} para agregar uno.",
1002
1003
  "PRE-MKT": "PRE-MKT",
1003
1004
  "OPEN": "ABIERTO",
1004
1005
  "AFTER-HRS": "POST-MKT",
@@ -1092,6 +1093,9 @@ export const es: Record<string, string> = {
1092
1093
  "Approve from your phone, no typing": "Aprueba desde tu teléfono, sin escribir",
1093
1094
  "Scan with the Gloom app": "Escanea con la app Gloom",
1094
1095
  "Open the Gloom app on your phone and approve this workspace.": "Abre la app Gloom en tu teléfono y aprueba este espacio de trabajo.",
1096
+ "Approve the code from a phone that is already signed in.": "Aprueba el código desde un teléfono que ya tenga la sesión iniciada.",
1097
+ "Layouts, watchlists and portfolios sync to your account.": "Los diseños, las listas de seguimiento y las carteras se sincronizan con tu cuenta.",
1098
+ "Use email instead": "Usar el correo",
1095
1099
  "Scan with the Gloom app to sign in": "Escanea con la app Gloom para iniciar sesión",
1096
1100
  "Waiting for approval...": "Esperando aprobación...",
1097
1101
  "Approved as {email}": "Aprobado como {email}",
package/src/i18n/ja.ts CHANGED
@@ -573,6 +573,7 @@ export const ja: Record<string, string> = {
573
573
  "Open the help window": "ヘルプ ウィンドウを開く",
574
574
  "Set up a portfolio": "ポートフォリオを設定する",
575
575
  "How would you like to get started?": "どの方法で始めますか?",
576
+ "{shortcut} to get started.": "{shortcut} で開始します。",
576
577
  "Edits later from the command bar.": "後でコマンド バーから編集します。",
577
578
  "Create Manual Portfolio": "手動ポートフォリオを作成",
578
579
  "Add tickers and positions by hand": "銘柄とポジションを手動で追加",
@@ -775,7 +776,7 @@ export const ja: Record<string, string> = {
775
776
  "Verify your email to send messages.": "メッセージを送信するにはメールを確認してください。",
776
777
  "Verify your email to use Cloud tweets.": "Cloud tweets を使用するにはメールを確認してください。",
777
778
  "Resend Verification Email": "確認メールを再送信",
778
- "Ctrl+P, then Resend Verification Email": "Ctrl+P から「確認メールを再送信」",
779
+ "{shortcut}, then Resend Verification Email": "{shortcut} から「確認メールを再送信」",
779
780
  "editing": "編集",
780
781
  "replying to": "返信先",
781
782
  "Reply": "返信",
@@ -1013,7 +1014,7 @@ export const ja: Record<string, string> = {
1013
1014
  "No watchlist tickers": "ウォッチリストに銘柄がありません",
1014
1015
  "No tickers in {name}.": "{name} に銘柄がありません。",
1015
1016
  "this collection": "このコレクション",
1016
- "Press Ctrl+P to add one.": "Ctrl+P を押して追加します。",
1017
+ "Press {shortcut} to add one.": "{shortcut} を押して追加します。",
1017
1018
  "PRE-MKT": "PRE-MKT",
1018
1019
  "OPEN": "取引中",
1019
1020
  "AFTER-HRS": "時間外",
@@ -1079,6 +1080,9 @@ export const ja: Record<string, string> = {
1079
1080
  "Approve from your phone, no typing": "スマホから承認、入力不要",
1080
1081
  "Scan with the Gloom app": "Gloom アプリでスキャン",
1081
1082
  "Open the Gloom app on your phone and approve this workspace.": "スマホで Gloom アプリを開き、このワークスペースを承認してください。",
1083
+ "Approve the code from a phone that is already signed in.": "すでにログイン済みのスマートフォンでコードを承認してください。",
1084
+ "Layouts, watchlists and portfolios sync to your account.": "レイアウト、ウォッチリスト、ポートフォリオがアカウントに同期されます。",
1085
+ "Use email instead": "メールを使う",
1082
1086
  "Scan with the Gloom app to sign in": "Gloom アプリでスキャンしてログイン",
1083
1087
  "Waiting for approval...": "承認を待っています...",
1084
1088
  "Approved as {email}": "{email} として承認されました",