gloomberb 0.16.1 → 0.16.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 (643) 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 +69 -0
  14. package/src/cli/command-options.ts +74 -0
  15. package/src/cli/commands/command-utils.ts +37 -0
  16. package/src/cli/commands/crypto-hints.ts +75 -0
  17. package/src/cli/commands/market.ts +560 -136
  18. package/src/cli/commands/overview.ts +136 -48
  19. package/src/cli/commands/ticker.ts +220 -67
  20. package/src/cli/desktop-pane-shot.ts +37 -4
  21. package/src/cli/freshness.ts +61 -0
  22. package/src/cli/help.ts +27 -6
  23. package/src/cli/helpers.ts +4 -8
  24. package/src/cli/history-rows.ts +187 -15
  25. package/src/cli/index.ts +23 -8
  26. package/src/cli/listing-arg.ts +321 -0
  27. package/src/cli/options.ts +5 -1
  28. package/src/cli/pane-functions/access-gate.ts +42 -0
  29. package/src/cli/pane-functions/capabilities.ts +5 -0
  30. package/src/cli/pane-functions/catalog.ts +28 -1
  31. package/src/cli/pane-functions/data.ts +3 -1
  32. package/src/cli/pane-functions/dom.ts +87 -16
  33. package/src/cli/pane-functions/freshness.ts +400 -0
  34. package/src/cli/pane-functions/function-help.ts +31 -22
  35. package/src/cli/pane-functions/headless.ts +201 -41
  36. package/src/cli/pane-functions/index.ts +24 -6
  37. package/src/cli/pane-functions/options.ts +3 -0
  38. package/src/cli/pane-functions/report.ts +12 -1
  39. package/src/cli/pane-functions/resolver.ts +63 -9
  40. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  41. package/src/cli/pane-functions/screenshot.ts +11 -3
  42. package/src/cli/registry.ts +6 -3
  43. package/src/cli/report-tables.ts +460 -0
  44. package/src/cli/result.ts +53 -29
  45. package/src/cli/ticker-resolution.ts +7 -5
  46. package/src/components/chart/composite/composite-chart.tsx +88 -2536
  47. package/src/components/chart/composite/drawing-store.ts +59 -0
  48. package/src/components/chart/composite/legend.tsx +319 -0
  49. package/src/components/chart/composite/panel-bitmap.ts +139 -0
  50. package/src/components/chart/composite/panel-gestures.ts +59 -0
  51. package/src/components/chart/composite/panel-labels.ts +107 -0
  52. package/src/components/chart/composite/panel-surface.tsx +876 -0
  53. package/src/components/chart/composite/tool-catalog.ts +98 -0
  54. package/src/components/chart/composite/toolbar.tsx +186 -0
  55. package/src/components/chart/composite/tools.ts +8 -0
  56. package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
  57. package/src/components/chart/composite/use-composite-viewport.ts +143 -0
  58. package/src/components/command-bar/list/model.ts +5 -0
  59. package/src/components/command-bar/list/view.tsx +16 -3
  60. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
  62. package/src/components/command-bar/workflow/ops.ts +1 -1
  63. package/src/components/command-bar/workflow/tickers.ts +6 -0
  64. package/src/components/data-table/export.ts +6 -51
  65. package/src/components/data-table/view.tsx +32 -11
  66. package/src/components/feedback-dialog.tsx +2 -1
  67. package/src/components/form-modal/field-row.tsx +2 -2
  68. package/src/components/layout/detached-pane-shell.tsx +6 -3
  69. package/src/components/layout/pane/content.tsx +23 -10
  70. package/src/components/layout/pane/error-boundary.tsx +103 -0
  71. package/src/components/layout/pane/footer/registration.tsx +68 -23
  72. package/src/components/layout/pane/header.tsx +11 -11
  73. package/src/components/layout/shell/active-drag.ts +67 -35
  74. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  75. package/src/components/layout/shell/drag/index.ts +12 -7
  76. package/src/components/layout/shell/drag/live.ts +122 -0
  77. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  78. package/src/components/layout/shell/drag/runtime.ts +62 -50
  79. package/src/components/layout/shell/drag/slide.ts +10 -0
  80. package/src/components/layout/shell/index.tsx +37 -51
  81. package/src/components/layout/shell/menu.ts +2 -1
  82. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  83. package/src/components/layout/shell/native/surfaces.ts +30 -14
  84. package/src/components/layout/shell/pane/actions.ts +2 -1
  85. package/src/components/layout/shell/pane/layers.tsx +419 -168
  86. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  87. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  88. package/src/components/metric-treemap/heat-surface.tsx +43 -16
  89. package/src/components/metric-treemap/heat-tiles.ts +18 -0
  90. package/src/components/metric-treemap/layout.ts +2 -0
  91. package/src/components/onboarding/onboarding-keyboard.ts +311 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +101 -788
  93. package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
  94. package/src/components/onboarding/use-onboarding-progress.ts +298 -0
  95. package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
  96. package/src/components/ticker/list-table-view.tsx +13 -1
  97. package/src/components/ui/fields.tsx +8 -0
  98. package/src/components/ui/range-track.tsx +10 -3
  99. package/src/components/ui/status.tsx +8 -2
  100. package/src/components/ui/tabs.tsx +7 -4
  101. package/src/components/ui/text-prompt-dialog.tsx +4 -2
  102. package/src/data/config/store/normalize.ts +17 -1
  103. package/src/data/json-persistence.ts +17 -3
  104. package/src/data/json-storage.ts +2 -0
  105. package/src/data/memory-resource-store.ts +46 -0
  106. package/src/i18n/es.ts +19 -1
  107. package/src/i18n/ja.ts +19 -1
  108. package/src/i18n/ko.ts +19 -1
  109. package/src/i18n/zh-cn.ts +19 -1
  110. package/src/i18n/zh-tw.ts +19 -1
  111. package/src/market-data/coordinator/chart.ts +1 -1
  112. package/src/market-data/coordinator/quotes.ts +23 -4
  113. package/src/market-data/fx-closes.ts +72 -0
  114. package/src/market-data/market/freshness.ts +123 -6
  115. package/src/market-data/market/session-close-note.ts +57 -0
  116. package/src/market-data/market/status.ts +151 -0
  117. package/src/market-data/published-apac-sessions.ts +66 -0
  118. package/src/market-data/quotes/contributions.ts +2 -0
  119. package/src/market-data/quotes/freshness.ts +5 -0
  120. package/src/market-data/quotes/resolution.ts +2 -0
  121. package/src/market-data/quotes/screener-live-quotes.ts +49 -4
  122. package/src/plugins/builtin/account-management/pane.tsx +4 -0
  123. package/src/plugins/builtin/alerts/cli.ts +10 -5
  124. package/src/plugins/builtin/alerts/index.tsx +1 -0
  125. package/src/plugins/builtin/analytics/metrics.ts +8 -3
  126. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  127. package/src/plugins/builtin/analytics/risk-client.ts +191 -66
  128. package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
  129. package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
  130. package/src/plugins/builtin/analytics/risk-model.ts +45 -17
  131. package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
  132. package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
  133. package/src/plugins/builtin/attention/headless.ts +6 -4
  134. package/src/plugins/builtin/attention/index.tsx +1 -1
  135. package/src/plugins/builtin/attention/pane.tsx +2 -2
  136. package/src/plugins/builtin/awards/headless.ts +1 -0
  137. package/src/plugins/builtin/awards/pane.tsx +1 -1
  138. package/src/plugins/builtin/backtest/headless.ts +1 -0
  139. package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
  140. package/src/plugins/builtin/buildout/index.tsx +1 -0
  141. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  142. package/src/plugins/builtin/catalysts/headless.ts +3 -1
  143. package/src/plugins/builtin/cds/headless.ts +2 -0
  144. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
  145. package/src/plugins/builtin/chart-composer/headless.ts +38 -3
  146. package/src/plugins/builtin/chart-composer/index.tsx +3 -1
  147. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
  148. package/src/plugins/builtin/chat/channels.ts +19 -3
  149. package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
  150. package/src/plugins/builtin/chat/content/footer.tsx +15 -6
  151. package/src/plugins/builtin/chat/content/index.tsx +120 -21
  152. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
  153. package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
  154. package/src/plugins/builtin/chat/direct-messages.ts +200 -0
  155. package/src/plugins/builtin/chat/index.tsx +1 -0
  156. package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
  157. package/src/plugins/builtin/chat/message/header.tsx +16 -6
  158. package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
  159. package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
  160. package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
  161. package/src/plugins/builtin/chat/message/types.ts +14 -2
  162. package/src/plugins/builtin/chat/profile-popover.ts +18 -13
  163. package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
  164. package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
  165. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  166. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  167. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  168. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  169. package/src/plugins/builtin/comment-letters/index.tsx +1 -0
  170. package/src/plugins/builtin/company-attention/headless.ts +5 -1
  171. package/src/plugins/builtin/company-attention/pane.tsx +2 -2
  172. package/src/plugins/builtin/company-kpis/headless.ts +1 -0
  173. package/src/plugins/builtin/congress-trades/headless.ts +1 -0
  174. package/src/plugins/builtin/correlation/compute.ts +59 -16
  175. package/src/plugins/builtin/correlation/geo.ts +34 -0
  176. package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
  177. package/src/plugins/builtin/correlation/headless.ts +42 -8
  178. package/src/plugins/builtin/correlation/history.ts +2 -0
  179. package/src/plugins/builtin/correlation/index.tsx +68 -14
  180. package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
  181. package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
  182. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  183. package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
  184. package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
  185. package/src/plugins/builtin/correlation/settings.ts +1 -1
  186. package/src/plugins/builtin/cot/headless.ts +6 -3
  187. package/src/plugins/builtin/cot/index.tsx +3 -3
  188. package/src/plugins/builtin/cot/model.ts +36 -3
  189. package/src/plugins/builtin/cpi/headless.ts +4 -0
  190. package/src/plugins/builtin/credit-boards/headless.ts +2 -0
  191. package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
  192. package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
  193. package/src/plugins/builtin/credit-documents/headless.ts +2 -0
  194. package/src/plugins/builtin/crypto-board/headless.ts +3 -0
  195. package/src/plugins/builtin/custom-view/loader.ts +1 -1
  196. package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
  197. package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
  198. package/src/plugins/builtin/doe/headless.ts +7 -0
  199. package/src/plugins/builtin/doe/range-bar.tsx +7 -37
  200. package/src/plugins/builtin/earnings/headless.ts +5 -0
  201. package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
  202. package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
  203. package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
  204. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
  205. package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
  206. package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
  207. package/src/plugins/builtin/econ/index.tsx +1 -0
  208. package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
  209. package/src/plugins/builtin/equity-screener/headless.ts +2 -0
  210. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  211. package/src/plugins/builtin/executives/index.tsx +1 -0
  212. package/src/plugins/builtin/exposure/headless.ts +3 -1
  213. package/src/plugins/builtin/fear-greed/headless.ts +2 -0
  214. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  215. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  216. package/src/plugins/builtin/futures-curve/headless.ts +39 -10
  217. package/src/plugins/builtin/futures-curve/index.tsx +3 -3
  218. package/src/plugins/builtin/futures-curve/model.ts +188 -4
  219. package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
  220. package/src/plugins/builtin/gpu/headless.ts +7 -3
  221. package/src/plugins/builtin/gpu/ladder.tsx +26 -30
  222. package/src/plugins/builtin/gpu/model.ts +27 -1
  223. package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
  224. package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
  225. package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
  226. package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
  227. package/src/plugins/builtin/holders/client.ts +3 -6
  228. package/src/plugins/builtin/holders/format.ts +31 -1
  229. package/src/plugins/builtin/holders/headless.ts +161 -6
  230. package/src/plugins/builtin/holders/index.tsx +10 -3
  231. package/src/plugins/builtin/holders/pane.tsx +42 -16
  232. package/src/plugins/builtin/holders/report-header.ts +136 -0
  233. package/src/plugins/builtin/holders/table-model.ts +20 -5
  234. package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
  235. package/src/plugins/builtin/holders/types.ts +2 -1
  236. package/src/plugins/builtin/insider/headless.ts +2 -0
  237. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
  238. package/src/plugins/builtin/iv-history/headless.ts +4 -1
  239. package/src/plugins/builtin/jobs/headless.ts +1 -0
  240. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
  241. package/src/plugins/builtin/kelly-sizer/pane.tsx +12 -6
  242. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  243. package/src/plugins/builtin/macro-day/headless.ts +2 -0
  244. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  245. package/src/plugins/builtin/market-heatmap/data.ts +2 -0
  246. package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
  247. package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
  248. package/src/plugins/builtin/market-heatmap/model.ts +146 -0
  249. package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
  250. package/src/plugins/builtin/market-movers/headless.ts +2 -0
  251. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  252. package/src/plugins/builtin/members/client.ts +20 -3
  253. package/src/plugins/builtin/members/headless.ts +5 -3
  254. package/src/plugins/builtin/members/model.ts +10 -2
  255. package/src/plugins/builtin/members/pane.tsx +23 -6
  256. package/src/plugins/builtin/mna/distress/headless.ts +1 -0
  257. package/src/plugins/builtin/mna/headless.ts +1 -0
  258. package/src/plugins/builtin/money-markets/headless.ts +1 -0
  259. package/src/plugins/builtin/news/headless.ts +2 -0
  260. package/src/plugins/builtin/news/wire/index.tsx +8 -1
  261. package/src/plugins/builtin/notes/cli.ts +14 -6
  262. package/src/plugins/builtin/notes/index.tsx +1 -0
  263. package/src/plugins/builtin/openfda/index.tsx +1 -0
  264. package/src/plugins/builtin/options-calculator/headless.ts +2 -0
  265. package/src/plugins/builtin/options-positioning/headless.ts +3 -0
  266. package/src/plugins/builtin/options-scenario/headless.ts +5 -0
  267. package/src/plugins/builtin/pe-band/client.ts +48 -3
  268. package/src/plugins/builtin/pe-band/headless.ts +25 -7
  269. package/src/plugins/builtin/pe-band/model.ts +92 -16
  270. package/src/plugins/builtin/pe-band/pane.tsx +33 -16
  271. package/src/plugins/builtin/perps/client.ts +50 -3
  272. package/src/plugins/builtin/perps/headless.ts +144 -16
  273. package/src/plugins/builtin/perps/index.tsx +6 -6
  274. package/src/plugins/builtin/perps/model.ts +189 -12
  275. package/src/plugins/builtin/perps/pane.tsx +249 -48
  276. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  277. package/src/plugins/builtin/portfolio-list/allocation.ts +248 -0
  278. package/src/plugins/builtin/portfolio-list/cached-account.ts +42 -0
  279. package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
  280. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
  281. package/src/plugins/builtin/portfolio-list/cli/render.ts +232 -39
  282. package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
  283. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
  284. package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
  285. package/src/plugins/builtin/portfolio-list/headless.ts +100 -16
  286. package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
  287. package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
  289. package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
  290. package/src/plugins/builtin/portfolio-list/pane/index.tsx +53 -16
  291. package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
  292. package/src/plugins/builtin/portfolio-list/summary/index.tsx +46 -5
  293. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  294. package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
  295. package/src/plugins/builtin/power/headless.ts +3 -1
  296. package/src/plugins/builtin/rate-path/headless.ts +2 -0
  297. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  298. package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
  299. package/src/plugins/builtin/research/analyst-headless.ts +3 -0
  300. package/src/plugins/builtin/research/events-headless.ts +2 -0
  301. package/src/plugins/builtin/research/headless.ts +2 -0
  302. package/src/plugins/builtin/research/index.tsx +1 -0
  303. package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
  304. package/src/plugins/builtin/research/relative-valuation-model.ts +18 -6
  305. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  306. package/src/plugins/builtin/research-search/index.tsx +1 -0
  307. package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
  309. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  310. package/src/plugins/builtin/risk-factors/headless.ts +2 -0
  311. package/src/plugins/builtin/scanner/flow-history.ts +2 -13
  312. package/src/plugins/builtin/scanner/headless.ts +14 -0
  313. package/src/plugins/builtin/seasonality/headless.ts +2 -0
  314. package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
  315. package/src/plugins/builtin/sec/filing-content.ts +42 -17
  316. package/src/plugins/builtin/sec/forms.ts +12 -0
  317. package/src/plugins/builtin/sec/headless.ts +2 -0
  318. package/src/plugins/builtin/sectors/client.ts +1 -0
  319. package/src/plugins/builtin/sectors/headless.ts +9 -1
  320. package/src/plugins/builtin/sectors/sector-model.ts +2 -0
  321. package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
  322. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  323. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  324. package/src/plugins/builtin/shared/report-freshness.ts +57 -0
  325. package/src/plugins/builtin/shared/research-cells.tsx +4 -9
  326. package/src/plugins/builtin/short-interest/headless.ts +2 -0
  327. package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
  328. package/src/plugins/builtin/short-volume/headless.ts +1 -0
  329. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  330. package/src/plugins/builtin/social-mentions/headless.ts +1 -0
  331. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  332. package/src/plugins/builtin/supply-chain/headless.ts +7 -1
  333. package/src/plugins/builtin/supply-chain/index.tsx +10 -1
  334. package/src/plugins/builtin/supply-chain/model.ts +27 -2
  335. package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
  336. package/src/plugins/builtin/themes/headless.ts +3 -0
  337. package/src/plugins/builtin/themes/pane.tsx +3 -0
  338. package/src/plugins/builtin/thirteenf/headless.ts +110 -25
  339. package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
  340. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  341. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  342. package/src/plugins/builtin/ticker-detail/headless.ts +27 -5
  343. package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
  344. package/src/plugins/builtin/ticker-detail/overview/model.ts +17 -5
  345. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +27 -26
  346. package/src/plugins/builtin/time-sales/headless.ts +5 -1
  347. package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
  348. package/src/plugins/builtin/volatility/headless.ts +6 -1
  349. package/src/plugins/builtin/world-indices/headless.ts +2 -0
  350. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +159 -0
  351. package/src/plugins/builtin/world-venue-map/basemap.ts +232 -0
  352. package/src/plugins/builtin/world-venue-map/client.ts +45 -0
  353. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  354. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
  355. package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
  356. package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
  357. package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
  358. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
  359. package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
  360. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +448 -0
  361. package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
  362. package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
  363. package/src/plugins/builtin/world-venue-map/index.ts +69 -6
  364. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +565 -0
  365. package/src/plugins/builtin/world-venue-map/layers.ts +406 -0
  366. package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
  367. package/src/plugins/builtin/world-venue-map/map.tsx +557 -85
  368. package/src/plugins/builtin/world-venue-map/model.ts +23 -4
  369. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +202 -0
  370. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  371. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
  372. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
  373. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
  374. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
  375. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
  376. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
  377. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
  378. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
  379. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
  380. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
  381. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
  382. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
  383. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
  384. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
  385. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
  386. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
  387. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
  388. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
  389. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
  390. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
  391. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
  392. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
  393. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
  394. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
  395. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
  396. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
  397. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
  398. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
  399. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
  400. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
  401. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
  402. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
  403. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
  404. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
  405. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
  406. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
  407. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
  408. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
  409. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
  410. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
  411. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
  412. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
  413. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
  414. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
  415. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
  416. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
  417. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
  418. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
  419. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
  420. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
  421. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
  422. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
  423. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
  424. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
  425. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
  426. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
  429. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
  430. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
  431. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
  432. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
  433. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
  434. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
  435. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
  436. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
  437. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
  438. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
  439. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
  440. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
  441. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
  442. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
  443. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
  444. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
  445. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
  446. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
  447. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
  448. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
  449. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
  450. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
  451. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
  452. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
  453. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
  454. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
  455. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
  456. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
  457. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
  458. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
  459. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
  460. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
  461. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
  462. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
  463. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
  464. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
  465. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
  466. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
  467. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
  468. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
  469. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
  470. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
  471. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
  472. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
  473. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
  474. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
  475. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
  476. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
  477. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
  478. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
  479. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
  480. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
  481. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
  482. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
  483. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
  484. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
  485. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
  486. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
  487. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
  488. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
  489. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
  490. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
  491. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
  492. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
  493. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
  494. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
  495. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
  496. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
  497. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
  498. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
  499. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
  500. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
  501. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
  502. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
  503. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
  504. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
  505. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
  506. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
  507. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
  508. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
  509. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
  510. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
  511. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
  512. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
  513. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
  514. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
  515. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
  516. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
  517. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
  518. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
  519. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
  520. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
  521. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
  522. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
  523. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
  524. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
  525. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
  526. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
  527. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
  528. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
  529. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
  530. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
  531. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
  532. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
  533. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
  534. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
  535. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
  536. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
  537. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
  538. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
  539. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
  540. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
  541. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
  542. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
  543. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
  544. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
  545. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
  546. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
  547. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
  548. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
  549. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
  550. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
  551. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
  552. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
  553. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
  554. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
  555. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
  556. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
  557. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
  558. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
  559. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
  560. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
  561. package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
  562. package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
  563. package/src/plugins/builtin/world-venue-map/pane.tsx +35 -44
  564. package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
  565. package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
  566. package/src/plugins/builtin/yield-curve/headless.ts +16 -0
  567. package/src/plugins/loader.ts +13 -1
  568. package/src/plugins/registry/pane-settings.ts +1 -0
  569. package/src/renderers/browser/app-services.ts +3 -1
  570. package/src/renderers/dom/build-assets.ts +81 -3
  571. package/src/renderers/dom/data-table/dom.ts +31 -0
  572. package/src/renderers/dom/data-table/index.tsx +5 -0
  573. package/src/renderers/dom/data-table/row.tsx +2 -0
  574. package/src/renderers/dom/desktop/controls.tsx +7 -3
  575. package/src/renderers/dom/desktop/list-view.tsx +35 -10
  576. package/src/renderers/dom/desktop/popover.tsx +73 -14
  577. package/src/renderers/dom/host/box.tsx +113 -17
  578. package/src/renderers/dom/host/drag-shield.ts +24 -0
  579. package/src/renderers/dom/host/input.tsx +14 -3
  580. package/src/renderers/dom/host/live-frame.ts +54 -0
  581. package/src/renderers/dom/host/mouse.ts +8 -1
  582. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  583. package/src/renderers/dom/host/tabs.tsx +3 -2
  584. package/src/renderers/dom/styles.css +16 -9
  585. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  586. package/src/renderers/electrobun/bun/desktop/backend.ts +2 -0
  587. package/src/renderers/electrobun/bun/desktop/initialization.ts +13 -0
  588. package/src/renderers/electrobun/shared/protocol.ts +10 -1
  589. package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
  590. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -0
  591. package/src/renderers/electrobun/view/host/native.ts +0 -10
  592. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  593. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  594. package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
  595. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  596. package/src/renderers/opentui/host.tsx +5 -0
  597. package/src/renderers/opentui/ui-host.tsx +7 -1
  598. package/src/sources/gloomberb-cloud/index.ts +25 -4
  599. package/src/sources/gloomberb-cloud/normalizers.ts +43 -23
  600. package/src/sources/provider-router/batches.ts +3 -3
  601. package/src/sources/provider-router/cached-routes.ts +4 -1
  602. package/src/sources/provider-router/financial-routes.ts +2 -2
  603. package/src/sources/provider-router/financials.ts +76 -12
  604. package/src/sources/provider-router/history.ts +15 -10
  605. package/src/sources/provider-router/primary.ts +2 -2
  606. package/src/sources/sec-registrant.ts +75 -0
  607. package/src/state/app/context/index.tsx +24 -8
  608. package/src/state/hooks/quote-streaming.ts +41 -26
  609. package/src/state/pane-scroll-registry.tsx +67 -35
  610. package/src/sync/core-contributors.ts +4 -0
  611. package/src/telemetry/crash-reports.ts +4 -1
  612. package/src/test-support/cli-context.ts +10 -2
  613. package/src/test-support/margin-account.ts +108 -0
  614. package/src/theme/heat-colors.ts +266 -84
  615. package/src/time-series/resolution.ts +15 -0
  616. package/src/time-series/resolve.ts +1 -1
  617. package/src/time-series/use-chart-resolution.ts +1 -1
  618. package/src/types/context-menu.ts +6 -0
  619. package/src/types/data-provider.ts +2 -0
  620. package/src/types/financials.ts +35 -3
  621. package/src/types/headless.ts +79 -2
  622. package/src/types/plugin.ts +20 -1
  623. package/src/types/ticker.ts +15 -0
  624. package/src/ui/host.tsx +26 -0
  625. package/src/ui/index.tsx +8 -3
  626. package/src/ui/scroll-box-axis.ts +26 -0
  627. package/src/updater/index.ts +9 -11
  628. package/src/utils/async-deadline.ts +21 -0
  629. package/src/utils/cli-output.ts +5 -1
  630. package/src/utils/crypto-pair.ts +41 -0
  631. package/src/utils/currency-units.ts +19 -0
  632. package/src/utils/depositary-receipt.ts +70 -0
  633. package/src/utils/display-number.ts +63 -0
  634. package/src/utils/exchanges.ts +72 -0
  635. package/src/utils/fire-and-forget.ts +24 -0
  636. package/src/utils/format.ts +13 -0
  637. package/src/utils/fundamentals.ts +17 -1
  638. package/src/utils/price-earnings.ts +70 -3
  639. package/src/utils/price-history-integrity.ts +19 -6
  640. package/src/utils/price-history.ts +58 -13
  641. package/src/utils/rpc-timeout-error.ts +27 -0
  642. package/src/utils/sec.ts +13 -1
  643. package/src/utils/utc-time.ts +79 -0
@@ -5,13 +5,28 @@ import { compositeAxisTicks } from "../../../components/chart/composite/format";
5
5
  import type { CompositeAxisDomain } from "../../../components/chart/composite/types";
6
6
  import { FUTURES_CONTRACTS, tickDecimals } from "../futures/contracts";
7
7
  import { formatPercentileRank } from "../../../utils/format";
8
+ import { cryptoPairCoin, isFuturesSymbol } from "../shared/crypto-pair";
9
+ import { parseReportTime } from "../../../utils/utc-time";
8
10
  import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
9
11
 
12
+ /**
13
+ * CME crypto futures have a curve but no FUT board row, so their name and tick
14
+ * live here rather than in the catalogue the board lists. Outright ticks:
15
+ * BTC $5, ETH $0.50, SOL $0.05, XRP $0.0005 (each $25 a contract).
16
+ */
17
+ const CRYPTO_CURVE_ROOTS: readonly { code: string; name: string; tick: number }[] = [
18
+ { code: "BTC", name: "Bitcoin", tick: 5 }, { code: "ETH", name: "Ether", tick: 0.5 },
19
+ { code: "SOL", name: "Solana", tick: 0.05 }, { code: "XRP", name: "XRP", tick: 0.0005 },
20
+ ];
21
+ const rootLabel = (code: string, name: string) => name === code ? code : `${code} ${name}`;
22
+
10
23
  export const CURVE_ROOTS = [
11
- ...FUTURES_CONTRACTS.filter((row) => row.curve !== false).map((row) => ({ value: row.code, label: `${row.code} ${row.name}` })),
24
+ ...FUTURES_CONTRACTS.filter((row) => row.curve !== false).map((row) => ({ value: row.code, label: rootLabel(row.code, row.name) })),
12
25
  { value: "VX", label: "VX VIX Futures" },
26
+ ...CRYPTO_CURVE_ROOTS.map((row) => ({ value: row.code, label: rootLabel(row.code, row.name) })),
13
27
  ];
14
28
 
29
+ /** A root typed as an argument, a stored pane setting or a request: every listed root, crypto included. */
15
30
  export function normalizeCurveRoot(value: unknown): string | null {
16
31
  if (typeof value !== "string") return null;
17
32
  const raw = value.trim().toUpperCase().replace(/=F$/, "");
@@ -19,6 +34,26 @@ export function normalizeCurveRoot(value: unknown): string | null {
19
34
  return CURVE_ROOTS.some((row) => row.value === root) ? root : null;
20
35
  }
21
36
 
37
+ /** What a root nothing lists is told, with roots that work. */
38
+ export function unsupportedCurveRootMessage(value: unknown): string {
39
+ const examples = ["ES", "CL", "GC", "ZN"].filter((root) => CURVE_ROOTS.some((row) => row.value === root));
40
+ return `Unsupported futures root: ${String(value)}. Try ${examples.join(", ")}.`;
41
+ }
42
+
43
+ /**
44
+ * The root for the ticker under the cursor, when CTM is opened without an
45
+ * argument. The crypto roots are also US ticker symbols (Grayscale's mini
46
+ * trusts are BTC and ETH), so a bare one is the equity's, never the future's:
47
+ * only a futures symbol (BTC=F) or a coin pair (BTC-USD) names the crypto curve.
48
+ */
49
+ export function curveRootForTicker(ticker: unknown): string | null {
50
+ if (typeof ticker !== "string") return null;
51
+ const coin = cryptoPairCoin(ticker);
52
+ if (coin) return CRYPTO_CURVE_ROOTS.some((row) => row.code === coin) ? coin : null;
53
+ const root = normalizeCurveRoot(ticker);
54
+ return root && CRYPTO_CURVE_ROOTS.some((row) => row.code === root) && !isFuturesSymbol(ticker) ? null : root;
55
+ }
56
+
22
57
  /**
23
58
  * Treasury futures trade in fractions of a 32nd (ZT to 1/256, ZF 1/128, ZN
24
59
  * 1/64, ZB and UB 1/32), so the catalogue leaves their tick unset. A root
@@ -32,7 +67,7 @@ function curvePriceDecimals(root: string): number {
32
67
  const rateTick = RATE_TICKS[root];
33
68
  if (rateTick != null) return tickDecimals(rateTick);
34
69
  if (root === "VX") return 4;
35
- const tick = FUTURES_CONTRACTS.find((row) => row.code === root)?.tick;
70
+ const tick = FUTURES_CONTRACTS.find((row) => row.code === root)?.tick ?? CRYPTO_CURVE_ROOTS.find((row) => row.code === root)?.tick;
36
71
  return tick == null ? 5 : Math.max(2, tickDecimals(tick));
37
72
  }
38
73
 
@@ -155,17 +190,166 @@ const CHANGE_COLUMNS: Readonly<Record<string, CurveLookback>> = { change1w: "1W"
155
190
 
156
191
  type CurveSortKey = "symbol" | "expiration" | "price" | "change" | "openInterest" | "volume" | "percentile" | "asOf";
157
192
 
193
+ const BASIS_COLUMNS: Readonly<Record<string, "vsSpotPct" | "annualisedBasisPct">> = { vsSpot: "vsSpotPct", annBasis: "annualisedBasisPct" };
194
+
158
195
  export function sortCurveContracts(rows: readonly FuturesContract[], id: string, direction: SortDirection,
159
- changes?: CurveContractChanges): FuturesContract[] {
196
+ changes?: CurveContractChanges, basis?: CurveBasisRows): FuturesContract[] {
160
197
  const keys: Record<string, CurveSortKey> = { symbol: "symbol", expiry: "expiration", price: "price", change: "change", oi: "openInterest", volume: "volume", percentile: "percentile", asOf: "asOf" };
161
198
  const key = keys[id] ?? "expiration";
162
199
  const lookback = CHANGE_COLUMNS[id];
163
- const value = (row: FuturesContract) => lookback ? changes?.get(row.symbol)?.[lookback] ?? null : row[key] ?? null;
200
+ const basisKey = BASIS_COLUMNS[id];
201
+ const value = (row: FuturesContract) => lookback ? changes?.get(row.symbol)?.[lookback] ?? null
202
+ : basisKey ? basis?.get(row.symbol)?.[basisKey] ?? null : row[key] ?? null;
164
203
  return [...rows].sort((a, b) => compareSortValues(value(a), value(b), direction));
165
204
  }
166
205
 
167
206
  const DAY_MS = 86_400_000;
168
207
 
208
+ /**
209
+ * The quote each crypto root's basis is measured against: the terminal's USD
210
+ * pair, the same quote every other pane shows, not the CME reference rate, so
211
+ * a small gap to settlement is expected. A root missing here has no basis
212
+ * columns; add its spot symbol to give it some.
213
+ */
214
+ const BASIS_SPOT_SYMBOLS: ReadonlyMap<string, string> = new Map([
215
+ ["BTC", "BTC-USD"], ["ETH", "ETH-USD"], ["SOL", "SOL-USD"], ["XRP", "XRP-USD"],
216
+ ]);
217
+
218
+ export function basisSpotSymbol(root: string): string | null {
219
+ return BASIS_SPOT_SYMBOLS.get(root) ?? null;
220
+ }
221
+
222
+ /** Crypto trades around the clock, so a spot quote older than this no longer prices a live future. */
223
+ const SPOT_STALE_MS = 10 * 60_000;
224
+ /** A free account's quote is held back on purpose; that delay is not staleness, so it is allowed on top. */
225
+ const DELAYED_SPOT_MS = 15 * 60_000;
226
+
227
+ /** The spot a curve's basis uses, or why there is none. `asOf` is the quote's own time, never the fetch's. */
228
+ export interface CurveSpot {
229
+ symbol: string;
230
+ price: number | null;
231
+ /** UTC ISO instant of the quote. */
232
+ asOf: string | null;
233
+ status: "ok" | "stale" | "missing";
234
+ reason: string | null;
235
+ }
236
+
237
+ export interface SpotQuote {
238
+ price: number;
239
+ lastUpdated: number;
240
+ stale?: boolean;
241
+ dataSource?: string;
242
+ }
243
+
244
+ const positive = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value) && value > 0;
245
+
246
+ function quoteAge(ms: number): string {
247
+ const minutes = Math.floor(ms / 60_000);
248
+ if (minutes < 60) return `${minutes}m`;
249
+ const hours = Math.floor(minutes / 60);
250
+ return hours < 48 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
251
+ }
252
+
253
+ /** Judges one spot quote at `now`: usable, older than the rule allows, or absent. */
254
+ export function curveSpot(symbol: string, quote: SpotQuote | null | undefined, now: number): CurveSpot {
255
+ if (!quote || !positive(quote.price)) return { symbol, price: null, asOf: null, status: "missing", reason: `no ${symbol} quote` };
256
+ const time = quote.lastUpdated;
257
+ if (!Number.isFinite(time) || time <= 0) {
258
+ return { symbol, price: quote.price, asOf: null, status: "stale", reason: `${symbol} quote has no time` };
259
+ }
260
+ const asOf = new Date(time).toISOString().replace(".000Z", "Z");
261
+ const age = now - time;
262
+ const limit = SPOT_STALE_MS + (quote.dataSource === "delayed" ? DELAYED_SPOT_MS : 0);
263
+ if (age > limit) return { symbol, price: quote.price, asOf, status: "stale", reason: `${symbol} quote is ${quoteAge(age)} old` };
264
+ if (quote.stale === true) return { symbol, price: quote.price, asOf, status: "stale", reason: `${symbol} quote is stale` };
265
+ return { symbol, price: quote.price, asOf, status: "ok", reason: null };
266
+ }
267
+
268
+ /** How far the contract trades over (positive) or under spot, in percent. */
269
+ export function vsSpotPct(futures: number | null | undefined, spot: number | null | undefined): number | null {
270
+ return positive(futures) && positive(spot) ? (futures / spot - 1) * 100 : null;
271
+ }
272
+
273
+ /** Calendar days from the UTC date of the spot's timestamp to the contract's expiration date. */
274
+ export function daysToExpiry(spotTime: number, expiration: string): number | null {
275
+ const expiry = Date.parse(`${expiration}T00:00:00Z`);
276
+ if (!Number.isFinite(spotTime) || !Number.isFinite(expiry)) return null;
277
+ const spotDay = Date.parse(`${new Date(spotTime).toISOString().slice(0, 10)}T00:00:00Z`);
278
+ return Math.round((expiry - spotDay) / DAY_MS);
279
+ }
280
+
281
+ /** The premium over spot as a yearly rate, on a 365-day year. Blank on or past expiry, where it has no meaning. */
282
+ export function annualisedBasisPct(futures: number | null | undefined, spot: number | null | undefined, days: number | null): number | null {
283
+ if (!positive(futures) || !positive(spot) || days == null || !Number.isFinite(days) || days <= 0) return null;
284
+ return (futures / spot - 1) * 365 / days * 100;
285
+ }
286
+
287
+ /** A contract that printed longer than this before the spot quote is too old to read against it. */
288
+ const CONTRACT_PRINT_MAX_AGE_MS = 60 * 60_000;
289
+
290
+ export interface ContractBasis {
291
+ vsSpotPct: number | null;
292
+ annualisedBasisPct: number | null;
293
+ /** Priced and not flagged stale, but its last print is over an hour before spot: a thin month. */
294
+ thin: boolean;
295
+ }
296
+
297
+ const NO_BASIS: ContractBasis = { vsSpotPct: null, annualisedBasisPct: null, thin: false };
298
+
299
+ /**
300
+ * One contract's basis. Blank without a usable spot, and for a contract whose
301
+ * own price is missing or stale: an old future against a live spot misleads.
302
+ * So does a print more than an hour older than the spot (`thin`): the server's
303
+ * stale flag allows two sessions, which a quote that moves all day outruns. A
304
+ * date-only `asOf` is UTC midnight of that date, one that cannot be read
305
+ * counts as old, and a print newer than the spot is never old (the spot of a
306
+ * delayed feed trails the futures).
307
+ */
308
+ export function contractBasis(row: Pick<FuturesContract, "price" | "stale" | "expiration" | "asOf">, spot: CurveSpot): ContractBasis {
309
+ if (spot.status !== "ok" || spot.asOf == null || row.stale) return NO_BASIS;
310
+ const vs = vsSpotPct(row.price, spot.price);
311
+ if (vs == null) return NO_BASIS;
312
+ const spotTime = Date.parse(spot.asOf);
313
+ const printed = parseReportTime(row.asOf)?.time ?? null;
314
+ if (printed == null || spotTime - printed > CONTRACT_PRINT_MAX_AGE_MS) return { ...NO_BASIS, thin: true };
315
+ return { vsSpotPct: vs, annualisedBasisPct: annualisedBasisPct(row.price, spot.price, daysToExpiry(spotTime, row.expiration)), thin: false };
316
+ }
317
+
318
+ /** A basis in percent, signed; one that rounds to zero stays unsigned. */
319
+ export function curveBasisPercent(value: number | null, decimals: number): string {
320
+ if (value == null) return "--";
321
+ const text = Math.abs(value).toFixed(decimals);
322
+ return /[1-9]/.test(text) ? `${value > 0 ? "+" : "-"}${text}%` : `${text}%`;
323
+ }
324
+
325
+ /** The footer's spot: `spot BTC-USD 67,250.10 · 07:25 UTC`, with the date when the quote is not from today. */
326
+ export function curveSpotLabel(spot: CurveSpot, root: string, now: number): string {
327
+ const decimals = curvePriceDecimals(root);
328
+ const price = spot.price == null ? "--" : spot.price.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
329
+ const stamp = curveTimestamp(spot.asOf);
330
+ const today = new Date(now).toISOString().slice(0, 10);
331
+ return `spot ${spot.symbol} ${price} · ${spot.asOf?.startsWith(today) ? stamp.slice(11) : stamp} UTC`;
332
+ }
333
+
334
+ /** The basis columns' sort values, which live beside the contract rather than on it. */
335
+ export type CurveBasisRows = ReadonlyMap<string, ContractBasis>;
336
+
337
+ export function curveBasisRows(rows: readonly FuturesContract[], spot: CurveSpot | null): CurveBasisRows {
338
+ return new Map(rows.map((row) => [row.symbol, spot ? contractBasis(row, spot) : NO_BASIS]));
339
+ }
340
+
341
+ /** The contracts a good spot left blank for their old print. */
342
+ export function thinContractCount(basis: Iterable<ContractBasis>): number {
343
+ let count = 0;
344
+ for (const row of basis) if (row.thin) count += 1;
345
+ return count;
346
+ }
347
+
348
+ /** The footer warning that thin contracts have no basis: the pane's `!` notice, which keeps the footer row for the quote times. */
349
+ export function thinContractsNotice(count: number): string {
350
+ return `Basis blank on ${count} thin contract${count === 1 ? "" : "s"} (last print over 1h before spot)`;
351
+ }
352
+
169
353
  /** A past date for the as-of view: empty (or "latest") for the live curve, else YYYY-MM-DD no later than today. */
170
354
  export function curveAsOfDate(value: unknown, now = new Date()): string {
171
355
  const date = typeof value === "string" ? value.trim() : "";
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { chartTableChromeRows, ChartTableHeader, CurveSurface, curveGhostColors, DataTableView, Notice, PaneStatusBody, QueryBar, useChartTableSelection, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, type DataTableColumn, type StatItem } from "../../../components";
3
+ import { fitChartTableColumns } from "../../../components/chart-table";
3
4
  import { curveStrip, curveSurfaceMinRows } from "../../../components/chart/curve";
4
5
  import { isAccessDenied } from "../../../api-client/errors";
5
6
  import type { FuturesContract } from "../../../api-client/futures-curve";
@@ -13,8 +14,9 @@ import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
13
14
  import { useAutoRefresh } from "../../../react/auto-refresh";
14
15
  import { futuresSessionRefreshInterval } from "../shared/futures-session";
15
16
  import { useResearchCloudSession } from "../shared/research-cloud-session";
17
+ import { useQuoteBoard } from "../shared/use-quote-board";
16
18
  import { getCachedFuturesCurve, loadFuturesCurve, loadFuturesCurveAsOf } from "./client";
17
- import { curveAsOfDate, curveAxisPrice, curveChangeText, curveContractChanges, curvePrice, curveRank, curveTimestamp, DEFAULT_CURVE_HORIZON, futuresCurveSeries, newestQuote, normalizeCurveRoot, sortCurveContracts, type CurveContractChanges } from "./model";
19
+ import { basisSpotSymbol, curveAsOfDate, curveAxisPrice, curveBasisPercent, curveBasisRows, curveChangeText, curveContractChanges, curvePrice, curveRank, curveSpot, curveSpotLabel, curveTimestamp, DEFAULT_CURVE_HORIZON, futuresCurveSeries, newestQuote, normalizeCurveRoot, sortCurveContracts, thinContractCount, thinContractsNotice, unsupportedCurveRootMessage, type CurveContractChanges } from "./model";
18
20
 
19
21
  const TABS = [{ value: "curve", label: "Curve" }, { value: "contracts", label: "Contracts" }];
20
22
  const COLUMNS: DataTableColumn[] = [
@@ -38,6 +40,19 @@ const CURVE_COLUMNS: DataTableColumn[] = [
38
40
  { id: "change1m", label: "VS 1M", width: 10, align: "right" },
39
41
  ...COLUMNS.slice(4, -1),
40
42
  ];
43
+ // A crypto curve also reads against spot. Its widths are the tightest each column
44
+ // holds (BTCV26.CME, 128000.00), so the two new columns fit beside the rest, and
45
+ // a narrow pane drops the least useful ones before a number would be clipped.
46
+ const BASIS_CURVE_COLUMNS: DataTableColumn[] = [
47
+ { ...CURVE_COLUMNS[0]!, width: 10 }, CURVE_COLUMNS[1]!, { ...CURVE_COLUMNS[2]!, width: 10 },
48
+ { id: "vsSpot", label: "VS SPOT", width: 9, align: "right" },
49
+ { id: "annBasis", label: "ANN BASIS", width: 11, align: "right" },
50
+ ...CURVE_COLUMNS.slice(3),
51
+ ];
52
+ const BASIS_DROP_ORDER = ["percentile", "volume", "change1m", "oi"];
53
+ const NO_SYMBOLS: string[] = [];
54
+ /** The spot's age is judged against the clock, so a quote that stops moving still goes stale on screen. */
55
+ const SPOT_CLOCK_MS = 30_000;
41
56
  const signedPercent = (value: number | null) => value == null ? "--" : formatPercentRaw(value);
42
57
  const integer = (value: number | null) => value == null ? "--" : value.toLocaleString("en-US");
43
58
  const contractKey = (row: FuturesContract) => row.symbol;
@@ -50,7 +65,7 @@ export function FuturesCurvePane(props: PaneProps) {
50
65
  const requested = pane?.settings?.root ?? pane?.params?.root ?? "ES";
51
66
  const root = normalizeCurveRoot(requested);
52
67
  return root ? <FuturesCurveView key={root} {...props} root={root} />
53
- : <PaneStatusBody error={`Unsupported futures root: ${String(requested)}`} subject="futures curve" />;
68
+ : <PaneStatusBody error={unsupportedCurveRootMessage(requested)} subject="futures curve" />;
54
69
  }
55
70
 
56
71
  function FuturesCurveView({ width, height, focused, root }: PaneProps & { root: string }) {
@@ -79,6 +94,23 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
79
94
  // A pending date change must never relabel the previous curve as the new one.
80
95
  const data = resource.data?.date === requestedDate ? resource.data.payload : null;
81
96
  usePaneTitle(`CTM ${root}`);
97
+ const curveTab = tab === "curve";
98
+ // The Curve tab of a crypto root reads against spot; a past date has no spot history.
99
+ const spotSymbol = basisSpotSymbol(root);
100
+ const basisActive = curveTab && !requestedDate && spotSymbol != null;
101
+ const spotSymbols = useMemo(() => basisActive ? [spotSymbol] : NO_SYMBOLS, [basisActive, spotSymbol]);
102
+ const { quotes: spotQuotes } = useQuoteBoard(spotSymbols);
103
+ const [clock, setClock] = useState(() => Date.now());
104
+ useEffect(() => {
105
+ if (!basisActive) return;
106
+ setClock(Date.now());
107
+ const timer = setInterval(() => setClock(Date.now()), SPOT_CLOCK_MS);
108
+ return () => clearInterval(timer);
109
+ }, [basisActive]);
110
+ const spotState = spotSymbol ? spotQuotes.get(spotSymbol) : undefined;
111
+ // Until the first answer there is nothing to say: no reason, only blank cells.
112
+ const spot = useMemo(() => !basisActive || !spotSymbol || !spotState || spotState.loading && !spotState.quote ? null
113
+ : curveSpot(spotSymbol, spotState.quote, clock), [basisActive, clock, spotState, spotSymbol]);
82
114
  const staleCount = data?.contracts.filter((row) => row.stale).length ?? 0;
83
115
  const newest = data ? newestQuote(data.contracts) : null;
84
116
  // A past curve is named by the session it holds: a weekend or holiday date shows the session before it.
@@ -86,8 +118,9 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
86
118
  requestedDate ? Date.parse(`${requestedDate}T00:00:00Z`) : Date.now(), requestedDate ? data.asOf ?? requestedDate : undefined) : [],
87
119
  [data, colors, horizon, requestedDate]);
88
120
  const changes = useMemo<CurveContractChanges>(() => data ? curveContractChanges(data) : new Map(), [data]);
89
- const rows = useMemo(() => sortCurveContracts(data?.contracts ?? [], sort.columnId, sort.direction, changes), [data, sort, changes]);
90
- const curveTab = tab === "curve";
121
+ const basis = useMemo(() => curveBasisRows(basisActive ? data?.contracts ?? [] : [], spot), [basisActive, data, spot]);
122
+ const thinCount = useMemo(() => thinContractCount(basis.values()), [basis]);
123
+ const rows = useMemo(() => sortCurveContracts(data?.contracts ?? [], sort.columnId, sort.direction, changes, basis), [data, sort, changes, basis]);
91
124
  // The curve tab lists the contracts the chart plots; Contracts keeps every one.
92
125
  const curveRows = useMemo(() => {
93
126
  const charted = new Set(curves[0]?.points.map((point) => point.id));
@@ -108,7 +141,8 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
108
141
  const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: TABS, activeValue: tab, onSelect: setTab, focused, dense: true });
109
142
  // The query bar takes one row below the tabs, and a refused date one more.
110
143
  const bodyHeight = Math.max(1, height - tabRows - 1 - (dateError ? 1 : 0));
111
- const columns = curveTab ? CURVE_COLUMNS : COLUMNS;
144
+ const columns = useMemo(() => !curveTab ? COLUMNS : basisActive ? fitChartTableColumns(BASIS_CURVE_COLUMNS, width, BASIS_DROP_ORDER) : CURVE_COLUMNS,
145
+ [basisActive, curveTab, width]);
112
146
  const formatValue = useCallback((value: number) => curvePrice(value, root), [root]);
113
147
  const formatChange = useCallback((value: number) => curveChangeText(value, root), [root]);
114
148
  const caption = `${sentenceCase(data?.quoteUnit ?? data?.currency ?? "price")} by contract month`;
@@ -142,7 +176,9 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
142
176
  };
143
177
  // A past date with no archived curve says why in the body, not behind the warning.
144
178
  const emptyPast = !!requestedDate && !!data && !data.contracts.length;
145
- usePaneNoticeFooter({ registrationId: "futures-curve:notices", focused, notices: emptyPast ? [] : data?.gaps ?? [] });
179
+ // Thin contracts join the data warnings: the info row has no room for them beside the quote times.
180
+ usePaneNoticeFooter({ registrationId: "futures-curve:notices", focused,
181
+ notices: emptyPast ? [] : [...data?.gaps ?? [], ...thinCount ? [thinContractsNotice(thinCount)] : []] });
146
182
  const delay = Math.max(0, ...(data?.contracts.map((row) => row.delayMinutes ?? 0) ?? []));
147
183
  usePaneStatusFooter({ registrationId: "futures-curve", loading: resource.loading, error: resource.error,
148
184
  hints: [
@@ -152,6 +188,8 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
152
188
  info: data ? [
153
189
  { id: "source", parts: [{ text: `${requestedDate ? "daily archive" : data.source === "cboe" ? "settlement" : delay > 0 ? `${delay}m delayed` : "dated quotes"} · ${data.quoteUnit ?? data.currency ?? "units unavailable"} · ${curveTimestamp(newest)}${newest?.includes("T") ? " UTC" : ""}`, tone: "muted" }] },
154
190
  ...(staleCount ? [{ id: "stale", parts: [{ text: `${staleCount} of ${data.contracts.length} stale`, tone: "warning" as const }] }] : []),
191
+ ...(spot ? [{ id: "spot", parts: [spot.status === "ok" ? { text: curveSpotLabel(spot, root, clock), tone: "muted" as const }
192
+ : { text: `Basis blank: ${spot.reason}`, tone: "warning" as const }] }] : []),
155
193
  ] : [],
156
194
  });
157
195
  const tableChromeRows = chartTableChromeRows(columns, width);
@@ -166,8 +204,8 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
166
204
  const renderCell = useCallback((row: FuturesContract, column: DataTableColumn) => {
167
205
  if (column.id === "symbol") return { text: row.symbol };
168
206
  if (column.id === "expiry") return { text: row.expiration, color: colors.textMuted };
169
- // Without the AS OF column, a stale quote shows on its price.
170
- if (column.id === "price") return { text: curvePrice(row.price, root), color: curveTab && row.stale ? colors.warning : undefined };
207
+ // Without the AS OF column, a stale quote shows on its price, and so does a print too old for the spot.
208
+ if (column.id === "price") return { text: curvePrice(row.price, root), color: curveTab && (row.stale || basis.get(row.symbol)?.thin) ? colors.warning : undefined };
171
209
  if (column.id === "change") {
172
210
  const text = curveChangeText(row.change ?? null, root);
173
211
  return { text, color: text.startsWith("+") ? colors.positive : text.startsWith("-") && /[1-9]/.test(text) ? colors.negative : colors.textMuted };
@@ -176,11 +214,15 @@ function FuturesCurveView({ width, height, focused, root }: PaneProps & { root:
176
214
  const change = changes.get(row.symbol)?.[column.id === "change1w" ? "1W" : "1M"] ?? null;
177
215
  return { text: curveChangeText(change, root), color: colors.textMuted };
178
216
  }
217
+ if (column.id === "vsSpot" || column.id === "annBasis") {
218
+ const cell = basis.get(row.symbol);
219
+ return { text: column.id === "vsSpot" ? curveBasisPercent(cell?.vsSpotPct ?? null, 2) : curveBasisPercent(cell?.annualisedBasisPct ?? null, 1), color: colors.textMuted };
220
+ }
179
221
  if (column.id === "percentile") return { text: row.samples < 2 || row.percentile == null ? "--" : row.percentile.toFixed(0) };
180
222
  if (column.id === "oi") return { text: integer(row.openInterest) };
181
223
  if (column.id === "volume") return { text: integer(row.volume) };
182
224
  return { text: curveTimestamp(row.asOf), color: row.stale ? colors.warning : colors.textMuted };
183
- }, [changes, colors, curveTab, root]);
225
+ }, [basis, changes, colors, curveTab, root]);
184
226
  return <Box width={width} height={height} flexDirection="column">
185
227
  {tabStrip}
186
228
  <QueryBar width={width} filters={[{
@@ -2,6 +2,7 @@ import type { GpuObservation } from "../../../api-client/gpu";
2
2
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { fetchGpuBoard, fetchGpuEvents, fetchGpuHistory, loadGpuEquityHistory } from "./client";
4
4
  import { GPU_TABS, gpuArgument, gpuBasisLabel, gpuChange, gpuEquityRows, gpuEventDate, gpuProvenanceLabel, gpuLabel, gpuRows, gpuSource, gpuTab } from "./model";
5
+ import { quoteFreshnessFields } from "../shared/report-freshness";
5
6
 
6
7
  const observationRow = (row: GpuObservation) => ({ gpu: gpuLabel(row), source: gpuSource(row), basis: gpuBasisLabel(row.basis),
7
8
  price: row.pricePerGpuHr, availability: row.availability, observedAt: row.observedAt, effectiveAt: row.effectiveAt, provenance: row.provenance ?? "live", provenanceLabel: gpuProvenanceLabel(row, true),
@@ -26,9 +27,11 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
26
27
  const tab = gpuTab(args.options.tab);
27
28
  const board = await fetchGpuBoard(ctx.apiClient);
28
29
  const metadata = { asOf: board.asOf, stale: board.stale, complete: board.status === "available" && !board.access?.locked, unit: "USD/GPU-hour", access: board.access };
30
+ // Prices observed on providers' pages, not a feed; each board row says whether its source has gone quiet.
31
+ const freshness = { source: "GPU cloud providers", status: "not-a-feed" as const, basis: "observed prices" };
29
32
  if (tab === "history") {
30
33
  const history = await fetchGpuHistory({ ...(model ? { gpuModel: model } : {}), limit: 10_000 }, ctx.apiClient);
31
- return { sections: [{ title: "Dated observations", columns: [...columns.filter((column) => !column.key.startsWith("change")), { key: "provenanceLabel", header: "Record" }], rows: history.points.map(observationRow) }], complete: !history.access?.locked, metadata: { ...metadata, access: history.access ?? board.access }, errors: board.gaps };
34
+ return { sections: [{ title: "Dated observations", columns: [...columns.filter((column) => !column.key.startsWith("change")), { key: "provenanceLabel", header: "Record" }], rows: history.points.map(observationRow) }], freshness: { ...freshness, oldest: null }, complete: !history.access?.locked, metadata: { ...metadata, access: history.access ?? board.access }, errors: board.gaps };
32
35
  }
33
36
  if (tab === "changes") {
34
37
  const payload = await fetchGpuEvents(model || undefined, ctx.apiClient);
@@ -41,7 +44,7 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
41
44
  oldPrice: event.oldPrice, newPrice: event.newPrice, changePct: event.changePct, kind: event.kind,
42
45
  oldMembers: event.oldMembers, newMembers: event.newMembers, provenance: event.provenance ?? "live",
43
46
  provenanceLabel: gpuProvenanceLabel(event, true), evidenceUrl: event.evidenceUrl ?? null, sourceUrl: event.sourceUrl ?? null,
44
- oldAvailability: event.oldAvailability ?? null, newAvailability: event.newAvailability ?? null })) }], complete: !payload.access?.locked, metadata: { ...metadata, access: payload.access ?? board.access }, errors: board.gaps };
47
+ oldAvailability: event.oldAvailability ?? null, newAvailability: event.newAvailability ?? null })) }], freshness: { ...freshness, oldest: null }, complete: !payload.access?.locked, metadata: { ...metadata, access: payload.access ?? board.access }, errors: board.gaps };
45
48
  }
46
49
  if (tab === "equities") {
47
50
  const related = gpuEquityRows(board.rows, model || "H100");
@@ -51,6 +54,7 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
51
54
  const history = histories.find((entry) => entry.symbol === row.symbol);
52
55
  return { symbol: row.symbol, role: row.role, price: quote?.price ?? null, change1d: quote?.changePercent ?? null,
53
56
  change5d: history?.value ?? null, fiveDayAsOf: history?.asOf ?? null, quoteAsOf: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : null,
57
+ ...quoteFreshnessFields(quote),
54
58
  gpu: row.reference ? gpuLabel(row.reference) : row.gpuModel, gpuSource: row.reference ? gpuSource(row.reference) : null, gpuChange7d: row.reference?.change7d ?? null };
55
59
  }));
56
60
  return { sections: [{ title: "Related equities", columns: [
@@ -62,6 +66,6 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
62
66
  }
63
67
  const rows = gpuRows(board.rows, model).map((row) => ({ ...observationRow(row), change1d: row.change1d, change7d: row.change7d,
64
68
  change30d: row.change30d, stale: row.stale, sample: row.stats ?? null }));
65
- return { sections: [{ title: "GPU rental prices", columns, rows }], complete: !board.access?.locked, metadata, errors: board.gaps };
69
+ return { sections: [{ title: "GPU rental prices", columns, rows }], freshness, complete: !board.access?.locked, metadata, errors: board.gaps };
66
70
  },
67
71
  };
@@ -1,8 +1,8 @@
1
1
  import { Box, Span, Text, TextAttributes, useUiCapabilities } from "../../../ui";
2
2
  import { useThemeColors } from "../../../theme/theme-context";
3
3
  import { blendHex } from "../../../theme/colors";
4
- import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
5
- import { gpuAxisTicks, gpuBasisColor, gpuBasisLabel, gpuPrice, type GpuLadderRow } from "./model";
4
+ import { truncateToDisplayWidth } from "../../../utils/format";
5
+ import { gpuAxisLabels, gpuBasisColor, gpuBasisLabel, gpuPrice, type GpuAxisLabel, type GpuLadderRow } from "./model";
6
6
 
7
7
  const LABEL_CELLS = 28;
8
8
  const VALUE_CELLS = 17;
@@ -10,14 +10,13 @@ const VALUE_CELLS = 17;
10
10
  const position = (value: number, low: number, high: number) => high > low ? (value - low) / (high - low) : 0.5;
11
11
  const percent = (ratio: number) => `${(Math.max(0, Math.min(1, ratio)) * 100).toFixed(2)}%`;
12
12
 
13
- /** The domain every basis shares, padded to whole axis steps so the ends never touch a band. */
14
- function gpuLadderDomain(rows: readonly GpuLadderRow[]): { low: number; high: number; ticks: number[] } | null {
13
+ /** The domain every basis shares, padded so the ends never touch a band. */
14
+ function gpuLadderDomain(rows: readonly GpuLadderRow[]): { low: number; high: number } | null {
15
15
  if (!rows.length) return null;
16
16
  const min = Math.min(...rows.map((row) => row.min));
17
17
  const max = Math.max(...rows.map((row) => row.max));
18
18
  const pad = Math.max((max - min) * 0.04, max * 0.02, 0.05);
19
- const low = Math.max(0, min - pad), high = max + pad;
20
- return { low, high, ticks: gpuAxisTicks(low, high).filter((tick) => tick >= low && tick <= high) };
19
+ return { low: Math.max(0, min - pad), high: max + pad };
21
20
  }
22
21
 
23
22
  /** Terminal cells of one track: thin rule for the range, heavy for the quartiles, a bar at the median. */
@@ -51,7 +50,10 @@ export function GpuPriceLadder({ rows, width, height, selected }: {
51
50
  const valueCells = wide ? VALUE_CELLS : 6;
52
51
  // Two cells of padding, two gaps, and one spare so the value never clips.
53
52
  const trackCellsWidth = Math.max(8, width - labelCells - valueCells - 5);
54
- const { low, high, ticks } = domain;
53
+ const { low, high } = domain;
54
+ // Labels may hang into the empty label and value columns of the axis row, so the end ticks stay centred.
55
+ const room = { left: labelCells + 1, right: valueCells + 1 };
56
+ const labels = gpuAxisLabels(low, high, trackCellsWidth, room);
55
57
  return (
56
58
  <Box flexDirection="column" width={width} height={shown.length + 1} paddingX={1} data-gloom-role="gpu-price-ladder">
57
59
  {shown.map((row) => {
@@ -91,37 +93,31 @@ export function GpuPriceLadder({ rows, width, height, selected }: {
91
93
  </Box>
92
94
  );
93
95
  })}
94
- <Box flexDirection="row" height={1} gap={1}>
95
- <Box width={labelCells} flexShrink={0} />
96
- <GpuLadderAxis ticks={ticks} low={low} high={high} width={trackCellsWidth} desktop={desktop} />
97
- </Box>
96
+ <GpuLadderAxis labels={labels} width={trackCellsWidth} room={room} desktop={desktop} />
98
97
  </Box>
99
98
  );
100
99
  }
101
100
 
102
- function GpuLadderAxis({ ticks, low, high, width, desktop }: { ticks: number[]; low: number; high: number; width: number; desktop: boolean }) {
101
+ /**
102
+ * Each label centred on its tick, on the scale the bands and markers use. The
103
+ * desktop places real elements by percentage, as it does the markers; the
104
+ * terminal writes the whole axis row so an end label can hang past the track.
105
+ */
106
+ function GpuLadderAxis({ labels, width, room, desktop }: { labels: readonly GpuAxisLabel[]; width: number; room: { left: number; right: number }; desktop: boolean }) {
103
107
  const colors = useThemeColors();
104
- const label = (tick: number) => `$${Number.isInteger(tick) ? tick : tick.toFixed(1)}`;
105
108
  if (desktop) {
106
109
  return (
107
- <Box width={width} height={1} flexShrink={0} style={{ position: "relative" }}>
108
- {ticks.map((tick, index) => {
109
- const ratio = position(tick, low, high);
110
- const align = index === 0 ? "0%" : index === ticks.length - 1 ? "-100%" : "-50%";
111
- return <Box key={tick} style={{ position: "absolute", left: percent(ratio), transform: `translateX(${align})` }}>
112
- <Text fg={colors.textDim}>{label(tick)}</Text>
113
- </Box>;
114
- })}
110
+ <Box flexDirection="row" height={1}>
111
+ <Box width={room.left} flexShrink={0} />
112
+ <Box width={width} height={1} flexShrink={0} style={{ position: "relative" }}>
113
+ {labels.map((label) => <Box key={label.value} style={{ position: "absolute", left: percent(label.ratio), transform: "translateX(-50%)", whiteSpace: "nowrap" }}>
114
+ <Text fg={colors.textDim}>{label.text}</Text>
115
+ </Box>)}
116
+ </Box>
115
117
  </Box>
116
118
  );
117
119
  }
118
- const line = Array.from({ length: width }, () => " ");
119
- for (const [index, tick] of ticks.entries()) {
120
- const text = label(tick);
121
- const center = Math.round(position(tick, low, high) * (width - 1));
122
- const start = index === 0 ? 0 : index === ticks.length - 1 ? width - displayWidth(text) : center - Math.floor(text.length / 2);
123
- if (start < 0 || start + text.length > width || line.slice(Math.max(0, start - 1), start + text.length + 1).some((char) => char !== " ")) continue;
124
- for (let i = 0; i < text.length; i++) line[start + i] = text[i]!;
125
- }
126
- return <Box width={width} height={1} flexShrink={0}><Text fg={colors.textDim}>{line.join("")}</Text></Box>;
120
+ const line = Array.from({ length: room.left + width + room.right }, () => " ");
121
+ for (const label of labels) for (let i = 0; i < label.text.length; i++) line[room.left + label.start + i] = label.text[i]!;
122
+ return <Box height={1} flexShrink={0}><Text fg={colors.textDim}>{line.join("").trimEnd()}</Text></Box>;
127
123
  }
@@ -151,7 +151,7 @@ export function gpuPriceLadder(rows: readonly GpuBoardRow[], model: string): Gpu
151
151
  }
152
152
 
153
153
  /** Round dollar ticks for a price axis: one, two or five times a power of ten, four to six of them. */
154
- export function gpuAxisTicks(low: number, high: number): number[] {
154
+ function gpuAxisTicks(low: number, high: number): number[] {
155
155
  if (!(high > low)) return [low];
156
156
  const raw = (high - low) / 5;
157
157
  const power = 10 ** Math.floor(Math.log10(raw));
@@ -161,6 +161,32 @@ export function gpuAxisTicks(low: number, high: number): number[] {
161
161
  return ticks;
162
162
  }
163
163
 
164
+ export interface GpuAxisLabel { value: number; text: string; ratio: number; start: number }
165
+
166
+ /**
167
+ * Labels for a price axis `cells` wide over `low`..`high`: each centred on its
168
+ * tick's own value, on the linear scale the bands and markers use (`ratio`, and
169
+ * `start`, the first cell on the terminal), with the decimals the tick step
170
+ * needs so neighbours never read alike ($2.0 $2.1 $2.2, $2.05 $2.10). A label
171
+ * that would touch its neighbour or run past the room beside the axis is left
172
+ * out, never moved off its value.
173
+ */
174
+ export function gpuAxisLabels(low: number, high: number, cells: number, room: { left: number; right: number } = { left: 0, right: 0 }): GpuAxisLabel[] {
175
+ const ticks = gpuAxisTicks(low, high).filter((tick) => tick >= low && tick <= high);
176
+ const digits = [0, 1, 2, 3].find((places) => ticks.every((tick) => Math.abs(Number(tick.toFixed(places)) - tick) < 1e-9)) ?? 3;
177
+ const labels: GpuAxisLabel[] = [];
178
+ for (const value of ticks) {
179
+ const text = `$${value.toFixed(digits)}`;
180
+ const ratio = high > low ? (value - low) / (high - low) : 0.5;
181
+ // The cell a track draws this value in, so the label's middle sits on the marker's cell.
182
+ const start = Math.round(ratio * (cells - 1)) - Math.floor(text.length / 2);
183
+ const previous = labels.at(-1);
184
+ if (start < -room.left || start + text.length > cells + room.right || (previous && start <= previous.start + previous.text.length)) continue;
185
+ labels.push({ value, text, ratio, start });
186
+ }
187
+ return labels;
188
+ }
189
+
164
190
  /** Published notices use their effective date; observed changes use the dated evidence. */
165
191
  export const gpuEventDate = (event: Pick<GpuEvent, "origin" | "effectiveAt" | "observedAt">) =>
166
192
  event.origin === "published" ? event.effectiveAt ?? event.observedAt : event.observedAt;