gloomberb 0.10.3 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +2 -2
  3. package/package.json +2 -1
  4. package/src/api-client/auth.ts +8 -0
  5. package/src/api-client/index.ts +70 -0
  6. package/src/api-client/types.ts +49 -0
  7. package/src/app/runtime/desktop-deeplink.test.ts +8 -0
  8. package/src/app/runtime/desktop-deeplink.ts +14 -1
  9. package/src/components/chart/composite/composite-chart.test.tsx +340 -17
  10. package/src/components/chart/composite/composite-chart.tsx +859 -68
  11. package/src/components/chart/composite/interactions.test.ts +78 -22
  12. package/src/components/chart/composite/interactions.ts +45 -17
  13. package/src/components/chart/composite/rasterizer.ts +8 -2
  14. package/src/components/chart/composite/renderers.test.ts +61 -1
  15. package/src/components/chart/composite/scene.ts +14 -4
  16. package/src/components/chart/composite/tools.test.ts +371 -0
  17. package/src/components/chart/composite/tools.ts +508 -0
  18. package/src/components/chart/composite/types.ts +4 -0
  19. package/src/components/chart/core/pointer.ts +2 -0
  20. package/src/components/chart/native/raster/primitives.ts +18 -0
  21. package/src/components/chart/price-axis-labels.tsx +42 -2
  22. package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
  23. package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
  24. package/src/components/command-bar/action-runtime.ts +3 -0
  25. package/src/components/command-bar/assist/inventory.test.ts +93 -0
  26. package/src/components/command-bar/assist/inventory.ts +92 -0
  27. package/src/components/command-bar/assist/model.test.ts +104 -0
  28. package/src/components/command-bar/assist/model.ts +169 -0
  29. package/src/components/command-bar/assist/runtime.ts +185 -0
  30. package/src/components/command-bar/commands/direct/index.ts +1 -1
  31. package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
  32. package/src/components/command-bar/list/model.ts +15 -2
  33. package/src/components/command-bar/list/navigation.ts +6 -0
  34. package/src/components/command-bar/list/view.tsx +16 -3
  35. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  36. package/src/components/command-bar/panel/body.tsx +2 -0
  37. package/src/components/command-bar/panel/index.tsx +3 -0
  38. package/src/components/command-bar/panel/runtime.ts +6 -0
  39. package/src/components/command-bar/panel/types.ts +1 -0
  40. package/src/components/command-bar/routes/root/results.ts +28 -2
  41. package/src/components/command-bar/routes/root/runtime.ts +66 -36
  42. package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
  43. package/src/components/command-bar/routing/navigation-state.ts +17 -0
  44. package/src/components/command-bar/selection-runtime.ts +26 -0
  45. package/src/components/command-bar/surface/assist.test.tsx +276 -0
  46. package/src/components/command-bar/surface/index.tsx +93 -2
  47. package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
  48. package/src/components/command-bar/view-model.ts +7 -1
  49. package/src/components/command-bar/workflow/broker.test.ts +22 -0
  50. package/src/components/command-bar/workflow/broker.ts +1 -0
  51. package/src/components/layout/detached-pane-shell.tsx +40 -2
  52. package/src/components/layout/floating-pane.tsx +4 -1
  53. package/src/components/layout/pane/header.tsx +64 -6
  54. package/src/components/layout/pane/index.tsx +4 -1
  55. package/src/components/layout/shell/index.tsx +23 -0
  56. package/src/components/layout/shell/pane/layers.tsx +5 -0
  57. package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
  58. package/src/components/onboarding/account-step/form-panel.tsx +157 -0
  59. package/src/components/onboarding/account-step/index.tsx +58 -0
  60. package/src/components/onboarding/account-step/model.test.ts +67 -0
  61. package/src/components/onboarding/account-step/model.ts +126 -0
  62. package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
  63. package/src/components/onboarding/onboarding-steps.tsx +91 -55
  64. package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
  65. package/src/components/onboarding/onboarding-wizard.tsx +93 -9
  66. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
  67. package/src/components/onboarding/wizard-account.ts +235 -0
  68. package/src/components/onboarding/wizard-keyboard.ts +67 -1
  69. package/src/components/onboarding/wizard-model.ts +2 -2
  70. package/src/i18n/es.ts +1093 -0
  71. package/src/i18n/index.test.ts +5 -2
  72. package/src/i18n/index.ts +3 -0
  73. package/src/i18n/ja.ts +64 -3
  74. package/src/i18n/ko.ts +64 -3
  75. package/src/i18n/languages.test.ts +4 -1
  76. package/src/i18n/languages.ts +12 -1
  77. package/src/i18n/zh-cn.ts +64 -3
  78. package/src/i18n/zh-tw.ts +64 -3
  79. package/src/market-data/coordinator/quotes.ts +63 -1
  80. package/src/market-data/coordinator/subscriptions.test.ts +33 -0
  81. package/src/market-data/quotes/cadence.ts +2 -0
  82. package/src/plugins/builtin/account-management/footer.ts +9 -3
  83. package/src/plugins/builtin/account-management/model.ts +53 -1
  84. package/src/plugins/builtin/account-management/pane.tsx +107 -24
  85. package/src/plugins/builtin/ai/index.tsx +7 -2
  86. package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
  87. package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
  88. package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
  89. package/src/plugins/builtin/chart-composer/index.tsx +10 -2
  90. package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
  91. package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
  92. package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
  93. package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
  94. package/src/plugins/builtin/cloud/plugin.tsx +9 -1
  95. package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
  96. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
  97. package/src/plugins/builtin/help/index.tsx +38 -0
  98. package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
  99. package/src/plugins/builtin/options/footer.test.ts +20 -124
  100. package/src/plugins/builtin/options/footer.ts +32 -90
  101. package/src/plugins/builtin/options/index.tsx +6 -0
  102. package/src/plugins/builtin/options/view.tsx +3 -0
  103. package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
  104. package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
  105. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
  106. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
  107. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
  108. package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
  109. package/src/plugins/builtin/shared/live-streaming.ts +60 -0
  110. package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
  111. package/src/plugins/builtin/shared/plan-access.ts +109 -0
  112. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
  113. package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
  114. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
  115. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
  116. package/src/plugins/registry/index.test.ts +51 -0
  117. package/src/plugins/registry/index.ts +12 -0
  118. package/src/plugins/registry/pane-settings.ts +29 -0
  119. package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
  120. package/src/renderers/electrobun/view/host/box.tsx +3 -0
  121. package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
  122. package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
  123. package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
  124. package/src/renderers/electrobun/view/host/input.tsx +16 -1
  125. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  126. package/src/renderers/electrobun/view/host/style.ts +1 -1
  127. package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
  128. package/src/renderers/electrobun/view/main.tsx +2 -0
  129. package/src/renderers/opentui/chart-surface.tsx +2 -0
  130. package/src/renderers/opentui/host.tsx +3 -0
  131. package/src/sources/gloomberb-cloud/index.test.ts +8 -0
  132. package/src/sources/gloomberb-cloud/index.ts +4 -4
  133. package/src/sources/provider-router/history.test.ts +3 -3
  134. package/src/sources/provider-router/history.ts +15 -4
  135. package/src/sources/yahoo-finance/history.test.ts +47 -0
  136. package/src/sources/yahoo-finance/history.ts +5 -1
  137. package/src/state/hooks/quote-streaming.test.tsx +124 -1
  138. package/src/state/hooks/quote-streaming.ts +90 -7
  139. package/src/theme/colors.ts +20 -1
  140. package/src/theme/themes.test.ts +3 -0
  141. package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
  142. package/src/time-series/resolution.test.ts +51 -0
  143. package/src/time-series/resolution.ts +41 -0
  144. package/src/time-series/resolve.test.ts +207 -1
  145. package/src/time-series/resolve.ts +196 -29
  146. package/src/time-series/spec-studies.test.ts +4 -0
  147. package/src/time-series/spec.ts +15 -1
  148. package/src/time-series/types.ts +7 -0
  149. package/src/time-series/use-chart-resolution.test.tsx +37 -0
  150. package/src/time-series/use-chart-resolution.ts +17 -1
  151. package/src/types/plugin.ts +9 -0
  152. package/src/ui/host.tsx +17 -0
package/README.md CHANGED
@@ -285,15 +285,15 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
285
285
  | `VF` | Toggle quote value flashing |
286
286
  | `TH <theme>` | Change color theme |
287
287
  | `CR` | Cycle chart renderer |
288
- | `LANG <locale>` | Change interface language (`auto`, `en`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
288
+ | `LANG <locale>` | Change interface language (`auto`, `en`, `es`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
289
289
  | `PL <plugin>` | Manage plugins |
290
290
 
291
291
  ## Localized interface
292
292
 
293
- Gloomberb includes English, Simplified Chinese, Traditional Chinese, Japanese, and Korean UI support. English remains the default fallback language.
293
+ Gloomberb includes English, Spanish, Simplified Chinese, Traditional Chinese, Japanese, and Korean UI support. English remains the default fallback language.
294
294
 
295
295
  - **Automatic detection:** supported `LANG` / `LC_ALL` and desktop system locales select the matching interface automatically.
296
- - **Command switching:** enter `LANG` in the command bar (Ctrl+P) to cycle languages, or use `LANG auto`, `LANG en`, `LANG zh-CN`, `LANG zh-TW`, `LANG ja`, or `LANG ko`. The choice is persisted in `config.json`.
296
+ - **Command switching:** enter `LANG` in the command bar (Ctrl+P) to cycle languages, or use `LANG auto`, `LANG en`, `LANG es`, `LANG zh-CN`, `LANG zh-TW`, `LANG ja`, or `LANG ko`. The choice is persisted in `config.json`.
297
297
  - **One-run override:** `GLOOMBERB_LANG=ja gloomberb` (or another supported locale) takes highest priority in environments that expose process locale variables.
298
298
 
299
299
  Implementation notes:
package/README.zh-CN.md CHANGED
@@ -267,7 +267,7 @@ gloomberb
267
267
  | `VF` | 切换行情数值闪烁 |
268
268
  | `TH <theme>` | 更换配色主题 |
269
269
  | `CR` | 切换图表渲染器 |
270
- | `LANG <locale>` | 切换界面语言(`auto`、`en`、`zh-CN`、`zh-TW`、`ja` 或 `ko`) |
270
+ | `LANG <locale>` | 切换界面语言(`auto`、`en`、`es`、`zh-CN`、`zh-TW`、`ja` 或 `ko`) |
271
271
  | `PL <plugin>` | 管理插件 |
272
272
 
273
273
  ## 本地化界面
@@ -275,7 +275,7 @@ gloomberb
275
275
  Gloomberb 内置 English、简体中文、繁體中文、日本語和한국어界面支持;英文仍是默认回退语言。
276
276
 
277
277
  - **自动检测**:终端 `LANG` / `LC_ALL` 与桌面系统语言会自动选择受支持的界面语言。
278
- - **命令切换**:在命令栏(Ctrl+P)输入 `LANG` 可循环切换,也可直接输入 `LANG auto`、`LANG en`、`LANG zh-CN`、`LANG zh-TW`、`LANG ja` 或 `LANG ko`。选择会保存到 `config.json`。
278
+ - **命令切换**:在命令栏(Ctrl+P)输入 `LANG` 可循环切换,也可直接输入 `LANG auto`、`LANG en`、`LANG es`、`LANG zh-CN`、`LANG zh-TW`、`LANG ja` 或 `LANG ko`。选择会保存到 `config.json`。
279
279
  - **单次覆盖**:在可读取进程语言变量的环境中,可用 `GLOOMBERB_LANG=ja gloomberb`(或其他受支持的语言代码)进行最高优先级覆盖。
280
280
 
281
281
  实现说明:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.10.3",
3
+ "version": "0.10.4",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/index.tsx",
@@ -49,6 +49,7 @@
49
49
  "@types/react-dom": "^19.2.3",
50
50
  "@types/three": "0.165.0",
51
51
  "electrobun": "^1.18.1",
52
+ "happy-dom": "^20.11.1",
52
53
  "png-to-ico": "^2.1.8",
53
54
  "rcedit": "^4.0.1"
54
55
  },
@@ -5,6 +5,7 @@ import type {
5
5
  AuthUser,
6
6
  BuildoutAccountResponse,
7
7
  BuildoutTokenResponse,
8
+ CloudPricing,
8
9
  CloudVerificationResponse,
9
10
  PersistedAuthUser,
10
11
  } from "./types";
@@ -39,6 +40,8 @@ export class CloudAuthApi {
39
40
  emailVerified: user.emailVerified === true,
40
41
  image: typeof user.image === "string" ? user.image : null,
41
42
  plan: user.plan,
43
+ trialEndsAt: typeof user.trialEndsAt === "string" ? user.trialEndsAt : null,
44
+ effectivePlan: user.effectivePlan,
42
45
  syncEnabled: user.syncEnabled === false ? false : true,
43
46
  weeklyRoundupEnabled: user.weeklyRoundupEnabled === false ? false : true,
44
47
  positionAlertsEnabled: user.positionAlertsEnabled === false ? false : true,
@@ -122,6 +125,11 @@ export class CloudAuthApi {
122
125
  return result.profile;
123
126
  }
124
127
 
128
+ /** Public Cloud Pro pricing, including the founding discount and trial length. */
129
+ async getCloudPricing(): Promise<CloudPricing> {
130
+ return this.options.request<CloudPricing>("/pricing", { method: "GET" });
131
+ }
132
+
125
133
  async getBuildoutAccount(): Promise<BuildoutAccountResponse> {
126
134
  return this.options.request<BuildoutAccountResponse>("/account/buildout", {
127
135
  method: "GET",
@@ -13,7 +13,10 @@ import type {
13
13
  CloudTickerTweetsParams,
14
14
  CloudTweetSearchParams,
15
15
  } from "./paths";
16
+ import { withDeadline } from "../utils/async-deadline";
16
17
  import type {
18
+ AssistCommandDescriptor,
19
+ AssistCommandResponse,
17
20
  ChatMessage,
18
21
  ChatChannel,
19
22
  ChatChannelState,
@@ -24,6 +27,7 @@ import type {
24
27
  AccountProfile,
25
28
  BuildoutAccountResponse,
26
29
  BuildoutTokenResponse,
30
+ CloudPricing,
27
31
  AccountProfileUpdate,
28
32
  CloudQuotePayload,
29
33
  CloudOptionsChainPayload,
@@ -54,8 +58,14 @@ import type { SyncSettings, SyncSnapshot } from "../sync/types";
54
58
  export type * from "./types";
55
59
  export { setCloudApiFetchTransport } from "./request";
56
60
 
61
+ /** Server-side caps for `/assist/command`; enforced here so a 422 is never sent. */
62
+ const ASSIST_QUERY_MAX_LENGTH = 200;
63
+ const ASSIST_COMMAND_LIMIT = 150;
64
+ const ASSIST_REQUEST_TIMEOUT_MS = 6_000;
65
+
57
66
  class GloomApiClient {
58
67
  private currentUser: AuthUser | null = null;
68
+ private readonly currentUserListeners = new Set<() => void>();
59
69
  private readonly transport = new CloudApiRequestTransport();
60
70
  private readonly auth: CloudAuthApi;
61
71
  private readonly socket: CloudApiSocket;
@@ -110,6 +120,7 @@ class GloomApiClient {
110
120
  this.transport.setSessionToken(token);
111
121
  if (!token) {
112
122
  this.currentUser = null;
123
+ this.emitCurrentUserChange();
113
124
  }
114
125
  this.socket.syncAuthState({ reconnect: changed });
115
126
  }
@@ -124,6 +135,14 @@ class GloomApiClient {
124
135
  return this.currentUser;
125
136
  }
126
137
 
138
+ /** Notifies when the signed-in user changes, including plan and trial entitlement. */
139
+ subscribeCurrentUser(listener: () => void): () => void {
140
+ this.currentUserListeners.add(listener);
141
+ return () => {
142
+ this.currentUserListeners.delete(listener);
143
+ };
144
+ }
145
+
127
146
  restoreCachedUser(user: PersistedAuthUser | null): void {
128
147
  this.auth.restoreCachedUser(user);
129
148
  }
@@ -136,6 +155,11 @@ class GloomApiClient {
136
155
  const changed = this.socketEntitlementKey(this.currentUser) !== this.socketEntitlementKey(user);
137
156
  this.currentUser = user;
138
157
  this.socket.syncAuthState({ reconnect: changed });
158
+ this.emitCurrentUserChange();
159
+ }
160
+
161
+ private emitCurrentUserChange(): void {
162
+ for (const listener of this.currentUserListeners) listener();
139
163
  }
140
164
 
141
165
  private updateCurrentUser(updater: (user: AuthUser) => AuthUser): void {
@@ -149,6 +173,8 @@ class GloomApiClient {
149
173
  user.id,
150
174
  user.emailVerified === true ? "verified" : "unverified",
151
175
  user.plan,
176
+ // A trial starting or lapsing changes the stream entitlement without touching `plan`.
177
+ user.effectivePlan,
152
178
  ].join(":");
153
179
  }
154
180
 
@@ -195,6 +221,10 @@ class GloomApiClient {
195
221
  return this.auth.getAccountProfile();
196
222
  }
197
223
 
224
+ async getCloudPricing(): Promise<CloudPricing> {
225
+ return this.auth.getCloudPricing();
226
+ }
227
+
198
228
  async getBuildoutAccount(): Promise<BuildoutAccountResponse> {
199
229
  return this.auth.getBuildoutAccount();
200
230
  }
@@ -255,6 +285,46 @@ class GloomApiClient {
255
285
  return this.auth.deleteAccount();
256
286
  }
257
287
 
288
+ /**
289
+ * Resolves a natural-language command-bar query into runnable command-bar
290
+ * inputs. Requires a verified session; free accounts are included. The
291
+ * request is bounded client-side so a stalled upstream cannot hold the
292
+ * command bar in its loading state.
293
+ */
294
+ async assistCommand(
295
+ query: string,
296
+ commands: AssistCommandDescriptor[],
297
+ options?: { signal?: AbortSignal },
298
+ ): Promise<AssistCommandResponse> {
299
+ const controller = new AbortController();
300
+ const callerSignal = options?.signal;
301
+ const abortFromCaller = () => controller.abort(callerSignal?.reason);
302
+ if (callerSignal?.aborted) {
303
+ abortFromCaller();
304
+ } else {
305
+ callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
306
+ }
307
+
308
+ try {
309
+ const request = this.request<AssistCommandResponse>("/assist/command", {
310
+ method: "POST",
311
+ body: JSON.stringify({
312
+ query: query.trim().slice(0, ASSIST_QUERY_MAX_LENGTH),
313
+ commands: commands.slice(0, ASSIST_COMMAND_LIMIT),
314
+ }),
315
+ signal: controller.signal,
316
+ });
317
+ return await withDeadline(
318
+ request,
319
+ ASSIST_REQUEST_TIMEOUT_MS,
320
+ `Assist request timed out after ${ASSIST_REQUEST_TIMEOUT_MS}ms`,
321
+ (error) => controller.abort(error),
322
+ );
323
+ } finally {
324
+ callerSignal?.removeEventListener("abort", abortFromCaller);
325
+ }
326
+ }
327
+
258
328
  async getChannels(): Promise<ChatChannel[]> {
259
329
  return this.chat.getChannels();
260
330
  }
@@ -75,6 +75,10 @@ export interface AuthUser {
75
75
  emailVerified: boolean;
76
76
  image: string | null;
77
77
  plan?: "free" | "pro";
78
+ /** ISO timestamp when the free Pro trial ends; null once it has lapsed or was never started. */
79
+ trialEndsAt?: string | null;
80
+ /** Plan the server actually entitles right now, i.e. `plan` upgraded to "pro" while a trial is running. */
81
+ effectivePlan?: "free" | "pro";
78
82
  company?: string | null;
79
83
  title?: string | null;
80
84
  bio?: string | null;
@@ -101,6 +105,10 @@ export interface AccountProfile {
101
105
  email: string;
102
106
  emailVerified: boolean;
103
107
  plan: "free" | "pro";
108
+ /** ISO timestamp when the free Pro trial ends; null once it has lapsed or was never started. */
109
+ trialEndsAt?: string | null;
110
+ /** Plan the server actually entitles right now, i.e. `plan` upgraded to "pro" while a trial is running. */
111
+ effectivePlan?: "free" | "pro";
104
112
  username: string | null;
105
113
  name: string;
106
114
  company: string | null;
@@ -159,6 +167,47 @@ export interface BuildoutTokenResponse {
159
167
  expiresAt: string;
160
168
  }
161
169
 
170
+ export interface CloudPricingTier {
171
+ /** Charged amount, in integer cents. */
172
+ amount: number;
173
+ /** List price the charged amount is discounted from, in integer cents. */
174
+ anchorAmount: number;
175
+ }
176
+
177
+ /** Public `/pricing` payload; no session required. */
178
+ export interface CloudPricing {
179
+ currency: "usd";
180
+ trialDays: number;
181
+ /** When false the anchor amount is the price, so it is shown without a strikethrough. */
182
+ founding: boolean;
183
+ monthly: CloudPricingTier;
184
+ yearly: CloudPricingTier;
185
+ }
186
+
187
+ /** One command-bar prefix described for `/assist/command`. */
188
+ export interface AssistCommandDescriptor {
189
+ prefix: string;
190
+ name: string;
191
+ description?: string;
192
+ arg?: {
193
+ placeholder?: string;
194
+ kind: "text" | "ticker" | "ticker-list";
195
+ };
196
+ }
197
+
198
+ /** A command-bar line the assistant believes answers the query. */
199
+ export interface AssistCommandCandidate {
200
+ /** Exact command-bar text to run, e.g. "G NVDA AMD". */
201
+ input: string;
202
+ title: string;
203
+ prefix: string;
204
+ confidence: number;
205
+ }
206
+
207
+ export interface AssistCommandResponse {
208
+ candidates: AssistCommandCandidate[];
209
+ }
210
+
162
211
  export type AccountProfileUpdate = Partial<{
163
212
  username: string;
164
213
  name: string;
@@ -18,6 +18,14 @@ describe("desktop deeplinks", () => {
18
18
  });
19
19
  });
20
20
 
21
+ test("routes the post-checkout success link to account management", () => {
22
+ expect(resolveDesktopDeepLinkAction("gloomberb://cloud/success")).toEqual({
23
+ type: "open-account-management",
24
+ route: { kind: "cloud-success", week: null },
25
+ message: "Pro is active.",
26
+ });
27
+ });
28
+
21
29
  test("routes ticker links with arbitrary registered tab ids", () => {
22
30
  expect(resolveDesktopDeepLinkAction("gloomberb://ticker/NVDA?tab=analyst-research")).toEqual({
23
31
  type: "open-ticker",
@@ -10,9 +10,10 @@ import {
10
10
  import type { DesktopDeepLinkBridge } from "../../types/desktop-deeplink";
11
11
  import type { DesktopWindowBridge } from "../../types/desktop-window";
12
12
  import { requestAccountManagementTab } from "../../plugins/builtin/account-management/navigation";
13
+ import { chatController } from "../../plugins/builtin/chat/controller";
13
14
 
14
15
  type CloudDeepLinkRoute = {
15
- kind: "cloud-alerts" | "cloud-emails" | "cloud-roundup";
16
+ kind: "cloud-alerts" | "cloud-emails" | "cloud-roundup" | "cloud-success";
16
17
  week: string | null;
17
18
  };
18
19
 
@@ -159,6 +160,13 @@ function parseCloudDeepLink(parsed: ParsedGloomUrl): DesktopDeepLinkAction {
159
160
  message: "Opened email settings.",
160
161
  };
161
162
  }
163
+ if (route === "success") {
164
+ return {
165
+ type: "open-account-management",
166
+ route: { kind: "cloud-success", week: null },
167
+ message: "Pro is active.",
168
+ };
169
+ }
162
170
  return { type: "unsupported", message: "Unsupported Gloomberb cloud link." };
163
171
  }
164
172
 
@@ -465,6 +473,11 @@ export function handleDesktopDeepLink(rawUrl: string, options: DesktopDeepLinkHa
465
473
  case "open-account-management":
466
474
  if (!requirePane(options.pluginRegistry, "account-management", "Account management is unavailable.")) return;
467
475
  if (action.route.kind === "cloud-emails") requestAccountManagementTab("emails");
476
+ if (action.route.kind === "cloud-success") {
477
+ requestAccountManagementTab("pro");
478
+ // Checkout just completed: re-read the session so the new plan shows at once.
479
+ void chatController.refreshSession().catch(() => {});
480
+ }
468
481
  options.pluginRegistry.showPane("account-management");
469
482
  notifySuccess(options.pluginRegistry, action.message);
470
483
  return;