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
@@ -0,0 +1,78 @@
1
+ import { updatePaneInstance } from "../../../pane-settings";
2
+ import { listVisibleTickerSourcePanes } from "../../../plugins/ticker-navigation";
3
+ import { resolveTickerForPane, type AppState } from "../../../state/app/context";
4
+ import {
5
+ TICKER_RESEARCH_PANE_ID,
6
+ type LayoutConfig,
7
+ type PaneInstanceConfig,
8
+ } from "../../../types/config";
9
+ import type { ContextMenuItem } from "../../../types/context-menu";
10
+ import type { PaneDef } from "../../../types/plugin";
11
+ import { getPaneDisplayTitle } from "../pane/title";
12
+
13
+ /**
14
+ * Flat link controls for a Ticker Research pane: one entry per visible ticker source, plus an
15
+ * unlink entry that pins the pane on the symbol it currently shows.
16
+ */
17
+ export function tickerLinkMenuItems({
18
+ instance,
19
+ layout,
20
+ panes,
21
+ state,
22
+ persistLayout,
23
+ }: {
24
+ instance: PaneInstanceConfig;
25
+ layout: LayoutConfig;
26
+ panes: ReadonlyMap<string, PaneDef>;
27
+ state: Pick<AppState, "config" | "paneState">;
28
+ persistLayout: (nextLayout: LayoutConfig) => void;
29
+ }): ContextMenuItem[] {
30
+ if (instance.paneId !== TICKER_RESEARCH_PANE_ID) return [];
31
+
32
+ const sourceInstanceId = instance.binding?.kind === "follow" ? instance.binding.sourceInstanceId : null;
33
+ const rebind = (binding: PaneInstanceConfig["binding"]) => {
34
+ persistLayout(updatePaneInstance(layout, instance.instanceId, (current) => ({ ...current, binding })));
35
+ };
36
+ const linkToSource = (nextSourceId: string) => {
37
+ persistLayout({
38
+ ...layout,
39
+ instances: layout.instances.map((current) => {
40
+ if (current.instanceId === instance.instanceId) {
41
+ return { ...current, binding: { kind: "follow" as const, sourceInstanceId: nextSourceId } };
42
+ }
43
+ if (
44
+ current.paneId !== TICKER_RESEARCH_PANE_ID
45
+ || current.binding?.kind !== "follow"
46
+ || current.binding.sourceInstanceId !== nextSourceId
47
+ ) return current;
48
+
49
+ const symbol = resolveTickerForPane(state as AppState, current.instanceId);
50
+ return { ...current, binding: symbol ? { kind: "fixed" as const, symbol } : { kind: "none" as const } };
51
+ }),
52
+ });
53
+ };
54
+
55
+ return listVisibleTickerSourcePanes(layout, panes).flatMap((source): ContextMenuItem[] => {
56
+ const paneDef = panes.get(source.paneId);
57
+ if (!paneDef) return [];
58
+ const title = getPaneDisplayTitle(state, source, paneDef, panes);
59
+
60
+ if (source.instanceId !== sourceInstanceId) {
61
+ return [{
62
+ id: `link:${source.instanceId}`,
63
+ label: `Link to ${title}`,
64
+ onSelect: () => linkToSource(source.instanceId),
65
+ }];
66
+ }
67
+
68
+ // Unlinking keeps the pane alive, so it only works once a symbol has resolved.
69
+ const symbol = resolveTickerForPane(state as AppState, instance.instanceId);
70
+ return symbol
71
+ ? [{
72
+ id: `unlink:${source.instanceId}`,
73
+ label: `Unlink from ${title}`,
74
+ onSelect: () => rebind({ kind: "fixed", symbol }),
75
+ }]
76
+ : [];
77
+ });
78
+ }
@@ -1,9 +1,10 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { testRender } from "../../renderers/opentui/test-utils";
3
3
  import { AppContext, createInitialState } from "../../state/app/context";
4
- import { cloneLayout, createDefaultConfig, TICKER_RESEARCH_PANE_ID, type LayoutConfig } from "../../types/config";
4
+ import { cloneLayout, createDefaultConfig, createPaneInstance, type LayoutConfig } from "../../types/config";
5
5
  import type { AppNotificationRequest } from "../../types/plugin";
6
6
  import { StatusBar } from "./status-bar";
7
+ import { getDockedPaneIds } from "../../plugins/pane-manager";
7
8
  import { setSharedRegistryForTests } from "../../plugins/registry";
8
9
  import { act, useEffect, useState } from "react";
9
10
  import { TransientLayoutProvider, useTransientLayout } from "./transient-layout";
@@ -185,34 +186,35 @@ describe("StatusBar", () => {
185
186
  expect(actions).toContainEqual({ type: "REORDER_LAYOUT", fromIndex: 0, toIndex: 2 });
186
187
  });
187
188
 
188
- test("shows a gridlock tip after a corner snap and runs gridlock on click", async () => {
189
- const config = createDefaultConfig("/tmp/gloomberb-test");
190
- const floatingLayout = cloneLayout(config.layout);
191
- floatingLayout.dockRoot = { kind: "pane", instanceId: "portfolio-list:main" };
192
- floatingLayout.floating = [{ instanceId: "ticker-detail:main", x: 8, y: 2, width: 36, height: 12 }];
193
-
189
+ test("offers to tidy three floating windows and tiles them on click", async () => {
190
+ const config = createDefaultConfig("/tmp/gloomberb-tidy-test");
191
+ const floatingLayout: LayoutConfig = {
192
+ dockRoot: null,
193
+ instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })),
194
+ floating: Array.from({ length: 3 }, (_, index) => ({
195
+ instanceId: `chat-${index}`,
196
+ x: index * 40,
197
+ y: 0,
198
+ width: 40,
199
+ height: 20,
200
+ zIndex: 50 + index,
201
+ })),
202
+ detached: [],
203
+ };
194
204
  const state = {
195
205
  ...createInitialState({
196
206
  ...config,
197
207
  layout: floatingLayout,
198
- layouts: [
199
- { name: "Default", layout: cloneLayout(floatingLayout) },
200
- { name: "Research", layout: cloneLayout(floatingLayout) },
201
- ],
208
+ layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }],
202
209
  }),
203
210
  statusBarVisible: true,
204
- gridlockTipVisible: true,
205
211
  };
206
-
207
212
  const actions: Array<{ type: string }> = [];
208
- let updatedLayout = null as ReturnType<typeof cloneLayout> | null;
213
+ let updatedLayout: LayoutConfig | null = null;
209
214
  const notifications: AppNotificationRequest[] = [];
210
215
 
211
216
  setSharedRegistryForTests({
212
- panes: new Map([
213
- ["portfolio-list", {}],
214
- [TICKER_RESEARCH_PANE_ID, {}],
215
- ]),
217
+ panes: new Map([["chat", { name: "Chat" }]]),
216
218
  getLayoutFn: () => state.config.layout,
217
219
  getTermSizeFn: () => ({ width: 120, height: 40 }),
218
220
  updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; },
@@ -228,76 +230,75 @@ describe("StatusBar", () => {
228
230
  );
229
231
 
230
232
  await testSetup.renderOnce();
231
-
232
233
  const frame = testSetup.captureCharFrame();
233
- expect(frame).toContain("Snapped a window?");
234
- expect(frame).toContain("Gridlock All");
235
-
236
- const buttonX = frame.split("\n")[0]?.indexOf("Gridlock All") ?? -1;
234
+ const buttonX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1;
237
235
  expect(buttonX).toBeGreaterThanOrEqual(0);
238
236
 
239
237
  await testSetup.mockMouse.click(buttonX + 1, 0);
240
238
  await testSetup.renderOnce();
241
239
 
242
240
  expect(updatedLayout?.floating).toHaveLength(0);
243
- expect(notifications).toHaveLength(1);
244
241
  expect(notifications[0]).toMatchObject({
245
- body: "Retiled all panes",
242
+ body: "Windows tidied",
246
243
  type: "success",
247
244
  action: { label: "Revert" },
248
245
  });
249
- expect(actions).toContainEqual({ type: "DISMISS_GRIDLOCK_TIP" });
250
-
251
246
  notifications[0]!.action!.onClick();
252
247
  expect(actions).toContainEqual({ type: "UNDO_LAYOUT" });
253
248
  });
254
249
 
255
- test("auto-dismisses the gridlock tip after its timeout", async () => {
256
- const config = createDefaultConfig("/tmp/gloomberb-test");
250
+ test("tidies covered windows instead of leaving them floating", async () => {
251
+ const config = createDefaultConfig("/tmp/gloomberb-covered-test");
252
+ const floatingLayout: LayoutConfig = {
253
+ dockRoot: null,
254
+ instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })),
255
+ floating: Array.from({ length: 3 }, (_, index) => ({
256
+ instanceId: `chat-${index}`,
257
+ x: 10,
258
+ y: 4,
259
+ width: 50,
260
+ height: 20,
261
+ zIndex: 50 + index,
262
+ })),
263
+ detached: [],
264
+ };
257
265
  const state = {
258
- ...createInitialState(config),
266
+ ...createInitialState({
267
+ ...config,
268
+ layout: floatingLayout,
269
+ layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }],
270
+ }),
259
271
  statusBarVisible: true,
260
- gridlockTipVisible: true,
261
- gridlockTipSequence: 1,
262
272
  };
263
- const actions: Array<{ type: string }> = [];
264
- const timers: Array<{ callback: (() => void) | null; delay: number | undefined }> = [];
265
- const originalSetTimeout = globalThis.setTimeout;
266
- const originalClearTimeout = globalThis.clearTimeout;
267
-
268
- globalThis.setTimeout = ((callback: Parameters<typeof setTimeout>[0], delay?: number) => {
269
- timers.push({ callback: typeof callback === "function" ? callback : null, delay });
270
- return 1 as unknown as ReturnType<typeof setTimeout>;
271
- }) as typeof setTimeout;
272
- globalThis.clearTimeout = (() => {}) as typeof clearTimeout;
273
+ let updatedLayout: LayoutConfig | null = null;
274
+ const notifications: AppNotificationRequest[] = [];
273
275
 
274
276
  setSharedRegistryForTests({
275
- panes: new Map(),
277
+ panes: new Map([["chat", { name: "Chat" }]]),
276
278
  getLayoutFn: () => state.config.layout,
277
279
  getTermSizeFn: () => ({ width: 120, height: 40 }),
278
- updateLayoutFn: () => {},
279
- notify: () => {},
280
+ updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; },
281
+ notify: (notification: AppNotificationRequest) => { notifications.push(notification); },
280
282
  Slot: () => null,
281
283
  } as any);
282
284
 
283
- try {
284
- testSetup = await testRender(
285
- <AppContext value={{ state, dispatch: (action) => actions.push(action as { type: string }) }}>
286
- <StatusBar />
287
- </AppContext>,
288
- { width: 120, height: 1 },
289
- );
285
+ testSetup = await testRender(
286
+ <AppContext value={{ state, dispatch: () => {} }}>
287
+ <StatusBar />
288
+ </AppContext>,
289
+ { width: 120, height: 1 },
290
+ );
290
291
 
291
- await testSetup.renderOnce();
292
+ await testSetup.renderOnce();
293
+ const frame = testSetup.captureCharFrame();
294
+ const tidyX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1;
295
+ expect(tidyX).toBeGreaterThanOrEqual(0);
292
296
 
293
- const gridlockTimer = timers.find((entry) => entry.delay === 60_000);
294
- expect(gridlockTimer?.callback).toBeDefined();
295
- gridlockTimer?.callback?.();
297
+ await testSetup.mockMouse.click(tidyX + 1, 0);
298
+ await testSetup.renderOnce();
296
299
 
297
- expect(actions).toContainEqual({ type: "DISMISS_GRIDLOCK_TIP" });
298
- } finally {
299
- globalThis.setTimeout = originalSetTimeout;
300
- globalThis.clearTimeout = originalClearTimeout;
301
- }
300
+ expect(updatedLayout?.floating).toHaveLength(0);
301
+ expect(getDockedPaneIds(updatedLayout!)).toHaveLength(3);
302
+ expect(notifications[0]?.body).toBe("Windows tidied");
302
303
  });
303
304
  });
@@ -1,28 +1,29 @@
1
1
  import { Box, Span, Text, TextAttributes, contextMenuDivider, useContextMenu, useUiCapabilities } from "../../ui";
2
2
  import { useDialog, type PromptContext } from "../../ui/dialog";
3
- import { useCallback, useEffect, useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import { blendHex, hoverBg } from "../../theme/colors";
5
5
  import { t, tf } from "../../i18n";
6
6
  import { useThemeColors } from "../../theme/theme-context";
7
7
  import { useAppDispatch, useAppSelector } from "../../state/app/context";
8
8
  import {
9
9
  selectActiveLayoutIndex,
10
- selectGridlockTipSequence,
11
- selectGridlockTipVisible,
10
+ selectLayout,
12
11
  selectSavedLayouts,
13
12
  selectStatusBarVisible,
14
13
  } from "../../state/selectors-ui";
15
14
  import { getSharedRegistry } from "../../plugins/registry";
16
- import { gridlockAllPanes } from "../../plugins/pane-manager";
15
+ import {
16
+ gridlockAllPanes,
17
+ shouldShowTidyWindows,
18
+ } from "../../plugins/pane-manager";
17
19
  import { notifyGridlockComplete } from "../../plugins/gridlock-notification";
18
20
  import { PluginSlot } from "../../react/plugins/plugin-slot";
19
21
  import type { ContextMenuItem } from "../../types/context-menu";
20
- import { Tabs } from "../ui/tabs";
22
+ import type { LayoutConfig } from "../../types/config";
21
23
  import { ConfirmDialog } from "../ui/confirm-dialog";
24
+ import { Tabs } from "../ui/tabs";
22
25
  import { useTransientLayout } from "./transient-layout";
23
26
 
24
- const GRIDLOCK_TIP_DURATION_MS = 60_000;
25
-
26
27
  type StatusBarEvent = { stopPropagation?: () => void; preventDefault?: () => void };
27
28
  type HoveredControl = string | null;
28
29
  type SetHoveredControl = (updater: (current: HoveredControl) => HoveredControl) => void;
@@ -37,10 +38,9 @@ type LayoutTabItem = {
37
38
  type StatusBarViewProps = {
38
39
  activeLayoutIdx: number;
39
40
  activeLayoutValue: string;
40
- dismissGridlockTip: (event?: StatusBarEvent) => void;
41
- handleGridlockTip: (event?: StatusBarEvent) => void;
42
41
  handleLayoutReorder: (fromValue: string, toValue: string) => void;
43
42
  handleLayoutSelect: (value: string) => void;
43
+ handleTidyWindows: (event?: StatusBarEvent) => void;
44
44
  hasMultipleLayouts: boolean;
45
45
  hoveredControl: HoveredControl;
46
46
  layoutTabItems: LayoutTabItem[];
@@ -48,7 +48,7 @@ type StatusBarViewProps = {
48
48
  openCommandBar: (event?: StatusBarEvent) => void;
49
49
  openLayoutContextMenu: (index: number, event: any) => void | Promise<unknown>;
50
50
  setHoveredControl: SetHoveredControl;
51
- showGridlockTip: boolean;
51
+ showTidyWindows: boolean;
52
52
  };
53
53
 
54
54
  function truncate(text: string, width: number): string {
@@ -67,13 +67,14 @@ export function StatusBar() {
67
67
  const layouts = useAppSelector(selectSavedLayouts);
68
68
  const activeLayoutIdx = useAppSelector(selectActiveLayoutIndex);
69
69
  const statusBarVisible = useAppSelector(selectStatusBarVisible);
70
- const gridlockTipVisible = useAppSelector(selectGridlockTipVisible);
71
- const gridlockTipSequence = useAppSelector(selectGridlockTipSequence);
70
+ const layout = useAppSelector(selectLayout);
72
71
  const { transientLayout } = useTransientLayout();
73
72
  const [hoveredControl, setHoveredControl] = useState<string | null>(null);
74
73
 
75
74
  const hasMultipleLayouts = layouts.length > 1 || !!transientLayout;
76
- const showGridlockTip = gridlockTipVisible && !!registry;
75
+ const showTidyWindows = useMemo(() => shouldShowTidyWindows(layout), [layout])
76
+ && !transientLayout?.active
77
+ && !!registry;
77
78
  const savedLayoutTabs = layouts.map((layout, index) => ({
78
79
  label: `^${index + 1} ${truncate(layout.name, 14)}`,
79
80
  value: String(index),
@@ -122,34 +123,24 @@ export function StatusBar() {
122
123
  dispatch({ type: "REORDER_LAYOUT", fromIndex, toIndex });
123
124
  };
124
125
 
125
- useEffect(() => {
126
- if (!gridlockTipVisible) return;
127
- const timer = setTimeout(() => {
128
- dispatch({ type: "DISMISS_GRIDLOCK_TIP" });
129
- }, GRIDLOCK_TIP_DURATION_MS);
130
- return () => clearTimeout(timer);
131
- }, [dispatch, gridlockTipSequence, gridlockTipVisible]);
132
-
133
- const handleGridlockTip = (event?: StatusBarEvent) => {
126
+ const handleTidyWindows = (event?: StatusBarEvent) => {
134
127
  event?.preventDefault?.();
135
128
  event?.stopPropagation?.();
136
129
  if (!registry) return;
130
+
131
+ const currentLayout = registry.getLayoutFn();
137
132
  const { width, height } = registry.getTermSizeFn();
138
- registry.updateLayoutFn(gridlockAllPanes(
139
- registry.getLayoutFn(),
133
+ const nextLayout = gridlockAllPanes(
134
+ currentLayout,
140
135
  { x: 0, y: 0, width, height },
141
136
  registry.panes,
142
- ));
137
+ );
138
+ if (nextLayout === currentLayout) return;
139
+
140
+ registry.updateLayoutFn(nextLayout);
143
141
  notifyGridlockComplete(registry.notify.bind(registry), () => {
144
142
  dispatch({ type: "UNDO_LAYOUT" });
145
143
  });
146
- dispatch({ type: "DISMISS_GRIDLOCK_TIP" });
147
- };
148
-
149
- const dismissGridlockTip = (event?: StatusBarEvent) => {
150
- event?.preventDefault?.();
151
- event?.stopPropagation?.();
152
- dispatch({ type: "DISMISS_GRIDLOCK_TIP" });
153
144
  };
154
145
 
155
146
  const openCommandBar = (event?: StatusBarEvent) => {
@@ -226,10 +217,15 @@ export function StatusBar() {
226
217
  onSelect: () => requestDeleteLayout(index),
227
218
  },
228
219
  contextMenuDivider("layout:actions-divider"),
220
+ {
221
+ id: "layout:gallery",
222
+ label: "Browse Layouts...",
223
+ onSelect: () => registry?.showPane("layout-marketplace"),
224
+ },
229
225
  {
230
226
  id: "layout:actions",
231
227
  label: "Layout Actions...",
232
- onSelect: () => registry?.openCommandBar("LAY "),
228
+ onSelect: () => registry?.openCommandBar("LMA "),
233
229
  },
234
230
  );
235
231
 
@@ -270,10 +266,9 @@ export function StatusBar() {
270
266
  const viewProps: StatusBarViewProps = {
271
267
  activeLayoutIdx,
272
268
  activeLayoutValue,
273
- dismissGridlockTip,
274
- handleGridlockTip,
275
269
  handleLayoutReorder,
276
270
  handleLayoutSelect,
271
+ handleTidyWindows,
277
272
  hasMultipleLayouts,
278
273
  hoveredControl,
279
274
  layoutTabItems,
@@ -281,7 +276,7 @@ export function StatusBar() {
281
276
  openCommandBar,
282
277
  openLayoutContextMenu,
283
278
  setHoveredControl,
284
- showGridlockTip,
279
+ showTidyWindows,
285
280
  };
286
281
 
287
282
  if (nativePaneChrome) {
@@ -294,7 +289,7 @@ export function StatusBar() {
294
289
  function NativeStatusBar({
295
290
  activeLayoutIdx,
296
291
  openLayoutContextMenu,
297
- showGridlockTip,
292
+ showTidyWindows,
298
293
  ...props
299
294
  }: StatusBarViewProps) {
300
295
  const colors = useThemeColors();
@@ -315,7 +310,7 @@ function NativeStatusBar({
315
310
  }}
316
311
  >
317
312
  <StatusBarLayoutControl nativePaneChrome {...props} />
318
- {showGridlockTip && <NativeGridlockTip {...props} />}
313
+ {showTidyWindows && <NativeTidyWindows {...props} />}
319
314
  <StatusBarWidgets />
320
315
  </Box>
321
316
  );
@@ -324,7 +319,7 @@ function NativeStatusBar({
324
319
  function TerminalStatusBar({
325
320
  activeLayoutIdx,
326
321
  openLayoutContextMenu,
327
- showGridlockTip,
322
+ showTidyWindows,
328
323
  ...props
329
324
  }: StatusBarViewProps) {
330
325
  const colors = useThemeColors();
@@ -340,7 +335,7 @@ function TerminalStatusBar({
340
335
  }}
341
336
  >
342
337
  <StatusBarLayoutControl nativePaneChrome={false} {...props} />
343
- {showGridlockTip && <TerminalGridlockTip {...props} />}
338
+ {showTidyWindows && <TerminalTidyWindows {...props} />}
344
339
  <StatusBarWidgets />
345
340
  </Box>
346
341
  );
@@ -422,62 +417,45 @@ function CommandBarHint({
422
417
  );
423
418
  }
424
419
 
425
- function NativeGridlockTip({
426
- dismissGridlockTip,
427
- handleGridlockTip,
420
+ function NativeTidyWindows({
421
+ handleTidyWindows,
428
422
  hoveredControl,
429
423
  setHoveredControl,
430
- }: Pick<StatusBarViewProps, "dismissGridlockTip" | "handleGridlockTip" | "hoveredControl" | "setHoveredControl">) {
424
+ }: Pick<StatusBarViewProps, "handleTidyWindows" | "hoveredControl" | "setHoveredControl">) {
431
425
  const colors = useThemeColors();
426
+ const hovered = hoveredControl === "tidy-windows";
432
427
  return (
433
- <Box paddingLeft={2} flexShrink={0} flexDirection="row" alignItems="center" gap={1}>
434
- <Text fg={colors.textDim}>{t("Snapped a window?")}</Text>
428
+ <Box paddingLeft={2} flexShrink={0} flexDirection="row" alignItems="center">
435
429
  <Text
436
- fg={hoveredControl === "gridlock-tip" ? colors.textBright : colors.borderFocused}
430
+ fg={hovered ? colors.textBright : colors.borderFocused}
437
431
  attributes={TextAttributes.BOLD}
438
- onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
439
- onMouseDown={handleGridlockTip}
440
- data-gloom-interactive="true"
441
- >
442
- {t("Gridlock All")}
443
- </Text>
444
- <Text
445
- fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
446
- onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
447
- onMouseDown={dismissGridlockTip}
432
+ title={t("Tidy Windows")}
433
+ onMouseOver={() => setHoveredControl((current) => (current === "tidy-windows" ? current : "tidy-windows"))}
434
+ onMouseDown={handleTidyWindows}
448
435
  data-gloom-interactive="true"
449
436
  >
450
- {t("Dismiss")}
437
+ {t("Tidy Windows")}
451
438
  </Text>
452
439
  </Box>
453
440
  );
454
441
  }
455
442
 
456
- function TerminalGridlockTip({
457
- dismissGridlockTip,
458
- handleGridlockTip,
443
+ function TerminalTidyWindows({
444
+ handleTidyWindows,
459
445
  hoveredControl,
460
446
  setHoveredControl,
461
- }: Pick<StatusBarViewProps, "dismissGridlockTip" | "handleGridlockTip" | "hoveredControl" | "setHoveredControl">) {
447
+ }: Pick<StatusBarViewProps, "handleTidyWindows" | "hoveredControl" | "setHoveredControl">) {
462
448
  const colors = useThemeColors();
449
+ const hovered = hoveredControl === "tidy-windows";
463
450
  return (
464
451
  <Box paddingLeft={1} flexShrink={0} flexDirection="row">
465
- <Text fg={colors.textDim}>{t("Snapped a window?")}</Text>
466
- <Box width={1} />
467
452
  <Box
468
- backgroundColor={hoveredControl === "gridlock-tip" ? hoverBg(colors) : colors.header}
469
- onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip" ? current : "gridlock-tip"))}
470
- onMouseDown={handleGridlockTip}
453
+ backgroundColor={hovered ? hoverBg(colors) : colors.header}
454
+ onMouseOver={() => setHoveredControl((current) => (current === "tidy-windows" ? current : "tidy-windows"))}
455
+ onMouseDown={handleTidyWindows}
471
456
  >
472
- <Text fg={colors.headerText}> {t("Gridlock All")} </Text>
457
+ <Text fg={colors.headerText}> {t("Tidy Windows")} </Text>
473
458
  </Box>
474
- <Text
475
- fg={hoveredControl === "gridlock-tip-dismiss" ? colors.text : colors.textDim}
476
- onMouseOver={() => setHoveredControl((current) => (current === "gridlock-tip-dismiss" ? current : "gridlock-tip-dismiss"))}
477
- onMouseDown={dismissGridlockTip}
478
- >
479
- {" x"}
480
- </Text>
481
459
  </Box>
482
460
  );
483
461
  }
@@ -3,7 +3,7 @@ import { Box, Text, TextAttributes, useUiHost, type InputRenderable } from "../.
3
3
  import { colors } from "../../../theme/colors";
4
4
  import { t } from "../../../i18n";
5
5
  import { TextField } from "../../ui";
6
- import type { AccountMode, AccountSubmitError } from "./model";
6
+ import type { AccountMode, AccountSubmitError } from "../../../plugins/builtin/cloud/auth-model";
7
7
 
8
8
  function TuiFieldRow({
9
9
  label,
@@ -103,7 +103,8 @@ export function AccountFormPanel({
103
103
  label={t("Email")}
104
104
  inputRef={onEmail ? inputRef : undefined}
105
105
  value={email}
106
- type="text"
106
+ type="email"
107
+ autoComplete="email"
107
108
  placeholder="email@example.com"
108
109
  focused={onEmail && editing && !submitting}
109
110
  backgroundColor={colors.panel}
@@ -118,6 +119,7 @@ export function AccountFormPanel({
118
119
  inputRef={!onEmail ? inputRef : undefined}
119
120
  value={password}
120
121
  type="password"
122
+ autoComplete={mode === "signup" ? "new-password" : "current-password"}
121
123
  placeholder={mode === "signup" ? t("At least 8 characters") : t("Your password")}
122
124
  focused={!onEmail && editing && !submitting}
123
125
  backgroundColor={colors.panel}
@@ -4,7 +4,7 @@ import { AccountChooserPanel } from "./chooser-panel";
4
4
  import { AccountFormPanel } from "./form-panel";
5
5
  import { AccountQrPanel } from "./qr-panel";
6
6
  import { AccountSignedInPanel } from "./signed-in-panel";
7
- import type { AccountOutcome, AccountSub, AccountSubmitError } from "./model";
7
+ import type { AccountOutcome, AccountSub, AccountSubmitError } from "../../../plugins/builtin/cloud/auth-model";
8
8
 
9
9
  export interface AccountStepProps {
10
10
  sub: AccountSub;
@@ -1,7 +1,7 @@
1
1
  import { Box, Text, TextAttributes, useUiHost } from "../../../ui";
2
2
  import { colors } from "../../../theme/colors";
3
3
  import { t, tf } from "../../../i18n";
4
- import type { AccountOutcome } from "./model";
4
+ import type { AccountOutcome } from "../../../plugins/builtin/cloud/auth-model";
5
5
 
6
6
  export function AccountSignedInPanel({ outcome }: { outcome: AccountOutcome | null }) {
7
7
  const email = outcome?.email ?? "";
@@ -21,7 +21,7 @@ import type { TickerRecord } from "../../types/ticker";
21
21
  import { EventBus } from "../../plugins/event-bus";
22
22
  import type { PluginRegistry } from "../../plugins/registry";
23
23
  import type { BrokerAdapter, BrokerPosition } from "../../types/broker";
24
- import { ACCOUNT_CHOICE_IDS } from "./account-step/model";
24
+ import { ACCOUNT_CHOICE_IDS } from "../../plugins/builtin/cloud/auth-model";
25
25
  import { OnboardingWizard } from "./onboarding-wizard";
26
26
 
27
27
  let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
@@ -595,6 +595,49 @@ describe("OnboardingWizard", () => {
595
595
  expect(capturedConfig?.onboardingProgress?.stage).toBe("account");
596
596
  });
597
597
 
598
+ test("loads the current Cloud price on the Pro step", async () => {
599
+ tempDataDir = await mkdtemp(join(tmpdir(), "gloomberb-onboarding-pro-price-"));
600
+ const getCloudPricing = apiClient.getCloudPricing;
601
+ let resolvePricing!: (pricing: Awaited<ReturnType<typeof apiClient.getCloudPricing>>) => void;
602
+ apiClient.getCloudPricing = () => new Promise((resolve) => {
603
+ resolvePricing = resolve;
604
+ });
605
+
606
+ try {
607
+ const pluginRegistry = createPluginRegistry();
608
+ const config = {
609
+ ...createDefaultConfig(tempDataDir),
610
+ onboardingProgress: {
611
+ version: 1 as const,
612
+ stage: "upgrade" as const,
613
+ accountStatus: "signed-in" as const,
614
+ },
615
+ };
616
+ testSetup = await testRender(
617
+ <WizardHarness config={config} pluginRegistry={pluginRegistry} />,
618
+ { width: 100, height: 30 },
619
+ );
620
+ await testSetup.renderOnce();
621
+ await act(async () => {
622
+ resolvePricing({
623
+ currency: "usd",
624
+ trialDays: 7,
625
+ founding: true,
626
+ monthly: { amount: 3900, anchorAmount: 4900 },
627
+ yearly: { amount: 39000, anchorAmount: 49000 },
628
+ });
629
+ await Bun.sleep(0);
630
+ await testSetup!.renderOnce();
631
+ });
632
+
633
+ const frame = testSetup.captureCharFrame();
634
+ expect(frame).toContain("$49/mo");
635
+ expect(frame).not.toContain("$29/mo");
636
+ } finally {
637
+ apiClient.getCloudPricing = getCloudPricing;
638
+ }
639
+ });
640
+
598
641
  test("Not now skips Pro and moves directly to ready", async () => {
599
642
  tempDataDir = await mkdtemp(join(tmpdir(), "gloomberb-onboarding-cloud-skip-"));
600
643
  const pluginRegistry = createPluginRegistry();