gloomberb 0.5.1 → 0.6.1

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 (506) hide show
  1. package/README.md +91 -11
  2. package/package.json +13 -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 +318 -158
  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 +53 -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/native/surface-visibility.ts +48 -0
  53. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  54. package/src/components/chart/static-chart-surface.tsx +191 -0
  55. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  56. package/src/components/chart/stock-chart.tsx +452 -191
  57. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  58. package/src/components/command-bar/command-bar.test.tsx +269 -20
  59. package/src/components/command-bar/command-bar.tsx +830 -353
  60. package/src/components/command-bar/command-registry.ts +10 -20
  61. package/src/components/command-bar/helpers.test.ts +1 -1
  62. package/src/components/command-bar/helpers.ts +9 -11
  63. package/src/components/command-bar/pane-template-display.ts +4 -8
  64. package/src/components/command-bar/root-shortcuts.ts +12 -11
  65. package/src/components/command-bar/view-model.test.ts +8 -42
  66. package/src/components/command-bar/view-model.ts +8 -36
  67. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  68. package/src/components/command-bar/workflow-ops.ts +40 -9
  69. package/src/components/command-bar/workflow-types.ts +2 -1
  70. package/src/components/data-table-stack-view.test.tsx +206 -0
  71. package/src/components/data-table-stack-view.tsx +152 -0
  72. package/src/components/data-table-view.test.tsx +188 -0
  73. package/src/components/data-table-view.tsx +279 -0
  74. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  75. package/src/components/feed-data-table-stack-view.tsx +365 -0
  76. package/src/components/index.ts +26 -31
  77. package/src/components/layout/detached-pane-shell.tsx +185 -0
  78. package/src/components/layout/floating-pane.tsx +87 -14
  79. package/src/components/layout/header.tsx +139 -49
  80. package/src/components/layout/pane-content.tsx +42 -0
  81. package/src/components/layout/pane-footer.test.tsx +195 -0
  82. package/src/components/layout/pane-footer.tsx +425 -0
  83. package/src/components/layout/pane-header.test.tsx +39 -0
  84. package/src/components/layout/pane-header.tsx +192 -14
  85. package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
  86. package/src/components/layout/pane-sizing.ts +9 -10
  87. package/src/components/layout/pane-title.ts +35 -0
  88. package/src/components/layout/pane.tsx +52 -11
  89. package/src/components/layout/shell.test.tsx +315 -107
  90. package/src/components/layout/shell.tsx +1010 -368
  91. package/src/components/layout/status-bar.test.tsx +17 -10
  92. package/src/components/layout/status-bar.tsx +367 -100
  93. package/src/components/layout/titlebar-overlay.ts +2 -0
  94. package/src/components/markdown-editor.tsx +29 -14
  95. package/src/components/markdown-text.tsx +25 -24
  96. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  97. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  98. package/src/components/pane-settings-dialog.tsx +521 -89
  99. package/src/components/pane-template-wizard.test.tsx +1 -1
  100. package/src/components/pane-template-wizard.tsx +38 -37
  101. package/src/components/price-selector-dialog.tsx +22 -21
  102. package/src/components/ticker-badge-list.tsx +53 -0
  103. package/src/components/ticker-badge-text.test.tsx +44 -1
  104. package/src/components/ticker-badge-text.tsx +78 -38
  105. package/src/components/ticker-badge.tsx +28 -7
  106. package/src/components/ticker-list-table-view.tsx +248 -0
  107. package/src/components/ticker-list-table.test.tsx +47 -1
  108. package/src/components/ticker-list-table.tsx +212 -41
  109. package/src/components/toggle-list.tsx +114 -8
  110. package/src/components/ui/button.tsx +24 -16
  111. package/src/components/ui/choice-dialog.tsx +89 -0
  112. package/src/components/ui/data-table.tsx +313 -114
  113. package/src/components/ui/external-link.tsx +52 -13
  114. package/src/components/ui/fields.tsx +44 -30
  115. package/src/components/ui/frame.tsx +24 -57
  116. package/src/components/ui/index.ts +15 -50
  117. package/src/components/ui/list-view.tsx +65 -25
  118. package/src/components/ui/loading.tsx +5 -60
  119. package/src/components/ui/message-composer.test.tsx +81 -0
  120. package/src/components/ui/message-composer.tsx +139 -0
  121. package/src/components/ui/multi-select-dialog.tsx +100 -32
  122. package/src/components/ui/multi-select.ts +112 -0
  123. package/src/components/ui/native-select.tsx +120 -0
  124. package/src/components/ui/page-stack-view.tsx +49 -19
  125. package/src/components/ui/remote-image.tsx +50 -0
  126. package/src/components/ui/row-value-cache.test.ts +17 -0
  127. package/src/components/ui/row-value-cache.ts +41 -0
  128. package/src/components/ui/shortcut-hint.tsx +55 -0
  129. package/src/components/ui/status.tsx +12 -67
  130. package/src/components/ui/table-layout.ts +59 -0
  131. package/src/components/ui/tabs.tsx +249 -27
  132. package/src/components/ui/toggle.tsx +19 -127
  133. package/src/components/ui/ui.test.tsx +266 -53
  134. package/src/components/use-double-click-activation.ts +7 -2
  135. package/src/components/use-external-link-footer.ts +79 -0
  136. package/src/core/app-services.ts +95 -0
  137. package/src/core/state/app-state.ts +886 -0
  138. package/src/{state → core/state}/session-persistence.ts +7 -6
  139. package/src/data/config-layout.ts +225 -0
  140. package/src/data/config-store-node.ts +390 -0
  141. package/src/data/config-store.test.ts +72 -0
  142. package/src/data/config-store.ts +35 -478
  143. package/src/data/sqlite-json.ts +2 -5
  144. package/src/data/sqlite-retry.ts +1 -0
  145. package/src/index.tsx +2 -68
  146. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  147. package/src/market-data/coordinator.test.ts +73 -4
  148. package/src/market-data/coordinator.ts +190 -24
  149. package/src/market-data/hooks.test.tsx +1 -1
  150. package/src/market-data/hooks.tsx +109 -87
  151. package/src/market-data/query-store.ts +3 -3
  152. package/src/market-data/request-types.test.ts +0 -14
  153. package/src/market-data/result-types.ts +1 -0
  154. package/src/news/aggregator.test.ts +267 -0
  155. package/src/news/aggregator.ts +452 -0
  156. package/src/news/hooks.ts +39 -0
  157. package/src/news/ticker-symbols.ts +21 -0
  158. package/src/news/types.ts +77 -0
  159. package/src/notifications/app-notifier.ts +21 -10
  160. package/src/pane-settings.test.ts +0 -2
  161. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  162. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  163. package/src/plugins/builtin/ai/providers.ts +1 -9
  164. package/src/plugins/builtin/ai/runner.ts +41 -5
  165. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  166. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  167. package/src/plugins/builtin/ai/settings.ts +0 -1
  168. package/src/plugins/builtin/ai/utils.ts +1 -71
  169. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  170. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  171. package/src/plugins/builtin/alerts/index.tsx +263 -155
  172. package/src/plugins/builtin/alerts/types.ts +7 -0
  173. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  174. package/src/plugins/builtin/analytics/index.tsx +78 -120
  175. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  176. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  177. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  178. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  179. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  180. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  181. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  182. package/src/plugins/builtin/chat-controller.ts +85 -14
  183. package/src/plugins/builtin/chat.test.tsx +283 -36
  184. package/src/plugins/builtin/chat.tsx +363 -170
  185. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  186. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  187. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  188. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  189. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  190. package/src/plugins/builtin/correlation/index.tsx +51 -46
  191. package/src/plugins/builtin/correlation/settings.ts +1 -2
  192. package/src/plugins/builtin/debug.tsx +99 -70
  193. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  194. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  195. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  196. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  197. package/src/plugins/builtin/earnings/index.tsx +187 -94
  198. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  199. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  200. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  201. package/src/plugins/builtin/econ/index.tsx +236 -421
  202. package/src/plugins/builtin/econ/types.ts +1 -0
  203. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  204. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  205. package/src/plugins/builtin/help.test.tsx +120 -60
  206. package/src/plugins/builtin/help.tsx +266 -82
  207. package/src/plugins/builtin/holders/index.test.ts +45 -0
  208. package/src/plugins/builtin/holders/index.tsx +949 -0
  209. package/src/plugins/builtin/insider/index.tsx +207 -253
  210. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  211. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  212. package/src/plugins/builtin/layout-manager.tsx +12 -40
  213. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  214. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  215. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  216. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  217. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  218. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  219. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  220. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  221. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  222. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  223. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  224. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  225. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  226. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  227. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  228. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  229. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  230. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  231. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  232. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  233. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  234. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  235. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  236. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  237. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  238. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  239. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  240. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  241. package/src/plugins/builtin/news.tsx +98 -25
  242. package/src/plugins/builtin/notes-files.ts +51 -10
  243. package/src/plugins/builtin/notes.tsx +332 -131
  244. package/src/plugins/builtin/options.test.tsx +185 -0
  245. package/src/plugins/builtin/options.tsx +346 -206
  246. package/src/plugins/builtin/plugin-groups.ts +92 -0
  247. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  248. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  249. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  250. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  251. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  252. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  253. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  254. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  255. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  256. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  257. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  258. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  259. package/src/plugins/builtin/research/index.tsx +724 -0
  260. package/src/plugins/builtin/sec.tsx +67 -19
  261. package/src/plugins/builtin/sectors/index.tsx +42 -80
  262. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  263. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  264. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  265. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  266. package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
  267. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  268. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  269. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
  270. package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
  271. package/src/plugins/builtin/ticker-surface.ts +68 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  273. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  274. package/src/plugins/builtin/yahoo.ts +55 -1
  275. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  276. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  277. package/src/plugins/catalog-ui.test.ts +24 -0
  278. package/src/plugins/catalog-ui.ts +44 -0
  279. package/src/plugins/catalog.ts +2 -50
  280. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  281. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  282. package/src/plugins/ibkr/account-cache.ts +15 -120
  283. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  284. package/src/plugins/ibkr/config.test.ts +0 -18
  285. package/src/plugins/ibkr/dialogs.tsx +14 -56
  286. package/src/plugins/ibkr/flex.test.ts +57 -2
  287. package/src/plugins/ibkr/flex.ts +116 -12
  288. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  289. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  290. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  291. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  292. package/src/plugins/ibkr/gateway-types.ts +27 -0
  293. package/src/plugins/ibkr/index.test.tsx +3 -11
  294. package/src/plugins/ibkr/index.tsx +1 -0
  295. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  296. package/src/plugins/ibkr/price-normalization.ts +1 -0
  297. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  298. package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
  299. package/src/plugins/ibkr/trade-tab.tsx +166 -117
  300. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  301. package/src/plugins/pane-manager.test.ts +3 -1
  302. package/src/plugins/pane-manager.ts +93 -19
  303. package/src/plugins/plugin-runtime.test.tsx +176 -74
  304. package/src/plugins/plugin-runtime.tsx +228 -10
  305. package/src/plugins/prediction-markets/cache.ts +49 -0
  306. package/src/plugins/prediction-markets/categories.ts +0 -10
  307. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  308. package/src/plugins/prediction-markets/chart.tsx +21 -20
  309. package/src/plugins/prediction-markets/cli.ts +1 -0
  310. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  311. package/src/plugins/prediction-markets/controller.ts +37 -108
  312. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  313. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  314. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  315. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  316. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  317. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  318. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  319. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  320. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  321. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  322. package/src/plugins/prediction-markets/launch.ts +1 -1
  323. package/src/plugins/prediction-markets/metrics.ts +25 -12
  324. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  325. package/src/plugins/prediction-markets/pane.tsx +180 -82
  326. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  327. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  328. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  329. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  330. package/src/plugins/prediction-markets/settings.ts +0 -5
  331. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  332. package/src/plugins/registry.test.ts +307 -0
  333. package/src/plugins/registry.ts +293 -82
  334. package/src/react/input.ts +61 -0
  335. package/src/react/plugins/plugin-slot.tsx +21 -0
  336. package/src/react/use-raf-callback.ts +44 -0
  337. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  338. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  339. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  340. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  341. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  342. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  343. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  344. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  345. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  346. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  347. package/src/renderers/electrobun/bun/index.ts +1149 -0
  348. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  349. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  350. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  351. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  352. package/src/renderers/electrobun/view/app-services.ts +348 -0
  353. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  354. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  355. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  356. package/src/renderers/electrobun/view/config-host.ts +35 -0
  357. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  358. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  359. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  360. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  361. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  362. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  363. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  364. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  365. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  366. package/src/renderers/electrobun/view/input-host.tsx +112 -0
  367. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  368. package/src/renderers/electrobun/view/key-event.ts +134 -0
  369. package/src/renderers/electrobun/view/main.tsx +104 -0
  370. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  372. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  373. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  374. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  375. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  376. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  377. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  378. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  379. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  380. package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
  381. package/src/renderers/electrobun/view/update-host.ts +38 -0
  382. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  383. package/src/renderers/opentui/dialog-host.tsx +56 -0
  384. package/src/renderers/opentui/host.tsx +140 -0
  385. package/src/renderers/opentui/image-loader.test.ts +25 -0
  386. package/src/renderers/opentui/image-loader.ts +105 -0
  387. package/src/renderers/opentui/image-surface.tsx +266 -0
  388. package/src/renderers/opentui/input-host.tsx +63 -0
  389. package/src/renderers/opentui/start.tsx +71 -0
  390. package/src/renderers/opentui/test-utils.tsx +76 -0
  391. package/src/renderers/opentui/toast-host.tsx +10 -0
  392. package/src/renderers/opentui/ui-host.tsx +98 -0
  393. package/src/sources/gloomberb-cloud.test.ts +288 -17
  394. package/src/sources/gloomberb-cloud.ts +176 -9
  395. package/src/sources/provider-errors.ts +1 -0
  396. package/src/sources/provider-router.test.ts +220 -81
  397. package/src/sources/provider-router.ts +294 -111
  398. package/src/sources/sec-edgar.ts +19 -6
  399. package/src/sources/yahoo-finance.test.ts +0 -5
  400. package/src/sources/yahoo-finance.ts +420 -11
  401. package/src/state/app-activity.ts +7 -7
  402. package/src/state/app-bootstrap.ts +114 -40
  403. package/src/state/app-context-selector.test.tsx +70 -1
  404. package/src/state/app-context.test.ts +71 -3
  405. package/src/state/app-context.test.tsx +14 -7
  406. package/src/state/app-context.tsx +209 -869
  407. package/src/state/config-save-scheduler.ts +35 -0
  408. package/src/state/pane-scroll-registry.tsx +254 -0
  409. package/src/state/persist-scheduler.test.ts +67 -0
  410. package/src/state/persist-scheduler.ts +69 -0
  411. package/src/state/selectors-ui.ts +55 -0
  412. package/src/state/selectors.ts +15 -71
  413. package/src/state/session-persistence.test.ts +1 -1
  414. package/src/state/ticker-refresh-queue.ts +52 -3
  415. package/src/state/use-inline-tickers.ts +18 -16
  416. package/src/state/use-quote-streaming.test.ts +0 -6
  417. package/src/state/use-quote-streaming.test.tsx +1 -1
  418. package/src/state/use-quote-streaming.ts +3 -2
  419. package/src/state/use-session-snapshot.ts +32 -17
  420. package/src/test-support/plugin-runtime.ts +149 -0
  421. package/src/theme/color-utils.ts +1 -0
  422. package/src/theme/colors.ts +16 -0
  423. package/src/types/broker.ts +16 -1
  424. package/src/types/capability-route-source.ts +23 -0
  425. package/src/types/config.test.ts +23 -0
  426. package/src/types/config.ts +145 -10
  427. package/src/types/context-menu.ts +95 -0
  428. package/src/types/data-provider.ts +34 -3
  429. package/src/types/desktop-menu.ts +13 -0
  430. package/src/types/desktop-window.ts +29 -0
  431. package/src/types/financials.ts +123 -2
  432. package/src/types/instrument.ts +1 -0
  433. package/src/types/news-source.ts +9 -0
  434. package/src/types/persistence.ts +1 -0
  435. package/src/types/plugin.ts +32 -8
  436. package/src/ui/ascii-font.ts +85 -0
  437. package/src/ui/context-menu.test.ts +94 -0
  438. package/src/ui/context-menu.tsx +433 -0
  439. package/src/ui/dialog.tsx +55 -0
  440. package/src/ui/host.tsx +364 -0
  441. package/src/ui/index.tsx +134 -0
  442. package/src/ui/toast.tsx +40 -0
  443. package/src/updater.test.ts +69 -0
  444. package/src/updater.ts +61 -18
  445. package/src/utils/api-client.test.ts +99 -9
  446. package/src/utils/api-client.ts +378 -18
  447. package/src/utils/back-navigation.ts +6 -0
  448. package/src/utils/cli-output.ts +1 -0
  449. package/src/utils/currency-units.test.ts +1 -6
  450. package/src/utils/currency-units.ts +1 -0
  451. package/src/utils/debug-log.ts +68 -0
  452. package/src/utils/dom-screenshot.ts +326 -0
  453. package/src/utils/exchange-rate-map.ts +1 -0
  454. package/src/utils/exchanges.ts +214 -0
  455. package/src/utils/format.test.ts +7 -0
  456. package/src/utils/format.ts +1 -0
  457. package/src/utils/fuzzy-search.ts +1 -0
  458. package/src/utils/html-entities.test.ts +8 -0
  459. package/src/utils/html-entities.ts +32 -0
  460. package/src/utils/http-transport.ts +11 -0
  461. package/src/utils/main-thread-monitor.ts +127 -0
  462. package/src/utils/perf-marks.ts +54 -0
  463. package/src/utils/quote-freshness.ts +27 -50
  464. package/src/utils/quote-resolution.test.ts +5 -5
  465. package/src/utils/quote-resolution.ts +0 -1
  466. package/src/utils/quote-time.ts +1 -5
  467. package/src/utils/sec.test.ts +0 -12
  468. package/src/utils/selection-clipboard.ts +14 -6
  469. package/src/utils/shortcut-labels.test.ts +20 -0
  470. package/src/utils/shortcut-labels.ts +67 -0
  471. package/src/utils/slugify.ts +1 -0
  472. package/src/utils/terminal-input-reset.ts +1 -0
  473. package/src/utils/text-wrap.ts +82 -0
  474. package/src/utils/throttled-fetch.test.ts +19 -0
  475. package/src/utils/throttled-fetch.ts +11 -9
  476. package/src/utils/ticker-list.test.ts +1 -7
  477. package/src/utils/ticker-list.ts +1 -0
  478. package/src/utils/timestamp.ts +29 -0
  479. package/src/utils/tweet-text.test.ts +13 -0
  480. package/src/utils/tweet-text.ts +18 -0
  481. package/src/utils/url.ts +11 -0
  482. package/src/version.ts +1 -1
  483. package/src/components/chart/chart-control-hint.tsx +0 -39
  484. package/src/components/detail-feed-view.test.tsx +0 -180
  485. package/src/components/detail-feed-view.tsx +0 -531
  486. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  487. package/src/components/layout/pixel-mouse.test.ts +0 -40
  488. package/src/components/spinner.tsx +0 -2
  489. package/src/components/tab-bar.tsx +0 -17
  490. package/src/components/ui/multi-select-chips.tsx +0 -69
  491. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  492. package/src/plugins/builtin/ask-ai.tsx +0 -9
  493. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  494. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  495. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  496. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  497. package/src/plugins/builtin/fred-settings.ts +0 -10
  498. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  499. package/src/plugins/builtin/options-availability.ts +0 -158
  500. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  501. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  502. package/src/plugins/prediction-markets/status.ts +0 -24
  503. package/src/plugins/prediction-markets/table.tsx +0 -87
  504. package/src/utils/quote-time.test.ts +0 -22
  505. package/src/utils/selection-clipboard.test.ts +0 -43
  506. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -1,17 +1,18 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import type { GloomPlugin, DetailTabProps } from "../../types/plugin";
1
+ import { Box, Text } from "../../ui";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import type { DetailTabProps, GloomPlugin, PaneProps } from "../../types/plugin";
3
4
  import type { SecFilingItem } from "../../types/data-provider";
4
5
  import { useResolvedEntryValue, useSecFilingContent, useSecFilingsQuery } from "../../market-data/hooks";
5
6
  import { instrumentFromTicker } from "../../market-data/request-types";
6
7
  import { usePluginPaneState } from "../../plugins/plugin-runtime";
7
8
  import { usePaneTicker } from "../../state/app-context";
8
9
  import { colors } from "../../theme/colors";
9
- import { Spinner } from "../../components/spinner";
10
- import { DetailFeedView, type DetailFeedItem } from "../../components/detail-feed-view";
10
+ import { FeedDataTableStackView, Spinner, useExternalLinkFooter, type FeedDataTableItem } from "../../components";
11
11
  import { isUsEquityTicker } from "../../utils/sec";
12
12
  import { getSharedMarketDataCoordinator } from "../../market-data/coordinator";
13
13
  import { parseForm4Xml, transactionTypeLabel } from "./insider/insider-data";
14
14
  import { formatCompact, formatCurrency } from "../../utils/format";
15
+ import { createTickerSurfacePaneTemplate } from "./ticker-surface";
15
16
 
16
17
  const SEC_FILING_LIMIT = 50;
17
18
  const OWNERSHIP_FORMS = new Set(["3", "4", "5"]);
@@ -85,13 +86,13 @@ function wrapMessageLines(text: string, width: number): string[] {
85
86
  function renderNotice(message: string, width: number) {
86
87
  const lines = wrapMessageLines(message, width - 4);
87
88
  return (
88
- <box flexDirection="column" paddingX={1} paddingY={1}>
89
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
89
90
  {lines.map((line, index) => (
90
- <box key={index} height={1}>
91
- <text fg={colors.textDim}>{line}</text>
92
- </box>
91
+ <Box key={index} height={1}>
92
+ <Text fg={colors.textDim}>{line}</Text>
93
+ </Box>
93
94
  ))}
94
- </box>
95
+ </Box>
95
96
  );
96
97
  }
97
98
 
@@ -178,7 +179,7 @@ function toFeedItems(
178
179
  selectedAccessionNumber: string | undefined,
179
180
  contentCache: Map<string, string | null>,
180
181
  loadingContent: boolean,
181
- ): DetailFeedItem[] {
182
+ ): FeedDataTableItem[] {
182
183
  return filings.map((filing) => {
183
184
  const displayTitle = getFilingDisplayTitle(filing);
184
185
  const formDesc = getFormDescription(filing.form);
@@ -221,17 +222,16 @@ function toFeedItems(
221
222
  : form4Detail ?? fetchedContent ?? fallbackBody
222
223
  )
223
224
  : form4Detail ?? fallbackBody,
224
- detailNote: filing.filingUrl || undefined,
225
225
  };
226
226
  });
227
227
  }
228
228
 
229
- function SecTab({ width, height, focused }: DetailTabProps) {
229
+ export function SecView({ width, height, focused }: Pick<DetailTabProps, "width" | "height" | "focused">) {
230
230
  const { ticker } = usePaneTicker();
231
231
  const selectionKey = `selectedIdx:${ticker?.metadata.ticker ?? "none"}`;
232
232
  const [selectedIdx, setSelectedIdx] = usePluginPaneState<number>(selectionKey, 0);
233
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
234
233
  const [contentCache, setContentCache] = useState<Map<string, string | null>>(new Map());
234
+ const [openItemId, setOpenItemId] = useState<string | null>(null);
235
235
  const contentFetchRef = useRef(0);
236
236
  const eligibleTicker = isUsEquityTicker(ticker);
237
237
  const instrument = instrumentFromTicker(ticker, ticker?.metadata.ticker ?? null);
@@ -245,12 +245,14 @@ function SecTab({ width, height, focused }: DetailTabProps) {
245
245
  const error = filingsEntry?.phase === "error" ? filingsEntry.error?.message ?? "Failed to load SEC filings" : null;
246
246
 
247
247
  useEffect(() => {
248
- setHoveredIdx(null);
249
248
  setContentCache(new Map());
250
249
  contentFetchRef.current += 1;
251
250
  }, [eligibleTicker, ticker?.metadata.exchange, ticker?.metadata.ticker]);
252
251
 
253
252
  const selected = filings[selectedIdx];
253
+ const openFiling = openItemId
254
+ ? filings.find((filing) => filing.accessionNumber === openItemId) ?? null
255
+ : null;
254
256
  const cachedSelectedContent = selected ? contentCache.get(selected.accessionNumber) : undefined;
255
257
  const filingContentEntry = useSecFilingContent(
256
258
  selected && cachedSelectedContent === undefined ? selected : null,
@@ -307,27 +309,49 @@ function SecTab({ width, height, focused }: DetailTabProps) {
307
309
  })();
308
310
  }, [filings]);
309
311
 
310
- if (!ticker) return <text fg={colors.textDim}>Select a ticker to view SEC filings.</text>;
312
+ const footerInfo = useMemo(() => [
313
+ ...(loading ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
314
+ ...(error ? [{ id: "error", parts: [{ text: "error", tone: "warning" as const }] }] : []),
315
+ ], [error, loading]);
316
+ useExternalLinkFooter({
317
+ registrationId: "sec",
318
+ focused,
319
+ url: error ? null : openFiling?.filingUrl,
320
+ source: openFiling?.form,
321
+ info: footerInfo,
322
+ label: "filing",
323
+ });
324
+
325
+ if (!ticker) return <Text fg={colors.textDim}>Select a ticker to view SEC filings.</Text>;
311
326
  if (!eligibleTicker) return renderNotice("SEC filings are only shown for US equities.", width);
312
327
  if (loading && filings.length === 0) return <Spinner label="Loading SEC filings..." />;
313
328
  if (error) return renderNotice(`Error: ${error}`, width);
314
329
  if (filings.length === 0) return renderNotice(`No recent SEC filings for ${ticker.metadata.ticker}.`, width);
315
330
 
316
331
  return (
317
- <DetailFeedView
332
+ <FeedDataTableStackView
318
333
  width={width}
319
334
  height={height}
320
335
  focused={focused}
321
336
  items={toFeedItems(filings, selected?.accessionNumber, contentCache, loadingContent)}
322
337
  selectedIdx={selectedIdx}
323
- hoveredIdx={hoveredIdx}
324
338
  onSelect={setSelectedIdx}
325
- onHover={setHoveredIdx}
326
- listVariant="single-line"
339
+ onOpenItemIdChange={setOpenItemId}
340
+ sourceLabel="Form"
341
+ titleLabel="Filing"
342
+ emptyStateTitle="No SEC filings."
327
343
  />
328
344
  );
329
345
  }
330
346
 
347
+ function SecTab(props: DetailTabProps) {
348
+ return <SecView {...props} />;
349
+ }
350
+
351
+ function SecPane({ focused, width, height }: PaneProps) {
352
+ return <SecView focused={focused} width={width} height={height} />;
353
+ }
354
+
331
355
  export const secPlugin: GloomPlugin = {
332
356
  id: "sec",
333
357
  name: "SEC",
@@ -335,6 +359,30 @@ export const secPlugin: GloomPlugin = {
335
359
  description: "Recent SEC filings for US equities",
336
360
  toggleable: true,
337
361
 
362
+ panes: [
363
+ {
364
+ id: "sec",
365
+ name: "SEC",
366
+ icon: "S",
367
+ component: SecPane,
368
+ defaultPosition: "right",
369
+ defaultMode: "floating",
370
+ defaultFloatingSize: { width: 100, height: 32 },
371
+ },
372
+ ],
373
+
374
+ paneTemplates: [
375
+ createTickerSurfacePaneTemplate({
376
+ id: "sec-pane",
377
+ paneId: "sec",
378
+ label: "SEC",
379
+ description: "Recent SEC filings for the selected ticker.",
380
+ keywords: ["sec", "filings", "10-k", "10-q", "8-k"],
381
+ shortcut: "SEC",
382
+ canCreate: (_context, options) => !options?.ticker || isUsEquityTicker(options.ticker),
383
+ }),
384
+ ],
385
+
338
386
  setup(ctx) {
339
387
  ctx.registerDetailTab({
340
388
  id: "sec",
@@ -1,12 +1,9 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import type { ScrollBoxRenderable } from "@opentui/core";
3
- import { useKeyboard } from "@opentui/react";
4
- import { DataTable, type DataTableCell, type DataTableColumn } from "../../../components";
2
+ import { DataTableView, usePaneFooter, type DataTableCell, type DataTableColumn, type DataTableKeyEvent } from "../../../components";
5
3
  import type { GloomPlugin, PaneProps } from "../../../types/plugin";
6
4
  import { colors, priceColor } from "../../../theme/colors";
7
5
  import { formatCurrency, formatPercentRaw } from "../../../utils/format";
8
- import { usePluginPaneState, usePluginTickerActions } from "../../plugin-runtime";
9
- import { getSharedDataProvider } from "../../registry";
6
+ import { useAssetData, usePluginPaneState, usePluginTickerActions } from "../../plugin-runtime";
10
7
  import { SECTORS, type SectorDef } from "./sector-data";
11
8
 
12
9
  const REFRESH_INTERVAL_MS = 60_000;
@@ -113,6 +110,7 @@ function nextSortPreference(current: SectorSortPreference, columnId: string): Se
113
110
  }
114
111
 
115
112
  export function SectorPerformancePane({ focused, width, height }: PaneProps) {
113
+ const dataProvider = useAssetData();
116
114
  const { navigateTicker } = usePluginTickerActions();
117
115
  const [rows, setRows] = useState<SectorRow[]>(
118
116
  SECTORS.map((sector) => ({ ...sector, price: null, changePercent: null, currency: "USD", loading: true })),
@@ -122,12 +120,9 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
122
120
  "sortPreference",
123
121
  DEFAULT_SORT_PREFERENCE,
124
122
  );
125
- const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
126
123
  const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
127
124
 
128
125
  const fetchGenRef = useRef(0);
129
- const scrollRef = useRef<ScrollBoxRenderable>(null);
130
- const headerScrollRef = useRef<ScrollBoxRenderable>(null);
131
126
 
132
127
  const columns = useMemo(() => buildColumns(width), [width]);
133
128
  const sortedRows = useMemo(() => sortRows(rows, sortPreference), [rows, sortPreference]);
@@ -136,23 +131,10 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
136
131
  : -1;
137
132
  const activeIdx = selectedIdx >= 0 ? selectedIdx : (sortedRows.length > 0 ? 0 : -1);
138
133
 
139
- const syncHeaderScroll = useCallback(() => {
140
- const bodyScrollBox = scrollRef.current;
141
- const headerScrollBox = headerScrollRef.current;
142
- if (bodyScrollBox && headerScrollBox && headerScrollBox.scrollLeft !== bodyScrollBox.scrollLeft) {
143
- headerScrollBox.scrollLeft = bodyScrollBox.scrollLeft;
144
- }
145
- }, []);
146
-
147
- const handleBodyScrollActivity = useCallback(() => {
148
- syncHeaderScroll();
149
- }, [syncHeaderScroll]);
150
-
151
134
  const fetchAll = useCallback(() => {
152
135
  fetchGenRef.current += 1;
153
136
  const gen = fetchGenRef.current;
154
- const provider = getSharedDataProvider();
155
- if (!provider) {
137
+ if (!dataProvider) {
156
138
  setRows((prev) => prev.map((row) => ({ ...row, loading: false })));
157
139
  return;
158
140
  }
@@ -161,7 +143,7 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
161
143
 
162
144
  const fetches = SECTORS.map(async (sector) => {
163
145
  try {
164
- const quote = await provider.getQuote(sector.etf, "");
146
+ const quote = await dataProvider.getQuote(sector.etf, "");
165
147
  if (fetchGenRef.current !== gen) return;
166
148
  setRows((prev) =>
167
149
  prev.map((row) =>
@@ -191,7 +173,7 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
191
173
  setLastRefresh(new Date());
192
174
  }
193
175
  });
194
- }, []);
176
+ }, [dataProvider]);
195
177
 
196
178
  useEffect(() => {
197
179
  fetchAll();
@@ -207,17 +189,6 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
207
189
  }
208
190
  }, [selectedEtf, setSelectedEtf, sortedRows]);
209
191
 
210
- useEffect(() => {
211
- const scrollBox = scrollRef.current;
212
- if (!scrollBox?.viewport || activeIdx < 0) return;
213
- const viewportHeight = Math.max(scrollBox.viewport.height, 1);
214
- if (activeIdx < scrollBox.scrollTop) {
215
- scrollBox.scrollTo(activeIdx);
216
- } else if (activeIdx >= scrollBox.scrollTop + viewportHeight) {
217
- scrollBox.scrollTo(activeIdx - viewportHeight + 1);
218
- }
219
- }, [activeIdx, sortedRows.length]);
220
-
221
192
  const openRow = useCallback((row: SectorRow) => {
222
193
  navigateTicker(row.etf);
223
194
  }, [navigateTicker]);
@@ -239,20 +210,14 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
239
210
  setSelectedEtf(row.etf);
240
211
  }, [openRow, selectedEtf, setSelectedEtf]);
241
212
 
242
- useKeyboard((event) => {
243
- if (!focused) return;
244
-
245
- if (event.name === "j" || event.name === "down") {
246
- selectIndex(Math.min(activeIdx + 1, sortedRows.length - 1));
247
- } else if (event.name === "k" || event.name === "up") {
248
- selectIndex(Math.max(activeIdx - 1, 0));
249
- } else if (event.name === "return") {
250
- const row = sortedRows[activeIdx];
251
- if (row) openRow(row);
252
- } else if (event.name === "r") {
213
+ const handleTableKeyDown = useCallback((event: DataTableKeyEvent) => {
214
+ if (event.name === "r") {
215
+ event.preventDefault?.();
253
216
  fetchAll();
217
+ return true;
254
218
  }
255
- });
219
+ return false;
220
+ }, [fetchAll]);
256
221
 
257
222
  const renderCell = useCallback((
258
223
  row: SectorRow,
@@ -290,40 +255,37 @@ export function SectorPerformancePane({ focused, width, height }: PaneProps) {
290
255
  }
291
256
  }, []);
292
257
 
258
+ usePaneFooter("sectors", () => ({
259
+ info: [
260
+ {
261
+ id: "updated",
262
+ parts: [{ text: lastRefresh ? formatTime(lastRefresh) : "loading", tone: lastRefresh ? "value" : "muted" }],
263
+ },
264
+ ],
265
+ hints: [{ id: "refresh", key: "r", label: "efresh", onPress: fetchAll }],
266
+ }), [fetchAll, lastRefresh]);
267
+
293
268
  return (
294
- <box flexDirection="column" width={width} height={height}>
295
- <box flexDirection="row" height={1} paddingX={1}>
296
- {lastRefresh ? (
297
- <text fg={colors.textMuted}>{formatTime(lastRefresh)}</text>
298
- ) : (
299
- <text fg={colors.textMuted}>loading…</text>
300
- )}
301
- </box>
302
-
303
- <DataTable<SectorRow, SectorColumn>
304
- columns={columns}
305
- items={sortedRows}
306
- sortColumnId={sortPreference.columnId}
307
- sortDirection={sortPreference.direction}
308
- onHeaderClick={handleHeaderClick}
309
- headerScrollRef={headerScrollRef}
310
- scrollRef={scrollRef}
311
- syncHeaderScroll={syncHeaderScroll}
312
- onBodyScrollActivity={handleBodyScrollActivity}
313
- hoveredIdx={hoveredIdx}
314
- setHoveredIdx={setHoveredIdx}
315
- getItemKey={(row) => row.etf}
316
- isSelected={(row) => row.etf === selectedEtf}
317
- onSelect={selectRow}
318
- renderCell={renderCell}
319
- emptyStateTitle="No sector data available"
320
- showHorizontalScrollbar={false}
321
- />
322
-
323
- <box height={1} paddingX={1}>
324
- <text fg={colors.textMuted}>[r]efresh</text>
325
- </box>
326
- </box>
269
+ <DataTableView<SectorRow, SectorColumn>
270
+ focused={focused}
271
+ selectedIndex={activeIdx}
272
+ onSelectIndex={selectIndex}
273
+ onActivateIndex={(_index, row) => openRow(row)}
274
+ onRootKeyDown={handleTableKeyDown}
275
+ rootWidth={width}
276
+ rootHeight={height}
277
+ columns={columns}
278
+ items={sortedRows}
279
+ sortColumnId={sortPreference.columnId}
280
+ sortDirection={sortPreference.direction}
281
+ onHeaderClick={handleHeaderClick}
282
+ getItemKey={(row) => row.etf}
283
+ isSelected={(row) => row.etf === selectedEtf}
284
+ onSelect={selectRow}
285
+ renderCell={renderCell}
286
+ emptyStateTitle="No sector data available"
287
+ showHorizontalScrollbar={false}
288
+ />
327
289
  );
328
290
  }
329
291
 
@@ -1,3 +1,4 @@
1
+
1
2
  export interface SectorDef {
2
3
  name: string;
3
4
  etf: string;
@@ -1,6 +1,6 @@
1
- import { useTerminalDimensions } from "@opentui/react";
1
+ import { Box } from "../../../ui";
2
+ import { useViewport } from "../../../react/input";
2
3
  import { useCallback, useMemo } from "react";
3
- import { saveConfig } from "../../../data/config-store";
4
4
  import { ChartIndicatorSelector } from "../../../components/chart/chart-indicator-selector";
5
5
  import {
6
6
  buildIndicatorConfigFromSelection,
@@ -13,12 +13,9 @@ import {
13
13
  } from "../../../components/chart/indicators/options";
14
14
  import type { ChartAxisMode } from "../../../components/chart/chart-types";
15
15
  import { ResolvedStockChart } from "../../../components/chart/stock-chart";
16
- import { useAppState } from "../../../state/app-context";
17
16
  import type { TickerFinancials } from "../../../types/financials";
18
17
  import type { TickerRecord } from "../../../types/ticker";
19
- import { getSharedRegistry } from "../../registry";
20
-
21
- const TICKER_DETAIL_PLUGIN_ID = "ticker-detail";
18
+ import { usePluginConfigState, useSetPluginConfigStates } from "../../plugin-runtime";
22
19
 
23
20
  export function ChartTab({
24
21
  width,
@@ -41,13 +38,13 @@ export function ChartTab({
41
38
  ticker: TickerRecord | null;
42
39
  financials: TickerFinancials | null;
43
40
  }) {
44
- const { width: termWidth, height: termHeight } = useTerminalDimensions();
45
- const { state, dispatch } = useAppState();
41
+ const { width: termWidth, height: termHeight } = useViewport();
42
+ const [rawIndicatorSelection] = usePluginConfigState<unknown>(CHART_INDICATORS_PLUGIN_CONFIG_KEY, null);
43
+ const [indicatorSelectionVersion] = usePluginConfigState<unknown>(CHART_INDICATORS_PLUGIN_CONFIG_VERSION_KEY, null);
44
+ const setPluginConfigStates = useSetPluginConfigStates();
46
45
 
47
46
  const chartWidth = Math.max((width || Math.floor(termWidth * 0.55)) - 2, 30);
48
- const chartHeight = Math.max((height || termHeight - 8) - 2, 10);
49
- const rawIndicatorSelection = state.config.pluginConfig[TICKER_DETAIL_PLUGIN_ID]?.[CHART_INDICATORS_PLUGIN_CONFIG_KEY];
50
- const indicatorSelectionVersion = state.config.pluginConfig[TICKER_DETAIL_PLUGIN_ID]?.[CHART_INDICATORS_PLUGIN_CONFIG_VERSION_KEY];
47
+ const chartHeight = Math.max(height ?? termHeight - 8, 10);
51
48
  const hasStoredIndicatorSelection = Array.isArray(rawIndicatorSelection);
52
49
  const selectedIndicatorIds = useMemo(
53
50
  () => resolveChartIndicatorSelection(rawIndicatorSelection, indicatorSelectionVersion),
@@ -60,57 +57,46 @@ export function ChartTab({
60
57
  const showVolume = selectedIndicatorIds.includes("volume");
61
58
  const persistIndicatorSelection = useCallback((nextSelection: ChartIndicatorId[]) => {
62
59
  const normalized = normalizeChartIndicatorSelection(nextSelection);
63
- const nextConfig = {
64
- ...state.config,
65
- pluginConfig: {
66
- ...state.config.pluginConfig,
67
- [TICKER_DETAIL_PLUGIN_ID]: {
68
- ...(state.config.pluginConfig[TICKER_DETAIL_PLUGIN_ID] ?? {}),
69
- [CHART_INDICATORS_PLUGIN_CONFIG_KEY]: normalized,
70
- [CHART_INDICATORS_PLUGIN_CONFIG_VERSION_KEY]: CURRENT_CHART_INDICATORS_CONFIG_VERSION,
71
- },
72
- },
73
- };
74
- dispatch({ type: "SET_CONFIG", config: nextConfig });
75
- saveConfig(nextConfig).catch(() => {});
76
- getSharedRegistry()?.events.emit("config:changed", { config: nextConfig });
77
- }, [dispatch, state.config]);
60
+ setPluginConfigStates({
61
+ [CHART_INDICATORS_PLUGIN_CONFIG_KEY]: normalized,
62
+ [CHART_INDICATORS_PLUGIN_CONFIG_VERSION_KEY]: CURRENT_CHART_INDICATORS_CONFIG_VERSION,
63
+ });
64
+ }, [setPluginConfigStates]);
78
65
 
79
66
  return (
80
- <box
67
+ <Box
81
68
  flexDirection="column"
82
69
  paddingX={1}
83
70
  flexGrow={1}
84
71
  >
85
- <box
72
+ <Box
86
73
  flexDirection="column"
87
74
  flexGrow={1}
88
75
  onMouseDown={() => {
89
76
  if (!interactive) onActivate?.();
90
77
  }}
91
78
  >
79
+ <ChartIndicatorSelector
80
+ width={chartWidth}
81
+ selectedIds={selectedIndicatorIds}
82
+ onChange={persistIndicatorSelection}
83
+ variant="pane-hint"
84
+ shortcutActive={focused}
85
+ />
92
86
  <ResolvedStockChart
93
87
  width={chartWidth}
94
88
  height={chartHeight}
95
89
  focused={focused}
96
90
  interactive={interactive}
91
+ onActivate={onActivate}
97
92
  axisMode={axisMode}
98
93
  symbol={symbol}
99
94
  ticker={ticker}
100
95
  financials={financials}
101
96
  indicatorConfig={hasStoredIndicatorSelection ? selectedIndicatorConfig : undefined}
102
97
  showVolume={showVolume}
103
- footerControls={(
104
- <ChartIndicatorSelector
105
- width={chartWidth}
106
- selectedIds={selectedIndicatorIds}
107
- onChange={persistIndicatorSelection}
108
- variant="hint"
109
- shortcutActive={focused}
110
- />
111
- )}
112
98
  />
113
- </box>
114
- </box>
99
+ </Box>
100
+ </Box>
115
101
  );
116
102
  }