@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
@@ -0,0 +1,247 @@
1
+ /**
2
+ * One company as TheBuildout sees it: who powers and hosts it, who supplies
3
+ * it, who pays it, where its sites are and where it hires. Read from the
4
+ * dataset's company profile with the account's own access, so the free tier
5
+ * gets what the dataset gives it and a Pro account the rest.
6
+ */
7
+ import { buildoutApi, getBuildoutProToken } from "../model";
8
+ import { normalizeUpdate } from "../model/normalizers";
9
+ import type { BuildoutUpdate, RawObject } from "../model/types";
10
+ import { metricNumber, tickerSymbol } from "../format";
11
+
12
+ export type RelationRole = "power" | "supplier" | "customer" | "competitor";
13
+
14
+ export interface BuildoutRelation {
15
+ id: string;
16
+ name: string;
17
+ ticker: string | null;
18
+ role: RelationRole;
19
+ /** The deal in a few words: "800 MW · 15 yr", "67% of FY2025 revenue". */
20
+ figure: string | null;
21
+ /** What the relationship is, in the dataset's words. */
22
+ summary: string | null;
23
+ /** MW for a power deal, dollars for the rest, to order and total them. */
24
+ weight: number | null;
25
+ }
26
+
27
+ export interface BuildoutCompanySite {
28
+ id: string;
29
+ name: string;
30
+ type: string | null;
31
+ /** Owner, or partner for a site the company is named at but does not own. */
32
+ role: "owner" | "partner";
33
+ mw: number | null;
34
+ place: string | null;
35
+ }
36
+
37
+ export interface BuildoutHiringSegment {
38
+ id: string;
39
+ place: string;
40
+ roleFamily: string | null;
41
+ roles: number | null;
42
+ change: number | null;
43
+ site: string | null;
44
+ }
45
+
46
+ export interface BuildoutCompanyProfile {
47
+ id: string;
48
+ name: string;
49
+ ticker: string | null;
50
+ marketCap: string | null;
51
+ return1y: string | null;
52
+ growth: string | null;
53
+ relations: BuildoutRelation[];
54
+ sites: BuildoutCompanySite[];
55
+ hiring: {
56
+ /** The dataset has hiring by site for this company, whether this account sees it or not. */
57
+ hasData: boolean;
58
+ segments: BuildoutHiringSegment[];
59
+ openRoles: number | null;
60
+ };
61
+ access: "free" | "pro";
62
+ }
63
+
64
+ const POWER_PATTERN = /power|energy|colocation|hosting|data_center_capacity|capacity_supply/i;
65
+
66
+ const str = (value: unknown): string | null => (typeof value === "string" && value.trim() ? value.trim() : null);
67
+ const num = (value: unknown): number | null => (typeof value === "number" && Number.isFinite(value) ? value : null);
68
+ const list = (value: unknown): RawObject[] => (Array.isArray(value) ? value.filter((item): item is RawObject => !!item && typeof item === "object") : []);
69
+
70
+ /** "data_center_onsite_power" reads "Data center onsite power". */
71
+ function words(value: string | null): string | null {
72
+ const text = value?.replace(/_/g, " ").replace(/\s+/g, " ").trim();
73
+ if (!text) return null;
74
+ const cased = text.replace(/\b(ai|hpc|gpu|cpu)\b/gi, (word) => word.toUpperCase());
75
+ return cased.charAt(0).toUpperCase() + cased.slice(1);
76
+ }
77
+
78
+ function dollars(value: number): string {
79
+ if (value >= 1e9) return `$${(value / 1e9).toFixed(value >= 1e11 ? 0 : 1).replace(/\.0$/, "")}B`;
80
+ if (value >= 1e6) return `$${Math.round(value / 1e6)}M`;
81
+ return `$${Math.round(value).toLocaleString("en-US")}`;
82
+ }
83
+
84
+ const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
85
+
86
+ /** The largest MW figure a text gives, as written: "approximately 590 MW". */
87
+ function largestMegawatts(text: string | null): number | null {
88
+ let best: number | null = null;
89
+ for (const match of text?.matchAll(/(\d[\d,]*(?:\.\d+)?)\s*(GW|MW)\b/g) ?? []) {
90
+ const value = Number(match[1]!.replace(/,/g, "")) * (match[2] === "GW" ? 1000 : 1);
91
+ if (Number.isFinite(value) && (best === null || value > best)) best = value;
92
+ }
93
+ return best;
94
+ }
95
+
96
+ /** "$14.2 billion through December 2031" reads "$14.2B through Dec 2031". */
97
+ function contractFigure(text: string | null): string | null {
98
+ const money = text?.match(/\$\s?(\d+(?:\.\d+)?)\s*(billion|million|bn|B|M)\b/i);
99
+ if (!money) return null;
100
+ const unit = /^(b|bn|billion)$/i.test(money[2]!) ? "B" : "M";
101
+ const until = text!.slice(money.index!).match(/through\s+([A-Z][a-z]+)\s+(\d{4})/);
102
+ const month = until && MONTHS.includes(until[1]!) ? until[1]!.slice(0, 3) : null;
103
+ return `$${money[1]}${unit}${month ? ` through ${month} ${until![2]}` : ""}`;
104
+ }
105
+
106
+ /** "approximately 67% of FY2025 revenue". */
107
+ function revenueShare(text: string | null): string | null {
108
+ const match = text?.match(/(\d+(?:\.\d+)?)%\s+of\s+(?:its\s+)?((?:FY|fiscal\s+)?\d{4}\s+)?(?:total\s+)?revenue/i);
109
+ return match ? `${match[1]}% of ${match[2]?.trim() ? `${match[2].trim()} ` : ""}revenue` : null;
110
+ }
111
+
112
+ function term(text: string | null): string | null {
113
+ const match = text?.match(/(\d+)-year/);
114
+ return match ? `${match[1]} yr` : null;
115
+ }
116
+
117
+ function relation(raw: RawObject, role: RelationRole): BuildoutRelation | null {
118
+ const name = str(raw.name);
119
+ if (!name) return null;
120
+ const description = str(raw.description);
121
+ const evidence = str(raw.evidenceSummary);
122
+ const usd = num(raw.weightUsd);
123
+ const category = str(raw.flowCategory) ?? str(raw.relationshipScope);
124
+ let figure: string | null = null;
125
+ let weight: number | null = usd;
126
+ if (role === "power") {
127
+ const mw = largestMegawatts(description) ?? largestMegawatts(evidence);
128
+ weight = mw;
129
+ const parts = [mw ? `${mw.toLocaleString("en-US")} MW` : null, usd ? dollars(usd) : null, term(str(raw.weightPeriod))].filter(Boolean);
130
+ figure = parts.length ? parts.join(" · ") : words(category);
131
+ } else if (role === "customer") {
132
+ figure = revenueShare(evidence) ?? revenueShare(description) ?? contractFigure(evidence) ?? contractFigure(description)
133
+ ?? (usd ? dollars(usd) : null) ?? words(category);
134
+ } else if (role === "supplier") {
135
+ figure = words(category);
136
+ }
137
+ return {
138
+ id: str(raw.companyId) ?? str(raw.id) ?? name,
139
+ name,
140
+ ticker: tickerSymbol(str(raw.ticker)),
141
+ role,
142
+ figure,
143
+ summary: description ?? evidence,
144
+ weight,
145
+ };
146
+ }
147
+
148
+ /** One card per company and role: the dataset can list a supplier once per thing it supplies. */
149
+ function unique(relations: (BuildoutRelation | null)[]): BuildoutRelation[] {
150
+ const seen = new Map<string, BuildoutRelation>();
151
+ for (const entry of relations) {
152
+ if (!entry) continue;
153
+ const key = `${entry.role}:${entry.id}`;
154
+ const existing = seen.get(key);
155
+ if (!existing || (existing.weight === null && entry.weight !== null)) seen.set(key, entry);
156
+ }
157
+ return [...seen.values()];
158
+ }
159
+
160
+ function byWeight(left: BuildoutRelation, right: BuildoutRelation) {
161
+ return (right.weight ?? -1) - (left.weight ?? -1);
162
+ }
163
+
164
+ function site(raw: RawObject): BuildoutCompanySite | null {
165
+ const id = str(raw.id);
166
+ const name = str(raw.name);
167
+ if (!id || !name) return null;
168
+ const capacity = str(raw.powerCapacity);
169
+ const location = raw.location && typeof raw.location === "object" ? raw.location as RawObject : {};
170
+ const mw = capacity ? largestMegawatts(capacity.replace(/(\d)(MW|GW)/, "$1 $2")) : null;
171
+ return {
172
+ id,
173
+ name,
174
+ type: str(raw.type),
175
+ role: str(raw.relationship) === "owner" ? "owner" : "partner",
176
+ mw: mw && mw > 0 ? mw : null,
177
+ place: [str(location.city), str(location.country)].filter(Boolean).join(", ") || null,
178
+ };
179
+ }
180
+
181
+ function hiringSegment(raw: RawObject): BuildoutHiringSegment | null {
182
+ const id = str(raw.id);
183
+ if (!id) return null;
184
+ const place = [str(raw.city), str(raw.region) ?? str(raw.countryIso)].filter(Boolean).join(", ") || str(raw.locationRaw) || "Unplaced";
185
+ const siteRecord = raw.site && typeof raw.site === "object" ? raw.site as RawObject : null;
186
+ return {
187
+ id,
188
+ place,
189
+ roleFamily: words(str(raw.roleFamily)),
190
+ roles: num(raw.latestRoleCountEstimate),
191
+ change: num(raw.deltaSincePriorPeriod) ?? num(raw.deltaSincePriorObservation),
192
+ site: siteRecord ? str(siteRecord.name) : null,
193
+ };
194
+ }
195
+
196
+ export function normalizeCompanyProfile(raw: RawObject, access: "free" | "pro"): BuildoutCompanyProfile {
197
+ const chain = raw.supplyChain && typeof raw.supplyChain === "object" ? raw.supplyChain as RawObject : {};
198
+ const suppliers = list(chain.suppliers);
199
+ const relations = [
200
+ ...unique(suppliers.filter((entry) => POWER_PATTERN.test(`${str(entry.flowCategory) ?? ""} ${str(entry.relationshipScope) ?? ""}`)).map((entry) => relation(entry, "power"))).sort(byWeight),
201
+ ...unique(suppliers.filter((entry) => !POWER_PATTERN.test(`${str(entry.flowCategory) ?? ""} ${str(entry.relationshipScope) ?? ""}`)).map((entry) => relation(entry, "supplier"))),
202
+ ...unique(list(chain.customers).map((entry) => relation(entry, "customer"))).sort(byWeight),
203
+ ...unique(list(chain.competitors).map((entry) => relation(entry, "competitor"))),
204
+ ];
205
+ const hiring = raw.hiring && typeof raw.hiring === "object" ? raw.hiring as RawObject : {};
206
+ return {
207
+ id: str(raw.id) ?? "",
208
+ name: str(raw.name) ?? "",
209
+ ticker: tickerSymbol(str(raw.ticker)),
210
+ marketCap: str(raw.marketCap),
211
+ return1y: str(raw.return1y),
212
+ growth: str(raw.revenueGrowthYoy),
213
+ relations,
214
+ sites: list(raw.sites).map(site).filter((entry): entry is BuildoutCompanySite => !!entry)
215
+ .sort((left, right) => Number(left.role !== "owner") - Number(right.role !== "owner") || (right.mw ?? -1) - (left.mw ?? -1)),
216
+ hiring: {
217
+ hasData: hiring.hasData === true,
218
+ segments: list(hiring.segments).map(hiringSegment).filter((entry): entry is BuildoutHiringSegment => !!entry),
219
+ openRoles: num(hiring.totalOpenRolesEstimate) ?? metricNumber(raw.recruitment_open_roles ?? raw.recruitmentOpenRoles),
220
+ },
221
+ access,
222
+ };
223
+ }
224
+
225
+ /** The company under `symbol`, or null when the dataset has none. */
226
+ export async function fetchCompanyProfile(symbol: string): Promise<BuildoutCompanyProfile | null> {
227
+ const token = await getBuildoutProToken();
228
+ try {
229
+ const raw = await buildoutApi<RawObject>(`/companies/${encodeURIComponent(symbol)}`, token);
230
+ return normalizeCompanyProfile(raw, token ? "pro" : "free");
231
+ } catch (error) {
232
+ if (error instanceof Error && /not found|404/i.test(error.message)) return null;
233
+ throw error;
234
+ }
235
+ }
236
+
237
+ export async function fetchCompanySignals(companyId: string): Promise<BuildoutUpdate[]> {
238
+ const token = await getBuildoutProToken();
239
+ const raw = await buildoutApi<RawObject[]>(`/updates?companyId=${encodeURIComponent(companyId)}&limit=40`, token);
240
+ return Array.isArray(raw) ? raw.map(normalizeUpdate) : [];
241
+ }
242
+
243
+ /** Power deals in MW, summed: what the header's power figure says. */
244
+ export function totalPowerMw(profile: BuildoutCompanyProfile): number | null {
245
+ const figures = profile.relations.filter((entry) => entry.role === "power" && entry.weight !== null).map((entry) => entry.weight!);
246
+ return figures.length ? figures.reduce((sum, value) => sum + value, 0) : null;
247
+ }
@@ -0,0 +1,317 @@
1
+ import { useCallback, useMemo, useState, type ReactNode } from "react";
2
+ import {
3
+ Button,
4
+ DataTableStackView,
5
+ EmptyState,
6
+ PaneStatusBody,
7
+ StatGrid,
8
+ delayedFooterSegment,
9
+ usePaneFooter,
10
+ usePaneTabs,
11
+ type DataTableCell,
12
+ type DataTableColumn,
13
+ type PaneFooterSegment,
14
+ type PaneHint,
15
+ type StatItem,
16
+ } from "../../../../components";
17
+ import { statGridWithin } from "../../../../components/ui/stat-grid";
18
+ import { useAsyncResource, usePaneSettingValue, usePluginAppActions } from "../../../../public/react";
19
+ import { usePaneStateValue } from "../../../../state/app/context";
20
+ import { usePaneTickerIdentity } from "../../../../state/hooks/pane-ticker";
21
+ import { useInlineTickers } from "../../../../state/hooks/inline-tickers";
22
+ import { collectUniqueTickerSymbols } from "../../../../tickers/tokenizer";
23
+ import { priceColor } from "../../../../theme/colors";
24
+ import { useThemeColors } from "../../../../theme/theme-context";
25
+ import type { PaneProps } from "../../../../types/plugin";
26
+ import { Box, ScrollBox, useRendererHost } from "../../../../ui";
27
+ import { CLOUD_PLAN_KEY } from "../../shared/cloud-upgrade";
28
+ import { useOpenDescription } from "../../shared/open-description";
29
+ import { renderBuildoutCell } from "../cells";
30
+ import { BuildoutDetail } from "../detail";
31
+ import { BUILDOUT_COMPANY_PANE_ID } from "./id";
32
+ import { metricNumber, moneyText, text, tickerSearchText, tickerSymbol } from "../format";
33
+ import type { BuildoutColumn, BuildoutRow, BuildoutUpdate } from "../model/types";
34
+ import { rowKey } from "../table-model";
35
+ import { CompanyFlow, CompanyFlowList, FLOW_MIN_WIDTH } from "./flow";
36
+ import {
37
+ fetchCompanyProfile,
38
+ fetchCompanySignals,
39
+ totalPowerMw,
40
+ type BuildoutCompanyProfile,
41
+ type BuildoutCompanySite,
42
+ type BuildoutHiringSegment,
43
+ type BuildoutRelation,
44
+ } from "./model";
45
+
46
+ const UPGRADE_URL = "https://thebuildout.ai/pricing";
47
+
48
+ export type CompanyTab = "flow" | "sites" | "hiring" | "signals";
49
+ const TABS: Array<{ label: string; value: CompanyTab }> = [
50
+ { label: "Flow", value: "flow" },
51
+ { label: "Sites", value: "sites" },
52
+ { label: "Hiring", value: "hiring" },
53
+ { label: "Signals", value: "signals" },
54
+ ];
55
+ const isCompanyTab = (value: unknown): value is CompanyTab => TABS.some((tab) => tab.value === value);
56
+
57
+ type SiteColumn = DataTableColumn & { id: "site" | "type" | "role" | "mw" | "place" };
58
+ const SITE_COLUMNS: SiteColumn[] = [
59
+ { id: "site", label: "Site", width: 24, align: "left", flexGrow: 1 },
60
+ { id: "type", label: "Type", width: 12, align: "left" },
61
+ { id: "role", label: "Role", width: 8, align: "left" },
62
+ { id: "mw", label: "MW", width: 6, align: "right" },
63
+ { id: "place", label: "Place", width: 18, align: "left" },
64
+ ];
65
+
66
+ type HiringColumn = DataTableColumn & { id: "place" | "roles" | "change" | "family" | "site" };
67
+ const HIRING_COLUMNS: HiringColumn[] = [
68
+ { id: "place", label: "Place", width: 18, align: "left", flexGrow: 1 },
69
+ { id: "roles", label: "Roles", width: 6, align: "right" },
70
+ { id: "change", label: "Chg", width: 5, align: "right" },
71
+ { id: "family", label: "Roles in", width: 18, align: "left" },
72
+ { id: "site", label: "Site", width: 22, align: "left", flexGrow: 1 },
73
+ ];
74
+
75
+ /** A company's signals: what happened first, then who else it names. */
76
+ const SIGNAL_COLUMNS: BuildoutColumn[] = [
77
+ { id: "time", label: "Time", width: 4, align: "left" },
78
+ { id: "headline", label: "Title", width: 40, align: "left", flexGrow: 4 },
79
+ { id: "companies", label: "Also", width: 16, align: "left" },
80
+ ];
81
+
82
+ /** Columns that fit `width`, the flexible ones first to give way after the fixed tail. */
83
+ function fitColumns<C extends DataTableColumn>(columns: readonly C[], width: number, dropOrder: readonly string[]): C[] {
84
+ let kept = [...columns];
85
+ for (const id of dropOrder) {
86
+ const total = kept.reduce((sum, column) => sum + column.width + 1, 0);
87
+ if (total <= width) break;
88
+ kept = kept.filter((column) => column.id !== id);
89
+ }
90
+ return kept;
91
+ }
92
+
93
+ function headerItems(profile: BuildoutCompanyProfile): StatItem[] {
94
+ const count = (role: BuildoutRelation["role"]) => profile.relations.filter((relation) => relation.role === role).length;
95
+ const power = count("power");
96
+ const mw = totalPowerMw(profile);
97
+ const owned = profile.sites.filter((site) => site.role === "owner").length;
98
+ const change = metricNumber(profile.return1y);
99
+ return [
100
+ { id: "company", label: "Company", value: profile.name, detail: profile.marketCap ? moneyText(profile.marketCap) : undefined },
101
+ { id: "return", label: "1Y", value: text(profile.return1y), ...(change == null ? {} : { color: priceColor(change) }) },
102
+ ...(mw
103
+ ? [{ id: "power", label: "Power", value: `${mw.toLocaleString("en-US")} MW`, detail: `${power} ${power === 1 ? "deal" : "deals"}` }]
104
+ : power ? [{ id: "power", label: "Power deals", value: String(power) }] : []),
105
+ { id: "suppliers", label: "Suppliers", value: String(count("supplier")) },
106
+ { id: "customers", label: "Customers", value: String(count("customer")) },
107
+ { id: "sites", label: "Sites", value: String(profile.sites.length), detail: profile.sites.length && owned < profile.sites.length ? `${owned} owned` : undefined },
108
+ ];
109
+ }
110
+
111
+ function siteCell(site: BuildoutCompanySite, column: SiteColumn, dim: string, bright: string): DataTableCell {
112
+ switch (column.id) {
113
+ case "site": return { text: site.name, color: bright };
114
+ case "type": return { text: text(site.type), color: dim };
115
+ case "role": return { text: site.role === "owner" ? "Owner" : "Partner", color: dim };
116
+ case "mw": return { text: site.mw == null ? "-" : site.mw.toLocaleString("en-US"), ...(site.mw == null ? {} : { value: site.mw }), color: dim };
117
+ case "place": return { text: text(site.place), color: dim };
118
+ }
119
+ }
120
+
121
+ function hiringCell(segment: BuildoutHiringSegment, column: HiringColumn, dim: string, bright: string): DataTableCell {
122
+ switch (column.id) {
123
+ case "place": return { text: segment.place, color: bright };
124
+ case "roles": return { text: segment.roles == null ? "-" : String(segment.roles), color: bright };
125
+ case "change": return { text: segment.change == null ? "-" : `${segment.change > 0 ? "+" : ""}${segment.change}`, color: segment.change ? priceColor(segment.change) : dim };
126
+ case "family": return { text: text(segment.roleFamily), color: dim };
127
+ case "site": return { text: text(segment.site), color: dim };
128
+ }
129
+ }
130
+
131
+ /**
132
+ * One company from TheBuildout: its power and hosting deals, suppliers and
133
+ * customers as a flow, its sites, where it hires, and the signals that name
134
+ * it. It follows the company selected in TheBuildout, or any list it is
135
+ * linked to.
136
+ */
137
+ export function BuildoutCompanyPane({ focused, width, height }: PaneProps) {
138
+ const colors = useThemeColors();
139
+ const rendererHost = useRendererHost();
140
+ const { createPaneFromTemplate } = usePluginAppActions();
141
+ const openDescription = useOpenDescription();
142
+ const { symbol } = usePaneTickerIdentity();
143
+ const [settingTab] = usePaneSettingValue<unknown>("tab", undefined);
144
+ const [tab, setTab] = usePaneStateValue<CompanyTab>("tab", isCompanyTab(settingTab) ? settingTab : "flow");
145
+ const [siteIndex, setSiteIndex] = useState(0);
146
+ const [hiringIndex, setHiringIndex] = useState(0);
147
+ const [signalIndex, setSignalIndex] = useState(0);
148
+ const [openSignal, setOpenSignal] = useState<BuildoutUpdate | null>(null);
149
+
150
+ const loader = useMemo(() => (symbol ? () => fetchCompanyProfile(symbol) : null), [symbol]);
151
+ const profile = useAsyncResource(loader, { keepPreviousData: true });
152
+ // The last company stays while the next loads; one the dataset does not have says so.
153
+ const company = profile.data;
154
+ const missing = !!symbol && !company && !profile.loading && !profile.error;
155
+ const signalsLoader = useMemo(
156
+ () => (company?.id && tab === "signals" ? () => fetchCompanySignals(company.id) : null),
157
+ [company?.id, tab],
158
+ );
159
+ const signals = useAsyncResource(signalsLoader);
160
+
161
+ const openRelation = useCallback((relation: BuildoutRelation) => {
162
+ if (relation.ticker) openDescription(relation.ticker);
163
+ }, [openDescription]);
164
+ const upgrade = useCallback(() => { void rendererHost.openExternal(UPGRADE_URL); }, [rendererHost]);
165
+
166
+ // Every signal names this company, so its row lists only the others.
167
+ const focusTicker = tickerSymbol(company?.ticker ?? symbol);
168
+ const signalRows = useMemo<BuildoutRow[]>(() => (signals.data ?? []).map((item) => ({
169
+ kind: "intel",
170
+ item: { ...item, companies: (item.companies ?? []).filter((entry) => tickerSymbol(entry.ticker) !== focusTicker) },
171
+ })), [focusTicker, signals.data]);
172
+ const tickerTexts = useMemo(() => {
173
+ if (!openSignal) return [];
174
+ const texts = [openSignal.headline, openSignal.content ?? "", openSignal.context ?? ""];
175
+ return [tickerSearchText([...collectUniqueTickerSymbols(texts)])];
176
+ }, [openSignal]);
177
+ const { catalog, openTicker } = useInlineTickers(tickerTexts, { badgeQuotes: true });
178
+
179
+ const { strip, rows: tabRows } = usePaneTabs({
180
+ tabs: TABS,
181
+ activeValue: tab,
182
+ onSelect: (value) => setTab(value as CompanyTab),
183
+ focused: focused && !openSignal,
184
+ compact: true,
185
+ variant: "bare",
186
+ });
187
+
188
+ const free = company?.access === "free";
189
+ const footerHints = useMemo<PaneHint[]>(() => {
190
+ const hints: PaneHint[] = [];
191
+ if (free) hints.push({ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade", onPress: upgrade });
192
+ return hints;
193
+ }, [free, upgrade]);
194
+ usePaneFooter(BUILDOUT_COMPANY_PANE_ID, () => {
195
+ const info: PaneFooterSegment[] = [];
196
+ if (profile.loading && company) info.push({ id: "loading", parts: [{ text: "loading", tone: "muted" }] });
197
+ if (profile.error && company) info.push({ id: "error", parts: [{ text: "refresh failed", tone: "warning" }] });
198
+ if (free && tab === "signals") info.push(delayedFooterSegment({ id: "access", detail: "72h delayed", delay: "72h", onPress: upgrade }));
199
+ return { info, hints: footerHints };
200
+ }, [company, footerHints, free, profile.error, profile.loading, tab, upgrade]);
201
+
202
+ const frame = (content: ReactNode) => (
203
+ <Box flexDirection="column" width={width} height={height} overflow="hidden">
204
+ {strip && <Box height={1}>{strip}</Box>}
205
+ {content}
206
+ </Box>
207
+ );
208
+ if (!symbol) {
209
+ return frame(<EmptyState title="No company selected." message="Link this pane to TheBuildout or a list, or open TBOC with a ticker." />);
210
+ }
211
+ if (!company) {
212
+ if (missing) return frame(<EmptyState title={`${symbol} is not in TheBuildout.`} />);
213
+ if (profile.error) return frame(<PaneStatusBody error={String(profile.error)} subject="TheBuildout" retry={{ onRetry: () => void profile.load(true) }} />);
214
+ return frame(<PaneStatusBody loading subject="TheBuildout" />);
215
+ }
216
+
217
+ const header = statGridWithin(headerItems(company), width, 2);
218
+ const bodyHeight = Math.max(1, height - tabRows - header.lines);
219
+ const dim = colors.textDim;
220
+ const bright = colors.text;
221
+
222
+ let body: ReactNode;
223
+ if (tab === "flow") {
224
+ body = company.relations.length === 0
225
+ ? <EmptyState title="No relationships in TheBuildout yet." />
226
+ : width >= FLOW_MIN_WIDTH
227
+ ? <CompanyFlow profile={company} width={width} height={bodyHeight} focused={focused} onOpen={openRelation} />
228
+ : (
229
+ <ScrollBox width={width} height={bodyHeight} scrollY focusable={false}>
230
+ <CompanyFlowList profile={company} width={width} onOpen={openRelation} />
231
+ </ScrollBox>
232
+ );
233
+ } else if (tab === "sites") {
234
+ const columns = fitColumns(SITE_COLUMNS, width - 1, ["place", "type", "mw"]);
235
+ body = (
236
+ <DataTableStackView<BuildoutCompanySite, SiteColumn>
237
+ focused={focused}
238
+ detailOpen={false}
239
+ onBack={() => {}}
240
+ detailContent={null}
241
+ rootWidth={width}
242
+ rootHeight={bodyHeight}
243
+ columns={columns}
244
+ sortColumnId={null}
245
+ sortDirection="asc"
246
+ items={company.sites}
247
+ selection={{ kind: "index", selectedIndex: siteIndex, onChange: setSiteIndex }}
248
+ getItemKey={(site) => site.id}
249
+ renderCell={(site, column) => siteCell(site, column, dim, bright)}
250
+ emptyStateTitle="No sites in TheBuildout yet."
251
+ />
252
+ );
253
+ } else if (tab === "hiring") {
254
+ const columns = fitColumns(HIRING_COLUMNS, width - 1, ["site", "family", "change"]);
255
+ body = company.hiring.segments.length ? (
256
+ <DataTableStackView<BuildoutHiringSegment, HiringColumn>
257
+ focused={focused}
258
+ detailOpen={false}
259
+ onBack={() => {}}
260
+ detailContent={null}
261
+ rootWidth={width}
262
+ rootHeight={bodyHeight}
263
+ columns={columns}
264
+ sortColumnId={null}
265
+ sortDirection="asc"
266
+ emptyStateTitle="No hiring by site."
267
+ items={company.hiring.segments}
268
+ selection={{ kind: "index", selectedIndex: hiringIndex, onChange: setHiringIndex }}
269
+ getItemKey={(segment) => segment.id}
270
+ renderCell={(segment, column) => hiringCell(segment, column, dim, bright)}
271
+ />
272
+ ) : (
273
+ <EmptyState
274
+ title={company.hiring.hasData && free ? "Hiring by site is in Pro." : `No hiring by site for ${company.name} yet.`}
275
+ message={company.hiring.hasData && free ? "Pro shows the roles a company is hiring for, place by place, and the site they match." : company.ticker ? "HIRE shows the company's open roles from its own postings." : undefined}
276
+ actions={company.hiring.hasData && free
277
+ ? <Button label="Upgrade" onPress={upgrade} />
278
+ : company.ticker ? <Button label={`HIRE ${company.ticker}`} onPress={() => createPaneFromTemplate("hiring-pane", { symbol: company.ticker })} /> : undefined}
279
+ />
280
+ );
281
+ } else {
282
+ body = (
283
+ <DataTableStackView<BuildoutRow, BuildoutColumn>
284
+ focused={focused}
285
+ detailOpen={!!openSignal}
286
+ onBack={() => setOpenSignal(null)}
287
+ detailTitle={openSignal?.headline}
288
+ detailContent={(
289
+ <BuildoutDetail
290
+ row={openSignal ? { kind: "intel", item: openSignal } : null}
291
+ width={width}
292
+ height={bodyHeight}
293
+ catalog={catalog}
294
+ openTicker={openTicker}
295
+ />
296
+ )}
297
+ rootWidth={width}
298
+ rootHeight={bodyHeight}
299
+ columns={fitColumns(SIGNAL_COLUMNS, width - 1, ["companies"])}
300
+ sortColumnId={null}
301
+ sortDirection="asc"
302
+ items={signalRows}
303
+ selection={{ kind: "index", selectedIndex: signalIndex, onChange: setSignalIndex }}
304
+ onActivate={(row) => { if (row.kind === "intel") setOpenSignal(row.item); }}
305
+ getItemKey={rowKey}
306
+ renderCell={(row, column, _index, rowState) => renderBuildoutCell(row, column, rowState, { favoriteBusyKey: null, toggleFavorite: () => {} })}
307
+ selectedTextOverridesCellColor
308
+ emptyStateTitle={signals.loading ? "Loading..." : signals.error ? "Signals could not load." : `No signals name ${company.name} yet.`}
309
+ />
310
+ );
311
+ }
312
+
313
+ return frame(<>
314
+ <StatGrid items={header.items} width={width} maxLines={2} />
315
+ {body}
316
+ </>);
317
+ }
@@ -15,17 +15,24 @@ import {
15
15
  loadBuildoutData,
16
16
  } from "./model";
17
17
  import { appendUniqueById } from "./format";
18
+ import { boardThemes, resolveBoardTheme } from "./pane/themes";
19
+
20
+ /** How long the board rests on a theme before its companies load. */
21
+ const THEME_SETTLE_MS = 180;
18
22
 
19
23
  interface UseBuildoutDataRuntimeOptions {
20
24
  activeTab: BuildoutTabId;
21
25
  onBeforeLoad: () => void;
22
26
  selectedList: BuildoutList | null;
27
+ /** The theme the Themes board is on, as the pane saved it. */
28
+ themeSlug: string | null;
23
29
  }
24
30
 
25
31
  export function useBuildoutDataRuntime({
26
32
  activeTab,
27
33
  onBeforeLoad,
28
- selectedList,
34
+ selectedList: openedList,
35
+ themeSlug,
29
36
  }: UseBuildoutDataRuntimeOptions): {
30
37
  loadCompanies: (list: BuildoutList, offset: number, append: boolean) => Promise<void>;
31
38
  loadIntel: (offset: number, append: boolean) => Promise<void>;
@@ -53,7 +60,8 @@ export function useBuildoutDataRuntime({
53
60
  ? { ...current, refreshing: true, refreshError: null }
54
61
  : { status: "loading" });
55
62
 
56
- const token = await getBuildoutProToken();
63
+ // A refresh the user pressed asks for access again: a plan bought or ended since shows at once.
64
+ const token = await getBuildoutProToken({ fresh: refreshVersion > 0 });
57
65
  const data = await loadBuildoutData(token);
58
66
  if (!cancelled) {
59
67
  onBeforeLoad();
@@ -88,7 +96,7 @@ export function useBuildoutDataRuntime({
88
96
  return {
89
97
  ...current,
90
98
  companies: {
91
- ...(append ? current.companies : { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0 }),
99
+ ...(append ? current.companies : { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0, listSlug: list.slug }),
92
100
  loadingMore: true,
93
101
  error: null,
94
102
  },
@@ -98,7 +106,8 @@ export function useBuildoutDataRuntime({
98
106
  try {
99
107
  const page = await fetchCompaniesPage(token, list.slug, offset);
100
108
  setState((current) => {
101
- if (current.status !== "ready") return current;
109
+ // A list closed or replaced since keeps the answer out.
110
+ if (current.status !== "ready" || current.companies.listSlug !== list.slug) return current;
102
111
  const previousItems = append ? current.companies.items : [];
103
112
  const items = appendUniqueById(previousItems, page.items);
104
113
  return {
@@ -112,12 +121,13 @@ export function useBuildoutDataRuntime({
112
121
  blurredCompanyCount: append
113
122
  ? Math.max(current.companies.blurredCompanyCount, page.blurredCompanyCount)
114
123
  : page.blurredCompanyCount,
124
+ listSlug: list.slug,
115
125
  },
116
126
  };
117
127
  });
118
128
  } catch (error) {
119
129
  setState((current) => {
120
- if (current.status !== "ready") return current;
130
+ if (current.status !== "ready" || current.companies.listSlug !== list.slug) return current;
121
131
  return {
122
132
  ...current,
123
133
  companies: {
@@ -216,17 +226,28 @@ export function useBuildoutDataRuntime({
216
226
  }
217
227
  }, [state]);
218
228
 
229
+ // The open list's companies, once; the Themes board waits for the pointer or the keys to settle on a theme.
219
230
  useEffect(() => {
220
- if (state.status !== "ready" || activeTab !== "companies" || !selectedList) return;
221
- if (state.companies.items.length > 0 || state.companies.loadingMore || state.companies.error) return;
222
- void loadCompanies(selectedList, 0, false);
223
- }, [activeTab, loadCompanies, selectedList, state]);
231
+ if (state.status !== "ready") return;
232
+ const selectedList = activeTab === "themes" ? resolveBoardTheme(boardThemes(state.lists), themeSlug) : openedList;
233
+ if ((activeTab !== "companies" && activeTab !== "themes") || !selectedList) return;
234
+ // A list that answered with no company this account can see is loaded too (no more pages), not asked again.
235
+ const { companies } = state;
236
+ const loaded = companies.listSlug === selectedList.slug;
237
+ if (loaded && (companies.items.length > 0 || companies.loadingMore || companies.error || !companies.hasMore)) return;
238
+ if (activeTab === "companies") {
239
+ void loadCompanies(selectedList, 0, false);
240
+ return;
241
+ }
242
+ const timer = setTimeout(() => { void loadCompanies(selectedList, 0, false); }, THEME_SETTLE_MS);
243
+ return () => clearTimeout(timer);
244
+ }, [activeTab, loadCompanies, openedList, state, themeSlug]);
224
245
 
225
246
  const resetCompanies = useCallback(() => {
226
247
  setState((current) => current.status === "ready"
227
248
  ? {
228
249
  ...current,
229
- companies: { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0 },
250
+ companies: { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0, listSlug: null },
230
251
  }
231
252
  : current);
232
253
  }, []);