gloomberb 0.16.1 → 0.16.2

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 (574) hide show
  1. package/README.md +2 -1
  2. package/package.json +2 -1
  3. package/src/api-client/beneficial-owners.ts +92 -0
  4. package/src/api-client/data.ts +26 -8
  5. package/src/api-client/errors.ts +12 -0
  6. package/src/api-client/geo.ts +363 -0
  7. package/src/api-client/index.ts +2 -0
  8. package/src/api-client/market-discovery.ts +10 -0
  9. package/src/api-client/paths.ts +30 -1
  10. package/src/api-client/perps.ts +37 -0
  11. package/src/api-client/request.ts +5 -2
  12. package/src/api-client/types.ts +2 -0
  13. package/src/brokers/account-cache.ts +24 -0
  14. package/src/cli/commands/command-utils.ts +8 -0
  15. package/src/cli/commands/crypto-hints.ts +75 -0
  16. package/src/cli/commands/market.ts +414 -106
  17. package/src/cli/commands/overview.ts +77 -32
  18. package/src/cli/commands/ticker.ts +101 -43
  19. package/src/cli/desktop-pane-shot.ts +34 -4
  20. package/src/cli/freshness.ts +61 -0
  21. package/src/cli/help.ts +1 -0
  22. package/src/cli/helpers.ts +4 -8
  23. package/src/cli/index.ts +12 -2
  24. package/src/cli/listing-arg.ts +321 -0
  25. package/src/cli/pane-functions/access-gate.ts +42 -0
  26. package/src/cli/pane-functions/catalog.ts +28 -1
  27. package/src/cli/pane-functions/data.ts +3 -1
  28. package/src/cli/pane-functions/dom.ts +20 -2
  29. package/src/cli/pane-functions/freshness.ts +400 -0
  30. package/src/cli/pane-functions/function-help.ts +27 -20
  31. package/src/cli/pane-functions/headless.ts +39 -11
  32. package/src/cli/pane-functions/index.ts +15 -4
  33. package/src/cli/pane-functions/options.ts +3 -0
  34. package/src/cli/pane-functions/report.ts +4 -0
  35. package/src/cli/pane-functions/resolver.ts +35 -7
  36. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  37. package/src/cli/pane-functions/screenshot.ts +7 -3
  38. package/src/cli/result.ts +41 -27
  39. package/src/cli/ticker-resolution.ts +7 -5
  40. package/src/components/chart/composite/composite-chart.tsx +88 -2536
  41. package/src/components/chart/composite/drawing-store.ts +59 -0
  42. package/src/components/chart/composite/legend.tsx +319 -0
  43. package/src/components/chart/composite/panel-bitmap.ts +139 -0
  44. package/src/components/chart/composite/panel-gestures.ts +59 -0
  45. package/src/components/chart/composite/panel-labels.ts +107 -0
  46. package/src/components/chart/composite/panel-surface.tsx +876 -0
  47. package/src/components/chart/composite/tool-catalog.ts +98 -0
  48. package/src/components/chart/composite/toolbar.tsx +186 -0
  49. package/src/components/chart/composite/tools.ts +8 -0
  50. package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
  51. package/src/components/chart/composite/use-composite-viewport.ts +143 -0
  52. package/src/components/command-bar/list/model.ts +5 -0
  53. package/src/components/command-bar/list/view.tsx +16 -3
  54. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  55. package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
  56. package/src/components/command-bar/workflow/ops.ts +1 -1
  57. package/src/components/command-bar/workflow/tickers.ts +6 -0
  58. package/src/components/data-table/view.tsx +29 -10
  59. package/src/components/feedback-dialog.tsx +2 -1
  60. package/src/components/form-modal/field-row.tsx +2 -2
  61. package/src/components/layout/detached-pane-shell.tsx +6 -3
  62. package/src/components/layout/pane/content.tsx +23 -10
  63. package/src/components/layout/pane/error-boundary.tsx +103 -0
  64. package/src/components/layout/pane/footer/registration.tsx +68 -23
  65. package/src/components/layout/pane/header.tsx +11 -11
  66. package/src/components/layout/shell/menu.ts +2 -1
  67. package/src/components/layout/shell/pane/actions.ts +2 -1
  68. package/src/components/layout/shell/pane/layers.tsx +14 -3
  69. package/src/components/metric-treemap/heat-surface.tsx +43 -16
  70. package/src/components/metric-treemap/heat-tiles.ts +18 -0
  71. package/src/components/metric-treemap/layout.ts +2 -0
  72. package/src/components/onboarding/onboarding-keyboard.ts +311 -0
  73. package/src/components/onboarding/onboarding-wizard.tsx +101 -788
  74. package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
  75. package/src/components/onboarding/use-onboarding-progress.ts +298 -0
  76. package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
  77. package/src/components/ticker/list-table-view.tsx +13 -1
  78. package/src/components/ui/fields.tsx +8 -0
  79. package/src/components/ui/range-track.tsx +10 -3
  80. package/src/components/ui/status.tsx +8 -2
  81. package/src/components/ui/tabs.tsx +7 -4
  82. package/src/components/ui/text-prompt-dialog.tsx +4 -2
  83. package/src/data/config/store/normalize.ts +17 -1
  84. package/src/i18n/es.ts +19 -1
  85. package/src/i18n/ja.ts +19 -1
  86. package/src/i18n/ko.ts +19 -1
  87. package/src/i18n/zh-cn.ts +19 -1
  88. package/src/i18n/zh-tw.ts +19 -1
  89. package/src/market-data/coordinator/chart.ts +1 -1
  90. package/src/market-data/coordinator/quotes.ts +23 -4
  91. package/src/market-data/fx-closes.ts +72 -0
  92. package/src/market-data/market/freshness.ts +78 -6
  93. package/src/market-data/market/session-close-note.ts +57 -0
  94. package/src/market-data/market/status.ts +151 -0
  95. package/src/market-data/published-apac-sessions.ts +66 -0
  96. package/src/market-data/quotes/contributions.ts +2 -0
  97. package/src/market-data/quotes/resolution.ts +2 -0
  98. package/src/market-data/quotes/screener-live-quotes.ts +49 -4
  99. package/src/plugins/builtin/account-management/pane.tsx +4 -0
  100. package/src/plugins/builtin/alerts/cli.ts +10 -5
  101. package/src/plugins/builtin/alerts/index.tsx +1 -0
  102. package/src/plugins/builtin/analytics/metrics.ts +8 -3
  103. package/src/plugins/builtin/analytics/risk-client.ts +191 -66
  104. package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
  105. package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
  106. package/src/plugins/builtin/analytics/risk-model.ts +45 -17
  107. package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
  108. package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
  109. package/src/plugins/builtin/attention/headless.ts +6 -4
  110. package/src/plugins/builtin/attention/index.tsx +1 -1
  111. package/src/plugins/builtin/attention/pane.tsx +1 -1
  112. package/src/plugins/builtin/awards/headless.ts +1 -0
  113. package/src/plugins/builtin/awards/pane.tsx +1 -1
  114. package/src/plugins/builtin/backtest/headless.ts +1 -0
  115. package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
  116. package/src/plugins/builtin/buildout/index.tsx +1 -0
  117. package/src/plugins/builtin/catalysts/headless.ts +3 -1
  118. package/src/plugins/builtin/cds/headless.ts +2 -0
  119. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
  120. package/src/plugins/builtin/chart-composer/headless.ts +38 -3
  121. package/src/plugins/builtin/chart-composer/index.tsx +3 -1
  122. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
  123. package/src/plugins/builtin/chat/channels.ts +19 -3
  124. package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
  125. package/src/plugins/builtin/chat/content/footer.tsx +15 -6
  126. package/src/plugins/builtin/chat/content/index.tsx +120 -21
  127. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
  128. package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
  129. package/src/plugins/builtin/chat/direct-messages.ts +200 -0
  130. package/src/plugins/builtin/chat/index.tsx +1 -0
  131. package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
  132. package/src/plugins/builtin/chat/message/header.tsx +16 -6
  133. package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
  134. package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
  135. package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
  136. package/src/plugins/builtin/chat/message/types.ts +14 -2
  137. package/src/plugins/builtin/chat/profile-popover.ts +18 -13
  138. package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
  139. package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
  140. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  141. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  142. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  143. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  144. package/src/plugins/builtin/comment-letters/index.tsx +1 -0
  145. package/src/plugins/builtin/company-attention/headless.ts +5 -1
  146. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  147. package/src/plugins/builtin/company-kpis/headless.ts +1 -0
  148. package/src/plugins/builtin/congress-trades/headless.ts +1 -0
  149. package/src/plugins/builtin/correlation/compute.ts +59 -16
  150. package/src/plugins/builtin/correlation/geo.ts +34 -0
  151. package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
  152. package/src/plugins/builtin/correlation/headless.ts +42 -8
  153. package/src/plugins/builtin/correlation/history.ts +2 -0
  154. package/src/plugins/builtin/correlation/index.tsx +68 -14
  155. package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
  156. package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
  157. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  158. package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
  159. package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
  160. package/src/plugins/builtin/correlation/settings.ts +1 -1
  161. package/src/plugins/builtin/cot/headless.ts +6 -3
  162. package/src/plugins/builtin/cot/index.tsx +3 -3
  163. package/src/plugins/builtin/cot/model.ts +36 -3
  164. package/src/plugins/builtin/cpi/headless.ts +4 -0
  165. package/src/plugins/builtin/credit-boards/headless.ts +2 -0
  166. package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
  167. package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
  168. package/src/plugins/builtin/credit-documents/headless.ts +2 -0
  169. package/src/plugins/builtin/crypto-board/headless.ts +3 -0
  170. package/src/plugins/builtin/custom-view/loader.ts +1 -1
  171. package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
  172. package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
  173. package/src/plugins/builtin/doe/headless.ts +7 -0
  174. package/src/plugins/builtin/doe/range-bar.tsx +7 -37
  175. package/src/plugins/builtin/earnings/headless.ts +5 -0
  176. package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
  177. package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
  178. package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
  179. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
  180. package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
  181. package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
  182. package/src/plugins/builtin/econ/index.tsx +1 -0
  183. package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
  184. package/src/plugins/builtin/equity-screener/headless.ts +2 -0
  185. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  186. package/src/plugins/builtin/executives/index.tsx +1 -0
  187. package/src/plugins/builtin/exposure/headless.ts +3 -1
  188. package/src/plugins/builtin/fear-greed/headless.ts +2 -0
  189. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  190. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  191. package/src/plugins/builtin/futures-curve/headless.ts +39 -10
  192. package/src/plugins/builtin/futures-curve/index.tsx +3 -3
  193. package/src/plugins/builtin/futures-curve/model.ts +188 -4
  194. package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
  195. package/src/plugins/builtin/gpu/headless.ts +7 -3
  196. package/src/plugins/builtin/gpu/ladder.tsx +26 -30
  197. package/src/plugins/builtin/gpu/model.ts +27 -1
  198. package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
  199. package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
  200. package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
  201. package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
  202. package/src/plugins/builtin/holders/headless.ts +139 -5
  203. package/src/plugins/builtin/holders/index.tsx +10 -3
  204. package/src/plugins/builtin/holders/pane.tsx +20 -3
  205. package/src/plugins/builtin/holders/report-header.ts +95 -0
  206. package/src/plugins/builtin/holders/table-model.ts +20 -5
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
  208. package/src/plugins/builtin/holders/types.ts +2 -1
  209. package/src/plugins/builtin/insider/headless.ts +2 -0
  210. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
  211. package/src/plugins/builtin/iv-history/headless.ts +4 -1
  212. package/src/plugins/builtin/jobs/headless.ts +1 -0
  213. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
  214. package/src/plugins/builtin/macro-day/headless.ts +2 -0
  215. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  216. package/src/plugins/builtin/market-heatmap/data.ts +2 -0
  217. package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
  218. package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
  219. package/src/plugins/builtin/market-heatmap/model.ts +146 -0
  220. package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
  221. package/src/plugins/builtin/market-movers/headless.ts +2 -0
  222. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  223. package/src/plugins/builtin/members/client.ts +20 -3
  224. package/src/plugins/builtin/members/headless.ts +5 -3
  225. package/src/plugins/builtin/members/model.ts +10 -2
  226. package/src/plugins/builtin/members/pane.tsx +13 -6
  227. package/src/plugins/builtin/mna/distress/headless.ts +1 -0
  228. package/src/plugins/builtin/mna/headless.ts +1 -0
  229. package/src/plugins/builtin/money-markets/headless.ts +1 -0
  230. package/src/plugins/builtin/news/headless.ts +2 -0
  231. package/src/plugins/builtin/news/wire/index.tsx +8 -1
  232. package/src/plugins/builtin/notes/cli.ts +14 -6
  233. package/src/plugins/builtin/notes/index.tsx +1 -0
  234. package/src/plugins/builtin/openfda/index.tsx +1 -0
  235. package/src/plugins/builtin/options-calculator/headless.ts +2 -0
  236. package/src/plugins/builtin/options-positioning/headless.ts +3 -0
  237. package/src/plugins/builtin/options-scenario/headless.ts +5 -0
  238. package/src/plugins/builtin/pe-band/client.ts +48 -3
  239. package/src/plugins/builtin/pe-band/headless.ts +25 -7
  240. package/src/plugins/builtin/pe-band/model.ts +92 -16
  241. package/src/plugins/builtin/pe-band/pane.tsx +33 -16
  242. package/src/plugins/builtin/perps/client.ts +50 -3
  243. package/src/plugins/builtin/perps/headless.ts +144 -16
  244. package/src/plugins/builtin/perps/index.tsx +6 -6
  245. package/src/plugins/builtin/perps/model.ts +189 -12
  246. package/src/plugins/builtin/perps/pane.tsx +248 -47
  247. package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
  248. package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
  249. package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
  250. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
  251. package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
  252. package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
  253. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
  254. package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
  255. package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
  256. package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
  257. package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
  258. package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
  259. package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
  260. package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
  261. package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
  262. package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
  263. package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
  264. package/src/plugins/builtin/power/headless.ts +3 -1
  265. package/src/plugins/builtin/rate-path/headless.ts +2 -0
  266. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  267. package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
  268. package/src/plugins/builtin/research/analyst-headless.ts +3 -0
  269. package/src/plugins/builtin/research/events-headless.ts +2 -0
  270. package/src/plugins/builtin/research/headless.ts +2 -0
  271. package/src/plugins/builtin/research/index.tsx +1 -0
  272. package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
  273. package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
  274. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  275. package/src/plugins/builtin/research-search/index.tsx +1 -0
  276. package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
  277. package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
  278. package/src/plugins/builtin/risk-factors/headless.ts +2 -0
  279. package/src/plugins/builtin/scanner/flow-history.ts +2 -13
  280. package/src/plugins/builtin/scanner/headless.ts +14 -0
  281. package/src/plugins/builtin/seasonality/headless.ts +2 -0
  282. package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
  283. package/src/plugins/builtin/sec/filing-content.ts +42 -17
  284. package/src/plugins/builtin/sec/forms.ts +12 -0
  285. package/src/plugins/builtin/sec/headless.ts +2 -0
  286. package/src/plugins/builtin/sectors/client.ts +1 -0
  287. package/src/plugins/builtin/sectors/headless.ts +9 -1
  288. package/src/plugins/builtin/sectors/sector-model.ts +2 -0
  289. package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
  290. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  291. package/src/plugins/builtin/shared/report-freshness.ts +57 -0
  292. package/src/plugins/builtin/shared/research-cells.tsx +4 -9
  293. package/src/plugins/builtin/short-interest/headless.ts +2 -0
  294. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  295. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  296. package/src/plugins/builtin/social-mentions/headless.ts +1 -0
  297. package/src/plugins/builtin/supply-chain/headless.ts +7 -1
  298. package/src/plugins/builtin/supply-chain/index.tsx +10 -1
  299. package/src/plugins/builtin/supply-chain/model.ts +27 -2
  300. package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
  301. package/src/plugins/builtin/themes/headless.ts +3 -0
  302. package/src/plugins/builtin/thirteenf/headless.ts +110 -25
  303. package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
  304. package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
  305. package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
  306. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
  308. package/src/plugins/builtin/time-sales/headless.ts +5 -1
  309. package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
  310. package/src/plugins/builtin/volatility/headless.ts +6 -1
  311. package/src/plugins/builtin/world-indices/headless.ts +2 -0
  312. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
  313. package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
  314. package/src/plugins/builtin/world-venue-map/client.ts +45 -0
  315. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
  316. package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
  317. package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
  318. package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
  319. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
  320. package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
  321. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
  322. package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
  323. package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
  324. package/src/plugins/builtin/world-venue-map/index.ts +67 -4
  325. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
  326. package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
  327. package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
  328. package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
  329. package/src/plugins/builtin/world-venue-map/model.ts +7 -4
  330. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
  331. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
  332. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
  333. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
  334. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
  335. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
  336. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
  337. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
  338. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
  339. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
  340. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
  341. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
  342. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
  343. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
  344. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
  345. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
  346. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
  347. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
  348. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
  349. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
  350. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
  351. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
  352. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
  353. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
  354. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
  355. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
  356. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
  357. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
  358. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
  359. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
  360. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
  361. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
  362. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
  363. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
  364. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
  365. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
  366. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
  367. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
  368. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
  369. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
  370. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
  371. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
  372. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
  373. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
  374. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
  375. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
  376. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
  377. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
  378. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
  379. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
  380. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
  381. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
  382. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
  383. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
  384. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
  385. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
  386. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
  387. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
  388. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
  389. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
  390. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
  391. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
  392. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
  393. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
  394. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
  395. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
  396. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
  397. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
  398. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
  399. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
  400. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
  401. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
  402. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
  403. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
  404. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
  405. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
  406. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
  407. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
  408. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
  409. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
  410. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
  411. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
  412. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
  413. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
  414. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
  415. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
  416. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
  417. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
  418. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
  419. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
  420. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
  421. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
  422. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
  423. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
  424. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
  425. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
  426. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
  429. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
  430. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
  431. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
  432. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
  433. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
  434. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
  435. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
  436. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
  437. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
  438. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
  439. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
  440. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
  441. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
  442. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
  443. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
  444. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
  445. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
  446. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
  447. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
  448. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
  449. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
  450. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
  451. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
  452. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
  453. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
  454. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
  455. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
  456. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
  457. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
  458. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
  459. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
  460. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
  461. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
  462. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
  463. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
  464. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
  465. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
  466. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
  467. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
  468. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
  469. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
  470. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
  471. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
  472. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
  473. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
  474. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
  475. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
  476. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
  477. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
  478. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
  479. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
  480. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
  481. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
  482. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
  483. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
  484. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
  485. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
  486. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
  487. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
  488. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
  489. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
  490. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
  491. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
  492. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
  493. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
  494. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
  495. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
  496. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
  497. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
  498. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
  499. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
  500. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
  501. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
  502. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
  503. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
  504. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
  505. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
  506. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
  507. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
  508. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
  509. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
  510. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
  511. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
  512. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
  513. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
  514. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
  515. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
  516. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
  517. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
  518. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
  519. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
  520. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
  521. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
  522. package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
  523. package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
  524. package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
  525. package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
  526. package/src/plugins/builtin/yield-curve/headless.ts +16 -0
  527. package/src/plugins/loader.ts +13 -1
  528. package/src/plugins/registry/pane-settings.ts +1 -0
  529. package/src/renderers/dom/build-assets.ts +81 -3
  530. package/src/renderers/dom/data-table/row.tsx +2 -0
  531. package/src/renderers/dom/desktop/controls.tsx +7 -3
  532. package/src/renderers/dom/desktop/list-view.tsx +35 -10
  533. package/src/renderers/dom/desktop/popover.tsx +73 -14
  534. package/src/renderers/dom/host/input.tsx +14 -3
  535. package/src/renderers/dom/styles.css +6 -7
  536. package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
  537. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  538. package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
  539. package/src/renderers/opentui/host.tsx +5 -0
  540. package/src/sources/gloomberb-cloud/index.ts +25 -4
  541. package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
  542. package/src/sources/provider-router/cached-routes.ts +4 -1
  543. package/src/sources/provider-router/financials.ts +8 -7
  544. package/src/sources/provider-router/history.ts +15 -10
  545. package/src/sources/sec-registrant.ts +75 -0
  546. package/src/state/app/context/index.tsx +24 -8
  547. package/src/state/hooks/quote-streaming.ts +41 -26
  548. package/src/sync/core-contributors.ts +4 -0
  549. package/src/telemetry/crash-reports.ts +4 -1
  550. package/src/test-support/cli-context.ts +10 -2
  551. package/src/theme/heat-colors.ts +266 -84
  552. package/src/time-series/resolve.ts +1 -1
  553. package/src/time-series/use-chart-resolution.ts +1 -1
  554. package/src/types/context-menu.ts +6 -0
  555. package/src/types/data-provider.ts +2 -0
  556. package/src/types/financials.ts +3 -0
  557. package/src/types/headless.ts +79 -2
  558. package/src/types/plugin.ts +20 -1
  559. package/src/types/ticker.ts +15 -0
  560. package/src/ui/host.tsx +14 -0
  561. package/src/updater/index.ts +9 -11
  562. package/src/utils/async-deadline.ts +21 -0
  563. package/src/utils/cli-output.ts +5 -1
  564. package/src/utils/crypto-pair.ts +41 -0
  565. package/src/utils/currency-units.ts +14 -0
  566. package/src/utils/depositary-receipt.ts +26 -0
  567. package/src/utils/exchanges.ts +72 -0
  568. package/src/utils/fire-and-forget.ts +24 -0
  569. package/src/utils/format.ts +13 -0
  570. package/src/utils/price-earnings.ts +70 -3
  571. package/src/utils/price-history.ts +58 -13
  572. package/src/utils/rpc-timeout-error.ts +27 -0
  573. package/src/utils/sec.ts +13 -1
  574. package/src/utils/utc-time.ts +79 -0
@@ -1,60 +1,133 @@
1
1
  import type { EarningsCalendarPayload, EarningsCalendarQuery } from "../../../api-client/earnings";
2
2
  import type { SupplyChainPayload } from "../../../api-client/supply-chain";
3
+ import { DEFAULT_GRAPH_OPTIONS, type GraphOptions, type GraphPayload } from "../../../api-client/supply-chain-graph";
3
4
  import { errorMessage } from "../../../utils/errors";
4
5
  import { addDays, newYorkToday } from "../earnings/board-model";
5
6
  import { loadEarningsBoard } from "../earnings/client";
6
7
  import { loadSupplyChain } from "../supply-chain/client";
7
- import { projectRipple, rippleCompanyTickers, RIPPLE_DAYS, type RippleRow } from "./model";
8
+ import { cachedGraph, loadGraph } from "../supply-chain/graph-client";
9
+ import { projectRipple, projectSecondHop, rippleCompanyTickers, RIPPLE_DAYS, secondHopLinks, type RippleRow, type SecondHopRow } from "./model";
8
10
 
9
11
  export interface RippleSources {
10
12
  supplyChain(symbol: string): Promise<SupplyChainPayload>;
11
13
  calendar(query: EarningsCalendarQuery): Promise<EarningsCalendarPayload>;
14
+ /** SPLC's graph, which returns both hops around a holding in one call. Absent when the account sees one hop only. */
15
+ graph?(symbol: string, options: GraphOptions): Promise<GraphPayload>;
12
16
  }
13
17
 
18
+ /**
19
+ * The graph query behind the 2 hops tab. Supplier and customer links only, and
20
+ * only evidence from filings, company announcements and calls (graph classes
21
+ * structured and primary), the tiers the 1 hop tab's disclosures are read with.
22
+ * The server filters before it ranks and caps each direction, so news and
23
+ * partner routes cannot use up the 50 companies a direction returns.
24
+ */
25
+ const RIPPLE_GRAPH_OPTIONS: GraphOptions = { ...DEFAULT_GRAPH_OPTIONS, depth: 2, roles: ["customer", "supplier"], tiers: ["structured", "primary"] };
26
+
27
+ /**
28
+ * What opens SPLC's Path view on a 2 hops row: from the holding to the company
29
+ * that reports, on RIPL's own query in the row's direction, so the route the
30
+ * row shows is the one Path lists.
31
+ */
32
+ export function rippleRouteSettings(row: Pick<SecondHopRow, "company" | "link">): Record<string, string> {
33
+ return { tab: "path", to: row.company, depth: String(RIPPLE_GRAPH_OPTIONS.depth), direction: row.link === "supplier" ? "upstream" : "downstream",
34
+ roles: RIPPLE_GRAPH_OPTIONS.roles.join(","), tiers: RIPPLE_GRAPH_OPTIONS.tiers.join(",") };
35
+ }
36
+
37
+ interface Failure { symbol: string; error: string }
38
+
14
39
  export interface RippleSnapshot {
15
40
  rows: RippleRow[];
16
41
  /** Holdings whose disclosures could not be read, with the reason. */
17
- failures: { symbol: string; error: string }[];
42
+ failures: Failure[];
18
43
  /** Holdings whose disclosures the free preview cut short, so a missing link may only be hidden. */
19
44
  truncated: string[];
45
+ /**
46
+ * Companies two hops away, when asked for. `locked`: the account sees one hop
47
+ * only. `truncated`: holdings whose graph search stopped at a limit, with the
48
+ * server's reasons, so a company missing there may only be cut off.
49
+ */
50
+ secondHop: { rows: SecondHopRow[]; failures: Failure[]; truncated: { symbol: string; reasons: string[] }[]; locked: boolean } | null;
20
51
  stale: boolean;
21
52
  from: string;
22
53
  to: string;
23
54
  }
24
55
 
25
- /** The same caches SPLC and ERN fill, so opening either pane after this costs nothing. */
26
- export function cachedRippleSources(accessKey: string, force = false): RippleSources & { staleFlags: boolean[] } {
56
+ /**
57
+ * The same caches SPLC and ERN fill, so opening either pane after this costs
58
+ * nothing. `graph`: no graph source, graphs read through the cache, or graphs
59
+ * from the cache alone (fetched only when missing) while 2 hops is not on screen.
60
+ */
61
+ export function cachedRippleSources(accessKey: string, force = false, graph: "off" | "load" | "cached" = "off"): RippleSources & { staleFlags: boolean[] } {
27
62
  const staleFlags: boolean[] = [];
28
63
  return {
29
64
  staleFlags,
30
65
  supplyChain: async (symbol) => { const result = await loadSupplyChain(symbol, accessKey, force); staleFlags.push(result.stale); return result.payload; },
31
66
  calendar: async (query) => { const result = await loadEarningsBoard(query, force); staleFlags.push(result.stale); return result.payload; },
67
+ // Cached by the full query, apart from SPLC Graph's default one.
68
+ ...(graph !== "off" ? { graph: async (symbol: string, options: GraphOptions) => {
69
+ const cached = graph === "cached" ? cachedGraph(symbol, "", options, accessKey) : null;
70
+ const result = cached ?? await loadGraph(symbol, "", options, accessKey, force); staleFlags.push(result.stale); return result.payload;
71
+ } } : {}),
32
72
  };
33
73
  }
34
74
 
35
75
  const CONCURRENCY = 6;
36
76
 
37
- export async function loadRipple(holdings: readonly string[], sources: RippleSources & { staleFlags?: boolean[] }, now = new Date()): Promise<RippleSnapshot> {
77
+ async function eachLimited(symbols: readonly string[], load: (symbol: string) => Promise<void>) {
78
+ const queue = [...symbols];
79
+ await Promise.all(Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
80
+ for (let symbol = queue.shift(); symbol; symbol = queue.shift()) await load(symbol);
81
+ }));
82
+ }
83
+
84
+ const locked = () => ({ graphs: new Map<string, GraphPayload>(), failures: [] as Failure[], locked: true });
85
+ const isPreview = (graphs: ReadonlyMap<string, GraphPayload>) => [...graphs.values()].some((graph) => graph.access === "preview");
86
+
87
+ /** One graph call per holding. The first answer says whether the account sees two hops; a one-hop preview skips the rest. */
88
+ async function loadGraphs(holdings: readonly string[], graph: NonNullable<RippleSources["graph"]>) {
89
+ const graphs = new Map<string, GraphPayload>();
90
+ const failures: Failure[] = [];
91
+ const load = async (symbol: string) => {
92
+ try { graphs.set(symbol, await graph(symbol, RIPPLE_GRAPH_OPTIONS)); }
93
+ catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
94
+ };
95
+ const [first, ...rest] = holdings;
96
+ if (first) await load(first);
97
+ if (isPreview(graphs)) return locked();
98
+ await eachLimited(rest, load);
99
+ // Only when the first holding failed can a preview get this far.
100
+ if (isPreview(graphs)) return locked();
101
+ return { graphs, failures: failures.sort((a, b) => a.symbol.localeCompare(b.symbol)), locked: false };
102
+ }
103
+
104
+ export async function loadRipple(holdings: readonly string[], sources: RippleSources & { staleFlags?: boolean[] }, now = new Date(), { secondHop = false } = {}): Promise<RippleSnapshot> {
38
105
  const from = newYorkToday(now);
39
106
  const to = addDays(from, RIPPLE_DAYS);
107
+ const symbols = [...new Set(holdings.map((symbol) => symbol.toUpperCase()))];
40
108
  const chains = new Map<string, SupplyChainPayload>();
41
- const failures: RippleSnapshot["failures"] = [];
42
- const queue = [...new Set(holdings.map((symbol) => symbol.toUpperCase()))];
43
- await Promise.all(Array.from({ length: Math.min(CONCURRENCY, queue.length) }, async () => {
44
- for (let symbol = queue.shift(); symbol; symbol = queue.shift()) {
45
- try { chains.set(symbol, await sources.supplyChain(symbol)); }
46
- catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
47
- }
48
- }));
49
- const companies = rippleCompanyTickers(chains);
109
+ const failures: Failure[] = [];
110
+ await eachLimited(symbols, async (symbol) => {
111
+ try { chains.set(symbol, await sources.supplyChain(symbol)); }
112
+ catch (error) { failures.push({ symbol, error: errorMessage(error) }); }
113
+ });
114
+ const second = !secondHop ? null : sources.graph ? await loadGraphs(symbols, sources.graph) : locked();
115
+ const graphs = second?.graphs ?? new Map<string, GraphPayload>();
116
+ const companies = [...rippleCompanyTickers(chains), ...secondHopLinks(chains, graphs).map((link) => link.company.ticker!.toUpperCase())];
50
117
  const reports: EarningsCalendarPayload["reports"] = [];
51
118
  if (companies.length) {
52
- const symbols = [...new Set([...companies, ...holdings.map((symbol) => symbol.toUpperCase())])].sort();
53
- for (let offset = 0; offset < symbols.length; offset += 200) {
54
- const payload = await sources.calendar({ from, to, perDay: 0, symbols: symbols.slice(offset, offset + 200) });
119
+ const calendarSymbols = [...new Set([...companies, ...symbols])].sort();
120
+ for (let offset = 0; offset < calendarSymbols.length; offset += 200) {
121
+ const payload = await sources.calendar({ from, to, perDay: 0, symbols: calendarSymbols.slice(offset, offset + 200) });
55
122
  reports.push(...payload.reports);
56
123
  }
57
124
  }
58
125
  const truncated = [...chains].filter(([, chain]) => chain.truncated).map(([symbol]) => symbol).sort();
59
- return { rows: projectRipple(chains, reports), failures, truncated, stale: !!sources.staleFlags?.some(Boolean), from, to };
126
+ return {
127
+ rows: projectRipple(chains, reports), failures, truncated,
128
+ secondHop: second ? { rows: projectSecondHop(chains, graphs, reports), failures: second.failures, locked: second.locked,
129
+ truncated: [...graphs].filter(([, graph]) => graph.truncated || !graph.search.complete)
130
+ .map(([symbol, graph]) => ({ symbol, reasons: graph.search.reasons })).sort((a, b) => a.symbol.localeCompare(b.symbol)) } : null,
131
+ stale: !!sources.staleFlags?.some(Boolean), from, to,
132
+ };
60
133
  }
@@ -1,35 +1,55 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { fetchEarningsCalendar } from "../earnings/client";
3
3
  import { fetchSupplyChain } from "../supply-chain/client";
4
+ import { fetchSupplyGraph } from "../supply-chain/graph-client";
4
5
  import { loadRipple } from "./client";
5
- import { revenueOwner } from "./model";
6
+ import { hopLabel, revenueOwner } from "./model";
6
7
 
7
8
  const percent = (value: unknown) => typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "--";
8
9
 
9
10
  export const earningsRippleHeadless: HeadlessPaneDefinition<"bundle"> = {
10
11
  shape: "bundle",
12
+ freshness: { source: "SEC filings and Gloom Cloud", status: "not-a-feed", basis: "filings and earnings calendar" },
11
13
  argument: { kind: "symbol-list", placeholder: "tickers", description: "Holdings to check against their customers' and suppliers' report dates.", minimum: 1, maximum: 60 },
12
- options: [],
14
+ options: [{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "direct", values: [{ value: "direct" }, { value: "two-hop" }],
15
+ description: "Direct customers and suppliers, or also companies two hops away (Pro)." }],
13
16
  describe: (args) => `RIPL ${args.symbols.join(", ")}`,
14
- discovery: { screenshotReadiness: "live-dom", limitations: ["Customers each holding names in its own US filings, and suppliers whose US filings name it, with a share of revenue."] },
17
+ discovery: { screenshotReadiness: "live-dom", limitations: ["Customers each holding names in its own US filings, and suppliers whose US filings name it, with a share of revenue.",
18
+ "Two hops: a supplier's supplier or a customer's customer from the supply chain graph, read from filings, company announcements and calls only, Pro only; companies already one hop away are left out."] },
15
19
  async load(args, ctx) {
20
+ const secondHop = args.options.tab === "two-hop";
16
21
  const snapshot = await loadRipple(args.symbols, {
17
22
  supplyChain: (symbol) => fetchSupplyChain(symbol, ctx.apiClient),
18
23
  calendar: (query) => fetchEarningsCalendar(query, ctx.apiClient),
19
- });
24
+ graph: (symbol, options) => fetchSupplyGraph(symbol, options, "", ctx.apiClient),
25
+ }, undefined, { secondHop });
20
26
  ctx.signal.throwIfAborted();
27
+ const second = snapshot.secondHop;
21
28
  return {
22
29
  sections: [{ title: `Customers and suppliers reporting ${snapshot.from} to ${snapshot.to}`, columns: [
23
30
  { key: "date", header: "Date" }, { key: "timing", header: "Time" }, { key: "company", header: "Reports" }, { key: "link", header: "Link" },
24
31
  { key: "holding", header: "Holding" }, { key: "pctOfRevenue", header: "Revenue %" }, { key: "revenueOf", header: "Of" }, { key: "averageMove", header: "Avg move", format: percent },
25
32
  { key: "holdingDate", header: "Holding reports" }, { key: "period", header: "Filing period" },
26
- ], rows: snapshot.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer of" : "supplier to", revenueOf: revenueOwner(row) })) }],
27
- complete: snapshot.failures.length === 0 && snapshot.truncated.length === 0,
33
+ ], rows: snapshot.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer of" : "supplier to", revenueOf: revenueOwner(row) })) },
34
+ ...(second && !second.locked ? [{ title: `Two hops away, reporting ${snapshot.from} to ${snapshot.to}`, columns: [
35
+ { key: "date", header: "Date" }, { key: "timing", header: "Time" }, { key: "company", header: "Reports" }, { key: "link", header: "Link" },
36
+ { key: "via", header: "Via" }, { key: "holding", header: "Holding" }, { key: "hop1", header: "Holding to via" }, { key: "hop2", header: "Via to company" },
37
+ { key: "exposure", header: "Estimated exposure" }, { key: "averageMove", header: "Avg move", format: percent }, { key: "holdingDate", header: "Holding reports" },
38
+ ], rows: second.rows.map((row) => ({ ...row, timing: row.timing?.toUpperCase() ?? null, link: row.link === "customer" ? "customer's customer" : "supplier's supplier",
39
+ hop1: hopLabel(row.hops[0]), hop2: hopLabel(row.hops[1]) })) }] : []),
40
+ ],
41
+ complete: snapshot.failures.length === 0 && snapshot.truncated.length === 0 && !second?.locked && !second?.failures.length && !second?.truncated.length,
28
42
  unavailableSymbols: snapshot.failures.map((failure) => failure.symbol),
29
43
  // Same wording as SPLC: the preview's cut hides relationships, it does not mean there are none.
30
44
  errors: [...snapshot.failures.map((failure) => `${failure.symbol}: ${failure.error}`),
31
- ...(snapshot.truncated.length ? ["Additional relationships need Gloom Pro"] : [])],
32
- metadata: { from: snapshot.from, to: snapshot.to, truncated: snapshot.truncated, unit: "percent of the seller's revenue, as the seller's filing discloses it", methodology: "docs/research-data.md#earnings-ripple" },
45
+ ...(snapshot.truncated.length ? ["Additional relationships need Gloom Pro"] : []),
46
+ ...(second?.locked ? ["Companies two hops away need Gloom Pro"] : []),
47
+ ...(second?.failures ?? []).map((failure) => `${failure.symbol} graph: ${failure.error}`),
48
+ // As SPLC words it: the graph stopped at a limit, so companies two hops away may be missing.
49
+ ...(second?.truncated ?? []).map(({ symbol, reasons }) => `${symbol}: ${reasons.length
50
+ ? `Graph search incomplete: ${reasons.join(", ").replaceAll("_", " ")}` : "Graph results are truncated"}`)],
51
+ metadata: { from: snapshot.from, to: snapshot.to, truncated: snapshot.truncated, ...(second ? { twoHopTruncated: second.truncated.map(({ symbol }) => symbol) } : {}),
52
+ unit: "percent of the seller's revenue, as the seller's filing discloses it", methodology: "docs/research-data.md#earnings-ripple" },
33
53
  };
34
54
  },
35
55
  };
@@ -18,8 +18,8 @@ export const earningsRippleModule: PluginModule = {
18
18
  }],
19
19
  paneTemplates: [{
20
20
  id: "earnings-ripple-pane", paneId: "earnings-ripple", label: "Earnings Ripple",
21
- description: "Customers of your holdings that report soon, with the share of each holding's revenue they make up.",
22
- keywords: ["ripl", "ripple", "earnings", "customers", "supply chain", "exposure", "read-through"],
21
+ description: "Customers and suppliers of your holdings that report soon, with the disclosed revenue share, and with Pro the companies two hops away.",
22
+ keywords: ["ripl", "ripple", "earnings", "customers", "supply chain", "exposure", "read-through", "suppliers", "two hop", "second order"],
23
23
  shortcut: { prefix: "RIPL", argPlaceholder: "tickers", argKind: "ticker-list", argOptional: true, openWithoutArg: true },
24
24
  headless: earningsRippleHeadless,
25
25
  canCreate: () => true,
@@ -1,7 +1,9 @@
1
1
  import type { EarningsCalendarReport, EarningsTiming } from "../../../api-client/earnings";
2
- import type { SupplyChainPayload, SupplyRow } from "../../../api-client/supply-chain";
2
+ import type { SupplyChainPayload, SupplyEntity, SupplyRow } from "../../../api-client/supply-chain";
3
+ import type { GraphPath, GraphPayload } from "../../../api-client/supply-chain-graph";
3
4
  import { isUsListingExchange } from "../../../utils/exchanges";
4
5
  import { MIN_AVERAGE_REPORTS } from "../earnings/board-model";
6
+ import { exposureLabel } from "../supply-chain/graph-model";
5
7
 
6
8
  /** Days ahead the calendar is read. */
7
9
  export const RIPPLE_DAYS = 30;
@@ -71,18 +73,24 @@ export function rippleCompanyTickers(chains: ReadonlyMap<string, SupplyChainPayl
71
73
  return [...companies];
72
74
  }
73
75
 
74
- /**
75
- * Customers a holding names in its own filings (`says`), and suppliers whose
76
- * filings name the holding as a customer (`names`), that report in the
77
- * calendar: soonest first, then by the share.
78
- */
79
- export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, reports: readonly EarningsCalendarReport[]): RippleRow[] {
76
+ /** Each company's soonest report in the window. */
77
+ function nextReports(reports: readonly EarningsCalendarReport[]) {
80
78
  const next = new Map<string, EarningsCalendarReport>();
81
79
  for (const report of reports) {
82
80
  const key = report.symbol.toUpperCase();
83
81
  const current = next.get(key);
84
82
  if (!current || report.date < current.date) next.set(key, report);
85
83
  }
84
+ return next;
85
+ }
86
+
87
+ /**
88
+ * Customers a holding names in its own filings (`says`), and suppliers whose
89
+ * filings name the holding as a customer (`names`), that report in the
90
+ * calendar: soonest first, then by the share.
91
+ */
92
+ export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, reports: readonly EarningsCalendarReport[]): RippleRow[] {
93
+ const next = nextReports(reports);
86
94
  const rows: RippleRow[] = [];
87
95
  for (const [holding, chain] of chains) {
88
96
  for (const { row, link } of rippleLinks(chain)) {
@@ -96,3 +104,85 @@ export function projectRipple(chains: ReadonlyMap<string, SupplyChainPayload>, r
96
104
  }
97
105
  return rows.sort((a, b) => a.date.localeCompare(b.date) || b.pctOfRevenue - a.pctOfRevenue || a.holding.localeCompare(b.holding));
98
106
  }
107
+
108
+ /** One hop of a two-hop route, with the share the reporting company's filing discloses. */
109
+ export interface RippleHop {
110
+ /** Whose figure it is: the reporting company. */
111
+ owner: string;
112
+ pct: number | null;
113
+ basis: SupplyRow["pctBasis"];
114
+ scoped: boolean;
115
+ }
116
+
117
+ export interface SecondHopRow extends Omit<RippleRow, "pctOfRevenue" | "pctScope" | "period"> {
118
+ /** The company between the holding and the one that reports. */
119
+ via: string;
120
+ viaName: string;
121
+ /** Holding to via, then via to the company. */
122
+ hops: [RippleHop, RippleHop];
123
+ /** Chained estimate along the route, when every hop's figure is compatible. */
124
+ exposure: string | null;
125
+ }
126
+
127
+ const entityLabel = (entity: SupplyEntity) => entity.ticker?.toUpperCase() ?? entity.name;
128
+
129
+ interface SecondHopLink { holding: string; company: SupplyEntity; via: SupplyEntity; link: "customer" | "supplier"; path: GraphPath; graph: GraphPayload }
130
+
131
+ /**
132
+ * Companies two disclosed hops from each holding: a supplier's supplier
133
+ * (upstream) or a customer's customer (downstream). Anyone the holding already
134
+ * has a direct link with, in its disclosures or in the graph, is hop 1 and is
135
+ * left out, as are companies outside the US calendar. One row per holding and
136
+ * company: the higher-scoring route when both directions reach it.
137
+ */
138
+ export function secondHopLinks(chains: ReadonlyMap<string, SupplyChainPayload>, graphs: ReadonlyMap<string, GraphPayload>): SecondHopLink[] {
139
+ const result: SecondHopLink[] = [];
140
+ for (const [holding, graph] of graphs) {
141
+ const chain = chains.get(holding);
142
+ const nodes = new Map(graph.nodes.map((node) => [node.id, node]));
143
+ const reaches = [...graph.upstream, ...graph.downstream];
144
+ const direct = new Set([holding.toUpperCase(),
145
+ ...[...chain?.says ?? [], ...chain?.names ?? []].flatMap((row) => row.counterparty.ticker ? [row.counterparty.ticker.toUpperCase()] : []),
146
+ ...reaches.filter((reach) => reach.hops === 1).flatMap((reach) => nodes.get(reach.entityId)?.ticker?.toUpperCase() ?? [])]);
147
+ const best = new Map<string, SecondHopLink>();
148
+ for (const reach of reaches) {
149
+ const path = reach.shortestPath;
150
+ const company = nodes.get(reach.entityId);
151
+ const via = nodes.get(path.nodeIds[1] ?? "");
152
+ const ticker = company?.ticker?.toUpperCase();
153
+ if (reach.hops !== 2 || path.hops !== 2 || !company || !via || !ticker || company.aggregate || direct.has(ticker)) continue;
154
+ if (company.exchange && !isUsListingExchange(company.exchange)) continue;
155
+ const current = best.get(ticker);
156
+ if (!current || path.score > current.path.score) best.set(ticker, { holding, company, via, link: reach.direction === "upstream" ? "supplier" : "customer", path, graph });
157
+ }
158
+ result.push(...best.values());
159
+ }
160
+ return result;
161
+ }
162
+
163
+ function hopShare(graph: GraphPayload, linkId: string): RippleHop {
164
+ const link = graph.links.find((entry) => entry.id === linkId);
165
+ const evidence = link?.evidence.find((item) => item.id === link.primaryEvidenceId);
166
+ return { owner: evidence ? entityLabel(evidence.reporter) : "--", pct: evidence?.pctOfRevenue ?? null, basis: evidence?.pctBasis ?? null, scoped: !!evidence?.pctScope };
167
+ }
168
+
169
+ /** `30% of TSM`, `12% of AVGO purchases`, or `--` where the filing names the link without a figure. */
170
+ export function hopLabel(hop: RippleHop) {
171
+ if (hop.pct == null) return "--";
172
+ return `${hop.pct}%${hop.scoped ? "*" : ""} of ${hop.owner}${hop.basis && hop.basis !== "revenue" ? ` ${hop.basis}` : ""}`;
173
+ }
174
+
175
+ export function projectSecondHop(chains: ReadonlyMap<string, SupplyChainPayload>, graphs: ReadonlyMap<string, GraphPayload>, reports: readonly EarningsCalendarReport[]): SecondHopRow[] {
176
+ const next = nextReports(reports);
177
+ const rows: SecondHopRow[] = [];
178
+ for (const { holding, company, via, link, path, graph } of secondHopLinks(chains, graphs)) {
179
+ const ticker = company.ticker!.toUpperCase();
180
+ const report = next.get(ticker);
181
+ if (!report) continue;
182
+ rows.push({ id: `${holding}:${link}:2:${ticker}`, company: ticker, companyName: report.name ?? company.name, link, date: report.date, timing: report.timing,
183
+ averageMove: report.averageReports >= MIN_AVERAGE_REPORTS ? report.averageMove : null, holding, holdingDate: next.get(holding.toUpperCase())?.date ?? null,
184
+ via: entityLabel(via), viaName: via.name, hops: [hopShare(graph, path.linkIds[0]!), hopShare(graph, path.linkIds[1]!)],
185
+ exposure: path.exposure ? exposureLabel(path, graph) : null });
186
+ }
187
+ return rows.sort((a, b) => a.date.localeCompare(b.date) || a.holding.localeCompare(b.holding) || a.company.localeCompare(b.company));
188
+ }
@@ -1,20 +1,25 @@
1
- import { useCallback, useMemo } from "react";
2
- import { DataTableView, EmptyState, PaneStatusBody, usePaneFooter, usePaneNoticeFooter,
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { DataTableView, EmptyState, PaneStatusBody, usePaneFooter, usePaneNoticeFooter, usePaneTabs,
3
3
  type DataTableCell, type DataTableColumn, type PaneHint } from "../../../components";
4
4
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
5
+ import { getTableWidth } from "../../../components/ui/table-layout";
5
6
  import { usePlanAccess } from "../../../api-client/plan-access";
6
7
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginAppActions, usePluginPaneState, useTickers } from "../../../public/react";
7
8
  import type { PaneProps } from "../../../types/plugin";
8
9
  import { Box } from "../../../ui";
9
10
  import { isUsListingExchange } from "../../../utils/exchanges";
11
+ import { displayWidth } from "../../../utils/format";
10
12
  import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
13
+ import { UpgradeLabel } from "../shared/locked-rows";
11
14
  import { useResearchCloudSession } from "../shared/research-cloud-session";
12
- import { cachedRippleSources, loadRipple } from "./client";
13
- import { revenueOwner, RIPPLE_DAYS, type RippleRow } from "./model";
15
+ import { cachedRippleSources, loadRipple, rippleRouteSettings } from "./client";
16
+ import { hopLabel, revenueOwner, RIPPLE_DAYS, type RippleRow, type SecondHopRow } from "./model";
14
17
 
15
- /** Calls per refresh stay bounded: one disclosure request per holding. */
18
+ /** Calls per refresh stay bounded: one disclosure request per holding, plus one graph request per holding on the second hop. */
16
19
  const RIPPLE_HOLDINGS_LIMIT = 60;
17
20
 
21
+ const TABS = [{ value: "direct", label: "1 hop" }, { value: "two-hop", label: "2 hops" }];
22
+
18
23
  const COLUMNS: DataTableColumn[] = [
19
24
  { id: "date", label: "Date", width: 11, align: "left" },
20
25
  { id: "timing", label: "Time", width: 5, align: "left" },
@@ -26,6 +31,67 @@ const COLUMNS: DataTableColumn[] = [
26
31
  { id: "holdingDate", label: "Holding reports", width: 16, align: "right" },
27
32
  ];
28
33
 
34
+ const SECOND_HOP_COLUMNS: DataTableColumn[] = [
35
+ { id: "date", label: "Date", width: 10, align: "left" },
36
+ { id: "timing", label: "Time", width: 5, align: "left" },
37
+ { id: "company", label: "Reports", width: 14, flexGrow: 1, align: "left" },
38
+ { id: "link", label: "Link", width: 19, align: "left" },
39
+ { id: "via", label: "Via", width: 9, align: "left" },
40
+ { id: "holding", label: "Holding", width: 9, align: "left" },
41
+ { id: "shares", label: "Share per hop", width: 28, align: "left" },
42
+ { id: "move", label: "Avg move", width: 9, align: "right" },
43
+ { id: "holdingDate", label: "Holding reports", width: 16, align: "right" },
44
+ ];
45
+ const HOP_SEPARATOR = " › ";
46
+
47
+ /**
48
+ * Holding to via, then via to the company that reports. The first hop is
49
+ * padded with no-break spaces, which the desktop does not collapse.
50
+ */
51
+ function shareText(row: SecondHopRow, firstHopWidth: number) {
52
+ const first = hopLabel(row.hops[0]);
53
+ return `${first}${"\u00a0".repeat(Math.max(0, firstHopWidth - displayWidth(first)))}${HOP_SEPARATOR}${hopLabel(row.hops[1])}`;
54
+ }
55
+
56
+ /**
57
+ * The 2 hops table at this width. The date, the company and the route (via,
58
+ * holding, every hop's share) always show, so the route never scrolls out of
59
+ * view; a pane too narrow for all of it clips the shares. What room is left
60
+ * goes to the rest, most needed first, each only if it fits: the link, the
61
+ * report time, a long via name in full, the average move, shares lined up so
62
+ * every second hop starts at the same place, and the holding's own report.
63
+ * `firstHopWidth` is what the first hop is padded to, 0 when not lined up.
64
+ */
65
+ function secondHopLayout(width: number, rows: readonly SecondHopRow[]) {
66
+ const widest = (texts: readonly string[]) => Math.max(0, ...texts.map(displayWidth));
67
+ const firstHop = widest(rows.map((row) => hopLabel(row.hops[0])));
68
+ const shares = Math.min(44, Math.max(28, widest(rows.map((row) => shareText(row, 0)))));
69
+ const aligned = Math.min(44, Math.max(shares, firstHop + HOP_SEPARATOR.length + widest(rows.map((row) => hopLabel(row.hops[1])))));
70
+ const widths: Record<string, number> = { via: 9, shares };
71
+ let firstHopWidth = 0;
72
+ const shown = new Set(["date", "company", "via", "holding", "shares"]);
73
+ const columns = () => SECOND_HOP_COLUMNS.filter((column) => shown.has(column.id))
74
+ .map((column) => widths[column.id] ? { ...column, width: widths[column.id]! } : column);
75
+ // The vertical scrollbar takes a cell, including when the rows fit.
76
+ const room = () => width - 1 - getTableWidth(columns());
77
+ const fitted = widths.shares = Math.max(28, shares + Math.min(0, room()));
78
+ const extras: [add: () => void, undo: () => void][] = [
79
+ ...["link", "timing"].map((id): [() => void, () => void] => [() => shown.add(id), () => shown.delete(id)]),
80
+ [() => { widths.via = Math.min(18, Math.max(9, widest(rows.map((row) => row.via)))); }, () => { widths.via = 9; }],
81
+ [() => shown.add("move"), () => shown.delete("move")],
82
+ [() => { widths.shares = aligned; firstHopWidth = firstHop; }, () => { widths.shares = fitted; firstHopWidth = 0; }],
83
+ [() => shown.add("holdingDate"), () => shown.delete("holdingDate")],
84
+ ];
85
+ for (const [add, undo] of extras) {
86
+ add();
87
+ if (room() < 0) undo();
88
+ }
89
+ return { columns: columns(), firstHopWidth };
90
+ }
91
+
92
+ const move = (row: RippleRow | SecondHopRow): DataTableCell =>
93
+ ({ text: row.averageMove == null ? "--" : `±${(row.averageMove * 100).toFixed(1)}%`, value: row.averageMove });
94
+
29
95
  function renderCell(row: RippleRow, column: DataTableColumn): DataTableCell {
30
96
  switch (column.id) {
31
97
  case "date": return { text: row.date };
@@ -34,16 +100,34 @@ function renderCell(row: RippleRow, column: DataTableColumn): DataTableCell {
34
100
  case "link": return { text: row.link === "customer" ? "customer of" : "supplier to" };
35
101
  case "holding": return { text: row.holding };
36
102
  case "pct": return { text: `${row.pctOfRevenue}%${row.pctScope ? "*" : ""} of ${revenueOwner(row)}`, value: row.pctOfRevenue };
37
- case "move": return { text: row.averageMove == null ? "--" : `±${(row.averageMove * 100).toFixed(1)}%`, value: row.averageMove };
103
+ case "move": return move(row);
104
+ default: return { text: row.holdingDate ?? "--" };
105
+ }
106
+ }
107
+
108
+ function renderSecondHopCell(row: SecondHopRow, column: DataTableColumn, firstHopWidth: number): DataTableCell {
109
+ switch (column.id) {
110
+ case "date": return { text: row.date };
111
+ case "timing": return { text: row.timing?.toUpperCase() ?? "--" };
112
+ case "company": return { text: `${row.company} ${row.companyName}` };
113
+ case "link": return { text: row.link === "customer" ? "customer's customer" : "supplier's supplier" };
114
+ case "via": return { text: row.via };
115
+ case "holding": return { text: row.holding };
116
+ case "shares": return { text: shareText(row, firstHopWidth), value: shareText(row, 0) };
117
+ case "move": return move(row);
38
118
  default: return { text: row.holdingDate ?? "--" };
39
119
  }
40
120
  }
41
121
 
42
122
  export function EarningsRipplePane({ width, height, focused }: PaneProps) {
43
123
  const [symbolsText] = usePaneSettingValue("symbols", "");
124
+ const [openingTab] = usePaneSettingValue("tab", "direct");
125
+ const [savedTab, setTab] = usePluginPaneState<string>("tab", openingTab);
126
+ const tab = savedTab === "two-hop" ? "two-hop" : "direct";
44
127
  const tickers = useTickers();
45
128
  const { createPaneFromTemplate } = usePluginAppActions();
46
129
  const [selectedId, setSelectedId] = usePluginPaneState<string | null>("selectedRow", null);
130
+ const [selectedSecondId, setSelectedSecondId] = usePluginPaneState<string | null>("selectedSecondHop", null);
47
131
  const session = useResearchCloudSession();
48
132
  const access = usePlanAccess();
49
133
  const accessKey = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
@@ -58,46 +142,87 @@ export function EarningsRipplePane({ width, height, focused }: PaneProps) {
58
142
  }, [symbolsText, tickers]);
59
143
  const holdings = scope.holdings;
60
144
  const holdingsKey = holdings.join(",");
61
- const loader = useCallback((force: boolean) => loadRipple(holdings, cachedRippleSources(accessKey, force)), [holdingsKey, accessKey]);
145
+ const secondHop = tab === "two-hop";
146
+ // A free account's graph stops at one hop, so it makes no graph requests and its second tab reuses the first load.
147
+ // Once a Pro account opens 2 hops the graph stays in the load, so going back and forth between tabs never reloads.
148
+ // A refresh on 1 hop reads the graphs from the cache instead of asking for them again.
149
+ const [graphOpened, setGraphOpened] = useState(false);
150
+ useEffect(() => { if (secondHop && access.hasProAccess) setGraphOpened(true); }, [secondHop, access.hasProAccess]);
151
+ const graphHop = access.hasProAccess && (secondHop || graphOpened);
152
+ const secondHopShown = useRef(secondHop);
153
+ secondHopShown.current = secondHop;
154
+ const loader = useCallback((force: boolean) => loadRipple(holdings,
155
+ cachedRippleSources(accessKey, force, !graphHop ? "off" : secondHopShown.current ? "load" : "cached"), undefined, { secondHop: graphHop }),
156
+ [holdingsKey, accessKey, graphHop]);
62
157
  const ripple = useAsyncResource(holdings.length ? loader : null);
63
158
  useAutoRefresh(ripple.updatedAt, ripple.load);
64
159
  usePaneRefreshKey(() => { void ripple.reload(); }, { focused });
160
+ const { strip, rows: tabRows } = usePaneTabs(holdings.length ? { tabs: TABS, activeValue: tab, onSelect: setTab, focused, dense: true } : null);
65
161
 
66
162
  const rows = ripple.data?.rows ?? [];
163
+ const second = secondHop ? ripple.data?.secondHop ?? null : null;
164
+ const secondRows = second?.rows ?? [];
67
165
  const selected = rows.find((row) => row.id === selectedId) ?? rows[0] ?? null;
166
+ const selectedSecond = secondRows.find((row) => row.id === selectedSecondId) ?? secondRows[0] ?? null;
167
+ const current = secondHop ? selectedSecond : selected;
68
168
  const failures = ripple.data?.failures ?? [];
69
169
  const truncated = ripple.data?.truncated ?? [];
170
+ const locked = secondHop && (!access.hasProAccess || !!second?.locked);
70
171
  const openUpgrade = useCloudUpgradeAction("ripl");
71
172
  usePaneNoticeFooter({ registrationId: "earnings-ripple-notices", focused, notices: [
72
173
  ...(scope.total > holdings.length ? [`Checking ${holdings.length} of ${scope.total} holdings${scope.named ? "" : ", positions first. Name tickers in pane settings to pick others"}`] : []),
73
174
  ...(failures.length ? [`No disclosures for ${failures.map((failure) => failure.symbol).join(", ")}`] : []),
74
- ...(truncated.length ? [`Free preview: top three customers and suppliers for ${truncated.join(", ")}`] : []),
175
+ ...(!secondHop && truncated.length ? [`Free preview: top three customers and suppliers for ${truncated.join(", ")}`] : []),
176
+ ...(second?.failures.length ? [`No two-hop graph for ${second.failures.map((failure) => failure.symbol).join(", ")}`] : []),
177
+ // The graph returns at most 50 companies per direction, so a large holding's list can be cut short.
178
+ ...(second?.truncated.length ? [`Graph search capped for ${second.truncated.map((entry) => entry.symbol).join(", ")}; some companies two hops away may be missing`] : []),
75
179
  ] });
76
180
  const openSupply = useCallback((row: RippleRow) => createPaneFromTemplate("supply-chain-pane", { symbol: row.holding }), [createPaneFromTemplate]);
181
+ // SPLC's own table does not reach a company two hops away; its Path view shows the row's route.
182
+ const openRoute = useCallback((row: SecondHopRow) => createPaneFromTemplate("supply-chain-pane", { symbol: row.holding, values: rippleRouteSettings(row) }),
183
+ [createPaneFromTemplate]);
77
184
  usePaneFooter("earnings-ripple", () => {
78
185
  const hints: PaneHint[] = [
79
- ...(selected ? [
80
- { id: "supply", key: "s", label: "upply chain", onPress: () => openSupply(selected) },
81
- { id: "earnings", key: "e", label: "arnings", onPress: () => createPaneFromTemplate("earnings-calendar-pane", { arg: selected.company }) },
82
- ] : []),
83
- ...(truncated.length ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
186
+ ...(secondHop && selectedSecond ? [{ id: "supply", key: "s", label: "upply chain", title: `Supply chain path from ${selectedSecond.holding} to ${selectedSecond.company}`,
187
+ onPress: () => openRoute(selectedSecond) }]
188
+ : !secondHop && selected ? [{ id: "supply", key: "s", label: "upply chain", onPress: () => openSupply(selected) }] : []),
189
+ ...(current ? [{ id: "earnings", key: "e", label: "arnings", onPress: () => createPaneFromTemplate("earnings-calendar-pane", { arg: current.company }) }] : []),
190
+ ...((secondHop ? locked : truncated.length) ? [{ id: "upgrade", key: CLOUD_PLAN_KEY, label: "upgrade", title: "Upgrade to Pro", onPress: openUpgrade }] : []),
84
191
  ];
85
192
  return { info: [
86
- ...(ripple.loading ? [{ id: "loading", parts: [{ text: "loading disclosures", tone: "muted" as const }] }] : []),
193
+ ...(ripple.loading ? [{ id: "loading", parts: [{ text: secondHop && graphHop ? "loading supply chain graph" : "loading disclosures", tone: "muted" as const }] }] : []),
87
194
  ...(ripple.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
88
195
  ], hints };
89
- }, [ripple.loading, ripple.data?.stale, selected, openSupply, createPaneFromTemplate, truncated.length, openUpgrade]);
196
+ }, [ripple.loading, ripple.data?.stale, current, selected, selectedSecond, secondHop, graphHop, locked, openSupply, openRoute, createPaneFromTemplate, truncated.length, openUpgrade]);
90
197
 
198
+ const secondLayout = useMemo(() => secondHopLayout(width, secondRows), [width, secondRows]);
199
+ const firstHopWidth = secondLayout.firstHopWidth;
200
+ const renderSecondCell = useCallback((row: SecondHopRow, column: DataTableColumn) => renderSecondHopCell(row, column, firstHopWidth), [firstHopWidth]);
201
+ const windowMeta = () => [["window", `${ripple.data?.from} to ${ripple.data?.to}`]];
202
+ const bodyHeight = Math.max(3, height - tabRows);
91
203
  return <Box width={width} height={height} flexDirection="column" overflow="hidden">
204
+ {strip}
92
205
  {!holdings.length ? <EmptyState title="Add tickers to a portfolio or watchlist, or name them in pane settings." />
93
- : <PaneStatusBody subject="earnings ripple" loading={ripple.loading && !ripple.data} error={!ripple.data ? ripple.error : null}
94
- empty={!!ripple.data && !rows.length} emptyTitle={`No disclosed customer or supplier of these holdings reports in the next ${RIPPLE_DAYS} days.`}>
95
- <DataTableView<RippleRow> focused={focused} columns={COLUMNS} items={rows} rootWidth={width} rootHeight={height}
96
- getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
97
- selection={{ kind: "id", selectedId: selected?.id ?? null, getId: (row) => row.id, onChange: setSelectedId }}
98
- onActivate={openSupply}
99
- getExportMetadata={() => [["window", `${ripple.data?.from} to ${ripple.data?.to}`], ["revenue share", "percent of the seller's revenue, as its filing discloses"]]}
100
- renderCell={renderCell} />
206
+ : secondHop && locked ? <Box paddingX={1} paddingTop={1}><EmptyState title="Companies two hops away need Gloom Pro."
207
+ actions={<UpgradeLabel text="Upgrade for two-hop supply chains" onPress={openUpgrade} role="ripple-upgrade" />} /></Box>
208
+ : <PaneStatusBody subject={secondHop ? "two-hop supply chains" : "earnings ripple"} loading={ripple.loading && (!ripple.data || (secondHop && !second))}
209
+ error={!ripple.data ? ripple.error : null}
210
+ empty={secondHop ? !!second && !secondRows.length : !!ripple.data && !rows.length}
211
+ emptyTitle={secondHop ? `No company two disclosed hops from these holdings reports in the next ${RIPPLE_DAYS} days.`
212
+ : `No disclosed customer or supplier of these holdings reports in the next ${RIPPLE_DAYS} days.`}>
213
+ {secondHop
214
+ ? <DataTableView<SecondHopRow> focused={focused} columns={secondLayout.columns} items={secondRows} rootWidth={width} rootHeight={bodyHeight}
215
+ getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
216
+ selection={{ kind: "id", selectedId: selectedSecond?.id ?? null, getId: (row) => row.id, onChange: setSelectedSecondId }}
217
+ onActivate={openRoute}
218
+ getExportMetadata={() => [...windowMeta(), ["share per hop", "percent of the reporting company's revenue unless another basis is named, holding to via, then via to the company"]]}
219
+ renderCell={renderSecondCell} />
220
+ : <DataTableView<RippleRow> focused={focused} columns={COLUMNS} items={rows} rootWidth={width} rootHeight={bodyHeight}
221
+ getItemKey={(row) => row.id} emptyStateTitle="No reports." sortColumnId={null} sortDirection="asc"
222
+ selection={{ kind: "id", selectedId: selected?.id ?? null, getId: (row) => row.id, onChange: setSelectedId }}
223
+ onActivate={openSupply}
224
+ getExportMetadata={() => [...windowMeta(), ["revenue share", "percent of the seller's revenue, as its filing discloses"]]}
225
+ renderCell={renderCell} />}
101
226
  </PaneStatusBody>}
102
227
  </Box>;
103
228
  }
@@ -361,6 +361,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
361
361
  export const economicCalendarModule: PluginModule = {
362
362
  panes: [{
363
363
  id: "econ-calendar",
364
+ reportFreshness: { status: "not-a-feed", basis: "calendar" },
364
365
  name: "Economic Calendar",
365
366
  icon: "E",
366
367
  component: EconCalendarPane,
@@ -123,6 +123,8 @@ export function projectStatsHeadlessBundle(
123
123
 
124
124
  export const econStatisticsHeadless: HeadlessPaneDefinition<"bundle"> = {
125
125
  shape: "bundle",
126
+ // Each row checks its own release schedule and says when it is stale.
127
+ freshness: { source: "FRED", status: "not-a-feed", basis: "published statistics" },
126
128
  argument: {
127
129
  kind: "free-text",
128
130
  placeholder: "statistic",