gloomberb 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -14
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +9 -10
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +52 -11
  87. package/src/components/layout/shell.test.tsx +315 -107
  88. package/src/components/layout/shell.tsx +1010 -368
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  180. package/src/plugins/builtin/chat-controller.ts +85 -14
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +363 -170
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +99 -9
  440. package/src/utils/api-client.ts +378 -18
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,20 +1,21 @@
1
+ import { AsciiText, Box, Input, Span, Strong, Text, Underline, useUiHost } from "../../ui";
1
2
  import { useState, useCallback, useEffect, useRef, useMemo, type RefObject } from "react";
2
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
3
- import { TextAttributes } from "@opentui/core";
4
- import type { InputRenderable } from "@opentui/core";
3
+ import { useShortcut, useViewport } from "../../react/input";
4
+ import { TextAttributes } from "../../ui";
5
+ import { type InputRenderable } from "../../ui";
5
6
  import { colors, applyTheme } from "../../theme/colors";
6
7
  import { themes, getThemeIds } from "../../theme/themes";
7
8
  import { saveConfig } from "../../data/config-store";
8
9
  import type { AppConfig } from "../../types/config";
9
10
  import type { PluginRegistry } from "../../plugins/registry";
10
11
  import { resolveBrokerConfigFields, type BrokerAdapter, type BrokerConfigField } from "../../types/broker";
11
- import { buildIbkrConfigFromValues } from "../../plugins/ibkr/config";
12
+ import { buildBrokerProfileConfig, validateBrokerProfileValues } from "../../brokers/profile-form";
12
13
  import { createBrokerInstanceId } from "../../utils/broker-instances";
13
14
  import { isBackNavigationKey, isPlainEscape } from "../../utils/back-navigation";
15
+ import { detectShortcutPlatform, formatPrimaryShortcut, getShortcutDisplayMode } from "../../utils/shortcut-labels";
14
16
  import { syncBrokerInstance } from "../../brokers/sync-broker-instance";
15
17
  import { debugLog } from "../../utils/debug-log";
16
- import { ToggleList, type ToggleListItem } from "../toggle-list";
17
- import { TextField, ExternalLink } from "../ui";
18
+ import { TextField, ExternalLink, ListView, type ListViewItem } from "../ui";
18
19
 
19
20
  interface OnboardingWizardProps {
20
21
  config: AppConfig;
@@ -22,8 +23,8 @@ interface OnboardingWizardProps {
22
23
  onComplete: (config: AppConfig) => void | Promise<void>;
23
24
  }
24
25
 
25
- type Step = "welcome" | "theme" | "portfolio" | "plugins" | "shortcuts" | "ready";
26
- const STEPS: Step[] = ["welcome", "theme", "portfolio", "plugins", "shortcuts", "ready"];
26
+ type Step = "welcome" | "theme" | "portfolio" | "shortcuts" | "ready";
27
+ const STEPS: Step[] = ["welcome", "theme", "portfolio", "shortcuts", "ready"];
27
28
 
28
29
  // Sub-steps within the portfolio step
29
30
  type PortfolioSub = "choose" | "manual-name" | "broker-setup" | "broker-fields" | "broker-sync";
@@ -34,12 +35,7 @@ interface BrokerOption {
34
35
  adapter: BrokerAdapter;
35
36
  }
36
37
 
37
- const LOGO = [
38
- "\u259E\u2580\u2596\u259C \u258C \u258C ",
39
- "\u258C\u2584\u2596\u2590 \u259E\u2580\u2596\u259E\u2580\u2596\u259B\u259A\u2580\u2596\u259B\u2580\u2596\u259E\u2580\u2596\u2599\u2580\u2596\u259B\u2580\u2596",
40
- "\u258C \u258C\u2590 \u258C \u258C\u258C \u258C\u258C\u2590 \u258C\u258C \u258C\u259B\u2580 \u258C \u258C \u258C",
41
- "\u259D\u2580 \u2598\u259D\u2580 \u259D\u2580 \u2598\u259D \u2598\u2580\u2580 \u259D\u2580\u2598\u2598 \u2580\u2580 ",
42
- ];
38
+ const LOGO_TEXT = "Gloomberb";
43
39
 
44
40
  const PASSWORD_MASK_CHAR = "*";
45
41
  const onboardingLog = debugLog.createLogger("onboarding");
@@ -49,23 +45,6 @@ interface BrokerSyncSummary {
49
45
  positionsImported: number;
50
46
  }
51
47
 
52
- function getToggleablePlugins(pluginRegistry: PluginRegistry) {
53
- const result: Array<{ id: string; name: string; description: string; order: number }> = [];
54
- for (const [, plugin] of pluginRegistry.allPlugins) {
55
- if (plugin.toggleable) {
56
- result.push({
57
- id: plugin.id,
58
- name: plugin.name,
59
- description: plugin.description ?? "",
60
- order: plugin.order ?? Number.MAX_SAFE_INTEGER,
61
- });
62
- }
63
- }
64
- return result
65
- .sort((left, right) => left.order - right.order || left.name.localeCompare(right.name))
66
- .map(({ order: _order, ...plugin }) => plugin);
67
- }
68
-
69
48
  function summarizeError(error: unknown): string {
70
49
  if (error instanceof Error && error.message.trim()) {
71
50
  return error.message.trim();
@@ -91,7 +70,6 @@ function focusPortfolioListCollection(config: AppConfig, collectionId: string):
91
70
  },
92
71
  settings: {
93
72
  ...instance.settings,
94
- lockedCollectionId: collectionId,
95
73
  visibleCollectionIds: [collectionId, ...visibleCollectionIds.filter((value) => value !== collectionId)],
96
74
  },
97
75
  };
@@ -107,7 +85,7 @@ function focusPortfolioListCollection(config: AppConfig, collectionId: string):
107
85
  }
108
86
 
109
87
  export function OnboardingWizard({ config, pluginRegistry, onComplete }: OnboardingWizardProps) {
110
- const { width: termWidth, height: termHeight } = useTerminalDimensions();
88
+ const { width: termWidth, height: termHeight } = useViewport();
111
89
  const [step, setStep] = useState<Step>("welcome");
112
90
  const [themeIdx, setThemeIdx] = useState(0);
113
91
 
@@ -128,13 +106,6 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
128
106
  const [isFinishing, setIsFinishing] = useState(false);
129
107
  const [finishError, setFinishError] = useState<string | null>(null);
130
108
 
131
- // Plugin state
132
- const toggleablePlugins = useMemo(() => getToggleablePlugins(pluginRegistry), [pluginRegistry]);
133
- const [disabledPlugins, setDisabledPlugins] = useState<string[]>(() => (
134
- config.disabledPlugins.filter((pluginId) => pluginId !== "gloomberb-cloud")
135
- ));
136
- const [pluginIdx, setPluginIdx] = useState(0);
137
-
138
109
  const inputRef = useRef<InputRenderable>(null);
139
110
  const brokerSyncAttemptRef = useRef(0);
140
111
  const themeIds = getThemeIds();
@@ -152,15 +123,15 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
152
123
  }, [pluginRegistry.brokers]);
153
124
 
154
125
  // Build the list of choices for the portfolio step: manual + all connectable brokers
155
- const portfolioChoices = useMemo(() => {
156
- const choices: Array<{ id: string; label: string; desc: string }> = [
157
- { id: "manual", label: "Create Manual Portfolio", desc: "Add tickers and positions by hand" },
126
+ const portfolioChoices = useMemo<ListViewItem[]>(() => {
127
+ const choices: ListViewItem[] = [
128
+ { id: "manual", label: "Create Manual Portfolio", description: "Add tickers and positions by hand" },
158
129
  ];
159
130
  for (const broker of brokerOptions) {
160
131
  choices.push({
161
132
  id: broker.id,
162
133
  label: `Connect ${broker.name}`,
163
- desc: `Auto-import positions via ${broker.name}`,
134
+ description: `Auto-import positions via ${broker.name}`,
164
135
  });
165
136
  }
166
137
  return choices;
@@ -232,10 +203,13 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
232
203
  }
233
204
 
234
205
  const brokerOption = brokerOptions.find((option) => option.id === selectedBrokerId);
206
+ const adapter = brokerOption?.adapter;
207
+ if (!adapter) throw new Error(`Unknown broker "${selectedBrokerId}".`);
208
+ const validationError = validateBrokerProfileValues(adapter, selectedValues);
209
+ if (validationError) throw new Error(validationError);
210
+
235
211
  const label = brokerOption?.name || selectedBrokerId;
236
- const brokerConfig = (selectedBrokerId === "ibkr"
237
- ? buildIbkrConfigFromValues(selectedValues)
238
- : { ...selectedValues }) as unknown as Record<string, unknown>;
212
+ const brokerConfig = buildBrokerProfileConfig(adapter, selectedValues);
239
213
  const instanceId = createBrokerInstanceId(
240
214
  selectedBrokerId,
241
215
  label,
@@ -280,7 +254,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
280
254
  brokers: pluginRegistry.brokers,
281
255
  tickerRepository: pluginRegistry.tickerRepository,
282
256
  resources: pluginRegistry.persistence.resources,
283
- persistResolvedIbkrConnection: true,
257
+ persistResolvedBrokerConfig: true,
284
258
  });
285
259
  if (brokerSyncAttemptRef.current !== attemptId) {
286
260
  return;
@@ -347,7 +321,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
347
321
  portfolios: isBroker
348
322
  ? (baseConfig ?? config).portfolios
349
323
  : [{ id: "main", name: portfolioName || "Main Portfolio", currency: "USD" }],
350
- disabledPlugins,
324
+ disabledPlugins: [],
351
325
  onboardingComplete: true,
352
326
  };
353
327
 
@@ -360,7 +334,6 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
360
334
  }, [
361
335
  brokerSyncedConfig,
362
336
  config,
363
- disabledPlugins,
364
337
  isFinishing,
365
338
  onComplete,
366
339
  portfolioName,
@@ -378,7 +351,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
378
351
  }));
379
352
  }, [resetBrokerSync]);
380
353
 
381
- useKeyboard((event) => {
354
+ useShortcut((event) => {
382
355
  if (isFinishing) {
383
356
  return;
384
357
  }
@@ -576,22 +549,6 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
576
549
  setBrokerSelectIdx((index) => Math.min(optionCount - 1, index + 1));
577
550
  }
578
551
  }
579
- } else if (step === "plugins") {
580
- if (event.name === "up" || event.name === "k") {
581
- setPluginIdx((i) => Math.max(0, i - 1));
582
- } else if (event.name === "down" || event.name === "j") {
583
- setPluginIdx((i) => Math.min(toggleablePlugins.length - 1, i + 1));
584
- } else if (event.name === "space" || event.name === " ") {
585
- event.stopPropagation?.();
586
- const plugin = toggleablePlugins[pluginIdx];
587
- if (plugin) {
588
- setDisabledPlugins((prev) =>
589
- prev.includes(plugin.id)
590
- ? prev.filter((id) => id !== plugin.id)
591
- : [...prev, plugin.id]
592
- );
593
- }
594
- }
595
552
  }
596
553
  });
597
554
 
@@ -623,7 +580,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
623
580
  : null;
624
581
 
625
582
  return (
626
- <box
583
+ <Box
627
584
  position="absolute"
628
585
  top={0}
629
586
  left={0}
@@ -632,7 +589,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
632
589
  backgroundColor={colors.bg}
633
590
  zIndex={200}
634
591
  >
635
- <box
592
+ <Box
636
593
  position="absolute"
637
594
  top={contentTop}
638
595
  left={contentLeft}
@@ -646,6 +603,7 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
646
603
  sub={portfolioSub}
647
604
  choices={portfolioChoices}
648
605
  optionIdx={portfolioOptionIdx}
606
+ onOptionSelect={setPortfolioOptionIdx}
649
607
  portfolioName={portfolioName}
650
608
  onNameChange={setPortfolioName}
651
609
  selectedBrokerId={selectedBrokerId}
@@ -660,21 +618,8 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
660
618
  brokerSyncError={brokerSyncError}
661
619
  />
662
620
  )}
663
- {step === "plugins" && (
664
- <PluginsStep
665
- plugins={toggleablePlugins}
666
- disabledPlugins={disabledPlugins}
667
- selectedIdx={pluginIdx}
668
- onToggle={(id) => {
669
- setDisabledPlugins((prev) =>
670
- prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
671
- );
672
- }}
673
- onSelect={setPluginIdx}
674
- />
675
- )}
676
621
  {step === "shortcuts" && (
677
- <ShortcutsStep pluginRegistry={pluginRegistry} disabledPlugins={disabledPlugins} />
622
+ <ShortcutsStep pluginRegistry={pluginRegistry} />
678
623
  )}
679
624
  {step === "ready" && (
680
625
  <ReadyStep
@@ -687,20 +632,20 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
687
632
  )}
688
633
 
689
634
  {/* Bottom: progress + nav hints */}
690
- <box height={1} />
691
- <box height={1} flexDirection="row" width={contentWidth}>
692
- <box flexGrow={1}>
693
- <text fg={colors.textMuted}>{progressDots}</text>
694
- </box>
695
- </box>
696
- <box height={1} flexDirection="row" width={contentWidth}>
697
- <box flexGrow={1} />
698
- <box>
699
- <text fg={colors.textMuted}>{hintText}</text>
700
- </box>
701
- </box>
702
- </box>
703
- </box>
635
+ <Box height={1} />
636
+ <Box height={1} flexDirection="row" width={contentWidth}>
637
+ <Box flexGrow={1}>
638
+ <Text fg={colors.textMuted}>{progressDots}</Text>
639
+ </Box>
640
+ </Box>
641
+ <Box height={1} flexDirection="row" width={contentWidth}>
642
+ <Box flexGrow={1} />
643
+ <Box>
644
+ <Text fg={colors.textMuted}>{hintText}</Text>
645
+ </Box>
646
+ </Box>
647
+ </Box>
648
+ </Box>
704
649
  );
705
650
  }
706
651
 
@@ -708,24 +653,20 @@ export function OnboardingWizard({ config, pluginRegistry, onComplete }: Onboard
708
653
 
709
654
  function WelcomeStep() {
710
655
  return (
711
- <box flexDirection="column" paddingX={2}>
712
- <box height={1} />
713
- {LOGO.map((line, i) => (
714
- <box key={i} height={1}>
715
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{line}</text>
716
- </box>
717
- ))}
718
- <box height={2} />
719
- <box height={1}>
720
- <text fg={colors.textDim}>{"The open terminal for modern finance."}</text>
721
- </box>
722
- <box height={2} />
723
- <box height={1}>
724
- <text fg={colors.textMuted}>{"Let's set things up (~30s)."}</text>
725
- </box>
726
- <box height={2} />
727
-
728
- </box>
656
+ <Box flexDirection="column" paddingX={2}>
657
+ <Box height={1} />
658
+ <AsciiText text={LOGO_TEXT} font="wordmark" color={colors.textBright} />
659
+ <Box height={2} />
660
+ <Box height={1}>
661
+ <Text fg={colors.textDim}>{"The open terminal for modern finance."}</Text>
662
+ </Box>
663
+ <Box height={2} />
664
+ <Box height={1}>
665
+ <Text fg={colors.textMuted}>{"Let's set things up (~30s)."}</Text>
666
+ </Box>
667
+ <Box height={2} />
668
+
669
+ </Box>
729
670
  );
730
671
  }
731
672
 
@@ -737,30 +678,30 @@ function ThemeStep({ themeIds, selectedIdx, height }: { themeIds: string[]; sele
737
678
  const windowEnd = Math.min(themeIds.length, windowStart + maxVisible);
738
679
 
739
680
  return (
740
- <box flexDirection="column" paddingX={2}>
741
- <box height={1}>
742
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Theme"}</text>
743
- </box>
744
- <box height={1} />
745
- <box height={1} flexDirection="row">
746
- <text fg={colors.textDim}>{"Change it later from the command bar with "}</text>
747
- <text fg={colors.text} attributes={TextAttributes.BOLD}>{"TH"}</text>
748
- </box>
749
- <box height={1} />
750
- <box height={1}>
751
- <text fg={colors.positive}>{" \u2588\u2588 "}</text>
752
- <text fg={colors.negative}>{" \u2588\u2588 "}</text>
753
- <text fg={colors.text}>{" \u2588\u2588 "}</text>
754
- <text fg={colors.textBright}>{" \u2588\u2588 "}</text>
755
- <text fg={colors.borderFocused}>{" \u2588\u2588 "}</text>
756
- <text fg={colors.textDim}>{" \u2588\u2588 "}</text>
757
- </box>
758
- <box height={1} />
681
+ <Box flexDirection="column" paddingX={2}>
682
+ <Box height={1}>
683
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Theme"}</Text>
684
+ </Box>
685
+ <Box height={1} />
686
+ <Box height={1} flexDirection="row">
687
+ <Text fg={colors.textDim}>{"Change it later from the command bar with "}</Text>
688
+ <Text fg={colors.text} attributes={TextAttributes.BOLD}>{"TH"}</Text>
689
+ </Box>
690
+ <Box height={1} />
691
+ <Box height={1}>
692
+ <Text fg={colors.positive}>{" \u2588\u2588 "}</Text>
693
+ <Text fg={colors.negative}>{" \u2588\u2588 "}</Text>
694
+ <Text fg={colors.text}>{" \u2588\u2588 "}</Text>
695
+ <Text fg={colors.textBright}>{" \u2588\u2588 "}</Text>
696
+ <Text fg={colors.borderFocused}>{" \u2588\u2588 "}</Text>
697
+ <Text fg={colors.textDim}>{" \u2588\u2588 "}</Text>
698
+ </Box>
699
+ <Box height={1} />
759
700
 
760
701
  {windowStart > 0 && (
761
- <box height={1}>
762
- <text fg={colors.textMuted}>{"\u2191 more"}</text>
763
- </box>
702
+ <Box height={1}>
703
+ <Text fg={colors.textMuted}>{"\u2191 more"}</Text>
704
+ </Box>
764
705
  )}
765
706
 
766
707
  {themeIds.slice(windowStart, windowEnd).map((id, i) => {
@@ -768,28 +709,28 @@ function ThemeStep({ themeIds, selectedIdx, height }: { themeIds: string[]; sele
768
709
  const globalIdx = windowStart + i;
769
710
  const isSel = globalIdx === selectedIdx;
770
711
  return (
771
- <box key={id} height={1} backgroundColor={isSel ? colors.selected : colors.bg}>
772
- <text fg={isSel ? colors.selectedText : colors.textDim}>
712
+ <Box key={id} height={1} backgroundColor={isSel ? colors.selected : colors.bg}>
713
+ <Text fg={isSel ? colors.selectedText : colors.textDim}>
773
714
  {isSel ? "\u25b8 " : " "}
774
- </text>
775
- <text fg={isSel ? colors.text : colors.textDim} attributes={isSel ? TextAttributes.BOLD : 0}>
715
+ </Text>
716
+ <Text fg={isSel ? colors.text : colors.textDim} attributes={isSel ? TextAttributes.BOLD : 0}>
776
717
  {theme.name}
777
- </text>
778
- </box>
718
+ </Text>
719
+ </Box>
779
720
  );
780
721
  })}
781
722
 
782
723
  {windowEnd < themeIds.length && (
783
- <box height={1}>
784
- <text fg={colors.textMuted}>{"\u2193 more"}</text>
785
- </box>
724
+ <Box height={1}>
725
+ <Text fg={colors.textMuted}>{"\u2193 more"}</Text>
726
+ </Box>
786
727
  )}
787
728
 
788
- <box height={1} />
789
- <box height={1}>
790
- <text fg={colors.textMuted}>{"Use \u2191\u2193 to browse"}</text>
791
- </box>
792
- </box>
729
+ <Box height={1} />
730
+ <Box height={1}>
731
+ <Text fg={colors.textMuted}>{"Use \u2191\u2193 to browse"}</Text>
732
+ </Box>
733
+ </Box>
793
734
  );
794
735
  }
795
736
 
@@ -797,6 +738,7 @@ function PortfolioStep({
797
738
  sub,
798
739
  choices,
799
740
  optionIdx,
741
+ onOptionSelect,
800
742
  portfolioName,
801
743
  onNameChange,
802
744
  selectedBrokerId,
@@ -811,8 +753,9 @@ function PortfolioStep({
811
753
  brokerSyncError,
812
754
  }: {
813
755
  sub: PortfolioSub;
814
- choices: Array<{ id: string; label: string; desc: string }>;
756
+ choices: ListViewItem[];
815
757
  optionIdx: number;
758
+ onOptionSelect: (idx: number) => void;
816
759
  portfolioName: string;
817
760
  onNameChange: (n: string) => void;
818
761
  selectedBrokerId: string | null;
@@ -828,62 +771,50 @@ function PortfolioStep({
828
771
  }) {
829
772
  if (sub === "choose") {
830
773
  return (
831
- <box flexDirection="column" paddingX={2}>
832
- <box height={1}>
833
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Set up a portfolio"}</text>
834
- </box>
835
- <box height={1} />
836
- <box height={1}>
837
- <text fg={colors.textDim}>{"How would you like to get started?"}</text>
838
- </box>
839
- <box height={2} />
840
-
841
- {choices.map((opt, i) => {
842
- const isSel = i === optionIdx;
843
- return (
844
- <box key={opt.id} height={1} backgroundColor={isSel ? colors.selected : colors.bg}>
845
- <text fg={isSel ? colors.selectedText : colors.textDim}>
846
- {isSel ? "\u25b8 " : " "}
847
- </text>
848
- <text fg={isSel ? colors.text : colors.textDim} attributes={isSel ? TextAttributes.BOLD : 0}>
849
- {opt.label}
850
- </text>
851
- </box>
852
- );
853
- })}
854
-
855
- <box height={1} />
856
- <box height={1}>
857
- <text fg={colors.textDim}>{choices[optionIdx]?.desc}</text>
858
- </box>
859
-
860
- <box height={1} />
861
- <box height={1}>
862
- <text fg={colors.textDim}>{"Edits later from the command bar."}</text>
863
- </box>
864
- <box height={1} />
865
- <box height={1}>
866
- <text fg={colors.textMuted}>{"Use \u2191\u2193 to choose"}</text>
867
- </box>
868
- </box>
774
+ <Box flexDirection="column" paddingX={2}>
775
+ <Box height={1}>
776
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Set up a portfolio"}</Text>
777
+ </Box>
778
+ <Box height={1} />
779
+ <Box height={1}>
780
+ <Text fg={colors.textDim}>{"How would you like to get started?"}</Text>
781
+ </Box>
782
+ <Box height={2} />
783
+
784
+ <ListView
785
+ items={choices}
786
+ selectedIndex={optionIdx}
787
+ onSelect={onOptionSelect}
788
+ showSelectedDescription
789
+ />
790
+
791
+ <Box height={1} />
792
+ <Box height={1}>
793
+ <Text fg={colors.textDim}>{"Edits later from the command bar."}</Text>
794
+ </Box>
795
+ <Box height={1} />
796
+ <Box height={1}>
797
+ <Text fg={colors.textMuted}>{"Use \u2191\u2193 to choose"}</Text>
798
+ </Box>
799
+ </Box>
869
800
  );
870
801
  }
871
802
 
872
803
  if (sub === "manual-name") {
873
804
  return (
874
- <box flexDirection="column" paddingX={2}>
875
- <box height={1}>
876
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Name your portfolio"}</text>
877
- </box>
878
- <box height={1} />
879
- <box height={1}>
880
- <text fg={colors.textDim}>{"Create watchlists later."}</text>
881
- </box>
882
- <box height={2} />
883
- <box height={1}>
884
- <text fg={colors.text}>{"Portfolio name:"}</text>
885
- </box>
886
- <box height={1}>
805
+ <Box flexDirection="column" paddingX={2}>
806
+ <Box height={1}>
807
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Name your portfolio"}</Text>
808
+ </Box>
809
+ <Box height={1} />
810
+ <Box height={1}>
811
+ <Text fg={colors.textDim}>{"Create watchlists later."}</Text>
812
+ </Box>
813
+ <Box height={2} />
814
+ <Box height={1}>
815
+ <Text fg={colors.text}>{"Portfolio name:"}</Text>
816
+ </Box>
817
+ <Box height={1}>
887
818
  {editing ? (
888
819
  <TextField
889
820
  inputRef={inputRef}
@@ -897,23 +828,23 @@ function PortfolioStep({
897
828
  onSubmit={() => {}}
898
829
  />
899
830
  ) : (
900
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
831
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
901
832
  {`> ${portfolioName}`}
902
- </text>
833
+ </Text>
903
834
  )}
904
- </box>
905
- <box height={2} />
906
- <box height={1} flexDirection="row">
907
- <text fg={colors.textDim}>{"After setup, use the command bar ("}</text>
908
- <text fg={colors.text}>{"Ctrl+P"}</text>
909
- <text fg={colors.textDim}>{") and"}</text>
910
- </box>
911
- <box height={1} flexDirection="row">
912
- <text fg={colors.textDim}>{"type "}</text>
913
- <text fg={colors.text} attributes={TextAttributes.BOLD}>{"T AAPL"}</text>
914
- <text fg={colors.textDim}>{" to search and add any stock or ETF."}</text>
915
- </box>
916
- </box>
835
+ </Box>
836
+ <Box height={2} />
837
+ <Box height={1} flexDirection="row">
838
+ <Text fg={colors.textDim}>{"After setup, use the command bar ("}</Text>
839
+ <Text fg={colors.text}>{"Ctrl+P"}</Text>
840
+ <Text fg={colors.textDim}>{") and"}</Text>
841
+ </Box>
842
+ <Box height={1} flexDirection="row">
843
+ <Text fg={colors.textDim}>{"type "}</Text>
844
+ <Text fg={colors.text} attributes={TextAttributes.BOLD}>{"DES AAPL"}</Text>
845
+ <Text fg={colors.textDim}>{" to open security details for a stock or ETF."}</Text>
846
+ </Box>
847
+ </Box>
917
848
  );
918
849
  }
919
850
 
@@ -923,110 +854,110 @@ function PortfolioStep({
923
854
  const isGateway = connectionMode === "gateway";
924
855
 
925
856
  return (
926
- <box flexDirection="column" paddingX={2}>
927
- <box height={1}>
928
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
857
+ <Box flexDirection="column" paddingX={2}>
858
+ <Box height={1}>
859
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
929
860
  {`Setup Guide — ${brokerLabel}`}
930
- </text>
931
- </box>
932
- <box height={1} />
861
+ </Text>
862
+ </Box>
863
+ <Box height={1} />
933
864
 
934
865
  {selectedBrokerId === "ibkr" && !isGateway && (
935
866
  <>
936
- <box height={1}>
937
- <text fg={colors.textDim}>{"You'll need 2 things from IBKR Account Management:"}</text>
938
- </box>
939
- <box height={2} />
940
- <box height={1}>
941
- <text fg={colors.textDim}>{"1. Go to "}<u><span fg={colors.text}>{"Reports > Flex Queries"}</span></u></text>
942
- </box>
943
- <box height={1}>
944
- <text fg={colors.textDim}>{"2. Create a Flex Query that includes positions data"}</text>
945
- </box>
946
- <box height={1}>
947
- <text fg={colors.textDim}>{"3. Note the "}<strong><span fg={colors.text}>{"Query ID"}</span></strong>{" (numeric)"}</text>
948
- </box>
949
- <box height={1}>
950
- <text fg={colors.textDim}>{"4. Under "}<u><span fg={colors.text}>{"Reports > Settings"}</span></u>{", generate a "}<strong><span fg={colors.text}>{"Flex Web Service Token"}</span></strong></text>
951
- </box>
952
- <box height={2} />
867
+ <Box height={1}>
868
+ <Text fg={colors.textDim}>{"You'll need 2 things from IBKR Account Management:"}</Text>
869
+ </Box>
870
+ <Box height={2} />
871
+ <Box height={1}>
872
+ <Text fg={colors.textDim}>{"1. Go to "}<Underline><Span fg={colors.text}>{"Reports > Flex Queries"}</Span></Underline></Text>
873
+ </Box>
874
+ <Box height={1}>
875
+ <Text fg={colors.textDim}>{"2. Create a Flex Query that includes positions data"}</Text>
876
+ </Box>
877
+ <Box height={1}>
878
+ <Text fg={colors.textDim}>{"3. Note the "}<Strong><Span fg={colors.text}>{"Query ID"}</Span></Strong>{" (numeric)"}</Text>
879
+ </Box>
880
+ <Box height={1}>
881
+ <Text fg={colors.textDim}>{"4. Under "}<Underline><Span fg={colors.text}>{"Reports > Settings"}</Span></Underline>{", generate a "}<Strong><Span fg={colors.text}>{"Flex Web Service Token"}</Span></Strong></Text>
882
+ </Box>
883
+ <Box height={2} />
953
884
  <ExternalLink url="https://www.ibkrguides.com/orgportal/performanceandstatements/flex.htm" />
954
885
  </>
955
886
  )}
956
887
 
957
888
  {selectedBrokerId === "ibkr" && isGateway && (
958
889
  <>
959
- <box height={1}>
960
- <text fg={colors.textDim}>{"You'll need IB Gateway or TWS running locally:"}</text>
961
- </box>
962
- <box height={2} />
963
- <box height={1}>
964
- <text fg={colors.textDim}>{"1. Download and install "}<strong><span fg={colors.text}>{"IB Gateway"}</span></strong>{" (or use TWS)"}</text>
965
- </box>
966
- <box height={1}>
967
- <text fg={colors.textDim}>{"2. Log in with your IBKR credentials"}</text>
968
- </box>
969
- <box height={1}>
970
- <text fg={colors.textDim}>{"3. In "}<u><span fg={colors.text}>{"Configuration > API > Settings"}</span></u>{":"}</text>
971
- </box>
972
- <box height={1}>
973
- <text fg={colors.textDim}>{" Enable \"ActiveX and Socket Clients\""}</text>
974
- </box>
975
- <box height={1}>
976
- <text fg={colors.textDim}>{" Gloomberb can auto-detect local API ports (4001, 4002, 7496, 7497)"}</text>
977
- </box>
978
- <box height={1}>
979
- <text fg={colors.textDim}>{" Use Manual setup only if you need a custom host or exact socket port"}</text>
980
- </box>
981
- <box height={1}>
982
- <text fg={colors.textDim}>{"4. Keep it running while using Gloomberb"}</text>
983
- </box>
984
- <box height={2} />
890
+ <Box height={1}>
891
+ <Text fg={colors.textDim}>{"You'll need IB Gateway or TWS running locally:"}</Text>
892
+ </Box>
893
+ <Box height={2} />
894
+ <Box height={1}>
895
+ <Text fg={colors.textDim}>{"1. Download and install "}<Strong><Span fg={colors.text}>{"IB Gateway"}</Span></Strong>{" (or use TWS)"}</Text>
896
+ </Box>
897
+ <Box height={1}>
898
+ <Text fg={colors.textDim}>{"2. Log in with your IBKR credentials"}</Text>
899
+ </Box>
900
+ <Box height={1}>
901
+ <Text fg={colors.textDim}>{"3. In "}<Underline><Span fg={colors.text}>{"Configuration > API > Settings"}</Span></Underline>{":"}</Text>
902
+ </Box>
903
+ <Box height={1}>
904
+ <Text fg={colors.textDim}>{" Enable \"ActiveX and Socket Clients\""}</Text>
905
+ </Box>
906
+ <Box height={1}>
907
+ <Text fg={colors.textDim}>{" Gloomberb can auto-detect local API ports (4001, 4002, 7496, 7497)"}</Text>
908
+ </Box>
909
+ <Box height={1}>
910
+ <Text fg={colors.textDim}>{" Use Manual setup only if you need a custom host or exact socket port"}</Text>
911
+ </Box>
912
+ <Box height={1}>
913
+ <Text fg={colors.textDim}>{"4. Keep it running while using Gloomberb"}</Text>
914
+ </Box>
915
+ <Box height={2} />
985
916
  <ExternalLink url="https://www.interactivebrokers.com/en/trading/ibgateway-stable.php" />
986
917
  </>
987
918
  )}
988
919
 
989
920
  {selectedBrokerId !== "ibkr" && (
990
921
  <>
991
- <box height={1}>
992
- <text fg={colors.textDim}>{`You'll need your ${brokerLabel} API credentials.`}</text>
993
- </box>
994
- <box height={1}>
995
- <text fg={colors.textDim}>{"Check your broker's documentation for setup instructions."}</text>
996
- </box>
922
+ <Box height={1}>
923
+ <Text fg={colors.textDim}>{`You'll need your ${brokerLabel} API credentials.`}</Text>
924
+ </Box>
925
+ <Box height={1}>
926
+ <Text fg={colors.textDim}>{"Check your broker's documentation for setup instructions."}</Text>
927
+ </Box>
997
928
  </>
998
929
  )}
999
930
 
1000
- <box height={2} />
931
+ <Box height={2} />
1001
932
 
1002
- </box>
933
+ </Box>
1003
934
  );
1004
935
  }
1005
936
 
1006
937
  if (sub === "broker-sync" && selectedBrokerId) {
1007
938
  const brokerLabel = choices.find((choice) => choice.id === selectedBrokerId)?.label.replace("Connect ", "") ?? selectedBrokerId;
1008
939
  return (
1009
- <box flexDirection="column" paddingX={2}>
1010
- <box height={1}>
1011
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{`Connect ${brokerLabel}`}</text>
1012
- </box>
1013
- <box height={1} />
1014
- <box height={1}>
1015
- <text fg={brokerSyncing ? colors.text : colors.negative}>
940
+ <Box flexDirection="column" paddingX={2}>
941
+ <Box height={1}>
942
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{`Connect ${brokerLabel}`}</Text>
943
+ </Box>
944
+ <Box height={1} />
945
+ <Box height={1}>
946
+ <Text fg={brokerSyncing ? colors.text : colors.negative}>
1016
947
  {brokerSyncing
1017
948
  ? `Connecting to ${brokerLabel} and importing accounts and positions...`
1018
949
  : brokerSyncError || `Unable to sync ${brokerLabel}.`}
1019
- </text>
1020
- </box>
1021
- <box height={2} />
1022
- <box height={1}>
1023
- <text fg={colors.textDim}>
950
+ </Text>
951
+ </Box>
952
+ <Box height={2} />
953
+ <Box height={1}>
954
+ <Text fg={colors.textDim}>
1024
955
  {brokerSyncing
1025
956
  ? "This happens now so your portfolio is ready before onboarding finishes."
1026
957
  : "Press Enter to retry, or Backspace to edit the broker settings."}
1027
- </text>
1028
- </box>
1029
- </box>
958
+ </Text>
959
+ </Box>
960
+ </Box>
1030
961
  );
1031
962
  }
1032
963
 
@@ -1036,13 +967,13 @@ function PortfolioStep({
1036
967
  const values = brokerValues[selectedBrokerId] ?? {};
1037
968
 
1038
969
  return (
1039
- <box flexDirection="column" paddingX={2}>
1040
- <box height={1}>
1041
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
970
+ <Box flexDirection="column" paddingX={2}>
971
+ <Box height={1}>
972
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
1042
973
  {"Connect "}{choices.find((c) => c.id === selectedBrokerId)?.label.replace("Connect ", "") ?? selectedBrokerId}
1043
- </text>
1044
- </box>
1045
- <box height={1} />
974
+ </Text>
975
+ </Box>
976
+ <Box height={1} />
1046
977
 
1047
978
  {/* Show completed fields */}
1048
979
  {brokerFields.map((field, i) => {
@@ -1055,10 +986,10 @@ function PortfolioStep({
1055
986
  ? field.options?.find((option) => option.value === val)?.label ?? val
1056
987
  : field.type === "password" ? PASSWORD_MASK_CHAR.repeat(val.length) : val;
1057
988
  return (
1058
- <box key={field.key} height={1}>
1059
- <text fg={colors.positive}>{"\u2713 "}</text>
1060
- <text fg={colors.text}>{`${field.label}: ${selectedLabel}`}</text>
1061
- </box>
989
+ <Box key={field.key} height={1}>
990
+ <Text fg={colors.positive}>{"\u2713 "}</Text>
991
+ <Text fg={colors.text}>{`${field.label}: ${selectedLabel}`}</Text>
992
+ </Box>
1062
993
  );
1063
994
  }
1064
995
 
@@ -1066,15 +997,15 @@ function PortfolioStep({
1066
997
  const activeSelectValue = field.options?.[brokerSelectIdx]?.value ?? values[field.key] ?? "";
1067
998
  const effectiveValue = val || field.defaultValue || "";
1068
999
  return (
1069
- <box key={field.key} flexDirection="column">
1070
- {i > 0 && <box height={1} />}
1071
- <box height={1}>
1072
- <text fg={colors.text} attributes={TextAttributes.BOLD}>
1000
+ <Box key={field.key} flexDirection="column">
1001
+ {i > 0 && <Box height={1} />}
1002
+ <Box height={1}>
1003
+ <Text fg={colors.text} attributes={TextAttributes.BOLD}>
1073
1004
  {`Step ${i + 1}: `}
1074
- </text>
1075
- <text fg={colors.text}>{field.label}</text>
1076
- </box>
1077
- <box height={1}>
1005
+ </Text>
1006
+ <Text fg={colors.text}>{field.label}</Text>
1007
+ </Box>
1008
+ <Box height={1}>
1078
1009
  {field.type !== "select" && (editing ? (
1079
1010
  <TextField
1080
1011
  inputRef={inputRef}
@@ -1089,142 +1020,87 @@ function PortfolioStep({
1089
1020
  onSubmit={() => {}}
1090
1021
  />
1091
1022
  ) : (
1092
- <text fg={effectiveValue ? colors.positive : colors.textMuted}>
1023
+ <Text fg={effectiveValue ? colors.positive : colors.textMuted}>
1093
1024
  {effectiveValue
1094
1025
  ? `\u2713 ${field.label}: ${field.type === "password" ? PASSWORD_MASK_CHAR.repeat(effectiveValue.length) : effectiveValue}`
1095
1026
  : "Press enter to type..."}
1096
- </text>
1027
+ </Text>
1097
1028
  ))}
1098
- </box>
1029
+ </Box>
1099
1030
  {field.type !== "select" && !val && field.defaultValue && (
1100
- <box height={1}>
1101
- <text fg={colors.textMuted}>{`Press enter to use ${field.defaultValue}`}</text>
1102
- </box>
1031
+ <Box height={1}>
1032
+ <Text fg={colors.textMuted}>{`Press enter to use ${field.defaultValue}`}</Text>
1033
+ </Box>
1103
1034
  )}
1104
1035
  {field.type === "select" && (
1105
- <box flexDirection="column">
1036
+ <Box flexDirection="column">
1106
1037
  {(field.options ?? []).map((option, optionIdx) => {
1107
1038
  const selected = optionIdx === brokerSelectIdx;
1108
1039
  return (
1109
- <box key={option.value} flexDirection="column" backgroundColor={selected ? colors.selected : colors.bg}>
1110
- <box height={1}>
1111
- <text fg={selected ? colors.selectedText : colors.textDim}>{selected ? "\u25b8 " : " "}</text>
1112
- <text
1040
+ <Box key={option.value} flexDirection="column" backgroundColor={selected ? colors.selected : colors.bg}>
1041
+ <Box height={1}>
1042
+ <Text fg={selected ? colors.selectedText : colors.textDim}>{selected ? "\u25b8 " : " "}</Text>
1043
+ <Text
1113
1044
  fg={selected ? colors.text : colors.textDim}
1114
1045
  attributes={selected ? TextAttributes.BOLD : 0}
1115
1046
  >
1116
1047
  {option.label}
1117
- </text>
1118
- </box>
1048
+ </Text>
1049
+ </Box>
1119
1050
  {option.description && (
1120
- <box height={1}>
1121
- <text fg={colors.textMuted}>{` ${option.description}`}</text>
1122
- </box>
1051
+ <Box height={1}>
1052
+ <Text fg={colors.textMuted}>{` ${option.description}`}</Text>
1053
+ </Box>
1123
1054
  )}
1124
- </box>
1055
+ </Box>
1125
1056
  );
1126
1057
  })}
1127
- </box>
1058
+ </Box>
1128
1059
  )}
1129
1060
  {field.type === "select" && (
1130
- <box height={1}>
1131
- <text fg={colors.textMuted}>
1061
+ <Box height={1}>
1062
+ <Text fg={colors.textMuted}>
1132
1063
  {activeSelectValue ? `Selected: ${field.options?.find((option) => option.value === activeSelectValue)?.label ?? activeSelectValue}` : "Use \u2191\u2193 to choose"}
1133
- </text>
1134
- </box>
1064
+ </Text>
1065
+ </Box>
1135
1066
  )}
1136
- </box>
1067
+ </Box>
1137
1068
  );
1138
1069
  }
1139
1070
 
1140
1071
  return null;
1141
1072
  })}
1142
1073
 
1143
- <box height={2} />
1144
- <box height={1}>
1145
- <text fg={colors.textDim}>{"Credentials are saved locally."}</text>
1146
- </box>
1147
- <box height={1} />
1148
- <box height={1}>
1149
- <text fg={colors.textMuted}>
1074
+ <Box height={2} />
1075
+ <Box height={1}>
1076
+ <Text fg={colors.textDim}>{"Credentials are saved locally."}</Text>
1077
+ </Box>
1078
+ <Box height={1} />
1079
+ <Box height={1}>
1080
+ <Text fg={colors.textMuted}>
1150
1081
  {`Field ${brokerFieldIdx + 1} of ${brokerFields.length}`}
1151
- </text>
1152
- </box>
1153
- </box>
1154
- );
1155
- }
1156
-
1157
- function PluginsStep({
1158
- plugins,
1159
- disabledPlugins,
1160
- selectedIdx,
1161
- onToggle,
1162
- onSelect,
1163
- }: {
1164
- plugins: { id: string; name: string; description: string }[];
1165
- disabledPlugins: string[];
1166
- selectedIdx: number;
1167
- onToggle: (id: string) => void;
1168
- onSelect: (idx: number) => void;
1169
- }) {
1170
- const items: ToggleListItem[] = plugins.map((p) => ({
1171
- id: p.id,
1172
- label: p.name,
1173
- enabled: !disabledPlugins.includes(p.id),
1174
- description: p.description,
1175
- }));
1176
-
1177
- return (
1178
- <box flexDirection="column" paddingX={2}>
1179
- <box height={1}>
1180
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Select plugins to enable"}</text>
1181
- </box>
1182
-
1183
- <box height={2} />
1184
-
1185
- <ToggleList
1186
- items={items}
1187
- selectedIdx={selectedIdx}
1188
- onToggle={onToggle}
1189
- onSelect={onSelect}
1190
- />
1191
-
1192
- <box height={1} />
1193
- <box height={1}>
1194
- <text fg={colors.textMuted}>{"Use \u2191\u2193 to navigate \u00b7 space to toggle"}</text>
1195
- </box>
1196
- <box height={1} />
1197
- <box height={1}>
1198
- <text fg={colors.textDim}>{"Toggle plugins anytime from the command bar"}</text>
1199
- </box>
1200
- <box height={1} flexDirection="row">
1201
- <text fg={colors.textDim}>{"with the "}</text>
1202
- <text fg={colors.text} attributes={TextAttributes.BOLD}>{"PL"}</text>
1203
- <text fg={colors.textDim}>{" prefix."}</text>
1204
- </box>
1205
- </box>
1082
+ </Text>
1083
+ </Box>
1084
+ </Box>
1206
1085
  );
1207
1086
  }
1208
1087
 
1209
- function ShortcutsStep({
1210
- pluginRegistry,
1211
- disabledPlugins,
1212
- }: {
1213
- pluginRegistry: PluginRegistry;
1214
- disabledPlugins: string[];
1215
- }) {
1088
+ function ShortcutsStep({ pluginRegistry }: { pluginRegistry: PluginRegistry }) {
1089
+ const uiHost = useUiHost();
1090
+ const shortcutPlatform = detectShortcutPlatform();
1091
+ const shortcutDisplayMode = getShortcutDisplayMode(uiHost.kind);
1092
+ const platformShortcut = (keys: string | readonly string[]) => formatPrimaryShortcut(keys, shortcutPlatform, shortcutDisplayMode);
1216
1093
  const keyboardShortcuts = [
1217
1094
  { key: "Ctrl+P / `", desc: "Open the command bar" },
1218
1095
  { key: "Tab", desc: "Switch between panels" },
1219
- { key: "Ctrl+W", desc: "Close the current window" },
1096
+ { key: platformShortcut("W"), desc: "Close the focused pane" },
1097
+ { key: platformShortcut(["Shift", "D"]), desc: "Dock or float the focused pane" },
1220
1098
  { key: "q", desc: "Quit" },
1221
1099
  ];
1222
1100
 
1223
- const disabledSet = useMemo(() => new Set(disabledPlugins), [disabledPlugins]);
1224
-
1225
1101
  const commandPrefixes = useMemo(() => {
1226
1102
  const builtIn = [
1227
- { key: "T AAPL", desc: "Search and add any ticker" },
1103
+ { key: "DES AAPL", desc: "Open security details" },
1228
1104
  { key: "TH", desc: "Switch theme" },
1229
1105
  { key: "PL", desc: "Toggle plugins" },
1230
1106
  { key: "PS", desc: "Edit the current window settings" },
@@ -1236,8 +1112,6 @@ function ShortcutsStep({
1236
1112
 
1237
1113
  for (const [, template] of pluginRegistry.paneTemplates) {
1238
1114
  if (!template.shortcut) continue;
1239
- const pluginId = pluginRegistry.getPaneTemplatePluginId(template.id);
1240
- if (pluginId && disabledSet.has(pluginId)) continue;
1241
1115
  if (builtInKeys.has(template.shortcut.prefix)) continue;
1242
1116
  const label = template.shortcut.argPlaceholder
1243
1117
  ? `${template.shortcut.prefix} <${template.shortcut.argPlaceholder}>`
@@ -1247,46 +1121,46 @@ function ShortcutsStep({
1247
1121
 
1248
1122
  pluginPrefixes.sort((a, b) => a.key.localeCompare(b.key));
1249
1123
  return [...builtIn, ...pluginPrefixes];
1250
- }, [pluginRegistry, disabledSet]);
1124
+ }, [pluginRegistry]);
1251
1125
 
1252
- const COL = 14;
1126
+ const COL = 20;
1253
1127
 
1254
1128
  return (
1255
- <box flexDirection="column" paddingX={2}>
1256
- <box height={1}>
1257
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Useful shortcuts"}</text>
1258
- </box>
1259
- <box height={1} />
1129
+ <Box flexDirection="column" paddingX={2}>
1130
+ <Box height={1}>
1131
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"After launch shortcuts"}</Text>
1132
+ </Box>
1133
+ <Box height={1} />
1260
1134
 
1261
1135
  {keyboardShortcuts.map((s) => (
1262
- <box key={s.key} height={1} flexDirection="row">
1263
- <text fg={colors.text} attributes={TextAttributes.BOLD}>{s.key.padEnd(COL)}</text>
1264
- <text fg={colors.textDim}>{s.desc}</text>
1265
- </box>
1136
+ <Box key={s.key} height={1} flexDirection="row">
1137
+ <Text fg={colors.text} attributes={TextAttributes.BOLD}>{s.key.padEnd(COL)}</Text>
1138
+ <Text fg={colors.textDim}>{s.desc}</Text>
1139
+ </Box>
1266
1140
  ))}
1267
1141
 
1268
- <box height={2} />
1269
- <box height={1}>
1270
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Command-bar prefixes"}</text>
1271
- </box>
1272
- <box height={1} />
1273
- <box height={1}>
1274
- <text fg={colors.textDim}>{"Type these in the command bar ("}<span fg={colors.text}><strong>{"Ctrl+P"}</strong></span>{" or "}<span fg={colors.text}><strong>{"`"}</strong></span>{"):"}</text>
1275
- </box>
1276
- <box height={1} />
1142
+ <Box height={2} />
1143
+ <Box height={1}>
1144
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"Command-bar prefixes"}</Text>
1145
+ </Box>
1146
+ <Box height={1} />
1147
+ <Box height={1}>
1148
+ <Text fg={colors.textDim}>{"Type these in the command bar ("}<Span fg={colors.text}><Strong>{"Ctrl+P"}</Strong></Span>{" or "}<Span fg={colors.text}><Strong>{"`"}</Strong></Span>{"):"}</Text>
1149
+ </Box>
1150
+ <Box height={1} />
1277
1151
 
1278
1152
  {commandPrefixes.map((s) => (
1279
- <box key={s.key} height={1} flexDirection="row">
1280
- <text fg={colors.text} attributes={TextAttributes.BOLD}>{s.key.padEnd(COL)}</text>
1281
- <text fg={colors.textDim}>{s.desc}</text>
1282
- </box>
1153
+ <Box key={s.key} height={1} flexDirection="row">
1154
+ <Text fg={colors.text} attributes={TextAttributes.BOLD}>{s.key.padEnd(COL)}</Text>
1155
+ <Text fg={colors.textDim}>{s.desc}</Text>
1156
+ </Box>
1283
1157
  ))}
1284
1158
 
1285
- <box height={2} />
1286
- <box height={1}>
1287
- <text fg={colors.textDim}>{"Everything is searchable, just type what you want."}</text>
1288
- </box>
1289
- </box>
1159
+ <Box height={2} />
1160
+ <Box height={1}>
1161
+ <Text fg={colors.textDim}>{"Everything is searchable, just type what you want."}</Text>
1162
+ </Box>
1163
+ </Box>
1290
1164
  );
1291
1165
  }
1292
1166
 
@@ -1307,58 +1181,58 @@ function ReadyStep({
1307
1181
  const positionLabel = positionsImported === 1 ? "position" : "positions";
1308
1182
 
1309
1183
  return (
1310
- <box flexDirection="column" paddingX={2}>
1311
- <box height={2} />
1312
- <box height={1}>
1313
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"You're all set"}</text>
1314
- </box>
1315
- <box height={2} />
1316
- <box height={1}>
1317
- <text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</text>
1318
- <text fg={colors.text}>{" Theme configured"}</text>
1319
- </box>
1320
- <box height={1}>
1321
- <text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</text>
1322
- <text fg={colors.text}>
1184
+ <Box flexDirection="column" paddingX={2}>
1185
+ <Box height={2} />
1186
+ <Box height={1}>
1187
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{"You're all set"}</Text>
1188
+ </Box>
1189
+ <Box height={2} />
1190
+ <Box height={1}>
1191
+ <Text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</Text>
1192
+ <Text fg={colors.text}>{" Theme configured"}</Text>
1193
+ </Box>
1194
+ <Box height={1}>
1195
+ <Text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</Text>
1196
+ <Text fg={colors.text}>
1323
1197
  {brokerName
1324
1198
  ? ` ${brokerName} connected. Imported ${positionsImported} ${positionLabel}`
1325
1199
  : ` Portfolio "${portfolioName}" created`}
1326
- </text>
1327
- </box>
1328
- <box height={1}>
1329
- <text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</text>
1330
- <text fg={colors.text}>{" Plugins selected"}</text>
1331
- </box>
1332
- <box height={2} />
1200
+ </Text>
1201
+ </Box>
1202
+ <Box height={1}>
1203
+ <Text fg={colors.positive} attributes={TextAttributes.BOLD}>{"\u2713"}</Text>
1204
+ <Text fg={colors.text}>{" Plugins enabled"}</Text>
1205
+ </Box>
1206
+ <Box height={2} />
1333
1207
  {brokerName ? (
1334
- <box height={1}>
1335
- <text fg={isFinishing ? colors.text : colors.textDim}>
1208
+ <Box height={1}>
1209
+ <Text fg={isFinishing ? colors.text : colors.textDim}>
1336
1210
  {isFinishing
1337
1211
  ? "Launching Gloomberb..."
1338
1212
  : positionsImported > 0
1339
1213
  ? "Your broker portfolio is ready and will open directly after launch."
1340
1214
  : "Broker sync finished. If you expected holdings, check the selected account or connection mode."}
1341
- </text>
1342
- </box>
1215
+ </Text>
1216
+ </Box>
1343
1217
  ) : (
1344
- <box height={1}>
1345
- <text fg={colors.textDim}>{"Search for broker names in the command bar to connect."}</text>
1346
- </box>
1218
+ <Box height={1}>
1219
+ <Text fg={colors.textDim}>{"Search for broker names in the command bar to connect."}</Text>
1220
+ </Box>
1347
1221
  )}
1348
1222
  {error && (
1349
1223
  <>
1350
- <box height={1} />
1351
- <box height={2}>
1352
- <text fg={colors.negative}>{error}</text>
1353
- </box>
1224
+ <Box height={1} />
1225
+ <Box height={2}>
1226
+ <Text fg={colors.negative}>{error}</Text>
1227
+ </Box>
1354
1228
  </>
1355
1229
  )}
1356
- <box height={2} />
1357
- <box height={1} flexDirection="row">
1358
- <text fg={colors.textDim}>{"Data stored in "}</text>
1359
- <text fg={colors.text}>{"~/gloomberb/"}</text>
1360
- </box>
1361
- <box height={1} />
1362
- </box>
1230
+ <Box height={2} />
1231
+ <Box height={1} flexDirection="row">
1232
+ <Text fg={colors.textDim}>{"Data stored in "}</Text>
1233
+ <Text fg={colors.text}>{"~/gloomberb/"}</Text>
1234
+ </Box>
1235
+ <Box height={1} />
1236
+ </Box>
1363
1237
  );
1364
1238
  }