gloomberb 0.9.6 → 0.9.8

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 (130) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/auth.ts +20 -0
  3. package/src/api-client/index.ts +48 -0
  4. package/src/api-client/types.ts +45 -0
  5. package/src/app/runtime/desktop-deeplink.test.ts +87 -0
  6. package/src/app/runtime/desktop-deeplink.ts +496 -0
  7. package/src/app.tsx +34 -0
  8. package/src/components/chart/bar-chart-renderer.test.ts +87 -12
  9. package/src/components/chart/bar-chart-renderer.ts +111 -55
  10. package/src/components/chart/static/bar-chart-surface.tsx +104 -20
  11. package/src/components/chart/stock-chart/footer.test.ts +65 -0
  12. package/src/components/chart/stock-chart/footer.ts +68 -13
  13. package/src/components/chart/stock-chart/resolved.tsx +18 -4
  14. package/src/components/command-bar/pane-templates/items.ts +11 -3
  15. package/src/components/command-bar/routes/root/results.ts +1 -1
  16. package/src/components/command-bar/surface/index.test.tsx +52 -0
  17. package/src/components/command-bar/workflow/ops.ts +81 -20
  18. package/src/components/index.ts +1 -0
  19. package/src/components/toggle-list.tsx +29 -73
  20. package/src/components/ui/button.tsx +4 -1
  21. package/src/components/ui/checkbox.tsx +76 -0
  22. package/src/components/ui/choice-dialog.tsx +15 -3
  23. package/src/components/ui/frame.tsx +1 -1
  24. package/src/components/ui/index.ts +2 -0
  25. package/src/components/ui/message-composer.tsx +4 -0
  26. package/src/components/ui/native-select.tsx +4 -2
  27. package/src/market-data/coordinator/events.test.ts +49 -0
  28. package/src/market-data/coordinator/events.ts +25 -4
  29. package/src/market-data/coordinator/index.test.ts +223 -0
  30. package/src/market-data/coordinator/index.ts +40 -3
  31. package/src/market-data/coordinator/quotes.ts +13 -5
  32. package/src/market-data/hooks.tsx +17 -13
  33. package/src/market-data/market/freshness.ts +58 -1
  34. package/src/market-data/quotes/freshness.test.ts +49 -1
  35. package/src/market-data/quotes/freshness.ts +8 -0
  36. package/src/market-data/quotes/resolution.test.ts +185 -0
  37. package/src/market-data/quotes/resolution.ts +94 -8
  38. package/src/market-data/request-types.test.ts +61 -1
  39. package/src/market-data/request-types.ts +42 -3
  40. package/src/pane-settings.test.ts +1 -0
  41. package/src/plugins/builtin/account-management/footer.ts +2 -5
  42. package/src/plugins/builtin/account-management/form-components.tsx +260 -24
  43. package/src/plugins/builtin/account-management/keyboard.ts +32 -2
  44. package/src/plugins/builtin/account-management/model.test.ts +99 -4
  45. package/src/plugins/builtin/account-management/model.ts +263 -3
  46. package/src/plugins/builtin/account-management/pane.tsx +784 -155
  47. package/src/plugins/builtin/chat/chat.test.tsx +125 -0
  48. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
  49. package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
  50. package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
  51. package/src/plugins/builtin/chat/content/composer.tsx +84 -1
  52. package/src/plugins/builtin/chat/content/index.tsx +154 -16
  53. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
  54. package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
  55. package/src/plugins/builtin/chat/content/mentions.ts +88 -0
  56. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
  57. package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
  58. package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
  59. package/src/plugins/builtin/chat/content/transcript.tsx +0 -4
  60. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
  61. package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
  62. package/src/plugins/builtin/chat/sidebar.test.tsx +69 -2
  63. package/src/plugins/builtin/chat/sidebar.tsx +26 -2
  64. package/src/plugins/builtin/chat/test-harness.tsx +21 -11
  65. package/src/plugins/builtin/cloud/plugin.tsx +7 -0
  66. package/src/plugins/builtin/correlation/index.tsx +4 -1
  67. package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
  68. package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
  69. package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
  70. package/src/plugins/builtin/portfolio-list/index.test.ts +102 -1
  71. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  72. package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
  73. package/src/plugins/builtin/portfolio-list/pane/data.ts +62 -1
  74. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +414 -8
  75. package/src/plugins/builtin/portfolio-list/pane/index.tsx +24 -32
  76. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +131 -22
  77. package/src/plugins/builtin/portfolio-list/settings.ts +36 -1
  78. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +43 -7
  79. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
  80. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
  81. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
  82. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
  83. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
  84. package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
  85. package/src/plugins/ibkr/gateway/market-data.ts +12 -5
  86. package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
  87. package/src/plugins/registry/context.ts +15 -0
  88. package/src/plugins/registry/index.ts +31 -0
  89. package/src/renderers/electrobun/bun/index.ts +42 -0
  90. package/src/renderers/electrobun/shared/protocol.ts +5 -0
  91. package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
  92. package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
  93. package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
  94. package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
  95. package/src/renderers/electrobun/view/host/box.tsx +0 -2
  96. package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
  97. package/src/renderers/electrobun/view/host/text.tsx +1 -2
  98. package/src/renderers/electrobun/view/main.tsx +3 -0
  99. package/src/renderers/electrobun/view/ui-host.tsx +2 -0
  100. package/src/sources/provider-router/batches.ts +11 -3
  101. package/src/sources/provider-router/cached-financials.test.ts +123 -0
  102. package/src/sources/provider-router/financial-routes.ts +55 -5
  103. package/src/sources/provider-router/financials.test.ts +49 -0
  104. package/src/sources/provider-router/financials.ts +40 -3
  105. package/src/sources/provider-router/index.test.ts +147 -0
  106. package/src/sources/provider-router/primary.ts +17 -3
  107. package/src/sources/provider-router/streaming.ts +6 -1
  108. package/src/sources/sec-edgar.test.ts +42 -0
  109. package/src/sources/sec-edgar.ts +1 -2
  110. package/src/sources/yahoo-finance/mappers.test.ts +29 -0
  111. package/src/sources/yahoo-finance/mappers.ts +2 -1
  112. package/src/sources/yahoo-finance/requests.ts +2 -1
  113. package/src/sources/yahoo-finance/snapshots.ts +13 -2
  114. package/src/sources/yahoo-finance.ts +6 -2
  115. package/src/state/hooks/quote-streaming.test.ts +14 -1
  116. package/src/state/hooks/quote-streaming.ts +5 -4
  117. package/src/sync/controller.ts +296 -0
  118. package/src/sync/core-contributors.test.ts +251 -0
  119. package/src/sync/core-contributors.ts +400 -0
  120. package/src/sync/profile-analytics.ts +39 -0
  121. package/src/sync/react.ts +53 -0
  122. package/src/sync/types.ts +77 -0
  123. package/src/types/config.ts +1 -0
  124. package/src/types/desktop-deeplink.ts +7 -0
  125. package/src/types/plugin.ts +3 -0
  126. package/src/ui/host.tsx +14 -1
  127. package/src/ui/index.tsx +1 -0
  128. package/src/utils/exchanges.test.ts +11 -1
  129. package/src/utils/exchanges.ts +5 -1
  130. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Market research and portfolio tracker for the terminal",
5
5
  "module": "src/index.tsx",
6
6
  "type": "module",
@@ -38,6 +38,11 @@ export class CloudAuthApi {
38
38
  username: typeof user.username === "string" ? user.username : null,
39
39
  emailVerified: user.emailVerified === true,
40
40
  image: typeof user.image === "string" ? user.image : null,
41
+ syncEnabled: user.syncEnabled === false ? false : true,
42
+ weeklyRoundupEnabled: user.weeklyRoundupEnabled === false ? false : true,
43
+ positionAlertsEnabled: user.positionAlertsEnabled === false ? false : true,
44
+ lastSyncAt: typeof user.lastSyncAt === "string" ? user.lastSyncAt : null,
45
+ lastRoundupEmailAt: typeof user.lastRoundupEmailAt === "string" ? user.lastRoundupEmailAt : null,
41
46
  createdAt: typeof user.createdAt === "string" ? user.createdAt : "",
42
47
  updatedAt: typeof user.updatedAt === "string" ? user.updatedAt : "",
43
48
  });
@@ -148,6 +153,12 @@ export class CloudAuthApi {
148
153
  xAccount: profile.xAccount,
149
154
  sharedPortfolioId: profile.sharedPortfolioId,
150
155
  acceptUnknownDms: profile.acceptUnknownDms,
156
+ portfolioAnalytics: profile.portfolioAnalytics,
157
+ syncEnabled: profile.syncEnabled,
158
+ weeklyRoundupEnabled: profile.weeklyRoundupEnabled,
159
+ positionAlertsEnabled: profile.positionAlertsEnabled,
160
+ lastSyncAt: profile.lastSyncAt,
161
+ lastRoundupEmailAt: profile.lastRoundupEmailAt,
151
162
  updatedAt: profile.updatedAt ?? currentUser.updatedAt,
152
163
  }));
153
164
  }
@@ -164,4 +175,13 @@ export class CloudAuthApi {
164
175
  }),
165
176
  });
166
177
  }
178
+
179
+ async deleteAccount(): Promise<void> {
180
+ await this.options.request("/account", {
181
+ method: "DELETE",
182
+ body: JSON.stringify({}),
183
+ });
184
+ this.options.setSessionToken(null);
185
+ this.options.setCurrentUser(null);
186
+ }
167
187
  }
@@ -46,8 +46,12 @@ import type {
46
46
  CloudMarketBatchTarget,
47
47
  CloudMarketBatchPayload,
48
48
  CloudVerificationResponse,
49
+ CloudRoundupPreviewResponse,
50
+ CloudSyncPushResponse,
51
+ CloudSyncSnapshotResponse,
49
52
  QuoteStreamTarget,
50
53
  } from "./types";
54
+ import type { SyncSettings, SyncSnapshot } from "../sync/types";
51
55
 
52
56
  export type * from "./types";
53
57
  export { setCloudApiFetchTransport } from "./request";
@@ -198,10 +202,54 @@ class GloomApiClient {
198
202
  return this.auth.updateAccountProfile(update);
199
203
  }
200
204
 
205
+ async getSyncSnapshot(): Promise<CloudSyncSnapshotResponse> {
206
+ return this.request<CloudSyncSnapshotResponse>("/sync/snapshot", { method: "GET" });
207
+ }
208
+
209
+ async putSyncSnapshot(snapshot: SyncSnapshot, options?: { baseRevision?: number | null }): Promise<CloudSyncPushResponse> {
210
+ return this.request<CloudSyncPushResponse>("/sync/snapshot", {
211
+ method: "PUT",
212
+ body: JSON.stringify({
213
+ snapshot,
214
+ baseRevision: options?.baseRevision ?? null,
215
+ }),
216
+ });
217
+ }
218
+
219
+ async updateSyncSettings(update: Partial<SyncSettings>): Promise<SyncSettings> {
220
+ const result = await this.request<{ settings: SyncSettings }>("/sync/settings", {
221
+ method: "PATCH",
222
+ body: JSON.stringify(update),
223
+ });
224
+ if (this.currentUser) {
225
+ this.currentUser = {
226
+ ...this.currentUser,
227
+ syncEnabled: result.settings.syncEnabled,
228
+ weeklyRoundupEnabled: result.settings.weeklyRoundupEnabled,
229
+ positionAlertsEnabled: result.settings.positionAlertsEnabled,
230
+ lastSyncAt: result.settings.lastSyncAt ?? this.currentUser.lastSyncAt,
231
+ lastRoundupEmailAt: result.settings.lastRoundupEmailAt ?? this.currentUser.lastRoundupEmailAt,
232
+ };
233
+ }
234
+ return result.settings;
235
+ }
236
+
237
+ async getRoundupPreview(): Promise<CloudRoundupPreviewResponse> {
238
+ return this.request<CloudRoundupPreviewResponse>("/sync/roundup/preview", { method: "POST", body: JSON.stringify({}) });
239
+ }
240
+
241
+ async sendRoundupTestEmail(): Promise<CloudRoundupPreviewResponse> {
242
+ return this.request<CloudRoundupPreviewResponse>("/sync/roundup/test-email", { method: "POST", body: JSON.stringify({}) });
243
+ }
244
+
201
245
  async changePassword(currentPassword: string, newPassword: string): Promise<void> {
202
246
  return this.auth.changePassword(currentPassword, newPassword);
203
247
  }
204
248
 
249
+ async deleteAccount(): Promise<void> {
250
+ return this.auth.deleteAccount();
251
+ }
252
+
205
253
  async getChannels(): Promise<ChatChannel[]> {
206
254
  return this.chat.getChannels();
207
255
  }
@@ -10,6 +10,7 @@ import type {
10
10
  Quote,
11
11
  TickerFinancials,
12
12
  } from "../types/financials";
13
+ import type { SyncSettings, SyncSnapshot } from "../sync/types";
13
14
 
14
15
  export interface ChatUserSummary {
15
16
  id: string;
@@ -20,6 +21,7 @@ export interface ChatUserSummary {
20
21
  title?: string | null;
21
22
  profilePublic?: boolean;
22
23
  acceptUnknownDms?: boolean;
24
+ portfolioAnalytics?: PublicPortfolioAnalytics | null;
23
25
  }
24
26
 
25
27
  export interface ChatMessage {
@@ -83,6 +85,12 @@ export interface AuthUser {
83
85
  xAccount?: string | null;
84
86
  sharedPortfolioId?: string | null;
85
87
  acceptUnknownDms?: boolean;
88
+ portfolioAnalytics?: PublicPortfolioAnalytics | null;
89
+ syncEnabled?: boolean;
90
+ weeklyRoundupEnabled?: boolean;
91
+ positionAlertsEnabled?: boolean;
92
+ lastSyncAt?: string | null;
93
+ lastRoundupEmailAt?: string | null;
86
94
  createdAt: string;
87
95
  updatedAt: string;
88
96
  }
@@ -104,9 +112,20 @@ export interface AccountProfile {
104
112
  xAccount: string | null;
105
113
  sharedPortfolioId: string | null;
106
114
  acceptUnknownDms: boolean;
115
+ portfolioAnalytics?: PublicPortfolioAnalytics | null;
116
+ syncEnabled: boolean;
117
+ weeklyRoundupEnabled: boolean;
118
+ positionAlertsEnabled: boolean;
119
+ lastSyncAt: string | null;
120
+ lastRoundupEmailAt: string | null;
107
121
  updatedAt: string | null;
108
122
  }
109
123
 
124
+ export interface PublicPortfolioAnalytics {
125
+ oneYearReturn?: number | null;
126
+ spyBeta?: number | null;
127
+ }
128
+
110
129
  export interface BuildoutAccountResponse {
111
130
  user: {
112
131
  id: string;
@@ -151,8 +170,34 @@ export type AccountProfileUpdate = Partial<{
151
170
  xAccount: string | null;
152
171
  sharedPortfolioId: string | null;
153
172
  acceptUnknownDms: boolean;
173
+ portfolioAnalytics: PublicPortfolioAnalytics | null;
174
+ syncEnabled: boolean;
175
+ weeklyRoundupEnabled: boolean;
176
+ positionAlertsEnabled: boolean;
154
177
  }>;
155
178
 
179
+ export interface CloudSyncSnapshotResponse {
180
+ snapshot: SyncSnapshot | null;
181
+ revision: number | null;
182
+ updatedAt: string | null;
183
+ settings: SyncSettings;
184
+ }
185
+
186
+ export interface CloudSyncPushResponse {
187
+ revision: number;
188
+ updatedAt: string;
189
+ settings: SyncSettings;
190
+ }
191
+
192
+ export interface CloudRoundupPreviewResponse {
193
+ subject: string;
194
+ text: string;
195
+ html: string;
196
+ sender: string;
197
+ replyTo: string;
198
+ recipient: string;
199
+ }
200
+
156
201
  export interface CloudQuotePayload extends Quote {
157
202
  providerId: "gloomberb-cloud";
158
203
  dataSource: "live" | "delayed";
@@ -0,0 +1,87 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { resolveDesktopDeepLinkAction } from "./desktop-deeplink";
3
+
4
+ describe("desktop deeplinks", () => {
5
+ test("routes cloud roundup links to account management", () => {
6
+ expect(resolveDesktopDeepLinkAction("gloomberb://cloud/roundup?week=2026-07-03")).toEqual({
7
+ type: "open-account-management",
8
+ route: { kind: "cloud-roundup", week: "2026-07-03" },
9
+ message: "Opened weekly roundup settings for 2026-07-03.",
10
+ });
11
+ });
12
+
13
+ test("routes cloud alert links to account management", () => {
14
+ expect(resolveDesktopDeepLinkAction("gloomberb://cloud/alerts")).toEqual({
15
+ type: "open-account-management",
16
+ route: { kind: "cloud-alerts", week: null },
17
+ message: "Opened portfolio alert settings.",
18
+ });
19
+ });
20
+
21
+ test("routes ticker links with arbitrary registered tab ids", () => {
22
+ expect(resolveDesktopDeepLinkAction("gloomberb://ticker/NVDA?tab=analyst-research")).toEqual({
23
+ type: "open-ticker",
24
+ symbol: "NVDA",
25
+ tabId: "analyst-research",
26
+ message: "Opened NVDA analyst-research tab.",
27
+ });
28
+ });
29
+
30
+ test("routes portfolio and watchlist links", () => {
31
+ expect(resolveDesktopDeepLinkAction("gloomberb://portfolio/main")).toEqual({
32
+ type: "open-collection",
33
+ kind: "portfolio",
34
+ collectionId: "main",
35
+ message: "Opened portfolio main.",
36
+ });
37
+ expect(resolveDesktopDeepLinkAction("gloomberb://watchlist/favorites")).toEqual({
38
+ type: "open-collection",
39
+ kind: "watchlist",
40
+ collectionId: "favorites",
41
+ message: "Opened watchlist favorites.",
42
+ });
43
+ });
44
+
45
+ test("routes alert links with structured values", () => {
46
+ expect(resolveDesktopDeepLinkAction("gloomberb://alert/new?symbol=nvda&side=above&price=$200")).toEqual({
47
+ type: "create-alert",
48
+ values: { symbol: "NVDA", condition: "above", price: "200" },
49
+ message: "Created NVDA above 200 alert.",
50
+ });
51
+ });
52
+
53
+ test("routes chat links", () => {
54
+ expect(resolveDesktopDeepLinkAction("gloomberb://chat/channel/everyone")).toEqual({
55
+ type: "open-chat-channel",
56
+ channelId: "everyone",
57
+ message: "Opened chat everyone.",
58
+ });
59
+ expect(resolveDesktopDeepLinkAction("gloomberb://chat/dm?users=@vince,@alex")).toEqual({
60
+ type: "open-chat-dm",
61
+ participants: "@vince,@alex",
62
+ message: "Opened DM.",
63
+ });
64
+ });
65
+
66
+ test("routes news links", () => {
67
+ expect(resolveDesktopDeepLinkAction("gloomberb://news?ticker=7203.T")).toEqual({
68
+ type: "open-news",
69
+ kind: "ticker",
70
+ symbol: "7203.T",
71
+ message: "Opened 7203.T news.",
72
+ });
73
+ expect(resolveDesktopDeepLinkAction("gloomberb://news/breaking")).toEqual({
74
+ type: "open-news",
75
+ kind: "breaking",
76
+ symbol: null,
77
+ message: "Opened breaking news.",
78
+ });
79
+ });
80
+
81
+ test("rejects command-bar, non-gloomberb, and malformed links", () => {
82
+ expect(resolveDesktopDeepLinkAction("gloomberb://command?query=profile").type).toBe("unsupported");
83
+ expect(resolveDesktopDeepLinkAction("gloomberb://search/NVDA").type).toBe("unsupported");
84
+ expect(resolveDesktopDeepLinkAction("https://gloom.sh/cloud").type).toBe("unsupported");
85
+ expect(resolveDesktopDeepLinkAction("not a url").type).toBe("unsupported");
86
+ });
87
+ });