gloomberb 0.11.0 → 0.11.1

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 (222) hide show
  1. package/README.md +29 -7
  2. package/README.zh-CN.md +6 -6
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +47 -0
  6. package/src/api-client/index.test.ts +77 -0
  7. package/src/api-client/index.ts +78 -4
  8. package/src/api-client/paths.ts +73 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +128 -0
  13. package/src/app/runtime/update.ts +8 -6
  14. package/src/app.tsx +12 -5
  15. package/src/architecture/import-boundaries.test.ts +5 -1
  16. package/src/capabilities/factories.ts +4 -0
  17. package/src/cli/index.ts +10 -4
  18. package/src/cli/registry.ts +1 -1
  19. package/src/components/chart/composite/composite-chart.tsx +4 -3
  20. package/src/components/chart/composite/format.test.ts +34 -0
  21. package/src/components/chart/composite/format.ts +33 -10
  22. package/src/components/chart/core/axis-format.ts +13 -3
  23. package/src/components/chart/core/index.test.ts +7 -0
  24. package/src/components/chart/core/renderer.ts +3 -4
  25. package/src/components/command-bar/surface/index.tsx +4 -1
  26. package/src/components/company-logo.test.ts +19 -0
  27. package/src/components/company-logo.tsx +60 -0
  28. package/src/components/feed-data-table/stack-view.tsx +7 -1
  29. package/src/components/index.ts +1 -0
  30. package/src/components/layout/detached-pane-shell.tsx +11 -5
  31. package/src/components/layout/header.tsx +5 -5
  32. package/src/components/layout/shell/index.tsx +3 -1
  33. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  34. package/src/components/onboarding/account-step/index.tsx +1 -1
  35. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  36. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  37. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  38. package/src/components/onboarding/wizard-account.ts +4 -23
  39. package/src/components/table-view-shared.test.ts +11 -0
  40. package/src/components/table-view-shared.tsx +26 -0
  41. package/src/components/ui/data-table/index.tsx +19 -1
  42. package/src/components/ui/fields.tsx +5 -1
  43. package/src/i18n/es.ts +10 -1
  44. package/src/i18n/ja.ts +10 -1
  45. package/src/i18n/ko.ts +10 -1
  46. package/src/i18n/zh-cn.ts +10 -1
  47. package/src/i18n/zh-tw.ts +10 -1
  48. package/src/market-data/coordinator/index.test.ts +2 -2
  49. package/src/market-data/coordinator/index.ts +1 -1
  50. package/src/market-data/market/format.test.ts +7 -0
  51. package/src/market-data/market/freshness.ts +5 -2
  52. package/src/market-data/quotes/freshness.test.ts +16 -0
  53. package/src/news/aggregator.test.ts +92 -3
  54. package/src/news/aggregator.ts +98 -18
  55. package/src/news/hooks.test.tsx +2 -0
  56. package/src/news/hooks.ts +27 -1
  57. package/src/news/news-model.ts +5 -2
  58. package/src/news/types.ts +7 -0
  59. package/src/plugins/broker-sync/broker-sync.test.ts +29 -0
  60. package/src/plugins/broker-sync/normalize.ts +35 -5
  61. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  62. package/src/plugins/builtin/alerts/alert-engine.test.ts +38 -1
  63. package/src/plugins/builtin/alerts/alert-engine.ts +30 -1
  64. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  65. package/src/plugins/builtin/alerts/index.tsx +7 -6
  66. package/src/plugins/builtin/alerts/pane.tsx +49 -1
  67. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  68. package/src/plugins/builtin/alerts/types.ts +1 -0
  69. package/src/plugins/builtin/analytics/index.tsx +8 -1
  70. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  71. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  72. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  73. package/src/plugins/builtin/chart-composer/pane.tsx +22 -1
  74. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  75. package/src/plugins/builtin/chat/controller/index.ts +6 -8
  76. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  77. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  78. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  79. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -9
  80. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  81. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  82. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  83. package/src/plugins/builtin/chat/controller.test.ts +78 -0
  84. package/src/plugins/builtin/chat/profile-popover.ts +1 -1
  85. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  86. package/src/plugins/builtin/cloud/auth-commands.ts +21 -47
  87. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  88. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  89. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +34 -3
  90. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  91. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  92. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  93. package/src/plugins/builtin/cloud/index.tsx +2 -0
  94. package/src/plugins/builtin/cloud/plugin.tsx +3 -23
  95. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  96. package/src/plugins/builtin/cloud-tweets/registration.ts +6 -1
  97. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  98. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  99. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  100. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  101. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  102. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  103. package/src/plugins/builtin/earnings/index.tsx +5 -3
  104. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  105. package/src/plugins/builtin/earnings/model.ts +17 -15
  106. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  107. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  108. package/src/plugins/builtin/insider/index.tsx +27 -11
  109. package/src/plugins/builtin/news/index.tsx +10 -6
  110. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  111. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  112. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  113. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  114. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  115. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  116. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  117. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  118. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  119. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  120. package/src/plugins/builtin/notes/files.ts +80 -0
  121. package/src/plugins/builtin/notes/index.tsx +3 -0
  122. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  123. package/src/plugins/builtin/notes/sync.ts +98 -0
  124. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +20 -0
  125. package/src/plugins/builtin/portfolio-list/account-metrics.ts +11 -6
  126. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  127. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  128. package/src/plugins/builtin/portfolio-list/summary/index.tsx +14 -5
  129. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  130. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +303 -0
  131. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +485 -0
  132. package/src/plugins/builtin/research/index.test.ts +14 -0
  133. package/src/plugins/builtin/research/index.tsx +25 -0
  134. package/src/plugins/builtin/sec/index.tsx +24 -9
  135. package/src/plugins/builtin/sectors/index.tsx +14 -0
  136. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  137. package/src/plugins/builtin/shared/public-share.ts +23 -0
  138. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  139. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  140. package/src/plugins/builtin/substack/api/types.ts +2 -0
  141. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  142. package/src/plugins/builtin/substack/pane.tsx +55 -12
  143. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  144. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  145. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  146. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  147. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  148. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  149. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  150. package/src/plugins/catalog-browser.test.ts +68 -0
  151. package/src/plugins/catalog-browser.ts +134 -0
  152. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  153. package/src/plugins/ibkr/flex/index.ts +1 -1
  154. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  155. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  156. package/src/plugins/prediction-markets/controller/index.ts +4 -1
  157. package/src/plugins/prediction-markets/pane.tsx +8 -0
  158. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  159. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  160. package/src/renderers/browser/app-services.ts +61 -0
  161. package/src/renderers/browser/cloud-transport.test.ts +32 -0
  162. package/src/renderers/browser/cloud-transport.ts +29 -0
  163. package/src/renderers/browser/config-host.ts +62 -0
  164. package/src/renderers/browser/error-boundary.tsx +23 -0
  165. package/src/renderers/browser/main.tsx +61 -0
  166. package/src/renderers/browser/persistence.ts +74 -0
  167. package/src/renderers/browser/storage.test.ts +66 -0
  168. package/src/renderers/browser/storage.ts +50 -0
  169. package/src/renderers/browser/ticker-repository.ts +59 -0
  170. package/src/renderers/browser/ui-host.tsx +40 -0
  171. package/src/renderers/cloudflare/worker.test.ts +133 -0
  172. package/src/renderers/cloudflare/worker.ts +115 -0
  173. package/src/renderers/electrobun/view/app-services.ts +14 -3
  174. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  175. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  176. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  177. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  178. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  179. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  180. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  181. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  182. package/src/renderers/electrobun/view/main.tsx +2 -0
  183. package/src/renderers/electrobun/view/ui-host.tsx +3 -137
  184. package/src/renderers/opentui/start.tsx +2 -0
  185. package/src/renderers/share/main.tsx +54 -0
  186. package/src/renderers/share/styles.css +19 -0
  187. package/src/renderers/share/view.test.tsx +22 -0
  188. package/src/renderers/share/view.tsx +79 -0
  189. package/src/shares/api.test.ts +56 -0
  190. package/src/shares/api.ts +103 -0
  191. package/src/shares/chart-snapshot.test.ts +34 -0
  192. package/src/shares/chart-snapshot.ts +28 -0
  193. package/src/shares/payload.ts +104 -0
  194. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  195. package/src/sources/gloomberb-cloud/index.ts +86 -14
  196. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  197. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  198. package/src/sources/provider-router/history.test.ts +2 -2
  199. package/src/sources/provider-router/news.test.ts +34 -0
  200. package/src/sources/provider-router/news.ts +15 -1
  201. package/src/sources/sec-edgar.test.ts +59 -0
  202. package/src/sources/sec-edgar.ts +63 -23
  203. package/src/sources/yahoo-finance/mappers.ts +3 -0
  204. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  205. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  206. package/src/sources/yahoo-finance/symbols.ts +13 -8
  207. package/src/sources/yahoo-finance.test.ts +1 -1
  208. package/src/sources/yahoo-finance.ts +6 -2
  209. package/src/sync/baseline.ts +32 -0
  210. package/src/sync/controller.test.ts +67 -0
  211. package/src/sync/controller.ts +32 -1
  212. package/src/sync/core-contributors.test.ts +44 -0
  213. package/src/sync/core-contributors.ts +59 -10
  214. package/src/sync/react.ts +9 -2
  215. package/src/sync/types.ts +12 -0
  216. package/src/tickers/search/index.test.ts +27 -0
  217. package/src/tickers/search/upsert.ts +10 -0
  218. package/src/types/capability-route-source.ts +2 -1
  219. package/src/ui/host.tsx +4 -0
  220. package/src/utils/price-history.ts +11 -10
  221. package/src/utils/sec.test.ts +6 -0
  222. package/src/utils/sec.ts +1 -1
@@ -19,7 +19,9 @@ export type CloudFredSeriesParams = {
19
19
  export type CloudCongressHouseParams = {
20
20
  year?: number;
21
21
  limit?: number;
22
+ offset?: number;
22
23
  filingLimit?: number;
24
+ filingOffset?: number;
23
25
  member?: string;
24
26
  ticker?: string;
25
27
  refresh?: boolean;
@@ -60,6 +62,23 @@ export type CloudTweetSearchParams = {
60
62
  hours?: number;
61
63
  };
62
64
 
65
+ const LOGO_SYMBOL_RE = /^[A-Z0-9][A-Z0-9._^:-]{0,23}$/;
66
+ const CRYPTO_QUOTE = /[-/](USD|USDT|USDC|EUR|GBP|BTC)$/;
67
+
68
+ export type CloudLogoKind = "ticker" | "crypto";
69
+
70
+ export function normalizeCloudLogoSymbol(kind: CloudLogoKind, symbol: string): string | null {
71
+ let normalized = symbol.trim().toUpperCase();
72
+ if (kind === "crypto") normalized = normalized.replace(CRYPTO_QUOTE, "");
73
+ return LOGO_SYMBOL_RE.test(normalized) ? normalized : null;
74
+ }
75
+
76
+ export function cloudLogoPath(kind: CloudLogoKind, symbol: string): string | null {
77
+ const normalized = normalizeCloudLogoSymbol(kind, symbol);
78
+ if (!normalized) return null;
79
+ return `/cloud/logos/${kind}/${encodeURIComponent(normalized)}`;
80
+ }
81
+
63
82
  function appendQuery(path: string, search: URLSearchParams): string {
64
83
  const query = search.toString();
65
84
  return `${path}${query ? `?${query}` : ""}`;
@@ -118,17 +137,71 @@ export function cloudFredSeriesPath(seriesId: string, params: CloudFredSeriesPar
118
137
  return appendQuery(`/cloud/econ/series/${encodeURIComponent(seriesId)}`, search);
119
138
  }
120
139
 
140
+ export type CloudSecFilingsParams = {
141
+ ticker: string;
142
+ limit?: number;
143
+ offset?: number;
144
+ };
145
+
146
+ export type CloudSecFilingParams = {
147
+ cik?: string;
148
+ accession?: string;
149
+ form?: string;
150
+ primaryDocument?: string;
151
+ primaryDocumentUrl?: string;
152
+ filingUrl?: string;
153
+ };
154
+
121
155
  export function cloudCongressHousePath(params: CloudCongressHouseParams = {}): string {
122
156
  const search = new URLSearchParams();
123
157
  if (params.year != null) search.set("year", String(params.year));
124
158
  if (params.limit != null) search.set("limit", String(params.limit));
159
+ if (params.offset != null) search.set("offset", String(params.offset));
125
160
  if (params.filingLimit != null) search.set("filingLimit", String(params.filingLimit));
161
+ if (params.filingOffset != null) search.set("filingOffset", String(params.filingOffset));
126
162
  if (params.member) search.set("member", params.member);
127
163
  if (params.ticker) search.set("ticker", params.ticker);
128
164
  if (params.refresh != null) search.set("refresh", String(params.refresh));
129
165
  return appendQuery("/cloud/congress/house", search);
130
166
  }
131
167
 
168
+ export function cloudSecFilingsPath(params: CloudSecFilingsParams): string {
169
+ const search = new URLSearchParams({ ticker: params.ticker });
170
+ if (params.limit != null) search.set("limit", String(params.limit));
171
+ if (params.offset != null) search.set("offset", String(params.offset));
172
+ return appendQuery("/cloud/sec/filings", search);
173
+ }
174
+
175
+ export function cloudSecFilingDocumentsPath(params: CloudSecFilingParams): string {
176
+ const search = new URLSearchParams();
177
+ if (params.cik) search.set("cik", params.cik);
178
+ if (params.accession) search.set("accession", params.accession);
179
+ if (params.form) search.set("form", params.form);
180
+ if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
181
+ if (params.filingUrl) search.set("filingUrl", params.filingUrl);
182
+ return appendQuery("/cloud/sec/filing/documents", search);
183
+ }
184
+
185
+ export function cloudSecFilingContentPath(params: CloudSecFilingParams): string {
186
+ const search = new URLSearchParams();
187
+ if (params.cik) search.set("cik", params.cik);
188
+ if (params.accession) search.set("accession", params.accession);
189
+ if (params.form) search.set("form", params.form);
190
+ if (params.primaryDocument) search.set("primaryDocument", params.primaryDocument);
191
+ if (params.primaryDocumentUrl) search.set("primaryDocumentUrl", params.primaryDocumentUrl);
192
+ if (params.filingUrl) search.set("filingUrl", params.filingUrl);
193
+ return appendQuery("/cloud/sec/filing/content", search);
194
+ }
195
+
196
+ export function cloudSec13FPath(path: string, params: Record<string, string | number | undefined> = {}): string {
197
+ const search = new URLSearchParams();
198
+ for (const [key, value] of Object.entries(params)) {
199
+ if (value !== undefined) search.set(key, String(value));
200
+ }
201
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
202
+ return appendQuery(`/cloud/sec/13f/${normalized}`, search);
203
+ }
204
+
132
205
  export function cloudNewsPath(params: CloudNewsParams = {}): string {
133
206
  const search = new URLSearchParams();
134
207
  if (params.feed) search.set("feed", params.feed);
@@ -24,10 +24,14 @@ export function setCloudApiFetchTransport(transport: CloudApiFetchTransport | nu
24
24
 
25
25
  declare const __GLOOMBERB_API_URL__: string | undefined;
26
26
 
27
- function getCloudApiBaseUrl(): string {
27
+ export function getCloudApiBaseUrl(): string {
28
28
  // Browser bundles have no `process`; the build replaces this with a literal.
29
29
  const bundled = typeof __GLOOMBERB_API_URL__ === "string" ? __GLOOMBERB_API_URL__ : "";
30
- if (bundled) return bundled;
30
+ if (bundled) {
31
+ return typeof location !== "undefined" && bundled === location.origin
32
+ ? `${bundled}/api`
33
+ : bundled;
34
+ }
31
35
  if (typeof process === "undefined") {
32
36
  return DEFAULT_API_URL;
33
37
  }
@@ -43,6 +47,7 @@ export class CloudApiRequestTransport {
43
47
  private sessionToken: string | null = null;
44
48
  private sessionCookieName: SessionCookieName | null = null;
45
49
  private websocketToken: string | null = null;
50
+ private cookieSessionMode = false;
46
51
  private readonly fetchTransport: CloudApiFetchTransport | null;
47
52
  private readonly marketRequestTimeoutMs: number;
48
53
  private readonly connectionHealth: ConnectionHealthRegistry;
@@ -67,6 +72,14 @@ export class CloudApiRequestTransport {
67
72
  return this.websocketToken;
68
73
  }
69
74
 
75
+ hasSessionCredential(): boolean {
76
+ return this.cookieSessionMode || !!this.sessionToken;
77
+ }
78
+
79
+ setCookieSessionMode(enabled: boolean): void {
80
+ this.cookieSessionMode = enabled;
81
+ }
82
+
70
83
  setSessionToken(token: string | null): void {
71
84
  if (this.sessionToken !== token) {
72
85
  this.sessionCookieName = null;
@@ -50,6 +50,7 @@ describe("CloudApiSocket connection health", () => {
50
50
  const socket = new CloudApiSocket({
51
51
  getBaseUrl: () => `http://127.0.0.1:${server.port}`,
52
52
  getSocketAuthToken: () => null,
53
+ hasSessionCredential: () => false,
53
54
  hasVerifiedUser: () => false,
54
55
  isUsingWebSocketToken: () => false,
55
56
  clearWebSocketTokenForFallback: () => false,
@@ -49,6 +49,7 @@ function mergeQuoteStreamSubscriptions(
49
49
  type CloudApiSocketDelegate = {
50
50
  getBaseUrl: () => string;
51
51
  getSocketAuthToken: () => string | null;
52
+ hasSessionCredential: () => boolean;
52
53
  hasVerifiedUser: () => boolean;
53
54
  isUsingWebSocketToken: () => boolean;
54
55
  clearWebSocketTokenForFallback: () => boolean;
@@ -427,7 +428,7 @@ export class CloudApiSocket {
427
428
 
428
429
  private shouldKeepSocketOpen(): boolean {
429
430
  if (this.quoteTargets.size > 0 || this.scannerListeners.size > 0) return true;
430
- return !!this.delegate.getSocketAuthToken()
431
+ return this.delegate.hasSessionCredential()
431
432
  && this.delegate.hasVerifiedUser()
432
433
  && this.channelListeners.size > 0;
433
434
  }
@@ -399,10 +399,66 @@ export interface CloudCongressHousePayload {
399
399
  indexUpdatedAt: string | null;
400
400
  filingsScanned: number;
401
401
  filingCount: number;
402
+ filingOffset?: number;
403
+ hasMore?: boolean;
404
+ hasMoreFilings?: boolean;
405
+ nextOffset?: number;
406
+ nextFilingOffset?: number;
402
407
  trades: CloudCongressTradePayload[];
403
408
  members: CloudCongressMemberPayload[];
404
409
  }
405
410
 
411
+ export interface CloudSecFilingPayload {
412
+ accessionNumber: string;
413
+ form: string;
414
+ filingDate: string;
415
+ acceptedAt?: string;
416
+ primaryDocument?: string;
417
+ primaryDocDescription?: string;
418
+ items?: string;
419
+ cik: string;
420
+ companyName?: string;
421
+ filingUrl: string;
422
+ primaryDocumentUrl?: string;
423
+ }
424
+
425
+ export interface CloudSecDocumentPayload {
426
+ sequence?: string;
427
+ type: string;
428
+ description?: string;
429
+ document: string;
430
+ url: string;
431
+ size?: string;
432
+ isPrimary: boolean;
433
+ }
434
+
435
+ export interface CloudSecFilingsResponse {
436
+ filings: CloudSecFilingPayload[];
437
+ hasMore: boolean;
438
+ nextOffset: number;
439
+ }
440
+
441
+ export interface CloudSecDocumentsResponse {
442
+ documents: CloudSecDocumentPayload[];
443
+ }
444
+
445
+ export interface CloudSecForm4Payload {
446
+ filingDate: string;
447
+ reportedName: string;
448
+ title: string;
449
+ transactionType: "P" | "S" | "A" | "D" | "";
450
+ shares: number;
451
+ pricePerShare: number | null;
452
+ totalValue: number | null;
453
+ sharesOwned: number | null;
454
+ form: string;
455
+ }
456
+
457
+ export interface CloudSecContentResponse {
458
+ content: string | null;
459
+ form4: CloudSecForm4Payload | null;
460
+ }
461
+
406
462
  interface CloudNewsEntityPayload {
407
463
  id: string;
408
464
  entityType: string;
@@ -617,6 +673,78 @@ export interface CloudMarketScreenerPayload {
617
673
  items: CloudMarketScreenerItem[];
618
674
  }
619
675
 
676
+ /** Cache reuse policy for one Equity Diagnostic request. */
677
+ export type CloudEquityDiagnosticMode = "cache-first" | "refresh";
678
+
679
+ export type CloudEquityDiagnosticFindingKind = "red_flag" | "green_flag" | "anomaly";
680
+
681
+ /** 3 is the most severe. */
682
+ export type CloudEquityDiagnosticSeverity = 1 | 2 | 3;
683
+
684
+ export interface CloudEquityDiagnosticFinding {
685
+ id: string;
686
+ kind: CloudEquityDiagnosticFindingKind;
687
+ severity: CloudEquityDiagnosticSeverity;
688
+ confidence: number;
689
+ title: string;
690
+ /** What the data says, with no reading attached. */
691
+ observation: string;
692
+ /** What the observation may mean; kept apart from the observation on purpose. */
693
+ interpretation: string;
694
+ /** Ids into `evidence`; unresolved ids are dropped by the client. */
695
+ evidenceIds: string[];
696
+ }
697
+
698
+ export interface CloudEquityDiagnosticCoverage {
699
+ dataset: string;
700
+ status: "available" | "no_data" | "unsupported" | "failed";
701
+ asOf?: string;
702
+ provider?: string;
703
+ note?: string;
704
+ }
705
+
706
+ /** The server owns citation URLs; the client never builds them. */
707
+ export interface CloudEquityDiagnosticEvidence {
708
+ id: string;
709
+ dataset: string;
710
+ label: string;
711
+ asOf?: string;
712
+ provider?: string;
713
+ url?: string;
714
+ }
715
+
716
+ export interface CloudEquityDiagnosticPending {
717
+ status: "generating";
718
+ retryAfterMs: number;
719
+ }
720
+
721
+ export interface CloudEquityDiagnosticResponse {
722
+ schemaVersion: 1;
723
+ symbol: string;
724
+ exchange: string;
725
+ companyName?: string;
726
+ status: "complete" | "partial" | "insufficient_data";
727
+ verdict: "risk_skewed" | "balanced" | "opportunity_skewed" | "unclear";
728
+ summary: string;
729
+ confidence: number;
730
+ findings: CloudEquityDiagnosticFinding[];
731
+ watchItems: string[];
732
+ coverage: CloudEquityDiagnosticCoverage[];
733
+ evidence: CloudEquityDiagnosticEvidence[];
734
+ generatedAt: string;
735
+ expiresAt: string;
736
+ /** Before this instant a `refresh` request may still answer from cache. */
737
+ refreshAllowedAt: string;
738
+ cached: boolean;
739
+ stale: boolean;
740
+ promptVersion: 1;
741
+ model: "gpt-5.6-luna";
742
+ }
743
+
744
+ export type CloudEquityDiagnosticResult =
745
+ | CloudEquityDiagnosticPending
746
+ | CloudEquityDiagnosticResponse;
747
+
620
748
  export interface CloudVerificationResponse {
621
749
  sent: boolean;
622
750
  email?: string;
@@ -13,6 +13,7 @@ import { VERSION } from "../../version";
13
13
  const UPDATE_CHECK_INTERVAL_MS = 60 * 60_000; // hourly
14
14
 
15
15
  export function useAppUpdateRuntime({
16
+ enabled = true,
16
17
  dispatch,
17
18
  isDetachedWindow,
18
19
  pluginRegistry,
@@ -21,6 +22,7 @@ export function useAppUpdateRuntime({
21
22
  updateCheckInProgress,
22
23
  updateProgress,
23
24
  }: {
25
+ enabled?: boolean;
24
26
  dispatch: Dispatch<AppAction>;
25
27
  isDetachedWindow: boolean;
26
28
  pluginRegistry: PluginRegistry;
@@ -76,15 +78,15 @@ export function useAppUpdateRuntime({
76
78
  }, [dispatch]);
77
79
 
78
80
  useEffect(() => {
79
- if (isDetachedWindow) return;
81
+ if (!enabled || isDetachedWindow) return;
80
82
  void runUpdateCheck(false);
81
83
  const interval = setInterval(() => { void runUpdateCheck(false); }, UPDATE_CHECK_INTERVAL_MS);
82
84
  return () => { clearInterval(interval); };
83
- }, [isDetachedWindow, runUpdateCheck]);
85
+ }, [enabled, isDetachedWindow, runUpdateCheck]);
84
86
 
85
87
  // First launch on a new version: show that release's notes, then remember the version.
86
88
  useEffect(() => {
87
- if (isDetachedWindow) return;
89
+ if (!enabled || isDetachedWindow) return;
88
90
  const config = stateRef.current.config;
89
91
  if (config.lastLaunchedVersion === VERSION) return;
90
92
  const isUpgrade = !!config.lastLaunchedVersion;
@@ -95,14 +97,14 @@ export function useAppUpdateRuntime({
95
97
  void pluginRegistry.createPaneFromTemplateAsyncFn("changelog-pane", {
96
98
  values: { version: VERSION },
97
99
  }).catch(() => {});
98
- }, [dispatch, isDetachedWindow, pluginRegistry, stateRef]);
100
+ }, [dispatch, enabled, isDetachedWindow, pluginRegistry, stateRef]);
99
101
 
100
102
  useEffect(() => {
101
- if (isDetachedWindow) return;
103
+ if (!enabled || isDetachedWindow) return;
102
104
  if (!updateAvailable || updateProgress || updateCheckInProgress) return;
103
105
  if (!canSelfUpdate(updateAvailable)) return;
104
106
  startUpdate(updateAvailable);
105
- }, [isDetachedWindow, startUpdate, updateAvailable, updateCheckInProgress, updateProgress]);
107
+ }, [enabled, isDetachedWindow, startUpdate, updateAvailable, updateCheckInProgress, updateProgress]);
106
108
 
107
109
  return {
108
110
  runUpdateCheck,
package/src/app.tsx CHANGED
@@ -24,7 +24,7 @@ import type { AppServicesFactory, AppTickerRepositoryPort } from "./core/app-ser
24
24
  import { useThemeColors } from "./theme/theme-context";
25
25
  import type { AppConfig } from "./types/config";
26
26
  import type { DesktopDeepLinkBridge } from "./types/desktop-deeplink";
27
- import type { CliLaunchRequest } from "./types/plugin";
27
+ import type { CliLaunchRequest, GloomPlugin } from "./types/plugin";
28
28
  import type { DataProvider } from "./types/data-provider";
29
29
  import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState, DesktopWindowBridge } from "./types/desktop-window";
30
30
  import type { DesktopApplicationMenuBridge } from "./types/desktop-menu";
@@ -32,7 +32,6 @@ import type { LayoutBounds } from "./plugins/pane-manager";
32
32
  import type { AppSessionSnapshot } from "./core/state/session-persistence";
33
33
  import type { MarketDataCoordinator } from "./market-data/coordinator";
34
34
  import { createAppNotifier } from "./notifications/app-notifier";
35
- import { getLoadablePlugins } from "./plugins/catalog";
36
35
  import { useBrokerImportRuntime } from "./app/runtime/broker-import";
37
36
  import { useDesktopDeepLinkRuntime } from "./app/runtime/desktop-deeplink";
38
37
  import { useDesktopApplicationMenuRuntime } from "./app/runtime/desktop-menu";
@@ -68,6 +67,7 @@ interface AppInnerProps {
68
67
  desktopApplicationMenuBridge?: DesktopApplicationMenuBridge;
69
68
  desktopDeepLinkBridge?: DesktopDeepLinkBridge;
70
69
  remoteControlAdapter?: RemoteControlAdapter;
70
+ updatesEnabled?: boolean;
71
71
  onboardingActive?: boolean;
72
72
  onOnboardingComplete?: (config: AppConfig) => void | Promise<void>;
73
73
  }
@@ -100,6 +100,7 @@ function AppInner({
100
100
  desktopApplicationMenuBridge,
101
101
  desktopDeepLinkBridge,
102
102
  remoteControlAdapter,
103
+ updatesEnabled = true,
103
104
  onboardingActive = false,
104
105
  onOnboardingComplete,
105
106
  }: AppInnerProps) {
@@ -221,6 +222,7 @@ function AppInner({
221
222
  });
222
223
 
223
224
  const { runUpdateCheck, startUpdate } = useAppUpdateRuntime({
225
+ enabled: updatesEnabled,
224
226
  dispatch,
225
227
  isDetachedWindow,
226
228
  pluginRegistry,
@@ -399,7 +401,7 @@ function AppInner({
399
401
  tickerRepository={tickerRepository}
400
402
  pluginRegistry={pluginRegistry}
401
403
  quitApp={() => rendererHost.requestExit()}
402
- onCheckForUpdates={() => runUpdateCheck(true)}
404
+ onCheckForUpdates={updatesEnabled ? () => runUpdateCheck(true) : undefined}
403
405
  onNativeOccluderChange={setCommandBarNativeOccluder}
404
406
  />
405
407
  )}
@@ -414,6 +416,7 @@ interface AppProps {
414
416
  config: AppConfig;
415
417
  servicesFactory: AppServicesFactory;
416
418
  externalPlugins?: LoadedExternalPlugin[];
419
+ plugins: readonly GloomPlugin[];
417
420
  cliLaunchRequest?: CliLaunchRequest | null;
418
421
  desktopWindowBridge?: DesktopWindowBridge;
419
422
  desktopApplicationMenuBridge?: DesktopApplicationMenuBridge;
@@ -421,12 +424,14 @@ interface AppProps {
421
424
  desktopSnapshot?: DesktopSharedStateSnapshot | null;
422
425
  desktopThemePreview?: DesktopThemePreviewState | null;
423
426
  remoteControlAdapter?: RemoteControlAdapter;
427
+ updatesEnabled?: boolean;
424
428
  }
425
429
 
426
430
  export function App({
427
431
  config: initialConfig,
428
432
  servicesFactory,
429
433
  externalPlugins: providedExternalPlugins,
434
+ plugins,
430
435
  cliLaunchRequest = null,
431
436
  desktopWindowBridge,
432
437
  desktopApplicationMenuBridge,
@@ -434,6 +439,7 @@ export function App({
434
439
  desktopSnapshot = null,
435
440
  desktopThemePreview = null,
436
441
  remoteControlAdapter,
442
+ updatesEnabled = true,
437
443
  }: AppProps) {
438
444
  useAppLanguage();
439
445
  const externalPlugins = providedExternalPlugins ?? EMPTY_EXTERNAL_PLUGINS;
@@ -469,14 +475,14 @@ export function App({
469
475
  return measurePerf("startup.app.create-services", () => (
470
476
  servicesFactory({
471
477
  config,
472
- plugins: getLoadablePlugins(externalPlugins),
478
+ plugins,
473
479
  })
474
480
  ), {
475
481
  externalPluginCount: externalPlugins.length,
476
482
  disabledPluginCount: config.disabledPlugins.length,
477
483
  brokerInstanceCount: config.brokerInstances.length,
478
484
  });
479
- }, [config.dataDir, externalPlugins, servicesFactory]);
485
+ }, [config.dataDir, externalPlugins, plugins, servicesFactory]);
480
486
 
481
487
  useEffect(() => {
482
488
  return () => services.destroy();
@@ -514,6 +520,7 @@ export function App({
514
520
  desktopApplicationMenuBridge={desktopApplicationMenuBridge}
515
521
  desktopDeepLinkBridge={desktopDeepLinkBridge}
516
522
  remoteControlAdapter={remoteControlAdapter}
523
+ updatesEnabled={updatesEnabled}
517
524
  onboardingActive={showOnboarding}
518
525
  onOnboardingComplete={(updatedConfig) => {
519
526
  setConfig(updatedConfig);
@@ -61,7 +61,11 @@ describe("import boundaries", () => {
61
61
  return !file.startsWith("src/renderers/electrobun/");
62
62
  }
63
63
  if (specifier === "react-dom" || specifier.startsWith("react-dom/")) {
64
- return !file.startsWith("src/renderers/electrobun/");
64
+ return ![
65
+ "src/renderers/electrobun/",
66
+ "src/renderers/browser/",
67
+ "src/renderers/share/",
68
+ ].some((prefix) => file.startsWith(prefix));
65
69
  }
66
70
  return false;
67
71
  });
@@ -147,6 +147,10 @@ export function newsProvider(options: {
147
147
  supports: op((input: any) => options.provider.supports?.(input.query) ?? true, "query"),
148
148
  getCachedNews: op((input: any) => options.provider.getCachedNews?.(input.query) ?? [], "query"),
149
149
  fetchNews: op((input: any) => options.provider.fetchNews(input.query), "query"),
150
+ fetchNewsPage: op((input: any) => (
151
+ options.provider.fetchNewsPage?.(input.query)
152
+ ?? options.provider.fetchNews(input.query).then((articles) => ({ articles, nextCursor: null }))
153
+ ), "query"),
150
154
  fetchNewsStory: op((input: any) => options.provider.fetchNewsStory?.(input.storyId) ?? null, "query"),
151
155
  },
152
156
  };
package/src/cli/index.ts CHANGED
@@ -35,7 +35,10 @@ import {
35
35
  } from "./commands/plugins";
36
36
  import { runPaneCatalog, runPaneFunction, runPaneScreenshot } from "./pane-functions";
37
37
 
38
- function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
38
+ function createCoreCliCommands(
39
+ renderHelp: () => string,
40
+ allCommands: () => CliCommandDef[],
41
+ ): CliCommandDef[] {
39
42
  let commands: CliCommandDef[] = [];
40
43
  const launchUiRequest: CliLaunchRequest = {
41
44
  applyConfig: (config) => ({ config }),
@@ -53,7 +56,7 @@ function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
53
56
  console.log(renderHelp());
54
57
  return;
55
58
  }
56
- ctx.printResult({ data: commands.map((command) => ({
59
+ ctx.printResult({ data: allCommands().map((command) => ({
57
60
  name: command.name,
58
61
  aliases: command.aliases ?? [],
59
62
  description: command.description,
@@ -190,7 +193,7 @@ function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
190
193
  ...marketDataCliCommands,
191
194
  ...overviewCliCommands,
192
195
  remoteCliCommand,
193
- ...createSystemCliCommands(() => commands),
196
+ ...createSystemCliCommands(allCommands),
194
197
  brokerCliCommand,
195
198
  ibkrCliCommand,
196
199
  aiCliCommand,
@@ -207,7 +210,10 @@ export interface DispatchCliOptions {
207
210
  async function createRegistry(options: DispatchCliOptions = {}): Promise<CliCommandRegistry> {
208
211
  const config = await loadCliConfigIfAvailable();
209
212
  let registry: CliCommandRegistry | null = null;
210
- const coreCommands = createCoreCliCommands(() => renderCliHelp(registry!, VERSION));
213
+ const coreCommands = createCoreCliCommands(
214
+ () => renderCliHelp(registry!, VERSION),
215
+ () => registry!.commands.map((entry) => entry.command),
216
+ );
211
217
  registry = buildCliCommandRegistry({
212
218
  coreCommands,
213
219
  externalPlugins: options.externalPlugins ?? [],
@@ -208,7 +208,7 @@ export function buildCliCommandRegistry({
208
208
  commands,
209
209
  lookup,
210
210
  config,
211
- plugins: loadablePlugins,
211
+ plugins: loadablePlugins.filter((plugin) => !disabledPlugins.has(plugin.id)),
212
212
  externalPlugins,
213
213
  descriptors,
214
214
  };
@@ -38,6 +38,7 @@ import {
38
38
  compositeAxisTicks,
39
39
  formatCompositeAxisValue,
40
40
  formatCompositeCursorDate,
41
+ formatCompositeCursorValue,
41
42
  formatCompositePointDetails,
42
43
  formatCompositeSeriesValue,
43
44
  formatCompositeTimeAxisDate,
@@ -291,7 +292,7 @@ function cursorAxisLabel(
291
292
  const domain = panel.axes[side];
292
293
  if (!domain || cursorYRatio === null) return null;
293
294
  const value = unprojectCompositeValue(cursorYRatio, domain);
294
- return value === null ? null : formatCompositeAxisValue(value, domain);
295
+ return value === null ? null : formatCompositeCursorValue(value, domain);
295
296
  }
296
297
 
297
298
  const MINIMUM_AXIS_LABEL_WIDTH = 3;
@@ -707,10 +708,10 @@ function CompositePanelSurface({
707
708
  domain: measureDomain,
708
709
  }),
709
710
  startValueLabel: measureDomain && startValue !== null
710
- ? formatCompositeAxisValue(startValue, measureDomain)
711
+ ? formatCompositeCursorValue(startValue, measureDomain)
711
712
  : null,
712
713
  endValueLabel: measureDomain && endValue !== null
713
- ? formatCompositeAxisValue(endValue, measureDomain)
714
+ ? formatCompositeCursorValue(endValue, measureDomain)
714
715
  : null,
715
716
  };
716
717
  }, [measureDomain, scene, toolDrag]);
@@ -1,8 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { CompositeChartScene } from "./types";
3
3
  import {
4
+ formatChartLegendValue,
4
5
  formatCompositeAxisValue,
5
6
  formatCompositeCursorDate,
7
+ formatCompositeCursorValue,
6
8
  formatCompositePointDetails,
7
9
  formatCompositeSeriesValue,
8
10
  formatCompositeTimeAxisDate,
@@ -147,4 +149,36 @@ describe("composite chart unit formatting", () => {
147
149
  seriesIds: ["ratio"],
148
150
  })).toBe("0.500");
149
151
  });
152
+
153
+ test("shows full price precision in the legend and cursor, not compact axis ticks", () => {
154
+ const btc: ResolvedSeries = {
155
+ id: "btc",
156
+ label: "BTC-USD Price",
157
+ color: "#ffffff",
158
+ unit: "USD",
159
+ unitGroup: "price:USD",
160
+ nativeFrequency: "daily",
161
+ dataShape: "ohlcv",
162
+ style: "candles",
163
+ transform: "raw",
164
+ axis: "left",
165
+ panelId: "main",
166
+ interpolation: "none",
167
+ points: [],
168
+ };
169
+ const domain = {
170
+ side: "left" as const,
171
+ min: 70_000,
172
+ max: 80_000,
173
+ scale: "linear" as const,
174
+ unit: "USD",
175
+ unitGroup: "price:USD",
176
+ seriesIds: ["btc"],
177
+ };
178
+
179
+ expect(formatCompositeSeriesValue(79_432.18, btc)).toBe("$79,432.18");
180
+ expect(formatChartLegendValue(79_432.18, "USD", "price:USD")).toBe("$79,432.18");
181
+ expect(formatCompositeCursorValue(79_432.18, domain)).toBe("$79,432.18");
182
+ expect(formatCompositeAxisValue(79_432.18, domain)).toBe("$79K");
183
+ });
150
184
  });