gloomberb 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
@@ -1,13 +1,12 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
3
  import { backtestHeadless } from "./headless";
4
- import { BacktestPane } from "./pane";
5
4
  import { BACKTEST_PRESETS, LOOKBACK_OPTIONS } from "./presets";
6
5
  import { BACKTEST_FRESHNESS } from "./report";
7
6
 
8
7
  export const backtestModule: PluginModule = {
9
8
  panes: [{
10
- id: "backtest", name: "Backtest", icon: "B", component: BacktestPane,
9
+ id: "backtest", name: "Backtest", icon: "B", get component(): typeof import("./pane").BacktestPane { const { BacktestPane } = require("./pane"); return BacktestPane; },
11
10
  defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 120, height: 34 },
12
11
  tableExport: true,
13
12
  // A capture is dated as `fn BT` dates the run, by its last session, never by the ticker's live quote.
@@ -1,4 +1,4 @@
1
- import { ema, rsi, sma, type IndexedValue } from "../../../time-series/studies";
1
+ import { ema, rsi, sma, type IndexedValue } from "../../../time-series/studies/indicators";
2
2
 
3
3
  /** One daily bar as the rules see it. Open is optional; fills fall back to the close. */
4
4
  export interface BacktestBar {
@@ -9,6 +9,8 @@ export interface BacktestBar {
9
9
  close: number;
10
10
  /** The bar's day had not ended when it was served: its close is the latest price. */
11
11
  partial?: true;
12
+ /** A future's session that closed before its settlement was in: its close is the last trade. */
13
+ unsettled?: true;
12
14
  }
13
15
 
14
16
  export type Operand =
@@ -4,6 +4,7 @@ import type { BacktestHistory } from "./client";
4
4
  import { runBacktest, type BacktestOptions, type BacktestResult } from "./engine";
5
5
  import { delistingLabel } from "../../../market-data/quotes/delisting";
6
6
  import type { BacktestBar, Rule } from "./rules";
7
+ import { formingBarsState, isFormingBar } from "../shared/forming-bars";
7
8
 
8
9
  /** Which served sessions a run tests. */
9
10
  export interface BacktestWindowOptions {
@@ -62,17 +63,17 @@ export function backtestWindow(
62
63
  if (history.undatedSkipped > 0) {
63
64
  warnings.push(`${history.undatedSkipped} price ${history.undatedSkipped === 1 ? "point" : "points"} without a date ${history.undatedSkipped === 1 ? "was" : "were"} left out.`);
64
65
  }
65
- const forming = inWindow.filter((bar) => bar.partial);
66
- const bars = forming.length === 0 || options.includePartial ? inWindow : inWindow.filter((bar) => !bar.partial);
66
+ // A future's session that has not settled moves too: its close becomes the settlement.
67
+ const forming = inWindow.filter(isFormingBar);
68
+ const bars = forming.length === 0 || options.includePartial ? inWindow : inWindow.filter((bar) => !isFormingBar(bar));
67
69
  const through = bars.at(-1)?.date;
68
70
  if (forming.length > 0) {
69
- const dates = listDates(forming.map((bar) => bar.date));
71
+ const dates = `${listDates(forming.map((bar) => bar.date))} ${formingBarsState(forming)}`;
70
72
  warnings.push(options.includePartial
71
- ? `${dates} ${forming.length === 1 ? "is" : "are"} still forming (partial) and kept: the last close is the latest price, so the results move until the session ends.`
72
- : `${dates} ${forming.length === 1 ? "is" : "are"} still forming (partial) and left out`
73
- + `${through ? `: the test runs through ${through}, the last complete session` : ""}.`);
73
+ ? `${dates} and kept: the last close is ${forming.every((bar) => bar.partial) ? "the latest price" : "not final"}, so the results move until the session ends.`
74
+ : `${dates} and left out${through ? `: the test runs through ${through}, the last complete session` : ""}.`);
74
75
  }
75
- const newestComplete = history.bars.findLast((bar) => !bar.partial)?.date;
76
+ const newestComplete = history.bars.findLast((bar) => !isFormingBar(bar))?.date;
76
77
  const delisting = history.delisting;
77
78
  if (delisting) {
78
79
  const listing = `${history.symbol ? `${parsePublicTickerKey(history.symbol).symbol}: ` : ""}${delistingLabel(delisting)}`;
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, type Ref } from "react";
2
- import { Box, ScrollBox, Text, TextAttributes, type BoxRenderable, type ScrollBoxRenderable } from "../../../ui";
2
+ import { Box, ScrollBox, Text, TextAttributes, useNativeRenderer, useUiCapabilities, type BoxRenderable, type ScrollBoxRenderable } from "../../../ui";
3
3
  import { Button, FieldLabel, NumberField, SectionHeading, SegmentedControl, TextField, useFieldRing } from "../../../components";
4
4
  import { afterLayout, revealInScrollBox } from "../../../components/ui/reveal-in-scroll-box";
5
5
  import {
@@ -294,10 +294,23 @@ export function BrokerDetailContent({
294
294
  // Enter saves from any field, so what stopped the save can be below the
295
295
  // fold; bring it and the buttons into view.
296
296
  const actionsRef = useRef<BoxRenderable | null>(null);
297
+ const desktop = useUiCapabilities().nativePaneChrome === true;
298
+ const nativeRenderer = useNativeRenderer();
297
299
  useEffect(() => {
298
300
  if (!editError) return;
299
- return afterLayout(() => revealInScrollBox(scrollRef.current, actionsRef.current));
300
- }, [editError]);
301
+ const reveal = () => revealInScrollBox(scrollRef.current, actionsRef.current);
302
+ if (desktop) return afterLayout(reveal);
303
+ // The terminal lays the error line out only when it draws a frame, which
304
+ // can come after a 0 ms timer: measured before it, the buttons are one row
305
+ // short and the error pushes Save back below the fold.
306
+ const onFrame = () => {
307
+ nativeRenderer.off("frame", onFrame);
308
+ reveal();
309
+ };
310
+ nativeRenderer.on("frame", onFrame);
311
+ nativeRenderer.requestRender();
312
+ return () => nativeRenderer.off("frame", onFrame);
313
+ }, [desktop, editError, nativeRenderer]);
301
314
 
302
315
  if (!row) return <Box flexGrow={1} />;
303
316
 
@@ -365,7 +365,7 @@ export const brokerManagerModule: PluginModule = {
365
365
  personalData: "account",
366
366
  icon: "B",
367
367
  component: BrokersPane,
368
- reportFreshness: { status: "not-a-feed", basis: "broker connections" },
368
+ reportFreshness: { status: "not-a-feed", basis: "broker connections", local: true },
369
369
  defaultPosition: "right",
370
370
  defaultMode: "floating",
371
371
  defaultFloatingSize: { width: 92, height: 24 },
@@ -11,6 +11,7 @@ import {
11
11
  activityColor,
12
12
  activityLabel,
13
13
  criticalityColor,
14
+ listWord,
14
15
  metricColor,
15
16
  moneyAmount,
16
17
  moneyText,
@@ -115,6 +116,10 @@ export function renderBuildoutCell(
115
116
  return { text: text(company.return1y), color: metricColor(company.return1y) };
116
117
  case "employees":
117
118
  return { text: text(company.employeeCount), color: colors.textDim };
119
+ case "listRole":
120
+ return { text: text(listWord(company.listRole)), color: colors.textDim };
121
+ case "listTier":
122
+ return { text: text(listWord(company.listTier)), color: company.listTier === "leader" ? colors.text : colors.textDim };
118
123
  }
119
124
  }
120
125
 
@@ -0,0 +1,367 @@
1
+ import { useMemo, useRef, useState } from "react";
2
+ import type { ChartVectorShape } from "../../../../ui/host";
3
+ import { Box, ChartSurface, Span, Text, TextAttributes, useUiCapabilities } from "../../../../ui";
4
+ import { BADGE_TINT, ClippedText, NATIVE_BADGE_STYLE } from "../../../../components/ui/display";
5
+ import { cardSurfaceStyle } from "../../../../components/ui/card";
6
+ import { useShortcut } from "../../../../react/input";
7
+ import { blendHex } from "../../../../theme/colors";
8
+ import { SERIES_COLORS } from "../../../../theme/series-colors";
9
+ import { useThemeColors } from "../../../../theme/theme-context";
10
+ import { displayWidth } from "../../../../utils/format";
11
+ import { isPlainKey } from "../../../../utils/keyboard";
12
+ import { ribbonY } from "../../supply-chain/flow-layout";
13
+ import type { BuildoutCompanyProfile, BuildoutRelation, RelationRole } from "./model";
14
+
15
+ /** Power and hosting gold, then the supply chain's own supplier, customer and competitor colours. */
16
+ const RELATION_COLORS: Record<RelationRole, string> = {
17
+ power: SERIES_COLORS[2]!,
18
+ supplier: SERIES_COLORS[0]!,
19
+ customer: SERIES_COLORS[1]!,
20
+ competitor: SERIES_COLORS[5]!,
21
+ };
22
+
23
+ const GROUP_LABELS: Record<RelationRole, string> = { power: "Power", supplier: "Suppliers", customer: "Customers", competitor: "Competitors" };
24
+ const CARD_ROWS = 2;
25
+ const LINK_STEPS = 40;
26
+ const CHIP_LABEL_CELLS = 13;
27
+ /** Below this the columns and the ribbons between them do not fit; the flow reads as a list. */
28
+ export const FLOW_MIN_WIDTH = 64;
29
+
30
+ interface Rect { x: number; y: number; width: number; height: number }
31
+ interface FlowCard extends Rect { relation: BuildoutRelation }
32
+ interface FlowHeader { role: RelationRole; x: number; y: number }
33
+ interface FlowMore { role: RelationRole; x: number; y: number; hidden: number }
34
+ interface FlowLink { id: string; role: RelationRole; x0: number; y0: number; x1: number; y1: number }
35
+ interface FlowChip { relation: BuildoutRelation | null; label: string; x: number; width: number }
36
+
37
+ export interface FlowGeometry {
38
+ center: Rect;
39
+ cards: FlowCard[];
40
+ headers: FlowHeader[];
41
+ more: FlowMore[];
42
+ links: FlowLink[];
43
+ chips: FlowChip[];
44
+ chipRow: number | null;
45
+ }
46
+
47
+ interface Group { role: RelationRole; items: BuildoutRelation[]; page: number }
48
+
49
+ /** Rows a column of groups takes with `counts` cards each. */
50
+ function columnRows(groups: readonly Group[], counts: readonly number[], gap: number): number {
51
+ return groups.reduce((sum, group, index) => {
52
+ if (!group.items.length) return sum;
53
+ const shown = counts[index]!;
54
+ return sum + (sum ? 1 : 0) + 1 + shown * CARD_ROWS + Math.max(0, shown - 1) * gap + (shown < group.items.length ? 1 : 0);
55
+ }, 0);
56
+ }
57
+
58
+ /** Places a column's groups in `rows`, the last group giving up cards first, centred when there is room. */
59
+ function placeColumn(groups: readonly Group[], x: number, width: number, rows: number) {
60
+ const cards: FlowCard[] = [];
61
+ const headers: FlowHeader[] = [];
62
+ const more: FlowMore[] = [];
63
+ for (const gap of [1, 0]) {
64
+ const counts = groups.map((group) => group.items.length);
65
+ while (columnRows(groups, counts, gap) > rows) {
66
+ let index = -1;
67
+ for (let i = counts.length - 1; i >= 0; i--) if (counts[i]! > 1) { index = i; break; }
68
+ if (index < 0) break;
69
+ counts[index]!--;
70
+ }
71
+ const used = columnRows(groups, counts, gap);
72
+ if (used > rows && gap === 1) continue;
73
+ let y = Math.max(0, Math.floor((rows - used) / 2));
74
+ let first = true;
75
+ groups.forEach((group, index) => {
76
+ if (!group.items.length) return;
77
+ if (!first) y += 1;
78
+ first = false;
79
+ headers.push({ role: group.role, x, y });
80
+ y += 1;
81
+ const shown = counts[index]!;
82
+ const pages = Math.max(1, Math.ceil(group.items.length / Math.max(1, shown)));
83
+ const start = shown < group.items.length ? (group.page % pages) * shown : 0;
84
+ group.items.slice(start, start + shown).forEach((relation, offset) => {
85
+ cards.push({ relation, x, y, width, height: CARD_ROWS });
86
+ y += CARD_ROWS + (offset < shown - 1 ? gap : 0);
87
+ });
88
+ if (shown < group.items.length) {
89
+ more.push({ role: group.role, x, y, hidden: group.items.length - shown });
90
+ y += 1;
91
+ }
92
+ });
93
+ break;
94
+ }
95
+ return { cards, headers, more };
96
+ }
97
+
98
+ /**
99
+ * Power and hosting deals and suppliers on the left, customers on the right,
100
+ * the company between them with a line from every card to its edge, in card
101
+ * order so no two cross; competitors as chips on the last row.
102
+ */
103
+ function flowGeometry(profile: BuildoutCompanyProfile, width: number, height: number, focusLabel: string, pages: Partial<Record<RelationRole, number>>): FlowGeometry {
104
+ const of = (role: RelationRole) => profile.relations.filter((relation) => relation.role === role);
105
+ const competitors = of("competitor");
106
+ const chipRow = competitors.length ? height - 1 : null;
107
+ const rows = Math.max(CARD_ROWS + 1, (chipRow ?? height) - (chipRow === null ? 0 : 1));
108
+ const cardWidth = Math.max(18, Math.min(38, Math.floor(width * (width < 110 ? 0.31 : 0.27))));
109
+ const left = placeColumn([{ role: "power", items: of("power"), page: pages.power ?? 0 }, { role: "supplier", items: of("supplier"), page: pages.supplier ?? 0 }], 1, cardWidth, rows);
110
+ const right = placeColumn([{ role: "customer", items: of("customer"), page: pages.customer ?? 0 }], width - cardWidth - 1, cardWidth, rows);
111
+ const centerWidth = Math.max(12, Math.min(26, displayWidth(focusLabel) + 6));
112
+ const centerHeight = Math.max(3, Math.min(9, Math.floor(rows * 0.4)));
113
+ const center = { x: Math.floor((width - centerWidth) / 2), y: Math.floor((rows - centerHeight) / 2), width: centerWidth, height: centerHeight };
114
+ const links: FlowLink[] = [];
115
+ const leftCards = left.cards;
116
+ leftCards.forEach((card, index) => links.push({
117
+ id: `${card.relation.role}:${card.relation.id}`, role: card.relation.role,
118
+ x0: card.x + card.width, y0: card.y + 1, x1: center.x, y1: center.y + (center.height * (index + 0.5)) / leftCards.length,
119
+ }));
120
+ right.cards.forEach((card, index) => links.push({
121
+ id: `${card.relation.role}:${card.relation.id}`, role: card.relation.role,
122
+ x0: center.x + center.width, y0: center.y + (center.height * (index + 0.5)) / right.cards.length, x1: card.x, y1: card.y + 1,
123
+ }));
124
+ // Competitors as chips while they fit, then "+N more".
125
+ const chips: FlowChip[] = [];
126
+ let x = CHIP_LABEL_CELLS + 1;
127
+ for (const [index, relation] of competitors.entries()) {
128
+ const label = relation.ticker ?? relation.name;
129
+ const chipWidth = Math.min(22, displayWidth(label) + 2);
130
+ const rest = competitors.length - index - 1;
131
+ if (x + chipWidth + (rest ? 11 : 0) > width - 1) {
132
+ chips.push({ relation: null, label: `+${competitors.length - index} more`, x, width: displayWidth(`+${competitors.length - index} more`) + 2 });
133
+ break;
134
+ }
135
+ chips.push({ relation, label, x, width: chipWidth });
136
+ x += chipWidth + 1;
137
+ }
138
+ return {
139
+ center,
140
+ cards: [...left.cards, ...right.cards],
141
+ headers: [...left.headers, ...right.headers],
142
+ more: [...left.more, ...right.more],
143
+ links,
144
+ chips,
145
+ chipRow,
146
+ };
147
+ }
148
+
149
+ const linkId = (relation: BuildoutRelation) => `${relation.role}:${relation.id}`;
150
+
151
+ export function CompanyFlow({ profile, width, height, focused, onOpen }: {
152
+ profile: BuildoutCompanyProfile;
153
+ width: number;
154
+ height: number;
155
+ focused: boolean;
156
+ onOpen: (relation: BuildoutRelation) => void;
157
+ }) {
158
+ const colors = useThemeColors();
159
+ const desktop = !!useUiCapabilities().nativePaneChrome;
160
+ const [pages, setPages] = useState<Partial<Record<RelationRole, number>>>({});
161
+ const [selectedId, setSelectedId] = useState<string | null>(null);
162
+ const [hovered, setHovered] = useState<string | null>(null);
163
+ const surfaceRef = useRef<{ x: number; y: number; absoluteX?: number; absoluteY?: number } | null>(null);
164
+ const focusLabel = profile.ticker ?? profile.name;
165
+ const geometry = useMemo(() => flowGeometry(profile, width, height, focusLabel, pages), [focusLabel, height, pages, profile, width]);
166
+ const { cards, links, center } = geometry;
167
+ const selectable = useMemo(() => [...cards.map((card) => card.relation), ...geometry.chips.flatMap((chip) => chip.relation ? [chip.relation] : [])], [cards, geometry.chips]);
168
+ const selectedIndex = Math.max(0, selectable.findIndex((relation) => linkId(relation) === selectedId));
169
+ const selected = selectable[selectedIndex] ? linkId(selectable[selectedIndex]!) : null;
170
+ const active = hovered ?? (focused ? selected : null);
171
+ const focusTint = blendHex(colors.bg, colors.textBright, 0.55);
172
+
173
+ useShortcut((event) => {
174
+ if (!focused || event.defaultPrevented || selectable.length === 0) return;
175
+ if (isPlainKey(event, "j", "down") || isPlainKey(event, "k", "up")) {
176
+ const step = isPlainKey(event, "j", "down") ? 1 : -1;
177
+ setSelectedId(linkId(selectable[(selectedIndex + step + selectable.length) % selectable.length]!));
178
+ } else if (isPlainKey(event, "return", "enter")) onOpen(selectable[selectedIndex]!);
179
+ else return;
180
+ event.preventDefault();
181
+ event.stopPropagation();
182
+ });
183
+
184
+ const linkColors = (link: FlowLink) => link.x0 < center.x ? [RELATION_COLORS[link.role], focusTint] : [focusTint, RELATION_COLORS[link.role]];
185
+ const vectors = useMemo<ChartVectorShape[]>(() => links.map((link) => ({
186
+ id: link.id,
187
+ points: Array.from({ length: LINK_STEPS + 1 }, (_, step) => {
188
+ const x = link.x0 + (link.x1 - link.x0) * step / LINK_STEPS;
189
+ return { x: x / width, y: ribbonY(link, x) / height };
190
+ }),
191
+ color: RELATION_COLORS[link.role],
192
+ gradient: linkColors(link),
193
+ strokeWidth: active === link.id ? 2.4 : 1.4,
194
+ opacity: active && active !== link.id ? 0.3 : active === link.id ? 1 : 0.75,
195
+ })), [active, height, links, width, focusTint, center.x]);
196
+
197
+ // The terminal draws the same curves in braille, each coloured from its card to the company.
198
+ const braille = useMemo(() => {
199
+ if (desktop) return null;
200
+ const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => ({ mask: 0, color: colors.textDim })));
201
+ const bits = [[1, 8], [2, 16], [4, 32], [64, 128]];
202
+ for (const link of links) {
203
+ const [from, to] = linkColors(link);
204
+ for (let dx = Math.ceil(link.x0 * 2); dx < Math.floor(link.x1 * 2); dx++) {
205
+ const x = (dx + 0.5) / 2;
206
+ const t = (x - link.x0) / Math.max(1e-6, link.x1 - link.x0);
207
+ const dy = Math.round(ribbonY(link, x) * 4 - 0.5);
208
+ const cell = grid[Math.floor(dy / 4)]?.[Math.floor(dx / 2)];
209
+ if (!cell || dy < 0) continue;
210
+ cell.mask |= bits[dy % 4]![dx % 2]!;
211
+ const color = blendHex(from!, to!, t);
212
+ cell.color = active === link.id ? color : blendHex(colors.bg, color, 0.75);
213
+ }
214
+ }
215
+ return grid.map((line) => {
216
+ const spans: { text: string; color: string }[] = [];
217
+ for (const cell of line) {
218
+ const char = cell.mask ? String.fromCharCode(0x2800 + cell.mask) : " ";
219
+ if (spans.at(-1)?.color === cell.color) spans.at(-1)!.text += char;
220
+ else spans.push({ text: char, color: cell.color });
221
+ }
222
+ return spans;
223
+ });
224
+ }, [active, colors.bg, colors.textDim, desktop, height, links, width, focusTint, center.x]);
225
+
226
+ /** The line under the pointer comes forward. */
227
+ const hoverAt = (event: { preciseX?: number; preciseY?: number; x: number; y: number }) => {
228
+ const origin = surfaceRef.current;
229
+ if (!origin) return;
230
+ const x = (event.preciseX ?? event.x) - (origin.absoluteX ?? origin.x);
231
+ const y = (event.preciseY ?? event.y) - (origin.absoluteY ?? origin.y);
232
+ let best: { id: string; distance: number } | null = null;
233
+ for (const link of links) {
234
+ if (x < link.x0 || x > link.x1) continue;
235
+ const distance = Math.abs(ribbonY(link, x) - y);
236
+ if (distance < 0.7 && (!best || distance < best.distance)) best = { id: link.id, distance };
237
+ }
238
+ const next = best?.id ?? null;
239
+ if (next !== hovered) setHovered(next);
240
+ };
241
+
242
+ return (
243
+ <Box width={width} height={height} flexGrow={1} flexBasis={0} minHeight={0} position="relative" overflow="hidden">
244
+ <ChartSurface ref={surfaceRef} width={width} height={height} position="absolute" left={0} top={0}
245
+ vectors={desktop ? vectors : undefined} flexDirection="column"
246
+ onMouseMove={hoverAt} onMouseOut={() => setHovered(null)}
247
+ onMouseDown={() => { const card = hovered ? cards.find((entry) => linkId(entry.relation) === hovered) : null; if (card) onOpen(card.relation); }}
248
+ aria-label={`${profile.name} power, suppliers and customers`}>
249
+ {braille ? braille.map((spans, y) => <Text key={y}>{spans.map((span, i) => <Span key={i} fg={span.color}>{span.text}</Span>)}</Text>) : null}
250
+ </ChartSurface>
251
+ {geometry.headers.map((header) => (
252
+ <Box key={header.role} position="absolute" left={header.x} top={header.y} width={20} height={1}>
253
+ <Text fg={RELATION_COLORS[header.role]} attributes={TextAttributes.BOLD}>{GROUP_LABELS[header.role]}</Text>
254
+ </Box>
255
+ ))}
256
+ <FocusCard profile={profile} label={focusLabel} rect={center} desktop={desktop} />
257
+ {cards.map((card) => (
258
+ <RelationCard key={linkId(card.relation)} card={card} desktop={desktop} active={active === linkId(card.relation)}
259
+ onPress={() => { setSelectedId(linkId(card.relation)); onOpen(card.relation); }}
260
+ onHover={(on) => setHovered(on ? linkId(card.relation) : null)} />
261
+ ))}
262
+ {geometry.more.map((entry) => (
263
+ <Box key={`more:${entry.role}`} position="absolute" left={entry.x} top={entry.y} width={20} height={1}
264
+ onMouseDown={() => setPages((current) => ({ ...current, [entry.role]: (current[entry.role] ?? 0) + 1 }))}
265
+ style={desktop ? { cursor: "pointer" } : undefined} role="button" aria-label={`Show ${entry.hidden} more ${GROUP_LABELS[entry.role].toLowerCase()}`}>
266
+ <Text fg={colors.textDim}>{`+${entry.hidden} more`}</Text>
267
+ </Box>
268
+ ))}
269
+ {geometry.chipRow !== null ? (
270
+ <Box position="absolute" left={1} top={geometry.chipRow} width={CHIP_LABEL_CELLS} height={1}>
271
+ <Text fg={RELATION_COLORS.competitor}>Competitors</Text>
272
+ </Box>
273
+ ) : null}
274
+ {geometry.chipRow !== null ? geometry.chips.map((chip) => (
275
+ <CompetitorChip key={chip.label} chip={chip} row={geometry.chipRow!} desktop={desktop}
276
+ active={!!chip.relation && active === linkId(chip.relation)} onPress={() => { if (chip.relation) onOpen(chip.relation); }} />
277
+ )) : null}
278
+ </Box>
279
+ );
280
+ }
281
+
282
+ function FocusCard({ profile, label, rect, desktop }: { profile: BuildoutCompanyProfile; label: string; rect: Rect; desktop: boolean }) {
283
+ const colors = useThemeColors();
284
+ const showName = profile.name && profile.name.toUpperCase() !== label.toUpperCase() && rect.height >= (desktop ? 3 : 4);
285
+ return (
286
+ <Box position="absolute" left={rect.x} top={rect.y} width={rect.width} height={rect.height} flexDirection="column" alignItems="center" justifyContent="center"
287
+ border={!desktop} borderStyle="rounded" borderColor={colors.textMuted}
288
+ style={desktop ? cardSurfaceStyle({ padding: "0" }) : undefined}>
289
+ <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{label}</Text>
290
+ {showName ? <ClippedText fg={colors.textDim} text={profile.name} width={rect.width - 4} /> : null}
291
+ </Box>
292
+ );
293
+ }
294
+
295
+ function RelationCard({ card, desktop, active, onPress, onHover }: {
296
+ card: FlowCard; desktop: boolean; active: boolean; onPress: () => void; onHover: (on: boolean) => void;
297
+ }) {
298
+ const colors = useThemeColors();
299
+ const { relation } = card;
300
+ const accent = RELATION_COLORS[relation.role];
301
+ const inner = Math.max(4, card.width - 2);
302
+ const nameWidth = Math.max(4, inner - (relation.ticker ? displayWidth(relation.ticker) + 1 : 0));
303
+ const listed = !!relation.ticker;
304
+ return (
305
+ <Box position="absolute" left={card.x} top={card.y} width={card.width} height={card.height} flexDirection="column"
306
+ backgroundColor={desktop ? undefined : active ? colors.selected : blendHex(colors.bg, accent, 0.12)}
307
+ style={desktop ? { ...cardSurfaceStyle({ selected: active, accent, padding: "0 var(--gloom-space-sm)" }), cursor: "pointer" } : undefined}
308
+ onMouseDown={onPress} onMouseOver={() => onHover(true)} onMouseOut={() => onHover(false)}
309
+ data-gloom-role="buildout-flow-card" role="button" aria-label={`Open ${relation.name}`}>
310
+ <Box flexDirection="row" height={1} overflow="hidden" style={desktop ? { flexWrap: "wrap" } : undefined}>
311
+ {!desktop ? <Text fg={accent}>▍</Text> : null}
312
+ <ClippedText fg={active && !desktop ? colors.selectedText : listed ? colors.textBright : colors.text}
313
+ attributes={listed ? TextAttributes.BOLD : 0} text={relation.name} width={nameWidth} />
314
+ {relation.ticker && (desktop || displayWidth(relation.name) + displayWidth(relation.ticker) + 1 <= inner)
315
+ ? <Text fg={colors.textDim} style={desktop ? { flex: "0 0 auto" } : undefined}>{` ${relation.ticker}`}</Text> : null}
316
+ </Box>
317
+ {relation.figure ? (
318
+ <Box height={1} paddingLeft={desktop ? 0 : 1} overflow="hidden">
319
+ <ClippedText fg={active && !desktop ? colors.selectedText : colors.textDim} text={relation.figure} width={inner - (desktop ? 0 : 1)} />
320
+ </Box>
321
+ ) : null}
322
+ </Box>
323
+ );
324
+ }
325
+
326
+ function CompetitorChip({ chip, row, desktop, active, onPress }: { chip: FlowChip; row: number; desktop: boolean; active: boolean; onPress: () => void }) {
327
+ const colors = useThemeColors();
328
+ const accent = RELATION_COLORS.competitor;
329
+ const more = !chip.relation;
330
+ const background = desktop
331
+ ? active ? accent : more ? undefined : blendHex(colors.bg, accent, BADGE_TINT)
332
+ : active ? blendHex(colors.bg, accent, 0.45) : more ? undefined : blendHex(colors.bg, accent, 0.14);
333
+ const text = <ClippedText fg={more ? colors.textDim : active ? desktop ? colors.bg : colors.textBright : accent} text={chip.label} width={chip.width - 2} />;
334
+ return (
335
+ <Box position="absolute" left={chip.x} top={row} width={chip.width} height={1} justifyContent={desktop ? "center" : undefined}
336
+ onMouseDown={onPress} role="button" aria-label={chip.relation ? `Open ${chip.relation.name}` : chip.label}>
337
+ {desktop
338
+ ? <Box paddingX={1} minWidth={0} backgroundColor={background} justifyContent="center" style={{ ...NATIVE_BADGE_STYLE, cursor: more ? undefined : "pointer" }}>{text}</Box>
339
+ : <Box paddingX={1} height={1} backgroundColor={background}>{text}</Box>}
340
+ </Box>
341
+ );
342
+ }
343
+
344
+ /** The flow as a list, for a pane too narrow for columns: each group under its name. */
345
+ export function CompanyFlowList({ profile, width, onOpen }: { profile: BuildoutCompanyProfile; width: number; onOpen: (relation: BuildoutRelation) => void }) {
346
+ const colors = useThemeColors();
347
+ const roles: RelationRole[] = ["power", "customer", "supplier", "competitor"];
348
+ return (
349
+ <Box flexDirection="column" width={width} paddingX={1}>
350
+ {roles.map((role) => {
351
+ const items = profile.relations.filter((relation) => relation.role === role);
352
+ if (!items.length) return null;
353
+ return (
354
+ <Box key={role} flexDirection="column" marginBottom={1}>
355
+ <Text fg={RELATION_COLORS[role]} attributes={TextAttributes.BOLD}>{GROUP_LABELS[role]}</Text>
356
+ {items.map((relation) => (
357
+ <Box key={linkId(relation)} flexDirection="column" onMouseDown={() => onOpen(relation)}>
358
+ <ClippedText fg={relation.ticker ? colors.textBright : colors.text} text={`${relation.name}${relation.ticker ? ` ${relation.ticker}` : ""}`} width={width - 2} />
359
+ {relation.figure ? <ClippedText fg={colors.textDim} text={relation.figure} width={width - 2} /> : null}
360
+ </Box>
361
+ ))}
362
+ </Box>
363
+ );
364
+ })}
365
+ </Box>
366
+ );
367
+ }
@@ -0,0 +1,2 @@
1
+ /** The company pane's id, apart from its component so the plugin can name it without loading the pane. */
2
+ export const BUILDOUT_COMPANY_PANE_ID = "buildout-company";