gloomberb 0.17.6 → 0.18.0

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 (768) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/package.json +6 -1
  4. package/patches/electrobun@1.18.1.patch +74 -0
  5. package/src/account/auth-model.ts +3 -2
  6. package/src/account/device-signin-dialog.tsx +38 -13
  7. package/src/account/device-signin.ts +2 -1
  8. package/src/account/upgrade-dialog.tsx +6 -2
  9. package/src/api-client/askg.ts +5 -3
  10. package/src/api-client/bank-figures.ts +73 -0
  11. package/src/api-client/credit-boards.ts +21 -1
  12. package/src/api-client/crypto-markets.ts +88 -1
  13. package/src/api-client/data.ts +104 -4
  14. package/src/api-client/debt-maturities.ts +18 -0
  15. package/src/api-client/equity-screener.ts +3 -0
  16. package/src/api-client/errors.ts +13 -0
  17. package/src/api-client/fund-facts.ts +72 -0
  18. package/src/api-client/futures-curve.ts +26 -3
  19. package/src/api-client/geo.ts +11 -1
  20. package/src/api-client/index.ts +25 -2
  21. package/src/api-client/insider-signals.ts +95 -11
  22. package/src/api-client/normalizers.ts +1 -0
  23. package/src/api-client/paths.ts +20 -1
  24. package/src/api-client/request.ts +11 -3
  25. package/src/api-client/research-activity.ts +15 -8
  26. package/src/api-client/socket.ts +6 -3
  27. package/src/api-client/supply-chain-graph.ts +2 -2
  28. package/src/api-client/supply-chain.ts +8 -0
  29. package/src/api-client/tape.ts +12 -0
  30. package/src/api-client/types.ts +66 -2
  31. package/src/api-client/upgrade-experiment.ts +4 -3
  32. package/src/api-client/wall-experiments.ts +5 -3
  33. package/src/api-client/wall-summary.ts +3 -1
  34. package/src/api-client/web-experiments.ts +5 -2
  35. package/src/api-client/yield-curves.ts +17 -0
  36. package/src/app/command-bar-intro.ts +38 -0
  37. package/src/app/keybindings/actions.ts +1 -1
  38. package/src/app/pane-runtime/pane-template-runtime.ts +35 -7
  39. package/src/app/pane-runtime/plugin-bindings.ts +4 -0
  40. package/src/app/pane-runtime/ticker-open-runtime.ts +4 -14
  41. package/src/app/pane-runtime/ticker-open-target.ts +31 -0
  42. package/src/app/runtime/desktop-deeplink.ts +3 -4
  43. package/src/app/runtime/plugin-bindings.ts +3 -2
  44. package/src/app/runtime/startup.ts +3 -4
  45. package/src/app/runtime/ticker-refresh.ts +13 -12
  46. package/src/app/runtime/update.ts +3 -2
  47. package/src/architecture/core-builtin-allowlist.json +0 -3
  48. package/src/architecture/silent-catch-allowlist.json +3 -0
  49. package/src/architecture/ui-literals-allowlist.json +186 -0
  50. package/src/architecture/ui-tokens-allowlist.json +40 -0
  51. package/src/ask-gloom/pane-context.ts +3 -1
  52. package/src/brokers/signed-in/catalog.ts +2 -1
  53. package/src/brokers/signed-in/client.ts +7 -2
  54. package/src/brokers/signed-in/sign-in-dialog.tsx +120 -26
  55. package/src/brokers/signed-in/sign-in.ts +22 -20
  56. package/src/brokers/sync-broker-instance.ts +2 -1
  57. package/src/capabilities/chart-series.ts +2 -0
  58. package/src/capabilities/registry.ts +1 -3
  59. package/src/cli/bank-section.ts +147 -0
  60. package/src/cli/base-converter.ts +5 -2
  61. package/src/cli/board-coins.ts +70 -0
  62. package/src/cli/commands/fred-series.ts +90 -0
  63. package/src/cli/commands/market.ts +340 -69
  64. package/src/cli/commands/news-search.ts +35 -2
  65. package/src/cli/commands/options-chain.ts +7 -5
  66. package/src/cli/commands/options-leaps.ts +3 -3
  67. package/src/cli/commands/overview.ts +78 -29
  68. package/src/cli/commands/plugins.ts +1 -0
  69. package/src/cli/commands/search.ts +87 -24
  70. package/src/cli/commands/system.ts +5 -2
  71. package/src/cli/commands/ticker.ts +337 -60
  72. package/src/cli/context.ts +2 -1
  73. package/src/cli/desktop-pane-shot.ts +11 -3
  74. package/src/cli/earnings-calendar.ts +2 -1
  75. package/src/cli/errors.ts +10 -0
  76. package/src/cli/export-source.ts +18 -3
  77. package/src/cli/glossary.ts +196 -14
  78. package/src/cli/history-rows.ts +60 -9
  79. package/src/cli/index-roots.ts +26 -0
  80. package/src/cli/index.ts +34 -9
  81. package/src/cli/known-gaps.ts +156 -22
  82. package/src/cli/listing-arg.ts +156 -12
  83. package/src/cli/listing-suggestion.ts +4 -2
  84. package/src/cli/name-entries.ts +76 -0
  85. package/src/cli/not-a-ticker.ts +15 -6
  86. package/src/cli/pane-functions/access-gate.ts +22 -0
  87. package/src/cli/pane-functions/capabilities.ts +62 -4
  88. package/src/cli/pane-functions/catalog.ts +94 -39
  89. package/src/cli/pane-functions/data.ts +6 -5
  90. package/src/cli/pane-functions/discovery.ts +1 -1
  91. package/src/cli/pane-functions/dom.ts +3 -3
  92. package/src/cli/pane-functions/freshness.ts +49 -8
  93. package/src/cli/pane-functions/function-help.ts +13 -10
  94. package/src/cli/pane-functions/headless.ts +177 -42
  95. package/src/cli/pane-functions/index.ts +64 -8
  96. package/src/cli/pane-functions/options.ts +17 -3
  97. package/src/cli/pane-functions/report-notices.ts +17 -3
  98. package/src/cli/pane-functions/resolver.ts +48 -13
  99. package/src/cli/pane-functions/screenshot.ts +124 -23
  100. package/src/cli/pane-functions/shot-options.ts +68 -0
  101. package/src/cli/perpetual-contract.ts +3 -1
  102. package/src/cli/report-tables.ts +10 -2
  103. package/src/cli/staged-tickers.ts +5 -0
  104. package/src/cli/statement-filings.ts +261 -0
  105. package/src/cli/symbol-args.ts +47 -8
  106. package/src/cli/symbol-list.ts +9 -2
  107. package/src/cli/symbol-rows.ts +269 -29
  108. package/src/cli/ticker-resolution.ts +148 -34
  109. package/src/cli/treasury-yields.ts +86 -0
  110. package/src/cli/types.ts +3 -0
  111. package/src/cli/uncovered-instrument.ts +20 -0
  112. package/src/components/chart/composite/axis-overlays.tsx +16 -9
  113. package/src/components/chart/composite/composite-chart.tsx +85 -6
  114. package/src/components/chart/composite/downsample.ts +19 -3
  115. package/src/components/chart/composite/format.ts +3 -0
  116. package/src/components/chart/composite/legend.tsx +155 -76
  117. package/src/components/chart/composite/panel-bitmap.ts +8 -2
  118. package/src/components/chart/composite/panel-surface.tsx +3 -3
  119. package/src/components/chart/composite/toolbar.tsx +12 -7
  120. package/src/components/chart/composite/types.ts +6 -0
  121. package/src/components/chart/composite/use-composite-chart-keyboard.ts +28 -1
  122. package/src/components/chart/curve/curve-surface.tsx +1 -1
  123. package/src/components/chart/static/series.ts +39 -0
  124. package/src/components/chart/static/trail-chart-surface.tsx +2 -1
  125. package/src/components/command-bar/commands/collection-list.ts +45 -15
  126. package/src/components/command-bar/commands/collection.ts +10 -3
  127. package/src/components/command-bar/commands/registry.ts +2 -2
  128. package/src/components/command-bar/list/badge.ts +4 -3
  129. package/src/components/command-bar/list/model.ts +5 -0
  130. package/src/components/command-bar/list/view.tsx +1 -1
  131. package/src/components/command-bar/panel/index.tsx +19 -8
  132. package/src/components/command-bar/panel/native-surface.ts +6 -4
  133. package/src/components/command-bar/panel/prompt-example.ts +22 -0
  134. package/src/components/command-bar/routes/root/provider-search.ts +3 -9
  135. package/src/components/command-bar/routes/root/results.ts +13 -0
  136. package/src/components/command-bar/routes/root/start-here.ts +90 -0
  137. package/src/components/command-bar/routes/ticker-search/results.ts +42 -3
  138. package/src/components/command-bar/routes/ticker-search/route.ts +3 -10
  139. package/src/components/command-bar/theme-picker.tsx +3 -1
  140. package/src/components/command-bar/workflow/builtin.ts +5 -1
  141. package/src/components/command-bar/workflow/collection-actions.ts +21 -12
  142. package/src/components/command-bar/workflow/ops.ts +21 -1
  143. package/src/{plugins/builtin/portfolio-list/command-bar.ts → components/command-bar/workflow/portfolio-position.ts} +42 -9
  144. package/src/components/command-bar/workflow/tickers.ts +6 -4
  145. package/src/components/feedback-dialog.tsx +1 -1
  146. package/src/components/form-modal/broker-step.tsx +7 -63
  147. package/src/components/form-modal/content.tsx +7 -2
  148. package/src/components/form-modal/field-row.tsx +6 -8
  149. package/src/components/form-modal/host.tsx +3 -3
  150. package/src/components/form-modal/model.ts +3 -0
  151. package/src/components/form-modal/routes.ts +17 -2
  152. package/src/components/index.ts +6 -6
  153. package/src/components/input-search-bar.tsx +43 -6
  154. package/src/components/layout/detached-pane-shell.tsx +0 -1
  155. package/src/components/layout/floating-pane.tsx +0 -1
  156. package/src/components/layout/header.tsx +8 -7
  157. package/src/components/layout/pane/footer/index.tsx +7 -4
  158. package/src/components/layout/pane/footer/model.ts +28 -2
  159. package/src/components/layout/pane/header.tsx +9 -19
  160. package/src/components/layout/pane/index.tsx +0 -1
  161. package/src/components/layout/pane/instrument-coverage-gate.tsx +57 -0
  162. package/src/components/layout/pane/pane-tabs.tsx +4 -3
  163. package/src/components/layout/pane/sizing.ts +9 -0
  164. package/src/components/layout/pane/ticker-fate-gate.tsx +8 -2
  165. package/src/components/layout/shell/pane/capture.ts +44 -20
  166. package/src/components/layout/status-bar.tsx +3 -6
  167. package/src/components/markdown-text.tsx +17 -2
  168. package/src/components/market-board/index.tsx +19 -8
  169. package/src/components/metric-treemap/heat-surface.tsx +2 -7
  170. package/src/components/onboarding/account-step/qr-panel.tsx +5 -2
  171. package/src/components/onboarding/onboarding-frame.tsx +24 -21
  172. package/src/components/onboarding/portfolio-step/positions-panel.tsx +20 -10
  173. package/src/components/onboarding/wizard-positions.ts +40 -11
  174. package/src/components/pane-settings-dialog/desktop.tsx +12 -50
  175. package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -1
  176. package/src/components/price-performance.tsx +18 -2
  177. package/src/components/reader/article-reader.tsx +11 -10
  178. package/src/components/reader/detail-reader.tsx +9 -5
  179. package/src/components/reader/reader-text.tsx +51 -24
  180. package/src/components/sign-in-code-panel.tsx +129 -75
  181. package/src/components/ticker/badge/index.tsx +2 -1
  182. package/src/components/ui/action-row.tsx +5 -2
  183. package/src/components/ui/audio-player.tsx +70 -0
  184. package/src/components/ui/card.tsx +75 -0
  185. package/src/components/ui/data-table/index.tsx +4 -2
  186. package/src/components/ui/data-table/types.ts +11 -0
  187. package/src/components/ui/detail-scroll-body.tsx +1 -1
  188. package/src/components/ui/dialog-actions.tsx +1 -21
  189. package/src/components/ui/display.tsx +13 -5
  190. package/src/components/ui/external-link.tsx +4 -2
  191. package/src/components/ui/fields.tsx +12 -0
  192. package/src/components/ui/figure.tsx +5 -3
  193. package/src/components/ui/frame.tsx +40 -6
  194. package/src/components/ui/icon.tsx +4 -0
  195. package/src/components/ui/index.ts +4 -1
  196. package/src/components/ui/list-view.tsx +8 -0
  197. package/src/components/ui/prose.tsx +97 -25
  198. package/src/components/ui/query-bar-keys.tsx +3 -1
  199. package/src/components/ui/query-bar.tsx +6 -0
  200. package/src/components/ui/quote.ts +42 -0
  201. package/src/components/ui/range-track.tsx +15 -2
  202. package/src/components/ui/reveal-in-scroll-box.ts +21 -3
  203. package/src/components/ui/select-button.tsx +38 -3
  204. package/src/components/ui/status.tsx +73 -4
  205. package/src/components/ui/table-layout.ts +20 -0
  206. package/src/components/use-pinned-moment.ts +19 -0
  207. package/src/core/app-runtime.ts +2 -2
  208. package/src/core/state/app/instrument.ts +18 -1
  209. package/src/core/state/app/layout.ts +6 -0
  210. package/src/core/state/app/state.ts +14 -2
  211. package/src/data/cached-query.ts +1 -1
  212. package/src/data/config/chart-settings.ts +3 -1
  213. package/src/data/config/home.ts +4 -1
  214. package/src/data/config/store/node.ts +9 -4
  215. package/src/data/config/store/normalize.ts +9 -0
  216. package/src/data/json-storage.ts +6 -3
  217. package/src/data/json-ticker-repository.ts +3 -1
  218. package/src/data/plugin-cache.ts +5 -1
  219. package/src/data/sqlite/json.ts +3 -1
  220. package/src/data/ticker-repository.ts +5 -2
  221. package/src/feedback/diagnostics.ts +2 -6
  222. package/src/i18n/es.ts +86 -6
  223. package/src/i18n/index.ts +3 -9
  224. package/src/i18n/ja.ts +86 -6
  225. package/src/i18n/ko.ts +86 -6
  226. package/src/i18n/zh-cn.ts +86 -6
  227. package/src/i18n/zh-tw.ts +86 -6
  228. package/src/layout-marketplace/gallery-desktop.tsx +8 -4
  229. package/src/market-data/bars-per-year.ts +43 -0
  230. package/src/market-data/bond-funds.ts +85 -0
  231. package/src/market-data/coordinator/auxiliary.ts +5 -2
  232. package/src/market-data/coordinator/index.ts +6 -5
  233. package/src/market-data/futures-contract.ts +102 -5
  234. package/src/market-data/hooks.tsx +20 -11
  235. package/src/market-data/market/bar-period.ts +36 -0
  236. package/src/market-data/market/format.ts +5 -13
  237. package/src/market-data/market/freshness.ts +1 -3
  238. package/src/market-data/market/index-level.ts +51 -0
  239. package/src/market-data/model-dividend-yield.ts +57 -16
  240. package/src/market-data/quotes/live-valuation.ts +48 -2
  241. package/src/market-data/realized-volatility.ts +16 -7
  242. package/src/market-data/tax-equivalent.ts +344 -0
  243. package/src/news/aggregator.ts +4 -2
  244. package/src/notifications/notification-log.ts +5 -3
  245. package/src/plugins/auto-update-node.ts +1 -0
  246. package/src/plugins/builtin/account-management/footer.ts +1 -1
  247. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  248. package/src/plugins/builtin/account-management/model.ts +1 -0
  249. package/src/plugins/builtin/account-management/pane.tsx +71 -19
  250. package/src/plugins/builtin/account-management/trial-cancel.ts +40 -0
  251. package/src/plugins/builtin/alerts/cli.ts +7 -18
  252. package/src/plugins/builtin/alerts/constants.ts +8 -0
  253. package/src/plugins/builtin/alerts/event-rows.ts +16 -0
  254. package/src/plugins/builtin/alerts/events-pane.tsx +2 -14
  255. package/src/plugins/builtin/alerts/headless.ts +182 -0
  256. package/src/plugins/builtin/alerts/index.tsx +3 -0
  257. package/src/plugins/builtin/alerts/pane.tsx +1 -7
  258. package/src/plugins/builtin/alerts/storage.ts +17 -1
  259. package/src/plugins/builtin/analytics/index.tsx +6 -1
  260. package/src/plugins/builtin/analytics/metrics.ts +47 -9
  261. package/src/plugins/builtin/analytics/risk-headless.ts +14 -2
  262. package/src/plugins/builtin/analytics/risk-math.ts +83 -0
  263. package/src/plugins/builtin/analytics/risk-model.ts +97 -4
  264. package/src/plugins/builtin/analytics/risk-pane.tsx +11 -6
  265. package/src/plugins/builtin/analytics/sector-model.ts +14 -3
  266. package/src/plugins/builtin/awards/headless.ts +6 -5
  267. package/src/plugins/builtin/awards/index.tsx +1 -1
  268. package/src/plugins/builtin/backtest/client.ts +38 -8
  269. package/src/plugins/builtin/backtest/engine.ts +25 -13
  270. package/src/plugins/builtin/backtest/export.ts +27 -0
  271. package/src/plugins/builtin/backtest/format.ts +19 -3
  272. package/src/plugins/builtin/backtest/headless.ts +42 -28
  273. package/src/plugins/builtin/backtest/index.tsx +3 -0
  274. package/src/plugins/builtin/backtest/pane.tsx +39 -10
  275. package/src/plugins/builtin/backtest/report.ts +160 -0
  276. package/src/plugins/builtin/backtest/rules.ts +2 -0
  277. package/src/plugins/builtin/backtest/window.ts +112 -0
  278. package/src/plugins/builtin/bond-calculator/headless.ts +3 -2
  279. package/src/plugins/builtin/broker-manager/add-flow.tsx +4 -4
  280. package/src/plugins/builtin/catalysts/headless.ts +24 -13
  281. package/src/plugins/builtin/catalysts/index.tsx +4 -2
  282. package/src/plugins/builtin/catalysts/model.ts +18 -1
  283. package/src/plugins/builtin/catalysts/pane.tsx +2 -2
  284. package/src/plugins/builtin/cds/client.ts +28 -5
  285. package/src/plugins/builtin/cds/headless.ts +183 -31
  286. package/src/plugins/builtin/cds/index.tsx +1 -1
  287. package/src/plugins/builtin/cds/model.ts +229 -53
  288. package/src/plugins/builtin/cds/pane.tsx +82 -26
  289. package/src/plugins/builtin/central-bank-rates/headless.ts +1 -1
  290. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +39 -1
  291. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +77 -0
  292. package/src/plugins/builtin/chart-composer/chart-spec.ts +15 -0
  293. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +22 -2
  294. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +2 -2
  295. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -2
  296. package/src/plugins/builtin/chart-composer/headless.ts +99 -21
  297. package/src/plugins/builtin/chart-composer/index.tsx +70 -13
  298. package/src/plugins/builtin/chart-composer/pane.tsx +65 -15
  299. package/src/plugins/builtin/chart-composer/presets.ts +79 -11
  300. package/src/plugins/builtin/chart-composer/series-catalog.ts +50 -14
  301. package/src/plugins/builtin/chart-composer/series-expression.ts +9 -7
  302. package/src/plugins/builtin/chart-composer/series-listings.ts +178 -0
  303. package/src/plugins/builtin/chart-composer/session-range.ts +87 -0
  304. package/src/plugins/builtin/chat/attachments/desktop.tsx +4 -1
  305. package/src/plugins/builtin/chat/content/composer-runtime.ts +49 -14
  306. package/src/plugins/builtin/chat/content/composer.tsx +22 -1
  307. package/src/plugins/builtin/chat/content/index.tsx +35 -5
  308. package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -0
  309. package/src/plugins/builtin/chat/content/shortcuts.ts +32 -13
  310. package/src/plugins/builtin/chat/content/snapshot.ts +29 -6
  311. package/src/plugins/builtin/chat/ghost-user.ts +11 -3
  312. package/src/plugins/builtin/chat/layout.ts +2 -0
  313. package/src/plugins/builtin/chat/message/desktop.tsx +13 -5
  314. package/src/plugins/builtin/chat/message/header.tsx +26 -4
  315. package/src/plugins/builtin/chat/message/pane-chip.tsx +131 -35
  316. package/src/plugins/builtin/chat/message/render-state.ts +28 -1
  317. package/src/plugins/builtin/chat/message/terminal.tsx +9 -3
  318. package/src/plugins/builtin/chat/pane-links/composer.ts +87 -0
  319. package/src/plugins/builtin/chat/pane-links/model.ts +1 -1
  320. package/src/plugins/builtin/chat/share/flow.tsx +9 -73
  321. package/src/plugins/builtin/cloud/askg/controller.ts +5 -3
  322. package/src/plugins/builtin/cloud/askg/executor.ts +3 -1
  323. package/src/plugins/builtin/cloud/askg/lock-note.ts +16 -0
  324. package/src/plugins/builtin/cloud/askg/pane.tsx +6 -2
  325. package/src/plugins/builtin/cloud/auth-actions.tsx +4 -2
  326. package/src/plugins/builtin/cloud/company-picker.tsx +21 -26
  327. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +5 -3
  328. package/src/plugins/builtin/cloud/plugin.tsx +2 -0
  329. package/src/plugins/builtin/cloud/team/module.tsx +5 -1
  330. package/src/plugins/builtin/cloud/team/pane.tsx +11 -4
  331. package/src/plugins/builtin/cloud/team/views.ts +11 -4
  332. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +9 -4
  333. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +3 -2
  334. package/src/plugins/builtin/cloud/thesis/detail.tsx +2 -1
  335. package/src/plugins/builtin/cloud/thesis/exposure.ts +3 -0
  336. package/src/plugins/builtin/cloud/thesis/prompts.tsx +3 -2
  337. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -1
  338. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +2 -1
  339. package/src/plugins/builtin/cloud/verification-status-widget.tsx +2 -1
  340. package/src/plugins/builtin/commodities-energy/index.tsx +8 -5
  341. package/src/plugins/builtin/commodities-energy/view-state.ts +11 -0
  342. package/src/plugins/builtin/company-attention/headless.ts +15 -7
  343. package/src/plugins/builtin/company-attention/index.tsx +1 -1
  344. package/src/plugins/builtin/company-attention/model.ts +49 -4
  345. package/src/plugins/builtin/company-attention/pane.tsx +11 -9
  346. package/src/plugins/builtin/company-kpis/headless.ts +4 -3
  347. package/src/plugins/builtin/company-kpis/index.tsx +2 -2
  348. package/src/plugins/builtin/company-kpis/pane.tsx +9 -3
  349. package/src/plugins/builtin/congress-trades/headless.ts +23 -2
  350. package/src/plugins/builtin/correlation/relationship/pane.tsx +18 -46
  351. package/src/plugins/builtin/cot/headless.ts +5 -2
  352. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +41 -13
  353. package/src/plugins/builtin/credit-boards/client.ts +9 -2
  354. package/src/plugins/builtin/credit-boards/headless.ts +93 -27
  355. package/src/plugins/builtin/credit-boards/index.tsx +15 -4
  356. package/src/plugins/builtin/credit-boards/model.ts +105 -6
  357. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +14 -3
  358. package/src/plugins/builtin/credit-documents/detail.tsx +2 -1
  359. package/src/plugins/builtin/credit-documents/index.tsx +3 -0
  360. package/src/plugins/builtin/credit-documents/model.ts +3 -10
  361. package/src/plugins/builtin/crypto-board/client.ts +152 -2
  362. package/src/plugins/builtin/crypto-board/headless.ts +138 -19
  363. package/src/plugins/builtin/crypto-board/index.tsx +5 -3
  364. package/src/plugins/builtin/crypto-board/model.ts +210 -12
  365. package/src/plugins/builtin/crypto-board/pane.tsx +148 -9
  366. package/src/plugins/builtin/crypto-board/series.ts +52 -1
  367. package/src/plugins/builtin/crypto-board/supply.ts +123 -0
  368. package/src/plugins/builtin/debt-maturities/client.ts +54 -1
  369. package/src/plugins/builtin/debt-maturities/headless.ts +27 -3
  370. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  371. package/src/plugins/builtin/debt-maturities/model.ts +58 -13
  372. package/src/plugins/builtin/debt-maturities/pane.tsx +47 -7
  373. package/src/plugins/builtin/dividend-yield/client.ts +42 -36
  374. package/src/plugins/builtin/dividend-yield/headless.ts +113 -18
  375. package/src/plugins/builtin/dividend-yield/index.tsx +5 -2
  376. package/src/plugins/builtin/dividend-yield/pane.tsx +37 -13
  377. package/src/plugins/builtin/dividend-yield/provider-client.ts +7 -3
  378. package/src/plugins/builtin/dividend-yield/ranking.ts +187 -0
  379. package/src/plugins/builtin/dividend-yield/reference-price.ts +28 -5
  380. package/src/plugins/builtin/dividend-yield/types.ts +6 -1
  381. package/src/plugins/builtin/dividend-yield/view.ts +1 -1
  382. package/src/plugins/builtin/docs/blocks.tsx +54 -8
  383. package/src/plugins/builtin/docs/pane.tsx +8 -13
  384. package/src/plugins/builtin/earnings/headless.ts +3 -2
  385. package/src/plugins/builtin/earnings-calls/data.ts +51 -0
  386. package/src/plugins/builtin/earnings-calls/headless.ts +7 -0
  387. package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
  388. package/src/plugins/builtin/earnings-calls/model.ts +97 -0
  389. package/src/plugins/builtin/earnings-calls/pane.tsx +107 -7
  390. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +140 -22
  391. package/src/plugins/builtin/earnings-calls/use-call-playback.ts +194 -0
  392. package/src/plugins/builtin/econ/calendar-model.ts +22 -0
  393. package/src/plugins/builtin/econ/calendar-source.ts +35 -0
  394. package/src/plugins/builtin/econ/index.tsx +40 -8
  395. package/src/plugins/builtin/econ-statistics/defs.ts +7 -0
  396. package/src/plugins/builtin/econ-statistics/detail.tsx +8 -1
  397. package/src/plugins/builtin/econ-statistics/headless.ts +26 -5
  398. package/src/plugins/builtin/econ-statistics/pane.tsx +4 -2
  399. package/src/plugins/builtin/econ-statistics/stats.ts +10 -0
  400. package/src/plugins/builtin/econ-statistics/view.ts +19 -0
  401. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  402. package/src/plugins/builtin/equity-screener/model.ts +46 -4
  403. package/src/plugins/builtin/equity-screener/pane.tsx +3 -0
  404. package/src/plugins/builtin/estimate-revisions/client.ts +32 -22
  405. package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
  406. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -0
  407. package/src/plugins/builtin/estimate-revisions/pane.tsx +5 -3
  408. package/src/plugins/builtin/executives/index.tsx +1 -0
  409. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  410. package/src/plugins/builtin/fear-greed/index.tsx +9 -0
  411. package/src/plugins/builtin/filing-events/index.tsx +1 -0
  412. package/src/plugins/builtin/futures/index.tsx +2 -2
  413. package/src/plugins/builtin/futures/model.ts +34 -21
  414. package/src/plugins/builtin/futures/table.tsx +1 -1
  415. package/src/plugins/builtin/futures/use-board-terms.ts +2 -2
  416. package/src/plugins/builtin/futures-curve/headless.ts +4 -2
  417. package/src/plugins/builtin/futures-curve/model.ts +5 -7
  418. package/src/plugins/builtin/help/index.tsx +55 -8
  419. package/src/plugins/builtin/help/shortcut-table.tsx +15 -2
  420. package/src/plugins/builtin/holders/headless.ts +12 -0
  421. package/src/plugins/builtin/insider/headless.ts +311 -41
  422. package/src/plugins/builtin/insider/index.tsx +280 -106
  423. package/src/plugins/builtin/insider/insiders.tsx +224 -0
  424. package/src/plugins/builtin/insider/signals-cells.tsx +52 -0
  425. package/src/plugins/builtin/insider/signals-client.ts +135 -11
  426. package/src/plugins/builtin/insider/signals-model.ts +628 -56
  427. package/src/plugins/builtin/insider/signals.tsx +283 -83
  428. package/src/plugins/builtin/ipo-calendar/headless.ts +17 -7
  429. package/src/plugins/builtin/ipo-calendar/index.tsx +23 -3
  430. package/src/plugins/builtin/ipo-calendar/model.ts +84 -36
  431. package/src/plugins/builtin/ipo-calendar/pane.tsx +20 -8
  432. package/src/plugins/builtin/iv-history/headless.ts +2 -0
  433. package/src/plugins/builtin/jobs/headless.ts +1 -0
  434. package/src/plugins/builtin/jobs/index.tsx +1 -0
  435. package/src/plugins/builtin/jodi/headless.ts +2 -0
  436. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  437. package/src/plugins/builtin/kelly-sizer/portfolio.ts +1 -1
  438. package/src/plugins/builtin/market-heatmap/headless.ts +1 -0
  439. package/src/plugins/builtin/market-heatmap/portfolio.ts +3 -1
  440. package/src/plugins/builtin/market-valuation/pane.tsx +1 -1
  441. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +31 -57
  442. package/src/plugins/builtin/mna/distress/headless.ts +2 -1
  443. package/src/plugins/builtin/mna/headless.ts +14 -2
  444. package/src/plugins/builtin/mna/index.tsx +1 -0
  445. package/src/plugins/builtin/news/headless.ts +4 -0
  446. package/src/plugins/builtin/news/wire/news/footer.ts +5 -3
  447. package/src/plugins/builtin/news/wire/news/table.tsx +57 -11
  448. package/src/plugins/builtin/notes/cli.ts +8 -4
  449. package/src/plugins/builtin/notes/files.ts +66 -20
  450. package/src/plugins/builtin/notes/migration.ts +18 -6
  451. package/src/plugins/builtin/notes/owner.tsx +3 -2
  452. package/src/plugins/builtin/notes/quick-notes-pane.tsx +3 -2
  453. package/src/plugins/builtin/options-scenario/client.ts +7 -4
  454. package/src/plugins/builtin/options-scenario/headless.ts +5 -3
  455. package/src/plugins/builtin/pe-band/index.tsx +1 -1
  456. package/src/plugins/builtin/pe-band/model.ts +1 -15
  457. package/src/plugins/builtin/perps/headless.ts +30 -13
  458. package/src/plugins/builtin/perps/model.ts +45 -3
  459. package/src/plugins/builtin/perps/pane.tsx +7 -7
  460. package/src/plugins/builtin/portfolio-list/cell-version.ts +10 -4
  461. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +128 -14
  462. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +108 -46
  463. package/src/plugins/builtin/portfolio-list/cli/render.ts +368 -45
  464. package/src/plugins/builtin/portfolio-list/cli/shared.ts +8 -2
  465. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +24 -34
  466. package/src/plugins/builtin/portfolio-list/grid.tsx +3 -1
  467. package/src/plugins/builtin/portfolio-list/headless.ts +151 -29
  468. package/src/plugins/builtin/portfolio-list/income.ts +296 -0
  469. package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
  470. package/src/plugins/builtin/portfolio-list/pane/futures-terms.ts +58 -0
  471. package/src/plugins/builtin/portfolio-list/pane/index.tsx +27 -10
  472. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +47 -28
  473. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +3 -2
  474. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +10 -3
  475. package/src/plugins/builtin/portfolio-list/summary/index.tsx +26 -4
  476. package/src/plugins/builtin/portfolio-list/summary/totals.ts +31 -3
  477. package/src/plugins/builtin/portfolio-list/table.tsx +25 -5
  478. package/src/plugins/builtin/power/prices-headless.ts +2 -1
  479. package/src/plugins/builtin/realized-vol/chart-model.ts +29 -5
  480. package/src/plugins/builtin/realized-vol/charts.tsx +14 -8
  481. package/src/plugins/builtin/realized-vol/evidence.ts +2 -0
  482. package/src/plugins/builtin/realized-vol/headless.ts +6 -4
  483. package/src/plugins/builtin/realized-vol/model.ts +5 -1
  484. package/src/plugins/builtin/realized-vol/pane.tsx +31 -11
  485. package/src/plugins/builtin/realized-vol/settings.ts +1 -1
  486. package/src/plugins/builtin/research/analyst-headless.ts +4 -0
  487. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -0
  488. package/src/plugins/builtin/research/event-model.ts +22 -2
  489. package/src/plugins/builtin/research/headless-schema.ts +1 -0
  490. package/src/plugins/builtin/research/headless.ts +3 -0
  491. package/src/plugins/builtin/research/index.tsx +3 -0
  492. package/src/plugins/builtin/research/relative-valuation-columns.ts +27 -5
  493. package/src/plugins/builtin/research/relative-valuation-headless.ts +2 -0
  494. package/src/plugins/builtin/revenue-breakdown/index.tsx +1 -0
  495. package/src/plugins/builtin/reverse-dcf/headless.ts +9 -6
  496. package/src/plugins/builtin/reverse-dcf/index.tsx +2 -2
  497. package/src/plugins/builtin/reverse-dcf/model.ts +51 -4
  498. package/src/plugins/builtin/reverse-dcf/pane.tsx +23 -10
  499. package/src/plugins/builtin/risk-factors/headless.ts +7 -0
  500. package/src/plugins/builtin/risk-factors/index.tsx +1 -0
  501. package/src/plugins/builtin/scanner/feed.ts +5 -3
  502. package/src/plugins/builtin/scanner/headless.ts +9 -57
  503. package/src/plugins/builtin/scanner/index.ts +1 -2
  504. package/src/plugins/builtin/sec/forms.ts +2 -6
  505. package/src/plugins/builtin/sec/fund-facts-view.tsx +53 -0
  506. package/src/plugins/builtin/sec/fund-facts.ts +328 -0
  507. package/src/plugins/builtin/sec/headless.ts +140 -44
  508. package/src/plugins/builtin/sec/index.tsx +95 -30
  509. package/src/plugins/builtin/sec/model.ts +2 -44
  510. package/src/plugins/builtin/shared/headless-options.ts +10 -0
  511. package/src/plugins/builtin/shared/locked-rows.tsx +3 -1
  512. package/src/plugins/builtin/shared/move-rank.ts +108 -0
  513. package/src/plugins/builtin/shared/report-freshness.ts +25 -9
  514. package/src/plugins/builtin/shared/scanner-capture.ts +95 -0
  515. package/src/plugins/builtin/shared/treasury-cutoffs.ts +15 -0
  516. package/src/plugins/builtin/shared/use-quote-board.ts +1 -0
  517. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -0
  518. package/src/plugins/builtin/shared/volatility-indices.ts +32 -0
  519. package/src/plugins/builtin/short-interest/headless.ts +3 -0
  520. package/src/plugins/builtin/short-interest/watch-model.ts +2 -2
  521. package/src/plugins/builtin/social-mentions/client.ts +8 -1
  522. package/src/plugins/builtin/social-mentions/pane.tsx +8 -6
  523. package/src/plugins/builtin/supply-chain/client.ts +12 -3
  524. package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +9 -5
  525. package/src/plugins/builtin/supply-chain/evidence.ts +3 -1
  526. package/src/plugins/builtin/supply-chain/flow-layout.ts +2 -1
  527. package/src/plugins/builtin/supply-chain/flow.tsx +23 -16
  528. package/src/plugins/builtin/supply-chain/graph-client.ts +8 -5
  529. package/src/plugins/builtin/supply-chain/graph-evidence.ts +1 -1
  530. package/src/plugins/builtin/supply-chain/graph-model.ts +10 -2
  531. package/src/plugins/builtin/supply-chain/graph-pane.tsx +9 -7
  532. package/src/plugins/builtin/supply-chain/graph.tsx +8 -6
  533. package/src/plugins/builtin/supply-chain/headless.ts +69 -27
  534. package/src/plugins/builtin/supply-chain/index.tsx +1 -1
  535. package/src/plugins/builtin/supply-chain/model.ts +76 -15
  536. package/src/plugins/builtin/supply-chain/pane.tsx +29 -15
  537. package/src/plugins/builtin/supply-chain/row-evidence.tsx +5 -4
  538. package/src/plugins/builtin/themes/model.ts +2 -5
  539. package/src/plugins/builtin/thirteenf/api.ts +50 -2
  540. package/src/plugins/builtin/thirteenf/data.ts +250 -11
  541. package/src/plugins/builtin/thirteenf/headless.ts +311 -28
  542. package/src/plugins/builtin/thirteenf/index.tsx +4 -1
  543. package/src/plugins/builtin/thirteenf/model.ts +244 -19
  544. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +133 -33
  545. package/src/plugins/builtin/thirteenf/overlap.ts +1 -1
  546. package/src/plugins/builtin/thirteenf/pane.tsx +24 -5
  547. package/src/plugins/builtin/thirteenf/report-notes.ts +78 -0
  548. package/src/plugins/builtin/thirteenf/types.ts +49 -0
  549. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -8
  550. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +17 -1
  551. package/src/plugins/builtin/ticker-detail/financials/model.ts +2 -42
  552. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
  553. package/src/plugins/builtin/ticker-detail/headless.ts +23 -10
  554. package/src/plugins/builtin/ticker-detail/overview/components.tsx +27 -3
  555. package/src/plugins/builtin/ticker-detail/overview/dividend.ts +60 -0
  556. package/src/plugins/builtin/ticker-detail/overview/model.ts +46 -9
  557. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +3 -0
  558. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +36 -3
  559. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +18 -19
  560. package/src/plugins/builtin/time-sales/client.ts +7 -4
  561. package/src/plugins/builtin/time-sales/headless.ts +71 -22
  562. package/src/plugins/builtin/time-sales/model.ts +12 -0
  563. package/src/plugins/builtin/time-sales/pane.tsx +31 -7
  564. package/src/plugins/builtin/time-sales/window.ts +128 -0
  565. package/src/plugins/builtin/trace-bonds/client.ts +351 -33
  566. package/src/plugins/builtin/trace-bonds/headless.ts +60 -22
  567. package/src/plugins/builtin/trace-bonds/index.tsx +4 -2
  568. package/src/plugins/builtin/trace-bonds/model.ts +27 -25
  569. package/src/plugins/builtin/trace-bonds/pane.tsx +63 -45
  570. package/src/plugins/builtin/treasury-auctions/cache.ts +61 -15
  571. package/src/plugins/builtin/treasury-auctions/client.ts +45 -10
  572. package/src/plugins/builtin/treasury-auctions/headless.ts +35 -13
  573. package/src/plugins/builtin/treasury-auctions/index.tsx +5 -2
  574. package/src/plugins/builtin/treasury-auctions/model.ts +20 -16
  575. package/src/plugins/builtin/treasury-auctions/pane.tsx +29 -12
  576. package/src/plugins/builtin/vol-surface/charts.tsx +2 -1
  577. package/src/plugins/builtin/vol-surface/headless.ts +3 -2
  578. package/src/plugins/builtin/vol-surface/pane.tsx +2 -1
  579. package/src/plugins/builtin/vol-surface/stored.ts +4 -7
  580. package/src/plugins/builtin/volatility/chart-model.ts +16 -15
  581. package/src/plugins/builtin/volatility/charts.tsx +5 -5
  582. package/src/plugins/builtin/volatility/client.ts +73 -2
  583. package/src/plugins/builtin/volatility/evidence.ts +13 -14
  584. package/src/plugins/builtin/volatility/headless.ts +13 -7
  585. package/src/plugins/builtin/volatility/index.tsx +1 -1
  586. package/src/plugins/builtin/volatility/model.ts +104 -38
  587. package/src/plugins/builtin/volatility/pane.tsx +42 -24
  588. package/src/plugins/builtin/volatility/series.ts +22 -13
  589. package/src/plugins/builtin/world-venue-map/geo-series.ts +9 -0
  590. package/src/plugins/builtin/world-venue-map/headless.ts +8 -5
  591. package/src/plugins/builtin/world-venue-map/index.ts +16 -2
  592. package/src/plugins/builtin/world-venue-map/map.tsx +13 -17
  593. package/src/plugins/builtin/yield-curve/curves.ts +29 -5
  594. package/src/plugins/builtin/yield-curve/headless.ts +41 -8
  595. package/src/plugins/builtin/yield-curve/history.ts +5 -5
  596. package/src/plugins/builtin/yield-curve/index.tsx +30 -13
  597. package/src/plugins/compat.ts +3 -1
  598. package/src/plugins/dependencies.ts +8 -2
  599. package/src/plugins/host-link.ts +12 -5
  600. package/src/plugins/installer.ts +16 -7
  601. package/src/plugins/loader.ts +9 -2
  602. package/src/plugins/marketplace/activation.ts +10 -4
  603. package/src/plugins/registry/host-actions.ts +3 -0
  604. package/src/plugins/registry/index.ts +3 -1
  605. package/src/{plugins/builtin/portfolio-list → portfolio}/allocation.ts +74 -12
  606. package/src/{plugins/builtin/portfolio-list → portfolio}/column-values.ts +140 -53
  607. package/src/portfolio/futures-positions.ts +213 -0
  608. package/src/portfolio/futures-terms.ts +75 -0
  609. package/src/portfolio/manual-lots.ts +78 -0
  610. package/src/portfolio/mutations.ts +54 -10
  611. package/src/portfolio/position-metrics.ts +44 -10
  612. package/src/portfolio/valuation-basis.ts +31 -4
  613. package/src/public/__snapshots__/public-api.test.ts.snap +4 -0
  614. package/src/public/time-series.ts +2 -1
  615. package/src/remote/confirm-change.tsx +2 -1
  616. package/src/remote/server.ts +1 -1
  617. package/src/remote/watchlist.ts +9 -1
  618. package/src/renderers/dom/data-table/row.tsx +1 -1
  619. package/src/renderers/dom/desktop/audio-player.tsx +312 -0
  620. package/src/renderers/dom/desktop/control-styles.ts +17 -82
  621. package/src/renderers/dom/desktop/controls.tsx +39 -63
  622. package/src/renderers/dom/desktop/dialog-actions.tsx +13 -53
  623. package/src/renderers/dom/desktop/icons.tsx +8 -0
  624. package/src/renderers/dom/desktop/list-view.tsx +6 -6
  625. package/src/renderers/dom/desktop/popover.tsx +0 -3
  626. package/src/renderers/dom/desktop/query-bar.tsx +6 -4
  627. package/src/renderers/dom/desktop/select-field.tsx +9 -8
  628. package/src/renderers/dom/desktop/stack-header.ts +3 -0
  629. package/src/renderers/dom/dom-ui-host.tsx +3 -2
  630. package/src/renderers/dom/host/chart-surface.tsx +3 -3
  631. package/src/renderers/dom/host/size-observer.ts +33 -4
  632. package/src/renderers/dom/host/surface-3d.tsx +3 -2
  633. package/src/renderers/dom/host/surface-fit.ts +4 -1
  634. package/src/renderers/dom/host/tabs.tsx +22 -132
  635. package/src/renderers/dom/styles.css +358 -160
  636. package/src/renderers/electrobun/bun/core-capabilities.ts +1 -1
  637. package/src/renderers/electrobun/bun/desktop/backend.ts +1 -1
  638. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  639. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +43 -1
  640. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +5 -1
  641. package/src/renderers/electrobun/shared/rpc-codec.ts +7 -0
  642. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +21 -17
  643. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  644. package/src/renderers/electrobun/view/desktop/window/bridge.ts +6 -8
  645. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +18 -0
  646. package/src/series/capability.ts +1 -0
  647. package/src/series/files.ts +9 -3
  648. package/src/series/registry.ts +40 -14
  649. package/src/series/resolve.ts +121 -18
  650. package/src/series/standalone.ts +3 -2
  651. package/src/series/types.ts +55 -1
  652. package/src/series/validate.ts +39 -0
  653. package/src/shares/api.ts +26 -7
  654. package/src/shares/bug-report-dialog.tsx +2 -2
  655. package/src/shares/bug-report-flow.tsx +4 -7
  656. package/src/shares/capture-flow.ts +5 -4
  657. package/src/shares/capture-preview.tsx +2 -2
  658. package/src/shares/chat.ts +7 -27
  659. package/src/shares/flow.tsx +86 -40
  660. package/src/shares/location.ts +1 -3
  661. package/src/shares/pane-image.ts +11 -9
  662. package/src/shares/pane.ts +5 -2
  663. package/src/shares/payload.ts +1 -1
  664. package/src/shares/personal-pane.ts +3 -3
  665. package/src/shares/portable-layout.ts +9 -2
  666. package/src/shares/ready.ts +42 -0
  667. package/src/shares/test-harness.ts +62 -0
  668. package/src/sources/gloomberb-cloud/batched-lookups.ts +73 -0
  669. package/src/sources/gloomberb-cloud/index.ts +64 -12
  670. package/src/sources/gloomberb-cloud/normalizers.ts +31 -2
  671. package/src/sources/gloomberb-cloud/perpetuals.ts +20 -1
  672. package/src/sources/history-omitted.ts +34 -0
  673. package/src/sources/history-result.ts +3 -0
  674. package/src/sources/history-retention.ts +1 -1
  675. package/src/sources/provider-errors.ts +43 -1
  676. package/src/sources/provider-router/batches.ts +3 -2
  677. package/src/sources/provider-router/cache.ts +2 -1
  678. package/src/sources/provider-router/cached-routes.ts +9 -3
  679. package/src/sources/provider-router/history.ts +9 -8
  680. package/src/sources/provider-router/index.ts +18 -3
  681. package/src/sources/provider-router/primary.ts +5 -4
  682. package/src/sources/provider-router/quote-route.ts +3 -1
  683. package/src/sources/provider-router/routing.ts +2 -1
  684. package/src/sources/provider-router/search.ts +47 -13
  685. package/src/sources/provider-router/streaming.ts +7 -4
  686. package/src/sources/sec-edgar.ts +1 -3
  687. package/src/state/app/context/index.tsx +9 -13
  688. package/src/state/app/tab-cursor.ts +149 -12
  689. package/src/state/hooks/quote-streaming.ts +3 -2
  690. package/src/state/hooks/session-snapshot.ts +3 -2
  691. package/src/state/persist-scheduler.ts +12 -4
  692. package/src/sync/controller.ts +22 -1
  693. package/src/sync/core-contributors.ts +58 -15
  694. package/src/team/collections-sync.ts +14 -4
  695. package/src/team/collections.ts +40 -11
  696. package/src/team/store.ts +5 -4
  697. package/src/telemetry/attention-counts.ts +8 -2
  698. package/src/telemetry/crash-reports-dom.ts +3 -0
  699. package/src/telemetry/crash-reports-node.ts +7 -4
  700. package/src/telemetry/crash-reports.ts +11 -11
  701. package/src/telemetry/usage-counts.ts +16 -12
  702. package/src/theme/colors.ts +2 -0
  703. package/src/theme/font-scale.ts +2 -0
  704. package/src/theme/ui-tokens.ts +71 -0
  705. package/src/tickers/batched-lookups.ts +186 -0
  706. package/src/tickers/company-name-listing.ts +97 -0
  707. package/src/tickers/company-name.ts +13 -0
  708. package/src/tickers/instrument-coverage.ts +112 -0
  709. package/src/tickers/listing-fate.ts +49 -9
  710. package/src/tickers/listing-venues.ts +24 -5
  711. package/src/tickers/open-target.ts +13 -5
  712. package/src/tickers/search/index.ts +112 -35
  713. package/src/tickers/search/ranking.ts +49 -1
  714. package/src/tickers/terminal-codes.ts +79 -0
  715. package/src/tickers/ticker-changes.ts +74 -3
  716. package/src/tickers/us-listing.ts +2 -1
  717. package/src/time-series/chart-data.ts +79 -8
  718. package/src/time-series/fundamentals.ts +55 -7
  719. package/src/time-series/ice-credit.ts +17 -2
  720. package/src/time-series/live-bars.ts +21 -8
  721. package/src/time-series/live-quotes.ts +2 -3
  722. package/src/time-series/price-comparison.ts +6 -1
  723. package/src/time-series/resolve.ts +65 -10
  724. package/src/time-series/spec.ts +2 -1
  725. package/src/time-series/studies.ts +3 -3
  726. package/src/time-series/types.ts +22 -0
  727. package/src/time-series/use-chart-resolution.ts +2 -1
  728. package/src/types/config.ts +7 -0
  729. package/src/types/data-provider.ts +20 -0
  730. package/src/types/headless.ts +142 -1
  731. package/src/types/instrument.ts +6 -0
  732. package/src/types/plugin.ts +32 -9
  733. package/src/types/price-history.ts +5 -0
  734. package/src/types/ticker-changes.ts +19 -0
  735. package/src/types/ticker.ts +10 -2
  736. package/src/ui/context-menu.tsx +2 -0
  737. package/src/ui/host.tsx +40 -4
  738. package/src/ui/measured-cells.ts +13 -7
  739. package/src/ui/qr.ts +7 -5
  740. package/src/updater/index.ts +5 -5
  741. package/src/utils/calendar-date.ts +86 -27
  742. package/src/utils/currency-units.ts +17 -0
  743. package/src/utils/debug-log.ts +1 -1
  744. package/src/utils/dividend-cash.ts +48 -0
  745. package/src/utils/dom-screenshot-rasters.ts +3 -1
  746. package/src/utils/dom-screenshot.ts +14 -10
  747. package/src/utils/exchange-rate-snapshot.ts +9 -4
  748. package/src/utils/external-url.ts +1 -0
  749. package/src/utils/fiscal-period.ts +47 -0
  750. package/src/utils/format.ts +5 -2
  751. package/src/utils/forward-dividend.ts +160 -11
  752. package/src/utils/futures-generic.ts +34 -8
  753. package/src/utils/http-proxy-response.ts +2 -1
  754. package/src/utils/local-utc-offset.ts +2 -0
  755. package/src/utils/log-failure.ts +31 -0
  756. package/src/utils/option-expiry.ts +12 -12
  757. package/src/utils/price-history-integrity.ts +56 -0
  758. package/src/utils/price-history.ts +13 -1
  759. package/src/utils/sec-filings.ts +245 -0
  760. package/src/utils/selection-clipboard.ts +5 -2
  761. package/src/utils/throttled-fetch.ts +1 -0
  762. package/src/{plugins/builtin/dividend-yield/trailing-cash.ts → utils/trailing-dividend.ts} +55 -8
  763. package/src/utils/url.ts +1 -0
  764. package/src/utils/utc-time.ts +11 -0
  765. package/src/plugins/builtin/chat/share/targets.ts +0 -27
  766. package/src/renderers/dom/host/top-surface.ts +0 -119
  767. package/src/shares/dialog.tsx +0 -221
  768. package/src/shares/live.ts +0 -19
@@ -0,0 +1,42 @@
1
+ import { requestAuthDialog } from "../account/auth-dialog";
2
+ import { apiClient } from "../api-client";
3
+ import type { AppNotificationRequest } from "../types/plugin";
4
+
5
+ type Notify = (notification: AppNotificationRequest) => unknown;
6
+
7
+ function errorText(error: unknown, fallback: string): string {
8
+ return error instanceof Error && error.message ? error.message : fallback;
9
+ }
10
+
11
+ /**
12
+ * Whether the account can make a share link or post to chat now. When it
13
+ * cannot, one toast says what is missing: a sign-in, which then calls `retry`
14
+ * so Share or the report carries on, or a verified email. `purpose` finishes
15
+ * "Sign in to ...".
16
+ */
17
+ export function readyToShare(notify: Notify, retry: () => void, purpose: string): boolean {
18
+ if (!apiClient.isSignedIn()) {
19
+ notify({
20
+ body: `Sign in to ${purpose}.`,
21
+ type: "info",
22
+ action: { label: "Sign in", onClick: () => { requestAuthDialog({ onSignedIn: retry }); } },
23
+ });
24
+ return false;
25
+ }
26
+ if (!apiClient.isVerified()) {
27
+ notify({
28
+ body: `Verify your email to ${purpose}.`,
29
+ type: "info",
30
+ action: {
31
+ label: "Resend link",
32
+ onClick: () => {
33
+ void apiClient.sendVerification()
34
+ .then(() => notify({ body: "Verification link sent.", type: "success" }))
35
+ .catch((error) => notify({ body: errorText(error, "Couldn't send the email. Try again."), type: "error" }));
36
+ },
37
+ },
38
+ });
39
+ return false;
40
+ }
41
+ return true;
42
+ }
@@ -0,0 +1,62 @@
1
+ import { afterEach } from "bun:test";
2
+ import type { ReactElement } from "react";
3
+ import { apiClient } from "../api-client";
4
+ import type { ConfirmDialogProps } from "../components/ui/confirm-dialog";
5
+ import type { AppNotificationRequest } from "../types/plugin";
6
+ import type { PaneShareHost, PaneShareView } from "./flow";
7
+ import { buildPaneSharePayload } from "./pane";
8
+
9
+ export const SHARE_ID = "a".repeat(32);
10
+ export const SHARE_LINK = `https://term.gloom.sh/api/shares/${SHARE_ID}/open`;
11
+
12
+ /** Puts back the account and share calls a suite's fixtures replace. */
13
+ export function restoreShareApiAfterEach(): void {
14
+ const original = { signedIn: apiClient.isSignedIn, verified: apiClient.isVerified, create: apiClient.createTerminalShare };
15
+ afterEach(() => {
16
+ apiClient.isSignedIn = original.signedIn;
17
+ apiClient.isVerified = original.verified;
18
+ apiClient.createTerminalShare = original.create;
19
+ });
20
+ }
21
+
22
+ /**
23
+ * A pane Share reads, the account and share service it talks to, and what it
24
+ * did: the toasts, what it copied and the links it made. A question shows as
25
+ * the shared confirm, recorded and answered as told; a suite that draws the
26
+ * real dialog replaces `host.promptDialog`.
27
+ */
28
+ export function shareFlowFixture(view: Partial<PaneShareView> = {}, { signedIn = true } = {}) {
29
+ const payload = buildPaneSharePayload({ panes: new Map([["test", { id: "test", name: "Test" }]]), paneTemplates: new Map() } as any,
30
+ { paneId: "test", instanceId: "test:1", settings: { selectedTab: "options", expiry: "2027-01-15" } });
31
+ const toasts: AppNotificationRequest[] = [];
32
+ const copied: string[] = [];
33
+ const created: unknown[] = [];
34
+ const questions: ConfirmDialogProps[] = [];
35
+ let reads = 0;
36
+ let answer: boolean | undefined = true;
37
+ let createShare = async () => ({ id: SHARE_ID, expiresAt: "2027-01-01T00:00:00Z" });
38
+ let copyText = async (text: string) => { copied.push(text); };
39
+ apiClient.isSignedIn = () => signedIn;
40
+ apiClient.isVerified = () => signedIn;
41
+ apiClient.createTerminalShare = async (value) => { created.push(value); return createShare(); };
42
+ const host: PaneShareHost = {
43
+ notify: (toast) => { toasts.push(toast); },
44
+ promptDialog: async <T,>(options: Parameters<PaneShareHost["promptDialog"]>[0]) => {
45
+ const content = options.content as (context: { resolve(value: T): void; dismiss(): void }) => ReactElement<ConfirmDialogProps>;
46
+ let result: T | undefined;
47
+ questions.push(content({ resolve: (value) => { result = value; }, dismiss: () => {} }).props);
48
+ if (answer !== undefined) result = answer as T;
49
+ return result;
50
+ },
51
+ copyText: (text) => copyText(text),
52
+ readShareView: async () => { reads += 1; return { sharePayload: payload, personalData: null, ...view }; },
53
+ };
54
+ return {
55
+ host, payload, toasts, copied, created, questions,
56
+ get reads() { return reads; },
57
+ set answer(value: boolean | undefined) { answer = value; },
58
+ set createShare(value: () => Promise<{ id: string; expiresAt: string }>) { createShare = value; },
59
+ /** Replaces the clipboard, as a browser that refuses a write does. */
60
+ set copyText(value: (text: string) => Promise<void>) { copyText = value; },
61
+ };
62
+ }
@@ -0,0 +1,73 @@
1
+ import { apiClient } from "../../api-client";
2
+ import { ApiRequestError } from "../../api-client/errors";
3
+ import type { InstrumentSearchResult } from "../../types/instrument";
4
+ import type { TickerChanges } from "../../types/ticker-changes";
5
+ import { logFailure } from "../../utils/log-failure";
6
+ import { chunked, mapWithConcurrency } from "../../utils/concurrency";
7
+
8
+ /** Symbols asked for in one batched ticker change request; the service reads up to 500. */
9
+ const TICKER_CHANGES_BATCH_SIZE = 500;
10
+ /** Queries in one batched search; the service runs up to 100, as many as a signed-out quote batch reads. */
11
+ const SEARCH_BATCH_SIZE = 100;
12
+ /** Batch requests of one lookup in flight at once, so a long list does not ask the service for everything at the same moment. */
13
+ const BATCHES_IN_FLIGHT = 3;
14
+
15
+ type BatchRoute = "ticker-changes" | "search";
16
+
17
+ /** Batch routes the service answered 404 for: an older service, asked one symbol at a time from then on. */
18
+ const unsupported = new Set<BatchRoute>();
19
+
20
+ /** A service without the batch route answers 404 (or 405, the path served for another method). */
21
+ const isMissingRoute = (error: unknown) => error instanceof ApiRequestError && (error.status === 404 || error.status === 405);
22
+
23
+ /**
24
+ * The answers to `keys`, a request per `size` of them, a few in flight at a
25
+ * time. A request that fails leaves its keys out, to be asked one at a time; a
26
+ * service without the route answers null, and is not asked for it again.
27
+ */
28
+ async function inBatches<T>(
29
+ route: BatchRoute,
30
+ keys: readonly string[],
31
+ size: number,
32
+ load: (chunk: string[]) => Promise<Map<string, T>>,
33
+ ): Promise<Map<string, T> | null> {
34
+ if (unsupported.has(route)) return null;
35
+ const answers = new Map<string, T>();
36
+ await mapWithConcurrency(chunked([...new Set(keys)], size), BATCHES_IN_FLIGHT, async (chunk) => {
37
+ // The first chunk an older service refused says the rest would be refused too.
38
+ if (unsupported.has(route)) return;
39
+ try {
40
+ for (const [key, value] of await load(chunk)) answers.set(key, value);
41
+ } catch (error) {
42
+ if (isMissingRoute(error)) unsupported.add(route);
43
+ else logFailure("sources.gloomberb-cloud.batched-lookups", `Batched ${route} request failed`)(error);
44
+ }
45
+ });
46
+ return unsupported.has(route) ? null : answers;
47
+ }
48
+
49
+ /** Each symbol's ticker changes, by symbol as asked (null: none on record); null from a service that cannot batch them. */
50
+ export function cloudTickerChangesBatch(
51
+ symbols: readonly string[],
52
+ options: { signal?: AbortSignal } = {},
53
+ ): Promise<Map<string, TickerChanges | null> | null> {
54
+ return inBatches("ticker-changes", symbols, TICKER_CHANGES_BATCH_SIZE, (chunk) => apiClient.getCloudTickerChangesBatch(chunk, options));
55
+ }
56
+
57
+ /**
58
+ * Each query's listings as the service's search answers it, by query as asked;
59
+ * null from a service that cannot batch searches. Perpetual markets, which
60
+ * `search` asks the board for beside the listings, are not included: the
61
+ * symbols a list is looked up by never name one.
62
+ */
63
+ export function cloudSearchBatch(
64
+ queries: readonly string[],
65
+ options: { limit?: number; signal?: AbortSignal } = {},
66
+ ): Promise<Map<string, InstrumentSearchResult[]> | null> {
67
+ return inBatches("search", queries, SEARCH_BATCH_SIZE, (chunk) => apiClient.searchInstrumentsBatch(chunk, options.limit ?? 10, options));
68
+ }
69
+
70
+ /** Forgets which batch routes the service lacked. */
71
+ export function resetCloudBatchSupport(): void {
72
+ unsupported.clear();
73
+ }
@@ -1,8 +1,10 @@
1
+ import { logFailure } from "../../utils/log-failure";
1
2
  import { exchangeRateMetadata } from "../../utils/exchange-rate-snapshot";
2
3
  import type { ExchangeRateSnapshot } from "../../types/exchange-rate";
3
4
  import type { TimeRange } from "../../time-series/range";
4
5
  import { subtractTimeRange } from "../../time-series/date-window";
5
6
  import { HistoryCoverageError, parseHistoryCoverageNote, parseHistoryCoverageStart } from "../history-coverage";
7
+ import { cloudNonPositiveCloseDates } from "../history-omitted";
6
8
  import {
7
9
  normalizeChartResolutionSupport,
8
10
  type ChartResolutionSupport,
@@ -39,9 +41,11 @@ import {
39
41
  import type { NewsArticle, NewsQuery } from "../../types/news-source";
40
42
  import { normalizeNewsFeed } from "../../news/news-model";
41
43
  import { resolveCurrencyUnit } from "../../utils/currency-units";
44
+ import { isRoundTheClockCoin } from "../../utils/crypto-pair";
42
45
  import { canonicalExchange, canonicalTickerKey, parsePublicTickerKey } from "../../utils/exchanges";
43
46
  import { normalizePriceHistory, priceHistoryIntervalMs, reachesLatestSettledSession } from "../../utils/price-history";
44
- import { createProviderMiss, providerMissReason } from "../provider-errors";
47
+ import { createProviderMiss, ProviderBusyError, providerMissReason } from "../provider-errors";
48
+ import { isRetryableApiError } from "../../api-client/errors";
45
49
  import { assertSecRegistrantMatches } from "../sec-registrant";
46
50
  import { nonUsSecListingVenue } from "../../utils/sec";
47
51
  import { publicListingTarget } from "../listing-target";
@@ -57,6 +61,7 @@ import {
57
61
  import {
58
62
  GLOOMBERB_CLOUD_PROVIDER_ID,
59
63
  formatCloudDateTime,
64
+ formatCloudHistoryEnd,
60
65
  isEmptyCloudStatus,
61
66
  mapBatchError,
62
67
  NOT_FOUND_REASON,
@@ -73,11 +78,17 @@ import {
73
78
  namesPerpetualMarket,
74
79
  perpetualTarget,
75
80
  searchPerpetualMarkets,
81
+ boardSearchesFrom,
76
82
  } from "./perpetuals";
77
83
  import { predictionMarketListing } from "../../tickers/prediction-listing";
78
84
  import { settleWithin } from "../../utils/async-deadline";
85
+ import { cloudSearchBatch, cloudTickerChangesBatch } from "./batched-lookups";
79
86
 
80
87
  const providerId = GLOOMBERB_CLOUD_PROVIDER_ID;
88
+ /** How long a search that is not typed waits for perpetual markets. */
89
+ const PERPETUAL_SEARCH_WAIT_MS = 2_000;
90
+ /** How long a batched search waits for the board it answers every query's perpetual markets from. */
91
+ const PERPETUAL_BOARD_WAIT_MS = 5_000;
81
92
  const CLOUD_RESOLUTION_SUPPORT = normalizeChartResolutionSupport([
82
93
  { resolution: "1m", maxRange: "1W" },
83
94
  { resolution: "5m", maxRange: "1M" },
@@ -109,6 +120,7 @@ function mapCloudSecFiling(item: {
109
120
  companyName?: string;
110
121
  filingUrl: string;
111
122
  primaryDocumentUrl?: string;
123
+ fileNumber?: string;
112
124
  }): SecFilingItem {
113
125
  return {
114
126
  accessionNumber: item.accessionNumber,
@@ -123,6 +135,8 @@ function mapCloudSecFiling(item: {
123
135
  companyName: item.companyName,
124
136
  filingUrl: item.filingUrl,
125
137
  primaryDocumentUrl: item.primaryDocumentUrl,
138
+ // Absent from an older server: no file number is not the same as an empty one.
139
+ ...(typeof item.fileNumber === "string" ? { fileNumber: item.fileNumber } : {}),
126
140
  };
127
141
  }
128
142
 
@@ -162,8 +176,8 @@ async function loadCloudHistory(
162
176
  if (response.status === "success" || response.status === "partial" || response.status === "empty") {
163
177
  return { response, extendedHours: response.session === "extended" };
164
178
  }
165
- } catch {
166
- // The regular session below.
179
+ } catch (error) {
180
+ logFailure("sources.gloomberb-cloud.index", "Loading extended-hours history failed")(error);
167
181
  }
168
182
  return { response: await load(), extendedHours: false };
169
183
  }
@@ -204,11 +218,12 @@ function mapCloudPriceHistory(
204
218
  const { divisor } = resolveCurrencyUnit(
205
219
  response.currency ?? response.providerMeta?.currency,
206
220
  );
221
+ const coin = isRoundTheClockCoin(ticker, exchange);
207
222
  const points = normalizePriceHistory(
208
223
  unwrapRequiredCloudResponse(
209
224
  response,
210
225
  `Cloud chart data is unavailable for ${ticker}`,
211
- ).map((point) => mapPricePoint(point, divisor, exchange)),
226
+ ).map((point) => mapPricePoint(point, divisor, exchange, coin)),
212
227
  );
213
228
  if (stale && !reachesLatestSettledSession(points, Date.now(), { symbol: ticker, exchange, intervalMs: priceHistoryIntervalMs(interval) })) {
214
229
  throw createProviderMiss(`Cloud chart data is stale for ${ticker}`);
@@ -234,10 +249,13 @@ function mapCloudPriceHistory(
234
249
  if (declared && (!session || !matchingSource || !matchingIdentity || !matchingInterval)) {
235
250
  throw createProviderMiss(`Cloud chart session metadata does not match the requested history for ${ticker}`);
236
251
  }
252
+ // Sessions of daily bars the answer left out for a close at or below zero.
253
+ const nonPositiveCloseDates = resolution === "1d" ? cloudNonPositiveCloseDates(response.omittedSessions) : undefined;
237
254
  return {
238
255
  points,
239
256
  resolution,
240
257
  ...(session && matchingSource && matchingIdentity ? { session } : {}),
258
+ ...(nonPositiveCloseDates ? { nonPositiveCloseDates } : {}),
241
259
  ...(coverageStart ? { coverageStart } : {}),
242
260
  ...(coverageNote ? { coverageNote } : {}),
243
261
  ...(extendedHours ? { extendedHours: true } : {}),
@@ -318,7 +336,10 @@ function matchCloudBatchItems<T extends { symbol: string; exchange?: string }, I
318
336
  const requested = new Set(pending.keys());
319
337
  const responses = items.flatMap((item) => {
320
338
  try { return [{ item, key: quoteTargetKey(item.symbol, item.exchange) }]; }
321
- catch { return []; }
339
+ catch (error) {
340
+ logFailure("sources.gloomberb-cloud.index", "Matching a cloud batch response failed")(error);
341
+ return [];
342
+ }
322
343
  });
323
344
  for (const { item, key: responseKey } of responses) {
324
345
  const key = cloudResponseTargetKey(responseKey, requested);
@@ -565,12 +586,15 @@ export class GloomberbCloudProvider implements AssetDataProvider {
565
586
  async search(query: string, context?: SearchRequestContext): Promise<InstrumentSearchResult[]> {
566
587
  // Perpetual markets are listings too, asked for beside the others. They are optional: a failed lookup leaves the
567
588
  // other results, and a keystroke waits for them only briefly once the listings are in, unless the query names one.
568
- const pending = searchPerpetualMarkets(query).catch(() => [] as InstrumentSearchResult[]);
589
+ const pending = searchPerpetualMarkets(query).catch(logFailure("sources.gloomberb-cloud.index", "Searching perpetual markets failed", [] as InstrumentSearchResult[]));
569
590
  const listings = await withCloudFallback(
570
591
  () => apiClient.searchInstruments(query, context?.limit ?? 10),
571
592
  "Cloud search is unavailable",
572
- );
573
- const perpetuals = await settleWithin(pending, namesPerpetualMarket(query) ? 2_000 : context?.interactive ? 250 : 2_000) ?? [];
593
+ ).catch((error: unknown) => {
594
+ // Rate limited or overloaded: the search was not run, which is not a search that found nothing.
595
+ throw isRetryableApiError(error) ? new ProviderBusyError("Cloud search is busy", { cause: error }) : error;
596
+ });
597
+ const perpetuals = await settleWithin(pending, namesPerpetualMarket(query) || !context?.interactive ? PERPETUAL_SEARCH_WAIT_MS : 250) ?? [];
574
598
  // Ahead of the listings, so a contract found by its plain name ("brent" for BZUSDT:BINANCE) is not cut
575
599
  // behind every warrant that names it; an exact symbol still leads.
576
600
  return perpetuals.length ? [...perpetuals, ...listings] : listings;
@@ -599,7 +623,8 @@ export class GloomberbCloudProvider implements AssetDataProvider {
599
623
  const quote = await this.getQuote(ticker, venue);
600
624
  const quoteVenue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
601
625
  return quoteVenue === venue ? quote.name?.trim() || null : null;
602
- } catch {
626
+ } catch (error) {
627
+ logFailure("sources.gloomberb-cloud.index", "Loading the filing listing name failed")(error);
603
628
  return null;
604
629
  }
605
630
  }
@@ -664,6 +689,30 @@ export class GloomberbCloudProvider implements AssetDataProvider {
664
689
  return response.status === "success" || response.status === "partial" ? response.data : null;
665
690
  }
666
691
 
692
+ getTickerChangesBatch(symbols: string[], options: { signal?: AbortSignal } = {}): Promise<Map<string, TickerChanges | null> | null> {
693
+ return cloudTickerChangesBatch(symbols, options);
694
+ }
695
+
696
+ async searchBatch(queries: string[], options: { limit?: number; signal?: AbortSignal } = {}): Promise<Map<string, InstrumentSearchResult[]> | null> {
697
+ const answers = await cloudSearchBatch(queries, options);
698
+ if (!answers) return null;
699
+ // Perpetual markets lead each answer, as `search` gives them, so a symbol in a list resolves as it does alone:
700
+ // one read of the board answers every query's search of it. A query that found no listing is searched again
701
+ // on its own, which finds them then; a board that does not answer leaves them out, as a search does.
702
+ const found = queries.filter((query) => answers.get(query)?.length);
703
+ const board = found.length
704
+ ? await settleWithin(apiClient.getCloudPerpsBoard().catch(logFailure("sources.gloomberb-cloud.index", "Reading the perpetual board failed", null)), PERPETUAL_BOARD_WAIT_MS)
705
+ : null;
706
+ if (!board) return answers;
707
+ const client = boardSearchesFrom(board);
708
+ for (const query of found) {
709
+ const perpetuals = await searchPerpetualMarkets(query, client)
710
+ .catch(logFailure("sources.gloomberb-cloud.index", "Searching perpetual markets failed", [] as InstrumentSearchResult[]));
711
+ if (perpetuals.length) answers.set(query, [...perpetuals, ...answers.get(query)!]);
712
+ }
713
+ return answers;
714
+ }
715
+
667
716
  async getArticleSummary(url: string): Promise<string | null> {
668
717
  return (await apiClient.getCloudArticleSummary(url)).summary;
669
718
  }
@@ -736,7 +785,7 @@ export class GloomberbCloudProvider implements AssetDataProvider {
736
785
  () => loadCloudHistory(interval, (session) => apiClient.getCloudHistory(target.symbol, exchange, {
737
786
  interval,
738
787
  startDate: formatCloudDateTime(startDate, includeTime, exchange),
739
- endDate: formatCloudDateTime(endDate, includeTime, exchange),
788
+ endDate: formatCloudHistoryEnd(endDate, includeTime, exchange),
740
789
  ...(session ? { session } : {}),
741
790
  }), _context),
742
791
  `Cloud chart data is unavailable for ${ticker}`,
@@ -800,7 +849,7 @@ export class GloomberbCloudProvider implements AssetDataProvider {
800
849
  const { valid } = cloudBatchTargets(splitPerpetualTargets(targets).others);
801
850
  if (!valid.length) return () => {};
802
851
  // No-op without a session credential; covers browser cookie sessions too.
803
- void apiClient.ensureVerifiedSession().catch(() => {});
852
+ void apiClient.ensureVerifiedSession().catch(logFailure("sources.gloomberb-cloud.index", "Verifying the quote streaming session failed"));
804
853
  const targetMap = new Map<string, QuoteSubscriptionTarget[]>();
805
854
  for (const { target } of valid) {
806
855
  const key = quoteTargetKey(target.symbol, target.exchange);
@@ -821,7 +870,10 @@ export class GloomberbCloudProvider implements AssetDataProvider {
821
870
  (target, quote) => {
822
871
  let key: string;
823
872
  try { key = quoteTargetKey(target.symbol, target.exchange); }
824
- catch { return; }
873
+ catch (error) {
874
+ logFailure("sources.gloomberb-cloud.index", "Matching a streamed quote failed")(error);
875
+ return;
876
+ }
825
877
  const matchedKey = cloudResponseTargetKey(key, requested);
826
878
  const matches = matchedKey ? targetMap.get(matchedKey) ?? [] : [];
827
879
  const mappedQuote = mapQuote(quote);
@@ -20,6 +20,8 @@ import {
20
20
  } from "../../api-client";
21
21
  import { normalizePriceValueByDivisor, resolveCurrencyUnit } from "../../utils/currency-units";
22
22
  import { resolveExchangeTimeZone } from "../../utils/exchanges";
23
+ import { isRoundTheClockCoin } from "../../utils/crypto-pair";
24
+ import { latestSettledSessionDate } from "../../utils/price-history";
23
25
  import { zonedDateTimeParts, zonedWallClockToUtcMs } from "../../utils/zoned-date-time";
24
26
  import { createProviderMiss } from "../provider-errors";
25
27
  import { reconcileQuoteDayRange } from "../../market-data/quotes/day-range";
@@ -216,10 +218,17 @@ function parseCloudPricePointDate(
216
218
  : new Date(value);
217
219
  }
218
220
 
221
+ /**
222
+ * One served bar. `coin`: the bars are a coin's, which trades every hour of every day. Its intraday volume is the
223
+ * value traded in the bar on major spot venues, and a bar no venue reported has none rather than 0, so a 0 (from an
224
+ * older copy, or a daily bar) is volume not reported, not a bar nothing traded in. It is read as missing, so no table
225
+ * prints 0 and no sum or average counts it. Any other listing keeps a 0, which a thin stock can truly trade.
226
+ */
219
227
  export function mapPricePoint(
220
228
  point: CloudPricePointPayload,
221
229
  divisor = 1,
222
230
  exchange = "",
231
+ coin = false,
223
232
  ): PricePoint {
224
233
  return {
225
234
  date: parseCloudPricePointDate(point.date, exchange),
@@ -227,7 +236,7 @@ export function mapPricePoint(
227
236
  high: normalizePriceValueByDivisor(point.high, divisor),
228
237
  low: normalizePriceValueByDivisor(point.low, divisor),
229
238
  close: normalizePriceValueByDivisor(point.close, divisor) ?? point.close,
230
- volume: point.volume,
239
+ volume: coin && point.volume === 0 ? undefined : point.volume,
231
240
  ...(point.partial === true ? { partial: true } : {}),
232
241
  ...(point.filled === true ? { filled: true } : {}),
233
242
  ...(point.unsettled === true ? { unsettled: true } : {}),
@@ -286,6 +295,7 @@ export function mapCloudFinancials(
286
295
  const quote = rawQuote ? mapQuote(rawQuote, providerMeta) : undefined;
287
296
  const divisor = rawQuote ? resolveCurrencyUnit(rawQuote.currency).divisor : 1;
288
297
  const exchange = rawQuote?.listingExchangeName ?? rawQuote?.exchangeName ?? "";
298
+ const coin = isRoundTheClockCoin(rawQuote?.symbol, exchange);
289
299
  return {
290
300
  quote,
291
301
  quoteMetadata: financials.quoteMetadata,
@@ -300,7 +310,7 @@ export function mapCloudFinancials(
300
310
  priceHistory: (financials.priceHistory ?? []).map((point) =>
301
311
  point.date instanceof Date
302
312
  ? point
303
- : mapPricePoint(point as unknown as CloudPricePointPayload, divisor, exchange),
313
+ : mapPricePoint(point as unknown as CloudPricePointPayload, divisor, exchange, coin),
304
314
  ),
305
315
  ...(financials.epsEstimates ? { epsEstimates: financials.epsEstimates } : {}),
306
316
  };
@@ -393,6 +403,25 @@ export function formatCloudDateTime(
393
403
  return `${year}-${month}-${day}`;
394
404
  }
395
405
 
406
+ const DAY_MS = 24 * 60 * 60_000;
407
+
408
+ /**
409
+ * The end of a window of bars that ends now. Gloom Cloud reads a date-only end
410
+ * as exclusive, so a daily or longer window ending on today's date holds no
411
+ * bar of today. Once today's regular session has settled its bar is final and
412
+ * the window ends the day after, so the bar is served as the range requests
413
+ * serve it. While the session trades, or before it opens, today's date stands:
414
+ * the day's bar is the live quote's then. Intraday ends carry their time.
415
+ */
416
+ export function formatCloudHistoryEnd(end: Date, includeTime: boolean, exchange = ""): string {
417
+ const formatted = formatCloudDateTime(end, includeTime, exchange);
418
+ if (includeTime) return formatted;
419
+ const settled = latestSettledSessionDate(exchange, end.getTime());
420
+ return settled && settled >= formatted
421
+ ? new Date(Date.parse(`${settled}T00:00:00Z`) + DAY_MS).toISOString().slice(0, 10)
422
+ : formatted;
423
+ }
424
+
396
425
  const HISTORY_OUTPUT_SIZE: Record<TimeRange, number> = {
397
426
  "1D": 24 * 12,
398
427
  "1W": 7 * 24,
@@ -1,5 +1,5 @@
1
1
  import { apiClient } from "../../api-client";
2
- import type { PerpBoardRow, PerpMarketPayload } from "../../api-client/perps";
2
+ import type { PerpBoardPayload, PerpBoardRow, PerpMarketPayload } from "../../api-client/perps";
3
3
  import type { InstrumentSearchResult } from "../../types/instrument";
4
4
  import type { PricePoint, Quote } from "../../types/financials";
5
5
  import type { PriceHistoryResult } from "../../types/price-history";
@@ -140,6 +140,25 @@ function searchResult(row: PerpBoardRow): InstrumentSearchResult | null {
140
140
  };
141
141
  }
142
142
 
143
+ /**
144
+ * A client whose board searches are answered from one copy of the board, as
145
+ * the board answers them: its rows whose symbol, base asset or name holds the
146
+ * words, in its order. The board read without a search is the one a search
147
+ * reads (all of it with Pro, its preview without), so a list's searches cost
148
+ * one board request and find what each would find alone.
149
+ */
150
+ export function boardSearchesFrom(board: PerpBoardPayload, client: PerpsClient = apiClient): PerpsClient {
151
+ const rows = Array.isArray(board?.rows) ? board.rows : [];
152
+ return {
153
+ getCloudPerpsMarket: client.getCloudPerpsMarket,
154
+ getCloudPerpsHistory: client.getCloudPerpsHistory,
155
+ getCloudPerpsBoard: async ({ search } = {}) => {
156
+ const wanted = search?.trim().toUpperCase();
157
+ return { ...board, rows: wanted ? rows.filter((row) => `${row.symbol} ${row.baseAsset} ${row.displayName}`.toUpperCase().includes(wanted)) : rows };
158
+ },
159
+ };
160
+ }
161
+
143
162
  /** The one market a query names by its market id or listing key; null for a search. */
144
163
  const namedListing = (query: string) => typedPerpetualListing(query) ?? (query.includes(":") ? perpetualListing(query) : null);
145
164
 
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Daily sessions a source left out of a price history because its close was
3
+ * at or below zero (CL=F settled at -37.63 on 2020-04-20): no price can stand
4
+ * for them, so they are not bars, but a reader of the series is told they
5
+ * were sessions.
6
+ */
7
+
8
+ const SESSION_DATE = /^\d{4}-\d{2}-\d{2}$/;
9
+ // More than any daily history holds; a longer list is not one a source wrote.
10
+ const MAX_DATES = 10_000;
11
+
12
+ function isSessionDate(value: unknown): value is string {
13
+ return typeof value === "string" && SESSION_DATE.test(value) && Number.isFinite(Date.parse(`${value}T00:00:00Z`));
14
+ }
15
+
16
+ /** A stored or passed-on list: unique session dates, ascending; undefined for anything else or nothing. */
17
+ export function readNonPositiveCloseDates(value: unknown): string[] | undefined {
18
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_DATES || !value.every(isSessionDate)) return undefined;
19
+ return [...new Set(value)].sort();
20
+ }
21
+
22
+ /**
23
+ * The sessions a Gloom Cloud answer lists as left out for a close at or below
24
+ * zero (`omittedSessions: [{ date, reason: "non-positive-close" }]`). Entries
25
+ * for a reason this client does not know, or that do not read, are ignored.
26
+ */
27
+ export function cloudNonPositiveCloseDates(value: unknown): string[] | undefined {
28
+ if (!Array.isArray(value) || value.length > MAX_DATES) return undefined;
29
+ return readNonPositiveCloseDates(value.flatMap((entry: unknown) => {
30
+ if (!entry || typeof entry !== "object") return [];
31
+ const { date, reason } = entry as { date?: unknown; reason?: unknown };
32
+ return reason === "non-positive-close" && isSessionDate(date) ? [date] : [];
33
+ }));
34
+ }
@@ -5,6 +5,7 @@ import type { PriceHistoryResult } from "../types/price-history";
5
5
  import { parseHistorySession } from "../market-data/history-session";
6
6
  import { canonicalHistoryInterval } from "./history-retention";
7
7
  import { historyCoverageNoteText, isHistoryCoverageDate } from "./history-coverage";
8
+ import { readNonPositiveCloseDates } from "./history-omitted";
8
9
 
9
10
  export type HistoryResultRequest =
10
11
  | { kind: "range"; range: TimeRange }
@@ -36,8 +37,10 @@ export function normalizeHistoryResult(
36
37
  interval: requestedInterval ?? result.resolution ?? undefined });
37
38
  // A contradictory declaration cannot be laundered into unknown legacy data.
38
39
  if (result.session != null && (!session || result.resolution === null)) return null;
40
+ const nonPositiveCloseDates = readNonPositiveCloseDates(result.nonPositiveCloseDates);
39
41
  return { points: result.points, resolution: result.resolution,
40
42
  ...(session ? { session } : {}),
43
+ ...(nonPositiveCloseDates ? { nonPositiveCloseDates } : {}),
41
44
  ...(typeof result.sourceKey === "string" && /^(provider|broker):[^\r\n\0]+$/.test(result.sourceKey) ? { sourceKey: result.sourceKey } : {}),
42
45
  ...(isHistoryCoverageDate(result.coverageStart) ? { coverageStart: result.coverageStart } : {}),
43
46
  ...(isHistoryCoverageDate(result.coverageStart) && historyCoverageNoteText(result.coverageNote)
@@ -173,5 +173,5 @@ export function parseHistoryRetentionError(value: unknown): HistoryRetentionErro
173
173
  return new HistoryRetentionError(retention, {
174
174
  candidates: value.candidates as HistoryRecoveryCandidate[], outcomes: value.outcomes as HistorySourceOutcome[],
175
175
  });
176
- } catch { return null; }
176
+ } catch { /* Intentional ignore: Invalid recovery metadata is rejected at the desktop JSON boundary. */ return null; }
177
177
  }
@@ -47,6 +47,27 @@ function isProviderMiss(error: unknown): error is ProviderMissError {
47
47
  || (!!error && typeof error === "object" && PROVIDER_MISS_BRAND in error);
48
48
  }
49
49
 
50
+ /**
51
+ * A source that could not answer just now (rate limited, overloaded, timed
52
+ * out) and may if asked again shortly. Unlike a miss, it says nothing about
53
+ * whether what was asked for exists.
54
+ */
55
+ export class ProviderBusyError extends Error {
56
+ constructor(message = "The data service is busy", options?: { cause?: unknown }) {
57
+ super(message, options);
58
+ this.name = "ProviderBusyError";
59
+ }
60
+ }
61
+
62
+ export function isProviderBusy(error: unknown): error is ProviderBusyError {
63
+ return error instanceof ProviderBusyError;
64
+ }
65
+
66
+ /** What a search its sources could not answer says, rather than that nothing matched. */
67
+ export const SEARCH_BUSY_MESSAGE = "Search is busy, try again in a moment.";
68
+ /** The code a structured answer (CLI `--json`, remote control) gives that failure. */
69
+ export const SEARCH_BUSY_CODE = "search_busy";
70
+
50
71
  /** The reason a provider gave for a miss, or undefined when it gave none. */
51
72
  export function providerMissReason(error: unknown): string | undefined {
52
73
  return isProviderMiss(error) ? error.reason : undefined;
@@ -57,12 +78,15 @@ export interface ProviderMissNote {
57
78
  reason?: string;
58
79
  /** True while every provider that failed said it found no listing; one that failed another way (a timeout) clears it. */
59
80
  notFound?: boolean;
81
+ /** The last provider failure that was not a miss (a refused session, a rate limit, a timeout). */
82
+ failure?: unknown;
60
83
  }
61
84
 
62
85
  export function noteProviderMiss(note: ProviderMissNote | undefined, error: unknown): void {
63
86
  if (!note) return;
64
87
  const reason = providerMissReason(error);
65
88
  if (reason) note.reason = reason;
89
+ if (!isProviderMiss(error)) note.failure = error;
66
90
  note.notFound = note.notFound !== false && isProviderMiss(error) && error.notFound === true;
67
91
  }
68
92
 
@@ -93,6 +117,13 @@ export function isNoProviderMessage(message: string): boolean {
93
117
  return /\bprovider available for\b/i.test(message) || notATickerSymbol(message) !== null;
94
118
  }
95
119
 
120
+ /** The router's generic answer that no provider served a request, holding the failure behind it when a provider failed rather than missed. */
121
+ class NoProviderError extends Error {
122
+ constructor(message: string, readonly failure: unknown) {
123
+ super(message);
124
+ }
125
+ }
126
+
96
127
  /**
97
128
  * The router's final error when no provider answered. A reason a provider gave
98
129
  * replaces the generic wording, and so does the service's answer that no
@@ -102,7 +133,18 @@ export function isNoProviderMessage(message: string): boolean {
102
133
  export function noProviderError(message: string, note: ProviderMissNote | undefined, symbol?: string): Error {
103
134
  if (note?.reason) return createProviderMiss(note.reason, note.reason);
104
135
  if (note?.notFound === true && symbol) return createProviderMiss(notATickerMessage(symbol), undefined, { notFound: true });
105
- return new Error(message);
136
+ return new NoProviderError(message, note?.failure);
137
+ }
138
+
139
+ /**
140
+ * Whether a failed read means no source has what was asked for: a source's
141
+ * miss, or the router's answer that none served it when none failed another
142
+ * way. A refused session, a rate limit or a timeout is not, as asked again it may answer.
143
+ */
144
+ export function isNoSourceAnswer(error: unknown): boolean {
145
+ if (isProviderMiss(error)) return true;
146
+ if (error instanceof NoProviderError) return error.failure === undefined;
147
+ return error instanceof Error && !(error instanceof ProviderBusyError) && isNoProviderMessage(error.message);
106
148
  }
107
149
 
108
150
  export function shouldLogProviderError(error: unknown): boolean {
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../utils/log-failure";
1
2
  import type {
2
3
  CachedFinancialsTarget,
3
4
  MarketDataRequestContext,
@@ -96,7 +97,7 @@ export class ProviderRouterBatchRoutes {
96
97
  providerIndexes.set(key, bucket);
97
98
  }
98
99
  const uniqueTargets = [...providerIndexes.values()].map((bucket) => bucket[0]!.target);
99
- const batchResults = await batchProvider.getQuotesBatch!(uniqueTargets, providerOptions).catch(() => []);
100
+ const batchResults = await batchProvider.getQuotesBatch!(uniqueTargets, providerOptions).catch(logFailure("sources.provider-router.batches", "Loading batch quotes failed", []));
100
101
  for (const item of batchResults) {
101
102
  if (!isProviderQuoteUsableForCurrentSession(item.quote, item.target.exchange, item.target.symbol, { recentAnswer: true })) continue;
102
103
  const key = this.quoteBatchKey(item.target);
@@ -163,7 +164,7 @@ export class ProviderRouterBatchRoutes {
163
164
  providerIndexes.set(key, bucket);
164
165
  }
165
166
  const uniqueTargets = [...providerIndexes.values()].map((bucket) => bucket[0]!.target);
166
- const batchResults = await batchProvider.getTickerFinancialsBatch!(uniqueTargets, options).catch(() => []);
167
+ const batchResults = await batchProvider.getTickerFinancialsBatch!(uniqueTargets, options).catch(logFailure("sources.provider-router.batches", "Loading batch financials failed", []));
167
168
  for (const item of batchResults) {
168
169
  if (!item.financials) continue;
169
170
  if (!item.target.instrument && !providerFinancialsMatchTarget(item.financials, item.target.symbol, item.target.exchange)) continue;