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
package/src/i18n/ko.ts CHANGED
@@ -571,6 +571,7 @@ export const ko: Record<string, string> = {
571
571
  "Open the help window": "도움말 창 열기",
572
572
  "Set up a portfolio": "포트폴리오 설정",
573
573
  "How would you like to get started?": "어떻게 시작하시겠습니까?",
574
+ "{shortcut} to get started.": "{shortcut} 로 시작하세요.",
574
575
  "Edits later from the command bar.": "나중에 명령 팔레트에서 수정할 수 있습니다.",
575
576
  "Create Manual Portfolio": "수동 포트폴리오 만들기",
576
577
  "Add tickers and positions by hand": "종목과 포지션 직접 추가",
@@ -773,7 +774,7 @@ export const ko: Record<string, string> = {
773
774
  "Verify your email to send messages.": "메시지를 보내려면 이메일을 인증하세요.",
774
775
  "Verify your email to use Cloud tweets.": "Cloud 게시물을 사용하려면 이메일을 인증하세요.",
775
776
  "Resend Verification Email": "확인 이메일 다시 보내기",
776
- "Ctrl+P, then Resend Verification Email": "Ctrl+P 후 인증 이메일 재전송",
777
+ "{shortcut}, then Resend Verification Email": "{shortcut} 후 인증 이메일 재전송",
777
778
  "editing": "편집 중",
778
779
  "replying to": "답장 대상",
779
780
  "Reply": "답장",
@@ -1011,7 +1012,7 @@ export const ko: Record<string, string> = {
1011
1012
  "No watchlist tickers": "관심 목록에 종목 없음",
1012
1013
  "No tickers in {name}.": "{name}에 종목이 없습니다.",
1013
1014
  "this collection": "이 컬렉션",
1014
- "Press Ctrl+P to add one.": "Ctrl+P를 눌러 추가하세요.",
1015
+ "Press {shortcut} to add one.": "{shortcut} 를 눌러 추가하세요.",
1015
1016
  "PRE-MKT": "장전",
1016
1017
  "OPEN": "장중",
1017
1018
  "AFTER-HRS": "장후",
@@ -1077,6 +1078,9 @@ export const ko: Record<string, string> = {
1077
1078
  "Approve from your phone, no typing": "입력 없이 휴대폰에서 승인",
1078
1079
  "Scan with the Gloom app": "Gloom 앱으로 스캔",
1079
1080
  "Open the Gloom app on your phone and approve this workspace.": "휴대폰에서 Gloom 앱을 열고 이 워크스페이스를 승인하세요.",
1081
+ "Approve the code from a phone that is already signed in.": "이미 로그인한 휴대폰에서 코드를 승인하세요.",
1082
+ "Layouts, watchlists and portfolios sync to your account.": "레이아웃, 관심 목록, 포트폴리오가 계정에 동기화됩니다.",
1083
+ "Use email instead": "이메일 사용",
1080
1084
  "Scan with the Gloom app to sign in": "Gloom 앱으로 스캔하여 로그인",
1081
1085
  "Waiting for approval...": "승인을 기다리는 중...",
1082
1086
  "Approved as {email}": "{email}(으)로 승인됨",
package/src/i18n/zh-cn.ts CHANGED
@@ -573,6 +573,7 @@ export const zhCN: Record<string, string> = {
573
573
  "Open the help window": "打开帮助窗口",
574
574
  "Set up a portfolio": "设置投资组合",
575
575
  "How would you like to get started?": "你想如何开始?",
576
+ "{shortcut} to get started.": "按 {shortcut} 开始。",
576
577
  "Edits later from the command bar.": "之后可在命令栏中修改。",
577
578
  "Create Manual Portfolio": "创建手动组合",
578
579
  "Add tickers and positions by hand": "手动添加股票与持仓",
@@ -775,7 +776,7 @@ export const zhCN: Record<string, string> = {
775
776
  "Verify your email to send messages.": "验证邮箱后才能发送消息。",
776
777
  "Verify your email to use Cloud tweets.": "验证邮箱后才能使用 Cloud tweets。",
777
778
  "Resend Verification Email": "重新发送验证邮件",
778
- "Ctrl+P, then Resend Verification Email": "按 Ctrl+P,然后选择“重新发送验证邮件”",
779
+ "{shortcut}, then Resend Verification Email": "按 {shortcut},然后选择“重新发送验证邮件”",
779
780
  "editing": "编辑中",
780
781
  "replying to": "回复",
781
782
  "Reply": "回复",
@@ -1013,7 +1014,7 @@ export const zhCN: Record<string, string> = {
1013
1014
  "No watchlist tickers": "自选列表暂无股票",
1014
1015
  "No tickers in {name}.": "{name} 中暂无股票。",
1015
1016
  "this collection": "此集合",
1016
- "Press Ctrl+P to add one.": "按 Ctrl+P 添加。",
1017
+ "Press {shortcut} to add one.": "按 {shortcut} 添加。",
1017
1018
  "PRE-MKT": "盘前",
1018
1019
  "OPEN": "盘中",
1019
1020
  "AFTER-HRS": "盘后",
@@ -1079,6 +1080,9 @@ export const zhCN: Record<string, string> = {
1079
1080
  "Approve from your phone, no typing": "在手机上确认,无需输入",
1080
1081
  "Scan with the Gloom app": "用 Gloom 应用扫码",
1081
1082
  "Open the Gloom app on your phone and approve this workspace.": "在手机上打开 Gloom 应用并确认此工作区。",
1083
+ "Approve the code from a phone that is already signed in.": "在已登录的手机上确认此代码。",
1084
+ "Layouts, watchlists and portfolios sync to your account.": "布局、自选列表和投资组合会同步到你的账户。",
1085
+ "Use email instead": "改用邮箱",
1082
1086
  "Scan with the Gloom app to sign in": "用 Gloom 应用扫码登录",
1083
1087
  "Waiting for approval...": "等待确认...",
1084
1088
  "Approved as {email}": "已以 {email} 登录",
package/src/i18n/zh-tw.ts CHANGED
@@ -573,6 +573,7 @@ export const zhTW: Record<string, string> = {
573
573
  "Open the help window": "開啟說明視窗",
574
574
  "Set up a portfolio": "設定投資組合",
575
575
  "How would you like to get started?": "你想如何開始?",
576
+ "{shortcut} to get started.": "按 {shortcut} 開始。",
576
577
  "Edits later from the command bar.": "之後可在命令列中修改。",
577
578
  "Create Manual Portfolio": "建立手動投資組合",
578
579
  "Add tickers and positions by hand": "手動新增股票與持倉",
@@ -775,7 +776,7 @@ export const zhTW: Record<string, string> = {
775
776
  "Verify your email to send messages.": "驗證電子郵件後才能傳送訊息。",
776
777
  "Verify your email to use Cloud tweets.": "驗證電子郵件後才能使用 Cloud tweets。",
777
778
  "Resend Verification Email": "重新發送驗證郵件",
778
- "Ctrl+P, then Resend Verification Email": "按 Ctrl+P,然後選擇「重新發送驗證郵件」",
779
+ "{shortcut}, then Resend Verification Email": "按 {shortcut},然後選擇「重新發送驗證郵件」",
779
780
  "editing": "編輯中",
780
781
  "replying to": "回覆",
781
782
  "Reply": "回覆",
@@ -1013,7 +1014,7 @@ export const zhTW: Record<string, string> = {
1013
1014
  "No watchlist tickers": "自選清單暫無股票",
1014
1015
  "No tickers in {name}.": "{name} 中暫無股票。",
1015
1016
  "this collection": "此清單",
1016
- "Press Ctrl+P to add one.": "按 Ctrl+P 新增。",
1017
+ "Press {shortcut} to add one.": "按 {shortcut} 新增。",
1017
1018
  "PRE-MKT": "盤前",
1018
1019
  "OPEN": "盤中",
1019
1020
  "AFTER-HRS": "盤後",
@@ -1079,6 +1080,9 @@ export const zhTW: Record<string, string> = {
1079
1080
  "Approve from your phone, no typing": "在手機上確認,無需輸入",
1080
1081
  "Scan with the Gloom app": "用 Gloom 應用程式掃碼",
1081
1082
  "Open the Gloom app on your phone and approve this workspace.": "在手機上開啟 Gloom 應用程式並確認此工作區。",
1083
+ "Approve the code from a phone that is already signed in.": "在已登入的手機上確認這組代碼。",
1084
+ "Layouts, watchlists and portfolios sync to your account.": "版面、自選清單與投資組合會同步到你的帳號。",
1085
+ "Use email instead": "改用電子郵件",
1082
1086
  "Scan with the Gloom app to sign in": "用 Gloom 應用程式掃碼登入",
1083
1087
  "Waiting for approval...": "等待確認...",
1084
1088
  "Approved as {email}": "已以 {email} 登入",
@@ -1,11 +1,11 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Box, Input, Text, TextAttributes, type InputRenderable } from "../ui";
3
- import { Button } from "../components/ui/button";
2
+ import { Box, Text, TextAttributes, type InputRenderable } from "../ui";
3
+ import { InputSearchBar, usePaneFooter } from "../components";
4
4
  import { ListView, type ListViewItem } from "../components/ui/list-view";
5
5
  import { useShortcut, useViewport } from "../react/input";
6
- import { useAppInputCapture } from "../state/app/input-capture";
7
6
  import { useThemeColors } from "../theme/theme-context";
8
7
  import { truncateToDisplayWidth } from "../utils/format";
8
+ import { isPlainKey } from "../utils/keyboard";
9
9
  import { t, tf } from "../i18n";
10
10
  import type { LayoutGalleryController } from "./gallery";
11
11
  import {
@@ -71,9 +71,23 @@ export function LayoutGalleryTerminal({
71
71
  const detailsWidth = Math.min(DETAILS_WIDTH, Math.max(28, Math.floor(paneWidth * 0.42)));
72
72
  const inputRef = useRef<InputRenderable | null>(null);
73
73
  const [searchFocused, setSearchFocused] = useState(false);
74
+ const [searchFocusToken, setSearchFocusToken] = useState(0);
74
75
  const [selectedIndex, setSelectedIndex] = useState(0);
75
- useAppInputCapture(searchFocused && !dialogOpen);
76
- const { community, owned, select } = controller;
76
+ const {
77
+ activate: activateLayout,
78
+ canDelete,
79
+ community,
80
+ copyLink,
81
+ deleteLayout,
82
+ duplicateLayout,
83
+ install: installLayout,
84
+ newLayout,
85
+ owned,
86
+ publishCurrent,
87
+ publishing,
88
+ renameLayout,
89
+ select,
90
+ } = controller;
77
91
  const discoverStatus = discoverStatusRow(controller);
78
92
 
79
93
  const rows = useMemo<GalleryRow[]>(() => {
@@ -138,6 +152,12 @@ export function LayoutGalleryTerminal({
138
152
  setSelectedIndex(selectableIndexes[nextPosition]!);
139
153
  }, [selectableIndexes, selectedIndex]);
140
154
 
155
+ const focusSearch = useCallback(() => {
156
+ setSearchFocused(true);
157
+ setSearchFocusToken((current) => current + 1);
158
+ }, []);
159
+ const blurSearch = useCallback(() => setSearchFocused(false), []);
160
+
141
161
  // The details panel is always on screen here, so Enter runs the action it shows
142
162
  // instead of stepping through a separate detail state.
143
163
  const activate = useCallback((row: GalleryRow | null) => {
@@ -147,70 +167,120 @@ export function LayoutGalleryTerminal({
147
167
  return;
148
168
  }
149
169
  if (!row.entry) return;
150
- if (row.entry.kind === "community") controller.install(row.entry);
151
- else controller.activate(row.entry);
152
- }, [controller]);
170
+ if (row.entry.kind === "community") installLayout(row.entry);
171
+ else activateLayout(row.entry);
172
+ }, [activateLayout, installLayout]);
173
+
174
+ const selectedRowRef = useRef<GalleryRow | null>(selectedRow);
175
+ selectedRowRef.current = selectedRow;
176
+ const activateSelected = useCallback(() => activate(selectedRowRef.current), [activate]);
177
+ const renameSelected = useCallback(() => {
178
+ const entry = selectedRowRef.current?.entry;
179
+ if (entry?.kind === "owned") renameLayout(entry);
180
+ }, [renameLayout]);
181
+ const copySelected = useCallback(() => {
182
+ const entry = selectedRowRef.current?.entry;
183
+ if (entry?.kind === "community") copyLink(entry);
184
+ else if (entry?.kind === "owned") duplicateLayout(entry);
185
+ }, [copyLink, duplicateLayout]);
186
+ const deleteSelected = useCallback(() => {
187
+ const entry = selectedRowRef.current?.entry;
188
+ if (entry?.kind === "owned") deleteLayout(entry);
189
+ }, [deleteLayout]);
190
+
191
+ usePaneFooter("layout-marketplace", () => ({
192
+ info: publishing
193
+ ? [{ id: "publishing", parts: [{ text: "publishing", tone: "muted" as const }] }]
194
+ : [],
195
+ hints: [
196
+ { id: "search", key: "/", label: "search", onPress: focusSearch },
197
+ { id: "new", key: "n", label: "ew", onPress: newLayout },
198
+ ...(selectedEntry?.kind === "owned"
199
+ ? [
200
+ { id: "open", key: "o", label: "pen", onPress: activateSelected },
201
+ { id: "rename", key: "r", label: "ename", onPress: renameSelected },
202
+ { id: "copy", key: "c", label: "opy", onPress: copySelected },
203
+ { id: "delete", key: "d", label: "elete", onPress: deleteSelected, disabled: !canDelete },
204
+ ]
205
+ : selectedEntry?.kind === "community"
206
+ ? [
207
+ { id: "add", key: "a", label: "dd layout", onPress: activateSelected },
208
+ { id: "copy-link", key: "c", label: "opy link", onPress: copySelected },
209
+ ]
210
+ : selectedRow?.id === "discover:retry"
211
+ ? [{ id: "retry", key: "r", label: "etry", onPress: activateSelected }]
212
+ : selectedRow?.action
213
+ ? [{ id: "open", key: "o", label: "pen", onPress: activateSelected }]
214
+ : []),
215
+ { id: "publish", key: "p", label: "ublish", onPress: publishCurrent, disabled: publishing },
216
+ ],
217
+ }), [
218
+ activateSelected,
219
+ canDelete,
220
+ copySelected,
221
+ deleteSelected,
222
+ focusSearch,
223
+ newLayout,
224
+ publishCurrent,
225
+ publishing,
226
+ renameSelected,
227
+ selectedEntry?.kind,
228
+ selectedRow?.action,
229
+ selectedRow?.id,
230
+ ]);
153
231
 
154
232
  useShortcut((event) => {
155
233
  if (dialogOpen) return;
156
- const editable = event.targetEditable === true;
157
234
  // Leaving the search field is the first Escape; the gallery closes on the next.
158
- if (event.name === "escape" && searchFocused) {
159
- event.preventDefault();
160
- event.stopPropagation();
161
- setSearchFocused(false);
162
- return;
163
- }
164
- if (event.name === "down" || (!editable && event.name === "j")) {
235
+ if (isPlainKey(event, "escape", "esc") && searchFocused) {
165
236
  event.preventDefault();
166
237
  event.stopPropagation();
167
- move(1);
238
+ blurSearch();
168
239
  return;
169
240
  }
170
- if (event.name === "up" || (!editable && event.name === "k")) {
171
- event.preventDefault();
172
- event.stopPropagation();
173
- move(-1);
174
- return;
175
- }
176
- if (event.name === "enter" || event.name === "return") {
177
- event.preventDefault();
178
- event.stopPropagation();
179
- activate(selectedRow);
180
- return;
181
- }
182
- if (!editable && (event.name === "/" || event.sequence === "/")) {
241
+ if (event.targetEditable) return;
242
+
243
+ const run = (action: () => void) => {
183
244
  event.preventDefault();
184
245
  event.stopPropagation();
185
- setSearchFocused(true);
186
- inputRef.current?.focus?.();
187
- }
246
+ action();
247
+ };
248
+ if (isPlainKey(event, "down", "j")) run(() => move(1));
249
+ else if (isPlainKey(event, "up", "k")) {
250
+ run(() => {
251
+ if (event.name === "up" && selectedIndex === selectableIndexes[0]) focusSearch();
252
+ else move(-1);
253
+ });
254
+ } else if (isPlainKey(event, "enter", "return")) run(activateSelected);
255
+ else if (isPlainKey(event, "/")) run(focusSearch);
256
+ else if (isPlainKey(event, "n")) run(newLayout);
257
+ else if (isPlainKey(event, "p")) run(publishCurrent);
258
+ else if (isPlainKey(event, "o") && (selectedEntry?.kind === "owned" || selectedRow?.action)) run(activateSelected);
259
+ else if (isPlainKey(event, "a") && selectedEntry?.kind === "community") run(activateSelected);
260
+ else if (isPlainKey(event, "r") && selectedRow?.id === "discover:retry") run(activateSelected);
261
+ else if (isPlainKey(event, "r") && selectedEntry?.kind === "owned") run(renameSelected);
262
+ else if (isPlainKey(event, "c") && selectedEntry) run(copySelected);
263
+ else if (isPlainKey(event, "d") && selectedEntry?.kind === "owned" && canDelete) run(deleteSelected);
188
264
  }, { allowEditable: true, enabled: focused && !dialogOpen, phase: "before", scope: "layout-gallery" });
189
265
 
190
266
  const bodyHeight = Math.max(4, paneHeight - 1);
191
267
 
192
268
  return (
193
269
  <Box flexGrow={1} flexDirection="column" backgroundColor={colors.bg}>
194
- <Box height={1} flexDirection="row" paddingX={1} backgroundColor={colors.panel}>
195
- <Text fg={searchFocused ? colors.textBright : colors.textDim}>{"/ "}</Text>
196
- <Input
197
- ref={inputRef}
198
- value={controller.query}
199
- focused={searchFocused && !dialogOpen}
200
- placeholder={t("Search layouts and panes")}
201
- placeholderColor={colors.textDim}
202
- textColor={colors.text}
203
- backgroundColor={colors.panel}
204
- focusedBackgroundColor={colors.panel}
205
- cursorColor={colors.textBright}
206
- flexGrow={1}
207
- onInput={controller.setQuery}
208
- onChange={controller.setQuery}
209
- onFocus={() => setSearchFocused(true)}
210
- onBlur={() => setSearchFocused(false)}
211
- onSubmit={() => setSearchFocused(false)}
212
- />
213
- </Box>
270
+ <InputSearchBar
271
+ value={controller.query}
272
+ focused={focused && !dialogOpen}
273
+ active={searchFocused}
274
+ width={paneWidth}
275
+ focusToken={searchFocusToken}
276
+ inputRef={inputRef}
277
+ placeholder={t("Search layouts and panes")}
278
+ debounceMs={80}
279
+ onFocus={focusSearch}
280
+ onBlur={blurSearch}
281
+ onNavigateDown={blurSearch}
282
+ onQueryChange={controller.setQuery}
283
+ />
214
284
 
215
285
  <Box flexDirection="row" flexGrow={1}>
216
286
  <Box flexGrow={1} flexDirection="column" paddingX={1}>
@@ -257,17 +327,12 @@ export function LayoutGalleryTerminal({
257
327
  controller={controller}
258
328
  entry={selectedEntry}
259
329
  width={detailsWidth}
260
- height={paneHeight}
330
+ height={bodyHeight}
261
331
  />
262
332
  ) : selectedRow?.action ? (
263
333
  <Box flexDirection="column" gap={1}>
264
334
  <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{selectedRow.label}</Text>
265
335
  {selectedRow.detail && <Text fg={colors.textMuted}>{selectedRow.detail}</Text>}
266
- <Button
267
- label={selectedRow.id === "discover:login" ? "Log in" : "Retry"}
268
- variant="primary"
269
- onPress={selectedRow.action}
270
- />
271
336
  </Box>
272
337
  ) : (
273
338
  <Text fg={colors.textDim}>{t("No layout selected.")}</Text>
@@ -292,8 +357,8 @@ function LayoutDetails({
292
357
  const colors = useThemeColors();
293
358
  const allPanes = summarizeLayoutPanes(entry.layout, controller.panes);
294
359
  const missing = allPanes.filter((pane) => pane.missing);
295
- // Leave room for the title block, the warning line, and both action rows.
296
- const paneBudget = Math.max(3, height - 13);
360
+ // Leave room for the title block and the optional warning.
361
+ const paneBudget = Math.max(3, height - 8);
297
362
  const overflow = Math.max(0, allPanes.length - paneBudget);
298
363
  const panes = overflow > 0 ? allPanes.slice(0, paneBudget - 1) : allPanes;
299
364
 
@@ -336,41 +401,6 @@ function LayoutDetails({
336
401
  </Text>
337
402
  </>
338
403
  )}
339
- <Box height={1} />
340
- <Box flexDirection="row" gap={1}>
341
- <Button
342
- label={entry.kind === "community" ? "Add Layout" : "Open"}
343
- variant="primary"
344
- onPress={() => (entry.kind === "community"
345
- ? controller.install(entry)
346
- : controller.activate(entry))}
347
- />
348
- {entry.kind === "community" && (
349
- <Button label="Copy Link" variant="secondary" onPress={() => controller.copyLink(entry)} />
350
- )}
351
- {entry.kind === "owned" && (
352
- <>
353
- <Button label="Rename" variant="secondary" onPress={() => controller.renameLayout(entry)} />
354
- <Button label="Copy" variant="secondary" onPress={() => controller.duplicateLayout(entry)} />
355
- <Button
356
- label="Delete"
357
- variant="secondary"
358
- disabled={!controller.canDelete}
359
- onPress={() => controller.deleteLayout(entry)}
360
- />
361
- </>
362
- )}
363
- </Box>
364
- <Box height={1} />
365
- <Box flexDirection="row" gap={1}>
366
- <Button label="New" variant="ghost" onPress={controller.newLayout} />
367
- <Button
368
- label={controller.publishing ? "Publishing" : "Publish"}
369
- variant="ghost"
370
- disabled={controller.publishing}
371
- onPress={controller.publishCurrent}
372
- />
373
- </Box>
374
404
  </Box>
375
405
  );
376
406
  }
@@ -4,6 +4,8 @@ import { testRender } from "../renderers/opentui/test-utils";
4
4
  import { AppContext, createInitialState, type AppAction } from "../state/app/context";
5
5
  import { cloneLayout, createDefaultConfig } from "../types/config";
6
6
  import type { PaneDef } from "../types/plugin";
7
+ import { Box } from "../ui";
8
+ import { PaneFooterBar, PaneFooterProvider } from "../components/layout/pane/footer";
7
9
  import type { PluginRegistry } from "../plugins/registry";
8
10
  import { LayoutMarketplaceGallery } from "./gallery";
9
11
 
@@ -43,7 +45,21 @@ async function renderGallery() {
43
45
 
44
46
  testSetup = await testRender(
45
47
  <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
46
- <LayoutMarketplaceGallery pluginRegistry={registry} onClose={() => { closed = true; }} />
48
+ <PaneFooterProvider>
49
+ {(footer) => (
50
+ <Box width={100} height={24} flexDirection="column">
51
+ <Box width={100} height={23}>
52
+ <LayoutMarketplaceGallery
53
+ pluginRegistry={registry}
54
+ width={100}
55
+ height={23}
56
+ onClose={() => { closed = true; }}
57
+ />
58
+ </Box>
59
+ <PaneFooterBar footer={footer} focused width={100} />
60
+ </Box>
61
+ )}
62
+ </PaneFooterProvider>
47
63
  </AppContext>,
48
64
  { width: 100, height: 24 },
49
65
  );
@@ -65,6 +81,54 @@ test("lists owned layouts before Discover and details the selected layout", asyn
65
81
  expect(frame).toContain("Portfolio");
66
82
  expect(frame).toContain("Ticker Research");
67
83
  expect(frame).toContain("3 docked");
84
+
85
+ const searchLine = frame.split("\n").find((line) => line.includes("Search layouts and panes"));
86
+ expect(searchLine?.startsWith("/ Search layouts and panes")).toBe(true);
87
+ expect(frame).toContain("[/]search");
88
+ expect(frame).toContain("[n]ew");
89
+ expect(frame).toContain("[o]pen");
90
+ expect(frame).toContain("[r]ename");
91
+ expect(frame).toContain("[c]opy");
92
+ expect(frame).toContain("[d]elete");
93
+ expect(frame).toContain("[p]ublish");
94
+ });
95
+
96
+ test("search Enter returns to the list before activating the filtered layout", async () => {
97
+ const { actions, isClosed } = await renderGallery();
98
+
99
+ await act(async () => {
100
+ testSetup!.mockInput.pressKey("/");
101
+ await testSetup!.renderOnce();
102
+ });
103
+ await act(async () => {
104
+ await testSetup!.mockInput.typeText("Research");
105
+ testSetup!.mockInput.pressEnter();
106
+ await testSetup!.renderOnce();
107
+ });
108
+
109
+ expect(isClosed()).toBe(false);
110
+ expect(actions.some((action) => action.type === "SWITCH_LAYOUT")).toBe(false);
111
+ expect(testSetup!.captureCharFrame()).toContain("/ Research");
112
+
113
+ await act(async () => {
114
+ testSetup!.mockInput.pressEnter();
115
+ await testSetup!.renderOnce();
116
+ });
117
+
118
+ expect(actions).toContainEqual({ type: "SWITCH_LAYOUT", index: 1 });
119
+ expect(isClosed()).toBe(true);
120
+ });
121
+
122
+ test("n opens the new layout workflow", async () => {
123
+ const { isClosed } = await renderGallery();
124
+
125
+ await act(async () => {
126
+ testSetup!.mockInput.pressKey("n");
127
+ await testSetup!.renderOnce();
128
+ });
129
+
130
+ expect(isClosed()).toBe(false);
131
+ expect(testSetup!.captureCharFrame()).toContain("Create Layout");
68
132
  });
69
133
 
70
134
  test("j/k move the selection and Enter switches to the layout and closes", async () => {
@@ -22,6 +22,7 @@ const IMAGERY_RULES: ReadonlyArray<readonly [string, PaneImagery]> = [
22
22
  ["sectors", "heatmap"],
23
23
  ["treemap", "heatmap"],
24
24
  ["fear-greed", "gauge"],
25
+ ["valuation", "chart"],
25
26
  ["kelly", "gauge"],
26
27
  ["gauge", "gauge"],
27
28
  ["calendar", "calendar"],
@@ -157,6 +157,23 @@ describe("layout marketplace payloads", () => {
157
157
  expect(JSON.stringify(pane)).not.toContain("secret");
158
158
  });
159
159
 
160
+ test("drops cache-sized pane state instead of failing the share", () => {
161
+ const pane = publishableMarketplacePane({
162
+ instanceId: "news-top:main",
163
+ paneId: "prediction-markets",
164
+ binding: { kind: "none" },
165
+ }, {
166
+ sort: { columnId: "time", direction: "desc" },
167
+ "news-top:articles": Array.from({ length: 400 }, (_, index) => ({
168
+ id: `article-${index}`,
169
+ title: "x".repeat(200),
170
+ publishedAt: "2026-08-26T00:00:00.000Z",
171
+ })),
172
+ }, panes);
173
+
174
+ expect(pane.paneState.p1).toEqual({ sort: { columnId: "time", direction: "desc" } });
175
+ });
176
+
160
177
  test("shares portable pane setup and state while redacting private data", () => {
161
178
  const fixture = portableFixture();
162
179
  const payload = publishableMarketplaceLayout(fixture.layout, fixture.paneState, panes);
@@ -12,6 +12,10 @@ import type {
12
12
  } from "../types/plugin";
13
13
 
14
14
  const MAX_LAYOUT_BYTES = 128 * 1024;
15
+ // Pane runtime state doubles as a cache (news panes keep six figures of bytes of
16
+ // article backlog), so bulky fields drop out of the share rather than failing
17
+ // the whole publish.
18
+ const MAX_PANE_STATE_FIELD_BYTES = 16 * 1024;
15
19
  const MAX_INSTANCES = 40;
16
20
  const MAX_DOCK_DEPTH = 20;
17
21
  const MAX_ID_LENGTH = 160;
@@ -493,14 +497,45 @@ function publishableLayout(
493
497
  height,
494
498
  })),
495
499
  };
496
- const parsed = parseMarketplaceLayoutPayload({
500
+ let publishableState = Object.fromEntries(
501
+ Object.entries(projectedState).flatMap(([id, state]) => {
502
+ const trimmed = withoutCacheFields(state);
503
+ return Object.keys(trimmed).length > 0 ? [[id, trimmed] as const] : [];
504
+ }),
505
+ );
506
+ let parsed = parsePublishablePayload(projected, publishableState);
507
+ while (!parsed && Object.keys(publishableState).length > 0) {
508
+ publishableState = withoutLargestState(publishableState);
509
+ parsed = parsePublishablePayload(projected, publishableState);
510
+ }
511
+ if (!parsed) throw new Error("This layout cannot be published safely.");
512
+ return parsed;
513
+ }
514
+
515
+ function parsePublishablePayload(
516
+ layout: LayoutConfig,
517
+ paneState: Record<string, PaneRuntimeState>,
518
+ ): LayoutMarketplacePayload | null {
519
+ return parseMarketplaceLayoutPayload({
497
520
  schemaVersion: 2,
498
521
  sourceConfigVersion: CURRENT_CONFIG_VERSION,
499
- layout: projected,
500
- paneState: projectedState,
522
+ layout,
523
+ paneState,
501
524
  });
502
- if (!parsed) throw new Error("This layout cannot be published safely.");
503
- return parsed;
525
+ }
526
+
527
+ function withoutCacheFields(state: PaneRuntimeState): PaneRuntimeState {
528
+ return Object.fromEntries(
529
+ Object.entries(state).filter(([, value]) => encodedSize(value) <= MAX_PANE_STATE_FIELD_BYTES),
530
+ ) as PaneRuntimeState;
531
+ }
532
+
533
+ function withoutLargestState(
534
+ paneState: Record<string, PaneRuntimeState>,
535
+ ): Record<string, PaneRuntimeState> {
536
+ const largest = Object.entries(paneState)
537
+ .reduce((worst, entry) => (encodedSize(entry[1]) > encodedSize(worst[1]) ? entry : worst));
538
+ return Object.fromEntries(Object.entries(paneState).filter(([id]) => id !== largest[0]));
504
539
  }
505
540
 
506
541
  export function publishableMarketplaceLayout(
@@ -55,7 +55,8 @@ import {
55
55
  loadQuoteBatchEntries,
56
56
  loadQuoteEntry,
57
57
  QuoteSubscriptionManager,
58
- type QuoteSubscriptionPriority,
58
+ type QuoteSubscriptionHandle,
59
+ type QuoteSubscriptionRequest,
59
60
  } from "./quotes";
60
61
 
61
62
  export class MarketDataCoordinator {
@@ -361,7 +362,7 @@ export class MarketDataCoordinator {
361
362
  });
362
363
  }
363
364
 
364
- subscribeQuotes(targets: Array<{ instrument: InstrumentRef; priority?: QuoteSubscriptionPriority }>): () => void {
365
+ subscribeQuotes(targets: QuoteSubscriptionRequest[]): QuoteSubscriptionHandle {
365
366
  return this.quoteSubscriptionManager.subscribe(targets);
366
367
  }
367
368