gloomberb 0.11.3 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (574) hide show
  1. package/README.md +8 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -2
  4. package/src/api-client/auth.ts +12 -1
  5. package/src/api-client/data.ts +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +139 -3
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/research-activity.ts +61 -0
  11. package/src/api-client/types.ts +197 -0
  12. package/src/app/pane-runtime/index.tsx +15 -12
  13. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  14. package/src/app/pane-runtime/layout-placement.ts +13 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  16. package/src/app.tsx +60 -18
  17. package/src/architecture/browser-entry-graph.test.ts +96 -0
  18. package/src/brokers/profile-form.test.ts +25 -27
  19. package/src/brokers/test-broker.ts +68 -0
  20. package/src/cli/commands/plugins.ts +43 -14
  21. package/src/cli/desktop-pane-shot.ts +362 -24
  22. package/src/cli/index.ts +1 -1
  23. package/src/cli/pane-functions/capabilities.ts +126 -43
  24. package/src/cli/pane-functions/catalog.ts +16 -4
  25. package/src/cli/pane-functions/discovery.ts +1 -0
  26. package/src/cli/pane-functions/dom.test.ts +41 -0
  27. package/src/cli/pane-functions/dom.ts +199 -0
  28. package/src/cli/pane-functions/headless.test.ts +247 -0
  29. package/src/cli/pane-functions/headless.ts +423 -0
  30. package/src/cli/pane-functions/index.test.ts +20 -0
  31. package/src/cli/pane-functions/index.ts +10 -6
  32. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  33. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  34. package/src/cli/pane-functions/options.ts +27 -2
  35. package/src/cli/pane-functions/report.test.ts +29 -1
  36. package/src/cli/pane-functions/report.ts +69 -4
  37. package/src/cli/pane-functions/resolver.ts +26 -8
  38. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  39. package/src/cli/pane-functions/screenshot.ts +428 -42
  40. package/src/cli/restore-plugins.ts +41 -0
  41. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  42. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  43. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  44. package/src/components/chart/composite/composite-chart.test.tsx +136 -21
  45. package/src/components/chart/composite/composite-chart.tsx +378 -215
  46. package/src/components/chart/composite/format.ts +8 -2
  47. package/src/components/chart/composite/interactions.test.ts +68 -102
  48. package/src/components/chart/composite/interactions.ts +214 -264
  49. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  50. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  51. package/src/components/chart/composite/rasterizer.ts +33 -0
  52. package/src/components/chart/composite/renderers.test.ts +51 -4
  53. package/src/components/chart/composite/scene.test.ts +128 -2
  54. package/src/components/chart/composite/scene.ts +149 -19
  55. package/src/components/chart/composite/text-renderer.ts +16 -2
  56. package/src/components/chart/composite/time-scale.ts +163 -62
  57. package/src/components/chart/composite/types.ts +51 -1
  58. package/src/components/chart/core/data.ts +9 -13
  59. package/src/components/chart/core/date-window.ts +7 -1
  60. package/src/components/chart/core/pointer.ts +3 -0
  61. package/src/components/chart/core/types.ts +0 -45
  62. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  63. package/src/components/chart/native/kitty/index.test.ts +0 -188
  64. package/src/components/chart/native/raster/crosshair.ts +6 -5
  65. package/src/components/chart/native/raster/primitives.ts +0 -23
  66. package/src/components/chart/native/renderer-selection.ts +81 -31
  67. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  68. package/src/components/chart/static/chart-surface.tsx +150 -0
  69. package/src/components/chart/static/index.ts +8 -0
  70. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  71. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  72. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  73. package/src/components/chart/static/series.ts +49 -0
  74. package/src/components/command-bar/action-runtime.ts +1 -3
  75. package/src/components/command-bar/assist/model.test.ts +17 -2
  76. package/src/components/command-bar/assist/model.ts +40 -9
  77. package/src/components/command-bar/commands/direct/index.ts +1 -1
  78. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  79. package/src/components/command-bar/commands/registry.ts +0 -11
  80. package/src/components/command-bar/helpers.test.ts +2 -2
  81. package/src/components/command-bar/helpers.ts +2 -6
  82. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  83. package/src/components/command-bar/list/badge.test.ts +54 -0
  84. package/src/components/command-bar/list/badge.ts +62 -0
  85. package/src/components/command-bar/list/model.test.ts +103 -0
  86. package/src/components/command-bar/list/model.ts +61 -4
  87. package/src/components/command-bar/list/view.test.tsx +113 -0
  88. package/src/components/command-bar/list/view.tsx +147 -131
  89. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  90. package/src/components/command-bar/panel/body.tsx +51 -65
  91. package/src/components/command-bar/panel/index.tsx +48 -13
  92. package/src/components/command-bar/panel/layout.test.ts +139 -0
  93. package/src/components/command-bar/panel/layout.ts +65 -28
  94. package/src/components/command-bar/panel/native-surface.ts +24 -0
  95. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  96. package/src/components/command-bar/panel/runtime.ts +6 -3
  97. package/src/components/command-bar/panel/state.ts +41 -7
  98. package/src/components/command-bar/panel/types.ts +7 -1
  99. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  100. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  101. package/src/components/command-bar/routes/root/results.ts +30 -28
  102. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  103. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  104. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  105. package/src/components/command-bar/routes/root/selection.ts +1 -12
  106. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  107. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  108. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  109. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  110. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  111. package/src/components/command-bar/routing/actions.ts +1 -81
  112. package/src/components/command-bar/routing/launch-request.ts +1 -1
  113. package/src/components/command-bar/routing/list-state.ts +6 -21
  114. package/src/components/command-bar/selection-runtime.ts +1 -1
  115. package/src/components/command-bar/surface/assist.test.tsx +34 -19
  116. package/src/components/command-bar/surface/environment.ts +8 -1
  117. package/src/components/command-bar/surface/index.test.tsx +84 -68
  118. package/src/components/command-bar/surface/index.tsx +33 -36
  119. package/src/components/command-bar/surface/test-harness.tsx +38 -7
  120. package/src/components/command-bar/theme-picker.tsx +43 -15
  121. package/src/components/command-bar/view-model.test.ts +53 -8
  122. package/src/components/command-bar/view-model.ts +43 -14
  123. package/src/components/command-bar/workflow/tickers.ts +1 -0
  124. package/src/components/command-bar/workflow/types.ts +1 -1
  125. package/src/components/data-table/view.test.tsx +45 -6
  126. package/src/components/data-table/view.tsx +55 -9
  127. package/src/components/index.ts +10 -2
  128. package/src/components/input-search-bar.test.tsx +36 -2
  129. package/src/components/input-search-bar.tsx +18 -1
  130. package/src/components/layout/header.test.tsx +57 -10
  131. package/src/components/layout/header.tsx +292 -149
  132. package/src/components/layout/market-summary.test.ts +81 -0
  133. package/src/components/layout/market-summary.ts +109 -0
  134. package/src/components/layout/shell/chrome.ts +96 -1
  135. package/src/components/layout/shell/index.tsx +11 -3
  136. package/src/components/layout/shell/menu.ts +9 -0
  137. package/src/components/layout/shell/pane/actions.ts +29 -0
  138. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  139. package/src/components/layout/shell/shortcuts.ts +3 -0
  140. package/src/components/layout/status-bar.test.tsx +9 -8
  141. package/src/components/layout/status-bar.tsx +85 -54
  142. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  143. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  144. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  145. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  146. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  147. package/src/components/price-sparkline/index.ts +61 -34
  148. package/src/components/price-sparkline/view.tsx +1 -1
  149. package/src/components/sign-in-gate.test.tsx +128 -0
  150. package/src/components/sign-in-gate.tsx +149 -0
  151. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  152. package/src/components/ticker/badge/list.tsx +17 -3
  153. package/src/components/ticker/list-table-view.tsx +6 -3
  154. package/src/components/ui/data-table/index.tsx +3 -2
  155. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  156. package/src/components/ui/frame.tsx +28 -0
  157. package/src/components/ui/index.ts +3 -0
  158. package/src/components/ui/native-select.tsx +20 -10
  159. package/src/components/ui/select-button.tsx +138 -0
  160. package/src/components/ui/status.tsx +2 -2
  161. package/src/components/ui/table-layout.ts +1 -1
  162. package/src/components/use-double-click-activation.ts +7 -3
  163. package/src/core/app-services.ts +3 -3
  164. package/src/data/config/layout.ts +12 -6
  165. package/src/data/config/store/index.test.ts +120 -0
  166. package/src/data/config/store/migrations.ts +41 -0
  167. package/src/data/config/store/node.ts +7 -1
  168. package/src/data/config/store/normalize.ts +9 -3
  169. package/src/i18n/es.ts +6 -2
  170. package/src/i18n/ja.ts +6 -2
  171. package/src/i18n/ko.ts +6 -2
  172. package/src/i18n/zh-cn.ts +6 -2
  173. package/src/i18n/zh-tw.ts +6 -2
  174. package/src/layout-marketplace/pane-imagery.ts +1 -0
  175. package/src/layout-marketplace/payload.test.ts +17 -0
  176. package/src/layout-marketplace/payload.ts +40 -5
  177. package/src/market-data/coordinator/index.ts +3 -2
  178. package/src/market-data/coordinator/quotes.ts +173 -66
  179. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  180. package/src/pane-settings.ts +1 -1
  181. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  182. package/src/plugins/builtin/analytics/view.tsx +1 -1
  183. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  184. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  185. package/src/plugins/builtin/cds/client.test.ts +20 -0
  186. package/src/plugins/builtin/cds/client.ts +13 -3
  187. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  188. package/src/plugins/builtin/cds/headless.ts +113 -0
  189. package/src/plugins/builtin/cds/index.tsx +4 -0
  190. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  191. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  192. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  193. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  194. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  195. package/src/plugins/builtin/cloud/askg/protocol.test.ts +189 -0
  196. package/src/plugins/builtin/cloud/askg/protocol.ts +274 -0
  197. package/src/plugins/builtin/cloud/auth-dialog.tsx +19 -227
  198. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  199. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  200. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  201. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  202. package/src/plugins/builtin/composite-plugins.ts +14 -1
  203. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  204. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  205. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  206. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  207. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  208. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  209. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  210. package/src/plugins/builtin/credit-conditions/index.tsx +6 -21
  211. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  212. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  213. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  214. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  215. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  216. package/src/plugins/builtin/earnings-calls/pane.tsx +760 -0
  217. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  218. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  219. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +337 -0
  220. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  221. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  222. package/src/plugins/builtin/econ/index.tsx +2 -1
  223. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  224. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  225. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  226. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  227. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  228. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  229. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  230. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  231. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  232. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  233. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  234. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  235. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  236. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  237. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  238. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  239. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  240. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  241. package/src/plugins/builtin/help/index.tsx +4 -0
  242. package/src/plugins/builtin/holders/index.tsx +1 -0
  243. package/src/plugins/builtin/insider/index.tsx +1 -0
  244. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  245. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  246. package/src/plugins/builtin/market-movers/client.ts +76 -0
  247. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  248. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  249. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  250. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  251. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  252. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  253. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  254. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  255. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  256. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  257. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  258. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  259. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  260. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  261. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  262. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  263. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  264. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  265. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  266. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  267. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  268. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  269. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  270. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  271. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  272. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  273. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  274. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  275. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  276. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  277. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  278. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  279. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  280. package/src/plugins/builtin/options/index.tsx +1 -0
  281. package/src/plugins/builtin/options/view.test.tsx +29 -1
  282. package/src/plugins/builtin/options/view.tsx +1 -22
  283. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  284. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  285. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  286. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  287. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  288. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  289. package/src/plugins/builtin/plugin-module.ts +9 -1
  290. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  291. package/src/plugins/builtin/research/event-model.ts +245 -0
  292. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  293. package/src/plugins/builtin/research/events-headless.ts +87 -0
  294. package/src/plugins/builtin/research/index.tsx +8 -0
  295. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  296. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  297. package/src/plugins/builtin/research-search/data.ts +109 -0
  298. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  299. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  300. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  301. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  302. package/src/plugins/builtin/research-search/index.tsx +85 -0
  303. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  304. package/src/plugins/builtin/research-search/model.ts +291 -0
  305. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  306. package/src/plugins/builtin/research-search/pane.tsx +682 -0
  307. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  308. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  309. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  310. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  311. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  312. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  313. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  314. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  315. package/src/plugins/builtin/scanner/headless.ts +125 -0
  316. package/src/plugins/builtin/scanner/index.ts +4 -0
  317. package/src/plugins/builtin/sec/index.tsx +1 -0
  318. package/src/plugins/builtin/sectors/client.ts +54 -0
  319. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  320. package/src/plugins/builtin/sectors/headless.ts +80 -0
  321. package/src/plugins/builtin/sectors/index.tsx +6 -51
  322. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  323. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  324. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  325. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  326. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  327. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  328. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  329. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  330. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  331. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  332. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  333. package/src/plugins/builtin/volatility/client.ts +16 -3
  334. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  335. package/src/plugins/builtin/volatility/headless.ts +75 -0
  336. package/src/plugins/builtin/volatility/index.tsx +5 -1
  337. package/src/plugins/builtin/world-indices/client.ts +43 -0
  338. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  339. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  340. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  341. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  342. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  343. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  344. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  345. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  346. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  347. package/src/plugins/builtin/yield-curve/index.tsx +7 -9
  348. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  349. package/src/plugins/bundle.test.ts +166 -0
  350. package/src/plugins/bundle.ts +141 -0
  351. package/src/plugins/catalog-backend.test.ts +26 -0
  352. package/src/plugins/catalog-backend.ts +9 -2
  353. package/src/plugins/catalog-browser.test.ts +1 -13
  354. package/src/plugins/catalog-browser.ts +14 -0
  355. package/src/plugins/catalog-manifest.test.ts +58 -0
  356. package/src/plugins/catalog-manifest.ts +99 -0
  357. package/src/plugins/catalog-ui.ts +20 -10
  358. package/src/plugins/catalog.ts +9 -2
  359. package/src/plugins/current-target.ts +28 -0
  360. package/src/plugins/host-contract.ts +46 -0
  361. package/src/plugins/host-link.ts +158 -0
  362. package/src/plugins/host-modules.ts +73 -0
  363. package/src/plugins/loader.ts +90 -23
  364. package/src/plugins/ownership.ts +1 -0
  365. package/src/plugins/prediction-markets/index.tsx +1 -0
  366. package/src/plugins/registry/context.ts +3 -0
  367. package/src/plugins/registry/contributions.ts +27 -0
  368. package/src/plugins/registry/index.test.ts +25 -0
  369. package/src/plugins/registry/index.ts +8 -0
  370. package/src/plugins/registry/pane-settings.ts +6 -13
  371. package/src/plugins/seed.test.ts +83 -0
  372. package/src/plugins/seed.ts +83 -0
  373. package/src/public/broker.ts +37 -0
  374. package/src/public/market-data.ts +9 -0
  375. package/src/public/pane-hooks.ts +57 -0
  376. package/src/public/public-api.test.ts +285 -0
  377. package/src/public/react.ts +62 -0
  378. package/src/public/test-support.ts +23 -0
  379. package/src/public/time-series.ts +8 -0
  380. package/src/public/utils.ts +77 -0
  381. package/src/remote/layout-helpers.ts +6 -8
  382. package/src/remote/resources.ts +2 -0
  383. package/src/remote/schema.test.ts +65 -0
  384. package/src/remote/schema.ts +323 -39
  385. package/src/remote/semantic-tree.test.tsx +105 -0
  386. package/src/remote/semantic-tree.tsx +53 -35
  387. package/src/remote/types.ts +49 -2
  388. package/src/renderers/browser/app-services.ts +3 -0
  389. package/src/renderers/browser/config-host.ts +24 -8
  390. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  391. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  392. package/src/renderers/browser/main.tsx +8 -0
  393. package/src/renderers/browser/research-entry.test.ts +8 -0
  394. package/src/renderers/browser/research-entry.ts +7 -0
  395. package/src/renderers/cloudflare/worker.test.ts +19 -0
  396. package/src/renderers/cloudflare/worker.ts +1 -1
  397. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  398. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  399. package/src/renderers/electrobun/bun/external-plugins.ts +156 -0
  400. package/src/renderers/electrobun/bun/index.ts +5 -2
  401. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  402. package/src/renderers/electrobun/view/build-assets.ts +0 -3
  403. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +114 -11
  404. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  405. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  406. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  407. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  408. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  409. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  410. package/src/renderers/electrobun/view/main.tsx +27 -2
  411. package/src/renderers/electrobun/view/styles.css +5 -0
  412. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  413. package/src/renderers/opentui/chart-surface.tsx +5 -1
  414. package/src/renderers/opentui/host.tsx +16 -2
  415. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  416. package/src/renderers/opentui/interaction-performance.ts +188 -0
  417. package/src/renderers/opentui/start.tsx +26 -1
  418. package/src/renderers/opentui/test-utils.tsx +13 -0
  419. package/src/renderers/opentui/toast-host.tsx +26 -2
  420. package/src/renderers/opentui/ui-host.tsx +1 -0
  421. package/src/sources/provider-router/search.test.ts +191 -0
  422. package/src/sources/provider-router/search.ts +92 -39
  423. package/src/state/app/context/index.tsx +2 -0
  424. package/src/state/app/context/selector.test.tsx +33 -1
  425. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  426. package/src/state/hooks/quote-streaming.ts +106 -30
  427. package/src/state/pane-table-export-registry.ts +19 -0
  428. package/src/sync/core-contributors.test.ts +111 -0
  429. package/src/sync/core-contributors.ts +155 -1
  430. package/src/theme/color-utils.ts +1 -1
  431. package/src/theme/colors.ts +50 -0
  432. package/src/theme/themes.test.ts +15 -2
  433. package/src/theme/themes.ts +19 -1
  434. package/src/tickers/search/index.test.ts +26 -0
  435. package/src/tickers/search/index.ts +42 -17
  436. package/src/tickers/search/ranking.ts +14 -0
  437. package/src/time-series/chart-data.test.ts +216 -0
  438. package/src/time-series/resolution.test.ts +25 -26
  439. package/src/time-series/resolution.ts +41 -10
  440. package/src/time-series/resolve.test.ts +118 -1
  441. package/src/time-series/resolve.ts +21 -16
  442. package/src/time-series/types.ts +3 -1
  443. package/src/time-series/use-chart-resolution.ts +5 -0
  444. package/src/types/config.ts +125 -3
  445. package/src/types/data-provider.ts +11 -0
  446. package/src/types/headless.ts +161 -0
  447. package/src/types/plugin.ts +102 -0
  448. package/src/ui/host.tsx +12 -1
  449. package/src/ui/index.tsx +1 -0
  450. package/src/ui/toast.tsx +8 -4
  451. package/src/utils/format.ts +35 -0
  452. package/src/utils/fuzzy-search.test.ts +94 -0
  453. package/src/utils/fuzzy-search.ts +81 -22
  454. package/src/utils/shortcut-labels.ts +14 -0
  455. package/src/components/chart/core/axis-format.ts +0 -157
  456. package/src/components/chart/core/index.test.ts +0 -1075
  457. package/src/components/chart/core/projection.ts +0 -357
  458. package/src/components/chart/core/render-utils.ts +0 -4
  459. package/src/components/chart/core/renderer.ts +0 -244
  460. package/src/components/chart/core/scene.ts +0 -180
  461. package/src/components/chart/core/time-axis.ts +0 -330
  462. package/src/components/chart/cursor-motion.ts +0 -47
  463. package/src/components/chart/indicators/types.ts +0 -26
  464. package/src/components/chart/market-session.ts +0 -176
  465. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  466. package/src/components/chart/multi-line/renderer.ts +0 -161
  467. package/src/components/chart/multi-line/scene.ts +0 -188
  468. package/src/components/chart/native/raster/layout.ts +0 -129
  469. package/src/components/chart/native/raster/price-chart.ts +0 -244
  470. package/src/components/chart/rendering/chart-draw.ts +0 -270
  471. package/src/components/chart/rendering/geometry.ts +0 -95
  472. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  473. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  474. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  475. package/src/components/chart/static/chart/surface.tsx +0 -358
  476. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  477. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  478. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  479. package/src/plugins/broker-sync/native-loader.ts +0 -10
  480. package/src/plugins/broker-sync/normalize.ts +0 -308
  481. package/src/plugins/broker-sync/public.ts +0 -122
  482. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  483. package/src/plugins/broker-sync/robinhood.ts +0 -114
  484. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  485. package/src/plugins/broker-sync/simplefin.ts +0 -70
  486. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  487. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  488. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  489. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  490. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  491. package/src/plugins/builtin/substack/api/store.ts +0 -186
  492. package/src/plugins/builtin/substack/api/types.ts +0 -42
  493. package/src/plugins/builtin/substack/api.test.ts +0 -298
  494. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  495. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  496. package/src/plugins/builtin/substack/content.ts +0 -478
  497. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  498. package/src/plugins/builtin/substack/index.tsx +0 -49
  499. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  500. package/src/plugins/builtin/substack/model.test.ts +0 -155
  501. package/src/plugins/builtin/substack/normalize.ts +0 -343
  502. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  503. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  504. package/src/plugins/builtin/substack/pane.tsx +0 -547
  505. package/src/plugins/builtin/substack/read-state.ts +0 -30
  506. package/src/plugins/builtin/substack/table.ts +0 -111
  507. package/src/plugins/builtin/substack/types.ts +0 -71
  508. package/src/plugins/builtin/substack/utils.ts +0 -131
  509. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  510. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  511. package/src/plugins/ibkr/account-cache.ts +0 -24
  512. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  513. package/src/plugins/ibkr/config.test.ts +0 -79
  514. package/src/plugins/ibkr/config.ts +0 -250
  515. package/src/plugins/ibkr/dialogs.tsx +0 -40
  516. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  517. package/src/plugins/ibkr/flex/index.ts +0 -367
  518. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  519. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  520. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  521. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  522. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  523. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  524. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  525. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  526. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  527. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  528. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  529. package/src/plugins/ibkr/gateway/history.ts +0 -120
  530. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  531. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  532. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  533. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  534. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  535. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  536. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  537. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  538. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  539. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  540. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  541. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  542. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  543. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  544. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  545. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  546. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  547. package/src/plugins/ibkr/gateway/types.ts +0 -27
  548. package/src/plugins/ibkr/index.test.tsx +0 -162
  549. package/src/plugins/ibkr/index.tsx +0 -159
  550. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  551. package/src/plugins/ibkr/instance-selection.ts +0 -36
  552. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  553. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  554. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  555. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  556. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  557. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  558. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  559. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  560. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  561. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  562. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  563. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  564. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  565. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  566. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  567. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  568. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  569. package/src/plugins/ibkr/trade/utils.ts +0 -144
  570. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  571. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  572. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  573. package/src/plugins/ibkr/trading/state.ts +0 -335
  574. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -10,8 +10,10 @@ import {
10
10
  cloneLayout,
11
11
  clonePaneSettings,
12
12
  createPaneInstanceId,
13
+ getPlacedPaneInstanceIds,
13
14
  normalizePaneLayout,
14
15
  normalizePaneId,
16
+ removeUnreachablePaneInstances,
15
17
  } from "../../types/config";
16
18
  import {
17
19
  migrateChartPaneSettings,
@@ -155,11 +157,14 @@ function sanitizePaneInstances(
155
157
  placementMemory: sanitizePlacementMemory(entry.placementMemory),
156
158
  };
157
159
  });
158
- return instances.length > 0 ? instances : cloneLayout(fallback).instances;
160
+ return instances;
159
161
  }
160
162
 
161
- function getDefaultFollowSourceInstanceId(instances: PaneInstanceConfig[]): string | null {
162
- return instances.find((instance) => instance.paneId === "portfolio-list")?.instanceId ?? null;
163
+ function getDefaultFollowSourceInstanceId(layout: LayoutConfig): string | null {
164
+ const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
165
+ return layout.instances.find((instance) => (
166
+ instance.paneId === "portfolio-list" && placedPaneIds.has(instance.instanceId)
167
+ ))?.instanceId ?? null;
163
168
  }
164
169
 
165
170
  function sanitizeFloatingEntries(value: unknown, validInstanceIds: Set<string>): LayoutConfig["floating"] {
@@ -219,7 +224,7 @@ export function sanitizeLayout(
219
224
  if (!Array.isArray((value as LayoutConfig & { instances?: unknown }).instances)) {
220
225
  const layout = cloneLayout(fallback);
221
226
  return normalizePaneLayout(layout, {
222
- defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
227
+ defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
223
228
  resolveOrphanSymbol: () => null,
224
229
  });
225
230
  }
@@ -241,8 +246,9 @@ export function sanitizeLayout(
241
246
  detached,
242
247
  };
243
248
 
244
- return normalizePaneLayout(layout, {
245
- defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout.instances),
249
+ const normalized = normalizePaneLayout(layout, {
250
+ defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
246
251
  resolveOrphanSymbol: () => null,
247
252
  });
253
+ return removeUnreachablePaneInstances(normalized);
248
254
  }
@@ -11,6 +11,7 @@ import {
11
11
  findPaneInstance,
12
12
  } from "../../../types/config";
13
13
  import { getDockedPaneIds } from "../../../plugins/pane-manager";
14
+ import { EXTRACTED_PLUGINS, seedExtractedPlugins } from "../../../plugins/seed";
14
15
 
15
16
  const tempDirs: string[] = [];
16
17
 
@@ -48,7 +49,73 @@ async function writeConfigJson(dataDir: string, config: Record<string, unknown>)
48
49
  await writeFile(join(dataDir, "config.json"), JSON.stringify(config), "utf-8");
49
50
  }
50
51
 
52
+ test("fresh installs skip plugin restoration across config reloads", async () => {
53
+ const dataDir = await createTempConfigDir();
54
+ const fresh = await loadConfig(dataDir);
55
+ await saveConfig(fresh);
56
+ const reloaded = await loadConfig(dataDir);
57
+ const installed: string[] = [];
58
+ await seedExtractedPlugins(reloaded, async (ref) => { installed.push(ref); }, dataDir);
59
+ expect(installed).toEqual([]);
60
+ expect(reloaded.seededPlugins).toEqual(EXTRACTED_PLUGINS.map((plugin) => plugin.id));
61
+ });
62
+
63
+ test("existing users retain pending and completed plugin migrations", async () => {
64
+ const dataDir = await createTempConfigDir();
65
+ await writeConfigJson(dataDir, createSavedConfig());
66
+ expect((await loadConfig(dataDir)).seededPlugins).toEqual([]);
67
+ await writeConfigJson(dataDir, createSavedConfig({ seededPlugins: ["substack", "substack", 42] }));
68
+ const loaded = await loadConfig(dataDir);
69
+ expect(loaded.seededPlugins).toEqual(["substack"]);
70
+ await saveConfig(loaded);
71
+ expect((await loadConfig(dataDir)).seededPlugins).toEqual(["substack"]);
72
+ });
73
+
51
74
  describe("sanitizeLayout", () => {
75
+ test("preserves an intentionally blank layout", () => {
76
+ expect(sanitizeLayout({
77
+ dockRoot: null,
78
+ instances: [],
79
+ floating: [],
80
+ detached: [],
81
+ }, DEFAULT_LAYOUT)).toEqual({
82
+ dockRoot: null,
83
+ instances: [],
84
+ floating: [],
85
+ detached: [],
86
+ });
87
+ });
88
+
89
+ test("prunes abandoned panes while retaining a hidden follow source", () => {
90
+ const layout = sanitizeLayout({
91
+ dockRoot: { kind: "pane", instanceId: "ticker-research:visible" },
92
+ instances: [
93
+ {
94
+ instanceId: "portfolio-list:source",
95
+ paneId: "portfolio-list",
96
+ binding: { kind: "none" },
97
+ },
98
+ {
99
+ instanceId: "ticker-research:visible",
100
+ paneId: "ticker-research",
101
+ binding: { kind: "follow", sourceInstanceId: "portfolio-list:source" },
102
+ },
103
+ {
104
+ instanceId: "ticker-research:closed",
105
+ paneId: "ticker-research",
106
+ binding: { kind: "fixed", symbol: "NVDA" },
107
+ },
108
+ ],
109
+ floating: [],
110
+ detached: [],
111
+ }, DEFAULT_LAYOUT);
112
+
113
+ expect(layout.instances.map((instance) => instance.instanceId)).toEqual([
114
+ "portfolio-list:source",
115
+ "ticker-research:visible",
116
+ ]);
117
+ });
118
+
52
119
  test("ships a focused Home layout with portfolio, chat, and following research", () => {
53
120
  expect(DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId)).toEqual([
54
121
  "portfolio-list:main",
@@ -318,6 +385,59 @@ describe("sanitizeLayout", () => {
318
385
  });
319
386
 
320
387
  describe("loadConfig", () => {
388
+ test("migrates unreachable pane instances and their saved state", async () => {
389
+ const dataDir = await createTempConfigDir();
390
+ const hiddenPaneId = "ticker-research:closed";
391
+ const legacyLayout = {
392
+ ...DEFAULT_LAYOUT,
393
+ instances: [
394
+ ...DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId === "chat:main"
395
+ ? {
396
+ ...instance,
397
+ placementMemory: {
398
+ docked: {
399
+ anchorInstanceId: hiddenPaneId,
400
+ position: "right" as const,
401
+ },
402
+ },
403
+ }
404
+ : instance),
405
+ {
406
+ instanceId: hiddenPaneId,
407
+ paneId: "ticker-research",
408
+ binding: { kind: "fixed" as const, symbol: "NVDA" },
409
+ placementMemory: { floating: { x: 12, y: 4, width: 90, height: 30 } },
410
+ },
411
+ ],
412
+ };
413
+ await writeConfigJson(dataDir, createSavedConfig({
414
+ configVersion: 21,
415
+ layout: legacyLayout,
416
+ layouts: [{
417
+ name: "Default",
418
+ layout: legacyLayout,
419
+ paneState: {
420
+ "chat:main": { draft: "keep" },
421
+ [hiddenPaneId]: { activeTabId: "overview" },
422
+ },
423
+ focusedPaneId: hiddenPaneId,
424
+ }],
425
+ }));
426
+
427
+ const config = await loadConfig(dataDir);
428
+
429
+ expect(config.configVersion).toBe(CURRENT_CONFIG_VERSION);
430
+ expect(config.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
431
+ expect(config.layouts[0]?.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
432
+ expect(config.layouts[0]?.paneState).toEqual({ "chat:main": { draft: "keep" } });
433
+ expect(config.layouts[0]?.focusedPaneId).toBeNull();
434
+ expect(findPaneInstance(config.layout, "chat:main")?.placementMemory?.docked).toEqual({
435
+ anchorInstanceId: undefined,
436
+ path: undefined,
437
+ position: "right",
438
+ });
439
+ });
440
+
321
441
  test("folds saved graph plugin state into composer specs and removes only chart-owned state", async () => {
322
442
  const dataDir = await createTempConfigDir();
323
443
  const legacyLayout = {
@@ -4,6 +4,7 @@ import {
4
4
  CURRENT_CONFIG_VERSION,
5
5
  DEFAULT_COLUMNS,
6
6
  DEFAULT_PORTFOLIO_COLUMN_IDS,
7
+ getPlacedPaneInstanceIds,
7
8
  type LayoutConfig,
8
9
  } from "../../../types/config";
9
10
  import {
@@ -24,6 +25,7 @@ const CLOUD_MACRO_SPLIT_CONFIG_VERSION = 15;
24
25
  const PORTFOLIO_DEFAULT_COLUMNS_CONFIG_VERSION = 17;
25
26
  const BUILTIN_OWNERSHIP_AND_CHART_CONFIG_VERSION = 20;
26
27
  const ONBOARDING_BACKFILL_CONFIG_VERSION = 21;
28
+ const UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION = 22;
27
29
 
28
30
  const LEGACY_MAIN_PORTFOLIO_COLUMN_IDS = DEFAULT_COLUMNS.map((column) => column.id);
29
31
  const PRE_SPARKLINE_PORTFOLIO_COLUMN_IDS = [
@@ -85,6 +87,11 @@ const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [
85
87
  toVersion: ONBOARDING_BACKFILL_CONFIG_VERSION,
86
88
  migrate: migrateOnboardingComplete,
87
89
  },
90
+ {
91
+ name: "prune-unreachable-pane-instances",
92
+ toVersion: UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION,
93
+ migrate: migrateUnreachablePaneInstances,
94
+ },
88
95
  ];
89
96
 
90
97
  export function migrateSavedConfig(saved: Record<string, unknown>, dataDir: string): ConfigMigrationResult {
@@ -139,6 +146,40 @@ function migrateOnboardingComplete(saved: Record<string, unknown>): Record<strin
139
146
  };
140
147
  }
141
148
 
149
+ function migrateUnreachablePaneInstances(
150
+ saved: Record<string, unknown>,
151
+ dataDir: string,
152
+ ): Record<string, unknown> {
153
+ const defaults = createDefaultConfig(dataDir);
154
+ const layout = sanitizeLayout(saved.layout, defaults.layout);
155
+ const layouts = Array.isArray(saved.layouts)
156
+ ? saved.layouts.map((entry) => {
157
+ if (!isPlainRecord(entry) || typeof entry.name !== "string") return entry;
158
+ const entryLayout = sanitizeLayout(entry.layout, layout);
159
+ const instanceIds = new Set(entryLayout.instances.map((instance) => instance.instanceId));
160
+ const placedIds = new Set(getPlacedPaneInstanceIds(entryLayout));
161
+ const paneState = isPlainRecord(entry.paneState)
162
+ ? Object.fromEntries(Object.entries(entry.paneState).filter(([instanceId]) => instanceIds.has(instanceId)))
163
+ : entry.paneState;
164
+ const focusedPaneId = typeof entry.focusedPaneId === "string" && !placedIds.has(entry.focusedPaneId)
165
+ ? null
166
+ : entry.focusedPaneId;
167
+ return {
168
+ ...entry,
169
+ layout: entryLayout,
170
+ paneState,
171
+ focusedPaneId,
172
+ };
173
+ })
174
+ : saved.layouts;
175
+
176
+ return {
177
+ ...saved,
178
+ layout,
179
+ layouts,
180
+ };
181
+ }
182
+
142
183
  function migrateCloudDefault(saved: Record<string, unknown>): Record<string, unknown> {
143
184
  return {
144
185
  ...saved,
@@ -4,6 +4,7 @@ import { dirname, join } from "path";
4
4
  import type { AppConfig } from "../../../types/config";
5
5
  import { createDefaultConfig } from "../../../types/config";
6
6
  import { debugLog } from "../../../utils/debug-log";
7
+ import { EXTRACTED_PLUGINS } from "../../../plugins/seed";
7
8
  import {
8
9
  normalizeConfigForSave,
9
10
  normalizeLoadedConfig,
@@ -54,7 +55,12 @@ async function loadConfigState(dataDir: string): Promise<{ config: AppConfig; ne
54
55
  const saved = JSON.parse(raw) as Record<string, unknown>;
55
56
  return normalizeLoadedConfig(saved, dataDir);
56
57
  } catch {
57
- return { config: createDefaultConfig(dataDir), needsSave: true };
58
+ // Fresh installs have no former built-in plugins to restore. Record the
59
+ // current baseline so onboarding never waits for plugin network installs.
60
+ return {
61
+ config: { ...createDefaultConfig(dataDir), seededPlugins: EXTRACTED_PLUGINS.map((plugin) => plugin.id) },
62
+ needsSave: true,
63
+ };
58
64
  }
59
65
  }
60
66
 
@@ -10,6 +10,7 @@ import {
10
10
  cloneLayout,
11
11
  createDefaultConfig,
12
12
  CURRENT_CONFIG_VERSION,
13
+ getPlacedPaneInstanceIds,
13
14
  } from "../../../types/config";
14
15
  import type { Portfolio, Watchlist } from "../../../types/ticker";
15
16
  import { isLanguagePreference } from "../../../i18n/languages";
@@ -51,6 +52,7 @@ export function normalizeLoadedConfig(saved: Record<string, unknown>, dataDir: s
51
52
  activeLayoutIndex,
52
53
  brokerInstances: sanitizeBrokerInstances(candidate.brokerInstances),
53
54
  disabledPlugins,
55
+ seededPlugins: sanitizeUniqueStringList(candidate.seededPlugins),
54
56
  disabledSources: sanitizeUniqueStringList(candidate.disabledSources ?? defaults.disabledSources),
55
57
  pluginConfig: sanitizePluginConfig(candidate.pluginConfig),
56
58
  theme: typeof candidate.theme === "string" ? candidate.theme : defaults.theme,
@@ -107,6 +109,7 @@ export function normalizeConfigForSave(config: AppConfig): AppConfig {
107
109
  activeLayoutIndex,
108
110
  brokerInstances: sanitizeBrokerInstances(config.brokerInstances),
109
111
  disabledPlugins: sanitizeUniqueStringList(config.disabledPlugins),
112
+ seededPlugins: sanitizeUniqueStringList(config.seededPlugins),
110
113
  disabledSources: sanitizeUniqueStringList(config.disabledSources),
111
114
  pluginConfig: sanitizePluginConfig(config.pluginConfig),
112
115
  chartPreferences: sanitizeChartPreferences(config.chartPreferences, defaults.chartPreferences),
@@ -129,6 +132,8 @@ const ONBOARDING_STAGES = new Set<OnboardingProgress["stage"]>([
129
132
  "welcome",
130
133
  "portfolio",
131
134
  "add-ticker",
135
+ "research",
136
+ "verify",
132
137
  "account",
133
138
  "upgrade",
134
139
  "ready",
@@ -305,15 +310,16 @@ function sanitizeSavedLayouts(
305
310
  )
306
311
  .map((entry) => {
307
312
  const layout = sanitizeLayout(entry.layout, fallbackLayout);
313
+ const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
308
314
  const paneState = sanitizeSavedPaneState((entry as { paneState?: unknown }).paneState, layout);
309
315
  return {
310
316
  id: typeof entry.id === "string" ? entry.id : undefined,
311
317
  name: entry.name,
312
318
  layout,
313
319
  paneState,
314
- focusedPaneId: typeof entry.focusedPaneId === "string" || entry.focusedPaneId === null
315
- ? entry.focusedPaneId
316
- : undefined,
320
+ focusedPaneId: typeof entry.focusedPaneId === "string"
321
+ ? placedPaneIds.has(entry.focusedPaneId) ? entry.focusedPaneId : null
322
+ : entry.focusedPaneId === null ? null : undefined,
317
323
  activePanel: entry.activePanel === "right" || entry.activePanel === "left"
318
324
  ? entry.activePanel
319
325
  : undefined,
package/src/i18n/es.ts CHANGED
@@ -572,6 +572,7 @@ export const es: Record<string, string> = {
572
572
  "Open the help window": "Abrir la ventana de ayuda",
573
573
  "Set up a portfolio": "Configura una cartera",
574
574
  "How would you like to get started?": "¿Cómo quieres empezar?",
575
+ "{shortcut} to get started.": "{shortcut} para empezar.",
575
576
  "Edits later from the command bar.": "Se edita luego desde la barra de comandos.",
576
577
  "Create Manual Portfolio": "Crear cartera manual",
577
578
  "Add tickers and positions by hand": "Agrega tickers y posiciones a mano",
@@ -760,7 +761,7 @@ export const es: Record<string, string> = {
760
761
  "Verify your email to send messages.": "Verifica tu correo para enviar mensajes.",
761
762
  "Verify your email to use Cloud tweets.": "Verifica tu correo para usar Cloud tweets.",
762
763
  "Resend Verification Email": "Reenviar correo de verificación",
763
- "Ctrl+P, then Resend Verification Email": "Ctrl+P y luego Reenviar correo de verificación",
764
+ "{shortcut}, then Resend Verification Email": "{shortcut} y luego Reenviar correo de verificación",
764
765
  "editing": "editando",
765
766
  "replying to": "respondiendo a",
766
767
  "Reply": "Responder",
@@ -998,7 +999,7 @@ export const es: Record<string, string> = {
998
999
  "No watchlist tickers": "Sin tickers en seguimiento",
999
1000
  "No tickers in {name}.": "No hay tickers en {name}.",
1000
1001
  "this collection": "esta colección",
1001
- "Press Ctrl+P to add one.": "Pulsa Ctrl+P para agregar uno.",
1002
+ "Press {shortcut} to add one.": "Pulsa {shortcut} para agregar uno.",
1002
1003
  "PRE-MKT": "PRE-MKT",
1003
1004
  "OPEN": "ABIERTO",
1004
1005
  "AFTER-HRS": "POST-MKT",
@@ -1092,6 +1093,9 @@ export const es: Record<string, string> = {
1092
1093
  "Approve from your phone, no typing": "Aprueba desde tu teléfono, sin escribir",
1093
1094
  "Scan with the Gloom app": "Escanea con la app Gloom",
1094
1095
  "Open the Gloom app on your phone and approve this workspace.": "Abre la app Gloom en tu teléfono y aprueba este espacio de trabajo.",
1096
+ "Approve the code from a phone that is already signed in.": "Aprueba el código desde un teléfono que ya tenga la sesión iniciada.",
1097
+ "Layouts, watchlists and portfolios sync to your account.": "Los diseños, las listas de seguimiento y las carteras se sincronizan con tu cuenta.",
1098
+ "Use email instead": "Usar el correo",
1095
1099
  "Scan with the Gloom app to sign in": "Escanea con la app Gloom para iniciar sesión",
1096
1100
  "Waiting for approval...": "Esperando aprobación...",
1097
1101
  "Approved as {email}": "Aprobado como {email}",
package/src/i18n/ja.ts CHANGED
@@ -573,6 +573,7 @@ export const ja: 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 ja: 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 ja: 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": "PRE-MKT",
1018
1019
  "OPEN": "取引中",
1019
1020
  "AFTER-HRS": "時間外",
@@ -1079,6 +1080,9 @@ export const ja: 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/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} 登入",
@@ -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