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
@@ -19,8 +19,8 @@ export function AccountChooserPanel({
19
19
  const choices = useMemo<ListViewItem[]>(() => [
20
20
  {
21
21
  id: "qr",
22
- label: t("Scan QR with the mobile app"),
23
- description: t("Approve from your phone, no typing"),
22
+ label: t("Continue in browser"),
23
+ description: t("Sign in securely, or scan with your phone"),
24
24
  },
25
25
  {
26
26
  id: "signup",
@@ -1,7 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- import { Box, Text, TextAttributes } from "../../../ui";
3
- import { colors } from "../../../theme/colors";
4
- import { t } from "../../../i18n";
2
+ import { Box } from "../../../ui";
5
3
  import { useAppLanguage } from "../../../i18n/react";
6
4
  import { useShortcut } from "../../../react/input";
7
5
  import { isPlainKey } from "../../../utils/keyboard";
@@ -56,13 +54,7 @@ export function AccountQrPanel({
56
54
 
57
55
  return (
58
56
  <Box flexDirection="column" paddingX={2}>
59
- <Box height={1}>
60
- <Text fg={colors.textBright} attributes={TextAttributes.BOLD}>
61
- {t("Scan with the Gloom app to sign in")}
62
- </Text>
63
- </Box>
64
- <Box height={1} />
65
- <DeviceSignInPanel snapshot={snapshot} height={Math.max(4, height - 2)} />
57
+ <DeviceSignInPanel snapshot={snapshot} height={height} />
66
58
  </Box>
67
59
  );
68
60
  }
@@ -63,6 +63,7 @@ function createPluginRegistry(options: {
63
63
  tickerRepository: options.tickerRepository ?? createTickerRepository(),
64
64
  persistence: { resources: undefined },
65
65
  openCommandBar: () => {},
66
+ navigateTicker: () => {},
66
67
  } as unknown as PluginRegistry;
67
68
  }
68
69
 
@@ -265,7 +266,7 @@ describe("OnboardingWizard", () => {
265
266
  expect(capturedConfig?.onboardingProgress?.stage).toBe("add-ticker");
266
267
  });
267
268
 
268
- test("advances directly to Cloud after the expected AP save", async () => {
269
+ test("opens research before Cloud after the expected AP save", async () => {
269
270
  tempDataDir = await mkdtemp(join(tmpdir(), "gloomberb-onboarding-actions-"));
270
271
  const pluginRegistry = createPluginRegistry();
271
272
  const config = {
@@ -300,12 +301,12 @@ describe("OnboardingWizard", () => {
300
301
  await Bun.sleep(0);
301
302
  await testSetup!.renderOnce();
302
303
  });
303
- const frame = await waitForFrame("Sign up free");
304
- expect(frame).toContain("Sign up free");
305
- expect(capturedConfig?.onboardingProgress?.stage).toBe("account");
304
+ const frame = await waitForFrame("Connect free Cloud");
305
+ expect(frame).toContain("Connect free Cloud");
306
+ expect(capturedConfig?.onboardingProgress?.stage).toBe("research");
306
307
  });
307
308
 
308
- test("imports a broker portfolio before moving to Cloud", async () => {
309
+ test("imports a broker portfolio before opening research", async () => {
309
310
  tempDataDir = await mkdtemp(join(tmpdir(), "gloomberb-onboarding-broker-"));
310
311
  const tickerRepository = createTickerRepository();
311
312
  const broker: BrokerAdapter = {
@@ -348,10 +349,10 @@ describe("OnboardingWizard", () => {
348
349
  await waitForFrame("Connect Demo Broker");
349
350
  await emitKeypress({ name: "return", sequence: "\r" });
350
351
 
351
- const frame = await waitForFrame("Sign up free");
352
- expect(frame).toContain("Sign up free");
352
+ const frame = await waitForFrame("Connect free Cloud");
353
+ expect(frame).toContain("Connect free Cloud");
353
354
  expect(capturedConfig?.onboardingProgress).toMatchObject({
354
- stage: "account",
355
+ stage: "research",
355
356
  path: "broker",
356
357
  tickerSymbol: "AAPL",
357
358
  brokerName: "Demo Broker",
@@ -511,9 +512,9 @@ describe("OnboardingWizard", () => {
511
512
  await testSetup!.renderOnce();
512
513
  });
513
514
 
514
- await waitForFrame("Sign up free");
515
+ await waitForFrame("Connect free Cloud");
515
516
  expect((await tickerRepository.loadAllTickers()).map((ticker) => ticker.metadata.ticker).sort()).toEqual(["AAPL", "MSFT"]);
516
- expect(capturedConfig?.onboardingProgress?.stage).toBe("account");
517
+ expect(capturedConfig?.onboardingProgress?.stage).toBe("research");
517
518
  });
518
519
 
519
520
  test("keeps Skip locked until broker onboarding finalization completes", async () => {
@@ -590,9 +591,34 @@ describe("OnboardingWizard", () => {
590
591
  await testSetup!.renderOnce();
591
592
  });
592
593
 
593
- await waitForFrame("Sign up free");
594
+ await waitForFrame("Connect free Cloud");
594
595
  expect(completionCount).toBe(0);
595
- expect(capturedConfig?.onboardingProgress?.stage).toBe("account");
596
+ expect(capturedConfig?.onboardingProgress?.stage).toBe("research");
597
+ });
598
+
599
+ test("waits for email verification before offering Pro", async () => {
600
+ tempDataDir = await mkdtemp(join(tmpdir(), "gloomberb-onboarding-verify-"));
601
+ const originalGetSession = apiClient.getSession;
602
+ let resolveSession!: (user: Awaited<ReturnType<typeof apiClient.getSession>>) => void;
603
+ apiClient.getSession = () => new Promise((resolve) => { resolveSession = resolve; });
604
+ try {
605
+ testSetup = await testRender(<WizardHarness config={{
606
+ ...createDefaultConfig(tempDataDir),
607
+ onboardingProgress: { version: 1, stage: "verify", accountStatus: "signed-in" },
608
+ }} pluginRegistry={createPluginRegistry()} />, { width: 100, height: 30 });
609
+ await testSetup.renderOnce();
610
+ expect(testSetup.captureCharFrame()).toContain("Check your email");
611
+ expect(capturedConfig?.onboardingProgress?.stage).toBe("verify");
612
+ apiClient.setSessionToken("verification-test-session");
613
+ apiClient.restoreCachedUser({ id: "verified-user", email: "research@example.com", emailVerified: true, plan: "free" });
614
+ await act(async () => {
615
+ resolveSession(apiClient.getCurrentUser());
616
+ await Bun.sleep(0);
617
+ await testSetup!.renderOnce();
618
+ });
619
+ await waitForFrame("Start 7-day free trial");
620
+ expect(capturedConfig?.onboardingProgress?.stage).toBe("upgrade");
621
+ } finally { apiClient.getSession = originalGetSession; }
596
622
  });
597
623
 
598
624
  test("loads the current Cloud price on the Pro step", async () => {
@@ -5,6 +5,8 @@ import {
5
5
  useRef,
6
6
  useState,
7
7
  } from "react";
8
+ import { CloudVerificationPanel } from "../../plugins/builtin/cloud/verification-panel";
9
+ import { recordResearchActivity } from "../../api-client/research-activity";
8
10
  import { apiClient, type CloudPricing } from "../../api-client";
9
11
  import type { AppBrokerImportRuntime } from "../../app/runtime/broker-import";
10
12
  import type { SyncBrokerInstanceResult } from "../../brokers/sync-broker-instance";
@@ -72,6 +74,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
72
74
  const stage = progress.stage;
73
75
  const [persistenceError, setPersistenceError] = useState<string | null>(null);
74
76
  const [isFinishing, setIsFinishing] = useState(false);
77
+ const researchOpenedRef = useRef<string | null>(null);
75
78
  const [pricing, setPricing] = useState<CloudPricing | null>(null);
76
79
 
77
80
  const [portfolioSub, setPortfolioSub] = useState<PortfolioSub>("choose");
@@ -92,7 +95,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
92
95
  const portfolioChoices = useMemo<ListViewItem[]>(() => [
93
96
  {
94
97
  id: "manual",
95
- label: t("Create a manual portfolio"),
98
+ label: t("Research a company"),
96
99
  description: t("Add one company with the command bar"),
97
100
  detail: t("Recommended"),
98
101
  },
@@ -148,7 +151,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
148
151
 
149
152
  const account = useOnboardingAccount({
150
153
  nextStep: () => {
151
- saveProgressInBackground({ stage: "upgrade", accountStatus: "signed-in" });
154
+ saveProgressInBackground({ stage: apiClient.getCurrentUser()?.emailVerified ? "upgrade" : "verify", accountStatus: "signed-in" });
152
155
  },
153
156
  setEditingField,
154
157
  });
@@ -163,7 +166,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
163
166
  ?? result.updatedTickers[0]?.metadata.ticker;
164
167
  const brokerName = brokerOptions.find((option) => option.id === selectedBrokerId)?.name;
165
168
  const nextConfig = withOnboardingProgress(syncedConfig, {
166
- stage: tickerSymbol ? "account" : "add-ticker",
169
+ stage: tickerSymbol ? "research" : "add-ticker",
167
170
  path: "broker",
168
171
  portfolioId: tickerSymbol ? (result.portfolioIds[0] ?? "main") : "main",
169
172
  tickerSymbol,
@@ -248,7 +251,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
248
251
  const current = getOnboardingProgress(stateRef.current.config);
249
252
  if (current.stage !== "add-ticker" || portfolioId !== current.portfolioId) return;
250
253
  saveProgressInBackground({
251
- stage: "account",
254
+ stage: "research",
252
255
  path: current.path,
253
256
  portfolioId,
254
257
  tickerSymbol: symbol,
@@ -256,6 +259,13 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
256
259
  },
257
260
  ), [pluginRegistry.events, saveProgressInBackground, stateRef]);
258
261
 
262
+ useEffect(() => {
263
+ if (stage !== "research" || !progress.tickerSymbol || researchOpenedRef.current === progress.tickerSymbol) return;
264
+ researchOpenedRef.current = progress.tickerSymbol;
265
+ pluginRegistry.navigateTicker(progress.tickerSymbol, { sourcePaneId: "ticker-detail:main" });
266
+ recordResearchActivity("ticker_saved");
267
+ }, [stage, progress.tickerSymbol, pluginRegistry]);
268
+
259
269
  useEffect(() => {
260
270
  if (stage !== "upgrade" || pricing) return;
261
271
  void apiClient.getCloudPricing().then(setPricing).catch(() => {});
@@ -439,7 +449,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
439
449
  // The QR panel owns enter (retry after a denial); approval advances itself.
440
450
  if (account.accountSub === "qr") return;
441
451
  if (account.accountSub === "signed-in") {
442
- saveProgressInBackground({ stage: "upgrade", accountStatus: "signed-in" });
452
+ saveProgressInBackground({ stage: apiClient.getCurrentUser()?.emailVerified ? "upgrade" : "verify", accountStatus: "signed-in" });
443
453
  return;
444
454
  }
445
455
  if (account.accountSubmitError?.kind === "switch-to-login") {
@@ -584,6 +594,11 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
584
594
  }
585
595
  return;
586
596
  }
597
+ if (stage === "research" && enter) {
598
+ consume();
599
+ saveProgressInBackground({ stage: "account" });
600
+ return;
601
+ }
587
602
  if (stage === "account") {
588
603
  if (enter) {
589
604
  consume();
@@ -630,6 +645,26 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
630
645
  return null;
631
646
  }
632
647
 
648
+ if (stage === "research") {
649
+ return <OnboardingCoach step={t("YOUR FIRST RESEARCH WORKSPACE")}
650
+ title={tf("Explore {ticker}", { ticker: progress.tickerSymbol ?? "your company" })}
651
+ actions={<><OnboardingButton label="Keep exploring" variant="ghost" onPress={() => { void finish(); }} />
652
+ <OnboardingButton label="Connect free Cloud" variant="primary" onPress={() => saveProgressInBackground({ stage: "account" })} /></>}>
653
+ <Text fg={colors.textDim} wrapText>{t("Your company is saved. Explore the chart and financials now. Connect Cloud when you're ready for synced layouts, news and Pro research.")}</Text>
654
+ </OnboardingCoach>;
655
+ }
656
+
657
+ if (stage === "verify") {
658
+ return <OnboardingModal width={66} height={14}>
659
+ <OnboardingTitle step={t("CONNECT CLOUD")} title={t("Check your email")}
660
+ description={apiClient.getCurrentUser()?.email ?? account.accountEmail} />
661
+ <CloudVerificationPanel onVerified={() => {
662
+ void chatController.refreshSession();
663
+ saveProgressInBackground({ stage: "upgrade", accountStatus: "signed-in" });
664
+ }} onContinueFree={() => { void finish(); }} />
665
+ </OnboardingModal>;
666
+ }
667
+
633
668
  if (stage === "add-ticker") {
634
669
  return (
635
670
  <OnboardingCoach
@@ -664,7 +699,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
664
699
  <OnboardingTitle
665
700
  step={t("WELCOME")}
666
701
  title={t("Make Gloomberb yours")}
667
- description={t("Build a real portfolio in the workspace, add one company you follow, then choose whether to connect Gloom Cloud.")}
702
+ description={t("Pick a company. Explore its chart and financials. Keep your workspace free, or add Pro research when you need it.")}
668
703
  />
669
704
  {persistenceError ? (
670
705
  <Text fg={colors.negative} wrapText style={desktop ? { marginTop: 10 } : undefined}>
@@ -673,7 +708,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
673
708
  ) : null}
674
709
  <OnboardingActions>
675
710
  <OnboardingButton
676
- label="Start with my portfolio"
711
+ label="Research my first company"
677
712
  variant="primary"
678
713
  onPress={() => saveProgressInBackground({ stage: "portfolio" })}
679
714
  />
@@ -771,14 +806,14 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
771
806
  : account.accountSub === "login"
772
807
  ? t("Log in")
773
808
  : account.accountSub === "qr"
774
- ? t("Scan with the Gloom app")
809
+ ? t("Continue in browser")
775
810
  : account.accountSub === "signed-in"
776
811
  ? t("Connected")
777
812
  : t("Sync across apps");
778
813
  const accountDescription = account.accountSub === "choose"
779
- ? t("Adds quotes, financials, options, research, news, chat and AI commands.")
814
+ ? t("Sync your layouts. Search calls, news and filings.")
780
815
  : account.accountSub === "qr"
781
- ? t("Open the Gloom app on your phone and approve this workspace.")
816
+ ? t("Open the sign-in link, or scan the code with your phone.")
782
817
  : account.accountSub === "signup"
783
818
  ? t("Create the free account now. Cloud features unlock after you verify your email.")
784
819
  : account.accountSub === "login"
@@ -791,7 +826,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
791
826
  // The QR grid is the tallest thing this wizard ever shows; DeviceSignInPanel
792
827
  // degrades to the code plus URL when the terminal cannot give it these rows.
793
828
  const accountModalHeight = account.accountSub === "choose"
794
- ? 17
829
+ ? 21
795
830
  : account.accountSub === "qr"
796
831
  ? 32
797
832
  : account.accountSub === "signed-in"
@@ -887,7 +922,7 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
887
922
  ) : undefined}
888
923
  description={planAccess.hasProAccess
889
924
  ? t("This account already has real-time Cloud data.")
890
- : t("Limited founding offer. Start a free 7-day trial.")}
925
+ : t("7 days free, then the price above. Card required. Cancel anytime.")}
891
926
  />
892
927
  <Box flexDirection="column" style={desktop ? { marginTop: 14, gap: 8 } : undefined}>
893
928
  <OnboardingFeature
@@ -899,8 +934,8 @@ export function OnboardingWizard({ pluginRegistry, importBrokerPositions, onComp
899
934
  description={t("Free: 12h delayed. Pro: real-time.")}
900
935
  />
901
936
  <OnboardingFeature
902
- title={t("Pro research tools")}
903
- description={t("More tools for company, market and portfolio research.")}
937
+ title={t("Earnings transcripts and instant search")}
938
+ description={t("Read calls. Search transcripts, news and filings. Ask Gloomberb AI coming soon.")}
904
939
  />
905
940
  </Box>
906
941
  {persistenceError ? (
@@ -11,6 +11,7 @@ import {
11
11
  findPaneInstance,
12
12
  } from "../../../types/config";
13
13
  import { getDockedPaneIds } from "../../../plugins/pane-manager";
14
+ import { EXTRACTED_PLUGINS, seedExtractedPlugins } from "../../../plugins/seed";
14
15
 
15
16
  const tempDirs: string[] = [];
16
17
 
@@ -48,6 +49,28 @@ async function writeConfigJson(dataDir: string, config: Record<string, unknown>)
48
49
  await writeFile(join(dataDir, "config.json"), JSON.stringify(config), "utf-8");
49
50
  }
50
51
 
52
+ test("fresh installs skip plugin restoration across config reloads", async () => {
53
+ const dataDir = await createTempConfigDir();
54
+ const fresh = await loadConfig(dataDir);
55
+ await saveConfig(fresh);
56
+ const reloaded = await loadConfig(dataDir);
57
+ const installed: string[] = [];
58
+ await seedExtractedPlugins(reloaded, async (ref) => { installed.push(ref); }, dataDir);
59
+ expect(installed).toEqual([]);
60
+ expect(reloaded.seededPlugins).toEqual(EXTRACTED_PLUGINS.map((plugin) => plugin.id));
61
+ });
62
+
63
+ test("existing users retain pending and completed plugin migrations", async () => {
64
+ const dataDir = await createTempConfigDir();
65
+ await writeConfigJson(dataDir, createSavedConfig());
66
+ expect((await loadConfig(dataDir)).seededPlugins).toEqual([]);
67
+ await writeConfigJson(dataDir, createSavedConfig({ seededPlugins: ["substack", "substack", 42] }));
68
+ const loaded = await loadConfig(dataDir);
69
+ expect(loaded.seededPlugins).toEqual(["substack"]);
70
+ await saveConfig(loaded);
71
+ expect((await loadConfig(dataDir)).seededPlugins).toEqual(["substack"]);
72
+ });
73
+
51
74
  describe("sanitizeLayout", () => {
52
75
  test("preserves an intentionally blank layout", () => {
53
76
  expect(sanitizeLayout({
@@ -4,6 +4,7 @@ import { dirname, join } from "path";
4
4
  import type { AppConfig } from "../../../types/config";
5
5
  import { createDefaultConfig } from "../../../types/config";
6
6
  import { debugLog } from "../../../utils/debug-log";
7
+ import { EXTRACTED_PLUGINS } from "../../../plugins/seed";
7
8
  import {
8
9
  normalizeConfigForSave,
9
10
  normalizeLoadedConfig,
@@ -54,7 +55,12 @@ async function loadConfigState(dataDir: string): Promise<{ config: AppConfig; ne
54
55
  const saved = JSON.parse(raw) as Record<string, unknown>;
55
56
  return normalizeLoadedConfig(saved, dataDir);
56
57
  } catch {
57
- return { config: createDefaultConfig(dataDir), needsSave: true };
58
+ // Fresh installs have no former built-in plugins to restore. Record the
59
+ // current baseline so onboarding never waits for plugin network installs.
60
+ return {
61
+ config: { ...createDefaultConfig(dataDir), seededPlugins: EXTRACTED_PLUGINS.map((plugin) => plugin.id) },
62
+ needsSave: true,
63
+ };
58
64
  }
59
65
  }
60
66
 
@@ -52,6 +52,7 @@ export function normalizeLoadedConfig(saved: Record<string, unknown>, dataDir: s
52
52
  activeLayoutIndex,
53
53
  brokerInstances: sanitizeBrokerInstances(candidate.brokerInstances),
54
54
  disabledPlugins,
55
+ seededPlugins: sanitizeUniqueStringList(candidate.seededPlugins),
55
56
  disabledSources: sanitizeUniqueStringList(candidate.disabledSources ?? defaults.disabledSources),
56
57
  pluginConfig: sanitizePluginConfig(candidate.pluginConfig),
57
58
  theme: typeof candidate.theme === "string" ? candidate.theme : defaults.theme,
@@ -108,6 +109,7 @@ export function normalizeConfigForSave(config: AppConfig): AppConfig {
108
109
  activeLayoutIndex,
109
110
  brokerInstances: sanitizeBrokerInstances(config.brokerInstances),
110
111
  disabledPlugins: sanitizeUniqueStringList(config.disabledPlugins),
112
+ seededPlugins: sanitizeUniqueStringList(config.seededPlugins),
111
113
  disabledSources: sanitizeUniqueStringList(config.disabledSources),
112
114
  pluginConfig: sanitizePluginConfig(config.pluginConfig),
113
115
  chartPreferences: sanitizeChartPreferences(config.chartPreferences, defaults.chartPreferences),
@@ -130,6 +132,8 @@ const ONBOARDING_STAGES = new Set<OnboardingProgress["stage"]>([
130
132
  "welcome",
131
133
  "portfolio",
132
134
  "add-ticker",
135
+ "research",
136
+ "verify",
133
137
  "account",
134
138
  "upgrade",
135
139
  "ready",
package/src/news/types.ts CHANGED
@@ -38,6 +38,7 @@ export interface NewsArticle {
38
38
  scores: NewsScores;
39
39
  isBreaking: boolean;
40
40
  isDeveloping: boolean;
41
+ sourceCount?: number;
41
42
  items?: NewsStoryItem[];
42
43
 
43
44
  // Compatibility aliases for RSS/Yahoo panes and existing table columns.
@@ -41,6 +41,26 @@ describe("loadCdsActivity", () => {
41
41
  expect(activity.issuer).toBe("Oracle Corporation");
42
42
  });
43
43
 
44
+ test("prefers the primary US common stock when exchanges reuse a ticker", async () => {
45
+ const cedear = {
46
+ ...result("ORCL", "Oracle Corp. - CEDEAR"),
47
+ exchange: "BYMA",
48
+ primaryExchange: "BYMA",
49
+ type: "Depositary Receipt",
50
+ currency: "ARS",
51
+ };
52
+ const primary = {
53
+ ...result("ORCL", "Oracle Corporation"),
54
+ currency: "USD",
55
+ primaryExchange: "NYSE",
56
+ };
57
+ const calls = spy([cedear, primary]);
58
+
59
+ await loadCdsActivity("ORCL", calls.fetchCds, calls.searchInstruments);
60
+
61
+ expect(calls.requested).toEqual(["Oracle Corporation"]);
62
+ });
63
+
44
64
  test("falls back to the first result when no symbol matches exactly", async () => {
45
65
  const calls = spy([result("AVGO", "Broadcom Inc."), result("AVGOP", "Broadcom Preferred")]);
46
66
 
@@ -16,14 +16,23 @@ export interface CdsActivity {
16
16
 
17
17
  export type CdsActivityLoader = (issuer: string | null) => Promise<CdsActivity>;
18
18
 
19
- type CdsFetch = (params: { issuer?: string; days?: number; limit?: number }) => Promise<CloudCdsResponse>;
20
- type InstrumentSearch = (query: string, limit?: number) => Promise<InstrumentSearchResult[]>;
19
+ export type CdsFetch = (params: { issuer?: string; days?: number; limit?: number }) => Promise<CloudCdsResponse>;
20
+ export type InstrumentSearch = (query: string, limit?: number) => Promise<InstrumentSearchResult[]>;
21
21
 
22
22
  /**
23
23
  * A bare ticker with normal symbol punctuation and no spaces. Anything longer or
24
24
  * containing a space is already a company name and is sent to the backend as-is.
25
25
  */
26
26
  const TICKER_LIKE = /^[A-Za-z0-9][A-Za-z0-9.^:-]{0,11}$/;
27
+ const US_PRIMARY_EXCHANGES = new Set(["AMEX", "NASDAQ", "NYSE", "NYSEAMERICAN", "NYSEARCA"]);
28
+
29
+ function isPrimaryUsCommonStock(result: InstrumentSearchResult): boolean {
30
+ const exchange = (result.primaryExchange || result.exchange).toUpperCase().replace(/[^A-Z]/g, "");
31
+ const type = result.type.toLowerCase();
32
+ return result.currency === "USD"
33
+ && US_PRIMARY_EXCHANGES.has(exchange)
34
+ && (type === "stk" || type === "equity" || type.includes("common stock"));
35
+ }
27
36
 
28
37
  /**
29
38
  * The backend matches DTCC issuer names, so a ticker has to become a company
@@ -40,7 +49,8 @@ export async function resolveIssuerName(
40
49
  try {
41
50
  const results = await searchInstruments(issuer);
42
51
  const symbol = issuer.toUpperCase();
43
- const match = results.find((result) => result.symbol.toUpperCase() === symbol) ?? results[0];
52
+ const exactMatches = results.filter((result) => result.symbol.toUpperCase() === symbol);
53
+ const match = exactMatches.find(isPrimaryUsCommonStock) ?? exactMatches[0] ?? results[0];
44
54
  return match?.name?.trim() || issuer;
45
55
  } catch {
46
56
  return issuer;
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
3
+ import { createCdsHeadless } from "./headless";
4
+ import type { CdsActivity } from "./client";
5
+
6
+ const activity: CdsActivity = {
7
+ source: "DTCC",
8
+ asOf: "2026-09-04T12:00:00Z",
9
+ issuer: "Oracle Corporation",
10
+ trades: [{
11
+ id: "1", issuer: "Oracle Corporation", issuerKey: "oracle", eventAt: 100,
12
+ maturity: "2031-06-20", notional: 5_000_000, notionalCapped: false,
13
+ currency: "USD", couponBp: 100, spreadBp: 45, upfront: null, upfrontCurrency: null,
14
+ }],
15
+ };
16
+
17
+ function args(argument: string | null): HeadlessPaneLoadArgs {
18
+ return { rawArgument: argument ?? "", argument, symbols: argument ? [argument] : [], options: {} };
19
+ }
20
+
21
+ describe("CDS headless model", () => {
22
+ test("switches from issuer summaries to trade rows for a ticker argument", async () => {
23
+ const headless = createCdsHeadless({ load: async () => activity });
24
+ const market = await headless.load(args(null), {} as HeadlessPaneContext);
25
+ const ticker = await headless.load(args("ORCL"), {} as HeadlessPaneContext);
26
+
27
+ expect(market.rows).toEqual([expect.objectContaining({ issuer: "Oracle Corporation", trades: 1 })]);
28
+ expect(ticker.rows).toEqual([expect.objectContaining({ id: "1", spreadBp: 45 })]);
29
+ });
30
+ });
@@ -0,0 +1,113 @@
1
+ import type {
2
+ HeadlessPaneDefinition,
3
+ HeadlessPaneLoadArgs,
4
+ HeadlessRowsResult,
5
+ } from "../../../types/plugin";
6
+ import {
7
+ loadCdsActivity,
8
+ type CdsActivity,
9
+ type CdsFetch,
10
+ type InstrumentSearch,
11
+ } from "./client";
12
+ import {
13
+ DEFAULT_ISSUER_SORT,
14
+ DEFAULT_TRADE_SORT,
15
+ formatBp,
16
+ formatEventTime,
17
+ formatNotional,
18
+ formatUpfront,
19
+ sortIssuers,
20
+ sortTrades,
21
+ summarizeIssuers,
22
+ type CdsTrade,
23
+ } from "./model";
24
+
25
+ const ISSUER_COLUMNS = [
26
+ { key: "issuer", header: "Issuer" },
27
+ { key: "trades", header: "Trades", align: "right" as const },
28
+ { key: "lastTradeAt", header: "Last UTC", format: (value: unknown) => formatEventTime(Number(value)) },
29
+ { key: "latestSpreadBp", header: "Spread", align: "right" as const, format: (value: unknown) => formatBp(value as number | null) },
30
+ ];
31
+
32
+ function tradeForFormatting(row: Record<string, unknown>): CdsTrade {
33
+ return row as unknown as CdsTrade;
34
+ }
35
+
36
+ const TRADE_COLUMNS = [
37
+ { key: "eventAt", header: "Time UTC", format: (value: unknown) => formatEventTime(Number(value)) },
38
+ { key: "issuer", header: "Issuer" },
39
+ { key: "maturity", header: "Maturity" },
40
+ {
41
+ key: "notional",
42
+ header: "Notional",
43
+ align: "right" as const,
44
+ format: (_value: unknown, row: Record<string, unknown>) => formatNotional(tradeForFormatting(row)),
45
+ },
46
+ { key: "currency", header: "CCY" },
47
+ { key: "couponBp", header: "Coupon", align: "right" as const, format: (value: unknown) => formatBp(value as number | null) },
48
+ { key: "spreadBp", header: "Spread", align: "right" as const, format: (value: unknown) => formatBp(value as number | null) },
49
+ {
50
+ key: "upfront",
51
+ header: "Upfront",
52
+ align: "right" as const,
53
+ format: (_value: unknown, row: Record<string, unknown>) => formatUpfront(tradeForFormatting(row)),
54
+ },
55
+ ];
56
+
57
+ export function projectCdsHeadless(activity: CdsActivity, issuer: string | null): HeadlessRowsResult {
58
+ if (issuer) {
59
+ return {
60
+ columns: TRADE_COLUMNS,
61
+ rows: sortTrades(activity.trades, DEFAULT_TRADE_SORT).map((trade) => ({ ...trade })),
62
+ metadata: { source: activity.source, asOf: activity.asOf, issuer: activity.issuer },
63
+ };
64
+ }
65
+ return {
66
+ columns: ISSUER_COLUMNS,
67
+ rows: sortIssuers(summarizeIssuers(activity.trades), DEFAULT_ISSUER_SORT).map((issuerRow) => ({ ...issuerRow })),
68
+ metadata: { source: activity.source, asOf: activity.asOf, issuer: null },
69
+ };
70
+ }
71
+
72
+ export interface CdsHeadlessDependencies {
73
+ load(
74
+ args: HeadlessPaneLoadArgs,
75
+ issuer: string | null,
76
+ fetchCds: CdsFetch,
77
+ searchInstruments: InstrumentSearch,
78
+ ): Promise<CdsActivity>;
79
+ }
80
+
81
+ const defaultDependencies: CdsHeadlessDependencies = {
82
+ load: (_args, issuer, fetchCds, searchInstruments) => (
83
+ loadCdsActivity(issuer, fetchCds, searchInstruments)
84
+ ),
85
+ };
86
+
87
+ export function createCdsHeadless(
88
+ dependencies: CdsHeadlessDependencies = defaultDependencies,
89
+ ): HeadlessPaneDefinition<"rows"> {
90
+ return {
91
+ shape: "rows",
92
+ argument: {
93
+ kind: "free-text",
94
+ placeholder: "ticker or issuer",
95
+ description: "Optional ticker or issuer. Omit for the most-active issuer view.",
96
+ optional: true,
97
+ },
98
+ options: [],
99
+ describe: (args) => typeof args.argument === "string" ? `CDS | ${args.argument}` : "Single-Name CDS",
100
+ async load(args, ctx) {
101
+ const issuer = typeof args.argument === "string" ? args.argument : null;
102
+ const activity = await dependencies.load(
103
+ args,
104
+ issuer,
105
+ (params) => ctx.apiClient.getCloudCds(params),
106
+ (query, limit) => ctx.apiClient.searchInstruments(query, limit),
107
+ );
108
+ return projectCdsHeadless(activity, issuer);
109
+ },
110
+ };
111
+ }
112
+
113
+ export const cdsHeadless = createCdsHeadless();
@@ -2,6 +2,9 @@ import type { PaneTemplateCreateOptions } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { CDS_PANE_ID } from "./model";
4
4
  import { CdsPane } from "./pane";
5
+ import { cdsHeadless } from "./headless";
6
+
7
+ export { cdsHeadless } from "./headless";
5
8
 
6
9
  /** Only an explicit argument binds a ticker; bare `CDS` stays market-wide. */
7
10
  function explicitSymbol(options?: PaneTemplateCreateOptions): string | null {
@@ -26,6 +29,7 @@ export const cdsModule: PluginModule = {
26
29
  description: "Single-name corporate CDS trade activity from DTCC public dissemination.",
27
30
  keywords: ["cds", "credit", "default", "swap", "single name", "issuer", "dtcc", "protection"],
28
31
  shortcut: { prefix: "CDS", argPlaceholder: "ticker", argKind: "ticker", argOptional: true },
32
+ headless: cdsHeadless,
29
33
  createInstance: (_context, options) => {
30
34
  const symbol = explicitSymbol(options);
31
35
  return symbol
@@ -45,7 +45,10 @@ export function applyChartComposerCapabilityOptions(
45
45
  options: NormalizedPaneFunctionOptions,
46
46
  ): ChartSpec {
47
47
  let next = spec;
48
- const range = chartRange(options);
48
+ const selectedRange = chartRange(options);
49
+ const range = capabilityId === "intraday-price-chart" && typeof options.session === "string"
50
+ ? "1D"
51
+ : selectedRange;
49
52
  const resolution = chartResolution(options);
50
53
  if (range || resolution) {
51
54
  next = {
@@ -279,7 +279,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
279
279
  id: "graph-intraday-price-pane",
280
280
  prefix: "GIP",
281
281
  label: "Intraday Price Graph",
282
- description: "Open a one-minute intraday price chart.",
282
+ description: "Open an intraday price chart for one or five sessions.",
283
283
  argKind: "ticker",
284
284
  minimumSymbols: 1,
285
285
  build: (symbols) => buildIntradayPriceChartPreset(symbols[0]!),