gloomberb 0.10.2 → 0.10.4

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 (164) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +2 -2
  3. package/package.json +2 -1
  4. package/src/api-client/auth.ts +8 -0
  5. package/src/api-client/index.ts +70 -0
  6. package/src/api-client/types.ts +49 -0
  7. package/src/app/runtime/desktop-deeplink.test.ts +8 -0
  8. package/src/app/runtime/desktop-deeplink.ts +14 -1
  9. package/src/components/chart/composite/composite-chart.test.tsx +340 -17
  10. package/src/components/chart/composite/composite-chart.tsx +859 -68
  11. package/src/components/chart/composite/interactions.test.ts +78 -22
  12. package/src/components/chart/composite/interactions.ts +45 -17
  13. package/src/components/chart/composite/rasterizer.ts +8 -2
  14. package/src/components/chart/composite/renderers.test.ts +61 -1
  15. package/src/components/chart/composite/scene.ts +14 -4
  16. package/src/components/chart/composite/tools.test.ts +371 -0
  17. package/src/components/chart/composite/tools.ts +508 -0
  18. package/src/components/chart/composite/types.ts +4 -0
  19. package/src/components/chart/core/pointer.ts +2 -0
  20. package/src/components/chart/native/raster/primitives.ts +18 -0
  21. package/src/components/chart/price-axis-labels.tsx +42 -2
  22. package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
  23. package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
  24. package/src/components/command-bar/action-runtime.ts +3 -0
  25. package/src/components/command-bar/assist/inventory.test.ts +93 -0
  26. package/src/components/command-bar/assist/inventory.ts +92 -0
  27. package/src/components/command-bar/assist/model.test.ts +104 -0
  28. package/src/components/command-bar/assist/model.ts +169 -0
  29. package/src/components/command-bar/assist/runtime.ts +185 -0
  30. package/src/components/command-bar/commands/direct/index.ts +1 -1
  31. package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
  32. package/src/components/command-bar/list/model.ts +15 -2
  33. package/src/components/command-bar/list/navigation.ts +6 -0
  34. package/src/components/command-bar/list/view.tsx +16 -3
  35. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  36. package/src/components/command-bar/panel/body.tsx +2 -0
  37. package/src/components/command-bar/panel/index.tsx +3 -0
  38. package/src/components/command-bar/panel/runtime.ts +6 -0
  39. package/src/components/command-bar/panel/types.ts +1 -0
  40. package/src/components/command-bar/routes/root/results.ts +28 -2
  41. package/src/components/command-bar/routes/root/runtime.ts +66 -36
  42. package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
  43. package/src/components/command-bar/routing/navigation-state.ts +17 -0
  44. package/src/components/command-bar/selection-runtime.ts +26 -0
  45. package/src/components/command-bar/surface/assist.test.tsx +276 -0
  46. package/src/components/command-bar/surface/index.tsx +93 -2
  47. package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
  48. package/src/components/command-bar/view-model.ts +7 -1
  49. package/src/components/command-bar/workflow/broker.test.ts +22 -0
  50. package/src/components/command-bar/workflow/broker.ts +1 -0
  51. package/src/components/index.ts +1 -1
  52. package/src/components/layout/detached-pane-shell.tsx +40 -2
  53. package/src/components/layout/floating-pane.tsx +4 -1
  54. package/src/components/layout/pane/header.tsx +64 -6
  55. package/src/components/layout/pane/index.tsx +4 -1
  56. package/src/components/layout/shell/index.tsx +23 -0
  57. package/src/components/layout/shell/pane/layers.tsx +5 -0
  58. package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
  59. package/src/components/onboarding/account-step/form-panel.tsx +157 -0
  60. package/src/components/onboarding/account-step/index.tsx +58 -0
  61. package/src/components/onboarding/account-step/model.test.ts +67 -0
  62. package/src/components/onboarding/account-step/model.ts +126 -0
  63. package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
  64. package/src/components/onboarding/onboarding-steps.tsx +91 -55
  65. package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
  66. package/src/components/onboarding/onboarding-wizard.tsx +93 -9
  67. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
  68. package/src/components/onboarding/wizard-account.ts +235 -0
  69. package/src/components/onboarding/wizard-keyboard.ts +67 -1
  70. package/src/components/onboarding/wizard-model.ts +2 -2
  71. package/src/components/ui/data-table/index.tsx +1 -0
  72. package/src/components/ui/data-table/opentui/index.tsx +44 -2
  73. package/src/components/ui/data-table/types.ts +10 -0
  74. package/src/components/ui/data-table/visible-range.test.ts +29 -0
  75. package/src/components/ui/data-table/visible-range.ts +21 -0
  76. package/src/components/ui/ui.test.tsx +23 -1
  77. package/src/i18n/es.ts +1093 -0
  78. package/src/i18n/index.test.ts +5 -2
  79. package/src/i18n/index.ts +3 -0
  80. package/src/i18n/ja.ts +66 -3
  81. package/src/i18n/ko.ts +66 -3
  82. package/src/i18n/languages.test.ts +4 -1
  83. package/src/i18n/languages.ts +12 -1
  84. package/src/i18n/zh-cn.ts +66 -3
  85. package/src/i18n/zh-tw.ts +66 -3
  86. package/src/market-data/coordinator/index.ts +1 -1
  87. package/src/market-data/coordinator/quotes.ts +64 -1
  88. package/src/market-data/coordinator/subscriptions.test.ts +72 -0
  89. package/src/market-data/quotes/cadence.ts +2 -0
  90. package/src/plugins/builtin/account-management/footer.ts +9 -3
  91. package/src/plugins/builtin/account-management/model.ts +53 -1
  92. package/src/plugins/builtin/account-management/pane.tsx +107 -24
  93. package/src/plugins/builtin/ai/index.tsx +7 -2
  94. package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
  95. package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
  96. package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
  97. package/src/plugins/builtin/chart-composer/index.tsx +10 -2
  98. package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
  99. package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
  100. package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
  101. package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
  102. package/src/plugins/builtin/cloud/plugin.tsx +9 -1
  103. package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
  104. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
  105. package/src/plugins/builtin/help/index.tsx +38 -0
  106. package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
  107. package/src/plugins/builtin/options/footer.test.ts +20 -112
  108. package/src/plugins/builtin/options/footer.ts +35 -78
  109. package/src/plugins/builtin/options/index.tsx +6 -0
  110. package/src/plugins/builtin/options/live-quotes.test.ts +117 -14
  111. package/src/plugins/builtin/options/live-quotes.ts +93 -39
  112. package/src/plugins/builtin/options/view.test.tsx +5 -2
  113. package/src/plugins/builtin/options/view.tsx +49 -9
  114. package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
  115. package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
  116. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
  117. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
  118. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
  119. package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
  120. package/src/plugins/builtin/shared/live-streaming.ts +60 -0
  121. package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
  122. package/src/plugins/builtin/shared/plan-access.ts +109 -0
  123. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
  124. package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
  125. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
  126. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
  127. package/src/plugins/registry/index.test.ts +51 -0
  128. package/src/plugins/registry/index.ts +12 -0
  129. package/src/plugins/registry/pane-settings.ts +29 -0
  130. package/src/renderers/electrobun/view/data-table/index.tsx +44 -3
  131. package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
  132. package/src/renderers/electrobun/view/host/box.tsx +3 -0
  133. package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
  134. package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
  135. package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
  136. package/src/renderers/electrobun/view/host/input.tsx +16 -1
  137. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  138. package/src/renderers/electrobun/view/host/style.ts +1 -1
  139. package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
  140. package/src/renderers/electrobun/view/main.tsx +2 -0
  141. package/src/renderers/opentui/chart-surface.tsx +2 -0
  142. package/src/renderers/opentui/host.tsx +3 -0
  143. package/src/sources/gloomberb-cloud/index.test.ts +8 -0
  144. package/src/sources/gloomberb-cloud/index.ts +4 -4
  145. package/src/sources/provider-router/history.test.ts +3 -3
  146. package/src/sources/provider-router/history.ts +15 -4
  147. package/src/sources/yahoo-finance/history.test.ts +47 -0
  148. package/src/sources/yahoo-finance/history.ts +5 -1
  149. package/src/state/hooks/quote-streaming.test.tsx +181 -1
  150. package/src/state/hooks/quote-streaming.ts +93 -7
  151. package/src/theme/colors.ts +20 -1
  152. package/src/theme/themes.test.ts +3 -0
  153. package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
  154. package/src/time-series/resolution.test.ts +51 -0
  155. package/src/time-series/resolution.ts +41 -0
  156. package/src/time-series/resolve.test.ts +207 -1
  157. package/src/time-series/resolve.ts +196 -29
  158. package/src/time-series/spec-studies.test.ts +4 -0
  159. package/src/time-series/spec.ts +15 -1
  160. package/src/time-series/types.ts +7 -0
  161. package/src/time-series/use-chart-resolution.test.tsx +37 -0
  162. package/src/time-series/use-chart-resolution.ts +17 -1
  163. package/src/types/plugin.ts +9 -0
  164. package/src/ui/host.tsx +17 -0
@@ -168,6 +168,10 @@ describe("chart spec normalization and validation", () => {
168
168
  expect(result.valid).toBe(false);
169
169
  expect(result.errors.map(({ code }) => code)).toContain("qoq-annual");
170
170
  expect(result.errors.map(({ code }) => code)).toContain("multiple-ohlc");
171
+ // The blocking series is usually hidden or drawn as a line for lack of OHLC
172
+ // data, so the message has to name it or it reads as a phantom conflict.
173
+ expect(result.errors.find(({ code }) => code === "multiple-ohlc")?.message)
174
+ .toBe("A already uses a candle or OHLC style on main. Give it another style first.");
171
175
  expect(result.errors.map(({ code }) => code)).toContain("missing-input");
172
176
  });
173
177
 
@@ -359,6 +359,7 @@ export function validateChartSpec(spec: ChartSpec): ChartSpecValidationResult {
359
359
 
360
360
  const seriesIds = new Set<string>();
361
361
  const candleCountByPanel = new Map<string, number>();
362
+ const firstCandleByPanel = new Map<string, string>();
362
363
  const unitGroupsByPanel = new Map<string, Set<string>>();
363
364
  const panelScaleById = new Map(spec.panels.map((panel) => [panel.id, panel.scale ?? "linear"] as const));
364
365
  spec.series.forEach((entry, index) => {
@@ -435,6 +436,13 @@ export function validateChartSpec(spec: ChartSpec): ChartSpecValidationResult {
435
436
  if (isOhlcSeriesStyle(entry.style)) {
436
437
  const count = (candleCountByPanel.get(entry.panelId) ?? 0) + 1;
437
438
  candleCountByPanel.set(entry.panelId, count);
439
+ if (count === 1) {
440
+ firstCandleByPanel.set(
441
+ entry.panelId,
442
+ entry.label?.trim()
443
+ || (entry.source.kind === "economic" ? entry.source.seriesId : entry.source.instrument.symbol),
444
+ );
445
+ }
438
446
  if (entry.transform !== "raw") {
439
447
  errors.push(issue(`${path}.transform`, "transformed-ohlc", "OHLC styles require raw values."));
440
448
  }
@@ -442,7 +450,13 @@ export function validateChartSpec(spec: ChartSpec): ChartSpecValidationResult {
442
450
  });
443
451
  for (const [panelId, count] of candleCountByPanel) {
444
452
  if (count > 1) {
445
- errors.push(issue("series", "multiple-ohlc", `Panel ${panelId} may contain only one candle or OHLC series.`));
453
+ // Name the series holding the slot: it is often hidden or drawn as a line
454
+ // for lack of OHLC data, so "this panel already has one" reads as a ghost.
455
+ const panelLabel = spec.panels.find((panel) => panel.id === panelId)?.label ?? panelId;
456
+ const holder = firstCandleByPanel.get(panelId);
457
+ errors.push(issue("series", "multiple-ohlc", holder
458
+ ? `${holder} already uses a candle or OHLC style on ${panelLabel}. Give it another style first.`
459
+ : `Panel ${panelLabel} may contain only one candle or OHLC series.`));
446
460
  }
447
461
  }
448
462
  for (const [panelId, groups] of unitGroupsByPanel) {
@@ -1,4 +1,5 @@
1
1
  import type { ChartResolution, TimeRange } from "./range";
2
+ import type { ChartResolutionSupport } from "./resolution";
2
3
  import type { InstrumentRef } from "../market-data/request-types";
3
4
 
4
5
  export const CHART_SPEC_VERSION = 1 as const;
@@ -129,8 +130,12 @@ export interface ResolvedSeries {
129
130
  interpolation: SeriesInterpolation;
130
131
  /** Present only for exchange-traded market observations. */
131
132
  timeBasis?: ResolvedSeriesMarketTimeBasis;
133
+ /** Regular-session move supplied with the latest market quote. */
134
+ latestChangePercent?: number;
132
135
  points: TimeSeriesPoint[];
133
136
  warning?: string;
137
+ /** Listed in the legend so it can be restored, but not drawn. */
138
+ hidden?: boolean;
134
139
  }
135
140
 
136
141
  export interface TimeSeriesFieldDefinition {
@@ -150,6 +155,8 @@ export interface TimeSeriesFieldDefinition {
150
155
 
151
156
  export interface ChartResolutionResult {
152
157
  series: ResolvedSeries[];
158
+ /** Provider capabilities shared by every active market series. */
159
+ resolutionSupport?: ChartResolutionSupport[];
153
160
  /** Series available to the legend, including hidden base series that can be restored. */
154
161
  legendSeries?: ResolvedSeries[];
155
162
  /** Loaded observations retained outside the visible window for interactive navigation. */
@@ -103,6 +103,14 @@ function ResolutionHarness({
103
103
  return <text>{`${latestResult.loading ? "loading" : "settled"}:${pointCount}`}</text>;
104
104
  }
105
105
 
106
+ function PollingResolutionHarness({ sources }: { sources: ChartResolveSources }) {
107
+ latestResult = useChartResolution(SPEC, sources, {
108
+ liveStreaming: false,
109
+ quotePollingIntervalMs: 30,
110
+ });
111
+ return <text>{latestResult.loading ? "loading" : "settled"}</text>;
112
+ }
113
+
106
114
  async function flushEffects(iterations = 1): Promise<void> {
107
115
  for (let iteration = 0; iteration < iterations; iteration += 1) {
108
116
  await act(async () => {
@@ -134,6 +142,35 @@ afterEach(async () => {
134
142
  });
135
143
 
136
144
  describe("useChartResolution", () => {
145
+ test("polls chart data without opening a quote subscription when live streaming is disabled", async () => {
146
+ let historyCalls = 0;
147
+ let subscribeCalls = 0;
148
+ const provider = createTestDataProvider({
149
+ getTickerFinancials: async () => EMPTY_FINANCIALS,
150
+ getPriceHistoryForResolution: async () => {
151
+ historyCalls += 1;
152
+ return INITIAL_HISTORY;
153
+ },
154
+ subscribeQuotes: () => {
155
+ subscribeCalls += 1;
156
+ return () => {};
157
+ },
158
+ });
159
+ testSetup = await testRender(<PollingResolutionHarness sources={sourcesFor(provider)} />, {
160
+ width: 24,
161
+ height: 1,
162
+ });
163
+
164
+ await waitFor(() => historyCalls >= 2 && latestResult?.loading === false);
165
+ expect(subscribeCalls).toBe(0);
166
+
167
+ const callsAfterImmediatePoll = historyCalls;
168
+ await act(async () => {
169
+ await new Promise((resolve) => setTimeout(resolve, 45));
170
+ });
171
+ await waitFor(() => historyCalls > callsAfterImmediatePoll);
172
+ });
173
+
137
174
  test("keeps renderable data settled through empty adaptive and live refreshes", async () => {
138
175
  const adaptiveHistory = deferred<PricePoint[]>();
139
176
  let detailedCalls = 0;
@@ -19,11 +19,15 @@ export interface UseChartResolutionResult extends ChartResolutionResult {
19
19
 
20
20
  export interface UseChartResolutionOptions {
21
21
  liveRefreshIntervalMs?: number;
22
+ liveStreaming?: boolean;
23
+ quotePollingIntervalMs?: number;
22
24
  autoViewport?: ChartResolveOptions["autoViewport"];
23
25
  requestViewport?: ChartResolveOptions["requestViewport"];
24
26
  targetPointCount?: number;
25
27
  }
26
28
 
29
+ const DEFAULT_QUOTE_POLL_INTERVAL_MS = 60_000;
30
+
27
31
  const EMPTY_RESULT: ChartResolutionResult = {
28
32
  series: [],
29
33
  loading: false,
@@ -150,9 +154,11 @@ export function useChartResolution(
150
154
 
151
155
  const liveTargetSignature = liveChartQuoteTargetSignature(spec);
152
156
  const liveRefreshIntervalMs = options.liveRefreshIntervalMs ?? LIVE_CHART_REFRESH_INTERVAL_MS;
157
+ const liveStreaming = options.liveStreaming !== false;
153
158
  useEffect(() => {
154
159
  const subscriptionGeneration = ++liveSubscriptionGenerationRef.current;
155
160
  liveQuoteOverridesRef.current = new Map();
161
+ if (!liveStreaming) return;
156
162
  const dispose = subscribeToLiveChartQuotes({
157
163
  spec,
158
164
  dataProvider: sources.dataProvider,
@@ -202,7 +208,17 @@ export function useChartResolution(
202
208
  liveQuoteOverridesRef.current = new Map();
203
209
  }
204
210
  };
205
- }, [liveRefreshIntervalMs, liveTargetSignature, sources.dataProvider]);
211
+ }, [liveRefreshIntervalMs, liveStreaming, liveTargetSignature, sources.dataProvider]);
212
+
213
+ const quotePollingIntervalMs = options.quotePollingIntervalMs ?? DEFAULT_QUOTE_POLL_INTERVAL_MS;
214
+ useEffect(() => {
215
+ if (liveStreaming || !liveTargetSignature) return;
216
+ setRevision((current) => current + 1);
217
+ const intervalId = setInterval(() => {
218
+ setRevision((current) => current + 1);
219
+ }, quotePollingIntervalMs);
220
+ return () => clearInterval(intervalId);
221
+ }, [liveStreaming, liveTargetSignature, quotePollingIntervalMs]);
206
222
 
207
223
  return { ...result, reload };
208
224
  }
@@ -53,6 +53,15 @@ export interface PaneDef {
53
53
  defaultFloatingSize?: { width: number; height: number };
54
54
  defaultMode?: "docked" | "floating";
55
55
  settings?: PaneSettingsDef | ((context: PaneSettingsContext) => PaneSettingsDef | null);
56
+ /** Compact controls surfaced next to the pane title. Toggle keys reference toggle fields in settings. */
57
+ quickSettings?: readonly PaneQuickSettingDef[];
58
+ }
59
+
60
+ export interface PaneQuickSettingDef {
61
+ type: "toggle";
62
+ key: string;
63
+ icon: "zap";
64
+ label?: string;
56
65
  }
57
66
 
58
67
  export interface PaneSettingsContext {
package/src/ui/host.tsx CHANGED
@@ -61,6 +61,21 @@ export interface ChartCrosshairOverlay {
61
61
  markers?: readonly { pixelY: number; color: string }[];
62
62
  }
63
63
 
64
+ /**
65
+ * Overlay shapes in plot ratios, drawn without touching the plot raster. The
66
+ * desktop composites them as vectors; the terminal paints them into the bitmap.
67
+ */
68
+ export interface ChartVectorShape {
69
+ id: string;
70
+ points: readonly { x: number; y: number }[];
71
+ color: string;
72
+ /** Closes the shape into a filled box, for range and area selections. */
73
+ box?: boolean;
74
+ fillOpacity?: number;
75
+ strokeWidth?: number;
76
+ handles?: boolean;
77
+ }
78
+
64
79
  interface TextEditBuffer {
65
80
  getText(): string;
66
81
  setText?(text: string): void;
@@ -210,8 +225,10 @@ interface InputProps extends BoxProps {}
210
225
  interface TextareaProps extends BoxProps {}
211
226
  export interface ChartSurfaceProps extends BoxProps {
212
227
  bitmap?: BitmapSurface | null;
228
+ /** Desktop stacks every layer; the terminal renders only the first. */
213
229
  bitmaps?: readonly BitmapSurface[] | null;
214
230
  crosshair?: ChartCrosshairOverlay | null;
231
+ vectors?: readonly ChartVectorShape[] | null;
215
232
  nativeBitmapsEnabled?: boolean;
216
233
  }
217
234
  export interface ImageSurfaceProps extends BoxProps {