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
@@ -37,6 +37,7 @@ export function WebButton({
37
37
  expanded,
38
38
  onPress,
39
39
  variant = "secondary",
40
+ tone,
40
41
  disabled = false,
41
42
  pending = false,
42
43
  active = false,
@@ -49,7 +50,7 @@ export function WebButton({
49
50
  title,
50
51
  }: ButtonProps) {
51
52
  const colors = useThemeColors();
52
- const palette = buttonPalette({ variant, active, disabled }, colors);
53
+ const palette = buttonPalette({ variant, active, disabled, tone }, colors);
53
54
  const filled = active || variant === "primary" || variant === "danger";
54
55
 
55
56
  return (
@@ -261,10 +262,12 @@ const COMFORTABLE_FIELD_HEIGHT = 26;
261
262
 
262
263
  export function WebTextField({
263
264
  label,
265
+ accessibleLabel,
264
266
  value,
265
267
  placeholder,
266
268
  maxLength,
267
269
  focused,
270
+ disabled = false,
268
271
  width,
269
272
  inputRef,
270
273
  onChange,
@@ -290,6 +293,7 @@ export function WebTextField({
290
293
  const renderer = useRendererHost();
291
294
  const { nativeContextMenu } = useUiCapabilities();
292
295
  const plain = variant === "plain";
296
+ const well = variant === "well";
293
297
  const comfortable = size === "comfortable";
294
298
 
295
299
  return (
@@ -311,6 +315,7 @@ export function WebTextField({
311
315
  alignItems="center"
312
316
  backgroundColor={plain ? "transparent" : resolvedBackgroundColor}
313
317
  onMouseDown={() => {
318
+ if (disabled) return;
314
319
  onMouseDown?.();
315
320
  resolvedInputRef.current?.focus?.();
316
321
  }}
@@ -324,12 +329,16 @@ export function WebTextField({
324
329
  data-gloom-role="desktop-text-field"
325
330
  style={{
326
331
  height: comfortable ? COMFORTABLE_FIELD_HEIGHT : undefined,
327
- border: plain ? "none" : `1px solid ${controlBorderColor(focused, false, colors)}`,
328
- borderRadius: plain ? 0 : CONTROL_RADIUS,
332
+ border: plain || well ? "none" : `1px solid ${controlBorderColor(focused, false, colors)}`,
333
+ borderRadius: plain ? 0 : well ? "var(--gloom-radius-sm)" : CONTROL_RADIUS,
334
+ // A well has no border: focus is the accent along its foot.
335
+ boxShadow: well && focused ? "inset 0 -2px var(--gloom-accent)" : undefined,
329
336
  overflow: "hidden",
330
337
  }}
331
338
  >
332
339
  <Input
340
+ aria-label={accessibleLabel ?? label}
341
+ disabled={disabled}
333
342
  ref={resolvedInputRef as RefObject<InputRenderable | null>}
334
343
  width="100%"
335
344
  value={value}
@@ -459,15 +468,18 @@ export function WebMessageComposer({
459
468
  }
460
469
 
461
470
  export function WebSegmentedControl({
471
+ accessibleLabel,
462
472
  options,
463
473
  value,
464
474
  onChange,
465
475
  focused,
466
476
  width,
467
477
  wrap = false,
478
+ size = "default",
468
479
  }: SegmentedControlProps) {
469
480
  const colors = useThemeColors();
470
481
  const enabled = options.filter((option) => !option.disabled);
482
+ const large = size === "large";
471
483
  const selectAdjacent = (direction: -1 | 1) => {
472
484
  const index = enabled.findIndex((option) => option.value === value);
473
485
  const next = enabled[index < 0 ? 0 : (index + direction + enabled.length) % enabled.length];
@@ -476,26 +488,37 @@ export function WebSegmentedControl({
476
488
 
477
489
  // A segmented choice is a tab strip (the tab rule in styles.css): text, the
478
490
  // chosen one bright and bold on the accent underline. One row tall, so it
479
- // never bleeds over the pane title or the table header around it.
491
+ // never bleeds over the pane title or the table header around it. A large
492
+ // one, such as an order's side, is two rows of blocks that share the width,
493
+ // the chosen one filled in its tone.
480
494
  return (
481
495
  <Box
482
496
  flexDirection="row"
483
497
  flexWrap={wrap ? "wrap" : "nowrap"}
484
498
  width={width}
485
- height={wrap ? undefined : 1}
499
+ height={wrap ? undefined : large ? 2 : 1}
486
500
  alignItems="stretch"
487
501
  role="radiogroup"
502
+ aria-label={accessibleLabel}
488
503
  data-gloom-role="segmented-control"
504
+ data-size={large ? "large" : undefined}
489
505
  data-focused={focused ? "true" : undefined}
506
+ style={large ? {
507
+ borderRadius: "var(--gloom-radius-md)",
508
+ overflow: "hidden",
509
+ boxShadow: focused ? "0 0 0 2px var(--gloom-accent)" : undefined,
510
+ } : undefined}
490
511
  >
491
512
  {options.map((option) => {
492
513
  const active = option.value === value;
514
+ const tone = option.tone ? colors[option.tone] : undefined;
493
515
  return (
494
516
  <Box
495
517
  key={option.value}
496
518
  flexDirection="row"
497
519
  alignItems="center"
498
520
  justifyContent="center"
521
+ flexGrow={large ? 1 : undefined}
499
522
  onMouseDown={() => {
500
523
  if (!option.disabled) onChange?.(option.value);
501
524
  }}
@@ -503,7 +526,7 @@ export function WebSegmentedControl({
503
526
  // after the keyboard has moved on to another pane.
504
527
  onClick={(event: { currentTarget: unknown; detail: number }) => releasePointerFocus(event.currentTarget, event.detail)}
505
528
  data-gloom-interactive={option.disabled ? undefined : "true"}
506
- data-gloom-role="tab-button"
529
+ data-gloom-role={large ? "segment-block" : "tab-button"}
507
530
  data-active={active ? "true" : undefined}
508
531
  role="radio"
509
532
  aria-checked={active}
@@ -521,9 +544,18 @@ export function WebSegmentedControl({
521
544
  onChange?.(option.value);
522
545
  }
523
546
  }}
524
- style={{
547
+ style={large ? {
548
+ height: "100%",
549
+ cursor: option.disabled ? "default" : "pointer",
550
+ outline: "none",
551
+ fontSize: "var(--gloom-type-heading)",
552
+ fontWeight: "var(--gloom-weight-bold)",
553
+ background: option.disabled ? "var(--gloom-panel)" : active ? tone ?? colors.selected : "var(--gloom-panel)",
554
+ color: option.disabled ? colors.textMuted : active ? tone ? colors.bg : colors.selectedText : tone ?? colors.textDim,
555
+ } : {
525
556
  height: wrap ? undefined : "100%",
526
557
  cursor: option.disabled ? "default" : "pointer",
558
+ ...(tone ? { "--tab-fg": tone, "--tab-active-fg": tone } as CSSProperties : {}),
527
559
  ...(option.disabled ? { color: colors.textMuted } : {}),
528
560
  }}
529
561
  >
@@ -151,6 +151,20 @@ const ICONS: Record<IconName, ReactNode> = {
151
151
  <path d="M1.5 5.3h9M4.8 2.5 4.3 5.3 6 10.1l1.7-4.8-.5-2.8" stroke="currentColor" strokeWidth=".9" strokeLinejoin="round" />
152
152
  </>
153
153
  ),
154
+ // A clock face at ten past: data that arrives late.
155
+ clock: (
156
+ <>
157
+ <circle cx="6" cy="6" r="4.4" stroke="currentColor" strokeWidth="1.2" />
158
+ <path d="M6 3.4V6l1.9 1.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
159
+ </>
160
+ ),
161
+ // A pencil, for changing something in place.
162
+ edit: (
163
+ <>
164
+ <path d="M2.2 9.8l.5-2.3 5.4-5.4a1.1 1.1 0 0 1 1.6 0l.2.2a1.1 1.1 0 0 1 0 1.6L4.5 9.3l-2.3.5Z" stroke="currentColor" strokeWidth="1.1" strokeLinejoin="round" />
165
+ <path d="M7.3 2.9l1.8 1.8" stroke="currentColor" strokeWidth="1.1" />
166
+ </>
167
+ ),
154
168
  restore: (
155
169
  <>
156
170
  <rect x="2" y="4" width="6" height="6" rx="0.8" stroke="currentColor" strokeWidth="1.1" />
@@ -3,6 +3,7 @@ import { useEffect, useId, useRef, useState, type CSSProperties } from "react";
3
3
  import { Box, ScrollBox, Text, type ScrollBoxRenderable } from "../../../ui";
4
4
  import { TextAttributes } from "../../../ui";
5
5
  import type { ListRowState, ListViewItem, ListViewProps } from "../../../components/ui/list-view";
6
+ import { ListRowLines } from "../../../components/ui/list-row-lines";
6
7
  import { hoverBg, type ThemeColors } from "../../../theme/colors";
7
8
  import { useThemeColors } from "../../../theme/theme-context";
8
9
  import { WEB_CELL_HEIGHT } from "../../../theme/font-scale";
@@ -18,9 +19,12 @@ import {
18
19
  function DefaultDesktopRow({
19
20
  item,
20
21
  selected,
22
+ stacked,
21
23
  }: {
22
24
  item: ListViewItem;
23
25
  selected: boolean;
26
+ /** The description on a second line under the label (`rowDescriptions`). */
27
+ stacked: boolean;
24
28
  }) {
25
29
  const colors = useThemeColors();
26
30
  return (
@@ -28,28 +32,52 @@ function DefaultDesktopRow({
28
32
  flexDirection="row"
29
33
  justifyContent="space-between"
30
34
  width="100%"
35
+ // A two-line row is taller than its lines: they sit in its middle.
36
+ height={stacked ? "100%" : undefined}
31
37
  minWidth={0}
32
38
  alignItems="center"
33
39
  style={{ boxSizing: "border-box" }}
34
40
  >
35
- <Box flexDirection="row" alignItems="center" minWidth={0} flexShrink={1} overflow="hidden">
36
- <Text
37
- fg={item.color ?? (selected ? colors.text : colors.textDim)}
38
- attributes={selected ? TextAttributes.BOLD : 0}
39
- style={{
40
- // The base text style pins flexShrink to 0, which lets a long
41
- // label run under the detail instead of shrinking to an ellipsis.
42
- flexShrink: 1,
43
- minWidth: 0,
44
- maxWidth: "100%",
45
- overflow: "hidden",
46
- textOverflow: "ellipsis",
47
- whiteSpace: "nowrap",
48
- }}
41
+ {stacked ? (
42
+ <ListRowLines
43
+ label={item.label}
44
+ description={item.description}
45
+ leading={item.leading}
46
+ labelColor={item.color}
47
+ labelBold={selected}
48
+ />
49
+ ) : (
50
+ <Box
51
+ flexDirection="row"
52
+ alignItems="center"
53
+ minWidth={0}
54
+ flexShrink={1}
55
+ overflow="hidden"
56
+ style={item.leading !== undefined ? { gap: "var(--gloom-space-sm)" } : undefined}
49
57
  >
50
- {item.label}
51
- </Text>
52
- </Box>
58
+ {item.leading !== undefined && (
59
+ <Box flexShrink={0} alignItems="center" aria-hidden="true" data-gloom-role="list-row-leading">
60
+ {item.leading}
61
+ </Box>
62
+ )}
63
+ <Text
64
+ fg={item.color ?? (selected ? colors.text : colors.textDim)}
65
+ attributes={selected ? TextAttributes.BOLD : 0}
66
+ style={{
67
+ // The base text style pins flexShrink to 0, which lets a long
68
+ // label run under the detail instead of shrinking to an ellipsis.
69
+ flexShrink: 1,
70
+ minWidth: 0,
71
+ maxWidth: "100%",
72
+ overflow: "hidden",
73
+ textOverflow: "ellipsis",
74
+ whiteSpace: "nowrap",
75
+ }}
76
+ >
77
+ {item.label}
78
+ </Text>
79
+ </Box>
80
+ )}
53
81
  {item.detail && (
54
82
  <Text fg={colors.textMuted} style={{ flexShrink: 0, marginLeft: 12 }}>
55
83
  {item.detail}
@@ -59,6 +87,9 @@ function DefaultDesktopRow({
59
87
  );
60
88
  }
61
89
 
90
+ /** A row with its description under its label: the two lines and a quarter row above and below them. */
91
+ const STACKED_ROW_HEIGHT = 2.5;
92
+
62
93
  const FRAMED_LIST_PADDING_PX = 4;
63
94
  /** What a framed list adds above and below its rows: padding and a 1px border on each side. */
64
95
  const FRAMED_LIST_CHROME_PX = 2 * (FRAMED_LIST_PADDING_PX + 1);
@@ -84,12 +115,13 @@ export function WebListView({
84
115
  renderRow,
85
116
  getRowBackgroundColor,
86
117
  showSelectedDescription = false,
118
+ rowDescriptions = false,
87
119
  emptyMessage = "Nothing to show.",
88
120
  bgColor,
89
121
  selectedBgColor,
90
122
  hoverBgColor,
91
123
  rowGap = 1,
92
- rowHeight = 1,
124
+ rowHeight = rowDescriptions ? STACKED_ROW_HEIGHT : 1,
93
125
  surface,
94
126
  selection = "frame",
95
127
  height,
@@ -242,7 +274,7 @@ export function WebListView({
242
274
  >
243
275
  {renderRow
244
276
  ? renderRow(item, state, index)
245
- : <DefaultDesktopRow item={item} selected={selected} />}
277
+ : <DefaultDesktopRow item={item} selected={selected} stacked={rowDescriptions} />}
246
278
  </Box>
247
279
  );
248
280
  });
@@ -1,5 +1,5 @@
1
1
  /** @jsxImportSource react */
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
3
3
  import type { HostMenuItem, HostMenuProps } from "../../../ui/host";
4
4
  import { CheckboxBox } from "./controls";
5
5
  import { WebIcon } from "./icons";
@@ -10,9 +10,12 @@ function selectable(item: HostMenuItem): boolean {
10
10
  return item.kind !== "divider" && item.kind !== "heading" && !item.disabled;
11
11
  }
12
12
 
13
+ const LEADING_STYLE: CSSProperties = { display: "inline-flex", flex: "none", alignItems: "center" };
14
+
13
15
  /** Desktop Menu: the one list every popover menu in the app renders. */
14
16
  export function WebMenu({ items, onSelect, selection = "none", title, label, onClose, highlightedId, onHighlight }: HostMenuProps) {
15
17
  const enabled = useMemo(() => items.filter(selectable), [items]);
18
+ const alignLeading = items.find((item) => item.leading !== undefined)?.leading;
16
19
  const controlled = highlightedId !== undefined;
17
20
  const [ownHighlighted, setOwnHighlighted] = useState<string | null>(
18
21
  () => items.find((item) => item.selected && selectable(item))?.id ?? enabled[0]?.id ?? null,
@@ -92,6 +95,12 @@ export function WebMenu({ items, onSelect, selection = "none", title, label, onC
92
95
  {(selection === "multi" || toggle) && (
93
96
  <CheckboxBox checked={!!item.checked} />
94
97
  )}
98
+ {item.leading !== undefined ? (
99
+ <span aria-hidden="true" style={LEADING_STYLE}>{item.leading}</span>
100
+ ) : alignLeading !== undefined ? (
101
+ // The room another item's artwork takes, so every label starts in one column.
102
+ <span aria-hidden="true" style={{ ...LEADING_STYLE, visibility: "hidden" }}>{alignLeading}</span>
103
+ ) : null}
95
104
  <span className="gloom-menu-label">
96
105
  <span className="gloom-menu-text" style={item.color ? { color: item.color } : undefined}>{item.label}</span>
97
106
  {item.description ? <span className="gloom-menu-description">{item.description}</span> : null}
@@ -38,6 +38,7 @@ export function WebSelectField({
38
38
  const [open, setOpen] = useState(false);
39
39
  const buttonRef = useRef<HTMLButtonElement | null>(null);
40
40
  const inline = variant === "inline";
41
+ const well = variant === "well";
41
42
  const current = options.find((option) => option.value === value);
42
43
  const showUnset = includeUnsetOption && !current;
43
44
 
@@ -54,9 +55,9 @@ export function WebSelectField({
54
55
  setSelectRef.current = selectRef;
55
56
 
56
57
  const resolvedWidth = width ?? (inline ? "auto" : 184);
57
- // A field is one row inside its border, as a text field is; inline it is the row.
58
+ // A field is one row inside its border, as a text field is; inline it is the row, and a well has no border.
58
59
  const row = WEB_CELL_HEIGHT;
59
- const resolvedHeight = height ?? (inline ? row : row + 2);
60
+ const resolvedHeight = height ?? (inline || well ? row : row + 2);
60
61
  const style: CSSProperties = {
61
62
  width: resolvedWidth,
62
63
  height: resolvedHeight,
@@ -65,10 +66,11 @@ export function WebSelectField({
65
66
  justifyContent: "space-between",
66
67
  gap: "var(--gloom-space-xs)",
67
68
  color: disabled ? colors.textMuted : colors.text,
68
- // The field look: sunk into the surface, the control border, the accent while open.
69
- backgroundColor: inline ? "transparent" : "var(--gloom-surface-sunken)",
70
- border: inline ? "none" : `1px solid ${open ? "var(--gloom-accent)" : "var(--gloom-control-border)"}`,
71
- borderRadius: inline ? 0 : "var(--gloom-radius-md)",
69
+ // The field look: sunk into the surface, the control border, the accent while open. A well is the panel's
70
+ // fill with no border, for a dense form such as the order ticket.
71
+ backgroundColor: inline ? "transparent" : well ? "var(--gloom-panel)" : "var(--gloom-surface-sunken)",
72
+ border: inline || well ? "none" : `1px solid ${open ? "var(--gloom-accent)" : "var(--gloom-control-border)"}`,
73
+ borderRadius: inline ? 0 : well ? "var(--gloom-radius-sm)" : "var(--gloom-radius-md)",
72
74
  padding: inline ? 0 : "0 var(--gloom-space-sm)",
73
75
  cursor: disabled ? "default" : "pointer",
74
76
  font: "inherit",
@@ -18,6 +18,7 @@ function cellInset(value: unknown, axis: "x" | "y"): CSSProperties["paddingLeft"
18
18
  }
19
19
 
20
20
  export function commonStyle(props: Record<string, unknown>): CSSProperties {
21
+ const border = props.border;
21
22
  const hasFixedInlineSize = props.width != null || props.flexBasis != null;
22
23
  const hasFixedBlockSize = props.height != null;
23
24
  const gapUnit = props.flexDirection === "row" ? WEB_CELL_WIDTH : WEB_CELL_HEIGHT;
@@ -60,7 +61,11 @@ export function commonStyle(props: Record<string, unknown>): CSSProperties {
60
61
  ...(props.columnGap != null ? { columnGap: cellWidth(props.columnGap) as CSSProperties["columnGap"] } : {}),
61
62
  ...(props.rowGap != null ? { rowGap: cellHeight(props.rowGap) as CSSProperties["rowGap"] } : {}),
62
63
  overflow: props.overflow as CSSProperties["overflow"],
63
- border: props.border ? `1px solid ${typeof props.borderColor === "string" ? props.borderColor : "var(--gloom-border)"}` : undefined,
64
+ // A requested divider or accent is not a frame. Use longhands consistently
65
+ // so switching between one edge, all edges and no border clears stale sides.
66
+ borderStyle: props.border ? "solid" : undefined,
67
+ borderColor: props.border ? typeof props.borderColor === "string" ? props.borderColor : "var(--gloom-border)" : undefined,
68
+ borderWidth: Array.isArray(border) ? ["top", "right", "bottom", "left"].map(side => border.includes(side) ? "1px" : "0px").join(" ") : border ? "1px" : undefined,
64
69
  boxSizing: "border-box",
65
70
  minInlineSize: zeroMinInlineSize ? 0 : undefined,
66
71
  minBlockSize: zeroMinBlockSize ? 0 : undefined,
@@ -258,6 +258,11 @@ export function WebTabs({
258
258
  tab.onContextMenu?.(tab.value, event);
259
259
  } : undefined}
260
260
  >
261
+ {tab.leading !== undefined && (
262
+ <span data-gloom-role="tab-leading" aria-hidden="true" style={{ display: "flex", flex: "none", alignItems: "center" }}>
263
+ {tab.leading}
264
+ </span>
265
+ )}
261
266
  <span
262
267
  data-gloom-role="tab-label"
263
268
  style={{
@@ -349,6 +354,7 @@ export function WebTabs({
349
354
  items={tabs.map((tab) => ({
350
355
  id: tab.value,
351
356
  label: tab.label,
357
+ leading: tab.leading,
352
358
  disabled: tab.disabled,
353
359
  selected: tab.value === activeValue,
354
360
  }))}
@@ -70,6 +70,10 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
70
70
  [data-gloom-role="desktop-button"]:is([data-gloom-variant="plain"], [data-gloom-variant="ghost"], [data-gloom-variant="secondary"]):not(:disabled):not([aria-pressed="true"]):hover {
71
71
  background-color: var(--gloom-hover-fill);
72
72
  }
73
+ /* Quiet quick actions take the same wash, over a chosen one's fill too. */
74
+ [data-gloom-role="desktop-button"][data-gloom-variant="quiet"]:not(:disabled):hover {
75
+ background-color: var(--gloom-hover-fill) !important;
76
+ }
73
77
  [data-gloom-hover-bg="true"]:hover {
74
78
  background-color: var(--gloom-box-hover-bg) !important;
75
79
  }
@@ -941,12 +945,14 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
941
945
  }
942
946
  .gloom-qb-inline .gloom-qb-view { margin-left: 0; box-shadow: none; align-self: stretch; }
943
947
  /* A long inline choice (an expiry strip) scrolls by itself, as the terminal's
944
- does, so its label and the controls beside it stay in view. */
948
+ does, so its label and the controls beside it stay in view. It narrows with
949
+ its group and has no floor of its own: a floor wider than the room the bar
950
+ gives it drew its next choice under the control beside it. */
945
951
  .gloom-qb-filters[data-strip="true"],
946
952
  .gloom-qb-inline[data-strip="true"] { flex: 0 1 auto; min-width: 0; }
947
953
  .gloom-qb-inline[data-strip="true"] .gloom-qb-segments {
948
954
  flex: 0 1 auto;
949
- min-width: 12ch;
955
+ min-width: 0;
950
956
  overflow-x: auto;
951
957
  scrollbar-width: none;
952
958
  }
@@ -1354,8 +1360,9 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
1354
1360
  white-space: nowrap;
1355
1361
  }
1356
1362
 
1357
- /* Icon-only button: pane header, footer, dialog close, window controls. */
1358
- .gloom-icon-button {
1363
+ /* Icon-only button: pane header, footer, dialog close, window controls. A
1364
+ footer status icon (a delayed source's clock) takes the same box. */
1365
+ .gloom-icon-button, [data-gloom-role="pane-footer-icon"] {
1359
1366
  flex: none;
1360
1367
  display: inline-flex;
1361
1368
  align-items: center;
@@ -1372,7 +1379,7 @@ html[data-gloom-screenshot="true"] [data-gloom-role="chart-watermark"] {
1372
1379
  cursor: pointer;
1373
1380
  transition: background-color 120ms ease, color 120ms ease;
1374
1381
  }
1375
- .gloom-icon-button:disabled { cursor: default; }
1382
+ .gloom-icon-button:disabled, [data-gloom-role="pane-footer-icon"] { cursor: default; }
1376
1383
  .gloom-icon-button:not(:disabled):hover { background: var(--gloom-hover-fill); color: var(--gloom-text-bright); }
1377
1384
  .gloom-icon-button[aria-pressed="true"] { color: var(--gloom-warning); }
1378
1385
  .gloom-qb-view button:disabled { color: var(--gloom-text-muted); opacity: 0.55; cursor: default; }
@@ -30,6 +30,7 @@ const BROKER_INVOKE_OPERATIONS = new Set([
30
30
  "getChartResolutionCapabilities",
31
31
  "getOptionsChain",
32
32
  "listOpenOrders",
33
+ "getOrderStatus",
33
34
  "listExecutions",
34
35
  "previewOrder",
35
36
  "placeOrder",
@@ -116,6 +117,8 @@ function invokeBrokerOperation(
116
117
  case "previewOrder":
117
118
  case "placeOrder":
118
119
  return (broker[operation] as any)?.call(broker, instance, args[0]);
120
+ case "getOrderStatus":
121
+ return broker.getOrderStatus?.(instance, args[0] as number);
119
122
  case "modifyOrder":
120
123
  return broker.modifyOrder?.(instance, args[0] as number, args[1] as never);
121
124
  case "cancelOrder":
@@ -167,6 +170,15 @@ function createBrokerCapability(options: CoreCapabilityOptions): PluginCapabilit
167
170
  },
168
171
  );
169
172
  }),
173
+ orders: stream((input: any, emit) => {
174
+ const instanceId = requireString(input.instanceId, "Broker instance");
175
+ const { instance, broker } = resolveBrokerInstance(options, instanceId);
176
+ if (!broker.subscribeOrders) throw new Error(`Broker "${broker.name}" does not stream orders.`);
177
+ return broker.subscribeOrders(instance, {
178
+ order: (order) => emit({ kind: "order", order }),
179
+ live: (live) => emit({ kind: "orders-live", live }),
180
+ });
181
+ }),
170
182
  removeInstance: op(async (input: any) => {
171
183
  const { instance, broker } = resolveBrokerInstance(options, input.instanceId);
172
184
  await broker.disconnect?.(instance);
@@ -14,6 +14,7 @@ import {
14
14
  loadExternalPlugin,
15
15
  } from "../../../plugins/loader";
16
16
  import { createNodePluginManager } from "../../../plugins/manager-node";
17
+ import { readPluginIcon } from "../../../plugins/plugin-icon";
17
18
  import type { PluginRegistry } from "../../../plugins/registry";
18
19
  import { desktopRendererCapabilityManifests } from "./desktop/initialization";
19
20
  import { reportCrash } from "../../../telemetry/crash-reports";
@@ -70,6 +71,7 @@ async function bundlePluginDirectory(
70
71
 
71
72
  const { plugin } = loaded;
72
73
  const directory = basename(pluginDir);
74
+ const icon = readPluginIcon(pluginDir);
73
75
  const base = {
74
76
  id: plugin.id,
75
77
  name: plugin.name,
@@ -80,6 +82,7 @@ async function bundlePluginDirectory(
80
82
  ...(loaded.commit ? { commit: loaded.commit } : {}),
81
83
  ...(loaded.repo ? { repo: loaded.repo } : {}),
82
84
  ...(loaded.linked ? { linked: true } : {}),
85
+ ...(icon ? { icon } : {}),
83
86
  ...(plugin.targets ? { targets: plugin.targets } : {}),
84
87
  };
85
88
 
@@ -130,6 +130,8 @@ export interface DesktopExternalPluginBundle {
130
130
  /** `owner/repo` of the checkout's GitHub origin. */
131
131
  repo?: string;
132
132
  linked?: boolean;
133
+ /** The plugin's logo as a `data:` URL, read from the checkout by the Bun process. */
134
+ icon?: string;
133
135
  /** ES module source, absent when `error` or `unsupportedTarget` is set. */
134
136
  code?: string;
135
137
  targets?: readonly ("cli" | "tui" | "desktop" | "web")[];
@@ -125,6 +125,22 @@ const client: BrokerRemoteClient = {
125
125
  });
126
126
  },
127
127
 
128
+ subscribeOrders(instanceId, listener) {
129
+ return subscribeBackendCapability({
130
+ subscriptionId: `broker-orders:${instanceId}:${nextSubscriptionId++}`,
131
+ capabilityId: BROKER_CAPABILITY_ID,
132
+ operationId: "orders",
133
+ payload: { instanceId },
134
+ onEvent: (message) => {
135
+ const event = message as BrokerRemoteEvent;
136
+ if (event.kind === "order") listener.order(event.order);
137
+ else if (event.kind === "orders-live") listener.live?.(event.live);
138
+ },
139
+ // A stream that cannot open leaves the host polling open orders.
140
+ onError: (error) => { listener.live?.(false); console.error("Failed to subscribe to broker orders", error); },
141
+ });
142
+ },
143
+
128
144
  async removeInstance(instanceId) {
129
145
  await invokeBrokerCapability("removeInstance", { instanceId });
130
146
  statuses.delete(instanceId);
@@ -28,6 +28,7 @@ export async function loadDesktopExternalPlugin(bundle: DesktopExternalPluginBun
28
28
  ...(bundle.commit ? { commit: bundle.commit } : {}),
29
29
  ...(bundle.repo ? { repo: bundle.repo } : {}),
30
30
  ...(bundle.linked ? { linked: true } : {}),
31
+ ...(bundle.icon ? { icon: bundle.icon } : {}),
31
32
  };
32
33
  const fallback = {
33
34
  ...base,
@@ -14,8 +14,8 @@ const DRAW_STYLES: readonly SeriesDrawStyle[] = ["line", "area", "step", "column
14
14
  const PLACEMENTS: readonly SeriesPlacement[] = ["price", "panel"];
15
15
  const LINE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
16
16
  const COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
17
- /** A chart holds ten series: the ticker's price and the lines, with one to spare. */
18
- const MAX_SERIES_LINES = 8;
17
+ /** A chart holds twenty series: the ticker's price and up to sixteen lines, with room for a few more. */
18
+ const MAX_SERIES_LINES = 16;
19
19
 
20
20
  /** Units with no scale: a percentage of something, not a quantity of it. */
21
21
  const RATE_UNITS = ["%", "bp", "x", "ratio", "index", "score", "points"] as const;