gloomberb 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/README.md +91 -11
  2. package/package.json +12 -5
  3. package/src/app.tsx +414 -191
  4. package/src/architecture/import-boundaries.test.ts +118 -0
  5. package/src/assets/gloomberb-logo.png +0 -0
  6. package/src/assets/gloomberb-logo.svg +73 -0
  7. package/src/brokers/account-cache.ts +157 -0
  8. package/src/brokers/profile-form.test.ts +73 -0
  9. package/src/brokers/profile-form.ts +109 -0
  10. package/src/brokers/remote-broker-adapter.ts +91 -0
  11. package/src/brokers/sync-broker-instance.test.ts +124 -1
  12. package/src/brokers/sync-broker-instance.ts +113 -17
  13. package/src/capabilities/factories.ts +100 -0
  14. package/src/capabilities/index.ts +4 -0
  15. package/src/capabilities/registry.test.ts +137 -0
  16. package/src/capabilities/registry.ts +131 -0
  17. package/src/capabilities/service-capabilities.ts +27 -0
  18. package/src/capabilities/types.ts +84 -0
  19. package/src/cli/commands/ticker.ts +11 -3
  20. package/src/cli/context.ts +19 -13
  21. package/src/cli/helpers.ts +1 -0
  22. package/src/cli/types.ts +2 -2
  23. package/src/components/chart/chart-controller.ts +4 -24
  24. package/src/components/chart/chart-data.ts +3 -14
  25. package/src/components/chart/chart-indicator-selector.tsx +28 -6
  26. package/src/components/chart/chart-pane-settings.ts +15 -28
  27. package/src/components/chart/chart-renderer.ts +74 -99
  28. package/src/components/chart/chart-resolution.ts +10 -10
  29. package/src/components/chart/chart-scroll.test.ts +6 -5
  30. package/src/components/chart/chart-scroll.ts +2 -2
  31. package/src/components/chart/chart-types.ts +0 -1
  32. package/src/components/chart/chart-viewport.ts +2 -1
  33. package/src/components/chart/chart.test.ts +37 -0
  34. package/src/components/chart/comparison-chart-data.ts +3 -17
  35. package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
  36. package/src/components/chart/comparison-chart-renderer.ts +1 -1
  37. package/src/components/chart/comparison-stock-chart.tsx +304 -108
  38. package/src/components/chart/cursor-motion.ts +3 -2
  39. package/src/components/chart/indicators/bands.test.ts +0 -14
  40. package/src/components/chart/indicators/moving-averages.test.ts +0 -15
  41. package/src/components/chart/indicators/options.ts +3 -3
  42. package/src/components/chart/indicators/oscillators.test.ts +0 -21
  43. package/src/components/chart/indicators/types.ts +1 -2
  44. package/src/components/chart/native/chart-rasterizer.ts +37 -44
  45. package/src/components/chart/native/kitty-adapter.ts +2 -5
  46. package/src/components/chart/native/kitty-manager.ts +1 -1
  47. package/src/components/chart/native/kitty-protocol.ts +0 -1
  48. package/src/components/chart/native/kitty-support.ts +1 -1
  49. package/src/components/chart/native/kitty.test.ts +30 -13
  50. package/src/components/chart/native/renderer-selection.ts +1 -1
  51. package/src/components/chart/native/surface-manager.ts +1 -1
  52. package/src/components/chart/static-chart-surface.test.tsx +51 -0
  53. package/src/components/chart/static-chart-surface.tsx +191 -0
  54. package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
  55. package/src/components/chart/stock-chart.tsx +438 -141
  56. package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
  57. package/src/components/command-bar/command-bar.test.tsx +269 -20
  58. package/src/components/command-bar/command-bar.tsx +830 -353
  59. package/src/components/command-bar/command-registry.ts +10 -20
  60. package/src/components/command-bar/helpers.test.ts +1 -1
  61. package/src/components/command-bar/helpers.ts +9 -11
  62. package/src/components/command-bar/pane-template-display.ts +4 -8
  63. package/src/components/command-bar/root-shortcuts.ts +12 -11
  64. package/src/components/command-bar/view-model.test.ts +8 -42
  65. package/src/components/command-bar/view-model.ts +8 -36
  66. package/src/components/command-bar/workflow-ops.test.ts +76 -2
  67. package/src/components/command-bar/workflow-ops.ts +40 -9
  68. package/src/components/command-bar/workflow-types.ts +2 -1
  69. package/src/components/data-table-stack-view.test.tsx +206 -0
  70. package/src/components/data-table-stack-view.tsx +152 -0
  71. package/src/components/data-table-view.test.tsx +188 -0
  72. package/src/components/data-table-view.tsx +279 -0
  73. package/src/components/feed-data-table-stack-view.test.tsx +225 -0
  74. package/src/components/feed-data-table-stack-view.tsx +365 -0
  75. package/src/components/index.ts +26 -31
  76. package/src/components/layout/detached-pane-shell.tsx +185 -0
  77. package/src/components/layout/floating-pane.tsx +87 -14
  78. package/src/components/layout/header.tsx +139 -49
  79. package/src/components/layout/pane-content.tsx +40 -0
  80. package/src/components/layout/pane-footer.test.tsx +195 -0
  81. package/src/components/layout/pane-footer.tsx +410 -0
  82. package/src/components/layout/pane-header.test.tsx +39 -0
  83. package/src/components/layout/pane-header.tsx +192 -14
  84. package/src/components/layout/pane-sizing.ts +9 -10
  85. package/src/components/layout/pane-title.ts +35 -0
  86. package/src/components/layout/pane.tsx +52 -11
  87. package/src/components/layout/shell.test.tsx +315 -107
  88. package/src/components/layout/shell.tsx +1010 -368
  89. package/src/components/layout/status-bar.test.tsx +17 -10
  90. package/src/components/layout/status-bar.tsx +367 -100
  91. package/src/components/layout/titlebar-overlay.ts +2 -0
  92. package/src/components/markdown-editor.tsx +29 -14
  93. package/src/components/markdown-text.tsx +25 -24
  94. package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
  95. package/src/components/onboarding/onboarding-wizard.tsx +347 -473
  96. package/src/components/pane-settings-dialog.tsx +521 -89
  97. package/src/components/pane-template-wizard.test.tsx +1 -1
  98. package/src/components/pane-template-wizard.tsx +38 -37
  99. package/src/components/price-selector-dialog.tsx +22 -21
  100. package/src/components/ticker-badge-list.tsx +53 -0
  101. package/src/components/ticker-badge-text.test.tsx +44 -1
  102. package/src/components/ticker-badge-text.tsx +78 -38
  103. package/src/components/ticker-badge.tsx +28 -7
  104. package/src/components/ticker-list-table-view.tsx +248 -0
  105. package/src/components/ticker-list-table.test.tsx +47 -1
  106. package/src/components/ticker-list-table.tsx +212 -41
  107. package/src/components/toggle-list.tsx +114 -8
  108. package/src/components/ui/button.tsx +24 -16
  109. package/src/components/ui/choice-dialog.tsx +89 -0
  110. package/src/components/ui/data-table.tsx +313 -114
  111. package/src/components/ui/external-link.tsx +52 -13
  112. package/src/components/ui/fields.tsx +44 -30
  113. package/src/components/ui/frame.tsx +24 -57
  114. package/src/components/ui/index.ts +15 -50
  115. package/src/components/ui/list-view.tsx +65 -25
  116. package/src/components/ui/loading.tsx +5 -60
  117. package/src/components/ui/message-composer.test.tsx +81 -0
  118. package/src/components/ui/message-composer.tsx +139 -0
  119. package/src/components/ui/multi-select-dialog.tsx +100 -32
  120. package/src/components/ui/multi-select.ts +112 -0
  121. package/src/components/ui/native-select.tsx +120 -0
  122. package/src/components/ui/page-stack-view.tsx +49 -19
  123. package/src/components/ui/remote-image.tsx +75 -0
  124. package/src/components/ui/row-value-cache.test.ts +17 -0
  125. package/src/components/ui/row-value-cache.ts +41 -0
  126. package/src/components/ui/shortcut-hint.tsx +55 -0
  127. package/src/components/ui/status.tsx +12 -67
  128. package/src/components/ui/table-layout.ts +59 -0
  129. package/src/components/ui/tabs.tsx +249 -27
  130. package/src/components/ui/toggle.tsx +19 -127
  131. package/src/components/ui/ui.test.tsx +266 -53
  132. package/src/components/use-double-click-activation.ts +7 -2
  133. package/src/components/use-external-link-footer.ts +79 -0
  134. package/src/core/app-services.ts +95 -0
  135. package/src/core/state/app-state.ts +886 -0
  136. package/src/{state → core/state}/session-persistence.ts +7 -6
  137. package/src/data/config-layout.ts +225 -0
  138. package/src/data/config-store-node.ts +390 -0
  139. package/src/data/config-store.test.ts +72 -0
  140. package/src/data/config-store.ts +35 -478
  141. package/src/data/sqlite-json.ts +2 -5
  142. package/src/data/sqlite-retry.ts +1 -0
  143. package/src/index.tsx +2 -68
  144. package/src/market-data/coordinator-subscriptions.test.ts +125 -0
  145. package/src/market-data/coordinator.test.ts +73 -4
  146. package/src/market-data/coordinator.ts +190 -24
  147. package/src/market-data/hooks.test.tsx +1 -1
  148. package/src/market-data/hooks.tsx +109 -87
  149. package/src/market-data/query-store.ts +3 -3
  150. package/src/market-data/request-types.test.ts +0 -14
  151. package/src/market-data/result-types.ts +1 -0
  152. package/src/news/aggregator.test.ts +267 -0
  153. package/src/news/aggregator.ts +452 -0
  154. package/src/news/hooks.ts +39 -0
  155. package/src/news/ticker-symbols.ts +21 -0
  156. package/src/news/types.ts +77 -0
  157. package/src/notifications/app-notifier.ts +21 -10
  158. package/src/pane-settings.test.ts +0 -2
  159. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
  160. package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
  161. package/src/plugins/builtin/ai/providers.ts +1 -9
  162. package/src/plugins/builtin/ai/runner.ts +41 -5
  163. package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
  164. package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
  165. package/src/plugins/builtin/ai/settings.ts +0 -1
  166. package/src/plugins/builtin/ai/utils.ts +1 -71
  167. package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
  168. package/src/plugins/builtin/alerts/index.test.tsx +61 -82
  169. package/src/plugins/builtin/alerts/index.tsx +263 -155
  170. package/src/plugins/builtin/alerts/types.ts +7 -0
  171. package/src/plugins/builtin/analytics/index.test.tsx +13 -8
  172. package/src/plugins/builtin/analytics/index.tsx +78 -120
  173. package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
  174. package/src/plugins/builtin/ask-ai.test.tsx +55 -54
  175. package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
  176. package/src/plugins/builtin/broker-manager/index.tsx +759 -0
  177. package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
  178. package/src/plugins/builtin/broker-manager/model.ts +142 -0
  179. package/src/plugins/builtin/chat-controller.test.ts +129 -0
  180. package/src/plugins/builtin/chat-controller.ts +85 -14
  181. package/src/plugins/builtin/chat.test.tsx +283 -36
  182. package/src/plugins/builtin/chat.tsx +363 -170
  183. package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
  184. package/src/plugins/builtin/cloud-tweets.tsx +896 -0
  185. package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
  186. package/src/plugins/builtin/comparison-chart.tsx +11 -11
  187. package/src/plugins/builtin/correlation/compute.test.ts +0 -17
  188. package/src/plugins/builtin/correlation/index.tsx +51 -46
  189. package/src/plugins/builtin/correlation/settings.ts +1 -2
  190. package/src/plugins/builtin/debug.tsx +99 -70
  191. package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
  192. package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
  193. package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
  194. package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
  195. package/src/plugins/builtin/earnings/index.tsx +187 -94
  196. package/src/plugins/builtin/econ/calendar-source.ts +9 -116
  197. package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
  198. package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
  199. package/src/plugins/builtin/econ/index.tsx +236 -421
  200. package/src/plugins/builtin/econ/types.ts +1 -0
  201. package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
  202. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
  203. package/src/plugins/builtin/help.test.tsx +120 -60
  204. package/src/plugins/builtin/help.tsx +266 -82
  205. package/src/plugins/builtin/holders/index.test.ts +45 -0
  206. package/src/plugins/builtin/holders/index.tsx +949 -0
  207. package/src/plugins/builtin/insider/index.tsx +207 -253
  208. package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
  209. package/src/plugins/builtin/insider/insider-data.ts +1 -0
  210. package/src/plugins/builtin/layout-manager.tsx +12 -40
  211. package/src/plugins/builtin/market-movers/index.tsx +70 -144
  212. package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
  213. package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
  214. package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
  215. package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
  216. package/src/plugins/builtin/news-wire/categories.ts +114 -0
  217. package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
  218. package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
  219. package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
  220. package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
  221. package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
  222. package/src/plugins/builtin/news-wire/hash.ts +9 -0
  223. package/src/plugins/builtin/news-wire/index.tsx +92 -0
  224. package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
  225. package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
  226. package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
  227. package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
  228. package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
  229. package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
  230. package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
  231. package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
  232. package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
  233. package/src/plugins/builtin/news-wire/read-state.ts +76 -0
  234. package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
  235. package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
  236. package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
  237. package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
  238. package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
  239. package/src/plugins/builtin/news.tsx +98 -25
  240. package/src/plugins/builtin/notes-files.ts +51 -10
  241. package/src/plugins/builtin/notes.tsx +332 -131
  242. package/src/plugins/builtin/options.test.tsx +185 -0
  243. package/src/plugins/builtin/options.tsx +346 -206
  244. package/src/plugins/builtin/plugin-groups.ts +92 -0
  245. package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
  246. package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
  247. package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
  248. package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
  249. package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
  250. package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
  251. package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
  252. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
  253. package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
  254. package/src/plugins/builtin/portfolio-list.test.ts +60 -1
  255. package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
  256. package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
  257. package/src/plugins/builtin/research/index.tsx +724 -0
  258. package/src/plugins/builtin/sec.tsx +67 -19
  259. package/src/plugins/builtin/sectors/index.tsx +42 -80
  260. package/src/plugins/builtin/sectors/sector-data.ts +1 -0
  261. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
  262. package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
  263. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
  264. package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
  265. package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
  266. package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
  267. package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
  268. package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
  269. package/src/plugins/builtin/ticker-surface.ts +68 -0
  270. package/src/plugins/builtin/world-indices/index.tsx +44 -82
  271. package/src/plugins/builtin/world-indices/indices.ts +1 -0
  272. package/src/plugins/builtin/yahoo.ts +55 -1
  273. package/src/plugins/builtin/yield-curve/index.tsx +87 -145
  274. package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
  275. package/src/plugins/catalog-ui.test.ts +24 -0
  276. package/src/plugins/catalog-ui.ts +44 -0
  277. package/src/plugins/catalog.ts +2 -50
  278. package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
  279. package/src/plugins/ibkr/account-cache.test.ts +15 -14
  280. package/src/plugins/ibkr/account-cache.ts +15 -120
  281. package/src/plugins/ibkr/broker-adapter.ts +67 -0
  282. package/src/plugins/ibkr/config.test.ts +0 -18
  283. package/src/plugins/ibkr/dialogs.tsx +14 -56
  284. package/src/plugins/ibkr/flex.test.ts +57 -2
  285. package/src/plugins/ibkr/flex.ts +116 -12
  286. package/src/plugins/ibkr/gateway-helpers.ts +8 -3
  287. package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
  288. package/src/plugins/ibkr/gateway-service.test.ts +47 -9
  289. package/src/plugins/ibkr/gateway-service.ts +264 -2235
  290. package/src/plugins/ibkr/gateway-types.ts +27 -0
  291. package/src/plugins/ibkr/index.test.tsx +3 -11
  292. package/src/plugins/ibkr/index.tsx +1 -0
  293. package/src/plugins/ibkr/price-normalization.test.ts +1 -6
  294. package/src/plugins/ibkr/price-normalization.ts +1 -0
  295. package/src/plugins/ibkr/trade-badge.tsx +6 -5
  296. package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
  297. package/src/plugins/ibkr/trade-tab.tsx +164 -117
  298. package/src/plugins/ibkr/trading-pane.tsx +117 -83
  299. package/src/plugins/pane-manager.test.ts +3 -1
  300. package/src/plugins/pane-manager.ts +93 -19
  301. package/src/plugins/plugin-runtime.test.tsx +176 -74
  302. package/src/plugins/plugin-runtime.tsx +228 -10
  303. package/src/plugins/prediction-markets/cache.ts +49 -0
  304. package/src/plugins/prediction-markets/categories.ts +0 -10
  305. package/src/plugins/prediction-markets/chart.test.tsx +1 -1
  306. package/src/plugins/prediction-markets/chart.tsx +21 -20
  307. package/src/plugins/prediction-markets/cli.ts +1 -0
  308. package/src/plugins/prediction-markets/controller-data.ts +135 -89
  309. package/src/plugins/prediction-markets/controller.ts +37 -108
  310. package/src/plugins/prediction-markets/detail/book.tsx +23 -22
  311. package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
  312. package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
  313. package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
  314. package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
  315. package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
  316. package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
  317. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  318. package/src/plugins/prediction-markets/keyboard.ts +1 -0
  319. package/src/plugins/prediction-markets/launch.test.ts +1 -1
  320. package/src/plugins/prediction-markets/launch.ts +1 -1
  321. package/src/plugins/prediction-markets/metrics.ts +25 -12
  322. package/src/plugins/prediction-markets/pane.test.tsx +16 -7
  323. package/src/plugins/prediction-markets/pane.tsx +180 -82
  324. package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
  325. package/src/plugins/prediction-markets/services/fetch.ts +27 -10
  326. package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
  327. package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
  328. package/src/plugins/prediction-markets/settings.ts +0 -5
  329. package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
  330. package/src/plugins/registry.test.ts +307 -0
  331. package/src/plugins/registry.ts +293 -82
  332. package/src/react/input.ts +53 -0
  333. package/src/react/plugins/plugin-slot.tsx +21 -0
  334. package/src/react/use-raf-callback.ts +44 -0
  335. package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
  336. package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
  337. package/src/renderers/electrobun/bun/application-menu.ts +118 -0
  338. package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
  339. package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
  340. package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
  341. package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
  342. package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
  343. package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
  344. package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
  345. package/src/renderers/electrobun/bun/index.ts +1149 -0
  346. package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
  347. package/src/renderers/electrobun/bun/window-frame.ts +46 -0
  348. package/src/renderers/electrobun/shared/protocol.ts +72 -0
  349. package/src/renderers/electrobun/view/ai-host.ts +93 -0
  350. package/src/renderers/electrobun/view/app-services.ts +348 -0
  351. package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
  352. package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
  353. package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
  354. package/src/renderers/electrobun/view/config-host.ts +35 -0
  355. package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
  356. package/src/renderers/electrobun/view/context-menu.ts +164 -0
  357. package/src/renderers/electrobun/view/data-table.tsx +773 -0
  358. package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
  359. package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
  360. package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
  361. package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
  362. package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
  363. package/src/renderers/electrobun/view/http-fetch.ts +113 -0
  364. package/src/renderers/electrobun/view/input-host.tsx +65 -0
  365. package/src/renderers/electrobun/view/key-event.test.ts +34 -0
  366. package/src/renderers/electrobun/view/key-event.ts +134 -0
  367. package/src/renderers/electrobun/view/main.tsx +104 -0
  368. package/src/renderers/electrobun/view/native-renderer.ts +110 -0
  369. package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
  370. package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
  371. package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
  372. package/src/renderers/electrobun/view/notes-files.ts +56 -0
  373. package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
  374. package/src/renderers/electrobun/view/resource-store.ts +106 -0
  375. package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
  376. package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
  377. package/src/renderers/electrobun/view/toast-host.tsx +65 -0
  378. package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
  379. package/src/renderers/electrobun/view/update-host.ts +38 -0
  380. package/src/renderers/opentui/dialog-host.test.tsx +44 -0
  381. package/src/renderers/opentui/dialog-host.tsx +56 -0
  382. package/src/renderers/opentui/host.tsx +134 -0
  383. package/src/renderers/opentui/input-host.tsx +24 -0
  384. package/src/renderers/opentui/start.tsx +71 -0
  385. package/src/renderers/opentui/test-utils.tsx +76 -0
  386. package/src/renderers/opentui/toast-host.tsx +10 -0
  387. package/src/renderers/opentui/ui-host.tsx +97 -0
  388. package/src/sources/gloomberb-cloud.test.ts +288 -17
  389. package/src/sources/gloomberb-cloud.ts +176 -9
  390. package/src/sources/provider-errors.ts +1 -0
  391. package/src/sources/provider-router.test.ts +220 -81
  392. package/src/sources/provider-router.ts +294 -111
  393. package/src/sources/sec-edgar.ts +19 -6
  394. package/src/sources/yahoo-finance.test.ts +0 -5
  395. package/src/sources/yahoo-finance.ts +420 -11
  396. package/src/state/app-activity.ts +7 -7
  397. package/src/state/app-bootstrap.ts +114 -40
  398. package/src/state/app-context-selector.test.tsx +70 -1
  399. package/src/state/app-context.test.ts +71 -3
  400. package/src/state/app-context.test.tsx +14 -7
  401. package/src/state/app-context.tsx +205 -869
  402. package/src/state/config-save-scheduler.ts +35 -0
  403. package/src/state/persist-scheduler.test.ts +67 -0
  404. package/src/state/persist-scheduler.ts +69 -0
  405. package/src/state/selectors-ui.ts +55 -0
  406. package/src/state/selectors.ts +15 -71
  407. package/src/state/session-persistence.test.ts +1 -1
  408. package/src/state/ticker-refresh-queue.ts +52 -3
  409. package/src/state/use-inline-tickers.ts +18 -16
  410. package/src/state/use-quote-streaming.test.ts +0 -6
  411. package/src/state/use-quote-streaming.test.tsx +1 -1
  412. package/src/state/use-quote-streaming.ts +3 -2
  413. package/src/state/use-session-snapshot.ts +32 -17
  414. package/src/test-support/plugin-runtime.ts +149 -0
  415. package/src/theme/color-utils.ts +1 -0
  416. package/src/theme/colors.ts +16 -0
  417. package/src/types/broker.ts +16 -1
  418. package/src/types/capability-route-source.ts +23 -0
  419. package/src/types/config.test.ts +23 -0
  420. package/src/types/config.ts +145 -10
  421. package/src/types/context-menu.ts +95 -0
  422. package/src/types/data-provider.ts +34 -3
  423. package/src/types/desktop-menu.ts +13 -0
  424. package/src/types/desktop-window.ts +29 -0
  425. package/src/types/financials.ts +123 -2
  426. package/src/types/instrument.ts +1 -0
  427. package/src/types/news-source.ts +9 -0
  428. package/src/types/persistence.ts +1 -0
  429. package/src/types/plugin.ts +32 -8
  430. package/src/ui/ascii-font.ts +85 -0
  431. package/src/ui/context-menu.test.ts +94 -0
  432. package/src/ui/context-menu.tsx +433 -0
  433. package/src/ui/dialog.tsx +55 -0
  434. package/src/ui/host.tsx +361 -0
  435. package/src/ui/index.tsx +126 -0
  436. package/src/ui/toast.tsx +40 -0
  437. package/src/updater.test.ts +69 -0
  438. package/src/updater.ts +61 -18
  439. package/src/utils/api-client.test.ts +99 -9
  440. package/src/utils/api-client.ts +378 -18
  441. package/src/utils/back-navigation.ts +6 -0
  442. package/src/utils/cli-output.ts +1 -0
  443. package/src/utils/currency-units.test.ts +1 -6
  444. package/src/utils/currency-units.ts +1 -0
  445. package/src/utils/debug-log.ts +68 -0
  446. package/src/utils/dom-screenshot.ts +326 -0
  447. package/src/utils/exchange-rate-map.ts +1 -0
  448. package/src/utils/exchanges.ts +214 -0
  449. package/src/utils/format.test.ts +7 -0
  450. package/src/utils/format.ts +1 -0
  451. package/src/utils/fuzzy-search.ts +1 -0
  452. package/src/utils/html-entities.test.ts +8 -0
  453. package/src/utils/html-entities.ts +32 -0
  454. package/src/utils/http-transport.ts +11 -0
  455. package/src/utils/main-thread-monitor.ts +127 -0
  456. package/src/utils/perf-marks.ts +54 -0
  457. package/src/utils/quote-freshness.ts +27 -50
  458. package/src/utils/quote-resolution.test.ts +5 -5
  459. package/src/utils/quote-resolution.ts +0 -1
  460. package/src/utils/quote-time.ts +1 -5
  461. package/src/utils/sec.test.ts +0 -12
  462. package/src/utils/selection-clipboard.ts +14 -6
  463. package/src/utils/shortcut-labels.test.ts +20 -0
  464. package/src/utils/shortcut-labels.ts +67 -0
  465. package/src/utils/slugify.ts +1 -0
  466. package/src/utils/terminal-input-reset.ts +1 -0
  467. package/src/utils/text-wrap.ts +82 -0
  468. package/src/utils/throttled-fetch.test.ts +19 -0
  469. package/src/utils/throttled-fetch.ts +11 -9
  470. package/src/utils/ticker-list.test.ts +1 -7
  471. package/src/utils/ticker-list.ts +1 -0
  472. package/src/utils/timestamp.ts +29 -0
  473. package/src/utils/tweet-text.test.ts +13 -0
  474. package/src/utils/tweet-text.ts +18 -0
  475. package/src/utils/url.ts +11 -0
  476. package/src/version.ts +1 -1
  477. package/src/components/chart/chart-control-hint.tsx +0 -39
  478. package/src/components/detail-feed-view.test.tsx +0 -180
  479. package/src/components/detail-feed-view.tsx +0 -531
  480. package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
  481. package/src/components/layout/pixel-mouse.test.ts +0 -40
  482. package/src/components/spinner.tsx +0 -2
  483. package/src/components/tab-bar.tsx +0 -17
  484. package/src/components/ui/multi-select-chips.tsx +0 -69
  485. package/src/plugins/builtin/ai/index.test.tsx +0 -139
  486. package/src/plugins/builtin/ask-ai.tsx +0 -9
  487. package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
  488. package/src/plugins/builtin/correlation/settings.test.ts +0 -34
  489. package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
  490. package/src/plugins/builtin/econ/fred-client.ts +0 -79
  491. package/src/plugins/builtin/fred-settings.ts +0 -10
  492. package/src/plugins/builtin/options-availability.test.tsx +0 -179
  493. package/src/plugins/builtin/options-availability.ts +0 -158
  494. package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
  495. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
  496. package/src/plugins/prediction-markets/status.ts +0 -24
  497. package/src/plugins/prediction-markets/table.tsx +0 -87
  498. package/src/utils/quote-time.test.ts +0 -22
  499. package/src/utils/selection-clipboard.test.ts +0 -43
  500. package/src/utils/terminal-input-reset.test.ts +0 -50
@@ -0,0 +1,149 @@
1
+ import type { PluginRuntimeAccess } from "../plugins/plugin-runtime";
2
+ import type { AppConfig } from "../types/config";
3
+
4
+ interface TestConfigAccess {
5
+ getConfig(): AppConfig | null | undefined;
6
+ setConfig(config: AppConfig): void;
7
+ }
8
+
9
+ export function createTestPluginRuntime(
10
+ overrides: Partial<PluginRuntimeAccess> = {},
11
+ ): PluginRuntimeAccess {
12
+ return {
13
+ getMarketData: () => null,
14
+ getCapability: () => null,
15
+ getBrokerAdapter: () => null,
16
+ connectBrokerInstance: async () => {},
17
+ updateBrokerInstance: async () => {},
18
+ syncBrokerInstance: async () => {},
19
+ removeBrokerInstance: async () => {},
20
+ pinTicker() {},
21
+ navigateTicker() {},
22
+ selectTicker() {},
23
+ switchTab() {},
24
+ switchPanel() {},
25
+ openCommandBar() {},
26
+ showWidget() {},
27
+ hideWidget() {},
28
+ openPluginCommandWorkflow() {},
29
+ notify() {},
30
+ subscribeResumeState: () => () => {},
31
+ getResumeState: () => null,
32
+ setResumeState() {},
33
+ deleteResumeState() {},
34
+ getConfigState: () => null,
35
+ setConfigState: async () => {},
36
+ setConfigStates: async () => {},
37
+ deleteConfigState: async () => {},
38
+ getConfigStateKeys: () => [],
39
+ ...overrides,
40
+ };
41
+ }
42
+
43
+ export function createStatefulTestPluginRuntime(
44
+ overrides: Partial<PluginRuntimeAccess> = {},
45
+ ): PluginRuntimeAccess {
46
+ const resumeState = new Map<string, unknown>();
47
+ const listeners = new Map<string, Set<() => void>>();
48
+ const configState = new Map<string, Map<string, unknown>>();
49
+ const resumeKey = (pluginId: string, key: string) => `${pluginId}:${key}`;
50
+ const pluginConfig = (pluginId: string) => {
51
+ let state = configState.get(pluginId);
52
+ if (!state) {
53
+ state = new Map();
54
+ configState.set(pluginId, state);
55
+ }
56
+ return state;
57
+ };
58
+ const emitResumeChange = (key: string) => {
59
+ for (const listener of listeners.get(key) ?? []) listener();
60
+ };
61
+
62
+ return createTestPluginRuntime({
63
+ subscribeResumeState(pluginId, key, listener) {
64
+ const storeKey = resumeKey(pluginId, key);
65
+ if (!listeners.has(storeKey)) listeners.set(storeKey, new Set());
66
+ listeners.get(storeKey)!.add(listener);
67
+ return () => listeners.get(storeKey)?.delete(listener);
68
+ },
69
+ getResumeState(pluginId, key) {
70
+ return (resumeState.get(resumeKey(pluginId, key)) as any) ?? null;
71
+ },
72
+ setResumeState(pluginId, key, value) {
73
+ const storeKey = resumeKey(pluginId, key);
74
+ resumeState.set(storeKey, value);
75
+ emitResumeChange(storeKey);
76
+ },
77
+ deleteResumeState(pluginId, key) {
78
+ const storeKey = resumeKey(pluginId, key);
79
+ resumeState.delete(storeKey);
80
+ emitResumeChange(storeKey);
81
+ },
82
+ getConfigState(pluginId, key) {
83
+ return (pluginConfig(pluginId).get(key) as any) ?? null;
84
+ },
85
+ async setConfigState(pluginId, key, value) {
86
+ pluginConfig(pluginId).set(key, value);
87
+ },
88
+ async setConfigStates(pluginId, values) {
89
+ const state = pluginConfig(pluginId);
90
+ for (const [key, value] of Object.entries(values)) state.set(key, value);
91
+ },
92
+ async deleteConfigState(pluginId, key) {
93
+ pluginConfig(pluginId).delete(key);
94
+ },
95
+ getConfigStateKeys(pluginId) {
96
+ return [...pluginConfig(pluginId).keys()].sort();
97
+ },
98
+ ...overrides,
99
+ });
100
+ }
101
+
102
+ export function createConfigBackedTestPluginRuntime(
103
+ access: TestConfigAccess,
104
+ overrides: Partial<PluginRuntimeAccess> = {},
105
+ ): PluginRuntimeAccess {
106
+ const requireConfig = () => {
107
+ const config = access.getConfig();
108
+ if (!config) throw new Error("test plugin runtime used before config is available");
109
+ return config;
110
+ };
111
+ const writePluginConfig = (pluginId: string, values: Record<string, unknown>) => {
112
+ const config = requireConfig();
113
+ access.setConfig({
114
+ ...config,
115
+ pluginConfig: {
116
+ ...config.pluginConfig,
117
+ [pluginId]: {
118
+ ...(config.pluginConfig[pluginId] ?? {}),
119
+ ...values,
120
+ },
121
+ },
122
+ });
123
+ };
124
+
125
+ return createStatefulTestPluginRuntime({
126
+ getConfigState(pluginId, key) {
127
+ return (access.getConfig()?.pluginConfig[pluginId]?.[key] as any) ?? null;
128
+ },
129
+ async setConfigState(pluginId, key, value) {
130
+ writePluginConfig(pluginId, { [key]: value });
131
+ },
132
+ async setConfigStates(pluginId, values) {
133
+ writePluginConfig(pluginId, values);
134
+ },
135
+ async deleteConfigState(pluginId, key) {
136
+ const config = requireConfig();
137
+ const currentPluginConfig = { ...(config.pluginConfig[pluginId] ?? {}) };
138
+ delete currentPluginConfig[key];
139
+ const pluginConfig = { ...config.pluginConfig };
140
+ if (Object.keys(currentPluginConfig).length === 0) delete pluginConfig[pluginId];
141
+ else pluginConfig[pluginId] = currentPluginConfig;
142
+ access.setConfig({ ...config, pluginConfig });
143
+ },
144
+ getConfigStateKeys(pluginId) {
145
+ return Object.keys(access.getConfig()?.pluginConfig[pluginId] ?? {}).sort();
146
+ },
147
+ ...overrides,
148
+ });
149
+ }
@@ -1,3 +1,4 @@
1
+
1
2
  export function blendHex(a: string, b: string, ratio: number): string {
2
3
  const parse = (hex: string) => {
3
4
  const h = hex.replace("#", "");
@@ -55,6 +55,22 @@ export function commandBarBg(): string {
55
55
  return blendForContrast(base, colors.bg, accent, 1.45);
56
56
  }
57
57
 
58
+ export function commandBarPanelBg(): string {
59
+ return blendHex(commandBarBg(), colors.panel, 0.28);
60
+ }
61
+
62
+ export function commandBarInputBg(): string {
63
+ return blendHex(commandBarPanelBg(), colors.bg, 0.22);
64
+ }
65
+
66
+ export function commandBarPanelBorder(): string {
67
+ return blendHex(colors.border, colors.borderFocused, 0.18);
68
+ }
69
+
70
+ export function commandBarOverlayBg(): string {
71
+ return blendHex(colors.bg, colors.panel, 0.18);
72
+ }
73
+
58
74
  export function commandBarSelectedBg(): string {
59
75
  const base = commandBarBg();
60
76
  const accent = higherContrast(colors.selectedText, colors.textBright, colors.selected);
@@ -5,7 +5,7 @@ import type { BrokerInstanceConfig } from "./config";
5
5
  import type { QuoteSubscriptionTarget } from "./data-provider";
6
6
  import type { BrokerContractRef, InstrumentSearchResult } from "./instrument";
7
7
  import type { BrokerAccount, BrokerExecution, BrokerOrder, BrokerOrderPreview, BrokerOrderRequest } from "./trading";
8
- import type { CachePolicyMap } from "./persistence";
8
+ import type { CachePolicy, CachePolicyMap } from "./persistence";
9
9
 
10
10
  export interface BrokerPosition {
11
11
  ticker: string;
@@ -64,6 +64,14 @@ export interface BrokerConnectionStatus {
64
64
  updatedAt: number;
65
65
  }
66
66
 
67
+ export interface BrokerProfileAction {
68
+ id: string;
69
+ label: string;
70
+ widgetId?: string;
71
+ disabled?: boolean;
72
+ disabledReason?: string;
73
+ }
74
+
67
75
  export interface BrokerAdapter {
68
76
  readonly id: string;
69
77
  readonly name: string;
@@ -74,6 +82,13 @@ export interface BrokerAdapter {
74
82
  connect?(instance: BrokerInstanceConfig): Promise<void>;
75
83
  disconnect?(instance: BrokerInstanceConfig): Promise<void>;
76
84
  getStatus?(instance: BrokerInstanceConfig): BrokerConnectionStatus;
85
+ subscribeStatus?(instance: BrokerInstanceConfig, listener: () => void): () => void;
86
+ getPersistedConfigUpdate?(instance: BrokerInstanceConfig): Record<string, unknown> | null | Promise<Record<string, unknown> | null>;
87
+ getAccountCacheSourceKey?(instance: BrokerInstanceConfig): string;
88
+ getAccountCachePolicy?(instance: BrokerInstanceConfig): CachePolicy;
89
+ getProfileActions?(instance: BrokerInstanceConfig): BrokerProfileAction[];
90
+ toConfigValues?(instance: BrokerInstanceConfig): Record<string, unknown>;
91
+ fromConfigValues?(values: Record<string, unknown>, previous?: BrokerInstanceConfig): Record<string, unknown>;
77
92
  listAccounts?(instance: BrokerInstanceConfig): Promise<BrokerAccount[]>;
78
93
  searchInstruments?(query: string, instance: BrokerInstanceConfig): Promise<InstrumentSearchResult[]>;
79
94
  getTickerFinancials?(ticker: string, instance: BrokerInstanceConfig, exchange?: string, instrument?: BrokerContractRef | null): Promise<TickerFinancials>;
@@ -0,0 +1,23 @@
1
+ import type { AssetDataProvider } from "./data-provider";
2
+ import type { NewsArticle, NewsQuery } from "../news/types";
3
+ import type { CachePolicyMap } from "./persistence";
4
+
5
+ export interface NewsDataProvider {
6
+ supports?(query: NewsQuery): boolean;
7
+ getCachedNews?(query: NewsQuery): NewsArticle[];
8
+ fetchNews(query: NewsQuery): Promise<NewsArticle[]>;
9
+ }
10
+
11
+ export interface CapabilityRouteSource {
12
+ readonly id: string;
13
+ readonly name: string;
14
+ readonly priority?: number;
15
+ readonly cachePolicy?: CachePolicyMap;
16
+ isEnabled?(): boolean;
17
+ readonly market?: AssetDataProvider;
18
+ readonly news?: NewsDataProvider;
19
+ }
20
+
21
+ export function routeSourcePriority(source: Pick<CapabilityRouteSource, "priority">): number {
22
+ return source.priority ?? 1000;
23
+ }
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  createPaneInstance,
4
4
  findPrimaryPaneInstance,
5
+ materializeDetachedPanesAsFloating,
5
6
  resolveFollowBindingInstance,
6
7
  resolvePaneInstance,
7
8
  type LayoutConfig,
@@ -13,6 +14,7 @@ function createLayout(instances: PaneInstanceConfig[]): LayoutConfig {
13
14
  dockRoot: null,
14
15
  instances,
15
16
  floating: [],
17
+ detached: [],
16
18
  };
17
19
  }
18
20
 
@@ -105,3 +107,24 @@ describe("resolveFollowBindingInstance", () => {
105
107
  ).toBeUndefined();
106
108
  });
107
109
  });
110
+
111
+ describe("materializeDetachedPanesAsFloating", () => {
112
+ test("degrades detached panes into floating panes for non-desktop renderers", () => {
113
+ const layout = createLayout([
114
+ createPaneInstance("chat", {
115
+ instanceId: "chat:main",
116
+ binding: { kind: "none" },
117
+ }),
118
+ ]);
119
+ layout.detached = [
120
+ { instanceId: "chat:main", x: 14, y: 6, width: 48, height: 18 },
121
+ ];
122
+
123
+ const next = materializeDetachedPanesAsFloating(layout);
124
+
125
+ expect(next.detached).toEqual([]);
126
+ expect(next.floating).toEqual([
127
+ { instanceId: "chat:main", x: 14, y: 6, width: 48, height: 18 },
128
+ ]);
129
+ });
130
+ });
@@ -1,6 +1,6 @@
1
1
  import type { Portfolio, Watchlist } from "./ticker";
2
2
 
3
- export const CURRENT_CONFIG_VERSION = 11;
3
+ export const CURRENT_CONFIG_VERSION = 15;
4
4
 
5
5
  export type DefaultChartRenderMode = "area" | "line" | "candles" | "ohlc";
6
6
  export type ChartRendererPreference = "auto" | "kitty" | "braille";
@@ -45,9 +45,17 @@ export interface FloatingPlacementMemory {
45
45
  height: number;
46
46
  }
47
47
 
48
+ export interface DetachedPlacementMemory {
49
+ x: number;
50
+ y: number;
51
+ width: number;
52
+ height: number;
53
+ }
54
+
48
55
  export interface PanePlacementMemory {
49
56
  docked?: DockedPlacementMemory;
50
57
  floating?: FloatingPlacementMemory;
58
+ detached?: DetachedPlacementMemory;
51
59
  }
52
60
 
53
61
  export interface PaneInstanceConfig {
@@ -84,10 +92,19 @@ export interface FloatingPaneEntry {
84
92
  zIndex?: number;
85
93
  }
86
94
 
95
+ export interface DetachedPaneEntry {
96
+ instanceId: string;
97
+ x: number;
98
+ y: number;
99
+ width: number;
100
+ height: number;
101
+ }
102
+
87
103
  export interface LayoutConfig {
88
104
  dockRoot: DockLayoutNode | null;
89
105
  instances: PaneInstanceConfig[];
90
106
  floating: FloatingPaneEntry[];
107
+ detached: DetachedPaneEntry[];
91
108
  }
92
109
 
93
110
  export interface SavedLayout {
@@ -108,6 +125,7 @@ export interface AppConfig {
108
125
  brokerInstances: BrokerInstanceConfig[];
109
126
  plugins: string[];
110
127
  disabledPlugins: string[];
128
+ disabledSources: string[];
111
129
  pluginConfig: Record<string, Record<string, unknown>>;
112
130
  theme: string;
113
131
  chartPreferences: ChartPreferences;
@@ -144,11 +162,11 @@ export const DEFAULT_PORTFOLIO_COLUMN_IDS = [
144
162
  "pnl_pct",
145
163
  ];
146
164
 
147
- export const DEFAULT_LAYOUT: LayoutConfig = {
165
+ export const DEFAULT_HOME_LAYOUT: LayoutConfig = {
148
166
  dockRoot: {
149
167
  kind: "split",
150
168
  axis: "horizontal",
151
- ratio: 0.4,
169
+ ratio: 0.34,
152
170
  first: {
153
171
  kind: "split",
154
172
  axis: "vertical",
@@ -156,7 +174,13 @@ export const DEFAULT_LAYOUT: LayoutConfig = {
156
174
  first: { kind: "pane", instanceId: "portfolio-list:main" },
157
175
  second: { kind: "pane", instanceId: "chat:main" },
158
176
  },
159
- second: { kind: "pane", instanceId: "ticker-detail:main" },
177
+ second: {
178
+ kind: "split",
179
+ axis: "vertical",
180
+ ratio: 0.46,
181
+ first: { kind: "pane", instanceId: "ticker-detail:main" },
182
+ second: { kind: "pane", instanceId: "ticker-detail:nvda" },
183
+ },
160
184
  },
161
185
  instances: [
162
186
  {
@@ -167,8 +191,6 @@ export const DEFAULT_LAYOUT: LayoutConfig = {
167
191
  columnIds: [...DEFAULT_PORTFOLIO_COLUMN_IDS],
168
192
  collectionScope: "all",
169
193
  visibleCollectionIds: [],
170
- hideTabs: false,
171
- lockedCollectionId: "main",
172
194
  },
173
195
  binding: { kind: "none" },
174
196
  },
@@ -183,6 +205,18 @@ export const DEFAULT_LAYOUT: LayoutConfig = {
183
205
  },
184
206
  binding: { kind: "follow", sourceInstanceId: "portfolio-list:main" },
185
207
  },
208
+ {
209
+ instanceId: "ticker-detail:nvda",
210
+ paneId: "ticker-detail",
211
+ title: "NVDA",
212
+ settings: {
213
+ hideTabs: false,
214
+ lockedTabId: "overview",
215
+ chartRangePreset: "5Y",
216
+ chartResolution: "auto",
217
+ },
218
+ binding: { kind: "fixed", symbol: "NVDA" },
219
+ },
186
220
  {
187
221
  instanceId: "chat:main",
188
222
  paneId: "chat",
@@ -191,10 +225,66 @@ export const DEFAULT_LAYOUT: LayoutConfig = {
191
225
  },
192
226
  binding: { kind: "none" },
193
227
  },
228
+ {
229
+ instanceId: "help:main",
230
+ paneId: "help",
231
+ binding: { kind: "none" },
232
+ },
233
+ ],
234
+ floating: [
235
+ { instanceId: "help:main", x: 12, y: 4, width: 88, height: 32, zIndex: 90 },
236
+ ],
237
+ detached: [],
238
+ };
239
+
240
+ export const DEFAULT_MONITOR_LAYOUT: LayoutConfig = {
241
+ dockRoot: {
242
+ kind: "split",
243
+ axis: "vertical",
244
+ ratio: 0.48,
245
+ first: {
246
+ kind: "split",
247
+ axis: "horizontal",
248
+ ratio: 0.42,
249
+ first: { kind: "pane", instanceId: "news-top:main" },
250
+ second: { kind: "pane", instanceId: "prediction-markets:main" },
251
+ },
252
+ second: {
253
+ kind: "split",
254
+ axis: "horizontal",
255
+ ratio: 0.42,
256
+ first: { kind: "pane", instanceId: "world-indices:main" },
257
+ second: { kind: "pane", instanceId: "econ-calendar:main" },
258
+ },
259
+ },
260
+ instances: [
261
+ {
262
+ instanceId: "news-top:main",
263
+ paneId: "news-top",
264
+ binding: { kind: "none" },
265
+ },
266
+ {
267
+ instanceId: "prediction-markets:main",
268
+ paneId: "prediction-markets",
269
+ binding: { kind: "none" },
270
+ },
271
+ {
272
+ instanceId: "world-indices:main",
273
+ paneId: "world-indices",
274
+ binding: { kind: "none" },
275
+ },
276
+ {
277
+ instanceId: "econ-calendar:main",
278
+ paneId: "econ-calendar",
279
+ binding: { kind: "none" },
280
+ },
194
281
  ],
195
282
  floating: [],
283
+ detached: [],
196
284
  };
197
285
 
286
+ export const DEFAULT_LAYOUT = DEFAULT_HOME_LAYOUT;
287
+
198
288
  let nextPaneInstanceSeq = 0;
199
289
 
200
290
  function clampDockRatio(ratio: number | undefined): number {
@@ -281,6 +371,7 @@ export function clonePlacementMemory(memory: PanePlacementMemory | undefined): P
281
371
  position: memory.docked.position,
282
372
  } : undefined,
283
373
  floating: memory.floating ? { ...memory.floating } : undefined,
374
+ detached: memory.detached ? { ...memory.detached } : undefined,
284
375
  };
285
376
  }
286
377
 
@@ -369,11 +460,13 @@ export function removePaneInstances(layout: LayoutConfig, instanceIds: Iterable<
369
460
  const validInstanceIds = new Set(instances.map((instance) => instance.instanceId));
370
461
  const dockRoot = normalizeDockNode(layout.dockRoot, validInstanceIds, new Set<string>());
371
462
  const dockedPaneIds = new Set(getDockedPaneIdsFromNode(dockRoot));
463
+ const detached = layout.detached ?? [];
372
464
  return {
373
465
  ...layout,
374
466
  instances,
375
467
  dockRoot,
376
468
  floating: layout.floating.filter((entry) => !removedIds.has(entry.instanceId) && !dockedPaneIds.has(entry.instanceId)),
469
+ detached: detached.filter((entry) => !removedIds.has(entry.instanceId) && !dockedPaneIds.has(entry.instanceId)),
377
470
  };
378
471
  }
379
472
 
@@ -429,6 +522,17 @@ export function normalizePaneLayout(
429
522
  const validInstanceIds = new Set(nextLayout.instances.map((instance) => instance.instanceId));
430
523
  const dockRoot = normalizeDockNode(nextLayout.dockRoot, validInstanceIds, new Set<string>());
431
524
  const dockedPaneIds = new Set(getDockedPaneIdsFromNode(dockRoot));
525
+ const detached = (nextLayout.detached ?? [])
526
+ .filter((entry) => validInstanceIds.has(entry.instanceId) && !dockedPaneIds.has(entry.instanceId))
527
+ .filter((entry, index, entries) => entries.findIndex((candidate) => candidate.instanceId === entry.instanceId) === index)
528
+ .map((entry) => ({
529
+ instanceId: entry.instanceId,
530
+ x: entry.x,
531
+ y: entry.y,
532
+ width: entry.width,
533
+ height: entry.height,
534
+ }));
535
+ const detachedPaneIds = new Set(detached.map((entry) => entry.instanceId));
432
536
 
433
537
  return {
434
538
  dockRoot,
@@ -440,12 +544,18 @@ export function normalizePaneLayout(
440
544
  placementMemory: clonePlacementMemory(instance.placementMemory),
441
545
  })),
442
546
  floating: nextLayout.floating
443
- .filter((entry) => validInstanceIds.has(entry.instanceId) && !dockedPaneIds.has(entry.instanceId))
547
+ .filter((entry) => (
548
+ validInstanceIds.has(entry.instanceId)
549
+ && !dockedPaneIds.has(entry.instanceId)
550
+ && !detachedPaneIds.has(entry.instanceId)
551
+ ))
444
552
  .map((entry) => ({ ...entry })),
553
+ detached,
445
554
  };
446
555
  }
447
556
 
448
557
  export function cloneLayout(layout: LayoutConfig): LayoutConfig {
558
+ const detached = layout.detached ?? [];
449
559
  return {
450
560
  dockRoot: layout.dockRoot ? cloneDockNode(layout.dockRoot) : null,
451
561
  instances: layout.instances.map((instance) => ({
@@ -456,6 +566,7 @@ export function cloneLayout(layout: LayoutConfig): LayoutConfig {
456
566
  placementMemory: clonePlacementMemory(instance.placementMemory),
457
567
  })),
458
568
  floating: layout.floating.map((entry) => ({ ...entry })),
569
+ detached: detached.map((entry) => ({ ...entry })),
459
570
  };
460
571
  }
461
572
 
@@ -464,7 +575,7 @@ export function findPaneInstance(layout: LayoutConfig, instanceId: string): Pane
464
575
  }
465
576
 
466
577
  export function createDefaultConfig(dataDir: string): AppConfig {
467
- const layout = cloneLayout(DEFAULT_LAYOUT);
578
+ const layout = cloneLayout(DEFAULT_HOME_LAYOUT);
468
579
  return {
469
580
  dataDir,
470
581
  configVersion: CURRENT_CONFIG_VERSION,
@@ -473,11 +584,15 @@ export function createDefaultConfig(dataDir: string): AppConfig {
473
584
  portfolios: [{ id: "main", name: "Main Portfolio", currency: "USD" }],
474
585
  watchlists: [{ id: "watchlist", name: "Watchlist" }],
475
586
  layout,
476
- layouts: [{ name: "Default", layout: cloneLayout(layout) }],
587
+ layouts: [
588
+ { name: "Home", layout: cloneLayout(layout) },
589
+ { name: "Monitor", layout: cloneLayout(DEFAULT_MONITOR_LAYOUT) },
590
+ ],
477
591
  activeLayoutIndex: 0,
478
592
  brokerInstances: [],
479
593
  plugins: ["portfolio-list", "ticker-detail", "manual-entry", "ibkr"],
480
- disabledPlugins: ["gloomberb-cloud"],
594
+ disabledPlugins: [],
595
+ disabledSources: [],
481
596
  pluginConfig: {},
482
597
  theme: "amber",
483
598
  chartPreferences: {
@@ -487,3 +602,23 @@ export function createDefaultConfig(dataDir: string): AppConfig {
487
602
  recentTickers: [],
488
603
  };
489
604
  }
605
+
606
+ export function materializeDetachedPanesAsFloating(layout: LayoutConfig): LayoutConfig {
607
+ const detached = layout.detached ?? [];
608
+ if (detached.length === 0) return cloneLayout(layout);
609
+
610
+ return normalizePaneLayout({
611
+ ...cloneLayout(layout),
612
+ floating: [
613
+ ...layout.floating.map((entry) => ({ ...entry })),
614
+ ...detached.map((entry) => ({
615
+ instanceId: entry.instanceId,
616
+ x: entry.x,
617
+ y: entry.y,
618
+ width: entry.width,
619
+ height: entry.height,
620
+ })),
621
+ ],
622
+ detached: [],
623
+ });
624
+ }
@@ -0,0 +1,95 @@
1
+ import type { TickerFinancials } from "./financials";
2
+ import type { TickerRecord } from "./ticker";
3
+
4
+ export type ContextMenuRole =
5
+ | "undo"
6
+ | "redo"
7
+ | "cut"
8
+ | "copy"
9
+ | "paste"
10
+ | "pasteAndMatchStyle"
11
+ | "delete"
12
+ | "selectAll";
13
+
14
+ interface ContextMenuItemBase {
15
+ id?: string;
16
+ label?: string;
17
+ enabled?: boolean;
18
+ checked?: boolean;
19
+ hidden?: boolean;
20
+ accelerator?: string;
21
+ tooltip?: string;
22
+ }
23
+
24
+ export interface ContextMenuActionItem extends ContextMenuItemBase {
25
+ type?: "normal";
26
+ id: string;
27
+ label: string;
28
+ onSelect?: () => void | Promise<void>;
29
+ submenu?: ContextMenuItem[];
30
+ }
31
+
32
+ export interface ContextMenuRoleItem extends ContextMenuItemBase {
33
+ type: "role";
34
+ role: ContextMenuRole;
35
+ submenu?: never;
36
+ onSelect?: never;
37
+ }
38
+
39
+ export interface ContextMenuDividerItem {
40
+ type: "divider";
41
+ id?: string;
42
+ }
43
+
44
+ export type ContextMenuItem =
45
+ | ContextMenuActionItem
46
+ | ContextMenuRoleItem
47
+ | ContextMenuDividerItem;
48
+
49
+ export type ContextMenuContext =
50
+ | { kind: "app" }
51
+ | {
52
+ kind: "pane";
53
+ paneId: string;
54
+ paneType: string;
55
+ title: string;
56
+ floating: boolean;
57
+ }
58
+ | {
59
+ kind: "ticker";
60
+ symbol: string;
61
+ ticker: TickerRecord;
62
+ financials: TickerFinancials | null;
63
+ }
64
+ | {
65
+ kind: "link";
66
+ url: string;
67
+ label?: string;
68
+ }
69
+ | {
70
+ kind: "editable-text";
71
+ value?: string;
72
+ selectedText?: string;
73
+ }
74
+ | {
75
+ kind: "selected-text";
76
+ text: string;
77
+ }
78
+ | {
79
+ kind: "layout";
80
+ layoutIndex: number;
81
+ layoutName: string;
82
+ active: boolean;
83
+ };
84
+
85
+ export function hasRunnableContextMenuItem(items: readonly ContextMenuItem[]): boolean {
86
+ return items.some((item) => {
87
+ if (item.type === "divider" || item.hidden === true || item.enabled === false) return false;
88
+ if (item.type === "role") return true;
89
+ return item.submenu?.length ? hasRunnableContextMenuItem(item.submenu) : true;
90
+ });
91
+ }
92
+
93
+ export function contextMenuDivider(id?: string): ContextMenuDividerItem {
94
+ return id ? { type: "divider", id } : { type: "divider" };
95
+ }