gloomberb 0.13.1 → 0.13.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 (92) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/index.test.ts +34 -0
  3. package/src/api-client/request.ts +4 -1
  4. package/src/cli/pane-functions/cloud-session.test.ts +54 -0
  5. package/src/cli/pane-functions/cloud-session.ts +38 -0
  6. package/src/cli/pane-functions/index.ts +5 -1
  7. package/src/cli/pane-functions/screenshot.ts +2 -19
  8. package/src/components/command-bar/assist/runtime.ts +38 -30
  9. package/src/components/command-bar/surface/assist.test.tsx +43 -0
  10. package/src/news/types.ts +1 -0
  11. package/src/plugins/builtin/chat/controller/channels.ts +2 -1
  12. package/src/plugins/builtin/chat/controller/index.ts +13 -5
  13. package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
  14. package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
  15. package/src/plugins/builtin/chat/controller/state.ts +1 -0
  16. package/src/plugins/builtin/chat/controller.test.ts +260 -4
  17. package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
  18. package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
  19. package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
  20. package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
  21. package/src/plugins/builtin/cloud/askg/protocol.test.ts +3 -2
  22. package/src/plugins/builtin/cloud/askg/protocol.ts +16 -4
  23. package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
  24. package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
  25. package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
  26. package/src/plugins/builtin/cloud/plugin.tsx +2 -0
  27. package/src/plugins/builtin/congress-trades/client.ts +11 -0
  28. package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
  29. package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
  30. package/src/plugins/builtin/congress-trades/headless.ts +145 -0
  31. package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
  32. package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
  33. package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
  34. package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
  35. package/src/plugins/builtin/dividend-yield/model.ts +4 -22
  36. package/src/plugins/builtin/dividend-yield/view.ts +17 -0
  37. package/src/plugins/builtin/earnings/headless.test.ts +82 -0
  38. package/src/plugins/builtin/earnings/headless.ts +162 -0
  39. package/src/plugins/builtin/earnings/index.tsx +2 -0
  40. package/src/plugins/builtin/earnings-calls/data.ts +29 -8
  41. package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
  42. package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
  43. package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
  44. package/src/plugins/builtin/earnings-calls/model.ts +238 -0
  45. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +8 -9
  46. package/src/plugins/builtin/holders/client.ts +29 -0
  47. package/src/plugins/builtin/holders/headless.test.ts +58 -0
  48. package/src/plugins/builtin/holders/headless.ts +162 -0
  49. package/src/plugins/builtin/holders/index.tsx +14 -8
  50. package/src/plugins/builtin/holders/pane.tsx +7 -1
  51. package/src/plugins/builtin/insider/client.ts +50 -0
  52. package/src/plugins/builtin/insider/headless.test.ts +122 -0
  53. package/src/plugins/builtin/insider/headless.ts +117 -0
  54. package/src/plugins/builtin/insider/index.tsx +21 -50
  55. package/src/plugins/builtin/insider/insider-data.ts +1 -1
  56. package/src/plugins/builtin/insider/model.ts +72 -0
  57. package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
  58. package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
  59. package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
  60. package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
  61. package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
  62. package/src/plugins/builtin/news/headless.test.ts +138 -0
  63. package/src/plugins/builtin/news/headless.ts +190 -0
  64. package/src/plugins/builtin/news/index.tsx +13 -9
  65. package/src/plugins/builtin/news/wire/index.tsx +2 -1
  66. package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
  67. package/src/plugins/builtin/research/analyst-headless.ts +172 -0
  68. package/src/plugins/builtin/research/analyst-model.ts +219 -0
  69. package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
  70. package/src/plugins/builtin/research/client.ts +12 -0
  71. package/src/plugins/builtin/research/headless.test.ts +93 -0
  72. package/src/plugins/builtin/research/headless.ts +161 -0
  73. package/src/plugins/builtin/research/index.tsx +28 -18
  74. package/src/plugins/builtin/sec/client.ts +16 -0
  75. package/src/plugins/builtin/sec/headless.test.ts +72 -0
  76. package/src/plugins/builtin/sec/headless.ts +69 -0
  77. package/src/plugins/builtin/sec/index.tsx +20 -75
  78. package/src/plugins/builtin/sec/model.ts +89 -0
  79. package/src/plugins/builtin/short-interest/client.ts +5 -2
  80. package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
  81. package/src/plugins/builtin/short-interest/headless.ts +111 -0
  82. package/src/plugins/builtin/short-interest/index.tsx +15 -9
  83. package/src/plugins/builtin/thirteenf/data.ts +13 -11
  84. package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
  85. package/src/plugins/builtin/thirteenf/headless.ts +286 -0
  86. package/src/plugins/builtin/thirteenf/index.tsx +4 -0
  87. package/src/plugins/builtin/thirteenf/model.ts +13 -0
  88. package/src/plugins/builtin/thirteenf/table.ts +3 -15
  89. package/src/plugins/prediction-markets/headless.test.ts +119 -0
  90. package/src/plugins/prediction-markets/headless.ts +255 -0
  91. package/src/plugins/prediction-markets/index.tsx +2 -0
  92. package/src/sources/gloomberb-cloud/news.ts +1 -0
@@ -0,0 +1,162 @@
1
+ import type { EarningsEvent } from "../../../types/data-provider";
2
+ import type {
3
+ HeadlessPaneContext,
4
+ HeadlessPaneDefinition,
5
+ HeadlessPaneLoadArgs,
6
+ HeadlessRowsResult,
7
+ } from "../../../types/plugin";
8
+ import {
9
+ loadEarningsCalendar,
10
+ type EarningsCalendarResult,
11
+ } from "./data/cache";
12
+
13
+ const EARNINGS_COLUMNS = [
14
+ { key: "date", header: "Date" },
15
+ { key: "timing", header: "When" },
16
+ { key: "dateStatus", header: "Status" },
17
+ { key: "symbol", header: "Ticker" },
18
+ { key: "name", header: "Name" },
19
+ { key: "epsEstimate", header: "EPS est", align: "right" as const },
20
+ { key: "epsLow", header: "EPS low", align: "right" as const },
21
+ { key: "epsHigh", header: "EPS high", align: "right" as const },
22
+ { key: "epsGrowth", header: "EPS growth", align: "right" as const },
23
+ { key: "epsTrend30dAgo", header: "EPS 30D ago", align: "right" as const },
24
+ { key: "epsRevisionUp30d", header: "Rev up", align: "right" as const },
25
+ { key: "epsRevisionDown30d", header: "Rev down", align: "right" as const },
26
+ { key: "revenueEstimate", header: "Sales est", align: "right" as const },
27
+ { key: "revenueLow", header: "Sales low", align: "right" as const },
28
+ { key: "revenueHigh", header: "Sales high", align: "right" as const },
29
+ { key: "revenueGrowth", header: "Sales growth", align: "right" as const },
30
+ { key: "epsAnalysts", header: "EPS analysts", align: "right" as const },
31
+ { key: "revenueAnalysts", header: "Sales analysts", align: "right" as const },
32
+ ];
33
+
34
+ export interface EarningsCalendarHeadlessDependencies {
35
+ loadCalendar(
36
+ symbols: string[],
37
+ context: HeadlessPaneContext,
38
+ ): Promise<EarningsCalendarResult>;
39
+ }
40
+
41
+ const defaultDependencies: EarningsCalendarHeadlessDependencies = {
42
+ loadCalendar: (symbols, context) => loadEarningsCalendar(context.marketData, symbols),
43
+ };
44
+
45
+ function timingMatches(event: EarningsEvent, timing: string): boolean {
46
+ if (timing === "all") return true;
47
+ if (timing === "unknown") return !event.timing;
48
+ return event.timing?.toUpperCase() === timing;
49
+ }
50
+
51
+ function eventRow(event: EarningsEvent) {
52
+ return {
53
+ date: event.earningsDate.toISOString(),
54
+ earningsCallDate: event.earningsCallDate?.toISOString() ?? null,
55
+ timing: event.timing || null,
56
+ dateStatus: event.isDateEstimate == null
57
+ ? null
58
+ : event.isDateEstimate
59
+ ? "estimated"
60
+ : "confirmed",
61
+ symbol: event.symbol,
62
+ name: event.name,
63
+ epsEstimate: event.epsEstimate ?? null,
64
+ epsLow: event.epsLow ?? null,
65
+ epsHigh: event.epsHigh ?? null,
66
+ epsYearAgo: event.epsYearAgo ?? null,
67
+ epsGrowth: event.epsGrowth ?? null,
68
+ epsAnalysts: event.epsAnalysts ?? null,
69
+ epsTrend7dAgo: event.epsTrend7dAgo ?? null,
70
+ epsTrend30dAgo: event.epsTrend30dAgo ?? null,
71
+ epsRevisionUp7d: event.epsRevisionUp7d ?? null,
72
+ epsRevisionUp30d: event.epsRevisionUp30d ?? null,
73
+ epsRevisionDown7d: event.epsRevisionDown7d ?? null,
74
+ epsRevisionDown30d: event.epsRevisionDown30d ?? null,
75
+ epsActual: event.epsActual ?? null,
76
+ revenueEstimate: event.revenueEstimate ?? null,
77
+ revenueLow: event.revenueLow ?? null,
78
+ revenueHigh: event.revenueHigh ?? null,
79
+ revenueYearAgo: event.revenueYearAgo ?? null,
80
+ revenueGrowth: event.revenueGrowth ?? null,
81
+ revenueAnalysts: event.revenueAnalysts ?? null,
82
+ revenueActual: event.revenueActual ?? null,
83
+ surprise: event.surprise ?? null,
84
+ };
85
+ }
86
+
87
+ export function projectEarningsCalendarHeadless(
88
+ result: EarningsCalendarResult,
89
+ args: HeadlessPaneLoadArgs,
90
+ ): HeadlessRowsResult {
91
+ const timing = String(args.options.timing ?? "all").toUpperCase();
92
+ const normalizedTiming = timing === "ALL" || timing === "UNKNOWN"
93
+ ? timing.toLowerCase()
94
+ : timing;
95
+ const limit = Number(args.options.limit ?? 50);
96
+ const matching = result.events
97
+ .filter((event) => timingMatches(event, normalizedTiming))
98
+ .sort((left, right) => left.earningsDate.getTime() - right.earningsDate.getTime());
99
+ const rows = matching.slice(0, limit).map(eventRow);
100
+
101
+ return {
102
+ columns: EARNINGS_COLUMNS,
103
+ rows,
104
+ errors: result.refreshError ? [result.refreshError] : undefined,
105
+ metadata: {
106
+ fetchedAt: result.fetchedAt,
107
+ stale: result.stale,
108
+ timing: normalizedTiming,
109
+ total: matching.length,
110
+ returned: rows.length,
111
+ truncated: rows.length < matching.length,
112
+ },
113
+ };
114
+ }
115
+
116
+ export function createEarningsCalendarHeadless(
117
+ dependencies: EarningsCalendarHeadlessDependencies = defaultDependencies,
118
+ ): HeadlessPaneDefinition<"rows"> {
119
+ return {
120
+ shape: "rows",
121
+ argument: {
122
+ kind: "symbol-list",
123
+ placeholder: "tickers",
124
+ description: "Comma-separated company symbols.",
125
+ minimum: 1,
126
+ maximum: 100,
127
+ },
128
+ options: [
129
+ {
130
+ key: "timing",
131
+ description: "Release timing to include.",
132
+ type: "enum",
133
+ values: [
134
+ { value: "all" },
135
+ { value: "BMO" },
136
+ { value: "AMC" },
137
+ { value: "TNS" },
138
+ { value: "unknown" },
139
+ ],
140
+ defaultValue: "all",
141
+ },
142
+ {
143
+ key: "limit",
144
+ description: "Maximum earnings rows to return.",
145
+ type: "integer",
146
+ defaultValue: 50,
147
+ minimum: 1,
148
+ maximum: 200,
149
+ },
150
+ ],
151
+ columns: EARNINGS_COLUMNS,
152
+ describe: (args) => `Earnings Calendar | ${args.symbols.join(", ")}`,
153
+ async load(args, context) {
154
+ return projectEarningsCalendarHeadless(
155
+ await dependencies.loadCalendar(args.symbols, context),
156
+ args,
157
+ );
158
+ },
159
+ };
160
+ }
161
+
162
+ export const earningsCalendarHeadless = createEarningsCalendarHeadless();
@@ -23,6 +23,7 @@ import {
23
23
  type EarningsDisplayRow,
24
24
  type EarningsEventDisplayRow,
25
25
  } from "./model";
26
+ import { earningsCalendarHeadless } from "./headless";
26
27
  import {
27
28
  buildEarningsColumns,
28
29
  renderEarningsCell,
@@ -266,6 +267,7 @@ export const earningsModule: PluginModule = {
266
267
  description: "Upcoming earnings dates and estimates for your tickers.",
267
268
  keywords: ["earn", "earnings", "calendar", "eps", "revenue", "quarterly"],
268
269
  shortcut: { prefix: "ERN" },
270
+ headless: earningsCalendarHeadless,
269
271
  createInstance: (context) => ({
270
272
  settings: context.activeCollectionId
271
273
  ? { collectionId: context.activeCollectionId }
@@ -4,7 +4,12 @@ import {
4
4
  type CloudEarningsTranscriptPayload,
5
5
  } from "../../../api-client";
6
6
  import { ApiRequestError } from "../../../api-client/errors";
7
- import type { PluginPersistence } from "../../../types/plugin";
7
+ import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
8
+
9
+ type EarningsCallsApiClient = Pick<
10
+ HeadlessPaneApiClient,
11
+ "getCloudEarningsCalls" | "getCloudEarningsTranscript"
12
+ >;
8
13
 
9
14
  const LIST_KIND = "calls";
10
15
  const TRANSCRIPT_KIND = "transcript";
@@ -83,7 +88,8 @@ function listKey(ticker: string | null): string {
83
88
  return ticker ? ticker.toUpperCase() : "__all__";
84
89
  }
85
90
 
86
- export async function loadEarningsCalls(
91
+ export async function loadEarningsCallsWithClient(
92
+ client: EarningsCallsApiClient,
87
93
  ticker: string | null,
88
94
  options?: { force?: boolean; limit?: number },
89
95
  ): Promise<EarningsCallsResult> {
@@ -101,7 +107,7 @@ export async function loadEarningsCalls(
101
107
  const active = activeListFetches.get(key);
102
108
  if (active && !force) return active;
103
109
 
104
- const request = apiClient
110
+ const request = client
105
111
  .getCloudEarningsCalls({
106
112
  ticker: ticker ?? undefined,
107
113
  limit: options?.limit ?? 50,
@@ -155,6 +161,13 @@ export async function loadEarningsCalls(
155
161
  return request;
156
162
  }
157
163
 
164
+ export function loadEarningsCalls(
165
+ ticker: string | null,
166
+ options?: { force?: boolean; limit?: number },
167
+ ): Promise<EarningsCallsResult> {
168
+ return loadEarningsCallsWithClient(apiClient, ticker, options);
169
+ }
170
+
158
171
  /**
159
172
  * The server answers 202 with a pending marker when a call is known but not
160
173
  * transcribed yet, having just queued it. That is not a transcript and must
@@ -163,12 +176,13 @@ export async function loadEarningsCalls(
163
176
  export function isPendingTranscript(
164
177
  value: CloudEarningsTranscriptPayload | { status?: string } | null,
165
178
  ): boolean {
166
- if (!value) return false
167
- const record = value as { status?: string; turns?: unknown }
168
- return record.status === "pending" || !Array.isArray(record.turns)
179
+ if (!value) return false;
180
+ const record = value as { status?: string; turns?: unknown };
181
+ return record.status === "pending" || !Array.isArray(record.turns);
169
182
  }
170
183
 
171
- export async function loadTranscript(
184
+ export async function loadTranscriptWithClient(
185
+ client: EarningsCallsApiClient,
172
186
  callId: string,
173
187
  options?: { force?: boolean },
174
188
  ): Promise<CloudEarningsTranscriptPayload> {
@@ -184,7 +198,7 @@ export async function loadTranscript(
184
198
  const active = activeTranscriptFetches.get(callId);
185
199
  if (active && !force) return active;
186
200
 
187
- const request = apiClient
201
+ const request = client
188
202
  .getCloudEarningsTranscript(callId)
189
203
  .then((transcript) => {
190
204
  if (isPendingTranscript(transcript)) return transcript;
@@ -221,3 +235,10 @@ export async function loadTranscript(
221
235
  activeTranscriptFetches.set(callId, request);
222
236
  return request;
223
237
  }
238
+
239
+ export function loadTranscript(
240
+ callId: string,
241
+ options?: { force?: boolean },
242
+ ): Promise<CloudEarningsTranscriptPayload> {
243
+ return loadTranscriptWithClient(apiClient, callId, options);
244
+ }
@@ -0,0 +1,192 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type {
3
+ CloudEarningsCallPayload,
4
+ CloudEarningsTranscriptPayload,
5
+ } from "../../../api-client";
6
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
7
+ import {
8
+ createEarningsCallsHeadless,
9
+ createEarningsTranscriptHeadless,
10
+ type EarningsCallsHeadlessDependencies,
11
+ } from "./headless";
12
+
13
+ const calls: CloudEarningsCallPayload[] = [
14
+ {
15
+ id: "amd-fq2-2026",
16
+ ticker: "AMD",
17
+ companyName: "Advanced Micro Devices",
18
+ fiscalYear: 2026,
19
+ fiscalQuarter: 2,
20
+ callAt: "2026-07-29T21:00:00.000Z",
21
+ status: "published",
22
+ durationSeconds: 3_600,
23
+ wordCount: 12_000,
24
+ hasTranscript: true,
25
+ sentiment: 0.2,
26
+ },
27
+ {
28
+ id: "amd-fq1-2026",
29
+ ticker: "AMD",
30
+ companyName: "Advanced Micro Devices",
31
+ fiscalYear: 2026,
32
+ fiscalQuarter: 1,
33
+ callAt: "2026-04-29T21:00:00.000Z",
34
+ status: "discovered",
35
+ durationSeconds: null,
36
+ wordCount: null,
37
+ hasTranscript: false,
38
+ sentiment: null,
39
+ },
40
+ ];
41
+
42
+ const transcript: CloudEarningsTranscriptPayload = {
43
+ id: "amd-fq2-2026",
44
+ ticker: "AMD",
45
+ companyName: "Advanced Micro Devices",
46
+ fiscalYear: 2026,
47
+ fiscalQuarter: 2,
48
+ callAt: "2026-07-29T21:00:00.000Z",
49
+ timing: "AMC",
50
+ webcastUrl: "https://example.com/call",
51
+ durationSeconds: 3_600,
52
+ status: "published",
53
+ fullText: "",
54
+ turns: [
55
+ {
56
+ speaker: "Jean Hu",
57
+ role: "Chief Financial Officer",
58
+ company: "AMD",
59
+ text: `Gross margin expanded. ${"Margin discipline remained strong. ".repeat(80)}`,
60
+ isQa: false,
61
+ startSeconds: 120,
62
+ },
63
+ {
64
+ speaker: "Analyst One",
65
+ role: "Analyst",
66
+ company: "Research Firm",
67
+ text: "What should investors expect from the CFO on data center margins?",
68
+ isQa: true,
69
+ startSeconds: 2_400,
70
+ },
71
+ ],
72
+ participants: [
73
+ { name: "Jean Hu", role: "Chief Financial Officer", company: "AMD" },
74
+ ],
75
+ summary: "Revenue grew while gross margin expanded.",
76
+ guidance: "Management expects sequential growth.",
77
+ riskFactors: "Supply remains constrained.",
78
+ analystFocus: "Analysts focused on data center margins.",
79
+ notable: "Management raised its annual outlook.",
80
+ sentiment: 0.2,
81
+ sentimentRationale: "Constructive outlook.",
82
+ qaStartTurn: 1,
83
+ wordCount: 12_000,
84
+ asrModel: "test",
85
+ updatedAt: "2026-07-30T00:00:00.000Z",
86
+ };
87
+
88
+ function callArgs(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
89
+ return {
90
+ rawArgument: "",
91
+ argument: null,
92
+ symbols: [],
93
+ options,
94
+ };
95
+ }
96
+
97
+ function transcriptArgs(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
98
+ return {
99
+ rawArgument: "AMD",
100
+ argument: "AMD",
101
+ symbols: ["AMD"],
102
+ options,
103
+ };
104
+ }
105
+
106
+ function dependencies(): EarningsCallsHeadlessDependencies {
107
+ return {
108
+ loadCalls: async () => ({
109
+ calls,
110
+ fetchedAt: 123,
111
+ stale: false,
112
+ }),
113
+ loadTranscript: async () => transcript,
114
+ };
115
+ }
116
+
117
+ const context = {} as HeadlessPaneContext;
118
+
119
+ describe("earnings calls headless", () => {
120
+ test("maps call shelf rows and applies availability", async () => {
121
+ const definition = createEarningsCallsHeadless(dependencies());
122
+
123
+ const result = await definition.load(
124
+ callArgs({ availability: "pending", limit: 1 }),
125
+ context,
126
+ );
127
+
128
+ expect(result.rows).toEqual([
129
+ expect.objectContaining({
130
+ id: "amd-fq1-2026",
131
+ ticker: "AMD",
132
+ status: "queued",
133
+ hasTranscript: false,
134
+ }),
135
+ ]);
136
+ expect(result.metadata).toMatchObject({ availability: "pending", returned: 1 });
137
+ });
138
+ });
139
+
140
+ describe("earnings transcript headless", () => {
141
+ test("selects a quarter and filters speakers by role acronym", async () => {
142
+ const definition = createEarningsTranscriptHeadless(dependencies());
143
+
144
+ const result = await definition.load(transcriptArgs({
145
+ section: "transcript",
146
+ quarter: "FQ2-2026",
147
+ speaker: "CFO",
148
+ search: "margin",
149
+ offset: 0,
150
+ limit: 20,
151
+ }), context);
152
+
153
+ expect(result.rows.length).toBeGreaterThan(0);
154
+ expect(result.rows.every((row) => row.speaker === "Jean Hu")).toBe(true);
155
+ expect(result.metadata).toMatchObject({
156
+ fiscalYear: 2026,
157
+ fiscalQuarter: 2,
158
+ section: "transcript",
159
+ speaker: "CFO",
160
+ });
161
+ });
162
+
163
+ test("pages bounded transcript segments and reports truncation", async () => {
164
+ const definition = createEarningsTranscriptHeadless(dependencies());
165
+ const first = await definition.load(transcriptArgs({
166
+ section: "transcript",
167
+ quarter: "latest",
168
+ speaker: "CFO",
169
+ search: "",
170
+ offset: 0,
171
+ limit: 1,
172
+ }), context);
173
+ const second = await definition.load(transcriptArgs({
174
+ section: "transcript",
175
+ quarter: "latest",
176
+ speaker: "CFO",
177
+ search: "",
178
+ offset: 1,
179
+ limit: 1,
180
+ }), context);
181
+
182
+ expect(first.rows).toHaveLength(1);
183
+ expect(first.metadata).toMatchObject({
184
+ returnedSegments: 1,
185
+ truncated: true,
186
+ hasMore: true,
187
+ nextOffset: 1,
188
+ });
189
+ expect(second.rows).toHaveLength(1);
190
+ expect(second.rows[0]?.index).toBe(1);
191
+ });
192
+ });