gloomberb 0.18.1 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/commodity-balances.ts +6 -0
  3. package/src/api-client/company-kpis.ts +9 -0
  4. package/src/api-client/crypto-markets.ts +9 -0
  5. package/src/api-client/data.ts +6 -5
  6. package/src/api-client/equity-screener.ts +2 -0
  7. package/src/api-client/types.ts +5 -0
  8. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  9. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  10. package/src/app/runtime/plugin-bindings.ts +6 -2
  11. package/src/brokers/logos.ts +66 -0
  12. package/src/brokers/profile-form.ts +3 -0
  13. package/src/brokers/remote-broker-adapter.ts +13 -3
  14. package/src/capabilities/index.ts +1 -1
  15. package/src/capabilities/service-capabilities.ts +7 -1
  16. package/src/cli/base-converter.ts +60 -18
  17. package/src/cli/commands/market.ts +281 -154
  18. package/src/cli/commands/news-search.ts +35 -8
  19. package/src/cli/commands/system.ts +35 -1
  20. package/src/cli/commands/ticker.ts +100 -50
  21. package/src/cli/currency-hints.ts +27 -0
  22. package/src/cli/fx-pair.ts +41 -4
  23. package/src/cli/glossary.ts +57 -4
  24. package/src/cli/helpers.ts +36 -3
  25. package/src/cli/index.ts +4 -1
  26. package/src/cli/known-gaps.ts +37 -1
  27. package/src/cli/listing-arg.ts +37 -2
  28. package/src/cli/pane-functions/catalog.ts +3 -1
  29. package/src/cli/pane-functions/discovery.ts +15 -1
  30. package/src/cli/pane-functions/dom.ts +9 -4
  31. package/src/cli/pane-functions/freshness.ts +4 -4
  32. package/src/cli/pane-functions/function-help.ts +27 -3
  33. package/src/cli/pane-functions/headless.ts +6 -5
  34. package/src/cli/pane-functions/index.ts +3 -2
  35. package/src/cli/pane-functions/lock-note.ts +7 -4
  36. package/src/cli/pane-functions/resolver.ts +2 -1
  37. package/src/cli/quote-answer.ts +179 -0
  38. package/src/cli/series-search.ts +2 -1
  39. package/src/cli/types.ts +4 -0
  40. package/src/components/chart/composite/composite-chart.tsx +11 -2
  41. package/src/components/chart/composite/format.ts +13 -1
  42. package/src/components/chart/composite/level-labels.ts +30 -0
  43. package/src/components/chart/composite/levels.ts +17 -6
  44. package/src/components/chart/composite/panel-surface.tsx +58 -8
  45. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  46. package/src/components/index.ts +2 -0
  47. package/src/components/layout/pane/content.tsx +16 -14
  48. package/src/components/layout/pane/footer/index.tsx +22 -3
  49. package/src/components/layout/pane/footer/model.ts +99 -4
  50. package/src/components/layout/pane/header-tabs.tsx +15 -1
  51. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  52. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  53. package/src/components/onboarding/wizard-model.ts +10 -3
  54. package/src/components/ui/button.tsx +11 -4
  55. package/src/components/ui/choice-dialog.tsx +8 -5
  56. package/src/components/ui/display.tsx +11 -6
  57. package/src/components/ui/fields.tsx +100 -20
  58. package/src/components/ui/icon.tsx +6 -1
  59. package/src/components/ui/list-row-lines.tsx +87 -0
  60. package/src/components/ui/list-view.tsx +14 -0
  61. package/src/components/ui/logo-mark.tsx +64 -0
  62. package/src/components/ui/number-step.ts +54 -0
  63. package/src/components/ui/select-button.tsx +5 -4
  64. package/src/components/ui/select-field.tsx +3 -3
  65. package/src/components/ui/status.tsx +17 -3
  66. package/src/components/ui/tabs.tsx +9 -1
  67. package/src/components/ui/toggle.tsx +17 -3
  68. package/src/core/state/app/instrument.ts +8 -3
  69. package/src/data/config/store/normalize.ts +4 -1
  70. package/src/data/plugin-cache.ts +32 -6
  71. package/src/docs/snapshot.json +5 -5
  72. package/src/i18n/es.ts +9 -3
  73. package/src/i18n/ja.ts +9 -3
  74. package/src/i18n/ko.ts +9 -3
  75. package/src/i18n/zh-cn.ts +10 -4
  76. package/src/i18n/zh-tw.ts +10 -4
  77. package/src/market-data/commodity-names.ts +80 -0
  78. package/src/market-data/coordinator/auxiliary.ts +3 -1
  79. package/src/market-data/coordinator/chart.ts +3 -1
  80. package/src/market-data/futures-board.ts +38 -1
  81. package/src/market-data/futures-contract.ts +91 -0
  82. package/src/market-data/fx-closes.ts +78 -1
  83. package/src/market-data/market/change-basis.ts +219 -0
  84. package/src/market-data/market/format.ts +28 -12
  85. package/src/market-data/market/freshness.ts +38 -0
  86. package/src/market-data/market/status.ts +55 -9
  87. package/src/market-data/market/year-to-date.ts +46 -0
  88. package/src/market-data/quotes/contributions.ts +12 -0
  89. package/src/market-data/quotes/delisting.ts +34 -0
  90. package/src/market-data/quotes/live-valuation.ts +23 -0
  91. package/src/market-data/quotes/resolution.ts +8 -2
  92. package/src/market-data/tax-equivalent.ts +10 -2
  93. package/src/notifications/app-notifier.ts +2 -2
  94. package/src/notifications/notification-log.ts +8 -6
  95. package/src/plugins/builtin/alerts/cli.ts +26 -18
  96. package/src/plugins/builtin/alerts/headless.ts +1 -1
  97. package/src/plugins/builtin/alerts/index.tsx +13 -4
  98. package/src/plugins/builtin/attention/trending.tsx +8 -6
  99. package/src/plugins/builtin/backtest/client.ts +5 -2
  100. package/src/plugins/builtin/backtest/window.ts +16 -3
  101. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  102. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  103. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  104. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  105. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  106. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  107. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  108. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  109. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  110. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  111. package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
  112. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  113. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  114. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  115. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  116. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  117. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  118. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  119. package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
  120. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  121. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  122. package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
  123. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  124. package/src/plugins/builtin/buildout/format.ts +1 -0
  125. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  126. package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
  127. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  128. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  129. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  130. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  131. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  132. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  133. package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
  134. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  135. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  136. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  137. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  138. package/src/plugins/builtin/chat/index.tsx +1 -1
  139. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  140. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  141. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  142. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  143. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  144. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  145. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  146. package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
  147. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  148. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  149. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  150. package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
  151. package/src/plugins/builtin/composite-plugins.ts +3 -2
  152. package/src/plugins/builtin/cot/headless.ts +1 -1
  153. package/src/plugins/builtin/cot/index.tsx +1 -1
  154. package/src/plugins/builtin/cot/model.ts +35 -12
  155. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  156. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  157. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  158. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  159. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  160. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  161. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  162. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  163. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  164. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  165. package/src/plugins/builtin/econ/calendar-model.ts +35 -0
  166. package/src/plugins/builtin/econ/index.tsx +31 -4
  167. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  168. package/src/plugins/builtin/equity-screener/headless.ts +20 -5
  169. package/src/plugins/builtin/equity-screener/model.ts +40 -2
  170. package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
  171. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  172. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  173. package/src/plugins/builtin/exposure/client.ts +4 -1
  174. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  175. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  176. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  177. package/src/plugins/builtin/exposure/pane.tsx +37 -16
  178. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  179. package/src/plugins/builtin/futures/table.tsx +6 -3
  180. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  181. package/src/plugins/builtin/insider/base-values.ts +72 -0
  182. package/src/plugins/builtin/insider/headless.ts +29 -4
  183. package/src/plugins/builtin/jobs/headless.ts +2 -2
  184. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  185. package/src/plugins/builtin/market-halts/model.ts +31 -6
  186. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  187. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  188. package/src/plugins/builtin/members/pane.tsx +8 -3
  189. package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
  190. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  191. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  192. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  193. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  194. package/src/plugins/builtin/options/footer.ts +25 -20
  195. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  196. package/src/plugins/builtin/options/view.tsx +77 -22
  197. package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
  198. package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
  199. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  200. package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
  201. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  202. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  203. package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
  204. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  205. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  206. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  207. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  208. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  209. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  210. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  211. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  212. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  213. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  214. package/src/plugins/builtin/scanner/feed.ts +6 -1
  215. package/src/plugins/builtin/scanner/headless.ts +1 -1
  216. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  217. package/src/plugins/builtin/series-inventory.ts +1 -0
  218. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  219. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  220. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  221. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  222. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  223. package/src/plugins/builtin/shared/research-trading.tsx +62 -0
  224. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  225. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  226. package/src/plugins/builtin/themes/headless.ts +8 -2
  227. package/src/plugins/builtin/themes/model.ts +43 -10
  228. package/src/plugins/builtin/themes/pane.tsx +8 -6
  229. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  230. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  231. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  232. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  233. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  234. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  235. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  236. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  237. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  238. package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
  239. package/src/plugins/builtin-editorial.ts +1 -1
  240. package/src/plugins/compat.ts +3 -0
  241. package/src/plugins/external-runtime.ts +5 -0
  242. package/src/plugins/loader.ts +6 -0
  243. package/src/plugins/plugin-icon.ts +61 -0
  244. package/src/plugins/registry/host-actions.ts +2 -1
  245. package/src/plugins/registry/index.ts +9 -1
  246. package/src/plugins/runtime/context.tsx +5 -0
  247. package/src/plugins/runtime/index.tsx +3 -0
  248. package/src/portfolio/column-values.ts +13 -4
  249. package/src/portfolio/cost-fx.ts +5 -28
  250. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  251. package/src/public/broker.ts +8 -0
  252. package/src/public/market-data.ts +5 -0
  253. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  254. package/src/renderers/dom/desktop/controls.tsx +39 -7
  255. package/src/renderers/dom/desktop/icons.tsx +14 -0
  256. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  257. package/src/renderers/dom/desktop/menu.tsx +10 -1
  258. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  259. package/src/renderers/dom/host/style.ts +6 -1
  260. package/src/renderers/dom/host/tabs.tsx +6 -0
  261. package/src/renderers/dom/styles.css +12 -5
  262. package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
  263. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  264. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  265. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  266. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  267. package/src/series/validate.ts +2 -2
  268. package/src/sources/gloomberb-cloud/index.ts +50 -17
  269. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  270. package/src/sources/history-result.ts +3 -0
  271. package/src/sources/provider-errors.ts +3 -1
  272. package/src/sources/provider-router/history.ts +49 -6
  273. package/src/sources/provider-router/index.ts +2 -1
  274. package/src/state/app/activity.ts +13 -0
  275. package/src/sync/controller.ts +4 -0
  276. package/src/sync/core-contributors.ts +62 -11
  277. package/src/sync/local-only.ts +121 -0
  278. package/src/sync/react.ts +6 -0
  279. package/src/sync/types.ts +3 -0
  280. package/src/tickers/listing-fate.ts +27 -8
  281. package/src/tickers/search/index.ts +24 -3
  282. package/src/time-series/price-comparison.ts +35 -0
  283. package/src/time-series/resolve.ts +52 -11
  284. package/src/time-series/spec.ts +1 -1
  285. package/src/time-series/types.ts +5 -0
  286. package/src/types/broker.ts +56 -1
  287. package/src/types/config.ts +6 -0
  288. package/src/types/data-provider.ts +5 -1
  289. package/src/types/financials.ts +23 -0
  290. package/src/types/headless.ts +12 -2
  291. package/src/types/instrument.ts +4 -0
  292. package/src/types/plugin.ts +24 -6
  293. package/src/types/price-history.ts +6 -1
  294. package/src/types/trading.ts +67 -0
  295. package/src/ui/host.tsx +4 -0
  296. package/src/utils/base-currency.ts +108 -0
  297. package/src/utils/currency-names.ts +128 -0
  298. package/src/utils/currency-pair.ts +2 -1
  299. package/src/utils/currency-units.ts +32 -1
  300. package/src/utils/dom-screenshot.ts +22 -9
  301. package/src/utils/exchange-notation.generated.ts +360 -0
  302. package/src/utils/exchanges.ts +15 -322
  303. package/src/utils/format.ts +31 -1
  304. package/src/utils/instrument-identity.ts +38 -1
  305. package/src/utils/options.ts +46 -1
  306. package/src/utils/price-history.ts +4 -0
  307. package/src/utils/reported-money.ts +8 -4
@@ -4,6 +4,7 @@ import { signalWithTimeout } from "../../../utils/async-deadline";
4
4
  import { parsePublicTickerKey } from "../../../utils/exchanges";
5
5
  import { httpFetch } from "../../../utils/http-transport";
6
6
  import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
7
+ import { logFailure } from "../../../utils/log-failure";
7
8
  import { zonedDateKey } from "../../../utils/zoned-date-time";
8
9
 
9
10
  const ENDPOINT = "https://services-dynarep.ddwa.finra.org/public/reporting/v2/data/group/FixedIncomeMarket/name/corporateAndAgencySecurities";
@@ -226,6 +227,7 @@ function dataRows(data: unknown): unknown[] | null {
226
227
  const parsed = JSON.parse(data) as unknown;
227
228
  return Array.isArray(parsed) ? parsed : null;
228
229
  } catch {
230
+ // Intentional ignore: malformed nested rows make postBondPage reject the response shape.
229
231
  return null;
230
232
  }
231
233
  }
@@ -340,7 +342,9 @@ async function readPayload(response: Response): Promise<unknown> {
340
342
  async function openSession(signal: AbortSignal): Promise<string> {
341
343
  const session = await httpFetch(ENDPOINT, { headers: REQUEST_HEADERS, signal });
342
344
  const token = xsrfToken(session);
343
- await session.body?.cancel().catch(() => {});
345
+ await session.body?.cancel().catch(() => {
346
+ // Intentional ignore: only session headers are needed; discarding its unused body is best effort.
347
+ });
344
348
  if (!token) throw new Error(failureText(session.status, "session"));
345
349
  return token;
346
350
  }
@@ -477,7 +481,14 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
477
481
  let answer = first;
478
482
  let issuer: BondIssuer | null = null;
479
483
  if (first.bonds.length === 0 && query?.kind === "issuer" && query.ticker && options.companyName) {
480
- const company = await options.companyName(query.ticker).catch(() => null);
484
+ const company = await options.companyName(query.ticker).catch((error: unknown) => {
485
+ options.signal?.throwIfAborted();
486
+ if (error instanceof Error && error.name === "AbortError") throw error;
487
+ logFailure("trace-bonds.client", "Resolve bond issuer company failed")(error);
488
+ answer = { ...first, failures: [...first.failures, "Issuer-name lookup failed."] };
489
+ return null;
490
+ });
491
+ options.signal?.throwIfAborted();
481
492
  const name = company ? tapeIssuerName(company) : null;
482
493
  if (company && name && name !== query.name) {
483
494
  issuer = { company, name };
@@ -492,7 +503,7 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
492
503
  // No prints for the query: date the answer by the market's newest sale.
493
504
  const market = await postBondPage(token, [TRADED], 1, deadline)
494
505
  .then((payload) => latestTradeDate(mergeBondAnswers([{ payload, accepts: anyBond, limit: 1 }]).bonds))
495
- .catch(() => null);
506
+ .catch(logFailure("trace-bonds.client", "Load bond market date failed", null));
496
507
  return { ...result, tradeDate: market };
497
508
  }
498
509
 
@@ -89,7 +89,7 @@ export const traceBondsHeadless: HeadlessPaneDefinition<"bundle"> = {
89
89
  rows: bonds.map(reportRow),
90
90
  ...(query ? { empty: noPrintsText(query, tradeDate, issuer) } : {}),
91
91
  }],
92
- complete: true,
92
+ complete: failures.length === 0,
93
93
  ...(failures.length > 0 ? { errors: failures } : {}),
94
94
  ...(truncated ? { notes: [TRUNCATED_TEXT] } : {}),
95
95
  ...(shown ? { warnings: [issuerFallbackText(query!, issuer)] } : {}),
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
2
+ import { splitDelayedStatus, DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
3
3
  usePaneNoticeFooter, usePaneTabs, usePaneTicker, type DataTableColumn, type DataTableKeyEvent, type StatItem } from "../../../components";
4
4
  import { useTableLoadMore } from "../../../components/table-view-shared";
5
5
  import { useStaticChartBitmapSize } from "../../../components/chart/composite/bitmap";
@@ -360,7 +360,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
360
360
  info: [
361
361
  ...(active.loading ? [{ id: "loading", parts: [{ text: snapshot && snapshot.loaded < snapshot.requested
362
362
  ? `loading ${snapshot.loaded}/${snapshot.requested} expiries` : "loading", tone: "muted" as const }] }] : []),
363
- ...(freshness ? [{ id: "source", parts: [{ text: freshness, tone: "muted" as const }] }] : []),
363
+ ...(freshness ? splitDelayedStatus({ id: "source", parts: [{ text: freshness, tone: "muted" }] }) : []),
364
364
  ...(snapshot?.expiries.some((entry) => entry.stale) ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
365
365
  ...(selectedExpiry?.fit && activeTab === "smile" ? [{ id: "fit", parts: [{ text: `${selectedExpiry.fit.method} · RMSE ${(selectedExpiry.fit.residual * 100).toFixed(3)} vol pts`, tone: "muted" as const }] }] : []),
366
366
  ...(selectionStatus ? [{ id: "selection", parts: [{ text: selectionStatus, tone: "muted" as const }] }] : []),
@@ -31,7 +31,7 @@ export const BUILTIN_EDITORIAL: Readonly<Record<string, BuiltinEditorial>> = {
31
31
  tagline: "Account, chat, Ask Gloom and Cloud data.",
32
32
  icon: "plugin-icons/gloomberb-cloud.webp",
33
33
  },
34
- alerts: { categories: ["alerts"], tagline: "Price, market and filing alerts." },
34
+ alerts: { categories: ["alerts"], tagline: "Price, earnings, filing, news and flow alerts." },
35
35
  "alt-data": {
36
36
  categories: ["research", "data"],
37
37
  tagline: "Supply chains, hiring, apps, GPUs and power.",
@@ -68,6 +68,8 @@ export interface PluginManifest {
68
68
  id: string | null;
69
69
  /** The oldest Gloomberb the checkout says it runs on. */
70
70
  minGloomberb: string | null;
71
+ /** gloom.json `icon`: the plugin's logo, a path inside the checkout such as `icon.webp`. */
72
+ icon: string | null;
71
73
  }
72
74
 
73
75
  function readJson(path: string): Record<string, unknown> | null {
@@ -106,6 +108,7 @@ export function readPluginManifest(pluginDir: string): PluginManifest {
106
108
  minGloomberb: versionField(gloom?.minGloom)
107
109
  ?? versionField(gloom?.minGloomberb)
108
110
  ?? peerRangeMinimum(peers?.gloomberb),
111
+ icon: typeof gloom?.icon === "string" && gloom.icon.trim() ? gloom.icon.trim() : null,
109
112
  };
110
113
  }
111
114
 
@@ -24,6 +24,11 @@ export function listExternalPlugins(): readonly LoadedExternalPlugin[] {
24
24
  return entries;
25
25
  }
26
26
 
27
+ /** The logo of a plugin loaded here, as a `data:` URL; null for a built-in or a plugin without one. */
28
+ export function externalPluginIcon(pluginId: string): string | null {
29
+ return entries.find((entry) => entry.plugin.id === pluginId)?.icon ?? null;
30
+ }
31
+
27
32
  /** Replaces the entry for the same plugin id or directory, or appends. */
28
33
  export function upsertExternalPlugin(entry: LoadedExternalPlugin): void {
29
34
  const index = entries.findIndex((existing) => (
@@ -33,6 +33,12 @@ export interface LoadedExternalPlugin {
33
33
  repo?: string;
34
34
  /** A symlink to a local checkout (`gloomberb plugin link`) rather than a clone. */
35
35
  linked?: boolean;
36
+ /**
37
+ * The plugin's logo as a `data:` URL (plugin-icon.ts), for the desktop and
38
+ * the web to draw beside what it contributes, such as its broker. The
39
+ * terminal draws no images and leaves it unread.
40
+ */
41
+ icon?: string;
36
42
  error?: string;
37
43
  /**
38
44
  * The Gloomberb the checkout declares it needs, when this one is older. The
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from "fs";
2
+ import { basename, extname, isAbsolute, relative, resolve, sep } from "path";
3
+ import { logFailure } from "../utils/log-failure";
4
+ import { readPluginManifest } from "./compat";
5
+
6
+ /**
7
+ * A plugin's logo as the desktop and the web draw it: the file its gloom.json
8
+ * names as `icon` (PLUGINS.md), or the `icon.svg`, `icon.webp` or `icon.png`
9
+ * a checkout without one ships beside its entry, read into a `data:` URL. The
10
+ * view has no filesystem, so the Bun process that reads the plugins folder
11
+ * hands the image over with the plugin's bundle.
12
+ */
13
+
14
+ const ICON_TYPES: Readonly<Record<string, string>> = {
15
+ ".svg": "image/svg+xml",
16
+ ".webp": "image/webp",
17
+ ".png": "image/png",
18
+ ".jpg": "image/jpeg",
19
+ ".jpeg": "image/jpeg",
20
+ };
21
+
22
+ const CONVENTIONAL_ICONS = ["icon.svg", "icon.webp", "icon.png"];
23
+
24
+ /** A logo is drawn at most a few dozen pixels wide; anything larger is not one. */
25
+ const MAX_ICON_BYTES = 256 * 1024;
26
+
27
+ /**
28
+ * The icon `name` names inside `pluginDir`, typed by the name the plugin gave
29
+ * it, or null for a path that leaves the checkout or is not a small image.
30
+ */
31
+ function iconFile(pluginDir: string, name: string): { path: string; type: string } | null {
32
+ const type = ICON_TYPES[extname(name).toLowerCase()];
33
+ if (!type) return null;
34
+ const root = realpathSync(pluginDir);
35
+ const path = resolve(root, name);
36
+ if (!existsSync(path)) return null;
37
+ // A link that points out of the checkout is not the plugin's to show.
38
+ const real = realpathSync(path);
39
+ const inside = relative(root, real);
40
+ if (!inside || inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside)) return null;
41
+ const info = statSync(real);
42
+ return info.isFile() && info.size > 0 && info.size <= MAX_ICON_BYTES ? { path: real, type } : null;
43
+ }
44
+
45
+ /**
46
+ * The plugin's logo as a `data:` URL. A declared icon that is missing,
47
+ * unusable or unreadable falls back to the conventional names, each tried on
48
+ * its own, so one file the process may not read does not hide the next.
49
+ */
50
+ export function readPluginIcon(pluginDir: string): string | null {
51
+ const declared = readPluginManifest(pluginDir).icon;
52
+ for (const name of declared ? [declared, ...CONVENTIONAL_ICONS] : CONVENTIONAL_ICONS) {
53
+ try {
54
+ const icon = iconFile(pluginDir, name);
55
+ if (icon) return `data:${icon.type};base64,${readFileSync(icon.path).toString("base64")}`;
56
+ } catch (error) {
57
+ logFailure("plugin-icon", `Reading ${name} for ${basename(pluginDir)} failed`)(error);
58
+ }
59
+ }
60
+ return null;
61
+ }
@@ -9,6 +9,7 @@ import type { NewsQuery, NewsQueryState } from "../../types/news-source";
9
9
  import type {
10
10
  AppNotificationDelivery,
11
11
  AppNotificationRequest,
12
+ BrokerInstanceCreateOptions,
12
13
  BrokerInstanceUpdateOptions,
13
14
  PaneSettingField,
14
15
  PaneTemplateCreateOptions,
@@ -27,7 +28,7 @@ export interface PluginHostActions {
27
28
  getTicker(symbol: string): TickerRecord | null;
28
29
  getData(symbol: string): TickerFinancials | null;
29
30
  getConfig(): AppConfig;
30
- createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown>): Promise<BrokerInstanceConfig>;
31
+ createBrokerInstance(brokerType: string, label: string, values: Record<string, unknown>, options?: BrokerInstanceCreateOptions): Promise<BrokerInstanceConfig>;
31
32
  connectBrokerInstance(instanceId: string): Promise<void>;
32
33
  updateBrokerInstance(instanceId: string, values: Record<string, unknown>, options?: BrokerInstanceUpdateOptions): Promise<void>;
33
34
  syncBrokerInstance(instanceId: string): Promise<void>;
@@ -45,6 +45,7 @@ import type {
45
45
  import { debugLog } from "../../utils/debug-log";
46
46
  import { logFailure } from "../../utils/log-failure";
47
47
  import { EventBus, type HostEvents } from "../event-bus";
48
+ import { externalPluginIcon } from "../external-runtime";
48
49
  import { isReservedBuiltinPluginId } from "../ownership";
49
50
  import { createPluginPersistence } from "../plugin-persistence";
50
51
  import { createPluginTeamState } from "../team-state";
@@ -532,6 +533,12 @@ export class PluginRegistry implements PluginRuntimeAccess {
532
533
  return this.contributions.brokersMap.owners.get(brokerType);
533
534
  }
534
535
 
536
+ /** The logo of the plugin that registered `brokerType` (its gloom.json `icon`), for lists of brokers. */
537
+ getBrokerLogo = (brokerType: string): string | null => {
538
+ const owner = this.getBrokerPluginId(brokerType);
539
+ return owner ? externalPluginIcon(owner) : null;
540
+ };
541
+
535
542
  getShortcutPluginId(shortcutId: string): string | undefined {
536
543
  return this.contributions.shortcutsMap.owners.get(shortcutId);
537
544
  }
@@ -619,6 +626,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
619
626
  getData: (ticker) => this.getData(ticker),
620
627
  getTicker: (symbol) => this.getTicker(symbol),
621
628
  getConfig: () => this.getConfig(),
629
+ getBrokerAdapter: this.getBrokerAdapter,
622
630
  getPaneDef: (paneId) => contributions.panesMap.get(paneId),
623
631
  marketData: this.marketData,
624
632
  connectionHealth: this.connectionHealth,
@@ -630,7 +638,7 @@ export class PluginRegistry implements PluginRuntimeAccess {
630
638
  updateLayout: (layout) => this.updateLayout(layout),
631
639
  resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
632
640
  }),
633
- createBrokerInstance: (brokerType, label, values) => this.createBrokerInstance(brokerType, label, values),
641
+ createBrokerInstance: (brokerType, label, values, options) => this.createBrokerInstance(brokerType, label, values, options),
634
642
  updateBrokerInstance: this.updateBrokerInstance,
635
643
  syncBrokerInstance: this.syncBrokerInstance,
636
644
  removeBrokerInstance: this.removeBrokerInstance,
@@ -18,6 +18,11 @@ export interface PluginRuntimeAccess extends CapabilityInvoker, PluginRuntimeHos
18
18
  getBrokerAdapter(brokerType: string): BrokerAdapter | null;
19
19
  /** Every broker adapter installed now, read when called: plugins come and go. */
20
20
  listBrokerAdapters(): BrokerAdapter[];
21
+ /**
22
+ * The logo of the plugin that registered a broker adapter, as an image URL
23
+ * for the desktop and the web; null when it has none.
24
+ */
25
+ getBrokerLogo?(brokerType: string): string | null;
21
26
  subscribeResumeState(pluginId: string, key: string, listener: () => void): () => void;
22
27
  getResumeState<T = unknown>(pluginId: string, key: string, schemaVersion?: number): T | null;
23
28
  setResumeState(pluginId: string, key: string, value: unknown, schemaVersion?: number): void;
@@ -78,11 +78,14 @@ export function useCapabilityInvoker(): CapabilityInvoker {
78
78
  return usePluginRenderContext().runtime;
79
79
  }
80
80
 
81
+ const noBrokerLogo = (): string | null => null;
82
+
81
83
  export function usePluginBrokerActions() {
82
84
  const { runtime } = usePluginRenderContext();
83
85
  return {
84
86
  getBrokerAdapter: runtime.getBrokerAdapter,
85
87
  listBrokerAdapters: runtime.listBrokerAdapters,
88
+ getBrokerLogo: runtime.getBrokerLogo ?? noBrokerLogo,
86
89
  createBrokerInstance: runtime.createBrokerInstance,
87
90
  connectBrokerInstance: runtime.connectBrokerInstance,
88
91
  updateBrokerInstance: runtime.updateBrokerInstance,
@@ -8,6 +8,7 @@ import type { EarningsEvent } from "../types/data-provider";
8
8
  import type { TickerRecord } from "../types/ticker";
9
9
  import { colors, priceColor } from "../theme/colors";
10
10
  import { formatQuoteAgeWithSource, resolveQuoteAgeTimestamp } from "../market-data/quotes/time";
11
+ import { delistingStateLabel } from "../market-data/quotes/delisting";
11
12
  import { formatFeedTime, formatShortDate, parseDisplayDate, type ShortDateOptions } from "../utils/datetime-format";
12
13
  import { convertCurrency, formatCompact, formatCompactAmount, formatNumber, formatPercentRaw } from "../utils/format";
13
14
  import {
@@ -40,6 +41,7 @@ import { currentListingName } from "../tickers/listing-name";
40
41
  import { PRICE_SPARKLINE_COLUMN_ID } from "../components/price-sparkline/view";
41
42
  import { followLiveSparklinePrice, resolveSparklineHistory, sparklineValues } from "../components/price-sparkline/model";
42
43
  import {
44
+ analystTargetDisplayCurrency,
43
45
  listingDividendYield,
44
46
  liveDividendYield,
45
47
  liveFiftyTwoWeekRange,
@@ -67,6 +69,7 @@ import {
67
69
  } from "./valuation-basis";
68
70
  import { futuresQuantityText, futuresSettlementDisplay, type PortfolioFuturesTerms } from "./futures-positions";
69
71
  import { isFiniteNumber } from "../utils/guards";
72
+ import { currencyCodeLabel } from "../utils/currency-units";
70
73
  import {
71
74
  allocationFigures,
72
75
  formatAllocationDrift,
@@ -453,7 +456,7 @@ export function getColumnValue(
453
456
  ),
454
457
  };
455
458
  case "currency":
456
- return { text: compactText(listedCurrency(positionCurrency, quoteCurrency, ticker, quote)) };
459
+ return { text: compactText(currencyCodeLabel(listedCurrency(positionCurrency, quoteCurrency, ticker, quote))) };
457
460
  case "sector":
458
461
  return { text: compactText(ticker.metadata.sector || financials?.profile?.sector) };
459
462
  case "industry":
@@ -464,6 +467,8 @@ export function getColumnValue(
464
467
  return resolvePortfolioPriceValue(headlineQuote, brokerMarkPrice, headlineQuote ? currentQuoteOptions : markOptions, col.width, quote?.marketState);
465
468
  case "change":
466
469
  if (!headlineQuote) return { text: "—" };
470
+ // A listing that no longer trades has no day's move: its last session's is years old.
471
+ if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
467
472
  return {
468
473
  text: formatSignedMarketPrice(headlineQuote.change, { ...currentQuoteOptions, maxWidth: col.width }),
469
474
  color: marketChangeColor(headlineQuote.change, quote?.marketState),
@@ -489,6 +494,7 @@ export function getColumnValue(
489
494
  return { text: `${formatCompact(quote?.bidSize)}/${formatCompact(quote?.askSize)}` };
490
495
  }
491
496
  case "change_pct": {
497
+ if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
492
498
  const changePercent = headlineChangePercent(headlineQuote, quote);
493
499
  return quote
494
500
  ? { text: formatPercentRaw(changePercent), color: marketChangeColor(changePercent, quote.marketState) }
@@ -573,6 +579,7 @@ export function getColumnValue(
573
579
  return { text, color: /[1-9]/.test(text) ? priceColor(allocation.tradeValue!) : undefined };
574
580
  }
575
581
  case "day_pnl":
582
+ if (quote?.delisting) return { text: delistingStateLabel(quote.delisting), color: colors.warning };
576
583
  if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
577
584
  const dayPnl = toBaseQuote(totalPriceUnits * activeQuote.change);
578
585
  return { text: formatCompactAmount(dayPnl, { signed: true }), color: priceColor(dayPnl) };
@@ -603,7 +610,8 @@ export function getColumnValue(
603
610
  const analyst = mapData(ctx.analystResearch, ticker.metadata.ticker);
604
611
  if (analyst.pending) return { text: "…" };
605
612
  const value = targetValue(analyst.data);
606
- const currency = analyst.data?.priceTarget?.currency ?? analyst.data?.currency ?? quoteCurrency;
613
+ // The target's own currency: one in pounds under a quote in pence reads £, not p.
614
+ const currency = analystTargetDisplayCurrency(analyst.data, quoteCurrency, displayQuote?.price);
607
615
  return {
608
616
  text: value == null
609
617
  ? "—"
@@ -730,9 +738,9 @@ export function getSortValue(
730
738
  ? (quote?.bidSize ?? 0) + (quote?.askSize ?? 0)
731
739
  : null;
732
740
  case "change":
733
- return headlineQuote?.change ?? null;
741
+ return quote?.delisting ? null : headlineQuote?.change ?? null;
734
742
  case "change_pct":
735
- return headlineChangePercent(headlineQuote, quote) ?? null;
743
+ return quote?.delisting ? null : headlineChangePercent(headlineQuote, quote) ?? null;
736
744
  case "volume":
737
745
  return reportedQuoteVolume(quote);
738
746
  case "dollar_volume": {
@@ -774,6 +782,7 @@ export function getSortValue(
774
782
  case "trade_value":
775
783
  return rowAllocation(ticker, activeQuote, baseMetrics, toBaseQuote, ctx).tradeValue;
776
784
  case "day_pnl":
785
+ if (quote?.delisting) return null;
777
786
  if (activeQuote && isFiniteNumber(activeQuote.change) && Number.isFinite(positionMetrics.grossPriceUnits) && positionMetrics.grossPriceUnits !== 0) {
778
787
  return toBaseQuote(totalPriceUnits * activeQuote.change);
779
788
  }
@@ -1,12 +1,9 @@
1
- import type { CloudMarketResponse, CloudPricePointPayload } from "../api-client/types";
2
- import type { CloudHistoryParams } from "../api-client/paths";
3
1
  import { fxLegForCurrency } from "../market-data/coordinator/fx-legs";
2
+ import { fxCloseOnDate, type FxHistoryClient } from "../market-data/fx-closes";
4
3
  import { heldCurrencyUnit } from "../utils/currency-units";
5
4
 
6
5
  /** Reads a pair's daily bars; `apiClient` in the app, a fake in tests. */
7
- export interface CostFxHistoryClient {
8
- getCloudHistory(symbol: string, exchange: string, params: CloudHistoryParams): Promise<CloudMarketResponse<CloudPricePointPayload[]>>;
9
- }
6
+ export type CostFxHistoryClient = FxHistoryClient;
10
7
 
11
8
  /** The rate a lot's cost was bought at: one unit of its currency in the portfolio's, at a dated daily close. */
12
9
  export interface CostFxReading {
@@ -23,36 +20,16 @@ export class CostFxError extends Error {
23
20
  }
24
21
  }
25
22
 
26
- const DAY_MS = 86_400_000;
27
- /** How far before the purchase date a close is looked for: a weekend and a holiday fit inside it, a gap in the data does not. */
28
- const LOOKBACK_DAYS = 7;
29
-
30
- const isoDay = (time: number) => new Date(time).toISOString().slice(0, 10);
31
-
32
23
  /**
33
24
  * USD per unit of an ISO currency at its last completed daily close on or
34
- * before `date`, looking back a week at most. Today's bar, still trading, is
35
- * never read. Null when the pair has no close in that week.
25
+ * before `date`, looking back a week at most (`fxCloseOnDate`, as `fx --date`
26
+ * reads it). Null when the pair has no close in that week.
36
27
  */
37
28
  async function usdPerUnitOn(code: string, date: string, client: CostFxHistoryClient, now: Date): Promise<CostFxReading | null> {
38
29
  if (code === "USD") return { rate: 1, date };
39
30
  const leg = fxLegForCurrency(code);
40
31
  if (!leg) throw new CostFxError(`No daily FX history is served for ${code}`);
41
- const day = Date.parse(`${date}T00:00:00Z`);
42
- const response = await client.getCloudHistory(leg.instrument.symbol, leg.instrument.exchange ?? "", {
43
- interval: "1day", startDate: isoDay(day - LOOKBACK_DAYS * DAY_MS), endDate: isoDay(day + DAY_MS), outputsize: LOOKBACK_DAYS + 4,
44
- });
45
- if (response.status !== "success" && response.status !== "partial") return null;
46
- const today = isoDay(now.getTime());
47
- let best: CostFxReading | null = null;
48
- for (const row of Array.isArray(response.data) ? response.data : []) {
49
- const at = typeof row?.date === "string" ? Date.parse(row.date) : Number.NaN;
50
- if (!Number.isFinite(at) || !Number.isFinite(row.close) || row.close <= 0) continue;
51
- const rowDay = isoDay(at);
52
- if (rowDay > date || rowDay >= today || Date.parse(`${rowDay}T00:00:00Z`) < day - LOOKBACK_DAYS * DAY_MS) continue;
53
- if (!best || rowDay > best.date) best = { rate: leg.invert ? 1 / row.close : row.close, date: rowDay };
54
- }
55
- return best;
32
+ return fxCloseOnDate(leg, date, client, now);
56
33
  }
57
34
 
58
35
  /**
@@ -4,6 +4,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
4
4
  {
5
5
  ".": [],
6
6
  "./broker": [
7
+ "LOCAL_BROKER_MARKET_DATA",
7
8
  "PRESERVED_PASSWORD_HINT",
8
9
  "cloudBrokerLink",
9
10
  "getBrokerRemoteClient",
@@ -53,6 +54,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
53
54
  "CompositeChart",
54
55
  "ConfirmDialog",
55
56
  "CurveSurface",
57
+ "DELAYED_SEGMENT",
56
58
  "DataTable",
57
59
  "DataTableStackView",
58
60
  "DataTableView",
@@ -132,6 +134,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
132
134
  "curveSlope",
133
135
  "curveStrip",
134
136
  "curveSurfaceMinRows",
137
+ "delayedFooterSegment",
135
138
  "fieldFrameStyle",
136
139
  "fieldGridColumns",
137
140
  "fieldGridRows",
@@ -159,6 +162,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
159
162
  "sortStackItems",
160
163
  "spanAxisFormatter",
161
164
  "spanDigits",
165
+ "splitDelayedStatus",
162
166
  "stackedSeries",
163
167
  "statGridColumns",
164
168
  "statGridRows",
@@ -201,6 +205,7 @@ exports[`the runtime exports of every public subpath are the reviewed ones 1`] =
201
205
  ],
202
206
  "./market-data": [
203
207
  "computeTickerPriceReturns",
208
+ "createProviderMiss",
204
209
  "currencyMinorDigits",
205
210
  "formatCompactMarketPriceWithCurrency",
206
211
  "formatMarketChangeWithCurrency",
@@ -20,6 +20,14 @@ export type { BrokerRemoteClient } from "../brokers/remote-broker-adapter";
20
20
  export type { ResourceStore } from "../data/resource-store";
21
21
  export { resolveTickerFinancialsForInstrument } from "../market-data/coordinator";
22
22
 
23
+ /**
24
+ * This host's Cloud sync honors `BrokerAdapter.localMarketData`. A broker whose market data must
25
+ * not reach Gloom Cloud reads it to tell an older host, which would sync its quotes and where it
26
+ * still has to ask for Cloud to be off: `import * as broker from "gloomberb/broker"`, then
27
+ * `broker.LOCAL_BROKER_MARKET_DATA === true`.
28
+ */
29
+ export const LOCAL_BROKER_MARKET_DATA = true;
30
+
23
31
  /**
24
32
  * The app's cached-resource store. Set by the host at startup and cleared on
25
33
  * teardown; null before services are constructed.
@@ -25,6 +25,11 @@ export type {
25
25
  AssetDisplayContext, AssetDisplayKind, MarketFormatOptions, StablePriceContext,
26
26
  } from "../market-data/market/format";
27
27
 
28
+ // How a provider says it has nothing for a symbol, as built-in sources do: the
29
+ // router tries the next provider, does not log it as a failure, and shows the
30
+ // reason where the data would be.
31
+ export { createProviderMiss } from "../sources/provider-errors";
32
+
28
33
  // The coordinator the app loads snapshots through, so a plugin pane that shows
29
34
  // its own list of tickers warms the same cache the rest of the app reads.
30
35
  export { getSharedMarketDataCoordinator } from "../market-data/coordinator";
@@ -35,9 +35,14 @@ export function controlBorderColor(focused = false, active = false, palette: The
35
35
  * casts a shadow. The icon button is `.gloom-icon-button` in styles.css.
36
36
  */
37
37
  export function buttonPalette(
38
- props: Pick<ButtonProps, "variant" | "active" | "disabled">,
38
+ props: Pick<ButtonProps, "variant" | "active" | "disabled" | "tone">,
39
39
  palette: ThemeColors = colors,
40
40
  ) {
41
+ if (props.variant === "quiet") return {
42
+ fg: props.disabled ? palette.textMuted : props.active ? palette.textBright : palette.textDim,
43
+ bg: props.active ? selectedPanelFill(palette) : "transparent",
44
+ border: "transparent",
45
+ };
41
46
  if (props.variant === "plain" || props.variant === "ghost") {
42
47
  return {
43
48
  fg: props.disabled ? palette.textMuted : props.active ? palette.textBright : palette.textDim,
@@ -52,6 +57,7 @@ export function buttonPalette(
52
57
  border: controlBorderColour(palette),
53
58
  };
54
59
  }
60
+ if (props.tone && props.variant === "primary") return { fg: palette.bg, bg: palette[props.tone], border: props.active ? palette.textBright : palette[props.tone] };
55
61
  if (props.active) {
56
62
  return {
57
63
  fg: palette.selectedText,