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,55 @@
1
+ import type {
2
+ HeadlessPaneDefinition,
3
+ HeadlessPaneLoadArgs,
4
+ } from "../../../types/plugin";
5
+ import {
6
+ loadCreditConditions,
7
+ type CreditConditionsLoadResult,
8
+ type CreditSeriesLoader,
9
+ } from "./client";
10
+
11
+ const COLUMNS = [
12
+ { key: "label", header: "Index" },
13
+ { key: "seriesId", header: "FRED series" },
14
+ { key: "oasBp", header: "OAS", align: "right" as const, format: (value: unknown) => `${Number(value).toFixed(1)}bp` },
15
+ {
16
+ key: "dailyChangeBp",
17
+ header: "1D",
18
+ align: "right" as const,
19
+ format: (value: unknown) => value == null ? "-" : `${Number(value) > 0 ? "+" : ""}${Number(value).toFixed(1)}bp`,
20
+ },
21
+ { key: "date", header: "As of" },
22
+ ];
23
+
24
+ export interface CreditConditionsHeadlessDependencies {
25
+ load(args: HeadlessPaneLoadArgs, loader: CreditSeriesLoader): Promise<CreditConditionsLoadResult>;
26
+ }
27
+
28
+ const defaultDependencies: CreditConditionsHeadlessDependencies = {
29
+ load: (_args, loader) => loadCreditConditions(false, loader),
30
+ };
31
+
32
+ export function createCreditConditionsHeadless(
33
+ dependencies: CreditConditionsHeadlessDependencies = defaultDependencies,
34
+ ): HeadlessPaneDefinition<"rows"> {
35
+ return {
36
+ shape: "rows",
37
+ argument: { kind: "none" },
38
+ options: [],
39
+ columns: COLUMNS,
40
+ describe: "Credit Spreads",
41
+ async load(args, ctx) {
42
+ const result = await dependencies.load(
43
+ args,
44
+ (seriesId, options) => ctx.apiClient.getCloudFredSeries(seriesId, options),
45
+ );
46
+ return {
47
+ rows: result.rows.map((row) => ({ ...row })),
48
+ errors: result.errors,
49
+ metadata: { stale: result.stale },
50
+ };
51
+ },
52
+ };
53
+ }
54
+
55
+ export const creditConditionsHeadless = createCreditConditionsHeadless();
@@ -16,12 +16,15 @@ import type { PaneProps } from "../../../types/plugin";
16
16
  import { colors } from "../../../theme/colors";
17
17
  import type { PluginModule } from "../plugin-module";
18
18
  import { getCachedCreditConditions, loadCreditConditions } from "./client";
19
+ import { creditConditionsHeadless } from "./headless";
19
20
  import {
20
21
  CREDIT_SERIES,
21
22
  type CreditConditionRow,
22
23
  type CreditSeriesId,
23
24
  } from "./model";
24
25
 
26
+ export { creditConditionsHeadless } from "./headless";
27
+
25
28
  type SortId = "label" | "oas" | "change";
26
29
  interface Column extends DataTableColumn { id: SortId }
27
30
 
@@ -204,5 +207,6 @@ export const creditConditionsModule: PluginModule = {
204
207
  description: "ICE BofA US corporate option-adjusted spreads from FRED.",
205
208
  keywords: ["credit", "spread", "oas", "corporate", "high yield", "investment grade", "macro"],
206
209
  shortcut: { prefix: "CRD" },
210
+ headless: creditConditionsHeadless,
207
211
  }],
208
212
  };
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
3
+ import type { DividendData } from "./client";
4
+ import { createDividendYieldHeadless } from "./headless";
5
+
6
+ const fixture: DividendData = {
7
+ price: 200,
8
+ metrics: {
9
+ trailingYield: 0.005,
10
+ forwardYield: 0.0052,
11
+ trailingRate: 1,
12
+ forwardRate: 1.04,
13
+ payoutRatio: 0.14,
14
+ growth1Y: 0.04,
15
+ growth3Y: 0.03,
16
+ paymentFrequency: "quarterly",
17
+ exDividendDate: new Date("2026-08-10T00:00:00.000Z"),
18
+ nextPayDate: new Date("2026-08-17T00:00:00.000Z"),
19
+ },
20
+ payments: [
21
+ {
22
+ exDate: new Date("2026-08-10T00:00:00.000Z"),
23
+ recordDate: null,
24
+ paymentDate: null,
25
+ declarationDate: null,
26
+ amount: 0.26,
27
+ currency: "USD",
28
+ type: "cash",
29
+ },
30
+ {
31
+ exDate: new Date("2026-05-11T00:00:00.000Z"),
32
+ recordDate: null,
33
+ paymentDate: null,
34
+ declarationDate: null,
35
+ amount: 0.24,
36
+ currency: "USD",
37
+ type: "special",
38
+ },
39
+ ],
40
+ };
41
+
42
+ function args(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
43
+ return {
44
+ rawArgument: "AAPL",
45
+ argument: "AAPL",
46
+ symbols: ["AAPL"],
47
+ options,
48
+ };
49
+ }
50
+
51
+ const context = {} as HeadlessPaneContext;
52
+
53
+ describe("dividend yield headless", () => {
54
+ test("maps metrics and payment rows", async () => {
55
+ const definition = createDividendYieldHeadless({
56
+ loadData: async () => fixture,
57
+ });
58
+
59
+ const result = await definition.load(args({ type: "all", limit: 40 }), context);
60
+
61
+ expect(result.sections[0]).toMatchObject({
62
+ title: "Dividend metrics",
63
+ entries: expect.arrayContaining([
64
+ { label: "Trailing yield", value: 0.005 },
65
+ { label: "Frequency", value: "quarterly" },
66
+ ]),
67
+ });
68
+ expect(result.sections[1]).toMatchObject({
69
+ title: "Dividend history",
70
+ rows: [
71
+ expect.objectContaining({ exDate: "2026-08-10", amount: 0.26, type: "cash" }),
72
+ expect.objectContaining({ exDate: "2026-05-11", amount: 0.24, type: "special" }),
73
+ ],
74
+ });
75
+ });
76
+
77
+ test("applies type and limit options", async () => {
78
+ const definition = createDividendYieldHeadless({ loadData: async () => fixture });
79
+
80
+ const result = await definition.load(args({ type: "cash", limit: 1 }), context);
81
+ const history = result.sections[1];
82
+
83
+ expect(history).toMatchObject({
84
+ rows: [expect.objectContaining({ type: "cash" })],
85
+ });
86
+ expect(result.metadata).toMatchObject({ totalPayments: 1, returnedPayments: 1 });
87
+ });
88
+ });
@@ -0,0 +1,135 @@
1
+ import type {
2
+ HeadlessBundleResult,
3
+ HeadlessPaneContext,
4
+ HeadlessPaneDefinition,
5
+ HeadlessPaneLoadArgs,
6
+ } from "../../../types/plugin";
7
+ import { fetchDividendData, type DividendData } from "./client";
8
+ import type { DividendPayment } from "./types";
9
+ import { toDividendRows } from "./view";
10
+
11
+ const PAYMENT_COLUMNS = [
12
+ { key: "exDate", header: "Ex-date" },
13
+ { key: "amount", header: "Amount", align: "right" as const },
14
+ { key: "currency", header: "CCY" },
15
+ { key: "type", header: "Type" },
16
+ ];
17
+
18
+ export interface DividendYieldHeadlessDependencies {
19
+ loadData(symbol: string, context: HeadlessPaneContext): Promise<DividendData>;
20
+ }
21
+
22
+ const defaultDependencies: DividendYieldHeadlessDependencies = {
23
+ async loadData(symbol, context) {
24
+ let currentPrice: number | null = null;
25
+ try {
26
+ currentPrice = (await context.marketData.getQuote(symbol, "")).price ?? null;
27
+ } catch {
28
+ currentPrice = null;
29
+ }
30
+ return fetchDividendData(symbol, currentPrice);
31
+ },
32
+ };
33
+
34
+ function matchesType(payment: DividendPayment, type: string): boolean {
35
+ return type === "all" || payment.type === type;
36
+ }
37
+
38
+ export function projectDividendYieldHeadless(
39
+ data: DividendData,
40
+ args: HeadlessPaneLoadArgs,
41
+ ): HeadlessBundleResult {
42
+ const type = String(args.options.type ?? "all");
43
+ const limit = Number(args.options.limit ?? 40);
44
+ const matching = data.payments.filter((payment) => matchesType(payment, type));
45
+ const selectedPayments = matching.slice(0, limit);
46
+ const rows = toDividendRows(selectedPayments).map((row, index) => {
47
+ const payment = selectedPayments[index];
48
+ return {
49
+ ...row,
50
+ recordDate: payment?.recordDate?.toISOString() ?? null,
51
+ paymentDate: payment?.paymentDate?.toISOString() ?? null,
52
+ declarationDate: payment?.declarationDate?.toISOString() ?? null,
53
+ type: payment?.type ?? null,
54
+ };
55
+ });
56
+ const metrics = data.metrics;
57
+
58
+ return {
59
+ sections: [
60
+ {
61
+ title: "Dividend metrics",
62
+ entries: [
63
+ { label: "Price", value: data.price },
64
+ { label: "Trailing yield", value: metrics.trailingYield },
65
+ { label: "Forward yield", value: metrics.forwardYield },
66
+ { label: "Trailing rate", value: metrics.trailingRate },
67
+ { label: "Forward rate", value: metrics.forwardRate },
68
+ { label: "Payout ratio", value: metrics.payoutRatio },
69
+ { label: "1Y growth", value: metrics.growth1Y },
70
+ { label: "3Y growth", value: metrics.growth3Y },
71
+ { label: "Frequency", value: metrics.paymentFrequency },
72
+ { label: "Ex-dividend", value: metrics.exDividendDate },
73
+ { label: "Next pay", value: metrics.nextPayDate },
74
+ ],
75
+ },
76
+ {
77
+ title: "Dividend history",
78
+ columns: PAYMENT_COLUMNS,
79
+ rows,
80
+ },
81
+ ],
82
+ metadata: {
83
+ totalPayments: matching.length,
84
+ returnedPayments: rows.length,
85
+ truncated: rows.length < matching.length,
86
+ type,
87
+ },
88
+ };
89
+ }
90
+
91
+ export function createDividendYieldHeadless(
92
+ dependencies: DividendYieldHeadlessDependencies = defaultDependencies,
93
+ ): HeadlessPaneDefinition<"bundle"> {
94
+ return {
95
+ shape: "bundle",
96
+ argument: {
97
+ kind: "ticker",
98
+ placeholder: "ticker",
99
+ description: "Company symbol.",
100
+ },
101
+ options: [
102
+ {
103
+ key: "type",
104
+ description: "Dividend payment type to include.",
105
+ type: "enum",
106
+ values: [
107
+ { value: "all" },
108
+ { value: "cash" },
109
+ { value: "special" },
110
+ { value: "stock" },
111
+ { value: "unknown" },
112
+ ],
113
+ defaultValue: "all",
114
+ },
115
+ {
116
+ key: "limit",
117
+ description: "Maximum historical payments to return.",
118
+ type: "integer",
119
+ defaultValue: 40,
120
+ minimum: 1,
121
+ maximum: 200,
122
+ },
123
+ ],
124
+ describe: (args) => `Dividend Yield | ${String(args.argument)}`,
125
+ async load(args, context) {
126
+ const symbol = String(args.argument);
127
+ return projectDividendYieldHeadless(
128
+ await dependencies.loadData(symbol, context),
129
+ args,
130
+ );
131
+ },
132
+ };
133
+ }
134
+
135
+ export const dividendYieldHeadless = createDividendYieldHeadless();
@@ -6,6 +6,7 @@ import {
6
6
  YAHOO_DIVIDENDS_CONNECTION_ID,
7
7
  } from "./client";
8
8
  import { DividendYieldPane } from "./pane";
9
+ import { dividendYieldHeadless } from "./headless";
9
10
 
10
11
  let disposeConnection: (() => void) | null = null;
11
12
 
@@ -50,13 +51,16 @@ export const dividendYieldModule: PluginModule = {
50
51
  ],
51
52
 
52
53
  paneTemplates: [
53
- createTickerSurfacePaneTemplate({
54
- id: "dividend-yield-pane",
55
- paneId: "dividend-yield",
56
- label: "Dividend Yield",
57
- description: "Dividend history, trailing/forward yield, growth rates, and payment schedule.",
58
- keywords: ["dividend", "yield", "dvd", "income", "payout", "ex-date", "distribution"],
59
- shortcut: "DVD",
60
- }),
54
+ {
55
+ ...createTickerSurfacePaneTemplate({
56
+ id: "dividend-yield-pane",
57
+ paneId: "dividend-yield",
58
+ label: "Dividend Yield",
59
+ description: "Dividend history, trailing/forward yield, growth rates, and payment schedule.",
60
+ keywords: ["dividend", "yield", "dvd", "income", "payout", "ex-date", "distribution"],
61
+ shortcut: "DVD",
62
+ }),
63
+ headless: dividendYieldHeadless,
64
+ },
61
65
  ],
62
66
  };
@@ -1,17 +1,13 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
2
  import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
3
- import type { DividendPayment } from "./types";
3
+ import type { DividendRow } from "./view";
4
+
5
+ export { toDividendRows } from "./view";
6
+ export type { DividendRow } from "./view";
4
7
 
5
8
  export type DividendColumnId = "exDate" | "amount" | "currency";
6
9
  export type DividendColumn = DataTableColumn & { id: DividendColumnId };
7
10
 
8
- export interface DividendRow {
9
- key: string;
10
- exDate: string;
11
- amount: number;
12
- currency: string;
13
- }
14
-
15
11
  export interface DividendSortPreference {
16
12
  columnId: DividendColumnId | null;
17
13
  direction: SortDirection;
@@ -67,17 +63,3 @@ export function nextSortPreference(
67
63
  }
68
64
  return DEFAULT_SORT_PREFERENCE;
69
65
  }
70
-
71
- export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
72
- return payments.map((p, i) => ({
73
- key: `${p.exDate.toISOString()}:${i}`,
74
- exDate: formatDate(p.exDate),
75
- amount: p.amount,
76
- currency: p.currency,
77
- }));
78
- }
79
-
80
- function formatDate(date: Date): string {
81
- const iso = date.toISOString();
82
- return iso.slice(0, 10);
83
- }
@@ -0,0 +1,17 @@
1
+ import type { DividendPayment } from "./types";
2
+
3
+ export interface DividendRow {
4
+ key: string;
5
+ exDate: string;
6
+ amount: number;
7
+ currency: string;
8
+ }
9
+
10
+ export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
11
+ return payments.map((payment, index) => ({
12
+ key: `${payment.exDate.toISOString()}:${index}`,
13
+ exDate: payment.exDate.toISOString().slice(0, 10),
14
+ amount: payment.amount,
15
+ currency: payment.currency,
16
+ }));
17
+ }
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { EarningsEvent } from "../../../types/data-provider";
3
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
4
+ import { createEarningsCalendarHeadless } from "./headless";
5
+
6
+ const events: EarningsEvent[] = [
7
+ {
8
+ symbol: "MSFT",
9
+ name: "Microsoft",
10
+ earningsDate: new Date("2026-09-08T20:00:00.000Z"),
11
+ earningsCallDate: new Date("2026-09-08T21:00:00.000Z"),
12
+ isDateEstimate: false,
13
+ epsEstimate: 3.67,
14
+ epsLow: 3.5,
15
+ epsHigh: 3.8,
16
+ epsGrowth: 0.12,
17
+ epsTrend30dAgo: 3.55,
18
+ epsRevisionUp30d: 8,
19
+ epsRevisionDown30d: 2,
20
+ epsActual: null,
21
+ revenueEstimate: 74_000_000_000,
22
+ revenueLow: 72_000_000_000,
23
+ revenueHigh: 76_000_000_000,
24
+ revenueGrowth: 0.16,
25
+ revenueActual: null,
26
+ surprise: null,
27
+ timing: "AMC",
28
+ },
29
+ {
30
+ symbol: "AAPL",
31
+ name: "Apple",
32
+ earningsDate: new Date("2026-09-07T12:00:00.000Z"),
33
+ epsEstimate: 1.5,
34
+ epsActual: null,
35
+ revenueEstimate: 95_000_000_000,
36
+ revenueActual: null,
37
+ surprise: null,
38
+ timing: "BMO",
39
+ },
40
+ ];
41
+
42
+ function args(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
43
+ return {
44
+ rawArgument: "AAPL,MSFT",
45
+ argument: ["AAPL", "MSFT"],
46
+ symbols: ["AAPL", "MSFT"],
47
+ options,
48
+ };
49
+ }
50
+
51
+ const context = {} as HeadlessPaneContext;
52
+
53
+ describe("earnings calendar headless", () => {
54
+ test("maps provider events into date-ordered rows", async () => {
55
+ const definition = createEarningsCalendarHeadless({
56
+ loadCalendar: async () => ({ events, fetchedAt: 123, stale: false }),
57
+ });
58
+
59
+ const result = await definition.load(args({ timing: "all", limit: 50 }), context);
60
+
61
+ expect(result.rows.map((row) => row.symbol)).toEqual(["AAPL", "MSFT"]);
62
+ expect(result.rows[1]).toMatchObject({
63
+ date: "2026-09-08T20:00:00.000Z",
64
+ timing: "AMC",
65
+ dateStatus: "confirmed",
66
+ epsEstimate: 3.67,
67
+ revenueEstimate: 74_000_000_000,
68
+ });
69
+ });
70
+
71
+ test("applies timing and limit options", async () => {
72
+ const definition = createEarningsCalendarHeadless({
73
+ loadCalendar: async () => ({ events, fetchedAt: 123, stale: false }),
74
+ });
75
+
76
+ const result = await definition.load(args({ timing: "AMC", limit: 1 }), context);
77
+
78
+ expect(result.rows).toHaveLength(1);
79
+ expect(result.rows[0]?.symbol).toBe("MSFT");
80
+ expect(result.metadata).toMatchObject({ timing: "AMC", returned: 1 });
81
+ });
82
+ });
@@ -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 }