@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,309 @@
1
+ /**
2
+ * TheBuildout's facilities as a layer of the world map: data centers, fabs,
3
+ * power plants, mines and the plants around them, read from the dataset's own
4
+ * map of located sites. A map linked to TheBuildout (or to any list) shows the
5
+ * selected company's sites, owned or not, or every site owned by a company in
6
+ * the selected theme. The dataset answers with what this account may see, so
7
+ * a theme's sites follow the same free and Pro split as the theme's companies.
8
+ */
9
+ import type {
10
+ GeoBbox,
11
+ GeoColumn,
12
+ GeoEntitiesPayload,
13
+ GeoEntityPayload,
14
+ GeoEntityRow,
15
+ GeoFeature,
16
+ GeoFeaturesPayload,
17
+ GeoLayerInfo,
18
+ GeoPropValue,
19
+ } from "../../../api-client/geo";
20
+ import type { LocalEntityQuery, LocalFeatureQuery, LocalGeoLayer } from "../world-venue-map/local-layers";
21
+ import { createTtlCache } from "../../../data/ttl-cache";
22
+ import { type BuildoutReader, buildoutApi, getBuildoutReader } from "./model";
23
+
24
+ const FACILITIES_LAYER_ID = "facilities";
25
+
26
+ interface WorldMapPoint {
27
+ id: string;
28
+ name: string;
29
+ type: string | null;
30
+ siteRoles?: string[] | null;
31
+ physicalForms?: string[] | null;
32
+ capabilities?: string[] | null;
33
+ countryIso?: string | null;
34
+ ownerCompanyId?: string | null;
35
+ ownerTicker?: string | null;
36
+ ownerName?: string | null;
37
+ powerCapacity?: string | null;
38
+ constructionActivity?: number | null;
39
+ relevance?: { category?: string | null; score?: number | null; tier?: string | null; reasons?: string[] | null } | null;
40
+ lat: number;
41
+ lng: number;
42
+ coordinateSource?: string | null;
43
+ }
44
+
45
+ interface WorldMapPayload {
46
+ generatedAt?: string | null;
47
+ points?: WorldMapPoint[];
48
+ scope?: { kind?: string; id?: string | null; ticker?: string | null } | null;
49
+ }
50
+
51
+ interface FacilitySet {
52
+ asOf: string | null;
53
+ rows: GeoEntityRow[];
54
+ byId: Map<string, { row: GeoEntityRow; point: WorldMapPoint }>;
55
+ }
56
+
57
+ const COLUMNS: GeoColumn[] = [
58
+ { key: "label", label: "Facility" },
59
+ { key: "type", label: "Type" },
60
+ { key: "mw", label: "MW", align: "right", format: "int" },
61
+ { key: "owner", label: "Owner" },
62
+ { key: "country", label: "Country" },
63
+ ];
64
+
65
+ const INFO: GeoLayerInfo = {
66
+ id: FACILITIES_LAYER_ID,
67
+ name: "Facilities",
68
+ group: "infrastructure",
69
+ geometry: "point",
70
+ cadence: "daily",
71
+ refreshSeconds: null,
72
+ asOf: null,
73
+ count: null,
74
+ status: "ok",
75
+ access: "free",
76
+ columns: COLUMNS,
77
+ series: [],
78
+ defaultVisible: false,
79
+ };
80
+
81
+ /** Sites with no use to a map of the build-out: head offices and rows the dataset has not typed. */
82
+ const LEFT_OUT = new Set(["office", ""]);
83
+ const ALL_TTL_MS = 30 * 60_000;
84
+ const SCOPED_TTL_MS = 10 * 60_000;
85
+ const THEME_PAGE = 200;
86
+
87
+ const TYPE_WORDS: Record<string, string> = {
88
+ "Manufacturing Plant": "Plant",
89
+ "Semiconductor Fab": "Fab",
90
+ "Power Station": "Power plant",
91
+ "Advanced Packaging": "Packaging",
92
+ "Energy Storage Facility": "Storage",
93
+ "R&D Center": "R&D",
94
+ "Logistics Hub": "Logistics",
95
+ "Transmission Line": "Transmission",
96
+ "Utility Network": "Utility",
97
+ };
98
+
99
+ function entityKind(type: string | null): string {
100
+ switch (type) {
101
+ case "Data Center": return "data-center";
102
+ case "Power Station":
103
+ case "Substation":
104
+ case "Energy Storage Facility": return "power-plant";
105
+ case "Semiconductor Fab":
106
+ case "Advanced Packaging": return "fab";
107
+ default: return "facility";
108
+ }
109
+ }
110
+
111
+ /** "300.00 MW" reads 300; a site the dataset has no figure for has none. */
112
+ function megawatts(value: string | null | undefined): number | null {
113
+ const match = value?.replace(/,/g, "").match(/([\d.]+)\s*(GW|MW|KW)/i);
114
+ if (!match) return null;
115
+ const number = Number(match[1]);
116
+ if (!Number.isFinite(number) || number <= 0) return null;
117
+ const unit = match[2]!.toUpperCase();
118
+ const mw = unit === "GW" ? number * 1000 : unit === "KW" ? number / 1000 : number;
119
+ return mw >= 10 ? Math.round(mw) : Math.round(mw * 10) / 10;
120
+ }
121
+
122
+ const PLACED_BY: Record<string, string> = { geocode: "Address", metadata: "Site record", boundary: "Traced boundary" };
123
+
124
+ function toRow(point: WorldMapPoint): GeoEntityRow {
125
+ const ticker = point.ownerTicker?.trim();
126
+ return {
127
+ id: point.id,
128
+ label: point.name,
129
+ lon: point.lng,
130
+ lat: point.lat,
131
+ props: {
132
+ type: point.type ? TYPE_WORDS[point.type] ?? point.type : null,
133
+ mw: megawatts(point.powerCapacity),
134
+ owner: point.ownerName ?? null,
135
+ country: point.countryIso ?? null,
136
+ },
137
+ ...(ticker ? { tickers: [{ symbol: ticker, via: "owner" as const, confidence: "reviewed" as const }] } : {}),
138
+ };
139
+ }
140
+
141
+ /** The score the dataset gives a site for the build-out, plus a little for its size, so a capped answer keeps what matters. */
142
+ function rank(point: WorldMapPoint): number {
143
+ const mw = megawatts(point.powerCapacity) ?? 0;
144
+ return (point.relevance?.score ?? 0) + Math.min(20, Math.log10(1 + mw) * 6);
145
+ }
146
+
147
+ function facilitySet(payload: WorldMapPayload, keep: (point: WorldMapPoint) => boolean = () => true): FacilitySet {
148
+ const points = (payload.points ?? []).filter((point) => (
149
+ Number.isFinite(point.lat) && Number.isFinite(point.lng) && !LEFT_OUT.has((point.type ?? "").toLowerCase()) && keep(point)
150
+ )).sort((left, right) => rank(right) - rank(left));
151
+ const byId = new Map<string, { row: GeoEntityRow; point: WorldMapPoint }>();
152
+ const rows = points.map((point) => {
153
+ const row = toRow(point);
154
+ byId.set(row.id, { row, point });
155
+ return row;
156
+ });
157
+ return { asOf: payload.generatedAt ?? null, rows, byId };
158
+ }
159
+
160
+ // Every entry is one reader's: the free set is the same for everyone signed
161
+ // out or without Buildout, and a token's answer belongs to its account. A
162
+ // sign-out, a plan that ends or one that starts reads under a new key.
163
+ const allSites = createTtlCache<FacilitySet>({ ttlMs: ALL_TTL_MS, maxEntries: 4 });
164
+ const scopedSites = createTtlCache<FacilitySet>({ ttlMs: SCOPED_TTL_MS, maxEntries: 64 });
165
+ const themeCompanies = createTtlCache<Set<string>>({ ttlMs: ALL_TTL_MS, maxEntries: 64 });
166
+
167
+ function readerKey(reader: BuildoutReader): string {
168
+ return reader.token ? `pro:${reader.account}` : "free";
169
+ }
170
+
171
+ function loadAll(reader: BuildoutReader): Promise<FacilitySet> {
172
+ return allSites.load(readerKey(reader), async () => facilitySet(await buildoutApi<WorldMapPayload>("/sites/world-map", reader.token)));
173
+ }
174
+
175
+ /** The company's own sites and the ones it is named at. An unknown ticker has none. */
176
+ function loadCompany(reader: BuildoutReader, symbol: string): Promise<FacilitySet> {
177
+ return scopedSites.load(`${readerKey(reader)}|company:${symbol}`, async () => {
178
+ try {
179
+ return facilitySet(await buildoutApi<WorldMapPayload>(`/sites/world-map?company=${encodeURIComponent(symbol)}`, reader.token));
180
+ } catch (error) {
181
+ if (error instanceof Error && /not found|404/i.test(error.message)) return { asOf: null, rows: [], byId: new Map() };
182
+ throw error;
183
+ }
184
+ });
185
+ }
186
+
187
+ /** The ids of the theme's companies this account can see. */
188
+ function loadThemeCompanies(reader: BuildoutReader, slug: string): Promise<Set<string>> {
189
+ return themeCompanies.load(`${readerKey(reader)}|${slug}`, async () => {
190
+ const ids = new Set<string>();
191
+ for (let offset = 0; offset < 2000; offset += THEME_PAGE) {
192
+ const page = await buildoutApi<{ companies?: Array<{ id?: string }> } | Array<{ id?: string }>>(
193
+ `/companies?list=${encodeURIComponent(slug)}&limit=${THEME_PAGE}&offset=${offset}&sort=marketCap&order=desc`,
194
+ reader.token,
195
+ );
196
+ const companies = Array.isArray(page) ? page : page.companies ?? [];
197
+ for (const company of companies) if (company.id) ids.add(company.id);
198
+ if (companies.length < THEME_PAGE) break;
199
+ }
200
+ return ids;
201
+ });
202
+ }
203
+
204
+ async function loadScope(scope: string | null): Promise<FacilitySet> {
205
+ const reader = await getBuildoutReader();
206
+ if (scope?.startsWith("company:")) return loadCompany(reader, scope.slice("company:".length));
207
+ if (scope?.startsWith("theme:")) {
208
+ const slug = scope.slice("theme:".length);
209
+ return scopedSites.load(`${readerKey(reader)}|${scope}`, async () => {
210
+ const [all, ids] = await Promise.all([loadAll(reader), loadThemeCompanies(reader, slug)]);
211
+ return {
212
+ asOf: all.asOf,
213
+ rows: all.rows.filter((row) => ids.has(all.byId.get(row.id)?.point.ownerCompanyId ?? "")),
214
+ byId: all.byId,
215
+ };
216
+ });
217
+ }
218
+ return loadAll(reader);
219
+ }
220
+
221
+ function inBbox(row: GeoEntityRow, bbox: GeoBbox | null): boolean {
222
+ if (!bbox) return true;
223
+ const [west, south, east, north] = bbox;
224
+ if (row.lat < south || row.lat > north) return false;
225
+ return west <= east ? row.lon >= west && row.lon <= east : row.lon >= west || row.lon <= east;
226
+ }
227
+
228
+ function feature(row: GeoEntityRow, point: WorldMapPoint): GeoFeature {
229
+ return {
230
+ id: row.id,
231
+ layer: FACILITIES_LAYER_ID,
232
+ entityKind: entityKind(point.type),
233
+ label: row.label,
234
+ geometry: { type: "Point", coordinates: [row.lon, row.lat] },
235
+ ts: null,
236
+ props: row.props,
237
+ ...(row.tickers ? { tickers: row.tickers } : {}),
238
+ };
239
+ }
240
+
241
+ function matches(row: GeoEntityRow, needle: string): boolean {
242
+ if (!needle) return true;
243
+ const symbol = needle.replace(/^\$/, "").toUpperCase();
244
+ if (row.tickers?.some((link) => link.symbol.toUpperCase() === symbol)) return true;
245
+ return [row.label, ...Object.values(row.props)].some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
246
+ }
247
+
248
+ function compare(key: string | null, dir: "asc" | "desc") {
249
+ const sign = dir === "asc" ? 1 : -1;
250
+ return (left: GeoEntityRow, right: GeoEntityRow) => {
251
+ if (!key) return 0;
252
+ const a: GeoPropValue = key === "label" ? left.label : left.props[key] ?? null;
253
+ const b: GeoPropValue = key === "label" ? right.label : right.props[key] ?? null;
254
+ // Missing values sink in either direction.
255
+ if (a === null || b === null) return a === b ? 0 : a === null ? 1 : -1;
256
+ if (typeof a === "number" && typeof b === "number") return (a - b) * sign;
257
+ return String(a).localeCompare(String(b)) * sign;
258
+ };
259
+ }
260
+
261
+ const PLAIN_LIST = (values: string[] | null | undefined, count = 6) => (values?.length ? values.slice(0, count).join(", ") : null);
262
+
263
+ export const facilitiesLayer: LocalGeoLayer = {
264
+ info: INFO,
265
+ async features(query: LocalFeatureQuery): Promise<GeoFeaturesPayload> {
266
+ const set = await loadScope(query.scope);
267
+ const inView = set.rows.filter((row) => inBbox(row, query.bbox));
268
+ const kept = inView.slice(0, query.limit);
269
+ return {
270
+ layer: FACILITIES_LAYER_ID,
271
+ asOf: set.asOf,
272
+ features: kept.map((row) => feature(row, set.byId.get(row.id)!.point)),
273
+ total: inView.length,
274
+ truncated: kept.length < inView.length,
275
+ };
276
+ },
277
+ async entities(query: LocalEntityQuery): Promise<GeoEntitiesPayload> {
278
+ const set = await loadScope(query.scope);
279
+ const needle = query.q.trim().toLowerCase();
280
+ const rows = set.rows.filter((row) => inBbox(row, query.bbox) && matches(row, needle));
281
+ const sorted = query.sort ? [...rows].sort(compare(query.sort, query.dir)) : rows;
282
+ return {
283
+ layer: FACILITIES_LAYER_ID,
284
+ asOf: set.asOf,
285
+ columns: COLUMNS,
286
+ rows: sorted.slice(query.offset, query.offset + query.limit),
287
+ total: rows.length,
288
+ };
289
+ },
290
+ async entity(id: string, scope: string | null): Promise<GeoEntityPayload> {
291
+ const set = await loadScope(scope);
292
+ const found = set.byId.get(id) ?? (await loadAll(await getBuildoutReader())).byId.get(id);
293
+ if (!found) throw new Error("Unknown facility");
294
+ const { row, point } = found;
295
+ const relevance = point.relevance?.tier
296
+ ? `${point.relevance.tier.charAt(0).toUpperCase()}${point.relevance.tier.slice(1)}${point.relevance.reasons?.length ? `: ${point.relevance.reasons.join(", ")}` : ""}`
297
+ : null;
298
+ return {
299
+ feature: feature(row, point),
300
+ detail: {
301
+ roles: PLAIN_LIST(point.siteRoles),
302
+ forms: PLAIN_LIST(point.physicalForms),
303
+ capabilities: PLAIN_LIST(point.capabilities),
304
+ relevance,
305
+ placedBy: point.coordinateSource ? PLACED_BY[point.coordinateSource] ?? null : null,
306
+ },
307
+ };
308
+ },
309
+ };
@@ -127,6 +127,15 @@ export function criticalityColor(value: string | null | undefined) {
127
127
  }
128
128
  }
129
129
 
130
+ /** A theme's tiers, most important first. */
131
+ export const LIST_TIER_ORDER: readonly string[] = ["leader", "major", "notable", "watchlist"];
132
+
133
+ /** `core_vendor` reads "Core vendor". */
134
+ export function listWord(value: string | null | undefined): string | null {
135
+ const words = value?.trim().replace(/[_-]+/g, " ").toLowerCase();
136
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : null;
137
+ }
138
+
130
139
  export function tickerSymbol(value: string | null | undefined): string | null {
131
140
  const symbol = value?.trim();
132
141
  return symbol ? symbol.toUpperCase() : null;
@@ -1,24 +1,70 @@
1
- import { BuildoutPane } from "./pane";
1
+ import { BUILDOUT_COMPANY_PANE_ID } from "./company/id";
2
2
  import type { PluginModule } from "../plugin-module";
3
+ import type { PaneReportOptionDef } from "../../../types/plugin";
4
+ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
5
+
6
+ let removeFacilities: (() => void) | null = null;
7
+
8
+ /** Which view the company pane opens on: a desk opens one on its signals. */
9
+ const COMPANY_TAB_OPTION: PaneReportOptionDef = {
10
+ key: "tab",
11
+ type: "enum",
12
+ description: "The view to open on: the flow of deals, suppliers and customers, the sites, hiring by site or the signals.",
13
+ values: [{ value: "flow" }, { value: "sites" }, { value: "hiring" }, { value: "signals" }],
14
+ example: "--tab signals",
15
+ };
3
16
 
4
17
  export const buildoutModule: PluginModule = {
18
+ // Facilities join the world map's layers for as long as the plugin is on.
19
+ setup() {
20
+ const { facilitiesLayer } = require("./facilities") as typeof import("./facilities");
21
+ const { registerLocalGeoLayer } = require("../world-venue-map/local-layers") as typeof import("../world-venue-map/local-layers");
22
+ removeFacilities?.();
23
+ removeFacilities = registerLocalGeoLayer(facilitiesLayer);
24
+ },
25
+ dispose() {
26
+ removeFacilities?.();
27
+ removeFacilities = null;
28
+ },
5
29
  panes: [{
6
30
  id: "buildout",
7
31
  reportFreshness: { status: "not-a-feed", basis: "infrastructure research" },
8
32
  name: "TheBuildout",
9
33
  icon: "T",
10
- component: BuildoutPane,
34
+ get component(): typeof import("./pane").BuildoutPane { const { BuildoutPane } = require("./pane"); return BuildoutPane; },
11
35
  defaultPosition: "right",
12
36
  defaultMode: "floating",
13
37
  defaultFloatingSize: { width: 110, height: 34 },
38
+ // The company under the cursor, for linked panes: its flow, a chart, a map of its sites.
39
+ tickerSource: true,
40
+ }, {
41
+ id: BUILDOUT_COMPANY_PANE_ID,
42
+ reportFreshness: { status: "not-a-feed", basis: "disclosed relationships" },
43
+ // Short, since a linked pane's title adds the ticker and what it follows.
44
+ name: "Buildout",
45
+ icon: "T",
46
+ get component(): typeof import("./company/pane").BuildoutCompanyPane { const { BuildoutCompanyPane } = require("./company/pane"); return BuildoutCompanyPane; },
47
+ defaultPosition: "right",
48
+ defaultMode: "floating",
49
+ defaultFloatingSize: { width: 120, height: 32 },
50
+ tickerFollower: true,
51
+ instruments: ["equity"],
52
+ reportOptions: [COMPANY_TAB_OPTION],
14
53
  }],
15
54
  paneTemplates: [{
16
55
  id: "buildout-pane",
17
56
  paneId: "buildout",
18
57
  label: "TheBuildout",
19
- description: "Open TheBuildout infrastructure intelligence.",
20
- keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "sites", "intel"],
58
+ description: "Open TheBuildout infrastructure intelligence: themes, companies, sites and intel.",
59
+ keywords: ["tbo", "buildout", "thebuildout", "infrastructure", "themes", "sites", "intel"],
21
60
  shortcut: { prefix: "TBO" },
22
61
  createInstance: () => ({ placement: "floating" }),
23
- }],
62
+ }, createTickerSurfacePaneTemplate({
63
+ id: "buildout-company-pane",
64
+ paneId: BUILDOUT_COMPANY_PANE_ID,
65
+ label: "TheBuildout Company",
66
+ description: "One company in TheBuildout: its power and hosting deals, suppliers and customers as a flow, its sites, hiring by site and the signals that name it.",
67
+ keywords: ["tboc", "buildout", "thebuildout", "power deals", "data centers", "sites", "suppliers", "customers", "signals"],
68
+ shortcut: "TBOC",
69
+ })],
24
70
  };
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../../api-client";
2
+ import { createTtlCache } from "../../../../data/ttl-cache";
2
3
  import { httpFetch } from "../../../../utils/http-transport";
3
4
  import { logFailure } from "../../../../utils/log-failure";
4
5
  import type {
@@ -119,26 +120,77 @@ export async function loadBuildoutData(token: string | null) {
119
120
  access,
120
121
  token,
121
122
  lists,
122
- companies: { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0 },
123
+ companies: { ...emptyPage<BuildoutCompany>(), blurredCompanyCount: 0, listSlug: null },
123
124
  sites: pageFromItems(sites),
124
125
  intel: pageFromItems(intel),
125
126
  };
126
127
  }
127
128
 
128
- export async function getBuildoutProToken() {
129
- if (!apiClient.isSignedIn()) return null;
129
+ /** What the dataset is read with: a Buildout token, or none for the free set. */
130
+ interface BuildoutGrant {
131
+ token: string | null;
132
+ expiresAtMs: number | null;
133
+ }
130
134
 
131
- try {
132
- const session = await apiClient.getSession();
133
- if (!session) return null;
135
+ /** Whose reads these are: the signed-in account, and its token when it has Buildout. */
136
+ export interface BuildoutReader {
137
+ account: string | null;
138
+ token: string | null;
139
+ }
140
+
141
+ const NO_GRANT: BuildoutGrant = { token: null, expiresAtMs: null };
142
+ /** A token is asked for again this long before it lapses. */
143
+ const TOKEN_RENEW_EARLY_MS = 60_000;
144
+ /** A token whose answer carries no expiry is kept this long. */
145
+ const TOKEN_FALLBACK_MS = 5 * 60_000;
146
+ /** An account without Buildout is asked again after this long, so a new plan shows within a minute. */
147
+ const NO_PLAN_MS = 60_000;
148
+
149
+ /**
150
+ * One account's grant, shared by every pane and the facilities layer: asking
151
+ * costs three account requests (one issues a token), and a linked desk asks
152
+ * on every company the cursor passes.
153
+ */
154
+ const grants = createTtlCache<BuildoutGrant>({
155
+ ttlMs: (grant) => {
156
+ if (!grant.token) return NO_PLAN_MS;
157
+ if (grant.expiresAtMs === null) return TOKEN_FALLBACK_MS;
158
+ return Math.max(0, grant.expiresAtMs - Date.now() - TOKEN_RENEW_EARLY_MS);
159
+ },
160
+ maxEntries: 4,
161
+ });
162
+
163
+ async function issueGrant(checkSession: boolean): Promise<BuildoutGrant> {
164
+ if (checkSession && !(await apiClient.getSession())) return NO_GRANT;
165
+ const account = await apiClient.getBuildoutAccount();
166
+ if (!account?.subscription.active) return NO_GRANT;
167
+ const issued = await apiClient.getBuildoutToken();
168
+ if (!issued?.token) return NO_GRANT;
169
+ const expiresAtMs = Date.parse(issued.expiresAt);
170
+ return { token: issued.token, expiresAtMs: Number.isFinite(expiresAtMs) ? expiresAtMs : null };
171
+ }
134
172
 
135
- const account = await apiClient.getBuildoutAccount();
136
- if (!account?.subscription.active) return null;
173
+ /**
174
+ * The account reading the dataset and its token. A token is reused until a
175
+ * minute before it lapses; `fresh` asks again, for a refresh the user pressed.
176
+ * Signed out there is no account and no token, at once.
177
+ */
178
+ export async function getBuildoutReader({ fresh = false }: { fresh?: boolean } = {}): Promise<BuildoutReader> {
179
+ if (!apiClient.isSignedIn()) return { account: null, token: null };
137
180
 
138
- const token = await apiClient.getBuildoutToken();
139
- return token?.token ?? null;
181
+ try {
182
+ // A user restored from the last run still has its session checked before a token is issued.
183
+ const restored = apiClient.getCurrentUser();
184
+ const user = restored ?? await apiClient.getSession();
185
+ if (!user) return { account: null, token: null };
186
+ const grant = await grants.load(user.id, () => issueGrant(restored !== null), { force: fresh });
187
+ return { account: user.id, token: grant.token };
140
188
  } catch (error) {
141
189
  logFailure("buildout.model", "Check access failed")(error);
142
190
  throw new Error("Access check failed. Access is unknown.");
143
191
  }
144
192
  }
193
+
194
+ export async function getBuildoutProToken(options?: { fresh?: boolean }) {
195
+ return (await getBuildoutReader(options)).token;
196
+ }
@@ -121,6 +121,9 @@ export function normalizeList(raw: RawObject): BuildoutList {
121
121
  avgSectorGrowth: stringField(raw, "avgSectorGrowth", "avg_sector_growth"),
122
122
  avgReturn1y: stringField(raw, "avgReturn1y", "avg_return_1y"),
123
123
  avgMargin: stringField(raw, "avgMargin", "avg_margin"),
124
+ leaders: raw.importanceCounts && typeof raw.importanceCounts === "object"
125
+ ? numberField(raw.importanceCounts as RawObject, "leader")
126
+ : null,
124
127
  };
125
128
  }
126
129
 
@@ -256,6 +259,8 @@ function readCompany(raw: RawObject): BuildoutCompany {
256
259
  logoUrl: stringField(raw, "logoUrl", "logo_url"),
257
260
  researchReport: stringField(raw, "researchReport", "research_report"),
258
261
  listReason: stringField(raw, "listReason", "list_reason"),
262
+ listRole: stringField(raw, "listCategoryRole", "list_category_role"),
263
+ listTier: stringField(raw, "listCategoryImportance", "list_category_importance"),
259
264
  intelligence: arrayField(raw, "intelligence").map((item) => ({
260
265
  publishedAt: stringField(item, "publishedAt", "published_at"),
261
266
  headline: stringField(item, "headline", "title"),
@@ -1,6 +1,6 @@
1
1
  import type { DataTableColumn } from "../../../../components";
2
2
 
3
- export type BuildoutTabId = "companies" | "sites" | "intel";
3
+ export type BuildoutTabId = "themes" | "companies" | "sites" | "intel";
4
4
  export type BuildoutAccess = "free" | "pro";
5
5
 
6
6
  export type RawObject = Record<string, unknown>;
@@ -40,6 +40,8 @@ export type BuildoutList = {
40
40
  avgSectorGrowth?: string | null;
41
41
  avgReturn1y?: string | null;
42
42
  avgMargin?: string | null;
43
+ /** Companies the theme ranks as its leaders. */
44
+ leaders?: number | null;
43
45
  };
44
46
 
45
47
  export type BuildoutCompany = {
@@ -135,6 +137,10 @@ export type BuildoutCompany = {
135
137
  logoUrl?: string | null;
136
138
  researchReport?: string | null;
137
139
  listReason?: string | null;
140
+ /** Its part in the open theme: core vendor, integrator... */
141
+ listRole?: string | null;
142
+ /** How much it matters to the open theme: leader, major, notable, watchlist. */
143
+ listTier?: string | null;
138
144
  intelligence?: Array<{
139
145
  publishedAt?: string | null;
140
146
  headline?: string | null;
@@ -267,7 +273,8 @@ export type BuildoutLoadState =
267
273
  access: BuildoutAccess;
268
274
  token: string | null;
269
275
  lists: BuildoutList[];
270
- companies: BuildoutPagedState<BuildoutCompany> & { blurredCompanyCount: number };
276
+ /** The companies of one list: `listSlug`, or none yet. */
277
+ companies: BuildoutPagedState<BuildoutCompany> & { blurredCompanyCount: number; listSlug: string | null };
271
278
  sites: BuildoutPagedState<BuildoutSite>;
272
279
  intel: BuildoutPagedState<BuildoutUpdate>;
273
280
  loadedAt: number;
@@ -299,6 +306,8 @@ export type BuildoutColumnId =
299
306
  | "dividendYield"
300
307
  | "return1y"
301
308
  | "employees"
309
+ | "listRole"
310
+ | "listTier"
302
311
  | "site"
303
312
  | "type"
304
313
  | "owner"