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,21 +1,17 @@
1
+ import { Box, ScrollBox, Text } from "../../../ui";
1
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { StyledText, TextAttributes, type ScrollBoxRenderable } from "@opentui/core";
3
- import { useKeyboard } from "@opentui/react";
4
- import { DataTable, PageStackView, type DataTableCell, type DataTableColumn } from "../../../components";
5
- import type { GloomPlugin, PaneProps } from "../../../types/plugin";
3
+ import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
4
+ import { useShortcut } from "../../../react/input";
5
+ import { DataTableStackView, StaticChartSurface, usePaneFooter, type DataTableCell, type DataTableColumn } from "../../../components";
6
+ import type { GloomPluginContext, PaneProps } from "../../../types/plugin";
6
7
  import { colors, blendHex } from "../../../theme/colors";
7
- import {
8
- attachEconCalendarPersistence,
9
- fetchEconCalendar,
10
- resetEconCalendarPersistence,
11
- } from "./calendar-source";
8
+ import { fetchEconCalendar } from "./calendar-source";
12
9
  import type { EconEvent, EconImpact } from "./types";
13
- import { usePluginConfigState, usePluginTickerActions } from "../../plugin-runtime";
10
+ import { usePluginTickerActions } from "../../plugin-runtime";
14
11
  import { resolveFredMapping } from "./fred-series-map";
15
- import { fetchFredObservations, fetchFredSeriesInfo, type FredObservation, type FredSeriesInfo } from "./fred-client";
16
- import { renderChart, resolveChartPalette, type StyledContent } from "../../../components/chart/chart-renderer";
12
+ import { resolveChartPalette } from "../../../components/chart/chart-renderer";
17
13
  import type { ProjectedChartPoint } from "../../../components/chart/chart-data";
18
- import { FRED_API_KEY_COMMAND_LABEL, FRED_API_KEY_CONFIG_KEY } from "../fred-settings";
14
+ import { apiClient, type CloudFredObservationPayload, type CloudFredSeriesInfoPayload } from "../../../utils/api-client";
19
15
 
20
16
  const CACHE_TTL_MS = 15 * 60 * 1000;
21
17
 
@@ -146,8 +142,8 @@ type EconCalendarColumn = DataTableColumn & { id: EconCalendarColumnId };
146
142
  // ---------------------------------------------------------------------------
147
143
 
148
144
  interface FredCache {
149
- observations: FredObservation[];
150
- info: FredSeriesInfo | null;
145
+ observations: CloudFredObservationPayload[];
146
+ info: CloudFredSeriesInfoPayload | null;
151
147
  }
152
148
 
153
149
  interface EconDetailViewProps {
@@ -158,7 +154,6 @@ interface EconDetailViewProps {
158
154
  }
159
155
 
160
156
  function EconDetailView({ event, width, height, focused }: EconDetailViewProps) {
161
- const [fredApiKey] = usePluginConfigState<string>(FRED_API_KEY_CONFIG_KEY, "");
162
157
  const { navigateTicker } = usePluginTickerActions();
163
158
  const cacheRef = useRef<Map<string, FredCache>>(new Map());
164
159
  const [loading, setLoading] = useState(false);
@@ -170,7 +165,6 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
170
165
 
171
166
  useEffect(() => {
172
167
  if (!mapping) return;
173
- if (!fredApiKey) return;
174
168
 
175
169
  const cached = cacheRef.current.get(mapping.seriesId);
176
170
  if (cached) {
@@ -185,11 +179,8 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
185
179
  fiveYearsAgo.setFullYear(fiveYearsAgo.getFullYear() - 5);
186
180
  const startDate = `${fiveYearsAgo.getFullYear()}-${String(fiveYearsAgo.getMonth() + 1).padStart(2, "0")}-${String(fiveYearsAgo.getDate()).padStart(2, "0")}`;
187
181
 
188
- Promise.all([
189
- fetchFredObservations(fredApiKey, mapping.seriesId, { startDate, sortOrder: "asc" }),
190
- fetchFredSeriesInfo(fredApiKey, mapping.seriesId),
191
- ])
192
- .then(([observations, info]) => {
182
+ apiClient.getCloudFredSeries(mapping.seriesId, { startDate, sortOrder: "asc" })
183
+ .then(({ observations, info }) => {
193
184
  const entry: FredCache = { observations, info };
194
185
  cacheRef.current.set(mapping.seriesId, entry);
195
186
  setData(entry);
@@ -200,7 +191,7 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
200
191
  .finally(() => {
201
192
  setLoading(false);
202
193
  });
203
- }, [mapping?.seriesId, fredApiKey]);
194
+ }, [mapping?.seriesId]);
204
195
 
205
196
  const scrollDetailBy = useCallback((delta: number) => {
206
197
  const scrollBox = scrollRef.current;
@@ -209,7 +200,7 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
209
200
  scrollBox.scrollTop = Math.max(0, Math.min(maxScrollTop, scrollBox.scrollTop + delta));
210
201
  }, []);
211
202
 
212
- useKeyboard((ev) => {
203
+ useShortcut((ev) => {
213
204
  if (!focused) return;
214
205
  if (ev.name === "j" || ev.name === "down") {
215
206
  ev.stopPropagation?.();
@@ -225,70 +216,52 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
225
216
  // No FRED mapping
226
217
  if (!mapping) {
227
218
  return (
228
- <box flexDirection="column" width={width} height={height}>
229
- <box height={1} paddingX={1} flexDirection="row">
230
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
219
+ <Box flexDirection="column" width={width} height={height}>
220
+ <Box height={1} paddingX={1} flexDirection="row">
221
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
231
222
  {event.event}
232
- </text>
233
- </box>
234
- <box flexGrow={1} justifyContent="center" alignItems="center">
235
- <text fg={colors.textMuted}>No historical data available for this indicator</text>
236
- </box>
237
- </box>
238
- );
239
- }
240
-
241
- // No API key
242
- if (!fredApiKey) {
243
- return (
244
- <box flexDirection="column" width={width} height={height}>
245
- <box height={1} paddingX={1} flexDirection="row">
246
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
247
- {event.event}
248
- </text>
249
- <box flexGrow={1} />
250
- <text fg={colors.textDim}>{mapping.seriesId}</text>
251
- </box>
252
- <box flexGrow={1} justifyContent="center" alignItems="center">
253
- <text fg={colors.textMuted}>{`Configure FRED API key: type '${FRED_API_KEY_COMMAND_LABEL}' in command bar`}</text>
254
- </box>
255
- </box>
223
+ </Text>
224
+ </Box>
225
+ <Box flexGrow={1} justifyContent="center" alignItems="center">
226
+ <Text fg={colors.textMuted}>No historical data available for this indicator</Text>
227
+ </Box>
228
+ </Box>
256
229
  );
257
230
  }
258
231
 
259
232
  // Loading
260
233
  if (loading) {
261
234
  return (
262
- <box flexDirection="column" width={width} height={height}>
263
- <box height={1} paddingX={1} flexDirection="row">
264
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
235
+ <Box flexDirection="column" width={width} height={height}>
236
+ <Box height={1} paddingX={1} flexDirection="row">
237
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
265
238
  {event.event}
266
- </text>
267
- <box flexGrow={1} />
268
- <text fg={colors.textDim}>{mapping.seriesId}</text>
269
- </box>
270
- <box flexGrow={1} justifyContent="center" alignItems="center">
271
- <text fg={colors.textMuted}>Loading...</text>
272
- </box>
273
- </box>
239
+ </Text>
240
+ <Box flexGrow={1} />
241
+ <Text fg={colors.textDim}>{mapping.seriesId}</Text>
242
+ </Box>
243
+ <Box flexGrow={1} justifyContent="center" alignItems="center">
244
+ <Text fg={colors.textMuted}>Loading...</Text>
245
+ </Box>
246
+ </Box>
274
247
  );
275
248
  }
276
249
 
277
250
  // Error
278
251
  if (error) {
279
252
  return (
280
- <box flexDirection="column" width={width} height={height}>
281
- <box height={1} paddingX={1} flexDirection="row">
282
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
253
+ <Box flexDirection="column" width={width} height={height}>
254
+ <Box height={1} paddingX={1} flexDirection="row">
255
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
283
256
  {event.event}
284
- </text>
285
- <box flexGrow={1} />
286
- <text fg={colors.textDim}>{mapping.seriesId}</text>
287
- </box>
288
- <box flexGrow={1} justifyContent="center" alignItems="center">
289
- <text fg={colors.negative}>{error}</text>
290
- </box>
291
- </box>
257
+ </Text>
258
+ <Box flexGrow={1} />
259
+ <Text fg={colors.textDim}>{mapping.seriesId}</Text>
260
+ </Box>
261
+ <Box flexGrow={1} justifyContent="center" alignItems="center">
262
+ <Text fg={colors.negative}>{error}</Text>
263
+ </Box>
264
+ </Box>
292
265
  );
293
266
  }
294
267
 
@@ -296,11 +269,11 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
296
269
 
297
270
  const { observations, info } = data;
298
271
  const chartWidth = Math.max(10, width - 2);
299
- const chartHeight = Math.min(16, Math.max(8, Math.floor(height * 0.3)));
272
+ const chartHeight = Math.min(18, Math.max(9, Math.floor(height * 0.38)));
300
273
 
301
274
  // Build chart points (ascending order for chart), exclude null values
302
275
  const chartPoints: ProjectedChartPoint[] = observations
303
- .filter((obs): obs is FredObservation & { value: number } => obs.value != null)
276
+ .filter((obs): obs is CloudFredObservationPayload & { value: number } => obs.value != null)
304
277
  .map((obs) => ({
305
278
  date: new Date(obs.date),
306
279
  open: obs.value,
@@ -312,21 +285,6 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
312
285
 
313
286
  const palette = resolveChartPalette(colors, "positive");
314
287
 
315
- let chartResult: ReturnType<typeof renderChart> | null = null;
316
- if (chartPoints.length >= 2) {
317
- chartResult = renderChart(chartPoints, {
318
- width: chartWidth,
319
- height: chartHeight,
320
- showVolume: false,
321
- volumeHeight: 0,
322
- cursorX: null,
323
- cursorY: null,
324
- mode: "area",
325
- colors: palette,
326
- timeAxisDates: chartPoints.map((p) => p.date),
327
- });
328
- }
329
-
330
288
  // Table rows (descending for display, last 12)
331
289
  // For "change" mode, compute m/m or q/q percent change from consecutive observations
332
290
  const descObs = [...observations].reverse();
@@ -360,114 +318,121 @@ function EconDetailView({ event, width, height, focused }: EconDetailViewProps)
360
318
  const separatorLine = "─".repeat(Math.max(0, width - 2));
361
319
 
362
320
  return (
363
- <box flexDirection="column" width={width} height={height}>
321
+ <Box flexDirection="column" width={width} height={height}>
364
322
  {/* Header */}
365
- <box height={1} paddingX={1} flexDirection="row">
366
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
323
+ <Box height={1} paddingX={1} flexDirection="row">
324
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
367
325
  {title}
368
- </text>
369
- <box flexGrow={1} />
370
- <text fg={colors.textDim}>{mapping.seriesId}</text>
371
- </box>
372
- <box height={1} paddingX={1} flexDirection="row">
373
- <text fg={colors.textMuted}>
326
+ </Text>
327
+ <Box flexGrow={1} />
328
+ <Text fg={colors.textDim}>{mapping.seriesId}</Text>
329
+ </Box>
330
+ <Box height={1} paddingX={1} flexDirection="row">
331
+ <Text fg={colors.textMuted}>
374
332
  {[units, info?.frequency, info?.seasonalAdjustment].filter(Boolean).join(" · ")}
375
- </text>
376
- </box>
333
+ </Text>
334
+ </Box>
377
335
 
378
336
  {/* Event context — scheduled time, forecast, prior */}
379
- <box paddingX={1} flexDirection="row" height={1}>
380
- <text fg={colors.textDim}>Scheduled: </text>
381
- <text fg={colors.text}>{event.time}</text>
337
+ <Box paddingX={1} flexDirection="row" height={1}>
338
+ <Text fg={colors.textDim}>Scheduled: </Text>
339
+ <Text fg={colors.text}>{event.time}</Text>
382
340
  {event.forecast ? (
383
341
  <>
384
- <text fg={colors.textDim}> Forecast: </text>
385
- <text fg={colors.text}>{event.forecast}</text>
342
+ <Text fg={colors.textDim}> Forecast: </Text>
343
+ <Text fg={colors.text}>{event.forecast}</Text>
386
344
  </>
387
345
  ) : null}
388
346
  {event.prior ? (
389
347
  <>
390
- <text fg={colors.textDim}> Prior: </text>
391
- <text fg={colors.text}>{event.prior}</text>
348
+ <Text fg={colors.textDim}> Prior: </Text>
349
+ <Text fg={colors.text}>{event.prior}</Text>
392
350
  </>
393
351
  ) : null}
394
- </box>
352
+ </Box>
395
353
 
396
354
  {/* Scrollable content */}
397
- <scrollbox ref={scrollRef} flexGrow={1} scrollY focusable={false}>
398
- <box flexDirection="column">
355
+ <ScrollBox ref={scrollRef} flexGrow={1} scrollY focusable={false}>
356
+ <Box flexDirection="column">
399
357
  {/* Chart */}
400
- {chartResult ? (
401
- <box flexDirection="column" paddingX={1} marginTop={1}>
402
- <box flexDirection="column" height={chartHeight} backgroundColor={palette.bgColor}>
403
- {chartResult.lines.map((line, i) => (
404
- <text key={i} content={chartLineContent(line)} />
405
- ))}
406
- </box>
407
- <text fg={colors.textDim}>{chartResult.timeLabels}</text>
408
- </box>
358
+ {chartPoints.length >= 2 ? (
359
+ <Box flexDirection="column" paddingX={1} marginTop={1}>
360
+ <StaticChartSurface
361
+ points={chartPoints}
362
+ width={chartWidth}
363
+ height={chartHeight}
364
+ mode="area"
365
+ colors={palette}
366
+ timeAxisDates={chartPoints.map((p) => p.date)}
367
+ showTimeAxis
368
+ timeAxisColor={colors.textDim}
369
+ yAxisLabel={units ? `Value (${units})` : "Value"}
370
+ yAxisColor={colors.textDim}
371
+ formatYAxisValue={(value) => formatCompactAxisValue(value, units)}
372
+ />
373
+ </Box>
409
374
  ) : (
410
- <box paddingX={1} marginTop={1}>
411
- <text fg={colors.textMuted}>Not enough data for chart</text>
412
- </box>
375
+ <Box paddingX={1} marginTop={1}>
376
+ <Text fg={colors.textMuted}>Not enough data for chart</Text>
377
+ </Box>
413
378
  )}
414
379
 
415
380
  {/* Separator */}
416
- <box paddingX={1} height={1} marginTop={1}>
417
- <text fg={colors.border}>{separatorLine}</text>
418
- </box>
381
+ <Box paddingX={1} height={1} marginTop={1}>
382
+ <Text fg={colors.border}>{separatorLine}</Text>
383
+ </Box>
419
384
 
420
385
  {/* Historical readings */}
421
- <box paddingX={1} height={1}>
422
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>HISTORICAL READINGS</text>
423
- </box>
424
- <box paddingX={1} flexDirection="row" height={1}>
425
- <box width={dateColWidth}>
426
- <text fg={colors.textDim}>DATE</text>
427
- </box>
428
- <box width={valueColWidth} justifyContent="flex-end">
429
- <text fg={colors.textDim}>VALUE</text>
430
- </box>
431
- </box>
386
+ <Box paddingX={1} height={1}>
387
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>HISTORICAL READINGS</Text>
388
+ </Box>
389
+ <Box paddingX={1} flexDirection="row" height={1}>
390
+ <Box width={dateColWidth}>
391
+ <Text fg={colors.textDim}>DATE</Text>
392
+ </Box>
393
+ <Box width={valueColWidth} justifyContent="flex-end">
394
+ <Text fg={colors.textDim}>VALUE</Text>
395
+ </Box>
396
+ </Box>
432
397
  {tableRows.map((row) => (
433
- <box key={row.date} paddingX={1} flexDirection="row" height={1}>
434
- <box width={dateColWidth}>
435
- <text fg={colors.textDim}>{row.date}</text>
436
- </box>
437
- <box width={valueColWidth} justifyContent="flex-end">
438
- <text fg={valueColor(row.display)}>{row.display}</text>
439
- </box>
440
- </box>
398
+ <Box key={row.date} paddingX={1} flexDirection="row" height={1}>
399
+ <Box width={dateColWidth}>
400
+ <Text fg={colors.textDim}>{row.date}</Text>
401
+ </Box>
402
+ <Box width={valueColWidth} justifyContent="flex-end">
403
+ <Text fg={valueColor(row.display)}>{row.display}</Text>
404
+ </Box>
405
+ </Box>
441
406
  ))}
442
407
 
443
408
  {/* Separator */}
444
409
  {mapping.relatedTickers.length > 0 ? (
445
- <box paddingX={1} height={1} marginTop={1}>
446
- <text fg={colors.border}>{separatorLine}</text>
447
- </box>
410
+ <Box paddingX={1} height={1} marginTop={1}>
411
+ <Text fg={colors.border}>{separatorLine}</Text>
412
+ </Box>
448
413
  ) : null}
449
414
 
450
415
  {/* Related tickers */}
451
416
  {mapping.relatedTickers.length > 0 ? (
452
- <box paddingX={1} height={1} flexDirection="row">
453
- <text fg={colors.textDim}>Related: </text>
417
+ <Box paddingX={1} height={1} flexDirection="row">
418
+ <Text fg={colors.textDim}>Related: </Text>
454
419
  {mapping.relatedTickers.map((ticker, i) => (
455
- <box
420
+ <Box
456
421
  key={ticker}
457
422
  marginLeft={i > 0 ? 2 : 0}
458
423
  onMouseDown={() => {
459
424
  navigateTicker(ticker);
460
425
  }}
461
426
  >
462
- <text fg={colors.textBright} attributes={TextAttributes.UNDERLINE}>{ticker}</text>
463
- </box>
427
+ <Text fg={colors.textBright} attributes={TextAttributes.UNDERLINE}>{ticker}</Text>
428
+ </Box>
464
429
  ))}
465
- </box>
430
+ </Box>
466
431
  ) : null}
467
- </box>
468
- </scrollbox>
432
+ </Box>
433
+ </ScrollBox>
469
434
 
470
- </box>
435
+ </Box>
471
436
  );
472
437
  }
473
438
 
@@ -491,8 +456,16 @@ function actualColor(actual: string | null, forecast: string | null): string {
491
456
  return colors.text;
492
457
  }
493
458
 
494
- function chartLineContent(line: string | StyledContent): string | StyledText {
495
- return typeof line === "string" ? line : new StyledText(line.chunks);
459
+ function formatCompactAxisValue(value: number, units: string): string {
460
+ const abs = Math.abs(value);
461
+ const normalizedUnits = units.toLowerCase();
462
+ if (normalizedUnits.includes("percent")) {
463
+ return `${value.toFixed(abs >= 10 ? 1 : 2)}%`;
464
+ }
465
+ if (abs >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(1)}B`;
466
+ if (abs >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
467
+ if (abs >= 1_000) return `${(value / 1_000).toFixed(abs >= 10_000 ? 0 : 1)}K`;
468
+ return value.toLocaleString("en-US", { maximumFractionDigits: abs >= 10 ? 1 : 2 });
496
469
  }
497
470
 
498
471
  // ---------------------------------------------------------------------------
@@ -504,7 +477,6 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
504
477
  const [loading, setLoading] = useState(false);
505
478
  const [error, setError] = useState<string | null>(null);
506
479
  const [selectedIdx, setSelectedIdx] = useState(0);
507
- const [hoveredRowIdx, setHoveredRowIdx] = useState<number | null>(null);
508
480
  const [impactFilter, setImpactFilter] = useState<ImpactFilter>("all");
509
481
  const [countryFilter, setCountryFilter] = useState<CountryFilter>("all");
510
482
  const [now, setNow] = useState(Date.now());
@@ -513,23 +485,8 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
513
485
  const fetchGenRef = useRef(0);
514
486
  const scrollRef = useRef<ScrollBoxRenderable>(null);
515
487
  const headerScrollRef = useRef<ScrollBoxRenderable>(null);
516
- const backfilledSeriesRef = useRef<Set<string>>(new Set());
517
-
518
- const [fredApiKey] = usePluginConfigState<string>(FRED_API_KEY_CONFIG_KEY, "");
519
-
520
- const syncHeaderScroll = useCallback(() => {
521
- const bodyScrollBox = scrollRef.current;
522
- const headerScrollBox = headerScrollRef.current;
523
- if (bodyScrollBox && headerScrollBox && headerScrollBox.scrollLeft !== bodyScrollBox.scrollLeft) {
524
- headerScrollBox.scrollLeft = bodyScrollBox.scrollLeft;
525
- }
526
- }, []);
527
488
 
528
- const handleBodyScrollActivity = useCallback(() => {
529
- syncHeaderScroll();
530
- }, [syncHeaderScroll]);
531
-
532
- const load = async (force = false) => {
489
+ const load = useCallback(async (force = false) => {
533
490
  fetchGenRef.current += 1;
534
491
  const gen = fetchGenRef.current;
535
492
  setLoading(true);
@@ -546,7 +503,7 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
546
503
  } finally {
547
504
  if (fetchGenRef.current === gen) setLoading(false);
548
505
  }
549
- };
506
+ }, []);
550
507
 
551
508
  useEffect(() => {
552
509
  if (sharedCache && Date.now() - sharedCache.fetchedAt < CACHE_TTL_MS) {
@@ -554,7 +511,7 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
554
511
  return;
555
512
  }
556
513
  load();
557
- }, []);
514
+ }, [load]);
558
515
 
559
516
  // Tick every 30s to update staleness + countdown
560
517
  useEffect(() => {
@@ -562,80 +519,6 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
562
519
  return () => clearInterval(interval);
563
520
  }, []);
564
521
 
565
- // Backfill actual values for past US events using FRED
566
- useEffect(() => {
567
- if (!fredApiKey || events.length === 0) return;
568
-
569
- const nowTs = Date.now();
570
- const pastEvents = events.filter(
571
- (ev) =>
572
- ev.date.getTime() < nowTs &&
573
- ev.actual === null &&
574
- resolveFredMapping(ev.event, ev.country),
575
- );
576
-
577
- // Group by series ID to deduplicate fetches
578
- const seriesGroups = new Map<string, { mapping: ReturnType<typeof resolveFredMapping>; events: EconEvent[] }>();
579
- for (const ev of pastEvents) {
580
- const mapping = resolveFredMapping(ev.event, ev.country)!;
581
- if (backfilledSeriesRef.current.has(mapping.seriesId)) continue;
582
- const group = seriesGroups.get(mapping.seriesId);
583
- if (group) {
584
- group.events.push(ev);
585
- } else {
586
- seriesGroups.set(mapping.seriesId, { mapping, events: [ev] });
587
- }
588
- }
589
-
590
- if (seriesGroups.size === 0) return;
591
-
592
- (async () => {
593
- const results = await Promise.allSettled(
594
- [...seriesGroups.entries()].map(async ([seriesId, { mapping }]) => {
595
- const obs = await fetchFredObservations(fredApiKey, seriesId, {
596
- limit: mapping!.displayMode === "change" ? 2 : 1,
597
- sortOrder: "desc",
598
- });
599
- backfilledSeriesRef.current.add(seriesId);
600
- if (obs.length === 0 || obs[0]!.value === null) return null;
601
-
602
- let formatted: string;
603
- if (mapping!.displayMode === "change" && obs.length >= 2 && obs[1]!.value) {
604
- // Compute month-over-month percent change
605
- const current = obs[0]!.value!;
606
- const prior = obs[1]!.value!;
607
- const pctChange = ((current - prior) / Math.abs(prior)) * 100;
608
- formatted = `${pctChange >= 0 ? "" : "-"}${Math.abs(pctChange).toFixed(1)}%`;
609
- } else {
610
- const value = obs[0]!.value!;
611
- formatted = value.toLocaleString("en-US", { maximumFractionDigits: 1 });
612
- }
613
- return { seriesId, formatted };
614
- }),
615
- );
616
-
617
- const actuals = new Map<string, string>();
618
- for (const result of results) {
619
- if (result.status === "fulfilled" && result.value) {
620
- actuals.set(result.value.seriesId, result.value.formatted);
621
- }
622
- }
623
-
624
- if (actuals.size === 0) return;
625
-
626
- setEvents((prev) =>
627
- prev.map((ev) => {
628
- if (ev.actual !== null) return ev;
629
- const mapping = resolveFredMapping(ev.event, ev.country);
630
- if (!mapping) return ev;
631
- const actual = actuals.get(mapping.seriesId);
632
- if (!actual) return ev;
633
- return { ...ev, actual };
634
- }),
635
- );
636
- })();
637
- }, [events, fredApiKey]);
638
-
639
522
  const filtered = useMemo(() => events
640
523
  .filter((ev) => matchesImpact(ev, impactFilter) && matchesCountry(ev, countryFilter))
641
524
  .sort((a, b) => b.date.getTime() - a.date.getTime()),
@@ -706,49 +589,44 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
706
589
  // Next upcoming event for countdown
707
590
  const nextEvent = nextUpcomingEventIdx >= 0 ? filtered[nextUpcomingEventIdx] : undefined;
708
591
  const nextCountdown = nextEvent ? formatCountdown(nextEvent.date.getTime() - now) : null;
592
+ const cycleImpactFilter = useCallback(() => {
593
+ setImpactFilter((prev) => {
594
+ const idx = FILTER_CYCLE.indexOf(prev);
595
+ return FILTER_CYCLE[(idx + 1) % FILTER_CYCLE.length]!;
596
+ });
597
+ setSelectedIdx(0);
598
+ }, []);
599
+ const cycleCountryFilter = useCallback(() => {
600
+ setCountryFilter((prev) => {
601
+ const idx = COUNTRY_CYCLE.indexOf(prev);
602
+ return COUNTRY_CYCLE[(idx + 1) % COUNTRY_CYCLE.length]!;
603
+ });
604
+ setSelectedIdx(0);
605
+ }, []);
709
606
 
710
- useKeyboard((event) => {
711
- if (!focused) return;
712
-
713
- // Detail mode handles its own keys
714
- if (detailEvent) return;
715
-
716
- if (event.name === "j" || event.name === "down") {
717
- setSelectedIdx((prev) => Math.min(prev + 1, filtered.length - 1));
718
- } else if (event.name === "k" || event.name === "up") {
719
- setSelectedIdx((prev) => Math.max(prev - 1, 0));
720
- } else if (event.name === "enter" || event.name === "return") {
721
- const ev = filtered[selectedIdx];
722
- if (ev) setDetailEvent(ev);
723
- } else if (event.name === "r") {
607
+ const handleRootKeyDown = useCallback((event: {
608
+ name?: string;
609
+ preventDefault?: () => void;
610
+ stopPropagation?: () => void;
611
+ }) => {
612
+ if (event.name === "r") {
613
+ event.stopPropagation?.();
614
+ event.preventDefault?.();
724
615
  load(true);
616
+ return true;
725
617
  } else if (event.name === "f") {
726
- setImpactFilter((prev) => {
727
- const idx = FILTER_CYCLE.indexOf(prev);
728
- return FILTER_CYCLE[(idx + 1) % FILTER_CYCLE.length]!;
729
- });
730
- setSelectedIdx(0);
618
+ event.stopPropagation?.();
619
+ event.preventDefault?.();
620
+ cycleImpactFilter();
621
+ return true;
731
622
  } else if (event.name === "c") {
732
- setCountryFilter((prev) => {
733
- const idx = COUNTRY_CYCLE.indexOf(prev);
734
- return COUNTRY_CYCLE[(idx + 1) % COUNTRY_CYCLE.length]!;
735
- });
736
- setSelectedIdx(0);
737
- }
738
- });
739
-
740
- // Scroll to keep selected row visible
741
- useEffect(() => {
742
- const sb = scrollRef.current;
743
- if (!sb?.viewport || filtered.length === 0 || selectedIdx < 0) return;
744
- const flatIdx = eventIdxToRowIdx.get(selectedIdx) ?? selectedIdx;
745
- const viewportHeight = Math.max(sb.viewport.height, 1);
746
- if (flatIdx < sb.scrollTop) {
747
- sb.scrollTo(flatIdx);
748
- } else if (flatIdx >= sb.scrollTop + viewportHeight) {
749
- sb.scrollTo(flatIdx - viewportHeight + 1);
623
+ event.stopPropagation?.();
624
+ event.preventDefault?.();
625
+ cycleCountryFilter();
626
+ return true;
750
627
  }
751
- }, [selectedIdx, filtered.length]);
628
+ return false;
629
+ }, [cycleCountryFilter, cycleImpactFilter, load]);
752
630
 
753
631
  const columns = useMemo<EconCalendarColumn[]>(() => {
754
632
  const timeWidth = 6;
@@ -781,6 +659,25 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
781
659
  ].filter(Boolean).join(" · ") || undefined
782
660
  : undefined;
783
661
 
662
+ usePaneFooter("econ-calendar", () => ({
663
+ info: [
664
+ { id: "impact", parts: [{ text: `impact: ${impactFilter}`, tone: impactFilter === "all" ? "muted" : "value" }] },
665
+ { id: "country", parts: [{ text: `country: ${countryFilter}`, tone: countryFilter === "all" ? "muted" : "value" }] },
666
+ ...(nextEvent && nextCountdown ? [{
667
+ id: "next",
668
+ parts: [{ text: `Next: ${nextEvent.event.length > 18 ? nextEvent.event.slice(0, 18).trimEnd() : nextEvent.event} ${nextCountdown}`, tone: "muted" as const }],
669
+ }] : []),
670
+ ...(staleness ? [{ id: "stale", parts: [{ text: staleness, tone: "muted" as const }] }] : []),
671
+ ...(loading ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
672
+ ...(error ? [{ id: "error", parts: [{ text: error, tone: "warning" as const }] }] : []),
673
+ ],
674
+ hints: [
675
+ { id: "impact", key: "f", label: "impact", onPress: cycleImpactFilter },
676
+ { id: "country", key: "c", label: "ountry", onPress: cycleCountryFilter },
677
+ { id: "refresh", key: "r", label: "efresh", onPress: () => load(true) },
678
+ ],
679
+ }), [countryFilter, cycleCountryFilter, cycleImpactFilter, error, impactFilter, load, loading, nextCountdown, nextEvent?.event, staleness]);
680
+
784
681
  const handleHeaderClick = useCallback(() => {}, []);
785
682
  const selectDisplayRow = useCallback((row: DisplayRow) => {
786
683
  if (row.kind !== "event") return;
@@ -847,74 +744,6 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
847
744
  }
848
745
  }, []);
849
746
 
850
- const calendarContent = (
851
- <box flexDirection="column" width={width} height={height}>
852
- {/* Header */}
853
- <box flexDirection="row" height={1} paddingX={1}>
854
- <text fg={colors.textBright} attributes={TextAttributes.BOLD}>
855
- Economic Calendar
856
- </text>
857
- <box marginLeft={1}>
858
- <text fg={colors.textDim}>[{impactFilter}]</text>
859
- </box>
860
- <box marginLeft={0}>
861
- <text fg={colors.textDim}>[{countryFilter}]</text>
862
- </box>
863
- <box marginLeft={1}>
864
- <text fg={colors.textMuted}>{filtered.length} events</text>
865
- </box>
866
- {nextEvent && nextCountdown && (
867
- <box marginLeft={1}>
868
- <text fg={colors.textMuted}>
869
- Next: {nextEvent.event.length > 16 ? nextEvent.event.slice(0, 16).trimEnd() : nextEvent.event} {nextCountdown}
870
- </text>
871
- </box>
872
- )}
873
- {loading && (
874
- <box marginLeft={1}>
875
- <text fg={colors.textMuted}>loading…</text>
876
- </box>
877
- )}
878
- <box flexGrow={1} />
879
- {staleness ? <text fg={colors.textMuted}>{staleness}</text> : null}
880
- </box>
881
-
882
- {/* Error state */}
883
- {error && (
884
- <box paddingX={1} paddingY={1}>
885
- <text fg={colors.negative}>Error: {error}</text>
886
- </box>
887
- )}
888
-
889
- <DataTable<DisplayRow, EconCalendarColumn>
890
- columns={columns}
891
- items={rows}
892
- sortColumnId={null}
893
- sortDirection="asc"
894
- onHeaderClick={handleHeaderClick}
895
- headerScrollRef={headerScrollRef}
896
- scrollRef={scrollRef}
897
- syncHeaderScroll={syncHeaderScroll}
898
- onBodyScrollActivity={handleBodyScrollActivity}
899
- hoveredIdx={hoveredRowIdx}
900
- setHoveredIdx={setHoveredRowIdx}
901
- getItemKey={(row) => row.key}
902
- isSelected={(row) => row.kind === "event" && row.eventIdx === selectedIdx}
903
- onSelect={selectDisplayRow}
904
- onActivate={openDisplayRow}
905
- renderSectionHeader={renderSectionHeader}
906
- renderCell={renderCell}
907
- emptyStateTitle={loading ? "Loading economic events..." : "No events"}
908
- emptyStateHint={emptyStateHint}
909
- showHorizontalScrollbar={false}
910
- />
911
-
912
- <box height={1} paddingX={1}>
913
- <text fg={colors.textMuted}>[r]efresh</text>
914
- </box>
915
- </box>
916
- );
917
-
918
747
  const detailContent = detailEvent ? (
919
748
  <EconDetailView
920
749
  event={detailEvent}
@@ -923,77 +752,63 @@ export function EconCalendarPane({ focused, width, height }: PaneProps) {
923
752
  focused={focused}
924
753
  />
925
754
  ) : (
926
- <box flexGrow={1} />
755
+ <Box flexGrow={1} />
927
756
  );
928
757
 
929
758
  return (
930
- <PageStackView
759
+ <DataTableStackView<DisplayRow, EconCalendarColumn>
931
760
  focused={focused}
932
761
  detailOpen={!!detailEvent}
933
762
  onBack={() => setDetailEvent(null)}
934
- rootContent={calendarContent}
935
763
  detailContent={detailContent}
764
+ rootWidth={width}
765
+ rootHeight={height}
766
+ onRootKeyDown={handleRootKeyDown}
767
+ selectedIndex={eventIdxToRowIdx.get(selectedIdx) ?? selectedIdx}
768
+ onSelectIndex={(_index, row) => {
769
+ if (row.kind === "event") setSelectedIdx(row.eventIdx);
770
+ }}
771
+ onActivateIndex={(_index, row) => {
772
+ if (row.kind === "event") setDetailEvent(row.event);
773
+ }}
774
+ columns={columns}
775
+ items={rows}
776
+ isNavigable={(row) => row.kind === "event"}
777
+ sortColumnId={null}
778
+ sortDirection="asc"
779
+ onHeaderClick={handleHeaderClick}
780
+ headerScrollRef={headerScrollRef}
781
+ scrollRef={scrollRef}
782
+ getItemKey={(row) => row.key}
783
+ isSelected={(row) => row.kind === "event" && row.eventIdx === selectedIdx}
784
+ onSelect={selectDisplayRow}
785
+ onActivate={openDisplayRow}
786
+ renderSectionHeader={renderSectionHeader}
787
+ renderCell={renderCell}
788
+ emptyStateTitle={loading ? "Loading economic events..." : "No events"}
789
+ emptyStateHint={emptyStateHint}
790
+ showHorizontalScrollbar={false}
936
791
  />
937
792
  );
938
793
  }
939
794
 
940
- export const econCalendarPlugin: GloomPlugin = {
941
- id: "econ-calendar",
942
- name: "Economic Calendar",
943
- version: "1.0.0",
944
- description: "Upcoming economic events and releases",
945
- toggleable: true,
946
-
947
- setup(ctx) {
948
- attachEconCalendarPersistence(ctx.persistence);
949
-
950
- ctx.registerCommand({
951
- id: "econ-set-fred-key",
952
- label: FRED_API_KEY_COMMAND_LABEL,
953
- keywords: ["fred", "api", "key", "economic", "econ", "configure", "setup"],
954
- category: "config",
955
- description: "Configure your free FRED API key for historical economic data (fred.stlouisfed.org)",
956
- wizard: [
957
- {
958
- key: "apiKey",
959
- label: "FRED API Key",
960
- placeholder: "Paste your FRED API key",
961
- type: "password",
962
- },
963
- ],
964
- async execute(values) {
965
- const key = values?.apiKey?.trim();
966
- if (!key) return;
967
- await ctx.configState.set(FRED_API_KEY_CONFIG_KEY, key);
968
- ctx.notify({ body: "FRED API key saved", type: "success" });
969
- },
970
- });
971
- },
972
-
973
- dispose() {
974
- resetEconCalendarPersistence();
975
- },
976
-
977
- panes: [
978
- {
979
- id: "econ-calendar",
980
- name: "Economic Calendar",
981
- icon: "E",
982
- component: EconCalendarPane,
983
- defaultPosition: "right",
984
- defaultMode: "floating",
985
- defaultFloatingSize: { width: 100, height: 30 },
986
- },
987
- ],
988
-
989
- paneTemplates: [
990
- {
991
- id: "econ-calendar-pane",
992
- paneId: "econ-calendar",
993
- label: "Economic Calendar",
994
- description: "Upcoming economic events, releases, and indicators.",
995
- keywords: ["econ", "economic", "calendar", "events", "macro", "releases", "fed", "cpi", "gdp"],
996
- shortcut: { prefix: "ECON" },
997
- },
998
- ],
999
- };
795
+ export function registerEconCalendarFeature(ctx: GloomPluginContext): void {
796
+ ctx.registerPane({
797
+ id: "econ-calendar",
798
+ name: "Economic Calendar",
799
+ icon: "E",
800
+ component: EconCalendarPane,
801
+ defaultPosition: "right",
802
+ defaultMode: "floating",
803
+ defaultFloatingSize: { width: 100, height: 30 },
804
+ });
805
+
806
+ ctx.registerPaneTemplate({
807
+ id: "econ-calendar-pane",
808
+ paneId: "econ-calendar",
809
+ label: "Economic Calendar",
810
+ description: "Upcoming economic events, releases, and indicators.",
811
+ keywords: ["econ", "economic", "calendar", "events", "macro", "releases", "fed", "cpi", "gdp"],
812
+ shortcut: { prefix: "ECON" },
813
+ });
814
+ }