@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -61,8 +61,23 @@ import {
61
61
  updateBuildoutFooterInfo,
62
62
  } from "./status";
63
63
  import { useBuildoutDataRuntime } from "../data-runtime";
64
+ import { GEO_SCOPE_STATE_KEY } from "../../world-venue-map/scope";
65
+ import type { GeoScope } from "../../world-venue-map/local-layers";
66
+ import { boardRows, boardThemes, resolveBoardTheme, ThemeBoard, ThemeStats, themeStatRows, useThemeStepKeys } from "./themes";
64
67
 
65
68
  const BUILDOUT_UPGRADE_URL = "https://thebuildout.ai/pricing";
69
+ /** How long the cursor rests on a company before linked panes follow it. */
70
+ const CURSOR_PUBLISH_MS = 150;
71
+
72
+ /** A symbol the rest of the app can open: the dataset writes some Asian listings by number alone. */
73
+ function linkableTicker(value: string | null | undefined): string | null {
74
+ const symbol = tickerSymbol(value);
75
+ return symbol && /^[A-Z][A-Z0-9.-]*$/.test(symbol) ? symbol : null;
76
+ }
77
+
78
+ function sameScope(left: GeoScope | null, right: GeoScope | null): boolean {
79
+ return left?.key === right?.key && left?.label === right?.label;
80
+ }
66
81
 
67
82
  /** A star a row's company or site did not take: said by its star cell, in full at the top of its detail. */
68
83
  const favoriteNoteKey = (row: BuildoutRow) => favoriteKey(row) ?? rowKey(row);
@@ -97,8 +112,11 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
97
112
  const tableScrollRef = useRef<ScrollBoxRenderable | null>(null);
98
113
  const favoriteBusyKeysRef = useRef<Set<string>>(new Set());
99
114
  const clearFavoriteNotesRef = useRef<() => void>(() => {});
100
- const [activeTab, setActiveTab] = usePaneStateValue<BuildoutTabId>("activeTab", "companies");
115
+ const [activeTab, setActiveTab] = usePaneStateValue<BuildoutTabId>("activeTab", "themes");
101
116
  const [selectedList, setSelectedList] = useState<BuildoutList | null>(null);
117
+ const [themeSlug, setThemeSlug] = usePaneStateValue<string | null>("themeSlug", null);
118
+ const [, setCursorSymbol] = usePaneStateValue<string | null>("cursorSymbol", null);
119
+ const [, setGeoScope] = usePaneStateValue<GeoScope | null>(GEO_SCOPE_STATE_KEY, null);
102
120
  const [selectedIndex, setSelectedIndex] = useState(0);
103
121
  const [detailRow, setDetailRow] = useState<BuildoutRow | null>(null);
104
122
  const [sort, setSort] = useState<SortPreference<BuildoutColumnId>>({ columnId: null, direction: "asc" });
@@ -126,7 +144,12 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
126
144
  activeTab,
127
145
  onBeforeLoad: handleBeforeLoad,
128
146
  selectedList,
147
+ themeSlug,
129
148
  });
149
+ const themes = useMemo(() => (state.status === "ready" ? boardThemes(state.lists) : []), [state]);
150
+ const themeList = useMemo(() => resolveBoardTheme(themes, themeSlug), [themeSlug, themes]);
151
+ // The list whose companies the table shows: the board's theme, or the one opened from Companies.
152
+ const currentList = activeTab === "themes" ? themeList : selectedList;
130
153
  const favoriteToken = state.status === "ready" ? state.token : null;
131
154
  const canFavorite = favoriteToken != null;
132
155
 
@@ -146,25 +169,43 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
146
169
  useEffect(() => {
147
170
  setSelectedIndex(0);
148
171
  setDetailRow(null);
149
- const nextColumn: BuildoutColumnId | null = activeTab === "companies"
150
- ? selectedList ? "marketCap" : null
172
+ const nextColumn: BuildoutColumnId | null = activeTab === "companies" || activeTab === "themes"
173
+ ? currentList ? "marketCap" : null
151
174
  : activeTab === "sites" ? "capture" : "time";
152
175
  setSort({ columnId: nextColumn, direction: defaultSortDirection(nextColumn) });
153
- }, [activeTab, selectedList?.slug]);
176
+ }, [activeTab, currentList?.slug]);
154
177
 
155
178
  const rows = useMemo(
156
- () => sortRows(activeRows(state, activeTab, selectedList), sort.columnId, sort.direction),
157
- [activeTab, selectedList, sort, state],
179
+ () => sortRows(activeRows(state, activeTab, currentList), sort.columnId, sort.direction),
180
+ [activeTab, currentList, sort, state],
158
181
  );
159
182
  // Until the next star on that row, or a reload.
160
183
  const favoriteNotes = useRowNotes<BuildoutRow, string>(rows, { key: favoriteNoteKey });
161
184
  const { setNote: setFavoriteNote, noteFor: favoriteNoteFor } = favoriteNotes;
162
185
  clearFavoriteNotesRef.current = favoriteNotes.clearNotes;
163
186
  const columns = useMemo(
164
- () => columnsForTab(activeTab, selectedList, canFavorite, width),
165
- [activeTab, canFavorite, selectedList, width],
187
+ () => columnsForTab(activeTab, currentList, canFavorite, width),
188
+ [activeTab, canFavorite, currentList, width],
166
189
  );
167
190
  const selectedRow = rows[selectedIndex] ?? rows[0] ?? null;
191
+
192
+ // Panes linked to this one follow the company under the cursor, and a linked map can widen to the
193
+ // theme it belongs to. Off the company tabs both stay where they were.
194
+ const cursorCompany = detailRow?.kind === "company" ? detailRow.item : selectedRow?.kind === "company" ? selectedRow.item : null;
195
+ const cursorTicker = linkableTicker(cursorCompany?.ticker);
196
+ // Published once the cursor rests: arrowing down a theme would otherwise load every company it passes.
197
+ useEffect(() => {
198
+ if (!cursorTicker) return;
199
+ const timer = setTimeout(() => setCursorSymbol(cursorTicker), CURSOR_PUBLISH_MS);
200
+ return () => clearTimeout(timer);
201
+ }, [cursorTicker, setCursorSymbol]);
202
+ const companyTab = activeTab === "themes" || activeTab === "companies";
203
+ const themeScope = companyTab && currentList && currentList.slug !== "all" ? currentList : null;
204
+ useEffect(() => {
205
+ if (!companyTab) return;
206
+ const next: GeoScope | null = themeScope ? { key: `theme:${themeScope.slug}`, label: themeScope.name } : null;
207
+ setGeoScope((current) => (sameScope(current, next) ? current : next));
208
+ }, [companyTab, setGeoScope, themeScope?.name, themeScope?.slug]);
168
209
  // The table's cells carry their own badges; this catalog only serves the open
169
210
  // detail: its row's tickers, plus the loaded rows' tickers its text mentions.
170
211
  // Streaming every loaded row here would subscribe pages of symbols nobody sees.
@@ -226,8 +267,8 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
226
267
  // The free tier sees the head of a list; the footer says so, the `$` hint
227
268
  // is the way out, and no count of what is hidden is drawn.
228
269
  const partialList = state.status === "ready"
229
- && activeTab === "companies"
230
- && !!selectedList
270
+ && companyTab
271
+ && !!currentList
231
272
  && !state.companies.loadingMore
232
273
  && !state.companies.hasMore
233
274
  && !state.companies.error
@@ -262,6 +303,10 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
262
303
  setDetailRow(row);
263
304
  }, [openCompanyList]);
264
305
 
306
+ const selectTheme = useCallback((list: BuildoutList) => {
307
+ setThemeSlug(list.slug);
308
+ }, [setThemeSlug]);
309
+
265
310
  const updateFavorite = useCallback((key: string, starred: boolean) => {
266
311
  setState((current) => applyFavoriteToState(current, key, starred));
267
312
  setDetailRow((current) => (
@@ -328,23 +373,23 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
328
373
  }, [chooseOpenTarget, favoriteBusyKey, favoriteTarget, favoriteTargetKey, openTargets.length, startUpgrade, state, toggleFavoriteRow]);
329
374
 
330
375
  usePaneFooter("buildout", () => ({
331
- info: updateBuildoutFooterInfo(state, activeTab, selectedList, {
376
+ info: updateBuildoutFooterInfo(state, activeTab, currentList, {
332
377
  upgradeMessage,
333
378
  partialList,
334
379
  onUpgrade: startUpgrade,
335
380
  quoteFreshness: detailQuoteFreshness,
336
381
  }),
337
382
  hints: footerHints,
338
- }), [activeTab, detailQuoteFreshness, footerHints, partialList, selectedList, startUpgrade, state, upgradeMessage]);
383
+ }), [activeTab, currentList, detailQuoteFreshness, footerHints, partialList, startUpgrade, state, upgradeMessage]);
339
384
 
340
- const activePage = state.status === "ready" ? activeBuildoutPage(state, activeTab, selectedList) : null;
385
+ const activePage = state.status === "ready" ? activeBuildoutPage(state, activeTab, currentList) : null;
341
386
  const loadMoreActiveRows = useTableLoadMore(
342
387
  tableScrollRef,
343
388
  !!activePage && !activePage.loadingMore && !!activePage.hasMore && !activePage.error,
344
389
  () => {
345
390
  if (!activePage) return;
346
- if (activeTab === "companies" && selectedList) {
347
- void loadCompanies(selectedList, activePage.offset, true);
391
+ if (companyTab && currentList) {
392
+ void loadCompanies(currentList, activePage.offset, true);
348
393
  } else if (activeTab === "sites") {
349
394
  void loadSites(activePage.offset, true);
350
395
  } else if (activeTab === "intel") {
@@ -385,6 +430,8 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
385
430
  toggleFavorite,
386
431
  }), [favoriteBusyKey, favoriteNoteFor, toggleFavorite]);
387
432
 
433
+ useThemeStepKeys({ themes, selectedSlug: themeList?.slug ?? null, enabled: focused && activeTab === "themes" && !detailRow, onSelect: selectTheme });
434
+
388
435
  const { strip: tabStrip, rows: tabRows } = usePaneTabs({
389
436
  tabs,
390
437
  activeValue: activeTab,
@@ -406,7 +453,12 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
406
453
  }
407
454
 
408
455
  const listOpen = activeTab === "companies" && !!selectedList;
409
- const tableHeight = Math.max(1, height - tabRows - (listOpen ? 1 : 0));
456
+ // Themes: the board, the theme in figures, then its companies; an open company takes the whole body.
457
+ const themesBody = activeTab === "themes" && !detailRow;
458
+ const bodyHeight = Math.max(1, height - tabRows);
459
+ const boardHeight = themesBody ? boardRows(bodyHeight) : 0;
460
+ const statHeight = themesBody ? themeStatRows(themeList, width) : 0;
461
+ const tableHeight = Math.max(1, bodyHeight - (listOpen ? 1 : 0) - boardHeight - statHeight);
410
462
  const table = (
411
463
  <DataTableStackView<BuildoutRow, BuildoutColumn>
412
464
  focused={focused}
@@ -430,7 +482,7 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
430
482
  selection={{
431
483
  kind: "index",
432
484
  selectedIndex,
433
- onChange: (index) => setSelectedIndex(index),
485
+ onChange: setSelectedIndex,
434
486
  }}
435
487
  onActivate={activateRow}
436
488
  // The lists start unsorted, and every table sorts by its headers.
@@ -441,16 +493,35 @@ export function BuildoutPane({ focused, width, height }: PaneProps) {
441
493
  getItemKey={rowKey}
442
494
  renderCell={renderCell}
443
495
  selectedTextOverridesCellColor
444
- emptyContent={renderBuildoutPageStatus(state, activeTab, selectedList)}
445
- emptyStateTitle={selectedList ? "No companies" : "No rows"}
496
+ emptyContent={renderBuildoutPageStatus(state, activeTab, currentList)}
497
+ emptyStateTitle={currentList ? "No companies" : "No rows"}
446
498
  onRootKeyDown={handleRootKeyDown}
447
499
  onDetailKeyDown={handleDetailKeyDown}
448
500
  onBodyScrollActivity={loadMoreActiveRows}
449
501
  scrollRef={tableScrollRef}
450
- resetScrollKey={`${activeTab}:${selectedList?.slug ?? "lists"}`}
502
+ resetScrollKey={`${activeTab}:${currentList?.slug ?? "lists"}`}
451
503
  />
452
504
  );
453
505
 
506
+ if (activeTab === "themes") {
507
+ return (
508
+ <Box flexDirection="column" width={width} height={height} overflow="hidden">
509
+ {tabStrip && <Box height={1}>{tabStrip}</Box>}
510
+ {boardHeight > 0 ? (
511
+ <ThemeBoard
512
+ themes={themes}
513
+ selectedSlug={themeList?.slug ?? null}
514
+ width={width}
515
+ height={boardHeight}
516
+ onSelect={selectTheme}
517
+ />
518
+ ) : null}
519
+ {themesBody ? <ThemeStats list={themeList} width={width} /> : null}
520
+ {table}
521
+ </Box>
522
+ );
523
+ }
524
+
454
525
  // Tabs on top; below them a list opened from Companies is a stack level with
455
526
  // the shared Back row, and a company opened from that list is the table's
456
527
  // own detail. The outer stack only listens while the inner detail is closed.
@@ -11,7 +11,7 @@ export function activeBuildoutPage(
11
11
  selectedList: BuildoutList | null,
12
12
  ) {
13
13
  if (state.status !== "ready") return null;
14
- if (activeTab === "companies") return selectedList ? state.companies : null;
14
+ if (activeTab === "companies" || activeTab === "themes") return selectedList ? state.companies : null;
15
15
  if (activeTab === "sites") return state.sites;
16
16
  return state.intel;
17
17
  }
@@ -0,0 +1,110 @@
1
+ import { useId, useMemo } from "react";
2
+ import { MetricTreemapSurface, StatGrid, type MetricTreemapItem, type StatItem } from "../../../../components";
3
+ import { statGridWithin } from "../../../../components/ui/stat-grid";
4
+ import { useShortcut } from "../../../../react/input";
5
+ import { priceColor } from "../../../../theme/colors";
6
+ import { isPlainKey } from "../../../../utils/keyboard";
7
+ import { metricNumber, moneyAmount, text } from "../format";
8
+ import type { BuildoutList } from "../model/types";
9
+
10
+ /** The board leaves the table at least this many rows, and draws only when it has this many itself. */
11
+ const MIN_TABLE_ROWS = 8;
12
+ const MIN_BOARD_ROWS = 6;
13
+
14
+ /** The themes the board draws, largest first: the order the arrows step through. */
15
+ export function boardThemes(lists: readonly BuildoutList[]): BuildoutList[] {
16
+ return lists
17
+ .filter((list) => list.slug !== "all")
18
+ .map((list, index) => ({ list, index, cap: moneyAmount(list.totalMarketCap) ?? 0 }))
19
+ .sort((left, right) => right.cap - left.cap || left.index - right.index)
20
+ .map(({ list }) => list);
21
+ }
22
+
23
+ /** The theme the board opens on: the one saved with the pane, else the largest. */
24
+ export function resolveBoardTheme(themes: readonly BuildoutList[], slug: string | null): BuildoutList | null {
25
+ return themes.find((list) => list.slug === slug) ?? themes[0] ?? null;
26
+ }
27
+
28
+ /** Rows for the board in a Themes body of `height` rows; zero when the table needs them all. */
29
+ export function boardRows(height: number): number {
30
+ const rows = Math.min(Math.floor(height * 0.46), height - MIN_TABLE_ROWS);
31
+ return rows >= MIN_BOARD_ROWS ? rows : 0;
32
+ }
33
+
34
+ /**
35
+ * Left and right step to the next theme by size, board or not; up and down
36
+ * stay with the companies table. Ahead of the pane's tabs, which take left and
37
+ * right as well; h and l still switch tabs.
38
+ */
39
+ export function useThemeStepKeys({ themes, selectedSlug, enabled, onSelect }: {
40
+ themes: readonly BuildoutList[];
41
+ selectedSlug: string | null;
42
+ enabled: boolean;
43
+ onSelect: (list: BuildoutList) => void;
44
+ }) {
45
+ const scope = useId();
46
+ const index = Math.max(0, themes.findIndex((list) => list.slug === selectedSlug));
47
+ useShortcut((event) => {
48
+ if (!enabled || themes.length === 0) return;
49
+ const step = isPlainKey(event, "left") ? -1 : isPlainKey(event, "right") ? 1 : 0;
50
+ if (!step) return;
51
+ event.preventDefault();
52
+ event.stopPropagation();
53
+ onSelect(themes[(index + step + themes.length) % themes.length]!);
54
+ }, { enabled, scope: `buildout-themes:${scope}` });
55
+ }
56
+
57
+ /** The selected theme in figures, between the board and its companies. */
58
+ function themeStatItems(list: BuildoutList | null): StatItem[] {
59
+ if (!list) return [];
60
+ const returnColor = metricNumber(list.avgReturn1y);
61
+ const growthColor = metricNumber(list.avgSectorGrowth);
62
+ return [
63
+ { id: "theme", label: "Theme", value: list.name, detail: list.shortDescription ?? undefined, detailVariants: [""], wide: true },
64
+ { id: "companies", label: "Companies", value: text(list.companyCount) },
65
+ { id: "return", label: "1Y median", value: text(list.avgReturn1y), ...(returnColor == null ? {} : { color: priceColor(returnColor) }) },
66
+ { id: "growth", label: "Growth", value: text(list.avgSectorGrowth), ...(growthColor == null ? {} : { color: priceColor(growthColor) }) },
67
+ { id: "leaders", label: "Leaders", value: text(list.leaders) },
68
+ ];
69
+ }
70
+
71
+ export function themeStatRows(list: BuildoutList | null, width: number): number {
72
+ return statGridWithin(themeStatItems(list), width, 2).lines;
73
+ }
74
+
75
+ export function ThemeStats({ list, width }: { list: BuildoutList | null; width: number }) {
76
+ const { items } = statGridWithin(themeStatItems(list), width, 2);
77
+ return <StatGrid items={items} width={width} maxLines={2} />;
78
+ }
79
+
80
+ /**
81
+ * The themes as a board: each tile sized by the theme's market value and
82
+ * coloured by its companies' median one-year return.
83
+ */
84
+ export function ThemeBoard({ themes, selectedSlug, width, height, onSelect }: {
85
+ themes: readonly BuildoutList[];
86
+ selectedSlug: string | null;
87
+ width: number;
88
+ height: number;
89
+ onSelect: (list: BuildoutList) => void;
90
+ }) {
91
+ const items = useMemo<MetricTreemapItem<BuildoutList>[]>(() => themes.map((list) => ({
92
+ id: list.slug,
93
+ label: list.name,
94
+ weight: moneyAmount(list.totalMarketCap),
95
+ colorValue: metricNumber(list.avgReturn1y),
96
+ primaryText: list.avgReturn1y,
97
+ tooltip: [list.name, `${text(list.avgReturn1y)} 1Y median · ${text(list.totalMarketCap)} · ${text(list.companyCount)} companies`],
98
+ data: list,
99
+ })), [themes]);
100
+ return (
101
+ <MetricTreemapSurface
102
+ items={items}
103
+ width={width}
104
+ height={height}
105
+ selectedId={selectedSlug}
106
+ onSelect={(item) => onSelect(item.data)}
107
+ emptyStateTitle="No themes"
108
+ />
109
+ );
110
+ }
@@ -10,9 +10,10 @@ import type {
10
10
  } from "./model/types";
11
11
  import { dropColumnsToFit } from "../../../components/ui/table-layout";
12
12
  import type { SortDirection } from "../../../utils/sort-values";
13
- import { metricNumber, sourceDetailEntries, textOrNull, tickerSymbol } from "./format";
13
+ import { LIST_TIER_ORDER, metricNumber, sourceDetailEntries, textOrNull, tickerSymbol } from "./format";
14
14
 
15
15
  export const tabs: Array<{ label: string; value: BuildoutTabId }> = [
16
+ { label: "Themes", value: "themes" },
16
17
  { label: "Companies", value: "companies" },
17
18
  { label: "Sites", value: "sites" },
18
19
  { label: "Intel", value: "intel" },
@@ -46,6 +47,17 @@ const companyColumns: BuildoutColumn[] = [
46
47
  { id: "employees", label: "Employees", width: 9, align: "right" },
47
48
  ];
48
49
 
50
+ /** A theme's companies under its board: what each does in the theme, then the figures. */
51
+ const themeCompanyColumns: BuildoutColumn[] = [
52
+ { id: "company", label: "Company", width: 26, align: "left", flexGrow: 2 },
53
+ { id: "listRole", label: "Role", width: 15, align: "left" },
54
+ { id: "listTier", label: "Tier", width: 9, align: "left" },
55
+ { id: "marketCap", label: "Mkt Cap", width: 10, align: "right" },
56
+ { id: "revenueGrowth", label: "Growth", width: 9, align: "right" },
57
+ { id: "return1y", label: "1Y", width: 8, align: "right" },
58
+ ];
59
+ const THEME_COMPANY_DROP_ORDER: readonly BuildoutColumnId[] = ["listTier", "revenueGrowth", "listRole"];
60
+
49
61
  const siteColumns: BuildoutColumn[] = [
50
62
  { id: "site", label: "Site Name", width: 28, align: "left", flexGrow: 2 },
51
63
  // The default sort, so it sits where it is always on screen.
@@ -160,11 +172,16 @@ export function activeRows(
160
172
  selectedList: BuildoutList | null,
161
173
  ): BuildoutRow[] {
162
174
  if (state.status !== "ready") return [];
175
+ if (activeTab === "themes") {
176
+ return selectedList && state.companies.listSlug === selectedList.slug
177
+ ? state.companies.items.map((item) => ({ kind: "company", item }))
178
+ : [];
179
+ }
163
180
  if (activeTab === "companies") {
164
181
  if (!selectedList) {
165
182
  return state.lists.map((item) => ({ kind: "list", item }));
166
183
  }
167
- return state.companies.items.map((item) => ({ kind: "company", item }));
184
+ return state.companies.listSlug === selectedList.slug ? state.companies.items.map((item) => ({ kind: "company", item })) : [];
168
185
  }
169
186
  if (activeTab === "sites") {
170
187
  return state.sites.items.map((item) => ({ kind: "site", item }));
@@ -190,6 +207,9 @@ export function columnsForTab(
190
207
  canFavorite: boolean,
191
208
  width: number,
192
209
  ): BuildoutColumn[] {
210
+ if (activeTab === "themes") {
211
+ return dropColumnsToFit(canFavorite ? [favoriteColumn, ...themeCompanyColumns] : themeCompanyColumns, width - 1, THEME_COMPANY_DROP_ORDER);
212
+ }
193
213
  if (activeTab === "companies") {
194
214
  const columns = selectedList
195
215
  ? canFavorite ? [favoriteColumn, ...companyColumns] : companyColumns
@@ -286,6 +306,10 @@ function sortValue(row: BuildoutRow, columnId: BuildoutColumnId): SortComparable
286
306
  return numberSort(company.return1y);
287
307
  case "employees":
288
308
  return numberSort(company.employeeCount);
309
+ case "listRole":
310
+ return stringSort(company.listRole);
311
+ case "listTier":
312
+ return numberSort(company.listTier && LIST_TIER_ORDER.includes(company.listTier) ? LIST_TIER_ORDER.indexOf(company.listTier) : null);
289
313
  default:
290
314
  return stringSort(rowTitle(row));
291
315
  }
@@ -163,3 +163,19 @@ export const earningsPluginMeta = {
163
163
  version: "1.0.0",
164
164
  toggleable: true,
165
165
  };
166
+
167
+ export const fearGreedPluginMeta = {
168
+ id: "fear-greed",
169
+ name: "Fear & Greed",
170
+ version: "1.0.0",
171
+ description: "CNN Fear & Greed index with the seven indicator charts",
172
+ toggleable: true,
173
+ };
174
+
175
+ export const notesPluginMeta = {
176
+ id: "notes",
177
+ name: "Notes",
178
+ version: "2.0.0",
179
+ description: "Markdown notes on tickers and on their own, personal or shared with a team.",
180
+ toggleable: true,
181
+ };
@@ -21,7 +21,7 @@ import {
21
21
  type CoveredIssuers,
22
22
  type InstrumentSearch,
23
23
  } from "./client";
24
- import { formatContract } from "../credit-boards/model";
24
+ import { formatContract, levelKinds } from "../credit-boards/model";
25
25
  import {
26
26
  DEFAULT_TRADE_SORT,
27
27
  ISSUER_LIST_SORTS,
@@ -122,6 +122,8 @@ function dailySection(history: CdsSpreadHistory, days: number, name: string): He
122
122
  const from = latest ? new Date(Date.parse(`${latest}T00:00:00Z`) - days * 86_400_000).toISOString().slice(0, 10) : "";
123
123
  // A name quoted upfront lists each day's points beside its spread.
124
124
  const upfront = history.quote === "upfront";
125
+ const shown = history.points.filter((point) => point.date > from).reverse();
126
+ const kinds = levelKinds(shown, (point) => point.kind);
125
127
  return {
126
128
  title: "Daily",
127
129
  columns: [
@@ -136,8 +138,9 @@ function dailySection(history: CdsSpreadHistory, days: number, name: string): He
136
138
  }] : []),
137
139
  { key: "prints", header: "Prints", align: "right" as const },
138
140
  { key: "maturity", header: "Contract", format: (value: unknown) => formatContract(typeof value === "string" ? value : null) },
141
+ ...kinds.columns,
139
142
  ],
140
- rows: history.points.filter((point) => point.date > from).reverse()
143
+ rows: shown
141
144
  .map((point) => ({
142
145
  day: point.date,
143
146
  spreadBp: point.spreadBp,
@@ -145,6 +148,7 @@ function dailySection(history: CdsSpreadHistory, days: number, name: string): He
145
148
  unit: upfront ? "pts" : "bp",
146
149
  prints: point.prints,
147
150
  maturity: point.maturity,
151
+ ...kinds.row(point),
148
152
  })),
149
153
  empty: "No daily 5Y levels.",
150
154
  symbol: name,
@@ -212,11 +216,14 @@ function spreadEntries(history: CdsSpreadHistory): HeadlessPaneEntry[] {
212
216
  : { lowBp: summary.range.low, highBp: summary.range.high, since: summary.range.since },
213
217
  formatted: `${formatSpreadRange(unit, summary.range)} since ${summary.range.since}`,
214
218
  } : null;
219
+ // A level that is the day's settlement price says so beside its date; one from trades says nothing.
220
+ const kind = summary.settlement ? [{ key: "kind", label: "Level", value: "settlement" }] : [];
215
221
  if (unit === "pts") {
216
222
  return [
217
223
  { key: "upfrontPts", label: "5Y upfront", value: summary.level, formatted: formatPoints(summary.level), unit: "pts" },
218
224
  { key: "couponBp", label: "Coupon", value: summary.couponBp, formatted: formatBp(summary.couponBp) },
219
225
  { key: "date", label: "Date", value: summary.date },
226
+ ...kind,
220
227
  { key: "change1wPts", label: "1W change", value: summary.change1w, formatted: formatPointsMove(summary.change1w), unit: "pts" },
221
228
  { key: "change1mPts", label: "1M change", value: summary.change1m, formatted: formatPointsMove(summary.change1m), unit: "pts" },
222
229
  ...(range ? [range] : []),
@@ -225,6 +232,7 @@ function spreadEntries(history: CdsSpreadHistory): HeadlessPaneEntry[] {
225
232
  return [
226
233
  { key: "spreadBp", label: "5Y spread", value: summary.level, formatted: formatBp(summary.level) },
227
234
  { key: "date", label: "Date", value: summary.date },
235
+ ...kind,
228
236
  { key: "change1wBp", label: "1W change", value: summary.change1w, formatted: formatSpreadMove(summary.change1w) },
229
237
  { key: "change1mBp", label: "1M change", value: summary.change1m, formatted: formatSpreadMove(summary.change1m) },
230
238
  ...(range ? [range] : []),
@@ -6,7 +6,7 @@ import type { TickerRecord } from "../../../types/ticker";
6
6
  import { formatCompact } from "../../../utils/format";
7
7
  import { compareSortValues, type SortPreference } from "../../../utils/sort-values";
8
8
  import { typoDistance } from "../../../utils/typo-distance";
9
- import { formatMove } from "../credit-boards/model";
9
+ import { formatMove, SETTLEMENT_TEXT } from "../credit-boards/model";
10
10
 
11
11
  export const CDS_PANE_ID = "cds";
12
12
 
@@ -858,6 +858,8 @@ export interface CdsSpreadSummary {
858
858
  /** The latest level in that unit, and the New York trade date it is from. */
859
859
  level: number;
860
860
  date: string;
861
+ /** The level is that day's settlement price, not its trades. */
862
+ settlement: boolean;
861
863
  /** The same level in bp. */
862
864
  spreadBp: number;
863
865
  /** The running coupon of a name quoted upfront, bp. */
@@ -888,6 +890,7 @@ export function spreadSummary(line: CdsSpreadLine): CdsSpreadSummary | null {
888
890
  unit: upfront ? "pts" : "bp",
889
891
  level: upfront ? line.upfrontPts! : latest.spreadBp,
890
892
  date: latest.date,
893
+ settlement: latest.kind === "settlement",
891
894
  spreadBp: latest.spreadBp,
892
895
  couponBp: upfront ? line.couponBp : null,
893
896
  change1w: upfront ? line.change1WPts : line.change1W,
@@ -921,15 +924,16 @@ export function spreadFigures(line: CdsSpreadLine): StatItem[] {
921
924
  const summary = spreadSummary(line);
922
925
  if (!summary) return [];
923
926
  const { unit } = summary;
927
+ const dated = summary.settlement ? `${summary.date} · ${SETTLEMENT_TEXT}` : summary.date;
924
928
  return [
925
929
  unit === "pts"
926
930
  ? {
927
931
  id: "spread",
928
932
  label: "5Y upfront",
929
933
  value: formatPoints(summary.level),
930
- detail: summary.couponBp == null ? summary.date : `${summary.date} · ${formatBp(summary.couponBp)} coupon`,
934
+ detail: summary.couponBp == null ? dated : `${dated} · ${formatBp(summary.couponBp)} coupon`,
931
935
  }
932
- : { id: "spread", label: "5Y spread", value: formatBp(summary.level), detail: summary.date },
936
+ : { id: "spread", label: "5Y spread", value: formatBp(summary.level), detail: dated },
933
937
  ...(summary.change1w != null ? [{ id: "1w", label: "1W", value: formatQuotedMove(unit, summary.change1w) }] : []),
934
938
  ...(summary.change1m != null ? [{ id: "1m", label: "1M", value: formatQuotedMove(unit, summary.change1m) }] : []),
935
939
  ...(summary.range ? [{
@@ -1,12 +1,11 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { centralBankRatesCache } from "./client";
3
3
  import { centralBankRatesHeadless } from "./headless";
4
- import { CentralBankRatesPane } from "./pane";
5
4
  import { CENTRAL_BANK_SERIES } from "./series";
6
5
 
7
6
  export const centralBankRatesModule: PluginModule = {
8
7
  series: CENTRAL_BANK_SERIES,
9
- panes: [{ id: "central-bank-rates", name: "Central Bank Rates", icon: "%", component: CentralBankRatesPane,
8
+ panes: [{ id: "central-bank-rates", name: "Central Bank Rates", icon: "%", get component(): typeof import("./pane").CentralBankRatesPane { const { CentralBankRatesPane } = require("./pane"); return CentralBankRatesPane; },
10
9
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 108, height: 25 },
11
10
  tableExport: true, headless: centralBankRatesHeadless }],
12
11
  paneTemplates: [{
@@ -2,6 +2,7 @@ import { useCallback, useMemo } from "react";
2
2
  import { Box } from "../../../ui";
3
3
  import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
4
4
  import { CompositeChart, MarketBoardStack, PaneStatusBody, usePaneNoticeFooter, usePaneStatusLinkFooter, type StatItem } from "../../../components";
5
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
5
6
  import { colors } from "../../../theme/colors";
6
7
  import { isAccessDenied } from "../../../api-client/errors";
7
8
  import type { CentralBankRow } from "../../../api-client/central-bank-rates";
@@ -59,7 +60,7 @@ export function CentralBankRatesPane({ width, height, focused }: PaneProps) {
59
60
  notices: [...(data ? policyNotices(data) : []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
60
61
  usePaneStatusLinkFooter({ registrationId: "central-bank-rates", focused, loading: resource.loading, error: resource.error,
61
62
  url: selected?.observation.sourceUrl ?? null, showOpenHint: true,
62
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
63
+ info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
63
64
  stale: !!data && resource.data?.stale,
64
65
  });
65
66
  return <Box width={width} height={height} flexDirection="column">
@@ -1,6 +1,6 @@
1
1
  import type { SeriesDefinition } from "../../../series/types";
2
2
  import { CENTRAL_BANKS, centralBankNames } from "../../../utils/central-banks";
3
- import { cachedSeriesWarning } from "../shared/series-warning";
3
+ import { cachedSeriesNote } from "../shared/series-warning";
4
4
  import { loadCentralBankRates } from "./client";
5
5
  import { policySeriesAttribution } from "./model";
6
6
 
@@ -24,12 +24,11 @@ export const CENTRAL_BANK_SERIES: readonly SeriesDefinition[] = CENTRAL_BANKS.ma
24
24
  const resource = await loadCentralBankRates();
25
25
  const row = resource.payload.rows.find((entry) => entry.id === bank.id);
26
26
  if (!row) throw new Error(`${bank.place} is not on the board right now.`);
27
- const warning = cachedSeriesWarning(resource);
28
27
  // Cited as CBR cites the row: the BIS or FRED series and the bank that sets the rate.
29
28
  const attribution = policySeriesAttribution(row);
30
29
  return {
31
30
  points: row.history.map((point) => ({ date: point.date, value: point.value })),
32
- ...(warning ? { warning } : {}),
31
+ ...cachedSeriesNote(resource),
33
32
  ...(attribution ? { attribution } : {}),
34
33
  };
35
34
  },
@@ -82,7 +82,8 @@ function ChangelogDetail({
82
82
  );
83
83
  }
84
84
 
85
- function ChangelogPane({ focused, width, height }: PaneProps) {
85
+ /** The Changelog, as its own pane (CHG) and as ROADMAP's Changelog view. */
86
+ export function ChangelogPane({ focused, width, height }: PaneProps) {
86
87
  const paneInstance = usePaneInstance();
87
88
  const requestedVersion = paneInstance?.params?.version ?? null;
88
89
  const [releases, setReleases] = useState<ChangelogRelease[]>(() => releaseCache.peek(RELEASES, { stale: true }) ?? []);
@@ -289,7 +290,7 @@ export const changelogModule: PluginModule = {
289
290
  name: "Changelog",
290
291
  icon: "L",
291
292
  component: ChangelogPane,
292
- reportFreshness: { status: "not-a-feed", basis: "release notes" },
293
+ reportFreshness: { status: "not-a-feed", basis: "release notes", local: true },
293
294
  defaultPosition: "right",
294
295
  defaultMode: "floating",
295
296
  defaultFloatingSize: { width: 100, height: 32 },
@@ -11,6 +11,7 @@ import {
11
11
  } from "../../../time-series/spec";
12
12
  import type { ChartSeriesSpec, ChartSpec, SeriesPeriod } from "../../../time-series/types";
13
13
  import { yearToDateBaseDay } from "../../../market-data/market/year-to-date";
14
+ import { readPriceWindow } from "../../../utils/price-window";
14
15
 
15
16
  const PRICE_CAPABILITIES = new Set(["price-chart", "intraday-price-chart", "price-comparison"]);
16
17
 
@@ -35,6 +36,13 @@ export function yearToDateWindow(spec: ChartSpec, now = Date.now()): { start: st
35
36
  return { start: starts.sort()[0] ?? `${Number(today.slice(0, 4)) - 1}-12-31`, end: today };
36
37
  }
37
38
 
39
+ /** The shortest range whose history reaches back to `from`: 5Y for a window from 2022, ALL for one from 2019. */
40
+ function rangeCovering(from: string, now = Date.now()): TimeRange {
41
+ const days = (now - Date.parse(`${from}T00:00:00Z`)) / 86_400_000;
42
+ const covering: Array<[TimeRange, number]> = [["1M", 31], ["3M", 92], ["6M", 183], ["1Y", 366], ["5Y", 5 * 366]];
43
+ return covering.find(([, span]) => days <= span)?.[0] ?? "ALL";
44
+ }
45
+
38
46
  function chartResolution(options: HeadlessPaneOptionValues): ChartResolution | null {
39
47
  return parseChartResolution(options.chartResolution ?? options.resolution);
40
48
  }
@@ -77,6 +85,14 @@ export function applyChartComposerCapabilityOptions(
77
85
  };
78
86
  }
79
87
 
88
+ // --from and --to: those dates, each leg rebased at its first close in them, as YTD is from the prior year's last.
89
+ const window = capabilityId === "price-comparison"
90
+ ? readPriceWindow(typeof options.from === "string" ? options.from : undefined, typeof options.to === "string" ? options.to : undefined)
91
+ : null;
92
+ if (window) {
93
+ next = { ...next, viewport: { ...next.viewport, range: rangeCovering(window.from), dateWindow: { start: window.from, end: window.to } } };
94
+ }
95
+
80
96
  if (capabilityId === "price-chart" && options.extendedHours === true) {
81
97
  next = { ...next, viewport: { ...next.viewport, extendedHours: true } };
82
98
  }