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
@@ -1,10 +1,16 @@
1
- import { TextAttributes } from "@opentui/core";
2
- import { useKeyboard } from "@opentui/react";
3
- import { useDialog } from "@opentui-ui/dialog/react";
1
+ import { Box, ScrollBox, Text } from "../../ui";
2
+ import { TextAttributes } from "../../ui";
3
+ import { useShortcut } from "../../react/input";
4
+ import { useDialog } from "../../ui/dialog";
4
5
  import { useCallback, useEffect, useState } from "react";
5
- import { PriceSelectorDialog } from "../../components";
6
+ import { PriceSelectorDialog, usePaneFooter } from "../../components";
6
7
  import { Button } from "../../components/ui/button";
7
- import { useAppState, usePaneCollection, usePaneInstanceId, usePaneTicker } from "../../state/app-context";
8
+ import {
9
+ useAppSelector,
10
+ usePaneCollection,
11
+ usePaneInstanceId,
12
+ usePaneTicker,
13
+ } from "../../state/app-context";
8
14
  import { colors, hoverBg } from "../../theme/colors";
9
15
  import type { DetailTabProps } from "../../types/plugin";
10
16
  import type { BrokerOrderRequest, BrokerOrderType } from "../../types/trading";
@@ -43,7 +49,8 @@ import {
43
49
  } from "./trade-utils";
44
50
 
45
51
  export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
46
- const { state } = useAppState();
52
+ const config = useAppSelector((state) => state.config);
53
+ const brokerAccounts = useAppSelector((state) => state.brokerAccounts);
47
54
  const paneId = usePaneInstanceId();
48
55
  const { collectionId } = usePaneCollection(paneId);
49
56
  const { ticker, financials } = usePaneTicker(paneId);
@@ -68,14 +75,14 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
68
75
  availableAccounts,
69
76
  gatewayRequiredMessage,
70
77
  } = useIbkrGatewaySelection(
71
- state.config,
72
- state.brokerAccounts,
78
+ config,
79
+ brokerAccounts,
73
80
  collectionId,
74
81
  preferredInstanceId,
75
82
  );
76
83
  const inferredAccountId = selectedInstance
77
84
  ? inferDraftAccountId(
78
- state.config,
85
+ config,
79
86
  collectionId,
80
87
  availableAccounts,
81
88
  selectedInstance.id,
@@ -138,7 +145,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
138
145
  useEffect(() => {
139
146
  if (!symbol || !ticker || !isGatewayMode || availableAccounts.length === 0 || ticketState.draft.accountId || !selectedInstance) return;
140
147
  const inferred = inferDraftAccountId(
141
- state.config,
148
+ config,
142
149
  collectionId,
143
150
  availableAccounts,
144
151
  selectedInstance.id,
@@ -154,7 +161,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
154
161
  availableAccounts,
155
162
  ticketState.draft.accountId,
156
163
  selectedInstance,
157
- state.config,
164
+ config,
158
165
  collectionId,
159
166
  tradeState.accountId,
160
167
  ]);
@@ -170,7 +177,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
170
177
  setTradeTicketBusy(symbol, true, ticker);
171
178
  await refreshGatewayData(selectedInstance);
172
179
  const inferred = inferDraftAccountId(
173
- state.config,
180
+ config,
174
181
  collectionId,
175
182
  availableAccounts,
176
183
  selectedInstance.id,
@@ -192,7 +199,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
192
199
  normalizedConfig,
193
200
  isGatewayMode,
194
201
  gatewayRequiredMessage,
195
- state.config,
202
+ config,
196
203
  collectionId,
197
204
  availableAccounts,
198
205
  tradeState.accountId,
@@ -222,14 +229,14 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
222
229
  choices={gatewayInstances.map((instance) => ({
223
230
  id: instance.id,
224
231
  label: instance.label,
225
- desc: "Gateway / TWS",
232
+ description: "Gateway / TWS",
226
233
  }))}
227
234
  />
228
235
  ),
229
236
  });
230
237
  if (!selected) return;
231
238
 
232
- const instance = getBrokerInstance(state.config.brokerInstances, selected);
239
+ const instance = getBrokerInstance(config.brokerInstances, selected);
233
240
  if (!instance) return;
234
241
  updateTradingPaneState({
235
242
  brokerInstanceId: instance.id,
@@ -253,7 +260,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
253
260
  lastError: undefined,
254
261
  lastInfo: undefined,
255
262
  }));
256
- }, [symbol, ticker, lockedBrokerInstanceId, gatewayInstances, dialog, state.config.brokerInstances]);
263
+ }, [symbol, ticker, lockedBrokerInstanceId, gatewayInstances, dialog, config.brokerInstances]);
257
264
 
258
265
  const chooseInstrument = useCallback(async () => {
259
266
  if (!symbol || !ticker) return;
@@ -292,7 +299,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
292
299
  const choices = results.slice(0, 12).map((result, index) => ({
293
300
  id: String(index),
294
301
  label: `${result.symbol} ${result.type}`.trim(),
295
- desc: `${selectedInstance.label} · ${result.name} · ${result.exchange || "SMART"}`,
302
+ description: `${selectedInstance.label} · ${result.name} · ${result.exchange || "SMART"}`,
296
303
  }));
297
304
  if (choices.length === 0) {
298
305
  setTradeTicketMessage(symbol, undefined, `No IBKR contracts matched "${search}".`, ticker);
@@ -322,7 +329,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
322
329
  }
323
330
 
324
331
  const nextAccounts = getKnownIbkrAccounts(
325
- state.brokerAccounts,
332
+ brokerAccounts,
326
333
  selectedInstance.id,
327
334
  gatewayService.getSnapshot().accounts,
328
335
  );
@@ -339,7 +346,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
339
346
  choices={nextAccounts.map((account) => ({
340
347
  id: account.accountId,
341
348
  label: `${selectedInstance.label} → ${account.accountId}`,
342
- desc: `${formatCurrency(account.netLiquidation || 0, account.currency || "USD")} net liq`,
349
+ description: `${formatCurrency(account.netLiquidation || 0, account.currency || "USD")} net liq`,
343
350
  }))}
344
351
  />
345
352
  ),
@@ -355,6 +362,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
355
362
  gatewayService,
356
363
  isGatewayMode,
357
364
  availableAccounts,
365
+ brokerAccounts,
358
366
  refresh,
359
367
  dialog,
360
368
  ]);
@@ -427,10 +435,10 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
427
435
  {...ctx}
428
436
  title="Order Type"
429
437
  choices={[
430
- { id: "MKT", label: "Market", desc: "Execute at market price" },
431
- { id: "LMT", label: "Limit", desc: "Set a limit price" },
432
- { id: "STP", label: "Stop", desc: "Trigger at a stop price" },
433
- { id: "STP LMT", label: "Stop Limit", desc: "Trigger a limit order at a stop price" },
438
+ { id: "MKT", label: "Market", description: "Execute at market price" },
439
+ { id: "LMT", label: "Limit", description: "Set a limit price" },
440
+ { id: "STP", label: "Stop", description: "Trigger at a stop price" },
441
+ { id: "STP LMT", label: "Stop Limit", description: "Trigger a limit order at a stop price" },
434
442
  ]}
435
443
  />
436
444
  ),
@@ -495,7 +503,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
495
503
  updateTradingPaneState({ accountId: request.accountId });
496
504
  setTradeTicketDraft(symbol, { brokerInstanceId: selectedInstance.id, accountId: request.accountId }, ticker);
497
505
  setTradeTicketPreview(symbol, preview, ticker);
498
- setTradeTicketMessage(symbol, "Review the what-if preview, then press Enter again to submit.", undefined, ticker);
506
+ setTradeTicketMessage(symbol, "Review the what-if preview, then submit when ready.", undefined, ticker);
499
507
  } catch (error: any) {
500
508
  const message = error?.message || "Failed to preview order.";
501
509
  setTradeTicketMessage(symbol, undefined, message.replace("Timeout has occurred", "Preview timed out — try again."), ticker);
@@ -534,7 +542,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
534
542
  }
535
543
  }, [draftRequest, symbol, ticker, selectedInstance, normalizedConfig, gatewayService, ticketState.preview, ticketState.editingOrderId, previewOrder, refresh]);
536
544
 
537
- useKeyboard((event) => {
545
+ useShortcut((event) => {
538
546
  if (!focused || !symbol || !ticker) return;
539
547
 
540
548
  const isEnter = event.name === "enter" || event.name === "return";
@@ -605,14 +613,6 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
605
613
  }
606
614
  });
607
615
 
608
- if (!ticker || !symbol) {
609
- return (
610
- <box flexGrow={1} alignItems="center" justifyContent="center">
611
- <text fg={colors.textDim}>Select a ticker to draft an IBKR trade.</text>
612
- </box>
613
- );
614
- }
615
-
616
616
  const showLimit = isLimitOrder(ticketState.draft.orderType);
617
617
  const showStop = isStopOrder(ticketState.draft.orderType);
618
618
  const hasProfile = Boolean(selectedInstance);
@@ -628,14 +628,16 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
628
628
  ? Math.max(16, Math.floor((ticketPanelWidth - 5) / 4))
629
629
  : fieldWidth;
630
630
  const fieldTextWidth = Math.max(8, fieldWidth - 3);
631
- const activeContract = ticketState.draft.contract.symbol ? ticketState.draft.contract : normalizeContract(ticker);
631
+ const activeContract = ticketState.draft.contract.symbol
632
+ ? ticketState.draft.contract
633
+ : ticker ? normalizeContract(ticker) : ticketState.draft.contract;
632
634
  const hasContract = Boolean(activeContract.symbol);
633
635
  const hasAccount = Boolean(currentAccountId);
634
636
  const hasPreview = Boolean(ticketState.preview);
635
637
  const previewTextWidth = Math.max(18, (previewPanelWidth ?? Math.max(34, width - 6)) - 4);
636
638
  const contractValue = truncateText(formatContractLabel(activeContract), fieldTextWidth);
637
639
  const contractMeta = truncateText(
638
- ticketState.contractName || ticker.metadata.name || "Using current ticker",
640
+ ticketState.contractName || ticker?.metadata.name || "Using current ticker",
639
641
  Math.max(fieldTextWidth * Math.max(2, fieldsPerRow), 18),
640
642
  );
641
643
  const connectionTone: TradeTone = gatewaySnapshot.status.state === "connected"
@@ -654,7 +656,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
654
656
  || ticketState.lastInfo
655
657
  || gatewaySnapshot.status.message
656
658
  || gatewaySnapshot.lastError
657
- || "Click a workflow step or field to activate the ticket, then preview before submit.";
659
+ || "Choose a workflow step or field, then preview before submit.";
658
660
  const previewTone: TradeTone = !ticketState.preview
659
661
  ? "neutral"
660
662
  : ticketState.preview.warningText
@@ -683,8 +685,56 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
683
685
  ? "accent"
684
686
  : "neutral";
685
687
  const ticketHint = interactive
686
- ? "Esc releases the ticket. Field shortcuts stay active while captured."
687
- : "Click a field or press Enter to edit. Shortcuts are shown inline.";
688
+ ? "Field shortcuts stay active while captured."
689
+ : "Click a field to edit. Shortcuts are in the pane footer.";
690
+
691
+ usePaneFooter("ibkr-trade", () => ({
692
+ info: [
693
+ { id: "next", parts: [{ text: nextStep, tone: workflowTone === "positive" ? "positive" : workflowTone === "accent" ? "value" : "muted", bold: workflowTone !== "neutral" }] },
694
+ { id: "ticket", parts: [{ text: interactive ? "captured" : "standby", tone: interactive ? "positive" : "muted" }] },
695
+ ...(statusText ? [{ id: "status", parts: [{ text: statusText, tone: statusTone === "negative" ? "negative" as const : statusTone === "positive" ? "positive" as const : "muted" as const }] }] : []),
696
+ ],
697
+ hints: [
698
+ { id: "refresh", key: "r", label: "efresh", onPress: () => refresh().catch(() => {}), disabled: ticketState.busy },
699
+ { id: "profile", key: "i", label: "profile", onPress: () => chooseBrokerInstance().catch(() => {}), disabled: ticketState.busy },
700
+ { id: "instrument", key: "s", label: "ymbol", onPress: () => chooseInstrument().catch(() => {}), disabled: ticketState.busy },
701
+ { id: "account", key: "a", label: "ccount", onPress: () => chooseAccount().catch(() => {}), disabled: ticketState.busy },
702
+ { id: "side", key: "b/v", label: "side", onPress: symbol && ticker ? () => setTradeTicketDraft(symbol, { action: ticketState.draft.action === "BUY" ? "SELL" : "BUY" }, ticker) : undefined, disabled: ticketState.busy || !symbol || !ticker },
703
+ { id: "quantity", key: "q", label: "ty", onPress: () => editNumericField("Quantity", ticketState.draft.quantity, (value) => { if (value != null && symbol && ticker) setTradeTicketDraft(symbol, { quantity: value }, ticker); }).catch(() => {}), disabled: ticketState.busy || !symbol || !ticker },
704
+ { id: "type", key: "t", label: "ype", onPress: () => editOrderType().catch(() => {}), disabled: ticketState.busy },
705
+ { id: "limit", key: "l", label: "imit", onPress: () => editPriceField("Limit Price", ticketState.draft.limitPrice, (value) => { if (symbol && ticker) setTradeTicketDraft(symbol, { limitPrice: value }, ticker); }).catch(() => {}), disabled: ticketState.busy || !showLimit || !symbol || !ticker },
706
+ { id: "stop", key: "x", label: "stop", onPress: () => editPriceField("Stop Price", ticketState.draft.stopPrice, (value) => { if (symbol && ticker) setTradeTicketDraft(symbol, { stopPrice: value }, ticker); }).catch(() => {}), disabled: ticketState.busy || !showStop || !symbol || !ticker },
707
+ { id: "preview", key: "p", label: "review", onPress: () => previewOrder().catch(() => {}), disabled: ticketState.busy || !symbol || !ticker },
708
+ ],
709
+ }), [
710
+ chooseAccount,
711
+ chooseBrokerInstance,
712
+ chooseInstrument,
713
+ editNumericField,
714
+ editOrderType,
715
+ editPriceField,
716
+ interactive,
717
+ nextStep,
718
+ previewOrder,
719
+ refresh,
720
+ showLimit,
721
+ showStop,
722
+ statusText,
723
+ statusTone,
724
+ symbol,
725
+ ticketState.busy,
726
+ ticketState.draft,
727
+ ticker,
728
+ workflowTone,
729
+ ]);
730
+
731
+ if (!ticker || !symbol) {
732
+ return (
733
+ <Box flexGrow={1} alignItems="center" justifyContent="center">
734
+ <Text fg={colors.textDim}>Select a ticker to draft an IBKR trade.</Text>
735
+ </Box>
736
+ );
737
+ }
688
738
 
689
739
  const renderFieldPill = ({
690
740
  id,
@@ -731,7 +781,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
731
781
  : valueColor ?? (disabled ? colors.textMuted : colors.text);
732
782
 
733
783
  return (
734
- <box
784
+ <Box
735
785
  key={id}
736
786
  width={itemWidth}
737
787
  minWidth={16}
@@ -741,18 +791,18 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
741
791
  paddingX={1}
742
792
  marginRight={1}
743
793
  onMouseMove={() => {
744
- if (!disabled) setHoveredField(id);
794
+ if (!disabled) setHoveredField((current) => (current === id ? current : id));
745
795
  }}
746
796
  onMouseDown={disabled ? undefined : () => {
747
797
  enterInteractive();
748
798
  onPress?.();
749
799
  }}
750
800
  >
751
- <text fg={labelColor}>{label}</text>
752
- <text fg={resolvedValueColor} attributes={valueAttributes}>
801
+ <Text fg={labelColor}>{label}</Text>
802
+ <Text fg={resolvedValueColor} attributes={valueAttributes}>
753
803
  {` ${truncateText(value, valueWidth)}`}
754
- </text>
755
- </box>
804
+ </Text>
805
+ </Box>
756
806
  );
757
807
  };
758
808
 
@@ -772,7 +822,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
772
822
  const palette = getTradeTonePalette(tone);
773
823
 
774
824
  return (
775
- <box
825
+ <Box
776
826
  key={id}
777
827
  height={1}
778
828
  flexDirection="row"
@@ -781,14 +831,14 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
781
831
  marginRight={1}
782
832
  onMouseDown={onPress}
783
833
  >
784
- <text fg={tone === "neutral" ? colors.textDim : palette.text}>{label}</text>
785
- <text fg={palette.text} attributes={TextAttributes.BOLD}>{` ${value}`}</text>
786
- </box>
834
+ <Text fg={tone === "neutral" ? colors.textDim : palette.text}>{label}</Text>
835
+ <Text fg={palette.text} attributes={TextAttributes.BOLD}>{` ${value}`}</Text>
836
+ </Box>
787
837
  );
788
838
  };
789
839
 
790
840
  const renderPreviewMetric = (label: string, value: string, tone: TradeTone = "neutral") => (
791
- <box
841
+ <Box
792
842
  key={label}
793
843
  width={previewMetricWidth}
794
844
  height={1}
@@ -796,35 +846,35 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
796
846
  paddingX={1}
797
847
  marginRight={1}
798
848
  >
799
- <text fg={tone === "negative" ? colors.negative : tone === "positive" ? colors.positive : colors.text}>
849
+ <Text fg={tone === "negative" ? colors.negative : tone === "positive" ? colors.positive : colors.text}>
800
850
  {truncateText(`${label} ${value}`, Math.max(6, previewMetricWidth - 2))}
801
- </text>
802
- </box>
851
+ </Text>
852
+ </Box>
803
853
  );
804
854
 
805
855
  return (
806
- <scrollbox flexGrow={1} scrollY>
807
- <box
856
+ <ScrollBox flexGrow={1} scrollY>
857
+ <Box
808
858
  flexDirection="column"
809
859
  paddingX={1}
810
860
  paddingBottom={1}
811
861
  gap={1}
812
862
  onMouseDown={!interactive ? enterInteractive : undefined}
813
863
  >
814
- <box flexDirection="row" flexWrap="wrap" justifyContent="space-between">
815
- <box flexDirection="column" marginBottom={1}>
816
- <box height={1} flexDirection="row">
817
- <text attributes={TextAttributes.BOLD} fg={colors.textBright}>{`Trade ${ticker.metadata.ticker}`}</text>
864
+ <Box flexDirection="row" flexWrap="wrap" justifyContent="space-between">
865
+ <Box flexDirection="column" marginBottom={1}>
866
+ <Box height={1} flexDirection="row">
867
+ <Text attributes={TextAttributes.BOLD} fg={colors.textBright}>{`Trade ${ticker.metadata.ticker}`}</Text>
818
868
  {ticker.metadata.name && ticker.metadata.name !== ticker.metadata.ticker && (
819
- <text fg={colors.textDim}>{` · ${ticker.metadata.name}`}</text>
869
+ <Text fg={colors.textDim}>{` · ${ticker.metadata.name}`}</Text>
820
870
  )}
821
- </box>
822
- <box height={1}>
823
- <text fg={colors.textMuted}>{formatQuoteSummary(financials?.quote, { assetCategory: ticker.metadata.assetCategory })}</text>
824
- </box>
825
- </box>
871
+ </Box>
872
+ <Box height={1}>
873
+ <Text fg={colors.textMuted}>{formatQuoteSummary(financials?.quote, { assetCategory: ticker.metadata.assetCategory })}</Text>
874
+ </Box>
875
+ </Box>
826
876
 
827
- <box flexDirection="row" flexWrap="wrap" justifyContent="flex-end">
877
+ <Box flexDirection="row" flexWrap="wrap" justifyContent="flex-end">
828
878
  <TradeBadge
829
879
  label="Broker"
830
880
  value={selectedInstance ? `${selectedInstance.label} ${isGatewayMode ? "Gateway" : "Flex"}` : "Select profile"}
@@ -852,10 +902,10 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
852
902
  chooseAccount().catch(() => {});
853
903
  }}
854
904
  />
855
- </box>
856
- </box>
905
+ </Box>
906
+ </Box>
857
907
 
858
- <box flexDirection="row" flexWrap="wrap">
908
+ <Box flexDirection="row" flexWrap="wrap">
859
909
  {renderSummaryPill({ id: "next", label: "Next", value: nextStep, tone: workflowTone })}
860
910
  {renderSummaryPill({
861
911
  id: "ticket",
@@ -866,21 +916,20 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
866
916
  })}
867
917
  <Button
868
918
  label="Refresh"
869
- shortcut="r"
870
919
  variant="ghost"
871
920
  disabled={ticketState.busy}
872
921
  onPress={() => refresh().catch(() => {})}
873
922
  />
874
- </box>
923
+ </Box>
875
924
 
876
- <box backgroundColor={getTradeTonePalette(statusTone).background} paddingX={1}>
877
- <text fg={ticketState.lastError ? colors.negative : ticketState.isSuccess ? colors.positive : colors.text}>
925
+ <Box backgroundColor={getTradeTonePalette(statusTone).background} paddingX={1}>
926
+ <Text fg={ticketState.lastError ? colors.negative : ticketState.isSuccess ? colors.positive : colors.text}>
878
927
  {statusText}
879
- </text>
880
- </box>
928
+ </Text>
929
+ </Box>
881
930
 
882
- <box flexDirection={wideLayout ? "row" : "column"} alignItems="stretch" gap={1}>
883
- <box
931
+ <Box flexDirection={wideLayout ? "row" : "column"} alignItems="stretch" gap={1}>
932
+ <Box
884
933
  flexDirection="column"
885
934
  flexGrow={1}
886
935
  width={wideLayout ? ticketPanelWidth : undefined}
@@ -889,17 +938,17 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
889
938
  borderColor={interactive ? colors.borderFocused : colors.border}
890
939
  paddingX={1}
891
940
  >
892
- <box height={1} flexDirection="row">
893
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>Ticket</text>
894
- <box flexGrow={1} />
895
- <text fg={interactive ? colors.positive : colors.textMuted}>
896
- {interactive ? "Captured" : "Click field / Enter"}
897
- </text>
898
- </box>
899
- <text fg={colors.textMuted}>{truncateText(ticketHint, Math.max(ticketPanelWidth - 4, 24))}</text>
900
- <box height={1} />
941
+ <Box height={1} flexDirection="row">
942
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>Ticket</Text>
943
+ <Box flexGrow={1} />
944
+ <Text fg={interactive ? colors.positive : colors.textMuted}>
945
+ {interactive ? "Captured" : "Ready"}
946
+ </Text>
947
+ </Box>
948
+ <Text fg={colors.textMuted}>{truncateText(ticketHint, Math.max(ticketPanelWidth - 4, 24))}</Text>
949
+ <Box height={1} />
901
950
 
902
- <box flexDirection="row" flexWrap="wrap">
951
+ <Box flexDirection="row" flexWrap="wrap">
903
952
  {renderFieldPill({
904
953
  id: "profile",
905
954
  label: "Profile",
@@ -924,12 +973,12 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
924
973
  widthOverride: coreFieldWidth,
925
974
  onPress: () => chooseAccount().catch(() => {}),
926
975
  })}
927
- </box>
928
- <box height={1} />
929
- <box flexDirection="row" flexWrap="wrap">
976
+ </Box>
977
+ <Box height={1} />
978
+ <Box flexDirection="row" flexWrap="wrap">
930
979
  {renderFieldPill({
931
980
  id: "action",
932
- label: "Side [b/v]",
981
+ label: "Side",
933
982
  value: ticketState.draft.action,
934
983
  valueColor: ticketState.draft.action === "BUY" ? colors.positive : colors.negative,
935
984
  valueAttributes: TextAttributes.BOLD,
@@ -940,14 +989,14 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
940
989
  })}
941
990
  {renderFieldPill({
942
991
  id: "orderType",
943
- label: "Type [t]",
992
+ label: "Type",
944
993
  value: ticketState.draft.orderType,
945
994
  widthOverride: orderFieldWidth,
946
995
  onPress: () => editOrderType().catch(() => {}),
947
996
  })}
948
997
  {renderFieldPill({
949
998
  id: "quantity",
950
- label: "Qty [q]",
999
+ label: "Qty",
951
1000
  value: formatMarketQuantity(ticketState.draft.quantity, {
952
1001
  assetCategory: ticker.metadata.assetCategory,
953
1002
  contractSecType: activeContract.secType,
@@ -962,7 +1011,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
962
1011
  })}
963
1012
  {showLimit && renderFieldPill({
964
1013
  id: "limitPrice",
965
- label: "Limit [l]",
1014
+ label: "Limit",
966
1015
  value: ticketState.draft.limitPrice != null
967
1016
  ? formatMarketPrice(ticketState.draft.limitPrice, {
968
1017
  assetCategory: ticker.metadata.assetCategory,
@@ -979,7 +1028,7 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
979
1028
  })}
980
1029
  {showStop && renderFieldPill({
981
1030
  id: "stopPrice",
982
- label: "Stop [x]",
1031
+ label: "Stop",
983
1032
  value: ticketState.draft.stopPrice != null
984
1033
  ? formatMarketPrice(ticketState.draft.stopPrice, {
985
1034
  assetCategory: ticker.metadata.assetCategory,
@@ -1007,12 +1056,12 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
1007
1056
  valueColor: colors.textBright,
1008
1057
  widthOverride: orderFieldWidth,
1009
1058
  })}
1010
- </box>
1011
- <box height={1} />
1012
- <text fg={colors.textMuted}>{contractMeta}</text>
1013
- </box>
1059
+ </Box>
1060
+ <Box height={1} />
1061
+ <Text fg={colors.textMuted}>{contractMeta}</Text>
1062
+ </Box>
1014
1063
 
1015
- <box
1064
+ <Box
1016
1065
  flexDirection="column"
1017
1066
  width={previewPanelWidth}
1018
1067
  minWidth={34}
@@ -1021,16 +1070,16 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
1021
1070
  borderColor={getTradeTonePalette(previewTone).border}
1022
1071
  paddingX={1}
1023
1072
  >
1024
- <box height={1} flexDirection="row">
1025
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>Preview</text>
1026
- <box flexGrow={1} />
1027
- <text fg={getTradeTonePalette(previewTone).text}>{previewHeading}</text>
1028
- </box>
1029
- <text fg={ticketState.preview?.warningText ? colors.negative : colors.textMuted}>
1073
+ <Box height={1} flexDirection="row">
1074
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>Preview</Text>
1075
+ <Box flexGrow={1} />
1076
+ <Text fg={getTradeTonePalette(previewTone).text}>{previewHeading}</Text>
1077
+ </Box>
1078
+ <Text fg={ticketState.preview?.warningText ? colors.negative : colors.textMuted}>
1030
1079
  {truncateText(formatPreviewSummary(ticketState.preview), previewTextWidth)}
1031
- </text>
1080
+ </Text>
1032
1081
 
1033
- <box flexDirection="row" flexWrap="wrap">
1082
+ <Box flexDirection="row" flexWrap="wrap">
1034
1083
  {renderPreviewMetric(
1035
1084
  "Fee",
1036
1085
  ticketState.preview?.commission != null
@@ -1041,28 +1090,26 @@ export function TradeTab({ focused, width, onCapture }: DetailTabProps) {
1041
1090
  {renderPreviewMetric("Maint", formatPreviewMetric(ticketState.preview?.maintMarginBefore, ticketState.preview?.maintMarginAfter))}
1042
1091
  {renderPreviewMetric("Equity", formatPreviewMetric(ticketState.preview?.equityWithLoanBefore, ticketState.preview?.equityWithLoanAfter))}
1043
1092
  {ticketState.preview?.warningText && renderPreviewMetric("Warn", ticketState.preview.warningText, "negative")}
1044
- </box>
1093
+ </Box>
1045
1094
 
1046
- <box flexDirection="row" flexWrap="wrap">
1095
+ <Box flexDirection="row" flexWrap="wrap">
1047
1096
  <Button
1048
1097
  label="Preview"
1049
- shortcut="p"
1050
1098
  variant="secondary"
1051
1099
  disabled={ticketState.busy}
1052
1100
  onPress={() => previewOrder().catch(() => {})}
1053
1101
  />
1054
- <box width={1} />
1102
+ <Box width={1} />
1055
1103
  <Button
1056
1104
  label={ticketState.editingOrderId ? "Submit Change" : "Submit Order"}
1057
- shortcut="Enter"
1058
1105
  variant="primary"
1059
1106
  disabled={!ticketState.preview || ticketState.busy}
1060
1107
  onPress={() => submitOrder().catch(() => {})}
1061
1108
  />
1062
- </box>
1063
- </box>
1064
- </box>
1065
- </box>
1066
- </scrollbox>
1109
+ </Box>
1110
+ </Box>
1111
+ </Box>
1112
+ </Box>
1113
+ </ScrollBox>
1067
1114
  );
1068
1115
  }