gloomberb 0.12.0 → 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 (219) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -2
  3. package/src/api-client/auth.ts +2 -1
  4. package/src/api-client/index.test.ts +34 -0
  5. package/src/api-client/index.ts +10 -2
  6. package/src/api-client/request.ts +4 -1
  7. package/src/api-client/research-activity.ts +61 -0
  8. package/src/api-client/types.ts +2 -1
  9. package/src/cli/commands/plugins.ts +4 -3
  10. package/src/cli/desktop-pane-shot.ts +79 -10
  11. package/src/cli/pane-functions/capabilities.ts +76 -23
  12. package/src/cli/pane-functions/cloud-session.test.ts +54 -0
  13. package/src/cli/pane-functions/cloud-session.ts +38 -0
  14. package/src/cli/pane-functions/dom.test.ts +41 -0
  15. package/src/cli/pane-functions/dom.ts +199 -0
  16. package/src/cli/pane-functions/index.test.ts +13 -2
  17. package/src/cli/pane-functions/index.ts +9 -5
  18. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  19. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  20. package/src/cli/pane-functions/report.test.ts +29 -1
  21. package/src/cli/pane-functions/report.ts +68 -7
  22. package/src/cli/pane-functions/screenshot.ts +195 -42
  23. package/src/cli/restore-plugins.ts +10 -3
  24. package/src/components/chart/composite/composite-chart.test.tsx +68 -1
  25. package/src/components/chart/composite/composite-chart.tsx +87 -21
  26. package/src/components/chart/composite/rasterizer.ts +33 -0
  27. package/src/components/chart/composite/renderers.test.ts +51 -4
  28. package/src/components/chart/composite/scene.test.ts +128 -2
  29. package/src/components/chart/composite/scene.ts +92 -16
  30. package/src/components/chart/composite/text-renderer.ts +13 -0
  31. package/src/components/chart/composite/time-scale.ts +30 -10
  32. package/src/components/chart/composite/types.ts +26 -0
  33. package/src/components/command-bar/assist/runtime.ts +38 -30
  34. package/src/components/command-bar/surface/assist.test.tsx +63 -9
  35. package/src/components/command-bar/surface/test-harness.tsx +23 -4
  36. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  37. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  38. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  39. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  40. package/src/data/config/store/index.test.ts +23 -0
  41. package/src/data/config/store/node.ts +7 -1
  42. package/src/data/config/store/normalize.ts +4 -0
  43. package/src/news/types.ts +1 -0
  44. package/src/plugins/builtin/cds/client.test.ts +20 -0
  45. package/src/plugins/builtin/cds/client.ts +13 -3
  46. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  47. package/src/plugins/builtin/cds/headless.ts +113 -0
  48. package/src/plugins/builtin/cds/index.tsx +4 -0
  49. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  50. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  51. package/src/plugins/builtin/chat/controller/channels.ts +2 -1
  52. package/src/plugins/builtin/chat/controller/index.ts +13 -5
  53. package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
  54. package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
  55. package/src/plugins/builtin/chat/controller/state.ts +1 -0
  56. package/src/plugins/builtin/chat/controller.test.ts +260 -4
  57. package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
  58. package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
  59. package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
  60. package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
  61. package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
  62. package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
  63. package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
  64. package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
  65. package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
  66. package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
  67. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  68. package/src/plugins/builtin/cloud/plugin.tsx +2 -0
  69. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  70. package/src/plugins/builtin/congress-trades/client.ts +11 -0
  71. package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
  72. package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
  73. package/src/plugins/builtin/congress-trades/headless.ts +145 -0
  74. package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
  75. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  76. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  77. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  78. package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
  79. package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
  80. package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
  81. package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
  82. package/src/plugins/builtin/dividend-yield/model.ts +4 -22
  83. package/src/plugins/builtin/dividend-yield/view.ts +17 -0
  84. package/src/plugins/builtin/earnings/headless.test.ts +82 -0
  85. package/src/plugins/builtin/earnings/headless.ts +162 -0
  86. package/src/plugins/builtin/earnings/index.tsx +2 -0
  87. package/src/plugins/builtin/earnings-calls/data.ts +29 -8
  88. package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
  89. package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
  90. package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
  91. package/src/plugins/builtin/earnings-calls/model.ts +238 -0
  92. package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
  93. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
  94. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  95. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  96. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  97. package/src/plugins/builtin/holders/client.ts +29 -0
  98. package/src/plugins/builtin/holders/headless.test.ts +58 -0
  99. package/src/plugins/builtin/holders/headless.ts +162 -0
  100. package/src/plugins/builtin/holders/index.tsx +14 -8
  101. package/src/plugins/builtin/holders/pane.tsx +7 -1
  102. package/src/plugins/builtin/insider/client.ts +50 -0
  103. package/src/plugins/builtin/insider/headless.test.ts +122 -0
  104. package/src/plugins/builtin/insider/headless.ts +117 -0
  105. package/src/plugins/builtin/insider/index.tsx +21 -50
  106. package/src/plugins/builtin/insider/insider-data.ts +1 -1
  107. package/src/plugins/builtin/insider/model.ts +72 -0
  108. package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
  109. package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
  110. package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
  111. package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
  112. package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
  113. package/src/plugins/builtin/market-movers/client.ts +76 -0
  114. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  115. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  116. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  117. package/src/plugins/builtin/news/headless.test.ts +138 -0
  118. package/src/plugins/builtin/news/headless.ts +190 -0
  119. package/src/plugins/builtin/news/index.tsx +13 -9
  120. package/src/plugins/builtin/news/wire/index.tsx +2 -1
  121. package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
  122. package/src/plugins/builtin/research/analyst-headless.ts +172 -0
  123. package/src/plugins/builtin/research/analyst-model.ts +219 -0
  124. package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
  125. package/src/plugins/builtin/research/client.ts +12 -0
  126. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  127. package/src/plugins/builtin/research/event-model.ts +245 -0
  128. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  129. package/src/plugins/builtin/research/events-headless.ts +87 -0
  130. package/src/plugins/builtin/research/headless.test.ts +93 -0
  131. package/src/plugins/builtin/research/headless.ts +161 -0
  132. package/src/plugins/builtin/research/index.tsx +32 -18
  133. package/src/plugins/builtin/research-search/pane.tsx +4 -0
  134. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  135. package/src/plugins/builtin/scanner/headless.ts +125 -0
  136. package/src/plugins/builtin/scanner/index.ts +4 -0
  137. package/src/plugins/builtin/sec/client.ts +16 -0
  138. package/src/plugins/builtin/sec/headless.test.ts +72 -0
  139. package/src/plugins/builtin/sec/headless.ts +69 -0
  140. package/src/plugins/builtin/sec/index.tsx +20 -75
  141. package/src/plugins/builtin/sec/model.ts +89 -0
  142. package/src/plugins/builtin/sectors/client.ts +54 -0
  143. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  144. package/src/plugins/builtin/sectors/headless.ts +80 -0
  145. package/src/plugins/builtin/sectors/index.tsx +6 -51
  146. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  147. package/src/plugins/builtin/short-interest/client.ts +5 -2
  148. package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
  149. package/src/plugins/builtin/short-interest/headless.ts +111 -0
  150. package/src/plugins/builtin/short-interest/index.tsx +15 -9
  151. package/src/plugins/builtin/thirteenf/data.ts +13 -11
  152. package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
  153. package/src/plugins/builtin/thirteenf/headless.ts +286 -0
  154. package/src/plugins/builtin/thirteenf/index.tsx +4 -0
  155. package/src/plugins/builtin/thirteenf/model.ts +13 -0
  156. package/src/plugins/builtin/thirteenf/table.ts +3 -15
  157. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  158. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  159. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  160. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  161. package/src/plugins/builtin/volatility/client.ts +16 -3
  162. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  163. package/src/plugins/builtin/volatility/headless.ts +75 -0
  164. package/src/plugins/builtin/volatility/index.tsx +4 -0
  165. package/src/plugins/builtin/world-indices/client.ts +43 -0
  166. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  167. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  168. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  169. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  170. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  171. package/src/plugins/builtin/yield-curve/index.tsx +6 -8
  172. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  173. package/src/plugins/bundle.test.ts +58 -1
  174. package/src/plugins/bundle.ts +4 -2
  175. package/src/plugins/catalog-backend.test.ts +26 -0
  176. package/src/plugins/catalog-backend.ts +9 -2
  177. package/src/plugins/catalog-browser.test.ts +0 -13
  178. package/src/plugins/catalog-browser.ts +4 -0
  179. package/src/plugins/catalog-manifest.test.ts +15 -0
  180. package/src/plugins/catalog-manifest.ts +12 -5
  181. package/src/plugins/catalog-ui.ts +0 -6
  182. package/src/plugins/host-contract.ts +9 -0
  183. package/src/plugins/host-modules.ts +12 -0
  184. package/src/plugins/loader.ts +63 -15
  185. package/src/plugins/prediction-markets/headless.test.ts +119 -0
  186. package/src/plugins/prediction-markets/headless.ts +255 -0
  187. package/src/plugins/prediction-markets/index.tsx +2 -0
  188. package/src/plugins/seed.ts +3 -0
  189. package/src/public/broker.ts +6 -0
  190. package/src/remote/schema.test.ts +65 -0
  191. package/src/remote/schema.ts +322 -39
  192. package/src/remote/types.ts +49 -2
  193. package/src/renderers/browser/app-services.ts +3 -0
  194. package/src/renderers/browser/config-host.ts +24 -8
  195. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  196. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  197. package/src/renderers/browser/main.tsx +3 -1
  198. package/src/renderers/browser/research-entry.test.ts +8 -0
  199. package/src/renderers/browser/research-entry.ts +7 -0
  200. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  201. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  202. package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
  203. package/src/renderers/electrobun/view/build-assets.ts +0 -2
  204. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
  205. package/src/sources/gloomberb-cloud/news.ts +1 -0
  206. package/src/sync/core-contributors.test.ts +111 -0
  207. package/src/sync/core-contributors.ts +155 -1
  208. package/src/time-series/resolve.test.ts +118 -1
  209. package/src/time-series/resolve.ts +14 -16
  210. package/src/types/config.ts +2 -0
  211. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  212. package/src/plugins/broker-sync/native-loader.ts +0 -10
  213. package/src/plugins/broker-sync/normalize.ts +0 -308
  214. package/src/plugins/broker-sync/public.ts +0 -122
  215. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  216. package/src/plugins/broker-sync/robinhood.ts +0 -114
  217. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  218. package/src/plugins/broker-sync/simplefin.ts +0 -70
  219. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -0,0 +1,41 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { ResolvedPaneFunction } from "./resolver";
3
+ import type { PaneScreenshotResult } from "./screenshot";
4
+ import { buildDomPaneReportFromRender } from "./dom";
5
+
6
+ test("marks rendered reports as truncated when a visible cell contains an ellipsis", () => {
7
+ const resolved = {
8
+ token: "INS",
9
+ label: "Insider",
10
+ capability: { id: "insider-pane" },
11
+ options: {},
12
+ } as unknown as ResolvedPaneFunction;
13
+ const screenshot = {
14
+ symbols: ["AVGO"],
15
+ render: {
16
+ visibleText: "NVIDIA Corporati…",
17
+ rows: [{
18
+ tableIndex: 0,
19
+ rowIndex: 0,
20
+ selected: false,
21
+ cells: [{ columnLabel: "Issuer", text: "NVIDIA Corporati…" }],
22
+ }],
23
+ truncated: false,
24
+ truncationReasons: [],
25
+ loadingStateDetected: false,
26
+ errorStateDetected: false,
27
+ errorStateMarkers: [],
28
+ emptyStateDetected: false,
29
+ emptyStateMarkers: [],
30
+ },
31
+ } as unknown as PaneScreenshotResult;
32
+
33
+ const report = buildDomPaneReportFromRender(resolved, screenshot);
34
+ expect(report.data).toMatchObject({
35
+ kind: "rendered-view",
36
+ rowCount: 1,
37
+ truncated: true,
38
+ complete: false,
39
+ limitation: expect.stringContaining("visible"),
40
+ });
41
+ });
@@ -0,0 +1,199 @@
1
+ import type { DesktopPaneShotRenderedRow } from "../desktop-pane-shot";
2
+ import { renderSection, renderTable } from "../../utils/cli-output";
3
+ import type { MarketContext } from "../types";
4
+ import type { PaneFunctionReport } from "./report";
5
+ import type { ResolvedPaneFunction } from "./resolver";
6
+ import { collectShotSymbols } from "./data";
7
+ import { renderDesktopShot, type PaneScreenshotResult } from "./screenshot";
8
+
9
+ const DOM_REPORT_WIDTH = 1280;
10
+ const DOM_REPORT_HEIGHT = 720;
11
+ const DOM_LIMITATION =
12
+ "Only values visible in the rendered pane are returned; clipped or off-screen data may be omitted.";
13
+
14
+ function rowsContainEllipsis(rows: DesktopPaneShotRenderedRow[]): boolean {
15
+ return rows.some((row) => row.cells.some((cell) => /\u2026|\.\.\./.test(cell.text)));
16
+ }
17
+
18
+ export function isDomReportTruncated(
19
+ render: Pick<PaneScreenshotResult["render"], "rows" | "truncated">,
20
+ ): boolean {
21
+ return render.truncated || rowsContainEllipsis(render.rows);
22
+ }
23
+
24
+ function renderDomTables(rows: DesktopPaneShotRenderedRow[]): string[] {
25
+ const byTable = new Map<number, DesktopPaneShotRenderedRow[]>();
26
+ for (const row of rows) {
27
+ const tableRows = byTable.get(row.tableIndex) ?? [];
28
+ tableRows.push(row);
29
+ byTable.set(row.tableIndex, tableRows);
30
+ }
31
+
32
+ return [...byTable.entries()].flatMap(([tableIndex, tableRows], index) => {
33
+ const columnCount = Math.max(0, ...tableRows.map((row) => row.cells.length));
34
+ const columns = Array.from({ length: columnCount }, (_, columnIndex) => ({
35
+ header: tableRows.find((row) => row.cells[columnIndex])?.cells[columnIndex]?.columnLabel
36
+ ?? `Column ${columnIndex + 1}`,
37
+ }));
38
+ const output = renderTable(
39
+ columns,
40
+ tableRows.map((row) => columns.map((_column, columnIndex) => row.cells[columnIndex]?.text ?? "")),
41
+ );
42
+ return [
43
+ ...(index > 0 ? [""] : []),
44
+ ...(byTable.size > 1 ? [renderSection(`Rendered table ${tableIndex + 1}`)] : []),
45
+ output,
46
+ ];
47
+ });
48
+ }
49
+
50
+ function renderedFailureReason(
51
+ result: PaneScreenshotResult,
52
+ rows: DesktopPaneShotRenderedRow[],
53
+ ): string | null {
54
+ if (result.render.loadingStateDetected) return "The rendered pane was still loading.";
55
+ if (result.render.errorStateDetected) {
56
+ const marker = result.render.errorStateMarkers[0];
57
+ return marker ? `The rendered pane reported: ${marker}` : "The rendered pane reported an error.";
58
+ }
59
+ if (result.render.emptyStateDetected) {
60
+ const marker = result.render.emptyStateMarkers[0];
61
+ return marker ? `The rendered pane reported: ${marker}` : "The rendered pane reported an empty state.";
62
+ }
63
+ if (rows.length === 0) return "The rendered pane exposed no structured rows or visible text.";
64
+ return null;
65
+ }
66
+
67
+ function reportRows(result: PaneScreenshotResult): DesktopPaneShotRenderedRow[] {
68
+ if (result.render.rows.length > 0) return result.render.rows;
69
+ if (
70
+ result.render.loadingStateDetected
71
+ || result.render.errorStateDetected
72
+ || result.render.emptyStateDetected
73
+ || !result.render.visibleText
74
+ ) {
75
+ return [];
76
+ }
77
+ return [{
78
+ tableIndex: 0,
79
+ rowIndex: 0,
80
+ selected: false,
81
+ cells: [{ columnLabel: "Rendered view", text: result.render.visibleText }],
82
+ }];
83
+ }
84
+
85
+ export function buildDomPaneReportFromRender(
86
+ resolved: ResolvedPaneFunction,
87
+ result: PaneScreenshotResult,
88
+ ): PaneFunctionReport {
89
+ const rows = reportRows(result);
90
+ const hasStructuredRows = result.render.rows.length > 0;
91
+ const truncated = isDomReportTruncated({ ...result.render, rows });
92
+ const truncationReasons = [...result.render.truncationReasons];
93
+ if (rowsContainEllipsis(rows) && !truncationReasons.includes("one or more cells are visibly clipped")) {
94
+ truncationReasons.push("one or more cells are visibly clipped");
95
+ }
96
+ const failureReason = renderedFailureReason(result, rows);
97
+ const unavailableSymbols = failureReason && result.symbols.length > 0 ? result.symbols : [];
98
+ const textLines = [resolved.label, ""];
99
+ if (hasStructuredRows) {
100
+ textLines.push(...renderDomTables(rows));
101
+ } else if (result.render.visibleText) {
102
+ textLines.push(result.render.visibleText);
103
+ } else {
104
+ textLines.push(failureReason ?? "No rendered values were available.");
105
+ }
106
+ if (failureReason && rows.length > 0) textLines.push("", failureReason);
107
+
108
+ return {
109
+ data: {
110
+ kind: "rendered-view",
111
+ target: resolved.token,
112
+ capabilityId: resolved.capability.id,
113
+ symbols: result.symbols,
114
+ options: resolved.options,
115
+ rowCount: rows.length,
116
+ empty: rows.length === 0,
117
+ complete: failureReason === null && !truncated,
118
+ unavailableSymbols,
119
+ rows,
120
+ visibleText: result.render.visibleText,
121
+ truncated,
122
+ truncationReasons,
123
+ limitation: DOM_LIMITATION,
124
+ ...(failureReason ? { reason: failureReason } : {}),
125
+ },
126
+ text: textLines.join("\n").trimEnd(),
127
+ };
128
+ }
129
+
130
+ export function appendDomReportFooter(
131
+ text: string,
132
+ elapsedMs: number,
133
+ truncated: boolean,
134
+ ): string {
135
+ const clipping = truncated ? "The rendered view is clipped." : "The rendered view may be clipped.";
136
+ return [
137
+ text,
138
+ "",
139
+ `Rendered view: values come from the visible pane. ${clipping} Render time: ${elapsedMs} ms.`,
140
+ ].join("\n");
141
+ }
142
+
143
+ function failedDomReport(
144
+ resolved: ResolvedPaneFunction,
145
+ rawArg: string,
146
+ error: unknown,
147
+ ): PaneFunctionReport {
148
+ const message = (error instanceof Error ? error.message : String(error))
149
+ .split("\n")[0]!
150
+ .replace(/^Error:\s*/, "")
151
+ .trim();
152
+ const reason = `The rendered view could not be read: ${message || "unknown renderer error"}`;
153
+ const symbols = collectShotSymbols(resolved, rawArg);
154
+ return {
155
+ data: {
156
+ kind: "rendered-view",
157
+ target: resolved.token,
158
+ capabilityId: resolved.capability.id,
159
+ symbols,
160
+ options: resolved.options,
161
+ rowCount: 0,
162
+ empty: true,
163
+ complete: false,
164
+ unavailableSymbols: symbols,
165
+ rows: [],
166
+ visibleText: "",
167
+ truncated: false,
168
+ truncationReasons: [],
169
+ limitation: DOM_LIMITATION,
170
+ reason,
171
+ },
172
+ text: [resolved.label, "", reason].join("\n"),
173
+ };
174
+ }
175
+
176
+ export async function buildDomFunctionReport(
177
+ resolved: ResolvedPaneFunction,
178
+ context: MarketContext,
179
+ rawArg: string,
180
+ ): Promise<PaneFunctionReport> {
181
+ try {
182
+ const result = await renderDesktopShot({
183
+ resolved,
184
+ context,
185
+ rawArg,
186
+ outputPath: "",
187
+ width: DOM_REPORT_WIDTH,
188
+ height: DOM_REPORT_HEIGHT,
189
+ theme: null,
190
+ scale: 1,
191
+ watermark: null,
192
+ options: {},
193
+ captureImage: false,
194
+ });
195
+ return buildDomPaneReportFromRender(resolved, result);
196
+ } catch (error) {
197
+ return failedDomReport(resolved, rawArg, error);
198
+ }
199
+ }
@@ -10,6 +10,7 @@ const {
10
10
  filterPaneCatalogEntries,
11
11
  renderPaneCatalogReport,
12
12
  getPaneFunctionCapability,
13
+ isDataPaneForDomFallback,
13
14
  normalizeCapabilityOptions,
14
15
  capabilityPluginState,
15
16
  } = paneFunctionTestInternals;
@@ -208,13 +209,23 @@ describe("pane function CLI args", () => {
208
209
  });
209
210
  });
210
211
 
211
- test("enables live DOM screenshots without claiming report support for unmapped panes", () => {
212
+ test("maps data panes to rendered reports and keeps interactive panes unsupported", () => {
212
213
  expect(capabilityFor("new-api-pane")).toMatchObject({
213
- id: "unverified-pane",
214
+ id: "new-api-pane",
214
215
  botSafe: false,
216
+ outputKind: "rendered-view",
217
+ reportReadiness: "live-dom",
218
+ screenshotReadiness: "live-dom",
219
+ });
220
+
221
+ const helpPane = { ...dummyPane, id: "help" };
222
+ expect(getPaneFunctionCapability(undefined, helpPane)).toMatchObject({
223
+ id: "help",
215
224
  reportReadiness: "unsupported",
216
225
  screenshotReadiness: "live-dom",
217
226
  });
227
+ expect(isDataPaneForDomFallback(dummyPane)).toBe(true);
228
+ expect(isDataPaneForDomFallback(helpPane)).toBe(false);
218
229
  });
219
230
 
220
231
  test("rejects financial statement options on a price comparison", () => {
@@ -25,8 +25,10 @@ import {
25
25
  import {
26
26
  capabilityPluginState,
27
27
  getPaneFunctionCapability,
28
+ isDataPaneForDomFallback,
28
29
  normalizeCapabilityOptions,
29
30
  } from "./capabilities";
31
+ import { withPersistedCloudSession } from "./cloud-session";
30
32
 
31
33
  async function withPaneRuntime<T>(
32
34
  ctx: CliCommandContext,
@@ -62,7 +64,10 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
62
64
  + `Use "gloomberb catalog ${resolved.token}" to inspect readiness.`,
63
65
  );
64
66
  }
65
- const report = await buildFunctionReport(resolved, context, parsed.arg);
67
+ const report = await withPersistedCloudSession(
68
+ context,
69
+ () => buildFunctionReport(resolved, context, parsed.arg),
70
+ );
66
71
  if (parsed.requireBotSafe && (report.data.empty || !report.data.complete)) {
67
72
  const unavailable = report.data.unavailableSymbols.length > 0
68
73
  ? ` Missing data for ${report.data.unavailableSymbols.join(", ")}.`
@@ -106,16 +111,14 @@ export async function runPaneScreenshot(args: string[], ctx: CliCommandContext)
106
111
  });
107
112
  if (parsed.requireBotSafe && !result.usable) {
108
113
  throw new Error(
109
- `${resolved.token} did not produce a usable bot-safe screenshot: `
110
- + `${result.empty ? "empty render" : ""}${result.empty && !result.complete ? ", " : ""}`
111
- + `${!result.complete ? `missing data for ${result.unavailableSymbols.join(", ")}` : ""}`
112
- + `${result.semanticMismatch ? `${(result.empty || !result.complete) ? ", " : ""}rendered content did not match the requested capability` : ""}`,
114
+ `${resolved.token} did not produce a usable bot-safe screenshot: ${result.unusableReason ?? "unknown reason"}`,
113
115
  );
114
116
  }
115
117
  ctx.printResult({ data: result }, {
116
118
  text: (data) => [
117
119
  `Saved screenshot to ${data.outputPath}`,
118
120
  `Result: ${data.rowCount} semantic rows; empty=${data.empty}; complete=${data.complete}; mismatch=${data.semanticMismatch}; usable=${data.usable}`,
121
+ ...(data.unusableReason ? [`Unusable reason: ${data.unusableReason}`] : []),
119
122
  ...(data.unavailableSymbols.length > 0
120
123
  ? [`Unavailable symbols: ${data.unavailableSymbols.join(", ")}`]
121
124
  : []),
@@ -180,6 +183,7 @@ export const paneFunctionTestInternals = {
180
183
  filterPaneCatalogEntries,
181
184
  renderPaneCatalogReport,
182
185
  getPaneFunctionCapability,
186
+ isDataPaneForDomFallback,
183
187
  normalizeCapabilityOptions,
184
188
  capabilityPluginState,
185
189
  };
@@ -0,0 +1,307 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { CHART_COMPOSER_PANE_ID, createDefaultConfig } from "../../types/config";
3
+ import type { DataProvider } from "../../types/data-provider";
4
+ import type { PricePoint, TickerFinancials } from "../../types/financials";
5
+ import {
6
+ buildIntradayPriceChartPreset,
7
+ buildPriceChartPreset,
8
+ } from "../../plugins/builtin/chart-composer/presets";
9
+ import type { MarketContext } from "../types";
10
+ import {
11
+ buildDesktopShotPayload,
12
+ intradayChartEvidenceMismatchesFor,
13
+ shotUnusableReasonFor,
14
+ } from "./screenshot";
15
+ import type { ResolvedPaneFunction } from "./resolver";
16
+ import {
17
+ resolveShotIntradaySessionWindow,
18
+ resolveShotIntradayRequest,
19
+ } from "./intraday-shot";
20
+
21
+ function sessionBars(date: string, base: number): PricePoint[] {
22
+ return [
23
+ {
24
+ date: new Date(`${date}T13:30:00.000Z`),
25
+ open: base,
26
+ high: base + 1,
27
+ low: base - 1,
28
+ close: base + 0.25,
29
+ volume: 100,
30
+ },
31
+ {
32
+ date: new Date(`${date}T13:35:00.000Z`),
33
+ open: base + 0.25,
34
+ high: base + 1.25,
35
+ low: base,
36
+ close: base + 0.5,
37
+ volume: 200,
38
+ },
39
+ ];
40
+ }
41
+
42
+ function financials(priceHistory: PricePoint[] = []): TickerFinancials {
43
+ return {
44
+ quote: {
45
+ symbol: "AAPL",
46
+ price: 108,
47
+ currency: "USD",
48
+ change: 1,
49
+ changePercent: 1,
50
+ lastUpdated: Date.parse("2026-09-03T20:00:00.000Z"),
51
+ listingExchangeName: "NASDAQ",
52
+ },
53
+ annualStatements: [],
54
+ quarterlyStatements: [],
55
+ priceHistory,
56
+ };
57
+ }
58
+
59
+ function contextWithProvider(provider: DataProvider): MarketContext {
60
+ return {
61
+ config: createDefaultConfig("/tmp/gloomberb-shot-test"),
62
+ dataDir: "/tmp/gloomberb-shot-test",
63
+ persistence: {
64
+ pluginState: { get: () => null },
65
+ },
66
+ store: {
67
+ loadTicker: async () => ({
68
+ metadata: {
69
+ ticker: "AAPL",
70
+ exchange: "NASDAQ",
71
+ currency: "USD",
72
+ name: "Apple",
73
+ portfolios: [],
74
+ watchlists: [],
75
+ positions: [],
76
+ custom: {},
77
+ tags: [],
78
+ },
79
+ }),
80
+ },
81
+ dataProvider: provider,
82
+ } as unknown as MarketContext;
83
+ }
84
+
85
+ function resolvedChart(
86
+ capabilityId: "intraday-price-chart" | "price-chart",
87
+ spec: ReturnType<typeof buildIntradayPriceChartPreset>,
88
+ options: Record<string, string>,
89
+ ): ResolvedPaneFunction {
90
+ return {
91
+ pane: { id: CHART_COMPOSER_PANE_ID },
92
+ capability: { id: capabilityId, options: [] },
93
+ options,
94
+ instance: {
95
+ instanceId: `${capabilityId}:test`,
96
+ paneId: CHART_COMPOSER_PANE_ID,
97
+ title: "AAPL",
98
+ binding: { kind: "fixed", symbol: "AAPL" },
99
+ settings: { chartSpec: spec },
100
+ },
101
+ createOptions: { symbol: "AAPL" },
102
+ } as unknown as ResolvedPaneFunction;
103
+ }
104
+
105
+ async function payloadFor(
106
+ resolved: ResolvedPaneFunction,
107
+ provider: DataProvider,
108
+ ) {
109
+ return buildDesktopShotPayload(
110
+ resolved,
111
+ contextWithProvider(provider),
112
+ "AAPL",
113
+ {},
114
+ 1200,
115
+ 675,
116
+ "tokyo",
117
+ 1.5,
118
+ null,
119
+ );
120
+ }
121
+
122
+ describe("GIP session windows", () => {
123
+ const history = [
124
+ ...sessionBars("2026-08-27", 100),
125
+ ...sessionBars("2026-08-28", 101),
126
+ ...sessionBars("2026-08-31", 102),
127
+ ...sessionBars("2026-09-01", 103),
128
+ ...sessionBars("2026-09-02", 104),
129
+ ...sessionBars("2026-09-03", 105),
130
+ ];
131
+
132
+ test("resolves the latest session, latest five sessions, and an explicit session", () => {
133
+ const latest = resolveShotIntradaySessionWindow(history, {
134
+ rangePreset: "1D",
135
+ timeZone: "America/New_York",
136
+ });
137
+ expect(latest.sessionDates).toEqual(["2026-09-03"]);
138
+ expect(latest.points).toHaveLength(2);
139
+
140
+ const week = resolveShotIntradaySessionWindow(history, {
141
+ rangePreset: "1W",
142
+ timeZone: "America/New_York",
143
+ });
144
+ expect(week.sessionDates).toEqual([
145
+ "2026-08-28",
146
+ "2026-08-31",
147
+ "2026-09-01",
148
+ "2026-09-02",
149
+ "2026-09-03",
150
+ ]);
151
+ expect(week.points).toHaveLength(10);
152
+
153
+ const explicit = resolveShotIntradaySessionWindow(history, {
154
+ rangePreset: "1W",
155
+ session: "2026-09-01",
156
+ timeZone: "America/New_York",
157
+ });
158
+ expect(explicit.sessionDates).toEqual(["2026-09-01"]);
159
+ expect(explicit.points.map((point) => point.close)).toEqual([103.25, 103.5]);
160
+ });
161
+
162
+ test("maps Auto to the preset interval and validates explicit dates", () => {
163
+ expect(resolveShotIntradayRequest({ rangePreset: "1D", chartResolution: "auto" }))
164
+ .toEqual({ rangePreset: "1D", resolution: "1m", session: null });
165
+ expect(resolveShotIntradayRequest({ rangePreset: "1W", chartResolution: "auto" }))
166
+ .toEqual({ rangePreset: "1W", resolution: "5m", session: null });
167
+ expect(resolveShotIntradayRequest({
168
+ rangePreset: "1W",
169
+ chartResolution: "15m",
170
+ session: "2026-09-02",
171
+ })).toEqual({ rangePreset: "1D", resolution: "15m", session: "2026-09-02" });
172
+ expect(() => resolveShotIntradayRequest({ session: "2026-02-30" }))
173
+ .toThrow("real calendar date");
174
+ });
175
+ });
176
+
177
+ describe("GIP screenshot payload", () => {
178
+ test("injects resolution-aware intraday bars for GIP", async () => {
179
+ const calls: Array<{ range: string; resolution: string }> = [];
180
+ const history = [
181
+ ...sessionBars("2026-09-02", 100),
182
+ ...sessionBars("2026-09-03", 105),
183
+ ];
184
+ const provider = {
185
+ id: "test",
186
+ name: "Test",
187
+ getTickerFinancials: async () => financials([
188
+ { date: new Date("2026-09-03T00:00:00.000Z"), close: 105 },
189
+ ]),
190
+ getPriceHistory: async () => [],
191
+ getPriceHistoryForResolution: async (_symbol, _exchange, range, resolution) => {
192
+ calls.push({ range, resolution });
193
+ return history;
194
+ },
195
+ getDetailedPriceHistory: async () => [],
196
+ } as DataProvider;
197
+ const spec = buildIntradayPriceChartPreset("AAPL");
198
+ spec.viewport.resolution = "auto";
199
+ const payload = await payloadFor(
200
+ resolvedChart("intraday-price-chart", spec, {
201
+ rangePreset: "1D",
202
+ chartResolution: "auto",
203
+ }),
204
+ provider,
205
+ );
206
+
207
+ expect(calls).toEqual([{ range: "1W", resolution: "1m" }]);
208
+ expect(payload.intradayHistories).toHaveLength(1);
209
+ expect(payload.intradayHistories[0]).toMatchObject({
210
+ symbol: "AAPL",
211
+ exchange: "NASDAQ",
212
+ rangePreset: "1D",
213
+ resolution: "1m",
214
+ requestedSession: null,
215
+ sessionDates: ["2026-09-03"],
216
+ unavailableReason: null,
217
+ });
218
+ expect(payload.intradayHistories[0]?.points).toHaveLength(2);
219
+ expect(payload.financials[0]?.[1].priceHistory).toHaveLength(2);
220
+
221
+ const renderedEvidence = [{
222
+ role: "chart-data",
223
+ metadata: {
224
+ kind: "chart-composer",
225
+ projectedPointCount: 2,
226
+ baseSeries: [{
227
+ pointCount: 2,
228
+ first: { date: "2026-09-03T13:30:00.000Z", value: 105.25 },
229
+ last: { date: "2026-09-03T13:35:00.000Z", value: 105.5 },
230
+ }],
231
+ },
232
+ }];
233
+ const resolved = resolvedChart("intraday-price-chart", spec, {
234
+ rangePreset: "1D",
235
+ chartResolution: "auto",
236
+ });
237
+ expect(intradayChartEvidenceMismatchesFor(resolved, payload, renderedEvidence as never))
238
+ .toEqual([]);
239
+ renderedEvidence[0]!.metadata.baseSeries[0]!.pointCount = 1;
240
+ expect(intradayChartEvidenceMismatchesFor(resolved, payload, renderedEvidence as never))
241
+ .toContain("rendered intraday point count does not match");
242
+ });
243
+
244
+ test("keeps GP on daily history without an intraday payload", async () => {
245
+ let intradayCalls = 0;
246
+ const daily = [
247
+ { date: new Date("2026-09-02T00:00:00.000Z"), close: 100 },
248
+ { date: new Date("2026-09-03T00:00:00.000Z"), close: 105 },
249
+ ];
250
+ const provider = {
251
+ id: "test",
252
+ name: "Test",
253
+ getTickerFinancials: async () => financials(),
254
+ getPriceHistory: async () => daily,
255
+ getPriceHistoryForResolution: async () => {
256
+ intradayCalls += 1;
257
+ return [];
258
+ },
259
+ } as DataProvider;
260
+ const payload = await payloadFor(
261
+ resolvedChart("price-chart", buildPriceChartPreset("AAPL"), { rangePreset: "3M" }),
262
+ provider,
263
+ );
264
+
265
+ expect(intradayCalls).toBe(0);
266
+ expect(payload.intradayHistories).toEqual([]);
267
+ expect(payload.financials[0]?.[1].priceHistory).toEqual(daily);
268
+ });
269
+
270
+ test("records an explicit unusable reason when no intraday bars exist", async () => {
271
+ const provider = {
272
+ id: "test",
273
+ name: "Test",
274
+ getTickerFinancials: async () => financials([
275
+ { date: new Date("2026-09-03T00:00:00.000Z"), close: 105 },
276
+ ]),
277
+ getPriceHistory: async () => [],
278
+ getPriceHistoryForResolution: async () => [],
279
+ getDetailedPriceHistory: async () => [],
280
+ } as DataProvider;
281
+ const spec = buildIntradayPriceChartPreset("AAPL");
282
+ spec.viewport.resolution = "auto";
283
+ const resolved = resolvedChart("intraday-price-chart", spec, {
284
+ rangePreset: "1D",
285
+ chartResolution: "auto",
286
+ session: "2020-01-02",
287
+ });
288
+ const payload = await payloadFor(resolved, provider);
289
+
290
+ expect(payload.intradayHistories[0]?.points).toEqual([]);
291
+ expect(payload.intradayHistories[0]?.start).toBe("2020-01-02T05:00:00.000Z");
292
+ expect(payload.intradayHistories[0]?.end).toBe("2020-01-03T05:00:00.000Z");
293
+ expect(payload.intradayHistories[0]?.unavailableReason)
294
+ .toBe("No intraday price history is available for AAPL for session 2020-01-02.");
295
+ expect(shotUnusableReasonFor(
296
+ resolved,
297
+ payload,
298
+ {
299
+ loadingStateDetected: false,
300
+ errorStateDetected: false,
301
+ emptyStateDetected: true,
302
+ },
303
+ ["AAPL"],
304
+ false,
305
+ )).toBe("No intraday price history is available for AAPL for session 2020-01-02.");
306
+ });
307
+ });