gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -1,27 +1,44 @@
1
- import { Box, SpinnerMark, Text, TextAttributes, useRendererHost, useUiCapabilities } from "../../ui";
2
- import { useCallback, useEffect, useState, type ReactNode } from "react";
3
- import { blendHex, priceColor } from "../../theme/colors";
1
+ import {
2
+ Box,
3
+ Input,
4
+ SpinnerMark,
5
+ Text,
6
+ TextAttributes,
7
+ useRendererHost,
8
+ useUiCapabilities,
9
+ useUiHost,
10
+ type InputRenderable,
11
+ } from "../../ui";
12
+ import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
13
+ import {
14
+ blendHex,
15
+ commandBarBg,
16
+ commandBarPanelBg,
17
+ commandBarSubtleText,
18
+ commandBarText,
19
+ } from "../../theme/colors";
4
20
  import { useThemeColors } from "../../theme/theme-context";
5
- import { useAppActive } from "../../state/app/activity";
21
+ import { NATIVE_COMMAND_SURFACE, nativeCommandSurfaceBorder } from "../command-bar/panel/native-surface";
22
+ import {
23
+ useCommandBarPromptBinding,
24
+ type CommandBarPromptBinding,
25
+ } from "../command-bar/panel/prompt-binding";
6
26
  import { useAppDispatch, useAppSelector } from "../../state/app/context";
7
27
  import {
8
- selectBaseCurrency,
28
+ selectCommandBarOpen,
9
29
  selectUpdateAvailable,
10
30
  selectUpdateCheckInProgress,
11
31
  selectUpdateNotice,
12
32
  selectUpdateProgress,
13
33
  } from "../../state/selectors-ui";
14
- import { getSharedMarketDataCoordinator } from "../../market-data/coordinator";
34
+ import { useViewport } from "../../react/input";
15
35
  import { t, tf } from "../../i18n";
16
- import { useQuoteEntry, useResolvedEntryValue } from "../../market-data/hooks";
17
- import { formatPercentRaw } from "../../utils/format";
18
- import { formatMarketPrice } from "../../market-data/market/format";
19
- import { getActiveQuoteDisplay, marketStateColor, marketStateCountdown, marketStateLabel } from "../../market-data/market/status";
20
- import { VERSION } from "../../version";
21
- import { getTitlebarLeadingInset } from "./titlebar-overlay";
36
+ import { truncateToDisplayWidth } from "../../utils/format";
37
+ import { formatCommandBarShortcut, getShortcutDisplayMode } from "../../utils/shortcut-labels";
38
+ import { resolveMarketSummaryFit, useMarketSummary } from "./market-summary";
39
+ import { resolveHeaderPromptGeometry } from "./shell/chrome";
22
40
  import { WindowControls, WINDOWS_CONTROL_GROUP_WIDTH_PX } from "./window-controls";
23
41
 
24
- const SPY_REFRESH_MS = 5 * 60_000; // 5 min
25
42
  const UPDATE_NOTICE_DURATION_MS = 5_000;
26
43
 
27
44
  type HeaderActionEvent = {
@@ -30,45 +47,198 @@ type HeaderActionEvent = {
30
47
  stopPropagation?: () => void;
31
48
  };
32
49
 
33
- function DesktopHeaderPill({
34
- children,
35
- backgroundColor,
36
- borderColor,
37
- onPress,
38
- ariaLabel,
50
+ /**
51
+ * Chooses what the prompt can say at a given width. The descriptive label earns
52
+ * its space before the shortcut hint does, because the prompt is what tells a
53
+ * first-time user the command bar exists at all.
54
+ */
55
+ function resolveHeaderPromptContent(width: number, shortcutLabel: string): {
56
+ placeholder: string;
57
+ shortcut: string;
58
+ } {
59
+ const textSpace = Math.max(0, width - 2 - "> ".length);
60
+ const full = t("Search or run a command");
61
+ const short = t("Search");
62
+ if (textSpace >= full.length + shortcutLabel.length + 2) return { placeholder: full, shortcut: shortcutLabel };
63
+ if (textSpace >= full.length) return { placeholder: full, shortcut: "" };
64
+ if (textSpace >= short.length) return { placeholder: short, shortcut: "" };
65
+ return { placeholder: "", shortcut: "" };
66
+ }
67
+
68
+ const PROMPT_CARET = "> ";
69
+
70
+ /**
71
+ * The topbar's own fill in the terminal. The prompt used to be the only thing
72
+ * wearing it, which left the bar reading as a pale input pasted onto a darker
73
+ * strip; now the whole row carries it and the prompt paints nothing of its own.
74
+ * Desktop has real chrome to sit on and uses the header colour directly.
75
+ */
76
+ function headerSurface(colors: ReturnType<typeof useThemeColors>): string {
77
+ return blendHex(colors.header, colors.bg, 0.55);
78
+ }
79
+
80
+ /**
81
+ * Desktop chrome for the prompt. Closed it draws nothing: no fill and no edge,
82
+ * so the caret and its placeholder sit straight on the header instead of in a
83
+ * pale box pasted onto it. The hover fill is the whole affordance, and the
84
+ * border stays in the box model as a transparent hairline so opening the bar
85
+ * cannot shift the text by a pixel. Open it is the top half of the command
86
+ * surface: same fill, same border and same shadow as the sheet, rounded only
87
+ * where it is not touching it, and stretched to the header's full height so the
88
+ * two meet with no gap. Both boxes take their left edge and width from
89
+ * `resolveHeaderPromptGeometry`, so the seam is invisible rather than nearly
90
+ * invisible.
91
+ */
92
+ function nativePromptSurfaceStyle(colors: ReturnType<typeof useThemeColors>, open: boolean) {
93
+ if (!open) {
94
+ return {
95
+ border: "1px solid transparent",
96
+ borderRadius: 5,
97
+ };
98
+ }
99
+ const { radiusPx, shadow } = NATIVE_COMMAND_SURFACE;
100
+ return {
101
+ alignSelf: "stretch",
102
+ height: "100%",
103
+ border: `1px solid ${nativeCommandSurfaceBorder(colors)}`,
104
+ borderBottomWidth: 0,
105
+ borderRadius: `${radiusPx}px ${radiusPx}px 0 0`,
106
+ boxShadow: shadow,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * The command bar's input while the bar is open. Gloomberb's panes are driven
112
+ * by bare single keys, so the input only exists while the bar is open; the idle
113
+ * prompt is an affordance that never takes keyboard focus.
114
+ */
115
+ function HeaderPromptInput({
116
+ binding,
117
+ nativePaneChrome,
118
+ width,
39
119
  }: {
40
- children: ReactNode;
41
- backgroundColor?: string;
42
- borderColor?: string;
43
- onPress?: (event?: HeaderActionEvent) => void;
44
- ariaLabel?: string;
120
+ binding: CommandBarPromptBinding;
121
+ nativePaneChrome: boolean;
122
+ width: number;
45
123
  }) {
46
124
  const colors = useThemeColors();
47
- const resolvedBackgroundColor = backgroundColor ?? blendHex(colors.header, colors.bg, 0.28);
48
- const resolvedBorderColor = borderColor ?? blendHex(colors.border, colors.headerText, 0.22);
125
+ const inputRef = useRef<InputRenderable | null>(null);
126
+ const { ghostSuffix, onQueryChange, placeholder, query, screenKey } = binding;
127
+ const textColor = commandBarText(colors);
128
+ const subtleColor = commandBarSubtleText(colors);
129
+
130
+ // The buffer is the source of truth while typing; only an outside change,
131
+ // such as Ctrl+U or a popped route restoring its query, has to be written back.
132
+ useLayoutEffect(() => {
133
+ const input = inputRef.current;
134
+ if (!input || input.editBuffer.getText() === query) return;
135
+ input.editBuffer.setText?.(query);
136
+ input.setCursorOffset?.(query.length);
137
+ }, [query, screenKey]);
138
+
139
+ const ghostLeft = Math.max(0, Math.min(query.length, width - 1));
140
+ return (
141
+ <Box width={width} height={1} position="relative" style={nativePaneChrome ? undefined : { overflow: "hidden" }}>
142
+ <Input
143
+ key={screenKey}
144
+ ref={inputRef}
145
+ value={query}
146
+ onInput={onQueryChange}
147
+ placeholder={placeholder}
148
+ focused
149
+ data-gloom-remote-scope="command-bar"
150
+ data-gloom-remote-surface="command-bar"
151
+ width={nativePaneChrome ? "100%" : width}
152
+ backgroundColor="transparent"
153
+ focusedBackgroundColor="transparent"
154
+ textColor={textColor}
155
+ focusedTextColor={textColor}
156
+ placeholderColor={subtleColor}
157
+ cursorColor={colors.textBright}
158
+ />
159
+ {ghostSuffix && (
160
+ <Box position="absolute" top={0} left={ghostLeft} width={Math.max(0, width - ghostLeft)} height={1}>
161
+ <Text fg={subtleColor}>{truncateToDisplayWidth(ghostSuffix, Math.max(0, width - ghostLeft))}</Text>
162
+ </Box>
163
+ )}
164
+ </Box>
165
+ );
166
+ }
167
+
168
+ function HeaderCommandPrompt({
169
+ nativePaneChrome,
170
+ onOpen,
171
+ open,
172
+ shortcutLabel,
173
+ width,
174
+ }: {
175
+ nativePaneChrome: boolean;
176
+ onOpen: (event?: HeaderActionEvent) => void;
177
+ open: boolean;
178
+ shortcutLabel: string;
179
+ width: number;
180
+ }) {
181
+ const colors = useThemeColors();
182
+ const binding = useCommandBarPromptBinding();
183
+ const { placeholder, shortcut } = resolveHeaderPromptContent(width, shortcutLabel);
184
+ const idleBg = headerSurface(colors);
185
+ // Open, the prompt takes the sheet's own surface so the two read as one
186
+ // control: the sheet is the prompt, expanded. Closed it fills nothing on
187
+ // either host; the terminal row is already this colour end to end, and on
188
+ // desktop the header is.
189
+ const backgroundColor = open
190
+ ? (nativePaneChrome ? commandBarPanelBg(colors) : commandBarBg(colors))
191
+ : undefined;
192
+ const caretColor = open
193
+ ? commandBarText(colors)
194
+ : blendHex(colors.headerText, colors.header, 0.15);
195
+ const mutedColor = blendHex(colors.headerText, colors.header, 0.42);
196
+ // Dimmer than the placeholder it trails: the label is what names the control,
197
+ // the binding is a footnote to it.
198
+ const shortcutColor = blendHex(colors.headerText, colors.header, 0.62);
199
+ // Desktop hover lifts the prompt off the header itself, which is the only
200
+ // thing telling the pointer this text is a control. The terminal has no
201
+ // chrome, so it lifts off the strip fill the whole row already wears.
202
+ const hoverBg = nativePaneChrome
203
+ ? blendHex(colors.header, colors.headerText, 0.14)
204
+ : blendHex(idleBg, colors.headerText, 0.16);
205
+ const inputWidth = Math.max(1, width - 2 - PROMPT_CARET.length);
206
+
49
207
  return (
50
208
  <Box
209
+ width={width}
51
210
  height={1}
52
211
  flexDirection="row"
53
212
  alignItems="center"
54
- backgroundColor={resolvedBackgroundColor}
55
- data-gloom-interactive={onPress ? "true" : undefined}
56
- role={onPress ? "button" : undefined}
57
- tabIndex={onPress ? 0 : undefined}
58
- aria-label={ariaLabel}
59
- onMouseDown={onPress}
60
- onKeyDown={(event: HeaderActionEvent) => {
61
- if (event.key === "Enter" || event.key === " ") onPress?.(event);
213
+ paddingLeft={1}
214
+ paddingRight={1}
215
+ backgroundColor={backgroundColor}
216
+ hoverBackgroundColor={open ? undefined : hoverBg}
217
+ data-gloom-role="header-command-prompt"
218
+ data-gloom-interactive={open ? undefined : "true"}
219
+ role={open ? undefined : "button"}
220
+ tabIndex={open ? undefined : 0}
221
+ aria-label={t("Search or run a command")}
222
+ aria-keyshortcuts={shortcutLabel}
223
+ onMouseDown={open ? undefined : onOpen}
224
+ onKeyDown={open ? undefined : (event: HeaderActionEvent) => {
225
+ if (event.key === "Enter" || event.key === " ") onOpen(event);
62
226
  }}
63
- hoverBackgroundColor={onPress ? blendHex(resolvedBackgroundColor, colors.headerText, 0.12) : undefined}
64
227
  style={{
65
- border: `1px solid ${resolvedBorderColor}`,
66
- borderRadius: 5,
67
- paddingInline: 6,
68
- cursor: onPress ? "pointer" : undefined,
228
+ cursor: open ? undefined : "pointer",
229
+ ...(nativePaneChrome ? nativePromptSurfaceStyle(colors, open) : {}),
69
230
  }}
70
231
  >
71
- {children}
232
+ <Text fg={caretColor} attributes={TextAttributes.BOLD}>{PROMPT_CARET}</Text>
233
+ {open ? (
234
+ binding ? <HeaderPromptInput binding={binding} nativePaneChrome={nativePaneChrome} width={inputWidth} /> : null
235
+ ) : (
236
+ <>
237
+ <Text fg={mutedColor}>{placeholder}</Text>
238
+ {shortcut ? <Text fg={shortcutColor}>{` ${shortcut}`}</Text> : null}
239
+ <Box flexGrow={1} minWidth={0} />
240
+ </>
241
+ )}
72
242
  </Box>
73
243
  );
74
244
  }
@@ -153,67 +323,81 @@ function UpdateStatus() {
153
323
  return null;
154
324
  }
155
325
 
326
+ /**
327
+ * Market state, SPY and the base currency at the header's right edge. It fits
328
+ * itself into the columns the prompt geometry reserved rather than into
329
+ * whatever this row has left over, so the cluster and the prompt can never
330
+ * both claim the same space.
331
+ */
332
+ function HeaderMarketSummary({ nativePaneChrome, width }: { nativePaneChrome: boolean; width: number }) {
333
+ const colors = useThemeColors();
334
+ const summary = useMarketSummary();
335
+ const fit = resolveMarketSummaryFit({
336
+ available: width,
337
+ baseCurrencyWidth: summary.baseCurrency.length + 1,
338
+ countdownWidth: summary.marketLabel.length - summary.marketLabelShort.length,
339
+ spyWidth: summary.spyText.length + 1,
340
+ stateWidth: summary.marketLabelShort ? summary.marketLabelShort.length + 1 : 0,
341
+ });
342
+ const marketLabel = fit.showCountdown ? summary.marketLabel : summary.marketLabelShort;
343
+
344
+ return (
345
+ <>
346
+ {fit.showState && marketLabel ? (
347
+ <Box paddingRight={1} flexShrink={0}>
348
+ <Text fg={summary.marketColor} {...(nativePaneChrome ? { attributes: TextAttributes.BOLD } : {})}>
349
+ {marketLabel}
350
+ </Text>
351
+ </Box>
352
+ ) : null}
353
+ {fit.showSpy ? (
354
+ <Box paddingRight={1} flexShrink={0}>
355
+ <Text fg={summary.spyColor}>{summary.spyText}</Text>
356
+ </Box>
357
+ ) : null}
358
+ {fit.showBaseCurrency ? (
359
+ <Box paddingRight={1} flexShrink={0}>
360
+ <Text fg={blendHex(colors.headerText, colors.header, 0.42)}>{summary.baseCurrency}</Text>
361
+ </Box>
362
+ ) : null}
363
+ </>
364
+ );
365
+ }
366
+
156
367
  export function Header({
157
368
  onOpenHelp,
158
- onOpenChangelog,
159
369
  }: {
160
370
  onOpenHelp?: () => void;
161
- onOpenChangelog?: (version: string) => void;
162
371
  }) {
163
372
  const colors = useThemeColors();
164
373
  const rendererHost = useRendererHost();
165
- const baseCurrency = useAppSelector(selectBaseCurrency);
166
- const appActive = useAppActive();
167
- const { titleBarOverlay, nativeWindowChrome = titleBarOverlay, windowControls } = useUiCapabilities();
374
+ const dispatch = useAppDispatch();
375
+ const commandBarOpen = useAppSelector(selectCommandBarOpen);
376
+ const { width: termWidth } = useViewport();
377
+ const uiKind = useUiHost().kind;
378
+ const { nativePaneChrome = false, titleBarOverlay, nativeWindowChrome = titleBarOverlay, windowControls } = useUiCapabilities();
168
379
  const showWindowControls = nativeWindowChrome && windowControls === "windows";
169
- const titlebarLeadingInset = titleBarOverlay && nativeWindowChrome ? getTitlebarLeadingInset() : 0;
170
- const spyQuoteEntry = useQuoteEntry("SPY", null);
171
- const spyQuote = useResolvedEntryValue(spyQuoteEntry);
172
- const mktState = spyQuote?.marketState;
173
- const [now, setNow] = useState(Date.now());
174
-
175
- useEffect(() => {
176
- if (!appActive || (mktState !== "PRE" && mktState !== "REGULAR")) return;
177
- setNow(Date.now());
178
- const id = setInterval(() => setNow(Date.now()), 1_000);
179
- return () => clearInterval(id);
180
- }, [appActive, mktState]);
181
-
182
- useEffect(() => {
183
- if (!appActive) return;
184
- const coordinator = getSharedMarketDataCoordinator();
185
- if (!coordinator) return;
186
- const fetchSpy = async () => {
187
- await coordinator.loadQuote({ symbol: "SPY" }).catch(() => {});
188
- };
189
- fetchSpy();
190
- const id = setInterval(fetchSpy, SPY_REFRESH_MS);
191
- return () => { clearInterval(id); };
192
- }, [appActive]);
193
-
194
- const activeSpyQuote = getActiveQuoteDisplay(spyQuote);
195
- const spyColor = activeSpyQuote ? priceColor(activeSpyQuote.change, colors) : colors.headerText;
196
- const spyText = activeSpyQuote
197
- ? `SPY ${formatMarketPrice(activeSpyQuote.price, { assetCategory: "ETF" })} ${formatPercentRaw(activeSpyQuote.changePercent)}`
198
- : "SPY —";
380
+ const prompt = resolveHeaderPromptGeometry({
381
+ nativePaneChrome,
382
+ nativeWindowChrome,
383
+ termWidth,
384
+ titleBarOverlay,
385
+ });
386
+ const shortcutLabel = formatCommandBarShortcut(getShortcutDisplayMode(uiKind));
387
+ // Desktop has its own window chrome to sit on; the terminal has none, so the
388
+ // bar itself wears the prompt's fill and reads as a single strip.
389
+ const headerBg = nativePaneChrome ? colors.header : headerSurface(colors);
199
390
 
200
391
  const startWindowDrag = useCallback(() => {
201
392
  if (!titleBarOverlay || !nativeWindowChrome) return;
202
393
  void rendererHost.startWindowDrag?.();
203
394
  }, [nativeWindowChrome, rendererHost, titleBarOverlay]);
204
395
 
205
- // Market status
206
- const mktCountdown = mktState ? marketStateCountdown(mktState, now) : null;
207
- const mktLabel = mktState
208
- ? `${t(marketStateLabel(mktState))}${mktCountdown ? ` · ${mktCountdown}` : ""}`
209
- : "";
210
- const mktColor = mktState ? marketStateColor(mktState, colors) : colors.headerText;
211
-
212
- const openChangelog = useCallback((event?: HeaderActionEvent) => {
396
+ const openCommandBar = useCallback((event?: HeaderActionEvent) => {
213
397
  event?.preventDefault?.();
214
398
  event?.stopPropagation?.();
215
- onOpenChangelog?.(VERSION);
216
- }, [onOpenChangelog]);
399
+ dispatch({ type: "SET_COMMAND_BAR", open: true, query: "" });
400
+ }, [dispatch]);
217
401
 
218
402
  const openHelp = useCallback((event?: HeaderActionEvent) => {
219
403
  event?.preventDefault?.();
@@ -221,36 +405,37 @@ export function Header({
221
405
  onOpenHelp?.();
222
406
  }, [onOpenHelp]);
223
407
 
408
+ const commandPrompt = (
409
+ <HeaderCommandPrompt
410
+ nativePaneChrome={nativePaneChrome}
411
+ onOpen={openCommandBar}
412
+ open={commandBarOpen}
413
+ shortcutLabel={shortcutLabel}
414
+ width={prompt.width}
415
+ />
416
+ );
417
+ const marketSummary = prompt.marketColumns > 0
418
+ ? <HeaderMarketSummary nativePaneChrome={nativePaneChrome} width={prompt.marketColumns} />
419
+ : null;
420
+
224
421
  if (titleBarOverlay) {
225
422
  return (
226
423
  <Box
227
424
  flexDirection="row"
228
425
  height={1}
229
426
  alignItems="center"
230
- backgroundColor={colors.header}
427
+ backgroundColor={headerBg}
231
428
  data-gloom-role="app-header"
232
429
  data-titlebar-overlay="true"
233
430
  onMouseDown={startWindowDrag}
234
431
  style={{
235
- boxShadow: `0 -1px 0 ${colors.header}, inset 0 1px 0 ${colors.header}`,
236
- paddingLeft: 8,
432
+ boxShadow: `0 -1px 0 ${headerBg}, inset 0 1px 0 ${headerBg}`,
237
433
  paddingRight: showWindowControls ? 0 : 12,
238
434
  position: "relative",
239
435
  }}
240
436
  >
241
- <Box paddingLeft={titlebarLeadingInset} flexDirection="row" alignItems="center" gap={1}>
242
- <Text attributes={TextAttributes.BOLD} fg={colors.headerText}>
243
- Gloomberb
244
- </Text>
245
- <DesktopHeaderPill
246
- backgroundColor={blendHex(colors.header, colors.headerText, 0.1)}
247
- borderColor={blendHex(colors.border, colors.headerText, 0.28)}
248
- onPress={onOpenChangelog ? openChangelog : undefined}
249
- ariaLabel={onOpenChangelog ? `Open changelog for v${VERSION}` : undefined}
250
- >
251
- <Text fg={colors.headerText} style={{ fontSize: 11 }}>v{VERSION}</Text>
252
- </DesktopHeaderPill>
253
- </Box>
437
+ <Box width={prompt.left} />
438
+ {commandPrompt}
254
439
  <Box flexGrow={1} paddingLeft={2} paddingRight={2} minWidth={0}>
255
440
  <UpdateStatus />
256
441
  </Box>
@@ -283,22 +468,7 @@ export function Header({
283
468
  <Text fg={blendHex(colors.headerText, colors.header, 0.38)} style={{ marginLeft: 6, fontSize: 10 }}>?</Text>
284
469
  </Box>
285
470
  ) : null}
286
- {mktLabel ? (
287
- <Box paddingRight={1}>
288
- <DesktopHeaderPill
289
- backgroundColor={blendHex(colors.header, mktColor, 0.12)}
290
- borderColor={blendHex(colors.border, mktColor, 0.3)}
291
- >
292
- <Text fg={mktColor} style={{ fontSize: 11, fontWeight: 700 }}>{mktLabel}</Text>
293
- </DesktopHeaderPill>
294
- </Box>
295
- ) : null}
296
- <Box paddingRight={1}>
297
- <Text fg={spyColor}>{spyText}</Text>
298
- </Box>
299
- <Box paddingRight={showWindowControls ? 1 : 0}>
300
- <Text fg={colors.headerText}>{baseCurrency}</Text>
301
- </Box>
471
+ {marketSummary}
302
472
  {showWindowControls ? <Box flexShrink={0} width={`${WINDOWS_CONTROL_GROUP_WIDTH_PX}px`} /> : null}
303
473
  {showWindowControls ? <WindowControls /> : null}
304
474
  </Box>
@@ -309,44 +479,17 @@ export function Header({
309
479
  <Box
310
480
  flexDirection="row"
311
481
  height={1}
312
- backgroundColor={colors.header}
482
+ backgroundColor={headerBg}
313
483
  data-gloom-role="app-header"
314
484
  data-titlebar-overlay={titleBarOverlay ? "true" : undefined}
315
485
  onMouseDown={startWindowDrag}
316
486
  >
317
- <Box paddingLeft={titleBarOverlay ? titlebarLeadingInset : 1} flexDirection="row">
318
- <Text attributes={TextAttributes.BOLD} fg={colors.headerText}>Gloomberb </Text>
319
- <Box
320
- data-gloom-interactive={onOpenChangelog ? "true" : undefined}
321
- role={onOpenChangelog ? "button" : undefined}
322
- tabIndex={onOpenChangelog ? 0 : undefined}
323
- aria-label={onOpenChangelog ? `Open changelog for v${VERSION}` : undefined}
324
- onMouseDown={onOpenChangelog ? openChangelog : undefined}
325
- onKeyDown={(event: HeaderActionEvent) => {
326
- if (event.key === "Enter" || event.key === " ") openChangelog(event);
327
- }}
328
- hoverBackgroundColor={onOpenChangelog ? blendHex(colors.header, colors.headerText, 0.15) : undefined}
329
- style={{ cursor: onOpenChangelog ? "pointer" : undefined }}
330
- >
331
- <Text attributes={TextAttributes.BOLD} fg={colors.headerText}>v{VERSION}</Text>
332
- </Box>
333
- </Box>
334
- <Box flexGrow={1} paddingLeft={2}>
487
+ <Box width={prompt.left} />
488
+ {commandPrompt}
489
+ <Box flexGrow={1} minWidth={0} paddingLeft={2}>
335
490
  <UpdateStatus />
336
491
  </Box>
337
- {mktLabel && (
338
- <Box paddingRight={1}>
339
- <Text fg={mktColor}>{mktLabel}</Text>
340
- </Box>
341
- )}
342
- <Box paddingRight={1}>
343
- <Text fg={spyColor}>{spyText}</Text>
344
- </Box>
345
- <Box paddingRight={1}>
346
- <Text fg={colors.headerText}>
347
- {baseCurrency}
348
- </Text>
349
- </Box>
492
+ {marketSummary}
350
493
  {showWindowControls ? <WindowControls /> : null}
351
494
  </Box>
352
495
  );
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { resolveHeaderPromptGeometry } from "./shell/chrome";
3
+ import { resolveMarketSummaryFit } from "./market-summary";
4
+
5
+ /** "PRE-MKT · 1h 17m SPY 762.43 -0.60% USD", each part with its trailing gap. */
6
+ const CLUSTER = {
7
+ baseCurrencyWidth: 4,
8
+ countdownWidth: 9,
9
+ spyWidth: 18,
10
+ stateWidth: 8,
11
+ };
12
+
13
+ describe("header market cluster", () => {
14
+ /**
15
+ * The cluster shares the header with the command prompt, so a narrow window
16
+ * has to take it apart in an order the eye can follow: the countdown suffix
17
+ * first, then the base currency, then the state label, and SPY last because
18
+ * it is the value that moves.
19
+ */
20
+ test("sheds the countdown, then the currency, then the state, keeping SPY longest", () => {
21
+ expect(resolveMarketSummaryFit({ available: 39, ...CLUSTER })).toEqual({
22
+ showSpy: true,
23
+ showState: true,
24
+ showBaseCurrency: true,
25
+ showCountdown: true,
26
+ });
27
+ expect(resolveMarketSummaryFit({ available: 38, ...CLUSTER })).toMatchObject({
28
+ showSpy: true,
29
+ showState: true,
30
+ showBaseCurrency: true,
31
+ showCountdown: false,
32
+ });
33
+ expect(resolveMarketSummaryFit({ available: 29, ...CLUSTER })).toMatchObject({
34
+ showSpy: true,
35
+ showState: true,
36
+ showBaseCurrency: false,
37
+ showCountdown: false,
38
+ });
39
+ expect(resolveMarketSummaryFit({ available: 25, ...CLUSTER })).toMatchObject({
40
+ showSpy: true,
41
+ showState: false,
42
+ showBaseCurrency: true,
43
+ });
44
+ expect(resolveMarketSummaryFit({ available: 17, ...CLUSTER })).toMatchObject({
45
+ showSpy: false,
46
+ showState: true,
47
+ showBaseCurrency: true,
48
+ });
49
+ });
50
+
51
+ /** A countdown without the label it extends would read as a lone timer. */
52
+ test("never shows a countdown without its state label", () => {
53
+ const fit = resolveMarketSummaryFit({ ...CLUSTER, available: 30, stateWidth: 0 });
54
+ expect(fit.showState).toBe(false);
55
+ expect(fit.showCountdown).toBe(false);
56
+ });
57
+
58
+ /**
59
+ * The reserve lives with the prompt geometry and the widths live here, so
60
+ * this is the seam where the two can drift: a reserve a column short would
61
+ * silently drop the countdown on a window wide enough to show it.
62
+ */
63
+ test("fits the whole cluster in the columns the header reserves for it", () => {
64
+ for (const termWidth of [120, 200]) {
65
+ const { marketColumns } = resolveHeaderPromptGeometry({ termWidth });
66
+ expect(resolveMarketSummaryFit({ available: marketColumns, ...CLUSTER })).toEqual({
67
+ showSpy: true,
68
+ showState: true,
69
+ showBaseCurrency: true,
70
+ showCountdown: true,
71
+ });
72
+ }
73
+
74
+ // 80 columns keeps only SPY, and reserves nothing it cannot spend.
75
+ const narrow = resolveHeaderPromptGeometry({ termWidth: 80 });
76
+ expect(resolveMarketSummaryFit({ available: narrow.marketColumns, ...CLUSTER })).toMatchObject({
77
+ showSpy: true,
78
+ showState: false,
79
+ });
80
+ });
81
+ });