gloomberb 0.5.0 → 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 -13
  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 +13 -4
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +56 -8
  87. package/src/components/layout/shell.test.tsx +322 -19
  88. package/src/components/layout/shell.tsx +1010 -366
  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 +150 -0
  180. package/src/plugins/builtin/chat-controller.ts +88 -16
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +373 -172
  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 +186 -6
  440. package/src/utils/api-client.ts +433 -33
  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
@@ -10,10 +10,10 @@ export interface CommandContext {
10
10
 
11
11
  export interface Command {
12
12
  id: string;
13
- prefix: string; // e.g., "T", "AW", "RP"
13
+ prefix: string; // e.g., "DES", "AW", "RP"
14
14
  label: string;
15
15
  description: string;
16
- hasArg?: boolean; // true if prefix takes an argument (e.g., "T AMD")
16
+ hasArg?: boolean; // true if prefix takes an argument (e.g., "DES AMD")
17
17
  argPlaceholder?: string;
18
18
  shortcut?: string;
19
19
  category: string;
@@ -21,14 +21,14 @@ export interface Command {
21
21
  }
22
22
 
23
23
  export const commands: Command[] = [
24
- // Ticker search
24
+ // Security description
25
25
  {
26
- id: "search-ticker",
27
- prefix: "T",
28
- label: "Search Ticker",
29
- description: "Search Yahoo Finance for a ticker",
26
+ id: "security-description",
27
+ prefix: "DES",
28
+ label: "Description",
29
+ description: "Open security details for a ticker",
30
30
  hasArg: true,
31
- argPlaceholder: "symbol or name",
31
+ argPlaceholder: "ticker",
32
32
  category: "Search",
33
33
  execute: () => {}, // handled specially by command bar
34
34
  },
@@ -159,16 +159,6 @@ export const commands: Command[] = [
159
159
  category: "Config",
160
160
  execute: () => {}, // handled by command bar
161
161
  },
162
- {
163
- id: "new-pane",
164
- prefix: "NP",
165
- label: "New Pane",
166
- description: "Add or create a new pane from plugin templates",
167
- hasArg: true,
168
- argPlaceholder: "pane",
169
- category: "Config",
170
- execute: () => {}, // handled by command bar
171
- },
172
162
  {
173
163
  id: "toggle-status-bar",
174
164
  prefix: "SB",
@@ -259,12 +249,12 @@ export function getThemeOptions(): Array<{ id: string; name: string; description
259
249
  }
260
250
 
261
251
  /** Find a command whose prefix matches the start of the input */
262
- export function matchPrefix(input: string): { command: Command; arg: string } | null {
252
+ export function matchPrefix(input: string, commandList: Command[] = commands): { command: Command; arg: string } | null {
263
253
  const upper = input.toUpperCase().trim();
264
254
  if (!upper) return null;
265
255
 
266
256
  // Sort by prefix length descending so longer prefixes match first (e.g., "AW" before "A")
267
- const sorted = [...commands].sort((a, b) => b.prefix.length - a.prefix.length);
257
+ const sorted = [...commandList].sort((a, b) => b.prefix.length - a.prefix.length);
268
258
 
269
259
  for (const cmd of sorted) {
270
260
  if (upper.startsWith(cmd.prefix + " ") || upper === cmd.prefix) {
@@ -212,7 +212,7 @@ describe("command-bar helpers", () => {
212
212
  expect(getScreenFooterLeft(orderedPickerRoute)).toBe("up/down move space toggle [ ] reorder enter done");
213
213
  expect(getScreenFooterRight(orderedPickerRoute)).toBe("backspace/esc back");
214
214
  expect(getScreenFooterRight(filteredPickerRoute)).toBe("backspace delete esc back");
215
- expect(getScreenFooterRight(workflowRoute)).toBe("esc back");
215
+ expect(getScreenFooterRight(workflowRoute)).toBe("backspace/esc back");
216
216
  expect(getScreenFooterRight(confirmRoute)).toBe("backspace/esc back");
217
217
  });
218
218
 
@@ -4,6 +4,7 @@ import type {
4
4
  PaneTemplateDef,
5
5
  WizardStep,
6
6
  } from "../../types/plugin";
7
+
7
8
  export { slugifyName } from "../../utils/slugify";
8
9
  import type {
9
10
  CommandBarFieldOption,
@@ -13,7 +14,7 @@ import type {
13
14
  } from "./workflow-types";
14
15
  import type { CollectionKind, CollectionMembershipAction } from "./workflow-ops";
15
16
 
16
- export type RouteCommandId = "search-ticker" | "theme" | "plugins" | "layout" | "new-pane";
17
+ export type RouteCommandId = "security-description" | "theme" | "plugins" | "layout";
17
18
  export type CollectionCommandId = "add-watchlist" | "add-portfolio" | "remove-watchlist" | "remove-portfolio";
18
19
 
19
20
  export function summarizeError(error: unknown): Record<string, string> {
@@ -28,11 +29,10 @@ export function summarizeError(error: unknown): Record<string, string> {
28
29
  }
29
30
 
30
31
  export function isRouteCommandId(commandId: string): commandId is RouteCommandId {
31
- return commandId === "search-ticker"
32
+ return commandId === "security-description"
32
33
  || commandId === "theme"
33
34
  || commandId === "plugins"
34
- || commandId === "layout"
35
- || commandId === "new-pane";
35
+ || commandId === "layout";
36
36
  }
37
37
 
38
38
  export function normalizeFieldOptions(
@@ -69,7 +69,7 @@ export function coerceFieldValues(value: CommandBarFieldValue | undefined): stri
69
69
  : [];
70
70
  }
71
71
 
72
- export function fieldOptionLabel(field: CommandBarWorkflowField, value: string): string {
72
+ function fieldOptionLabel(field: CommandBarWorkflowField, value: string): string {
73
73
  if (field.type !== "select" && field.type !== "multi-select" && field.type !== "ordered-multi-select") {
74
74
  return value;
75
75
  }
@@ -237,9 +237,9 @@ export function normalizeWizardFields(steps: WizardStep[]): {
237
237
  return { fields, description, initialValues, pendingLabel, successLabel };
238
238
  }
239
239
 
240
- export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-search" | "themes" | "plugins" | "layout" | "new-pane" {
240
+ export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-search" | "themes" | "plugins" | "layout" {
241
241
  switch (commandId) {
242
- case "search-ticker":
242
+ case "security-description":
243
243
  return "ticker-search";
244
244
  case "theme":
245
245
  return "themes";
@@ -247,8 +247,6 @@ export function routeCommandIdToScreen(commandId: RouteCommandId): "ticker-searc
247
247
  return "plugins";
248
248
  case "layout":
249
249
  return "layout";
250
- case "new-pane":
251
- return "new-pane";
252
250
  }
253
251
  }
254
252
 
@@ -327,7 +325,7 @@ export function getScreenFooterLeft(route: CommandBarRoute | null): string {
327
325
  export function getScreenFooterRight(route: CommandBarRoute | null): string {
328
326
  if (!route) return "esc cancel";
329
327
 
330
- if (route.kind === "workflow") return "esc back";
328
+ if (route.kind === "workflow") return "backspace/esc back";
331
329
  if (route.kind === "confirm") return "backspace/esc back";
332
330
 
333
331
  if (
@@ -341,7 +339,7 @@ export function getScreenFooterRight(route: CommandBarRoute | null): string {
341
339
  }
342
340
 
343
341
  export function isRootParsedCommand(commandId: string): boolean {
344
- return commandId === "search-ticker"
342
+ return commandId === "security-description"
345
343
  || commandId === "add-watchlist"
346
344
  || commandId === "add-portfolio"
347
345
  || commandId === "remove-watchlist"
@@ -1,12 +1,8 @@
1
1
  import type { PaneTemplateDef } from "../../types/plugin";
2
2
 
3
- export function formatPaneTemplateLabel(label: string): string {
4
- let normalized = label.trim();
5
- if (normalized.startsWith("New ")) normalized = normalized.slice(4);
6
- if (normalized.endsWith(" Pane")) normalized = normalized.slice(0, -5);
7
- return normalized;
8
- }
9
-
10
3
  export function getPaneTemplateDisplayLabel(template: Pick<PaneTemplateDef, "label">): string {
11
- return formatPaneTemplateLabel(template.label);
4
+ let label = template.label.trim();
5
+ if (label.startsWith("New ")) label = label.slice(4);
6
+ if (label.endsWith(" Pane")) label = label.slice(0, -5);
7
+ return label;
12
8
  }
@@ -2,8 +2,8 @@ import type { CommandDef, PaneTemplateDef } from "../../types/plugin";
2
2
  import type { Command } from "./command-registry";
3
3
  import { getPaneTemplateDisplayLabel } from "./pane-template-display";
4
4
 
5
- export type RootShortcutArgKind = "text" | "ticker" | "ticker-list";
6
- export type ShortcutIntentKind = "none" | "complete" | "inferred-complete" | "partial" | "ambiguous";
5
+ type RootShortcutArgKind = "text" | "ticker" | "ticker-list";
6
+ type ShortcutIntentKind = "none" | "complete" | "inferred-complete" | "partial" | "ambiguous";
7
7
 
8
8
  interface ShortcutIntentBase {
9
9
  kind: Exclude<ShortcutIntentKind, "none">;
@@ -16,22 +16,22 @@ interface ShortcutIntentBase {
16
16
  completionQuery: string | null;
17
17
  }
18
18
 
19
- export interface CommandShortcutIntent extends ShortcutIntentBase {
19
+ interface CommandShortcutIntent extends ShortcutIntentBase {
20
20
  source: "command";
21
21
  command: Command;
22
22
  }
23
23
 
24
- export interface PaneTemplateShortcutIntent extends ShortcutIntentBase {
24
+ interface PaneTemplateShortcutIntent extends ShortcutIntentBase {
25
25
  source: "pane-template";
26
26
  template: PaneTemplateDef;
27
27
  }
28
28
 
29
- export interface PluginCommandShortcutIntent extends ShortcutIntentBase {
29
+ interface PluginCommandShortcutIntent extends ShortcutIntentBase {
30
30
  source: "plugin-command";
31
31
  command: CommandDef;
32
32
  }
33
33
 
34
- export type ShortcutIntent =
34
+ type ShortcutIntent =
35
35
  | { kind: "none" }
36
36
  | CommandShortcutIntent
37
37
  | PaneTemplateShortcutIntent
@@ -60,11 +60,11 @@ function mapPlaceholderToArgKind(value: string | undefined): RootShortcutArgKind
60
60
  return null;
61
61
  }
62
62
 
63
- export function getPaneShortcutArgKind(template: PaneTemplateDef): RootShortcutArgKind | null {
63
+ function getPaneShortcutArgKind(template: PaneTemplateDef): RootShortcutArgKind | null {
64
64
  return template.shortcut?.argKind ?? mapPlaceholderToArgKind(template.shortcut?.argPlaceholder);
65
65
  }
66
66
 
67
- export function getCommandShortcutArgKind(command: Command): RootShortcutArgKind | null {
67
+ function getCommandShortcutArgKind(command: Command): RootShortcutArgKind | null {
68
68
  return mapPlaceholderToArgKind(command.argPlaceholder) ?? (command.hasArg ? "text" : null);
69
69
  }
70
70
 
@@ -135,9 +135,10 @@ export function parseRootShortcutIntent({
135
135
  if (!trimmed) return { kind: "none" };
136
136
 
137
137
  const upper = trimmed.toUpperCase();
138
- const match = buildShortcutCandidates(commands, pluginCommands, paneTemplates).find((candidate) => (
139
- upper === candidate.prefix || upper.startsWith(`${candidate.prefix} `)
140
- ));
138
+ const match = buildShortcutCandidates(commands, pluginCommands, paneTemplates).find((candidate) => {
139
+ if (upper === candidate.prefix) return true;
140
+ return !!candidate.argKind && upper.startsWith(`${candidate.prefix} `);
141
+ });
141
142
  if (!match) return { kind: "none" };
142
143
 
143
144
  const argText = trimmed.slice(match.prefix.length).trim();
@@ -1,25 +1,29 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  buildSections,
4
- getEmptyState,
5
- getFooterHints,
6
4
  getRowPresentation,
7
5
  rankTickerSearchItems,
8
6
  resolveCommandBarMode,
9
7
  } from "./view-model";
8
+ import { commands } from "./command-registry";
10
9
 
11
10
  describe("command bar view model helpers", () => {
12
11
  test("resolves prefix-driven modes", () => {
13
12
  expect(resolveCommandBarMode("")).toMatchObject({ kind: "default", badge: "BROWSE" });
14
- expect(resolveCommandBarMode("T NVDA")).toMatchObject({ kind: "search", badge: "SEARCH" });
13
+ expect(resolveCommandBarMode("DES NVDA")).toMatchObject({ kind: "search", badge: "DES" });
15
14
  expect(resolveCommandBarMode("TH ")).toMatchObject({ kind: "themes", badge: "THEMES" });
16
15
  expect(resolveCommandBarMode("PL notes")).toMatchObject({ kind: "plugins", badge: "PLUGINS" });
17
16
  expect(resolveCommandBarMode("LAY ")).toMatchObject({ kind: "layout", badge: "LAYOUT" });
18
- expect(resolveCommandBarMode("NP ")).toMatchObject({ kind: "new-pane", badge: "NEW PANE" });
17
+ expect(resolveCommandBarMode("NP ")).toMatchObject({ kind: "default", badge: "FILTER" });
19
18
  expect(resolveCommandBarMode("PS")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
20
19
  expect(resolveCommandBarMode("AW")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
21
20
  });
22
21
 
22
+ test("can resolve modes against a renderer-specific command list", () => {
23
+ const desktopCommands = commands.filter((command) => command.id !== "cycle-chart-renderer");
24
+ expect(resolveCommandBarMode("CR", desktopCommands)).toMatchObject({ kind: "default", badge: "FILTER" });
25
+ });
26
+
23
27
  test("builds sections while preserving order", () => {
24
28
  const sections = buildSections([
25
29
  { id: "a", category: "Tickers" },
@@ -42,44 +46,6 @@ describe("command bar view model helpers", () => {
42
46
  expect(sections.map((section) => section.category)).toEqual(["Tickers", "Config", "Danger", "Debug"]);
43
47
  });
44
48
 
45
- test("returns footer hints for plugin toggles", () => {
46
- expect(getFooterHints("plugins", false)).toEqual({
47
- left: "up/down move enter select space toggle",
48
- right: "esc cancel",
49
- });
50
- expect(getFooterHints("layout", false)).toEqual({
51
- left: "up/down move enter select",
52
- right: "esc cancel",
53
- });
54
- expect(getFooterHints("new-pane", false)).toEqual({
55
- left: "up/down move enter select",
56
- right: "esc cancel",
57
- });
58
- });
59
-
60
- test("returns specific empty states", () => {
61
- expect(getEmptyState("search", "T ", "")).toEqual({
62
- label: "Type a ticker symbol",
63
- detail: "Search Yahoo Finance and connected brokers",
64
- });
65
- expect(getEmptyState("search", "T zom", "zom")).toEqual({
66
- label: 'No matches for "zom"',
67
- detail: "Try a symbol, company name, or exchange variant",
68
- });
69
- expect(getEmptyState("default", "abc")).toEqual({
70
- label: 'No matches for "abc"',
71
- detail: "Try a ticker, command name, or prefix",
72
- });
73
- expect(getEmptyState("layout", "LAY ")).toEqual({
74
- label: "No layout actions match",
75
- detail: "LAY",
76
- });
77
- expect(getEmptyState("new-pane", "NP ")).toEqual({
78
- label: "No pane templates match",
79
- detail: "NP",
80
- });
81
- });
82
-
83
49
  test("derives row presentation for toggles and current theme rows", () => {
84
50
  expect(getRowPresentation({
85
51
  id: "plugin:news",
@@ -1,7 +1,8 @@
1
- import { matchPrefix } from "./command-registry";
1
+ import { matchPrefix, type Command } from "./command-registry";
2
+
2
3
  export { rankTickerSearchItems } from "../../utils/ticker-search";
3
4
 
4
- export type CommandBarMode = "default" | "search" | "themes" | "plugins" | "layout" | "new-pane" | "direct-command";
5
+ export type CommandBarMode = "default" | "search" | "themes" | "plugins" | "layout" | "direct-command";
5
6
 
6
7
  export interface CommandBarModeInfo {
7
8
  kind: CommandBarMode;
@@ -9,12 +10,6 @@ export interface CommandBarModeInfo {
9
10
  hint: string;
10
11
  }
11
12
 
12
- export interface CommandBarModeStripEntry {
13
- prefix: string;
14
- label: string;
15
- active: boolean;
16
- }
17
-
18
13
  export interface CommandBarFooterHints {
19
14
  left: string;
20
15
  right: string;
@@ -44,16 +39,8 @@ export interface CommandBarRowPresentation {
44
39
  primaryMuted: boolean;
45
40
  }
46
41
 
47
- const MODE_STRIP_ENTRIES = [
48
- { prefix: "T", label: "Search ticker", kind: "search" },
49
- { prefix: "TH", label: "Change theme", kind: "themes" },
50
- { prefix: "PL", label: "Toggle plugins", kind: "plugins" },
51
- { prefix: "LAY", label: "Layout actions", kind: "layout" },
52
- { prefix: "NP", label: "New pane", kind: "new-pane" },
53
- ] as const;
54
-
55
- export function resolveCommandBarMode(query: string): CommandBarModeInfo {
56
- const match = matchPrefix(query);
42
+ export function resolveCommandBarMode(query: string, commandList?: Command[]): CommandBarModeInfo {
43
+ const match = matchPrefix(query, commandList);
57
44
 
58
45
  if (!query.trim()) {
59
46
  return { kind: "default", badge: "BROWSE", hint: "Type a command, ticker, or prefix" };
@@ -64,29 +51,19 @@ export function resolveCommandBarMode(query: string): CommandBarModeInfo {
64
51
  }
65
52
 
66
53
  switch (match.command.id) {
67
- case "search-ticker":
68
- return { kind: "search", badge: "SEARCH", hint: "Search Yahoo Finance and broker-backed symbols" };
54
+ case "security-description":
55
+ return { kind: "search", badge: "DES", hint: "Open security details for a ticker" };
69
56
  case "theme":
70
57
  return { kind: "themes", badge: "THEMES", hint: "Preview with arrows, Enter to save, Esc to revert" };
71
58
  case "plugins":
72
59
  return { kind: "plugins", badge: "PLUGINS", hint: "Toggle plugins without leaving the list" };
73
60
  case "layout":
74
61
  return { kind: "layout", badge: "LAYOUT", hint: "Organize panes, history, and saved layouts" };
75
- case "new-pane":
76
- return { kind: "new-pane", badge: "NEW PANE", hint: "Create plugin-defined panes for the current workspace" };
77
62
  default:
78
63
  return { kind: "direct-command", badge: "COMMAND", hint: `Run ${match.command.label}` };
79
64
  }
80
65
  }
81
66
 
82
- export function getModeStrip(mode: CommandBarMode): CommandBarModeStripEntry[] {
83
- return MODE_STRIP_ENTRIES.map((entry) => ({
84
- prefix: entry.prefix,
85
- label: entry.label,
86
- active: entry.kind === mode,
87
- }));
88
- }
89
-
90
67
  export function buildSections<T extends { category: string }>(items: T[]): Array<CommandBarSection<T>> {
91
68
  const sections: Array<CommandBarSection<T>> = [];
92
69
  for (const item of items) {
@@ -117,9 +94,6 @@ export function getFooterHints(mode: CommandBarMode, isNarrow: boolean): Command
117
94
  if (mode === "layout") {
118
95
  return { left: moveAndSelect, right: "esc cancel" };
119
96
  }
120
- if (mode === "new-pane") {
121
- return { left: moveAndSelect, right: "esc cancel" };
122
- }
123
97
  return { left: moveAndSelect, right: "esc cancel" };
124
98
  }
125
99
 
@@ -127,7 +101,7 @@ export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?:
127
101
  switch (mode) {
128
102
  case "search":
129
103
  if (!searchQuery) {
130
- return { label: "Type a ticker symbol", detail: "Search Yahoo Finance and connected brokers" };
104
+ return { label: "Type a ticker symbol", detail: "Open security details after resolving a ticker" };
131
105
  }
132
106
  return { label: `No matches for "${searchQuery}"`, detail: "Try a symbol, company name, or exchange variant" };
133
107
  case "plugins":
@@ -136,8 +110,6 @@ export function getEmptyState(mode: CommandBarMode, query: string, searchQuery?:
136
110
  return { label: "No themes match", detail: query.trim() || "Installed themes will appear here" };
137
111
  case "layout":
138
112
  return { label: "No layout actions match", detail: query.trim() || "Focused-pane and layout actions will appear here" };
139
- case "new-pane":
140
- return { label: "No pane templates match", detail: query.trim() || "Plugin-defined pane templates will appear here" };
141
113
  default:
142
114
  if (query.trim()) {
143
115
  return { label: `No matches for "${query.trim()}"`, detail: "Try a ticker, command name, or prefix" };
@@ -1,8 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { createDefaultConfig } from "../../types/config";
2
+ import { cloneLayout, createDefaultConfig, findPaneInstance, type LayoutConfig } from "../../types/config";
3
3
  import { createInitialState } from "../../state/app-context";
4
4
  import { createTestDataProvider } from "../../test-support/data-provider";
5
- import { createPaneTemplateOrThrow } from "./workflow-ops";
5
+ import { applyPaneSettingFieldValue, createPaneTemplateOrThrow } from "./workflow-ops";
6
6
 
7
7
  function makeDataProvider() {
8
8
  return createTestDataProvider({ id: "test" });
@@ -68,3 +68,77 @@ describe("createPaneTemplateOrThrow", () => {
68
68
  expect(placeCalls).toHaveLength(0);
69
69
  });
70
70
  });
71
+
72
+ describe("applyPaneSettingFieldValue", () => {
73
+ test("keeps portfolio panes on their displayed collection when switching back to all collections", async () => {
74
+ const config = createDefaultConfig("/tmp/gloomberb-workflow-ops-test");
75
+ const layout = cloneLayout(config.layout);
76
+ const portfolioPane = findPaneInstance(layout, "portfolio-list:main");
77
+ if (!portfolioPane) throw new Error("missing portfolio pane");
78
+ portfolioPane.settings = {
79
+ ...(portfolioPane.settings ?? {}),
80
+ collectionScope: "watchlists",
81
+ visibleCollectionIds: ["watchlist"],
82
+ hideTabs: true,
83
+ lockedCollectionId: "watchlist",
84
+ };
85
+
86
+ const state = createInitialState({ ...config, layout });
87
+ state.paneState["portfolio-list:main"] = {
88
+ collectionId: "main",
89
+ cursorSymbol: null,
90
+ };
91
+
92
+ const persisted: LayoutConfig[] = [];
93
+ const actions: unknown[] = [];
94
+
95
+ await applyPaneSettingFieldValue("portfolio-list:main", {
96
+ key: "collectionScope",
97
+ label: "Collections",
98
+ type: "select",
99
+ options: [],
100
+ }, "all", {
101
+ dataProvider: makeDataProvider() as any,
102
+ tickerRepository: makeTickerRepository() as any,
103
+ dispatch: (action) => { actions.push(action); },
104
+ getState: () => state,
105
+ persistLayout: (nextLayout) => { persisted.push(nextLayout); },
106
+ pluginRegistry: {
107
+ resolvePaneSettings: () => ({
108
+ paneId: "portfolio-list:main",
109
+ pane: portfolioPane,
110
+ paneDef: {
111
+ id: "portfolio-list",
112
+ name: "Portfolio",
113
+ component: () => null,
114
+ defaultPosition: "left",
115
+ },
116
+ settingsDef: { title: "Portfolio Pane Settings", fields: [] },
117
+ context: {
118
+ config: state.config,
119
+ layout: state.config.layout,
120
+ paneId: "portfolio-list:main",
121
+ paneType: "portfolio-list",
122
+ pane: portfolioPane,
123
+ settings: portfolioPane.settings ?? {},
124
+ paneState: state.paneState["portfolio-list:main"] ?? {},
125
+ activeTicker: null,
126
+ activeCollectionId: "main",
127
+ },
128
+ }),
129
+ } as any,
130
+ });
131
+
132
+ const nextPane = findPaneInstance(persisted[0]!, "portfolio-list:main");
133
+ expect(nextPane?.settings).toMatchObject({ collectionScope: "all" });
134
+ expect("visibleCollectionIds" in (nextPane?.settings ?? {})).toBe(false);
135
+ expect("hideTabs" in (nextPane?.settings ?? {})).toBe(false);
136
+ expect("lockedCollectionId" in (nextPane?.settings ?? {})).toBe(false);
137
+ expect(nextPane?.params?.collectionId).toBe("watchlist");
138
+ expect(actions).toContainEqual({
139
+ type: "UPDATE_PANE_STATE",
140
+ paneId: "portfolio-list:main",
141
+ patch: { collectionId: "watchlist" },
142
+ });
143
+ });
144
+ });
@@ -10,6 +10,7 @@ import { resolveTickerSearch, upsertTickerFromSearchResult } from "../../utils/t
10
10
  import { formatTickerListInput, parseTickerListInput } from "../../utils/ticker-list";
11
11
  import { updatePaneInstance, setPaneSettings } from "../../pane-settings";
12
12
  import { isManualPortfolio } from "../../plugins/builtin/portfolio-list/mutations";
13
+ import { cleanPortfolioPaneSettings, resolvePortfolioPaneCollectionId } from "../../plugins/builtin/portfolio-list/settings";
13
14
  import {
14
15
  buildComparisonChartPaneTitle,
15
16
  COMPARISON_CHART_PANE_ID,
@@ -356,6 +357,14 @@ export async function applyPaneSettingFieldValue(
356
357
  const state = deps.getState();
357
358
  const shouldPushHistory = options?.pushHistory !== false;
358
359
 
360
+ if (field.storage === "plugin") {
361
+ if (!descriptor.pluginId) {
362
+ throw new Error("This pane setting is not owned by a plugin.");
363
+ }
364
+ await deps.pluginRegistry.setConfigState(descriptor.pluginId, field.key, value);
365
+ return;
366
+ }
367
+
359
368
  if (descriptor.pane.paneId === "quote-monitor" && field.key === "symbol") {
360
369
  const rawQuery = typeof value === "string" ? value.trim() : "";
361
370
  const resolvedTicker = await resolveTickerSearch({
@@ -419,18 +428,13 @@ export async function applyPaneSettingFieldValue(
419
428
  return;
420
429
  }
421
430
 
422
- const nextSettings = {
431
+ let nextSettings: Record<string, unknown> = {
423
432
  ...descriptor.context.settings,
424
433
  [field.key]: value,
425
434
  };
426
435
 
427
- if (descriptor.pane.paneId === "portfolio-list" && field.key === "hideTabs" && value === true) {
428
- const lockedCollectionId = typeof descriptor.context.paneState.collectionId === "string"
429
- ? descriptor.context.paneState.collectionId
430
- : descriptor.context.activeCollectionId;
431
- if (lockedCollectionId) {
432
- nextSettings.lockedCollectionId = lockedCollectionId;
433
- }
436
+ if (descriptor.pane.paneId === "portfolio-list") {
437
+ nextSettings = cleanPortfolioPaneSettings(nextSettings);
434
438
  }
435
439
 
436
440
  if (descriptor.pane.paneId === "ticker-detail" && field.key === "hideTabs" && value === true) {
@@ -440,6 +444,33 @@ export async function applyPaneSettingFieldValue(
440
444
  nextSettings.lockedTabId = lockedTabId;
441
445
  }
442
446
 
443
- const nextLayout = setPaneSettings(state.config.layout, targetId, nextSettings);
447
+ let nextLayout = setPaneSettings(state.config.layout, targetId, nextSettings);
448
+
449
+ if (descriptor.pane.paneId === "portfolio-list") {
450
+ const currentCollectionId = typeof descriptor.context.paneState.collectionId === "string"
451
+ ? descriptor.context.paneState.collectionId
452
+ : (descriptor.context.activeCollectionId ?? "");
453
+ const displayedCollectionId = resolvePortfolioPaneCollectionId(
454
+ state.config,
455
+ descriptor.context.settings,
456
+ currentCollectionId,
457
+ );
458
+ const nextCollectionId = resolvePortfolioPaneCollectionId(
459
+ state.config,
460
+ nextSettings,
461
+ displayedCollectionId || currentCollectionId,
462
+ );
463
+ if (nextCollectionId) {
464
+ nextLayout = updatePaneInstance(nextLayout, targetId, (instance) => ({
465
+ ...instance,
466
+ params: {
467
+ ...(instance.params ?? {}),
468
+ collectionId: nextCollectionId,
469
+ },
470
+ }));
471
+ deps.dispatch({ type: "UPDATE_PANE_STATE", paneId: targetId, patch: { collectionId: nextCollectionId } });
472
+ }
473
+ }
474
+
444
475
  deps.persistLayout(nextLayout, { pushHistory: shouldPushHistory });
445
476
  }
@@ -1,3 +1,4 @@
1
+
1
2
  export interface CommandBarMainSnapshot {
2
3
  query: string;
3
4
  selectedIdx: number;
@@ -37,7 +38,7 @@ export interface CommandBarRouteBase {
37
38
 
38
39
  export interface CommandBarModeRoute extends CommandBarRouteBase {
39
40
  kind: "mode";
40
- screen: "ticker-search" | "themes" | "plugins" | "layout" | "new-pane";
41
+ screen: "ticker-search" | "themes" | "plugins" | "layout";
41
42
  query: string;
42
43
  selectedIdx: number;
43
44
  hoveredIdx: number | null;