@gloom-sh/terminal 0.19.2 → 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 (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -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 +20 -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.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -26,6 +26,8 @@ import {
26
26
  type DataTableCell,
27
27
  type PageRequest,
28
28
  type PaneHint,
29
+ type QueryBarFilter,
30
+ TickerBadgeList,
29
31
  } from "../../../components";
30
32
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
31
33
  import {
@@ -36,13 +38,17 @@ import {
36
38
  usePluginPaneState,
37
39
  } from "../../../public/react";
38
40
  import { relativeLuminance } from "../../../theme/color-utils";
41
+ import { displayWidth } from "../../../utils/format";
42
+ import { truncateWithEllipsis } from "../../../utils/text-wrap";
39
43
  import { useThemeColors } from "../../../theme/theme-context";
40
44
  import type { PaneProps } from "../../../types/plugin";
41
45
  import { Box, Text, TextAttributes, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
42
46
  import { formatGeoSeriesExpression } from "../chart-composer/series-expression";
43
47
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
44
48
  import { useOpenDescription } from "../shared/open-description";
45
- import { ENTITY_PAGE_SIZE, loadEntityDetail, loadEntityPage, loadWorldVenues } from "./client";
49
+ import { ENTITY_PAGE_SIZE, loadEntityDetail, loadEntityPage, loadWorldVenues, scopedGeoRequest } from "./client";
50
+ import { isLocalGeoLayer } from "./local-layers";
51
+ import { useLinkedGeoScope } from "./scope";
46
52
  import { EntityDetail } from "./entity-detail";
47
53
  import { useWorldMapEvidence } from "./evidence";
48
54
  import { entityCellText, entityColumns, TICKER_COLUMN_ID, type EntityColumn } from "./entity-table";
@@ -52,6 +58,7 @@ import { WORLD_VENUE_MAP_PANE_ID } from "./ids";
52
58
  import {
53
59
  entityOpenZoom,
54
60
  entityTableBbox,
61
+ framePlaces,
55
62
  featureAnchor,
56
63
  formatGeoValue,
57
64
  formatTickerLinks,
@@ -162,7 +169,7 @@ const SelectedEntityHeader = memo(function SelectedEntityHeader({
162
169
  return (
163
170
  <Box flexDirection="column" height={2} width={width} paddingX={1}>
164
171
  <Box flexDirection="row" justifyContent="space-between" width="100%">
165
- <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{row.label}</Text>
172
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{truncateWithEllipsis(row.label, Math.max(1, width - 3 - displayWidth(layer.name)))}</Text>
166
173
  <Text fg={color}>{layer.name}</Text>
167
174
  </Box>
168
175
  <Text fg={colors.textMuted}>{meta}</Text>
@@ -193,7 +200,11 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
193
200
  setView((current) => sameGeoView(current, next) ? current : next);
194
201
  }, []);
195
202
  const canAccess = useCallback((layer: GeoLayerInfo) => layer.access !== "pro" || hasProAccess, [hasProAccess]);
196
- const states = useGeoLayerFeed(layers, view, visible, { canAccess });
203
+ // A linked map narrows the app's own layers to what its source pane selects.
204
+ const linkedScope = useLinkedGeoScope(layers.some((layer) => isLocalGeoLayer(layer.id)));
205
+ const scope = linkedScope.scope;
206
+ const request = useMemo(() => scopedGeoRequest(scope?.key ?? null), [scope?.key]);
207
+ const states = useGeoLayerFeed(layers, view, visible, { canAccess, request });
197
208
 
198
209
  // A layer coloured by class (ships) takes the first colour, so its own never meets the class colours.
199
210
  const colorOrder = useMemo(
@@ -244,7 +255,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
244
255
  ...(sort.key ? { sort: sort.key, dir: sort.dir } : {}),
245
256
  ...(tableBbox ? { bbox: tableBbox } : {}),
246
257
  offset,
247
- }, signal);
258
+ }, signal, request);
248
259
  const rows = page.rows ?? [];
249
260
  const total = typeof page.total === "number" ? page.total : null;
250
261
  return {
@@ -252,18 +263,28 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
252
263
  rows,
253
264
  hasMore: rows.length >= ENTITY_PAGE_SIZE && (total === null || offset + rows.length < total),
254
265
  nextOffset: offset + rows.length,
266
+ scopeKey: scope?.key ?? null,
255
267
  };
256
- }, [query, sort.dir, sort.key, tableBboxKey, tableLayer.id]);
268
+ }, [query, request, scope?.key, sort.dir, sort.key, tableBboxKey, tableLayer.id]);
257
269
  const pages = usePagedRows(tableReadable ? loadPage : null, { getId: (row) => row.id });
258
270
  const rows = pages.rows;
271
+ // A linked map frames the places of each company or theme it moves to, once.
272
+ const framedScopeRef = useRef<string | null>(null);
273
+ const firstPage = pages.pages[0];
274
+ useEffect(() => {
275
+ if (!firstPage?.scopeKey || !isLocalGeoLayer(tableLayer.id) || framedScopeRef.current === firstPage.scopeKey) return;
276
+ framedScopeRef.current = firstPage.scopeKey;
277
+ const frame = framePlaces(firstPage.rows);
278
+ if (frame) setFocus({ key: `scope:${firstPage.scopeKey}`, ...frame, exact: true });
279
+ }, [firstPage, tableLayer.id]);
259
280
  useWorldMapEvidence(tokens, states, rows.length, pages.loading);
260
281
  const refresh = useCallback(() => pages.reload(), [pages.reload]);
261
282
  usePaneRefreshKey(refresh, { focused: focused && !searchFocused });
262
283
 
263
284
  const detailOpen = !!open && open.layer === tableLayer.id;
264
285
  const detailLoader = useMemo(
265
- () => (detailOpen && open ? () => loadEntityDetail(open.layer, open.id) : null),
266
- [detailOpen, open?.id, open?.layer],
286
+ () => (detailOpen && open ? () => loadEntityDetail(open.layer, open.id, undefined, request) : null),
287
+ [detailOpen, open?.id, open?.layer, request],
267
288
  );
268
289
  const detail = useAsyncResource(detailLoader);
269
290
  const openRow = detailOpen
@@ -412,9 +433,29 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
412
433
 
413
434
  const onScroll = useTableLoadMore(scrollRef, pages.hasMore && !detailOpen, pages.loadMore);
414
435
  // A layer that cannot serve has nothing to search and, alone, nothing to switch to: no bar at all.
436
+ // What a linked map shows, before the search: a switch between the company and its theme when the
437
+ // source has both, else the one it has.
438
+ const scoped = !!scope && isLocalGeoLayer(tableLayer.id);
439
+ const { company: scopeCompany, theme: scopeTheme } = linkedScope;
440
+ const scopeFilter: QueryBarFilter | null = scoped && scopeCompany && scopeTheme ? {
441
+ id: "scope",
442
+ label: "Show",
443
+ inline: true,
444
+ value: linkedScope.mode,
445
+ options: [
446
+ { value: "company", label: scopeCompany.label },
447
+ { value: "theme", label: "Theme", description: scopeTheme.label },
448
+ ],
449
+ onChange: linkedScope.setMode,
450
+ } : null;
451
+ const scopeLeading = !scoped || scopeFilter ? undefined
452
+ : scopeCompany ? <TickerBadgeList symbols={[scopeCompany.label]} width={displayWidth(scopeCompany.label) + 2} listed />
453
+ : <Text fg={colors.textDim}>{truncateWithEllipsis(scope.label, Math.max(8, Math.floor(listWidth / 3)))}</Text>;
415
454
  const queryBar = !tableReadable && layers.length < 2 ? undefined : (
416
455
  <QueryBar
417
456
  width={listWidth}
457
+ leading={scopeLeading}
458
+ filters={scopeFilter ? [scopeFilter] : undefined}
418
459
  search={tableReadable ? {
419
460
  value: query,
420
461
  onChange: setQuery,
@@ -142,6 +142,7 @@ export const MAP_PRESET_OPTIONS = [
142
142
  { value: "ports", label: "Ports" },
143
143
  { value: "energy", label: "Pipelines, fields and terminals" },
144
144
  { value: "air", label: "Airports and flights" },
145
+ { value: "facilities", label: "AI buildout facilities" },
145
146
  ] as const;
146
147
 
147
148
  function shortDate(iso: string): string | null {
@@ -329,6 +330,22 @@ export function entityOpenZoom(layer: Pick<GeoLayerInfo, "geometry" | "minZoom">
329
330
  return layer.minZoom === undefined ? base : Math.max(base, 2 ** layer.minZoom);
330
331
  }
331
332
 
333
+ /** Below this span a frame is a region, not a street: one facility shows its surroundings. */
334
+ const FRAME_MIN_SPAN_DEGREES = 6;
335
+ const FRAME_MAX_ZOOM = 8;
336
+
337
+ /** The centre and zoom that frame a set of places (a linked company's facilities); null without one. */
338
+ export function framePlaces(places: readonly { lon: number; lat: number }[]): { longitude: number; latitude: number; zoom: number } | null {
339
+ const located = places.filter((place) => Number.isFinite(place.lon) && Number.isFinite(place.lat));
340
+ if (!located.length) return null;
341
+ const lons = located.map((place) => place.lon);
342
+ const lats = located.map((place) => place.lat);
343
+ const west = Math.min(...lons), east = Math.max(...lons), south = Math.min(...lats), north = Math.max(...lats);
344
+ // The map is about twice as wide as tall, so latitude counts double; a margin keeps edge markers inside.
345
+ const span = Math.max(east - west, (north - south) * 2, FRAME_MIN_SPAN_DEGREES) * 1.5;
346
+ return { longitude: (west + east) / 2, latitude: (south + north) / 2, zoom: Math.min(FRAME_MAX_ZOOM, Math.max(1, 360 / span)) };
347
+ }
348
+
332
349
  /**
333
350
  * The area a layer's table reads. A layer drawn only from a zoom (live
334
351
  * flights) lists what the map shows once it is that close; further out, and
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Layers the app serves itself, beside the server's catalog. A plugin whose
3
+ * rows have a place (TheBuildout's facilities) registers one, and the map,
4
+ * its table and its detail read it through the same wire contract as a server
5
+ * layer: the catalog lists it, and its feature, entity and detail requests are
6
+ * answered in the app without reaching Gloom Cloud. A scope (the company or
7
+ * theme the pane follows) narrows what a local layer answers; server layers
8
+ * never see it.
9
+ */
10
+ import {
11
+ resetGeoCatalogCache,
12
+ type GeoBbox,
13
+ type GeoEntitiesPayload,
14
+ type GeoEntityPayload,
15
+ type GeoFeaturesPayload,
16
+ type GeoLayerInfo,
17
+ type GeoLayersPayload,
18
+ type GeoRequest,
19
+ } from "../../../api-client/geo";
20
+ import { logFailure } from "../../../utils/log-failure";
21
+
22
+ /** What a linked map narrows a local layer to: `company:CRWV`, `theme:<slug>`. */
23
+ export interface GeoScope {
24
+ key: string;
25
+ /** The words the pane shows for it: a ticker or a theme's name. */
26
+ label: string;
27
+ }
28
+
29
+ export interface LocalFeatureQuery {
30
+ bbox: GeoBbox | null;
31
+ zoom: number;
32
+ limit: number;
33
+ scope: string | null;
34
+ }
35
+
36
+ export interface LocalEntityQuery {
37
+ q: string;
38
+ sort: string | null;
39
+ dir: "asc" | "desc";
40
+ limit: number;
41
+ offset: number;
42
+ bbox: GeoBbox | null;
43
+ scope: string | null;
44
+ }
45
+
46
+ export interface LocalGeoLayer {
47
+ info: GeoLayerInfo;
48
+ features: (query: LocalFeatureQuery, signal?: AbortSignal) => Promise<GeoFeaturesPayload>;
49
+ entities: (query: LocalEntityQuery, signal?: AbortSignal) => Promise<GeoEntitiesPayload>;
50
+ entity: (id: string, scope: string | null, signal?: AbortSignal) => Promise<GeoEntityPayload>;
51
+ }
52
+
53
+ const localLayers = new Map<string, LocalGeoLayer>();
54
+
55
+ /** Adds a layer to every map's catalog until the returned function removes it. */
56
+ export function registerLocalGeoLayer(layer: LocalGeoLayer): () => void {
57
+ localLayers.set(layer.info.id, layer);
58
+ // A catalog read before the layer existed would hide it for minutes.
59
+ resetGeoCatalogCache();
60
+ return () => {
61
+ if (localLayers.get(layer.info.id) !== layer) return;
62
+ localLayers.delete(layer.info.id);
63
+ resetGeoCatalogCache();
64
+ };
65
+ }
66
+
67
+ export function isLocalGeoLayer(layerId: string): boolean {
68
+ return localLayers.has(layerId);
69
+ }
70
+
71
+ function bboxParam(value: string | null): GeoBbox | null {
72
+ if (!value) return null;
73
+ const parts = value.split(",").map(Number);
74
+ return parts.length === 4 && parts.every(Number.isFinite) ? parts as GeoBbox : null;
75
+ }
76
+
77
+ function numberParam(value: string | null, fallback: number): number {
78
+ const parsed = value === null ? Number.NaN : Number(value);
79
+ return Number.isFinite(parsed) ? parsed : fallback;
80
+ }
81
+
82
+ /** The server's catalog with the local layers after it; a server without one still lists them. */
83
+ async function mergedCatalog(request: GeoRequest, init?: RequestInit): Promise<GeoLayersPayload> {
84
+ const local = [...localLayers.values()].map((layer) => layer.info);
85
+ const server = await request<GeoLayersPayload>("layers", init).catch((error: unknown) => {
86
+ if (!local.length) throw error;
87
+ logFailure("cloud-geo", "Geo catalog request failed")(error);
88
+ return null;
89
+ });
90
+ const serverLayers = Array.isArray(server?.layers) ? server.layers : [];
91
+ const ids = new Set(serverLayers.map((layer) => layer?.id));
92
+ return { layers: [...serverLayers, ...local.filter((layer) => !ids.has(layer.id))], asOf: server?.asOf ?? null };
93
+ }
94
+
95
+ /** A path segment as written when it is not valid percent-encoding (a stray `%` in an entity id). */
96
+ function decodeSegment(segment: string): string {
97
+ try {
98
+ return decodeURIComponent(segment);
99
+ } catch {
100
+ // Intentional ignore: a segment that does not decode is matched as it was written.
101
+ return segment;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * A request that answers the local layers itself and sends everything else on.
107
+ * `scope` narrows the local layers' answers; a new scope is a new request
108
+ * function, so the map's feed and table ask again.
109
+ */
110
+ export function withLocalGeoLayers(request: GeoRequest, scope: string | null = null): GeoRequest {
111
+ return (<T>(path: string, init?: RequestInit): Promise<T> => {
112
+ const [route = "", search = ""] = path.split("?");
113
+ const params = new URLSearchParams(search);
114
+ const signal = init?.signal ?? undefined;
115
+ if (route === "layers") return (localLayers.size ? mergedCatalog(request, init) : request(path, init)) as Promise<T>;
116
+ const parts = route.split("/").map(decodeSegment);
117
+ const layer = parts.length >= 3 ? localLayers.get(parts[1]!) ?? null : null;
118
+ if (parts[0] === "layers" && layer && parts[2] === "features") {
119
+ return layer.features({
120
+ bbox: bboxParam(params.get("bbox")),
121
+ zoom: numberParam(params.get("zoom"), 0),
122
+ limit: numberParam(params.get("limit"), 1500),
123
+ scope,
124
+ }, signal) as Promise<T>;
125
+ }
126
+ if (parts[0] === "layers" && layer && parts[2] === "entities") {
127
+ return layer.entities({
128
+ q: params.get("q") ?? "",
129
+ sort: params.get("sort"),
130
+ dir: params.get("dir") === "asc" ? "asc" : "desc",
131
+ limit: numberParam(params.get("limit"), 100),
132
+ offset: numberParam(params.get("offset"), 0),
133
+ bbox: bboxParam(params.get("bbox")),
134
+ scope,
135
+ }, signal) as Promise<T>;
136
+ }
137
+ const entityLayer = parts[0] === "entities" ? localLayers.get(parts[1] ?? "") : undefined;
138
+ if (entityLayer && parts[2]) return entityLayer.entity(parts.slice(2).join("/"), scope, signal) as Promise<T>;
139
+ return request<T>(path, init);
140
+ }) as GeoRequest;
141
+ }
@@ -18,6 +18,9 @@ export type MapSymbolId =
18
18
  | "terminal"
19
19
  | "field"
20
20
  | "chokepoint"
21
+ | "rack"
22
+ | "bolt"
23
+ | "chip"
21
24
  | "dot";
22
25
 
23
26
  export interface MapSymbol {
@@ -85,6 +88,17 @@ export const MAP_SYMBOLS: Record<MapSymbolId, MapSymbol> = {
85
88
  landmark: true,
86
89
  cell: "◆",
87
90
  },
91
+ // A server rack: the cabinet and its shelves, for a data center.
92
+ rack: { d: "M-5 -7.2H5V7.2H-5ZM-5 -2.4H5M-5 2.4H5M-2.6 -4.8H-0.6M-2.6 0H-0.6M-2.6 4.8H-0.6", paint: "stroke", size: 13, cell: "▤" },
93
+ // A lightning bolt, for a power plant or substation.
94
+ bolt: { d: "M1.8 -8L-5.4 1.4H-0.6L-2 8L5.4 -1.8H0.6Z", paint: "fill", size: 13, cell: "↯" },
95
+ // A chip and its pins, for a fab or a packaging plant.
96
+ chip: {
97
+ d: "M-4.6 -4.6H4.6V4.6H-4.6ZM-2 -4.6V-7.6M2 -4.6V-7.6M-2 4.6V7.6M2 4.6V7.6M-4.6 -2H-7.6M-4.6 2H-7.6M4.6 -2H7.6M4.6 2H7.6",
98
+ paint: "stroke",
99
+ size: 13,
100
+ cell: "▣",
101
+ },
88
102
  dot: { d: "M0 -3.4A3.4 3.4 0 1 1 0 3.4A3.4 3.4 0 1 1 0 -3.4Z", paint: "fill", size: 11, cell: "•" },
89
103
  };
90
104
 
@@ -162,6 +176,10 @@ const RULES: Record<string, SymbolRule> = {
162
176
  },
163
177
  terminal: () => fixed("terminal"),
164
178
  field: () => fixed("field"),
179
+ // Facilities (TheBuildout): by what the site is; anything else is a dot.
180
+ "data-center": () => fixed("rack"),
181
+ "power-plant": () => fixed("bolt"),
182
+ fab: () => fixed("chip"),
165
183
  };
166
184
 
167
185
  /** The layer registry: layer ids the app knows, then the kinds their entities carry. */
@@ -43,6 +43,8 @@ export interface WorldMapFocus {
43
43
  longitude: number;
44
44
  latitude: number;
45
45
  zoom?: number;
46
+ /** Exactly at `zoom`, out as well as in: a frame around a set of places. */
47
+ exact?: boolean;
46
48
  }
47
49
 
48
50
  interface WorldVenueMapProps {
@@ -690,8 +692,8 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
690
692
  if (!focus || !sized || appliedFocusRef.current === focus.key) return;
691
693
  appliedFocusRef.current = focus.key;
692
694
  setViewport((current) => {
693
- const zoom = Math.max(current.zoom, focus.zoom ?? 1);
694
- if (zoom <= 1) return current;
695
+ const zoom = focus.exact ? Math.max(1, focus.zoom ?? 1) : Math.max(current.zoom, focus.zoom ?? 1);
696
+ if (zoom <= 1 && !focus.exact) return current;
695
697
  return clampWorldMapViewport({ zoom, centerLongitude: focus.longitude, centerLatitude: focus.latitude }, props.width, plotHeight, 1, maxZoom);
696
698
  });
697
699
  }, [focusKey, sized]);
@@ -0,0 +1,63 @@
1
+ import { useMemo } from "react";
2
+ import { useAppSelector, usePaneInstance } from "../../../state/app/context";
3
+ import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
4
+ import { usePluginPaneState } from "../../../public/react";
5
+ import type { PaneInstanceConfig } from "../../../types/config";
6
+ import { parseLayerTokens, LAYERS_SETTING_KEY } from "./layers";
7
+ import { isLocalGeoLayer, type GeoScope } from "./local-layers";
8
+
9
+ /**
10
+ * Pane state a ticker source may publish beside `cursorSymbol`: the group the
11
+ * selected company belongs to, which a linked map can widen to. TheBuildout
12
+ * publishes the theme its table lists.
13
+ */
14
+ export const GEO_SCOPE_STATE_KEY = "geoScope";
15
+
16
+ type GeoScopeMode = "company" | "theme";
17
+
18
+ function publishedScope(value: unknown): GeoScope | null {
19
+ if (!value || typeof value !== "object") return null;
20
+ const { key, label } = value as Partial<GeoScope>;
21
+ return typeof key === "string" && key && typeof label === "string" ? { key, label } : null;
22
+ }
23
+
24
+ /** A map with one of the app's own layers on can be linked to a list's or a scanner's selection. */
25
+ export function mapFollowsTicker(pane: PaneInstanceConfig): boolean {
26
+ return parseLayerTokens(pane.settings?.[LAYERS_SETTING_KEY]).some(isLocalGeoLayer);
27
+ }
28
+
29
+ export interface LinkedGeoScope {
30
+ /** What the map's own layers show; null shows everything. */
31
+ scope: GeoScope | null;
32
+ /** The company the link resolves to. */
33
+ company: GeoScope | null;
34
+ /** The group its source publishes for that company. */
35
+ theme: GeoScope | null;
36
+ mode: GeoScopeMode;
37
+ setMode: (mode: GeoScopeMode) => void;
38
+ }
39
+
40
+ /**
41
+ * What a linked map narrows its own layers to: the company the link resolves
42
+ * to, or the theme its source publishes once the pane is switched to it (and
43
+ * whenever there is no company). An unlinked map has neither and shows
44
+ * everything.
45
+ */
46
+ export function useLinkedGeoScope(enabled: boolean): LinkedGeoScope {
47
+ const instance = usePaneInstance();
48
+ const linked = enabled && (instance?.binding?.kind === "follow" || instance?.binding?.kind === "fixed");
49
+ const sourceId = linked && instance?.binding?.kind === "follow" ? instance.binding.sourceInstanceId : null;
50
+ const published = useAppSelector((state) => (sourceId ? state.paneState[sourceId]?.[GEO_SCOPE_STATE_KEY] ?? null : null));
51
+ const { symbol } = usePaneTickerIdentity();
52
+ const [mode, setMode] = usePluginPaneState<GeoScopeMode>("geoScopeMode", "company");
53
+ const theme = linked ? publishedScope(published) : null;
54
+ const companySymbol = linked ? symbol : null;
55
+ const themeKey = theme?.key ?? null;
56
+ const themeLabel = theme?.label ?? "";
57
+ return useMemo(() => {
58
+ const company = companySymbol ? { key: `company:${companySymbol}`, label: companySymbol } : null;
59
+ const group = themeKey ? { key: themeKey, label: themeLabel } : null;
60
+ const scope = mode === "theme" ? group ?? company : company ?? group;
61
+ return { scope, company, theme: group, mode, setMode };
62
+ }, [companySymbol, mode, setMode, themeKey, themeLabel]);
63
+ }
@@ -44,7 +44,12 @@ export function curveOption(id: CurveId): CurveOption {
44
44
  return CURVE_OPTIONS.find((option) => option.id === id) ?? CURVE_OPTIONS[0]!;
45
45
  }
46
46
 
47
- /** `GC`, `GC 2026-01-02`, `GC JGB`, `GC TIPS 2024-01-02`: a curve word and a date, either order. */
47
+ /**
48
+ * `GC`, `GC 2026-01-02`, `GC JGB`, `GC TIPS 2024-01-02`: a curve word and a
49
+ * date, either order. Words with no digit are no date: one that names no
50
+ * curve (`GC world`, `GC "South Africa"`) lists the curves and points at the
51
+ * world tab, which has the other countries' government yields.
52
+ */
48
53
  export function parseCurveArgument(value: unknown, now = new Date()): { curve: CurveId | null; date: string } {
49
54
  const words = typeof value === "string" ? value.trim().split(/\s+/).filter(Boolean) : [];
50
55
  let curve: CurveId | null = null;
@@ -54,7 +59,16 @@ export function parseCurveArgument(value: unknown, now = new Date()): { curve: C
54
59
  if (match && !curve) curve = match.id;
55
60
  else rest.push(word);
56
61
  }
57
- return { curve, date: yieldCurveDate(rest.join(" ") || null, now) };
62
+ const text = rest.join(" ");
63
+ if (text && !/\d/.test(text) && text.toLowerCase() !== "latest") throw new Error(unknownCurveMessage(text));
64
+ return { curve, date: yieldCurveDate(text || null, now) };
65
+ }
66
+
67
+ /** `Unknown curve "world"`, with the curves there are and where the rest of the world's yields are. */
68
+ export function unknownCurveMessage(word: string): string {
69
+ const names = CURVE_OPTIONS.map((option) => option.aliases[0]);
70
+ return `Unknown curve "${word}"; the curves are ${names.slice(0, -1).join(", ")} and ${names.at(-1)}. `
71
+ + "Other countries' government bond yields are on the world tab: gloom fn GC --tab world.";
58
72
  }
59
73
 
60
74
  /**
@@ -3,8 +3,8 @@ import type {
3
3
  HeadlessPaneDefinition,
4
4
  } from "../../../types/plugin";
5
5
  import {
6
- compareDate, curveCutoff, CURVE_OPTIONS, curveIdOf, curveOption, curveSpreadFigures, curveTenorMove, loadComparePoints,
7
- parseCompareInput, parseCurveArgument, type CurveData, type CurveId,
6
+ compareDate, curveCutoff, curveIdOf, curveOption, curveSpreadFigures, curveTenorMove, loadComparePoints,
7
+ parseCompareInput, parseCurveArgument, unknownCurveMessage, type CurveData, type CurveId,
8
8
  } from "./curves";
9
9
  import { yieldTenorRows, type YieldLookbackCurves } from "./chart";
10
10
  import { curveAsOf, type YieldPoint, yieldCurveErrors } from "./treasury-data";
@@ -194,7 +194,7 @@ export function createYieldCurveHeadless(
194
194
  const argument = parseCurveArgument(args.argument);
195
195
  const optionCurve = args.options.curve == null || args.options.curve === "" ? null : curveIdOf(args.options.curve);
196
196
  if (args.options.curve && !optionCurve) {
197
- throw new Error(`Unknown curve ${String(args.options.curve)}; use ${CURVE_OPTIONS.map((option) => option.aliases[0]).join(", ")}.`);
197
+ throw new Error(unknownCurveMessage(String(args.options.curve)));
198
198
  }
199
199
  const curve = optionCurve ?? argument.curve ?? "us";
200
200
  const requestedDate = args.options.date != null ? yieldCurveDate(args.options.date, new Date(), "--date") : argument.date;
@@ -1,10 +1,16 @@
1
1
  import { mkdir } from "fs/promises";
2
- import { join } from "path";
2
+ import { basename, join } from "path";
3
3
 
4
4
  import { explainPluginLoadError } from "./compat";
5
5
  import { hostPublicModules } from "./host-link";
6
6
  import { resolvePluginBrowserEntry, resolvePluginEntry } from "./loader";
7
+ import { siblingImportsBundlerPlugin } from "./peer-imports";
7
8
  import { PLUGIN_HOST_GLOBAL, SHARED_SPECIFIERS, importPluginHostModule } from "./host-modules";
9
+ import {
10
+ PLUGIN_PROCESS_IDENTIFIER,
11
+ pluginProcessDeclaration,
12
+ pluginProcessModuleSource,
13
+ } from "./plugin-process";
8
14
 
9
15
  /**
10
16
  * Compiles an external plugin for a renderer that cannot read the filesystem.
@@ -90,6 +96,7 @@ export interface BundlePluginResult {
90
96
  function createSharedModuleResolver(
91
97
  exportNamesFor: (specifier: string) => Promise<readonly string[]>,
92
98
  onShared?: (specifier: string) => void,
99
+ options: { providesProcess?: boolean } = {},
93
100
  ): import("bun").BunPlugin {
94
101
  const namespace = "gloom-host";
95
102
  const shared = new Set<string>(SHARED_SPECIFIERS);
@@ -118,6 +125,19 @@ function createSharedModuleResolver(
118
125
  contents: buildSharedModuleSource(args.path, await exportNamesFor(args.path)),
119
126
  loader: "js",
120
127
  }));
128
+
129
+ if (!options.providesProcess) return;
130
+ // `import process from "node:process"` reads the same shim as the bare
131
+ // global. Bun's own browser stand-in has no default export, so without
132
+ // this such a plugin did not compile for the desktop at all.
133
+ build.onResolve({ filter: /^(?:node:)?process$/ }, () => ({
134
+ path: "process",
135
+ namespace: "gloom-process",
136
+ }));
137
+ build.onLoad({ filter: /.*/, namespace: "gloom-process" }, () => ({
138
+ contents: pluginProcessModuleSource(),
139
+ loader: "js",
140
+ }));
121
141
  },
122
142
  };
123
143
  }
@@ -144,6 +164,11 @@ export async function bundleExternalPlugin(
144
164
  */
145
165
  minify?: boolean;
146
166
  define?: Record<string, string>;
167
+ /**
168
+ * Named in the errors the browser's `process` stand-in throws. Defaults to
169
+ * the plugin's folder name.
170
+ */
171
+ pluginName?: string;
147
172
  /**
148
173
  * `bun` compiles the native entry the terminal imports instead. Only the
149
174
  * registry check uses it, to cover the native half of a plugin that has
@@ -161,7 +186,16 @@ export async function bundleExternalPlugin(
161
186
 
162
187
  await mkdir(outDir, { recursive: true });
163
188
 
189
+ // The browser has no `process`, which the terminal gives every plugin. Each
190
+ // bundle gets its own stand-in (see plugin-process.ts); a bundle compiled
191
+ // for Bun runs where the real one exists.
192
+ const providesProcess = (options.target ?? "browser") === "browser";
193
+ const pluginName = options.pluginName ?? basename(pluginDir);
194
+
164
195
  const shared = new Set<string>();
196
+ // A sibling plugin it imports is compiled from the sibling's folder, as
197
+ // the loader imports it, not through the plugin's link (peer-imports.ts).
198
+ const siblings = siblingImportsBundlerPlugin(pluginDir);
165
199
  const result = await Bun.build({
166
200
  entrypoints: [entry],
167
201
  outdir: outDir,
@@ -174,12 +208,19 @@ export async function bundleExternalPlugin(
174
208
  // `errors`. Returning it lets the check below report what actually broke,
175
209
  // such as an import this host version does not export.
176
210
  throw: false,
177
- ...(options.define ? { define: options.define } : {}),
211
+ // The more specific `process.env.NODE_ENV` a caller defines still wins,
212
+ // so dead branches keyed on it stay dropped.
213
+ define: {
214
+ ...(providesProcess ? { process: PLUGIN_PROCESS_IDENTIFIER } : {}),
215
+ ...options.define,
216
+ },
178
217
  plugins: [
179
218
  createSharedModuleResolver(
180
219
  options.exportNamesFor ?? hostExportNames,
181
220
  (specifier) => shared.add(specifier),
221
+ { providesProcess },
182
222
  ),
223
+ ...(siblings ? [siblings] : []),
183
224
  ],
184
225
  });
185
226
 
@@ -188,6 +229,15 @@ export async function bundleExternalPlugin(
188
229
  const output = result.outputs.find((entryOutput) => entryOutput.kind === "entry-point");
189
230
  if (!output) throw new Error(`Bundling ${pluginDir} produced no entry point`);
190
231
 
232
+ // Only a bundle that reads `process` carries the stand-in, so the rest of
233
+ // the plugins the web app serves stay as small as they were.
234
+ if (providesProcess) {
235
+ const code = await Bun.file(output.path).text();
236
+ if (code.includes(PLUGIN_PROCESS_IDENTIFIER)) {
237
+ await Bun.write(output.path, pluginProcessDeclaration(pluginName) + code);
238
+ }
239
+ }
240
+
191
241
  return {
192
242
  outputPath: output.path,
193
243
  shared: [...shared].sort(),
@@ -43,7 +43,7 @@ import { ipoCalendarPlugin } from "./builtin/ipo-calendar";
43
43
  import { clinicalTrialsPlugin } from "./builtin/clinical-trials";
44
44
  import { commentLettersPlugin } from "./builtin/comment-letters";
45
45
  import { openFdaPlugin } from "./builtin/openfda";
46
- import { browserGloomberbCloudPlugin } from "./builtin/cloud/browser";
46
+ import { gloomberbCloudPlugin } from "./builtin/cloud";
47
47
  import { changelogModule } from "./builtin/changelog";
48
48
  import { roadmapModule } from "./builtin/roadmap";
49
49
  import { chartComposerModule } from "./builtin/chart-composer";
@@ -298,7 +298,7 @@ const browserEarningsPlugin = composeBuiltinPlugin({
298
298
  * browser-safe public API are absent rather than registered behind stubs.
299
299
  */
300
300
  export const browserBuiltinPlugins: readonly GloomPlugin[] = [
301
- browserGloomberbCloudPlugin,
301
+ gloomberbCloudPlugin,
302
302
  browserPortfolioPlugin,
303
303
  browserTickerCorePlugin,
304
304
  browserOptionsVolatilityPlugin,
@@ -0,0 +1,20 @@
1
+ import type { CliCommandDef } from "../types/plugin";
2
+ import { fearGreedPluginMeta, newsPluginMeta, notesPluginMeta, portfolioPluginMeta } from "./builtin/builtin-plugin-meta";
3
+ import { ALERTS_PLUGIN_ID } from "./builtin/alerts/constants";
4
+ import { alertsCliCommand } from "./builtin/alerts/cli";
5
+ import { fearGreedCliCommand } from "./builtin/fear-greed/cli";
6
+ import { rssCliCommand } from "./builtin/news/wire/rss/cli";
7
+ import { notesCliCommand } from "./builtin/notes/cli";
8
+ import { portfolioCliCommand } from "./builtin/portfolio-list/cli/portfolio-command";
9
+ import { watchlistCliCommand } from "./builtin/portfolio-list/cli/watchlist-command";
10
+
11
+ /** Command definitions shared with the plugins, without loading their pane components. */
12
+ export function getBuiltinCliCommands(): { pluginId: string; commands: CliCommandDef[] }[] {
13
+ return [
14
+ { pluginId: portfolioPluginMeta.id, commands: [portfolioCliCommand, watchlistCliCommand] },
15
+ { pluginId: newsPluginMeta.id, commands: [rssCliCommand] },
16
+ { pluginId: notesPluginMeta.id, commands: [notesCliCommand] },
17
+ { pluginId: fearGreedPluginMeta.id, commands: [fearGreedCliCommand] },
18
+ { pluginId: ALERTS_PLUGIN_ID, commands: [alertsCliCommand] },
19
+ ];
20
+ }
@@ -225,6 +225,8 @@ export interface PeerLinkState {
225
225
  target: string | null;
226
226
  /** The link resolves to the sibling's folder. */
227
227
  linked: boolean;
228
+ /** Why reading through the link failed, when it did: it can be in place and still not lead anywhere. */
229
+ resolveError?: string;
228
230
  /** Links this process wrote at that path, and when it last did. */
229
231
  writes: number;
230
232
  lastWriteAt?: number;
@@ -272,11 +274,21 @@ export function describePeerLink(pluginDir: string, peer: string, pluginsDir: st
272
274
  link = stats.isSymbolicLink() ? (process.platform === "win32" ? "junction" : "symlink") : "file";
273
275
  }
274
276
  const isLinkType = link === "junction" || link === "symlink";
277
+ const linked = directory !== null && alreadyLinked(linkPath, join(pluginsDir, directory));
278
+ let resolveError: string | undefined;
279
+ if (isLinkType && !linked) {
280
+ try {
281
+ statSync(linkPath);
282
+ } catch (error) {
283
+ resolveError = errorCode(error);
284
+ }
285
+ }
275
286
  return {
276
287
  ...base,
277
288
  link,
278
289
  target: isLinkType ? describeTarget(linkPath, pluginsDir) : null,
279
- linked: directory !== null && alreadyLinked(linkPath, join(pluginsDir, directory)),
290
+ linked,
291
+ ...(resolveError ? { resolveError } : {}),
280
292
  };
281
293
  }
282
294