gloomberb 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/README.md +38 -15
  2. package/README.zh-CN.md +12 -12
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +54 -0
  6. package/src/api-client/index.test.ts +124 -0
  7. package/src/api-client/index.ts +139 -4
  8. package/src/api-client/paths.ts +89 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +163 -0
  13. package/src/app/pane-runtime/index.tsx +4 -1
  14. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  16. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  17. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  18. package/src/app/runtime/desktop-deeplink.ts +66 -2
  19. package/src/app/runtime/desktop-menu.ts +3 -0
  20. package/src/app/runtime/plugin-bindings.ts +30 -28
  21. package/src/app/runtime/update.ts +8 -6
  22. package/src/app.tsx +21 -7
  23. package/src/architecture/import-boundaries.test.ts +5 -1
  24. package/src/capabilities/factories.ts +4 -0
  25. package/src/cli/commands/automation.test.ts +9 -0
  26. package/src/cli/commands/automation.ts +22 -73
  27. package/src/cli/index.ts +10 -6
  28. package/src/cli/registry.ts +1 -1
  29. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  30. package/src/components/chart/composite/composite-chart.tsx +16 -12
  31. package/src/components/chart/composite/format.test.ts +34 -0
  32. package/src/components/chart/composite/format.ts +33 -10
  33. package/src/components/chart/composite/scene.ts +0 -21
  34. package/src/components/chart/composite/tools.ts +49 -0
  35. package/src/components/chart/core/axis-format.ts +13 -3
  36. package/src/components/chart/core/index.test.ts +7 -0
  37. package/src/components/chart/core/renderer.ts +3 -4
  38. package/src/components/command-bar/commands/direct/index.ts +4 -0
  39. package/src/components/command-bar/commands/registry.ts +8 -1
  40. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  41. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  42. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  43. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  44. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  45. package/src/components/command-bar/surface/index.test.tsx +6 -6
  46. package/src/components/command-bar/surface/index.tsx +4 -1
  47. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  48. package/src/components/command-bar/view-model.test.ts +2 -1
  49. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  50. package/src/components/command-bar/workflow/ops.ts +1 -1
  51. package/src/components/company-logo.test.ts +19 -0
  52. package/src/components/company-logo.tsx +60 -0
  53. package/src/components/feed-data-table/stack-view.tsx +7 -1
  54. package/src/components/index.ts +1 -0
  55. package/src/components/layout/detached-pane-shell.tsx +85 -15
  56. package/src/components/layout/header.tsx +5 -5
  57. package/src/components/layout/pane/title.ts +31 -3
  58. package/src/components/layout/shell/active-drag.ts +4 -11
  59. package/src/components/layout/shell/drag/index.ts +4 -20
  60. package/src/components/layout/shell/drag/runtime.ts +3 -4
  61. package/src/components/layout/shell/index.test.tsx +17 -3
  62. package/src/components/layout/shell/index.tsx +89 -31
  63. package/src/components/layout/shell/menu.test.ts +37 -1
  64. package/src/components/layout/shell/menu.ts +14 -0
  65. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  66. package/src/components/layout/shell/pane/actions.ts +0 -5
  67. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  68. package/src/components/layout/shell/shortcuts.ts +6 -3
  69. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  70. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  71. package/src/components/layout/status-bar.test.tsx +62 -61
  72. package/src/components/layout/status-bar.tsx +53 -75
  73. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  74. package/src/components/onboarding/account-step/index.tsx +1 -1
  75. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  76. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  77. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  78. package/src/components/onboarding/wizard-account.ts +4 -23
  79. package/src/components/table-view-shared.test.ts +11 -0
  80. package/src/components/table-view-shared.tsx +26 -0
  81. package/src/components/ui/data-table/index.tsx +19 -1
  82. package/src/components/ui/fields.tsx +5 -1
  83. package/src/core/state/app/layout-reducer.ts +36 -0
  84. package/src/core/state/app/layout.ts +10 -5
  85. package/src/core/state/app/state.ts +0 -13
  86. package/src/core/state/app/types.ts +6 -4
  87. package/src/data/config/chart-settings.ts +1 -1
  88. package/src/data/config/layout.ts +2 -0
  89. package/src/data/config/store/index.test.ts +27 -6
  90. package/src/data/config/store/normalize.ts +3 -5
  91. package/src/data/plugin-state-store.ts +11 -0
  92. package/src/data/sqlite/cache.test.ts +10 -0
  93. package/src/i18n/es.ts +13 -5
  94. package/src/i18n/ja.ts +13 -5
  95. package/src/i18n/ko.ts +13 -5
  96. package/src/i18n/zh-cn.ts +13 -5
  97. package/src/i18n/zh-tw.ts +13 -5
  98. package/src/layout-marketplace/api.test.ts +54 -0
  99. package/src/layout-marketplace/api.ts +65 -0
  100. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  101. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  102. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  103. package/src/layout-marketplace/gallery.test.tsx +84 -0
  104. package/src/layout-marketplace/gallery.tsx +323 -0
  105. package/src/layout-marketplace/geometry.test.ts +47 -0
  106. package/src/layout-marketplace/geometry.ts +80 -0
  107. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  108. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  109. package/src/layout-marketplace/model.test.ts +118 -0
  110. package/src/layout-marketplace/model.ts +171 -0
  111. package/src/layout-marketplace/name-dialog.tsx +60 -0
  112. package/src/layout-marketplace/pane-imagery.ts +85 -0
  113. package/src/layout-marketplace/pane.tsx +20 -0
  114. package/src/layout-marketplace/payload.test.ts +246 -0
  115. package/src/layout-marketplace/payload.ts +624 -0
  116. package/src/layout-marketplace/use-marketplace.ts +63 -0
  117. package/src/market-data/coordinator/index.test.ts +2 -2
  118. package/src/market-data/coordinator/index.ts +1 -1
  119. package/src/market-data/hooks.tsx +0 -41
  120. package/src/market-data/market/format.test.ts +7 -0
  121. package/src/market-data/market/freshness.ts +5 -2
  122. package/src/market-data/quotes/freshness.test.ts +16 -0
  123. package/src/news/aggregator.test.ts +92 -3
  124. package/src/news/aggregator.ts +98 -18
  125. package/src/news/hooks.test.tsx +2 -0
  126. package/src/news/hooks.ts +27 -1
  127. package/src/news/news-model.ts +5 -2
  128. package/src/news/types.ts +7 -0
  129. package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
  130. package/src/plugins/broker-sync/normalize.ts +42 -5
  131. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  132. package/src/plugins/builtin/ai/index.tsx +18 -2
  133. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  134. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  135. package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
  136. package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
  137. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  138. package/src/plugins/builtin/alerts/index.tsx +11 -6
  139. package/src/plugins/builtin/alerts/pane.tsx +51 -9
  140. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  141. package/src/plugins/builtin/alerts/types.ts +1 -0
  142. package/src/plugins/builtin/analytics/index.tsx +11 -1
  143. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  144. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  145. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  146. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  147. package/src/plugins/builtin/cds/client.test.ts +83 -0
  148. package/src/plugins/builtin/cds/client.ts +67 -0
  149. package/src/plugins/builtin/cds/index.tsx +41 -0
  150. package/src/plugins/builtin/cds/model.test.ts +232 -0
  151. package/src/plugins/builtin/cds/model.ts +401 -0
  152. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  153. package/src/plugins/builtin/cds/pane.tsx +358 -0
  154. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  156. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  157. package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
  158. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  159. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  160. package/src/plugins/builtin/chat/controller/index.ts +14 -9
  161. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  162. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  163. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  164. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  165. package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
  166. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  167. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  168. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  169. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  170. package/src/plugins/builtin/chat/controller.test.ts +142 -14
  171. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  172. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  173. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  174. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  175. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  176. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  177. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  178. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  179. package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
  180. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  181. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  182. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
  183. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  184. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  185. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  186. package/src/plugins/builtin/cloud/index.tsx +2 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +14 -24
  188. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  189. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  190. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  191. package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
  192. package/src/plugins/builtin/composite-plugins.ts +3 -1
  193. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  194. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  195. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  196. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  197. package/src/plugins/builtin/connections/index.ts +3 -0
  198. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  199. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  200. package/src/plugins/builtin/earnings/index.tsx +5 -3
  201. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  202. package/src/plugins/builtin/earnings/model.ts +17 -15
  203. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  204. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  205. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  206. package/src/plugins/builtin/help/index.tsx +2 -2
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  208. package/src/plugins/builtin/insider/index.tsx +27 -11
  209. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  210. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  211. package/src/plugins/builtin/news/index.tsx +11 -6
  212. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  213. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  214. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  215. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  216. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  217. package/src/plugins/builtin/news/wire/index.tsx +3 -3
  218. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  219. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  220. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  221. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  222. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  223. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  224. package/src/plugins/builtin/notes/files.ts +80 -0
  225. package/src/plugins/builtin/notes/index.tsx +3 -0
  226. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  227. package/src/plugins/builtin/notes/sync.ts +98 -0
  228. package/src/plugins/builtin/options/index.tsx +1 -0
  229. package/src/plugins/builtin/options/view.test.tsx +25 -3
  230. package/src/plugins/builtin/options/view.tsx +1 -0
  231. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
  232. package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
  233. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  234. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  235. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  236. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  237. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  238. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  239. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  240. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  241. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  242. package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
  243. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  244. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  245. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
  246. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
  247. package/src/plugins/builtin/research/index.test.ts +14 -0
  248. package/src/plugins/builtin/research/index.tsx +29 -0
  249. package/src/plugins/builtin/sec/index.tsx +24 -9
  250. package/src/plugins/builtin/sectors/index.tsx +14 -0
  251. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  252. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  253. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  254. package/src/plugins/builtin/shared/public-share.ts +23 -0
  255. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  256. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  257. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  258. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  259. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  260. package/src/plugins/builtin/substack/api/types.ts +2 -0
  261. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  262. package/src/plugins/builtin/substack/pane.tsx +55 -12
  263. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  264. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  265. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  266. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  267. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  268. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  269. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  270. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  271. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  273. package/src/plugins/catalog-browser.test.ts +78 -0
  274. package/src/plugins/catalog-browser.ts +136 -0
  275. package/src/plugins/gridlock-notification.ts +2 -1
  276. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  277. package/src/plugins/ibkr/flex/index.ts +1 -1
  278. package/src/plugins/pane-manager/gridlock.ts +87 -3
  279. package/src/plugins/pane-manager.test.ts +30 -0
  280. package/src/plugins/pane-manager.ts +6 -1
  281. package/src/plugins/prediction-markets/chart.tsx +2 -2
  282. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  283. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  284. package/src/plugins/prediction-markets/controller/index.ts +9 -2
  285. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  286. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  287. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  288. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  289. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  290. package/src/plugins/prediction-markets/index.tsx +8 -4
  291. package/src/plugins/prediction-markets/navigation.ts +0 -1
  292. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  293. package/src/plugins/prediction-markets/pane.tsx +12 -0
  294. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  295. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  296. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  297. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  298. package/src/plugins/registry/index.ts +2 -0
  299. package/src/plugins/ticker-navigation.test.ts +24 -0
  300. package/src/plugins/ticker-navigation.ts +40 -0
  301. package/src/remote/schema.ts +1 -1
  302. package/src/renderers/browser/app-services.ts +61 -0
  303. package/src/renderers/browser/cloud-transport.test.ts +68 -0
  304. package/src/renderers/browser/cloud-transport.ts +48 -0
  305. package/src/renderers/browser/config-host.ts +58 -0
  306. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  307. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  308. package/src/renderers/browser/error-boundary.tsx +23 -0
  309. package/src/renderers/browser/main.tsx +64 -0
  310. package/src/renderers/browser/persistence.ts +74 -0
  311. package/src/renderers/browser/storage.test.ts +66 -0
  312. package/src/renderers/browser/storage.ts +50 -0
  313. package/src/renderers/browser/ticker-repository.ts +59 -0
  314. package/src/renderers/browser/ui-host.tsx +40 -0
  315. package/src/renderers/cloudflare/worker.test.ts +136 -0
  316. package/src/renderers/cloudflare/worker.ts +116 -0
  317. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  318. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  319. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  320. package/src/renderers/electrobun/view/app-services.ts +14 -3
  321. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  322. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  323. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  324. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  325. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  326. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  327. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  328. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  329. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  330. package/src/renderers/electrobun/view/host/native.ts +0 -5
  331. package/src/renderers/electrobun/view/main.tsx +2 -0
  332. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  333. package/src/renderers/electrobun/view/styles.css +107 -7
  334. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  335. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  336. package/src/renderers/electrobun/view/ui-host.tsx +6 -137
  337. package/src/renderers/opentui/start.tsx +2 -0
  338. package/src/renderers/share/layout-view.test.tsx +58 -0
  339. package/src/renderers/share/layout-view.tsx +133 -0
  340. package/src/renderers/share/main.tsx +102 -0
  341. package/src/renderers/share/styles.css +54 -0
  342. package/src/renderers/share/view.test.tsx +100 -0
  343. package/src/renderers/share/view.tsx +150 -0
  344. package/src/shares/api.test.ts +105 -0
  345. package/src/shares/api.ts +127 -0
  346. package/src/shares/chart-snapshot.test.ts +34 -0
  347. package/src/shares/chart-snapshot.ts +28 -0
  348. package/src/shares/location.test.ts +19 -0
  349. package/src/shares/location.ts +11 -0
  350. package/src/shares/pane.test.ts +110 -0
  351. package/src/shares/pane.ts +54 -0
  352. package/src/shares/payload.ts +177 -0
  353. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  354. package/src/sources/gloomberb-cloud/index.ts +86 -14
  355. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  356. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  357. package/src/sources/provider-router/history.test.ts +2 -2
  358. package/src/sources/provider-router/news.test.ts +34 -0
  359. package/src/sources/provider-router/news.ts +15 -1
  360. package/src/sources/sec-edgar.test.ts +59 -0
  361. package/src/sources/sec-edgar.ts +63 -23
  362. package/src/sources/yahoo-finance/mappers.ts +3 -0
  363. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  364. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  365. package/src/sources/yahoo-finance/symbols.ts +13 -8
  366. package/src/sources/yahoo-finance.test.ts +1 -1
  367. package/src/sources/yahoo-finance.ts +6 -2
  368. package/src/state/app/bootstrap.ts +6 -7
  369. package/src/state/app/context/index.test.ts +41 -1
  370. package/src/state/app/context/index.test.tsx +34 -16
  371. package/src/state/selectors-ui.ts +0 -8
  372. package/src/sync/baseline.ts +32 -0
  373. package/src/sync/controller.test.ts +124 -1
  374. package/src/sync/controller.ts +54 -12
  375. package/src/sync/core-contributors.test.ts +44 -0
  376. package/src/sync/core-contributors.ts +59 -10
  377. package/src/sync/react.ts +22 -2
  378. package/src/sync/types.ts +12 -0
  379. package/src/theme/font-scale.ts +0 -11
  380. package/src/tickers/search/index.test.ts +27 -0
  381. package/src/tickers/search/upsert.ts +10 -0
  382. package/src/types/capability-route-source.ts +2 -1
  383. package/src/types/config.ts +35 -5
  384. package/src/types/desktop-menu.ts +1 -0
  385. package/src/types/plugin.ts +36 -0
  386. package/src/ui/context-menu.tsx +1 -1
  387. package/src/ui/host.tsx +4 -0
  388. package/src/ui/toast.tsx +2 -0
  389. package/src/utils/back-navigation.ts +0 -4
  390. package/src/utils/price-history.ts +11 -10
  391. package/src/utils/sec.test.ts +6 -0
  392. package/src/utils/sec.ts +1 -1
@@ -5,7 +5,7 @@ import {
5
5
  useRef,
6
6
  useState,
7
7
  } from "react";
8
- import { apiClient } from "../../api-client";
8
+ import { apiClient, type CloudPricing } from "../../api-client";
9
9
  import type { AppBrokerImportRuntime } from "../../app/runtime/broker-import";
10
10
  import type { SyncBrokerInstanceResult } from "../../brokers/sync-broker-instance";
11
11
  import { saveConfigImmediately } from "../../state/config-save-scheduler";
@@ -28,6 +28,7 @@ import { t, tf } from "../../i18n";
28
28
  import { useAppLanguage } from "../../i18n/react";
29
29
  import type { PluginRegistry } from "../../plugins/registry";
30
30
  import { chatController } from "../../plugins/builtin/chat/controller";
31
+ import { formatCloudMonthlyPrice } from "../../plugins/builtin/account-management/model";
31
32
  import { useCloudUpgradeAction } from "../../plugins/builtin/shared/cloud-upgrade";
32
33
  import { usePlanAccess } from "../../plugins/builtin/shared/plan-access";
33
34
  import type { ListViewItem } from "../ui";
@@ -42,7 +43,7 @@ import {
42
43
  OnboardingTitle,
43
44
  type OnboardingSectionId,
44
45
  } from "./onboarding-frame";
45
- import { ACCOUNT_CHOICE_IDS } from "./account-step/model";
46
+ import { ACCOUNT_CHOICE_IDS } from "../../plugins/builtin/cloud/auth-model";
46
47
  import { useOnboardingAccount } from "./wizard-account";
47
48
  import { useOnboardingBrokerSync } from "./wizard-broker-sync";
48
49
  import {
@@ -71,6 +72,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
71
72
  const stage = progress.stage;
72
73
  const [persistenceError, setPersistenceError] = useState<string | null>(null);
73
74
  const [isFinishing, setIsFinishing] = useState(false);
75
+ const [pricing, setPricing] = useState<CloudPricing | null>(null);
74
76
 
75
77
  const [portfolioSub, setPortfolioSub] = useState<PortfolioSub>("choose");
76
78
  const [portfolioOptionIdx, setPortfolioOptionIdx] = useState(0);
@@ -254,6 +256,11 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
254
256
  },
255
257
  ), [pluginRegistry.events, saveProgressInBackground, stateRef]);
256
258
 
259
+ useEffect(() => {
260
+ if (stage !== "upgrade" || pricing) return;
261
+ void apiClient.getCloudPricing().then(setPricing).catch(() => {});
262
+ }, [pricing, stage]);
263
+
257
264
  const appActive = useAppActive();
258
265
  const planAccess = usePlanAccess();
259
266
  const openUpgrade = useCloudUpgradeAction();
@@ -859,6 +866,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
859
866
 
860
867
  if (stage === "upgrade") {
861
868
  const primaryLabel = planAccess.hasProAccess ? t("Continue with Pro") : t("Start 7-day free trial");
869
+ const monthlyPrice = formatCloudMonthlyPrice(pricing);
862
870
  return (
863
871
  <OnboardingModal width={66} height={17}>
864
872
  <OnboardingHeader
@@ -871,10 +879,10 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
871
879
  />
872
880
  <OnboardingTitle
873
881
  step={desktop ? undefined : t("GLOOM CLOUD PRO")}
874
- title={planAccess.hasProAccess ? t("Pro is active") : t("$29/mo for real-time market data")}
875
- titlePrefix={!planAccess.hasProAccess ? (
882
+ title={planAccess.hasProAccess ? t("Pro is active") : monthlyPrice.price}
883
+ titlePrefix={!planAccess.hasProAccess && monthlyPrice.anchor ? (
876
884
  <Text fg={colors.textMuted} attributes={TextAttributes.STRIKETHROUGH}>
877
- {t("$49/mo")}
885
+ {monthlyPrice.anchor}
878
886
  </Text>
879
887
  ) : undefined}
880
888
  description={planAccess.hasProAccess
@@ -1,19 +1,17 @@
1
1
  import { useCallback, useRef, useState, type Dispatch, type SetStateAction } from "react";
2
2
  import { apiClient } from "../../api-client";
3
- import { chatController } from "../../plugins/builtin/chat/controller";
4
3
  import { debugLog } from "../../utils/debug-log";
5
4
  import {
6
5
  advanceAccountField,
7
6
  classifyAccountError,
8
- deriveUsernameFromEmail,
9
- isUsernameConflictError,
7
+ performEmailAuth,
10
8
  validateAccountEmail,
11
9
  validateAccountPassword,
12
10
  type AccountMode,
13
11
  type AccountOutcome,
14
12
  type AccountSub,
15
13
  type AccountSubmitError,
16
- } from "./account-step/model";
14
+ } from "../../plugins/builtin/cloud/auth-model";
17
15
 
18
16
  const onboardingLog = debugLog.createLogger("onboarding");
19
17
 
@@ -175,24 +173,7 @@ export function useOnboardingAccount({
175
173
 
176
174
  void (async () => {
177
175
  try {
178
- if (mode === "signup") {
179
- try {
180
- const username = deriveUsernameFromEmail(email);
181
- await apiClient.signUp(email, username, username, password);
182
- } catch (error) {
183
- if (!isUsernameConflictError(error)) throw error;
184
- const retryUsername = deriveUsernameFromEmail(email, 1);
185
- await apiClient.signUp(email, retryUsername, retryUsername, password);
186
- }
187
- await apiClient.sendVerification().catch(() => {});
188
- } else {
189
- await apiClient.signIn(email, password);
190
- }
191
-
192
- // Hands the freshly captured session cookie to the chat controller so it
193
- // reaches plugin persistence instead of living only in this process.
194
- chatController.clearSession();
195
- await chatController.refreshSession().catch(() => {});
176
+ await performEmailAuth(mode, email, password);
196
177
 
197
178
  if (attemptRef.current !== attemptId) return;
198
179
  onboardingLog.info("Onboarding account step completed", { mode });
@@ -235,7 +216,7 @@ export function useOnboardingAccount({
235
216
  }, [accountEmail, accountFieldIdx, accountPassword, accountSub, setEditingField, submitAccount]);
236
217
 
237
218
  const syncExistingAccountSession = useCallback(() => {
238
- if (accountSub !== "choose" || !apiClient.getSessionToken()) return;
219
+ if (accountSub !== "choose" || !apiClient.isSignedIn()) return;
239
220
  const user = apiClient.getCurrentUser();
240
221
  setAccountOutcome((current) => current ?? {
241
222
  mode: "login",
@@ -0,0 +1,11 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { isTableScrollNearEnd } from "./table-view-shared";
3
+
4
+ describe("isTableScrollNearEnd", () => {
5
+ test("is false until the remaining scroll is within the threshold", () => {
6
+ const scrollBox = { scrollTop: 0, scrollHeight: 40, viewport: { height: 20 } };
7
+ expect(isTableScrollNearEnd(scrollBox, 8)).toBe(false);
8
+ scrollBox.scrollTop = 12;
9
+ expect(isTableScrollNearEnd(scrollBox, 8)).toBe(true);
10
+ });
11
+ });
@@ -140,6 +140,32 @@ export function useTableViewState({
140
140
  };
141
141
  }
142
142
 
143
+ export function isTableScrollNearEnd(
144
+ scrollBox: {
145
+ scrollTop: number;
146
+ scrollHeight: number;
147
+ viewport?: { height: number } | null;
148
+ } | null,
149
+ threshold = 8,
150
+ ): boolean {
151
+ if (!scrollBox?.viewport) return false;
152
+ const viewportHeight = Math.max(1, scrollBox.viewport.height);
153
+ return scrollBox.scrollTop + viewportHeight >= scrollBox.scrollHeight - threshold;
154
+ }
155
+
156
+ export function useTableLoadMore(
157
+ scrollRef: RefObject<ScrollBoxRenderable | null>,
158
+ canLoadMore: boolean,
159
+ loadMore: () => void,
160
+ threshold = 8,
161
+ ) {
162
+ return useCallback(() => {
163
+ if (!canLoadMore) return;
164
+ if (!isTableScrollNearEnd(scrollRef.current, threshold)) return;
165
+ loadMore();
166
+ }, [canLoadMore, loadMore, scrollRef, threshold]);
167
+ }
168
+
143
169
  export function useTableBodyScrollActivity({
144
170
  onBodyScrollActivity,
145
171
  syncHeaderScroll,
@@ -6,7 +6,7 @@ import type {
6
6
  DataTableProps,
7
7
  } from "./types";
8
8
  import { useRemoteUiNode } from "../../../remote/semantic-tree";
9
- import { resolveRemoteItemIndex } from "../../../remote/semantic-helpers";
9
+ import { remoteNumberValue, resolveRemoteItemIndex } from "../../../remote/semantic-helpers";
10
10
 
11
11
  export type {
12
12
  DataTableCell,
@@ -46,6 +46,24 @@ export function DataTable<T, C extends DataTableColumn = DataTableColumn>(
46
46
  props.onHeaderClick(columnId);
47
47
  }
48
48
  },
49
+ scrollTo: (input) => {
50
+ const box = props.scrollRef.current;
51
+ if (!box) return;
52
+ box.scrollTo(Math.max(0, Math.round(remoteNumberValue(input, ["top", "index"]))));
53
+ props.onBodyScrollActivity();
54
+ },
55
+ scrollBy: (input) => {
56
+ const box = props.scrollRef.current;
57
+ if (!box) return;
58
+ const direction = input && typeof input === "object"
59
+ ? (input as { direction?: unknown }).direction
60
+ : undefined;
61
+ const delta = direction === "up"
62
+ ? remoteNumberValue(input, ["delta"], -1)
63
+ : remoteNumberValue(input, ["delta"], 1);
64
+ box.scrollTo(Math.max(0, Math.round((box.scrollTop ?? 0) + delta)));
65
+ props.onBodyScrollActivity();
66
+ },
49
67
  },
50
68
  metadata: {
51
69
  sortColumnId: props.sortColumnId,
@@ -16,7 +16,9 @@ export interface TextFieldProps {
16
16
  onSubmit?: (value: string) => void;
17
17
  onBlur?: (value: string) => void;
18
18
  hint?: string;
19
- type?: "text" | "password" | "date";
19
+ type?: "text" | "password" | "date" | "email";
20
+ /** DOM autofill hint, e.g. "email" or "current-password"; terminal hosts ignore it. */
21
+ autoComplete?: string;
20
22
  variant?: "default" | "plain";
21
23
  backgroundColor?: string;
22
24
  textColor?: string;
@@ -49,6 +51,7 @@ export function TextField({
49
51
  onBlur,
50
52
  hint,
51
53
  type = "text",
54
+ autoComplete,
52
55
  variant = "default",
53
56
  backgroundColor = colors.bg,
54
57
  textColor = colors.text,
@@ -87,6 +90,7 @@ export function TextField({
87
90
  onBlur={onBlur}
88
91
  hint={hint}
89
92
  type={type}
93
+ autoComplete={autoComplete}
90
94
  variant={variant}
91
95
  backgroundColor={backgroundColor}
92
96
  textColor={textColor}
@@ -14,6 +14,15 @@ import type { AppAction, AppState } from "./types";
14
14
 
15
15
  const MAX_LAYOUT_HISTORY = 50;
16
16
 
17
+ function availableLayoutName(name: string, layouts: SavedLayout[]): string {
18
+ const base = name.trim() || "Community Layout";
19
+ const existing = new Set(layouts.map((layout) => layout.name.toLowerCase()));
20
+ if (!existing.has(base.toLowerCase())) return base;
21
+ let suffix = 2;
22
+ while (existing.has(`${base} (${suffix})`.toLowerCase())) suffix += 1;
23
+ return `${base} (${suffix})`;
24
+ }
25
+
17
26
  export function reduceLayoutAction(state: AppState, action: AppAction): AppState | undefined {
18
27
  switch (action.type) {
19
28
  case "PUSH_LAYOUT_HISTORY": {
@@ -153,6 +162,33 @@ export function reduceLayoutAction(state: AppState, action: AppAction): AppState
153
162
  });
154
163
  }
155
164
 
165
+ case "INSTALL_LAYOUT_COPY": {
166
+ const currentConfig = syncConfigActiveLayoutState(
167
+ state.config,
168
+ state.paneState,
169
+ state.focusedPaneId,
170
+ state.activePanel,
171
+ );
172
+ const installed: SavedLayout = {
173
+ name: availableLayoutName(action.name, currentConfig.layouts),
174
+ layout: cloneLayout(action.layout),
175
+ paneState: clonePaneStateMap(action.paneState),
176
+ };
177
+ const layouts = [...currentConfig.layouts, installed];
178
+ return withFocusedPane({
179
+ ...state,
180
+ layoutHistory: setHistoryForIndex(state.layoutHistory, layouts.length - 1, { past: [], future: [] }),
181
+ }, {
182
+ ...currentConfig,
183
+ layout: cloneLayout(installed.layout),
184
+ layouts,
185
+ activeLayoutIndex: layouts.length - 1,
186
+ }, {
187
+ paneState: clonePaneStateMap(installed.paneState ?? {}),
188
+ focusedPaneId: null,
189
+ });
190
+ }
191
+
156
192
  case "DELETE_LAYOUT": {
157
193
  if (state.config.layouts.length <= 1) return state;
158
194
  const currentConfig = syncConfigActiveLayoutState(
@@ -118,15 +118,17 @@ function getPaneState(state: Pick<AppState, "paneState">, paneId: string): PaneR
118
118
  return state.paneState[paneId] ?? {};
119
119
  }
120
120
 
121
+ /**
122
+ * Any pane that publishes `cursorSymbol` in its pane state is a ticker source; every other pane
123
+ * resolves through its binding, so follow chains keep working across source types.
124
+ */
121
125
  export function resolveTickerForPane(state: AppState, paneId: string, seen = new Set<string>()): string | null {
122
126
  if (seen.has(paneId)) return null;
123
127
  seen.add(paneId);
124
128
  const instance = findPaneInstance(state.config.layout, paneId);
125
129
  if (!instance) return null;
126
- if (instance.paneId === "portfolio-list") {
127
- const paneState = getPaneState(state, paneId);
128
- return typeof paneState.cursorSymbol === "string" ? paneState.cursorSymbol : null;
129
- }
130
+ const cursorSymbol = getPaneState(state, paneId).cursorSymbol;
131
+ if (typeof cursorSymbol === "string" && cursorSymbol.trim()) return cursorSymbol;
130
132
  return resolveTickerFromBinding(state, instance.binding, seen);
131
133
  }
132
134
 
@@ -388,7 +390,10 @@ export function withFocusedPane(
388
390
  activePanel?: "left" | "right";
389
391
  } = {},
390
392
  ): AppState {
391
- const normalizedLayout = normalizePaneLayout(config.layout);
393
+ const normalizedLayout = normalizePaneLayout(config.layout, {
394
+ // Keep a follower alive on its last symbol when its source pane is gone.
395
+ resolveOrphanSymbol: (instanceId) => resolveTickerForPane(state, instanceId),
396
+ });
392
397
  const nextConfig = normalizedLayout === config.layout
393
398
  ? config
394
399
  : {
@@ -173,17 +173,6 @@ export function appReducer(state: AppState, action: AppAction): AppState {
173
173
  case "TOGGLE_STATUS_BAR":
174
174
  return { ...state, statusBarVisible: !state.statusBarVisible };
175
175
 
176
- case "SHOW_GRIDLOCK_TIP":
177
- return {
178
- ...state,
179
- gridlockTipVisible: true,
180
- gridlockTipSequence: state.gridlockTipSequence + 1,
181
- };
182
-
183
- case "DISMISS_GRIDLOCK_TIP":
184
- if (!state.gridlockTipVisible) return state;
185
- return { ...state, gridlockTipVisible: false };
186
-
187
176
  case "SET_THEME":
188
177
  if (state.config.theme === action.theme && state.themePreview == null) return state;
189
178
  return { ...state, themePreview: null, config: { ...state.config, theme: action.theme } };
@@ -360,8 +349,6 @@ export function createInitialState(config: AppConfig, sessionSnapshot: AppSessio
360
349
  refreshing: new Set(),
361
350
  initialized: false,
362
351
  statusBarVisible: sessionSnapshot?.statusBarVisible !== false,
363
- gridlockTipVisible: false,
364
- gridlockTipSequence: 0,
365
352
  inputCaptured: false,
366
353
  updateAvailable: null,
367
354
  updateProgress: null,
@@ -58,8 +58,6 @@ export interface AppState {
58
58
  refreshing: Set<string>;
59
59
  initialized: boolean;
60
60
  statusBarVisible: boolean;
61
- gridlockTipVisible: boolean;
62
- gridlockTipSequence: number;
63
61
  inputCaptured: boolean;
64
62
  updateAvailable: ReleaseInfo | null;
65
63
  updateProgress: UpdateProgress | null;
@@ -95,8 +93,6 @@ export type AppAction =
95
93
  | { type: "SET_BROKER_ACCOUNTS"; instanceId: string; accounts: BrokerAccount[] }
96
94
  | { type: "SET_INITIALIZED" }
97
95
  | { type: "TOGGLE_STATUS_BAR" }
98
- | { type: "SHOW_GRIDLOCK_TIP" }
99
- | { type: "DISMISS_GRIDLOCK_TIP" }
100
96
  | { type: "SET_THEME"; theme: string }
101
97
  | { type: "PREVIEW_THEME"; theme: string | null }
102
98
  | { type: "SET_UPDATE_AVAILABLE"; release: ReleaseInfo | null }
@@ -114,6 +110,12 @@ export type AppAction =
114
110
  | { type: "SWITCH_LAYOUT"; index: number }
115
111
  | { type: "REORDER_LAYOUT"; fromIndex: number; toIndex: number }
116
112
  | { type: "NEW_LAYOUT"; name: string }
113
+ | {
114
+ type: "INSTALL_LAYOUT_COPY";
115
+ name: string;
116
+ layout: LayoutConfig;
117
+ paneState: Record<string, PaneRuntimeState>;
118
+ }
117
119
  | { type: "DELETE_LAYOUT"; index: number }
118
120
  | { type: "RENAME_LAYOUT"; index: number; name: string }
119
121
  | { type: "DUPLICATE_LAYOUT"; index: number }
@@ -372,7 +372,7 @@ export function migrateChartPaneSettings(
372
372
  settings: Record<string, unknown> | undefined,
373
373
  context: LegacyChartMigrationContext = {},
374
374
  ): Record<string, unknown> | undefined {
375
- if (context.migrateLegacy === false) return settings;
375
+ if (!context.migrateLegacy) return settings;
376
376
  if (paneId === "chart-composer") {
377
377
  const retained = stripKeys(settings, [
378
378
  "chartAxisMode",
@@ -220,6 +220,7 @@ export function sanitizeLayout(
220
220
  const layout = cloneLayout(fallback);
221
221
  return normalizePaneLayout(layout, {
222
222
  defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
223
+ resolveOrphanSymbol: () => null,
223
224
  });
224
225
  }
225
226
 
@@ -242,5 +243,6 @@ export function sanitizeLayout(
242
243
 
243
244
  return normalizePaneLayout(layout, {
244
245
  defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
246
+ resolveOrphanSymbol: () => null,
245
247
  });
246
248
  }
@@ -118,7 +118,7 @@ describe("sanitizeLayout", () => {
118
118
  expect(getDockedPaneIds(layout)).toEqual(["portfolio-list:main", "ticker-detail:main"]);
119
119
  });
120
120
 
121
- test("removes follow panes whose source pane is missing", () => {
121
+ test("keeps a ticker research pane safely unlinked when its source is missing", () => {
122
122
  const layout = sanitizeLayout({
123
123
  dockRoot: { kind: "pane", instanceId: "ticker-detail:main" },
124
124
  instances: [
@@ -132,9 +132,8 @@ describe("sanitizeLayout", () => {
132
132
  detached: [],
133
133
  }, DEFAULT_LAYOUT);
134
134
 
135
- expect(findPaneInstance(layout, "ticker-detail:main")).toBeUndefined();
136
- expect(getDockedPaneIds(layout)).toHaveLength(0);
137
- expect(layout.dockRoot).toBeNull();
135
+ expect(findPaneInstance(layout, "ticker-detail:main")?.binding).toEqual({ kind: "none" });
136
+ expect(getDockedPaneIds(layout)).toEqual(["ticker-detail:main"]);
138
137
  });
139
138
 
140
139
  test("clamps floating placement memory and drops invalid docked placement hints", () => {
@@ -211,7 +210,7 @@ describe("sanitizeLayout", () => {
211
210
  }],
212
211
  floating: [],
213
212
  detached: [],
214
- }, DEFAULT_LAYOUT);
213
+ }, DEFAULT_LAYOUT, { migrateLegacy: true });
215
214
 
216
215
  const pane = findPaneInstance(layout, "comparison-chart:main");
217
216
  expect(pane?.paneId).toBe("chart-composer");
@@ -252,7 +251,7 @@ describe("sanitizeLayout", () => {
252
251
  }],
253
252
  floating: [],
254
253
  detached: [],
255
- }, DEFAULT_LAYOUT);
254
+ }, DEFAULT_LAYOUT, { migrateLegacy: true });
256
255
 
257
256
  const settings = findPaneInstance(layout, "ticker-detail:aapl")?.settings;
258
257
  expect(settings).toEqual({
@@ -277,6 +276,28 @@ describe("sanitizeLayout", () => {
277
276
  expect(settings).not.toHaveProperty("chartResolution");
278
277
  });
279
278
 
279
+ test("does not convert legacy chart settings during ordinary sanitization", () => {
280
+ const layout = sanitizeLayout({
281
+ dockRoot: { kind: "pane", instanceId: "ticker-detail:aapl" },
282
+ instances: [{
283
+ instanceId: "ticker-detail:aapl",
284
+ paneId: "ticker-research",
285
+ binding: { kind: "fixed", symbol: "AAPL" },
286
+ settings: {
287
+ chartRangePreset: "1Y",
288
+ chartResolution: "1wk",
289
+ },
290
+ }],
291
+ floating: [],
292
+ detached: [],
293
+ }, DEFAULT_LAYOUT);
294
+
295
+ expect(findPaneInstance(layout, "ticker-detail:aapl")?.settings).toEqual({
296
+ chartRangePreset: "1Y",
297
+ chartResolution: "1wk",
298
+ });
299
+ });
300
+
280
301
  test("falls back to the default layout when given an obsolete column layout", () => {
281
302
  const layout = sanitizeLayout({
282
303
  columns: [{ width: "100%" }],
@@ -17,13 +17,11 @@ import { clampFontSize } from "../../../theme/font-scale";
17
17
  import { isLayoutConfig, sanitizeLayout } from "../layout";
18
18
  import { migrateSavedConfig } from "./migrations";
19
19
 
20
- const CURRENT_CHART_SANITIZATION = { migrateLegacy: false } as const;
21
-
22
20
  export function normalizeLoadedConfig(saved: Record<string, unknown>, dataDir: string): { config: AppConfig; needsSave: boolean } {
23
21
  const defaults = createDefaultConfig(dataDir);
24
22
  const migration = migrateSavedConfig(saved, dataDir);
25
23
  const candidate = migration.config;
26
- const directLayout = sanitizeLayout(candidate.layout, defaults.layout, CURRENT_CHART_SANITIZATION);
24
+ const directLayout = sanitizeLayout(candidate.layout, defaults.layout);
27
25
  const layouts = sanitizeSavedLayouts(candidate.layouts, directLayout);
28
26
  const activeLayoutIndex = sanitizeActiveLayoutIndex(candidate.activeLayoutIndex, layouts.length);
29
27
  const layout = cloneLayout(layouts[activeLayoutIndex]?.layout ?? directLayout);
@@ -88,7 +86,7 @@ export function normalizeLoadedConfig(saved: Record<string, unknown>, dataDir: s
88
86
 
89
87
  export function normalizeConfigForSave(config: AppConfig): AppConfig {
90
88
  const defaults = createDefaultConfig(config.dataDir);
91
- const directLayout = sanitizeLayout(config.layout, defaults.layout, CURRENT_CHART_SANITIZATION);
89
+ const directLayout = sanitizeLayout(config.layout, defaults.layout);
92
90
  const sanitizedLayouts = sanitizeSavedLayouts(config.layouts, directLayout);
93
91
  const activeLayoutIndex = sanitizeActiveLayoutIndex(config.activeLayoutIndex, sanitizedLayouts.length || 1);
94
92
  const layout = cloneLayout(sanitizedLayouts[activeLayoutIndex]?.layout ?? directLayout);
@@ -306,7 +304,7 @@ function sanitizeSavedLayouts(
306
304
  && typeof (entry as SavedLayout).name === "string",
307
305
  )
308
306
  .map((entry) => {
309
- const layout = sanitizeLayout(entry.layout, fallbackLayout, CURRENT_CHART_SANITIZATION);
307
+ const layout = sanitizeLayout(entry.layout, fallbackLayout);
310
308
  const paneState = sanitizeSavedPaneState((entry as { paneState?: unknown }).paneState, layout);
311
309
  return {
312
310
  id: typeof entry.id === "string" ? entry.id : undefined,
@@ -126,6 +126,17 @@ export class PluginStateStore {
126
126
  ));
127
127
  }
128
128
 
129
+ pluginIds(): string[] {
130
+ return withSqliteBusyRetry("list plugin state ids", () => (
131
+ this.db
132
+ .query<{ plugin_id: string }, []>(
133
+ "SELECT DISTINCT plugin_id FROM plugin_state ORDER BY plugin_id",
134
+ )
135
+ .all()
136
+ .map((row) => row.plugin_id)
137
+ ));
138
+ }
139
+
129
140
  clear(pluginId: string): void {
130
141
  withSqliteBusyRetry("clear plugin state", () => {
131
142
  this.db.query("DELETE FROM plugin_state WHERE plugin_id = ?").run(pluginId);
@@ -221,6 +221,16 @@ describe("AppPersistence", () => {
221
221
  persistence.close();
222
222
  });
223
223
 
224
+ test("lists plugin ids that have stored state", () => {
225
+ const dbPath = createTempDbPath("plugin-state-ids");
226
+ const persistence = new AppPersistence(dbPath);
227
+ persistence.pluginState.set("gloomberb-cloud", "session", { sessionToken: "token" }, 1);
228
+ persistence.pluginState.set("ai", "provider", "openai", 1);
229
+
230
+ expect(persistence.pluginState.pluginIds()).toEqual(["ai", "gloomberb-cloud"]);
231
+ persistence.close();
232
+ });
233
+
224
234
  test("stores multiple plugin state records in one batch", () => {
225
235
  const dbPath = createTempDbPath("plugin-state-batch");
226
236
  const persistence = new AppPersistence(dbPath);
package/src/i18n/es.ts CHANGED
@@ -13,6 +13,7 @@ export const es: Record<string, string> = {
13
13
  "Cancel": "Cancelar",
14
14
  "Close": "Cerrar",
15
15
  "Open": "Abrir",
16
+ "Linked to {source}": "Vinculado a {source}",
16
17
  "Working…": "Procesando…",
17
18
  "Searching…": "Buscando…",
18
19
  "Loading...": "Cargando...",
@@ -220,12 +221,12 @@ export const es: Record<string, string> = {
220
221
  "Dock Pane": "Anclar panel",
221
222
  "Settings": "Ajustes",
222
223
  "Copy Screenshot": "Copiar captura",
224
+ "Share Pane": "Compartir panel",
223
225
  "Pop Out": "Abrir en ventana",
224
226
  "Move Window...": "Mover ventana...",
225
227
  "Resize Window...": "Redimensionar ventana...",
226
228
  "Move Window": "Mover ventana",
227
229
  "Resize Window": "Redimensionar ventana",
228
- "Gridlock All Windows": "Cuadricular todas las ventanas",
229
230
  "Change the current saved layout name": "Cambiar el nombre del diseño guardado actual",
230
231
  "Create a copy of the current layout": "Crear una copia del diseño actual",
231
232
  "Create a fresh saved layout": "Crear un nuevo diseño guardado",
@@ -517,7 +518,6 @@ export const es: Record<string, string> = {
517
518
  "Pop the focused pane out to a desktop window.": "Extraer el panel activo a una ventana de escritorio.",
518
519
  "Copy a screenshot of the focused pane.": "Copiar una captura del panel activo.",
519
520
  "Open layout actions.": "Abrir acciones de diseño.",
520
- "Gridlock all windows.": "Cuadricular todas las ventanas.",
521
521
  "Enter window move mode.": "Entrar en modo mover ventana.",
522
522
  "Enter window resize mode.": "Entrar en modo redimensionar ventana.",
523
523
  "Cancel an active pane drag.": "Cancelar el arrastre de panel activo.",
@@ -897,8 +897,7 @@ export const es: Record<string, string> = {
897
897
  "v{version} available — run {command}": "v{version} disponible — ejecuta {command}",
898
898
  "v{version} available — starting download...": "v{version} disponible — iniciando descarga...",
899
899
  "command bar": "barra de comandos",
900
- "Snapped a window?": "¿Ajustaste una ventana?",
901
- "Gridlock All": "Cuadricular todo",
900
+ "Tidy Windows": "Ordenar ventanas",
902
901
  "Dismiss": "Descartar",
903
902
  "Minimize": "Minimizar",
904
903
  "Maximize": "Maximizar",
@@ -1061,6 +1060,16 @@ export const es: Record<string, string> = {
1061
1060
  "Signed in as {email}": "Sesión iniciada como {email}",
1062
1061
  "Signed in to Gloom Cloud": "Sesión iniciada en Gloom Cloud",
1063
1062
  "Signing you in...": "Iniciando sesión...",
1063
+ "Forgot password?": "¿Olvidaste tu contraseña?",
1064
+ "Sending reset link...": "Enviando enlace de restablecimiento...",
1065
+ "Reset link sent to {email}. Check your inbox.": "Enlace de restablecimiento enviado a {email}. Revisa tu bandeja de entrada.",
1066
+ "Could not send the reset email.": "No se pudo enviar el correo de restablecimiento.",
1067
+ "Create Account": "Crear cuenta",
1068
+ "Log in instead": "Inicia sesión en su lugar",
1069
+ "Sign up instead": "Regístrate en su lugar",
1070
+ "We'll email you a verification link.": "Te enviaremos un enlace de verificación por correo.",
1071
+ "show": "mostrar",
1072
+ "hide": "ocultar",
1064
1073
  "signing in...": "iniciando sesión...",
1065
1074
  "Could not sign you in.": "No se pudo iniciar tu sesión.",
1066
1075
  "Signs this terminal in to your account.": "Inicia sesión en tu cuenta desde esta terminal.",
@@ -1107,7 +1116,6 @@ export const es: Record<string, string> = {
1107
1116
  "Revisa tu bandeja de entrada para verificar tu correo. Después puedes empezar tu prueba Pro gratis de 7 días cuando quieras: escribe UPGRADE.",
1108
1117
 
1109
1118
  // ── Onboarding setup ────────────────────────────────────────
1110
- "$29/mo for real-time market data": "$29/mes por datos de mercado en tiempo real",
1111
1119
  "Limited founding offer. Start a free 7-day trial.": "Oferta limitada de lanzamiento. Empieza una prueba gratuita de 7 días.",
1112
1120
  "Start 7-day free trial": "Iniciar prueba gratuita de 7 días",
1113
1121
  "Adds quotes, financials, options, research, news, chat and AI commands.": "Añade cotizaciones, datos financieros, opciones, análisis, noticias, chat y comandos de IA.",