gloomberb 0.9.7 → 0.9.9

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 (276) hide show
  1. package/package.json +1 -1
  2. package/src/api-client/auth.ts +20 -0
  3. package/src/api-client/index.test.ts +3 -3
  4. package/src/api-client/index.ts +48 -6
  5. package/src/api-client/request.test.ts +63 -0
  6. package/src/api-client/request.ts +53 -5
  7. package/src/api-client/types.ts +45 -2
  8. package/src/app/pane-runtime/layout-placement.ts +0 -1
  9. package/src/app/runtime/desktop-deeplink.test.ts +87 -0
  10. package/src/app/runtime/desktop-deeplink.ts +496 -0
  11. package/src/app.tsx +34 -0
  12. package/src/brokers/account-cache.ts +2 -10
  13. package/src/brokers/broker-portfolio-sync.ts +0 -1
  14. package/src/brokers/broker-ticker-sync.ts +20 -16
  15. package/src/brokers/remote-broker-adapter.ts +3 -0
  16. package/src/capabilities/registry.test.ts +25 -0
  17. package/src/capabilities/registry.ts +22 -5
  18. package/src/cli/commands/automation.ts +1 -3
  19. package/src/cli/commands/command-utils.ts +0 -7
  20. package/src/cli/commands/overview.ts +2 -2
  21. package/src/cli/commands/plugins.ts +34 -17
  22. package/src/cli/index.ts +2 -2
  23. package/src/cli.test.ts +0 -157
  24. package/src/components/chart/bar-chart-renderer.test.ts +87 -12
  25. package/src/components/chart/bar-chart-renderer.ts +111 -55
  26. package/src/components/chart/comparison/renderer.ts +0 -1
  27. package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
  28. package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
  29. package/src/components/chart/core/index.test.ts +0 -24
  30. package/src/components/chart/core/renderer.ts +0 -4
  31. package/src/components/chart/core/resolution.test.ts +0 -17
  32. package/src/components/chart/core/resolution.ts +0 -26
  33. package/src/components/chart/core/scroll.test.ts +0 -6
  34. package/src/components/chart/core/scroll.ts +0 -22
  35. package/src/components/chart/core/viewport.test.ts +2 -23
  36. package/src/components/chart/core/viewport.ts +0 -27
  37. package/src/components/chart/market-session.ts +1 -1
  38. package/src/components/chart/native/surface/hooks.ts +10 -3
  39. package/src/components/chart/static/bar-chart-surface.tsx +104 -20
  40. package/src/components/chart/stock-chart/data-runtime.ts +0 -1
  41. package/src/components/chart/stock-chart/footer.test.ts +65 -0
  42. package/src/components/chart/stock-chart/footer.ts +68 -13
  43. package/src/components/chart/stock-chart/resolved.tsx +18 -4
  44. package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
  45. package/src/components/command-bar/helpers.test.ts +1 -80
  46. package/src/components/command-bar/helpers.ts +1 -36
  47. package/src/components/command-bar/pane-settings.ts +1 -1
  48. package/src/components/command-bar/pane-templates/items.ts +11 -3
  49. package/src/components/command-bar/panel/runtime.ts +0 -2
  50. package/src/components/command-bar/routes/root/results.ts +1 -1
  51. package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
  52. package/src/components/command-bar/surface/index.test.tsx +52 -16
  53. package/src/components/command-bar/surface/index.tsx +0 -1
  54. package/src/components/command-bar/workflow/ops.ts +81 -20
  55. package/src/components/feed-data-table/stack-view.test.tsx +36 -130
  56. package/src/components/feed-data-table/stack-view.tsx +0 -1
  57. package/src/components/index.ts +1 -0
  58. package/src/components/layout/shell/index.test.tsx +2 -113
  59. package/src/components/layout/shell/index.tsx +0 -1
  60. package/src/components/layout/shell/menu.ts +0 -1
  61. package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
  62. package/src/components/onboarding/onboarding-wizard.tsx +3 -3
  63. package/src/components/onboarding/wizard-keyboard.ts +2 -23
  64. package/src/components/price-selector-dialog.tsx +3 -3
  65. package/src/components/ticker/badge/text.tsx +0 -1
  66. package/src/components/toggle-list.tsx +29 -73
  67. package/src/components/ui/button.tsx +4 -1
  68. package/src/components/ui/checkbox.tsx +76 -0
  69. package/src/components/ui/choice-dialog.tsx +15 -3
  70. package/src/components/ui/frame.tsx +1 -1
  71. package/src/components/ui/index.ts +2 -0
  72. package/src/components/ui/message-composer.tsx +4 -0
  73. package/src/components/ui/native-select.tsx +4 -2
  74. package/src/components/ui/page-stack-view.tsx +1 -1
  75. package/src/components/ui/ui.test.tsx +0 -78
  76. package/src/core/state/app/layout-reducer.ts +0 -1
  77. package/src/data/config/layout.ts +3 -3
  78. package/src/data/resource-store.ts +6 -0
  79. package/src/data/sqlite/cache.test.ts +5 -0
  80. package/src/data/ticker-store.ts +0 -7
  81. package/src/market-data/coordinator/auxiliary.ts +1 -44
  82. package/src/market-data/coordinator/entries.ts +0 -1
  83. package/src/market-data/coordinator/events.test.ts +49 -0
  84. package/src/market-data/coordinator/events.ts +25 -4
  85. package/src/market-data/coordinator/index.test.ts +223 -10
  86. package/src/market-data/coordinator/index.ts +41 -21
  87. package/src/market-data/coordinator/quotes.ts +13 -11
  88. package/src/market-data/coordinator/subscriptions.test.ts +1 -0
  89. package/src/market-data/hooks.tsx +17 -13
  90. package/src/market-data/market/freshness.ts +58 -1
  91. package/src/market-data/market/status.ts +19 -1
  92. package/src/market-data/query-store.ts +0 -4
  93. package/src/market-data/quotes/freshness.test.ts +49 -1
  94. package/src/market-data/quotes/freshness.ts +8 -0
  95. package/src/market-data/quotes/resolution.test.ts +185 -0
  96. package/src/market-data/quotes/resolution.ts +96 -10
  97. package/src/market-data/request-types.test.ts +61 -1
  98. package/src/market-data/request-types.ts +42 -8
  99. package/src/market-data/result-types.ts +0 -11
  100. package/src/market-data/selectors.ts +1 -5
  101. package/src/news/aggregator.ts +5 -1
  102. package/src/pane-settings.test.ts +0 -18
  103. package/src/plugins/builtin/account-management/footer.ts +2 -5
  104. package/src/plugins/builtin/account-management/form-components.tsx +175 -59
  105. package/src/plugins/builtin/account-management/keyboard.ts +32 -2
  106. package/src/plugins/builtin/account-management/model.test.ts +99 -4
  107. package/src/plugins/builtin/account-management/model.ts +259 -23
  108. package/src/plugins/builtin/account-management/pane.tsx +784 -155
  109. package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
  110. package/src/plugins/builtin/buildout/cells.tsx +1 -1
  111. package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
  112. package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
  113. package/src/plugins/builtin/buildout/format.ts +2 -11
  114. package/src/plugins/builtin/chat/chat.test.tsx +127 -1
  115. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
  116. package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
  117. package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
  118. package/src/plugins/builtin/chat/content/composer.tsx +84 -1
  119. package/src/plugins/builtin/chat/content/index.tsx +153 -16
  120. package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
  121. package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
  122. package/src/plugins/builtin/chat/content/mentions.ts +88 -0
  123. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
  124. package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
  125. package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
  126. package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
  127. package/src/plugins/builtin/chat/controller/index.ts +0 -10
  128. package/src/plugins/builtin/chat/controller.test.ts +5 -5
  129. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
  130. package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
  131. package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
  132. package/src/plugins/builtin/chat/sidebar.tsx +26 -2
  133. package/src/plugins/builtin/chat/test-harness.tsx +22 -12
  134. package/src/plugins/builtin/cloud/plugin.tsx +8 -1
  135. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
  136. package/src/plugins/builtin/congress-trades/model.ts +2 -7
  137. package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
  138. package/src/plugins/builtin/correlation/index.tsx +4 -1
  139. package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
  140. package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
  141. package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
  142. package/src/plugins/builtin/earnings/index.tsx +0 -9
  143. package/src/plugins/builtin/econ/index.tsx +1 -5
  144. package/src/plugins/builtin/help/index.tsx +2 -2
  145. package/src/plugins/builtin/holders/pane.tsx +0 -5
  146. package/src/plugins/builtin/insider/index.tsx +1 -8
  147. package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
  148. package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
  149. package/src/plugins/builtin/market-movers/index.tsx +0 -2
  150. package/src/plugins/builtin/news/index.tsx +0 -1
  151. package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
  152. package/src/plugins/builtin/options/view.test.tsx +1 -38
  153. package/src/plugins/builtin/plugin-groups.ts +17 -3
  154. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
  155. package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
  156. package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
  157. package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
  158. package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
  159. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
  160. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
  161. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
  162. package/src/plugins/builtin/sec/filing-documents.ts +0 -11
  163. package/src/plugins/builtin/sec/index.tsx +0 -2
  164. package/src/plugins/builtin/substack/article-detail.tsx +1 -1
  165. package/src/plugins/builtin/thirteenf/api.ts +0 -15
  166. package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
  167. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
  168. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
  169. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
  170. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
  171. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
  172. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
  173. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
  174. package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
  175. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
  176. package/src/plugins/builtin/world-indices/index.tsx +0 -4
  177. package/src/plugins/builtin/world-indices/model.ts +0 -7
  178. package/src/plugins/ibkr/account-cache.ts +1 -1
  179. package/src/plugins/ibkr/gateway/market-data.ts +12 -5
  180. package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
  181. package/src/plugins/ibkr/portfolio-performance.ts +1 -1
  182. package/src/plugins/ibkr/trade/utils.ts +1 -7
  183. package/src/plugins/loader.ts +2 -1
  184. package/src/plugins/prediction-markets/detail.test.tsx +0 -34
  185. package/src/plugins/registry/context.ts +15 -0
  186. package/src/plugins/registry/contributions.ts +22 -11
  187. package/src/plugins/registry/index.test.ts +44 -0
  188. package/src/plugins/registry/index.ts +75 -26
  189. package/src/remote/controller.ts +1 -7
  190. package/src/remote/json-patch.ts +15 -1
  191. package/src/remote/server.ts +16 -6
  192. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  193. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
  194. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
  195. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
  196. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  197. package/src/renderers/electrobun/bun/index.ts +42 -0
  198. package/src/renderers/electrobun/shared/protocol.ts +5 -0
  199. package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
  200. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
  201. package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
  202. package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
  203. package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
  204. package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
  205. package/src/renderers/electrobun/view/host/box.tsx +0 -2
  206. package/src/renderers/electrobun/view/host/mouse.ts +0 -11
  207. package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
  208. package/src/renderers/electrobun/view/host/text.tsx +1 -2
  209. package/src/renderers/electrobun/view/http-fetch.ts +15 -2
  210. package/src/renderers/electrobun/view/main.tsx +3 -0
  211. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
  212. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
  213. package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
  214. package/src/renderers/electrobun/view/ui-host.tsx +2 -0
  215. package/src/renderers/opentui/host.tsx +11 -3
  216. package/src/renderers/opentui/input-host.tsx +5 -1
  217. package/src/sources/gloomberb-cloud/index.test.ts +0 -72
  218. package/src/sources/gloomberb-cloud/index.ts +1 -30
  219. package/src/sources/gloomberb-cloud/news.ts +0 -12
  220. package/src/sources/provider-router/batches.ts +11 -3
  221. package/src/sources/provider-router/cached-financials.test.ts +123 -0
  222. package/src/sources/provider-router/financial-routes.ts +77 -5
  223. package/src/sources/provider-router/financials.test.ts +49 -0
  224. package/src/sources/provider-router/financials.ts +40 -3
  225. package/src/sources/provider-router/index.test.ts +314 -0
  226. package/src/sources/provider-router/primary.ts +17 -3
  227. package/src/sources/provider-router/search.ts +5 -0
  228. package/src/sources/provider-router/streaming.ts +6 -1
  229. package/src/sources/provider-router/supplemental.ts +4 -3
  230. package/src/sources/sec-edgar.test.ts +42 -0
  231. package/src/sources/sec-edgar.ts +1 -2
  232. package/src/sources/yahoo-finance/history.ts +5 -1
  233. package/src/sources/yahoo-finance/mappers.test.ts +29 -0
  234. package/src/sources/yahoo-finance/mappers.ts +2 -1
  235. package/src/sources/yahoo-finance/options.ts +59 -14
  236. package/src/sources/yahoo-finance/quote-summary.ts +4 -1
  237. package/src/sources/yahoo-finance/requests.ts +2 -1
  238. package/src/sources/yahoo-finance/snapshots.ts +13 -2
  239. package/src/sources/yahoo-finance.test.ts +40 -21
  240. package/src/sources/yahoo-finance.ts +19 -10
  241. package/src/state/app/context/index.test.tsx +0 -81
  242. package/src/state/app/context/selector.test.tsx +6 -2
  243. package/src/state/hooks/quote-streaming.test.ts +14 -1
  244. package/src/state/hooks/quote-streaming.ts +5 -4
  245. package/src/sync/controller.test.ts +31 -0
  246. package/src/sync/controller.ts +300 -0
  247. package/src/sync/core-contributors.test.ts +251 -0
  248. package/src/sync/core-contributors.ts +400 -0
  249. package/src/sync/profile-analytics.ts +39 -0
  250. package/src/sync/react.ts +53 -0
  251. package/src/sync/types.ts +77 -0
  252. package/src/test-support/data-provider.ts +0 -1
  253. package/src/types/desktop-deeplink.ts +7 -0
  254. package/src/types/plugin.ts +3 -0
  255. package/src/ui/host.tsx +15 -1
  256. package/src/ui/index.tsx +2 -0
  257. package/src/updater.test.ts +0 -21
  258. package/src/updater.ts +20 -16
  259. package/src/utils/async-deadline.ts +26 -0
  260. package/src/utils/exchanges.test.ts +11 -1
  261. package/src/utils/exchanges.ts +5 -1
  262. package/src/utils/selection-clipboard.ts +2 -2
  263. package/src/version.ts +1 -1
  264. package/src/components/layout/pane/header.test.tsx +0 -39
  265. package/src/plugins/builtin/chat/channels.test.ts +0 -28
  266. package/src/plugins/builtin/chat/index.tsx +0 -4
  267. package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
  268. package/src/plugins/builtin/earnings/index.test.ts +0 -12
  269. package/src/plugins/builtin/help/index.test.tsx +0 -242
  270. package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
  271. package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
  272. package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
  273. package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
  274. package/src/renderers/electrobun/view/host/text.test.ts +0 -22
  275. package/src/renderers/opentui/dialog-host.test.tsx +0 -44
  276. /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
@@ -0,0 +1,400 @@
1
+ import { scheduleConfigSave } from "../state/config-save-scheduler";
2
+ import type { AppConfig, BrokerInstanceConfig } from "../types/config";
3
+ import type { PricePoint, TickerFinancials } from "../types/financials";
4
+ import type { Portfolio, TickerMetadata, TickerPosition, TickerRecord, Watchlist } from "../types/ticker";
5
+ import { hydrateTickerMetadata } from "../tickers/metadata";
6
+ import type { SyncContributor } from "./types";
7
+ import { convertCurrency } from "../utils/format";
8
+ import {
9
+ computeDatedBeta,
10
+ computeDatedReturns,
11
+ computeWeightedPortfolioReturns,
12
+ type WeightedReturnSeries,
13
+ } from "../plugins/builtin/analytics/metrics";
14
+ import { getSyncedProfileAnalytics } from "./profile-analytics";
15
+
16
+ const SENSITIVE_KEY_PATTERN = /(token|secret|password|credential|private|api[_-]?key|access[_-]?key|refresh[_-]?key|session|cookie|dataDir|path|directory|localPath)/i;
17
+
18
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
19
+ return value != null && typeof value === "object" && !Array.isArray(value);
20
+ }
21
+
22
+ function sanitizeUnknown(value: unknown): unknown {
23
+ if (Array.isArray(value)) {
24
+ return value.map(sanitizeUnknown).filter((entry) => entry !== undefined);
25
+ }
26
+ if (!isPlainObject(value)) return value;
27
+
28
+ const output: Record<string, unknown> = {};
29
+ for (const [key, child] of Object.entries(value)) {
30
+ if (SENSITIVE_KEY_PATTERN.test(key)) continue;
31
+ const sanitized = sanitizeUnknown(child);
32
+ if (sanitized !== undefined) output[key] = sanitized;
33
+ }
34
+ return output;
35
+ }
36
+
37
+ function sanitizePortfolio(portfolio: Portfolio): Portfolio {
38
+ return {
39
+ id: portfolio.id,
40
+ name: portfolio.name,
41
+ description: portfolio.description,
42
+ currency: portfolio.currency,
43
+ brokerId: portfolio.brokerId,
44
+ lastSyncedAt: portfolio.lastSyncedAt,
45
+ };
46
+ }
47
+
48
+ function sanitizeWatchlist(watchlist: Watchlist): Watchlist {
49
+ return {
50
+ id: watchlist.id,
51
+ name: watchlist.name,
52
+ description: watchlist.description,
53
+ };
54
+ }
55
+
56
+ function sanitizeBrokerInstance(instance: BrokerInstanceConfig): Omit<BrokerInstanceConfig, "config"> {
57
+ return {
58
+ id: instance.id,
59
+ brokerType: instance.brokerType,
60
+ label: instance.label,
61
+ connectionMode: instance.connectionMode,
62
+ enabled: instance.enabled,
63
+ lastSyncedAt: instance.lastSyncedAt,
64
+ };
65
+ }
66
+
67
+ function sanitizePosition(position: TickerPosition): TickerPosition {
68
+ return {
69
+ portfolio: position.portfolio,
70
+ shares: position.shares,
71
+ avgCost: position.avgCost,
72
+ currency: position.currency,
73
+ dateAcquired: position.dateAcquired,
74
+ broker: position.broker,
75
+ side: position.side,
76
+ marketValue: position.marketValue,
77
+ unrealizedPnl: position.unrealizedPnl,
78
+ multiplier: position.multiplier,
79
+ markPrice: position.markPrice,
80
+ };
81
+ }
82
+
83
+ function pricePointTime(point: { date: Date | string | number }): number {
84
+ const value = point.date;
85
+ if (value instanceof Date) return value.getTime();
86
+ return new Date(value).getTime();
87
+ }
88
+
89
+ function weeklyQuoteMove(financials: TickerFinancials | null | undefined) {
90
+ const quote = financials?.quote;
91
+ if (!quote || !Number.isFinite(quote.price) || quote.price <= 0) return undefined;
92
+ const history = (financials?.priceHistory ?? [])
93
+ .filter((point) => Number.isFinite(point.close) && point.close > 0 && Number.isFinite(pricePointTime(point)))
94
+ .sort((left, right) => pricePointTime(left) - pricePointTime(right));
95
+ if (history.length === 0) return undefined;
96
+ const latestTime = Number.isFinite(quote.lastUpdated)
97
+ ? quote.lastUpdated
98
+ : pricePointTime(history[history.length - 1]!);
99
+ const cutoff = latestTime - 7 * 24 * 60 * 60 * 1000;
100
+ const reference = history.find((point) => pricePointTime(point) >= cutoff) ?? history[Math.max(0, history.length - 7)];
101
+ if (!reference || !Number.isFinite(reference.close) || reference.close <= 0) return undefined;
102
+ const weekChange = quote.price - reference.close;
103
+ return {
104
+ weekReferencePrice: reference.close,
105
+ weekChange,
106
+ weekChangePercent: (weekChange / reference.close) * 100,
107
+ };
108
+ }
109
+
110
+ function sanitizeQuote(financials: TickerFinancials | null | undefined) {
111
+ const quote = financials?.quote;
112
+ if (!quote) return undefined;
113
+ const week = weeklyQuoteMove(financials);
114
+ return {
115
+ price: Number.isFinite(quote.price) ? quote.price : undefined,
116
+ currency: quote.currency,
117
+ changePercent: Number.isFinite(quote.changePercent) ? quote.changePercent : undefined,
118
+ previousClose: Number.isFinite(quote.previousClose) ? quote.previousClose : undefined,
119
+ weekReferencePrice: week?.weekReferencePrice,
120
+ weekChange: week?.weekChange,
121
+ weekChangePercent: week?.weekChangePercent,
122
+ lastUpdated: quote.lastUpdated,
123
+ };
124
+ }
125
+
126
+ function sanitizeTickerMetadata(metadata: TickerMetadata, financials?: TickerFinancials | null): Omit<TickerMetadata, "broker_contracts"> & { quote?: ReturnType<typeof sanitizeQuote> } {
127
+ return {
128
+ ticker: metadata.ticker,
129
+ exchange: metadata.exchange,
130
+ currency: metadata.currency,
131
+ name: metadata.name,
132
+ sector: metadata.sector,
133
+ industry: metadata.industry,
134
+ assetCategory: metadata.assetCategory,
135
+ isin: metadata.isin,
136
+ cusip: metadata.cusip,
137
+ portfolios: [...metadata.portfolios],
138
+ watchlists: [...metadata.watchlists],
139
+ positions: metadata.positions.map(sanitizePosition),
140
+ custom: (sanitizeUnknown(metadata.custom) as Record<string, unknown>) ?? {},
141
+ tags: [...metadata.tags],
142
+ quote: sanitizeQuote(financials),
143
+ };
144
+ }
145
+
146
+ function collectCoreConfigPayload(config: AppConfig) {
147
+ return sanitizeUnknown({
148
+ configVersion: config.configVersion,
149
+ baseCurrency: config.baseCurrency,
150
+ refreshIntervalMinutes: config.refreshIntervalMinutes,
151
+ portfolios: config.portfolios.map(sanitizePortfolio),
152
+ watchlists: config.watchlists.map(sanitizeWatchlist),
153
+ layout: config.layout,
154
+ layouts: config.layouts,
155
+ activeLayoutIndex: config.activeLayoutIndex,
156
+ brokerInstances: config.brokerInstances.map(sanitizeBrokerInstance),
157
+ disabledPlugins: config.disabledPlugins,
158
+ disabledSources: config.disabledSources,
159
+ pluginConfig: config.pluginConfig,
160
+ theme: config.theme,
161
+ chartPreferences: config.chartPreferences,
162
+ valueFlashingEnabled: config.valueFlashingEnabled,
163
+ recentTickers: config.recentTickers,
164
+ onboardingComplete: config.onboardingComplete,
165
+ });
166
+ }
167
+
168
+ function positionValueBase(
169
+ position: TickerPosition,
170
+ financials: TickerFinancials | null | undefined,
171
+ baseCurrency: string,
172
+ exchangeRates: Map<string, number>,
173
+ ): number | null {
174
+ const quoteCurrency = financials?.quote?.currency || position.currency || baseCurrency;
175
+ const positionCurrency = position.currency || quoteCurrency;
176
+ if (typeof position.marketValue === "number" && Number.isFinite(position.marketValue)) {
177
+ return convertCurrency(Math.abs(position.marketValue), positionCurrency, baseCurrency, exchangeRates);
178
+ }
179
+ const hasMarkPrice = typeof position.markPrice === "number" && Number.isFinite(position.markPrice);
180
+ const hasQuotePrice = typeof financials?.quote?.price === "number" && Number.isFinite(financials.quote.price);
181
+ const price = hasMarkPrice ? position.markPrice : hasQuotePrice ? financials!.quote!.price : position.avgCost;
182
+ if (!Number.isFinite(position.shares) || typeof price !== "number" || !Number.isFinite(price)) return null;
183
+ return convertCurrency(
184
+ Math.abs(position.shares * price * (position.multiplier ?? 1)),
185
+ hasQuotePrice && !hasMarkPrice ? quoteCurrency : positionCurrency,
186
+ baseCurrency,
187
+ exchangeRates,
188
+ );
189
+ }
190
+
191
+ function pricePointTimeOrNull(point: PricePoint): number | null {
192
+ const value = point.date;
193
+ if (value == null) return null;
194
+ const time = value instanceof Date ? value.getTime() : new Date(value).getTime();
195
+ return Number.isFinite(time) ? time : null;
196
+ }
197
+
198
+ function recentPriceHistory(history: PricePoint[], days: number): PricePoint[] {
199
+ let latestTime = 0;
200
+ for (const point of history) {
201
+ const time = pricePointTimeOrNull(point);
202
+ if (time != null && time > latestTime) latestTime = time;
203
+ }
204
+ if (latestTime <= 0) return [];
205
+ const cutoff = latestTime - days * 24 * 60 * 60 * 1000;
206
+ return history.filter((point) => {
207
+ const time = pricePointTimeOrNull(point);
208
+ return time != null && time >= cutoff;
209
+ });
210
+ }
211
+
212
+ function oneYearReturnFromSeries(series: WeightedReturnSeries[]): number | null {
213
+ const returns = computeWeightedPortfolioReturns(series);
214
+ if (returns.length === 0) return null;
215
+ const cumulative = returns.reduce((acc, point) => (
216
+ Number.isFinite(point.value) ? acc * (1 + point.value) : acc
217
+ ), 1) - 1;
218
+ return Number.isFinite(cumulative) ? cumulative : null;
219
+ }
220
+
221
+ function collectAnalyticsByPortfolio(
222
+ config: AppConfig,
223
+ tickers: Map<string, TickerRecord>,
224
+ financials: Map<string, TickerFinancials>,
225
+ exchangeRates: Map<string, number>,
226
+ ) {
227
+ const output: Record<string, Record<string, unknown>> = {};
228
+ const spyReturns = computeDatedReturns(recentPriceHistory(financials.get("SPY")?.priceHistory ?? [], 366));
229
+ for (const portfolio of config.portfolios) {
230
+ let returnWeight = 0;
231
+ let weightedReturn = 0;
232
+ const datedReturnSeries: WeightedReturnSeries[] = [];
233
+ for (const ticker of tickers.values()) {
234
+ const tickerFinancials = financials.get(ticker.metadata.ticker);
235
+ const portfolioPositions = ticker.metadata.positions.filter((position) => position.portfolio === portfolio.id);
236
+ if (portfolioPositions.length === 0 && !ticker.metadata.portfolios.includes(portfolio.id)) continue;
237
+ for (const position of portfolioPositions) {
238
+ const value = positionValueBase(position, tickerFinancials, config.baseCurrency, exchangeRates);
239
+ if (value != null) {
240
+ const return1Y = tickerFinancials?.fundamentals?.return1Y;
241
+ if (typeof return1Y === "number" && Number.isFinite(return1Y)) {
242
+ returnWeight += value;
243
+ weightedReturn += return1Y * value;
244
+ }
245
+ const returns = computeDatedReturns(recentPriceHistory(tickerFinancials?.priceHistory ?? [], 366));
246
+ if (returns.length >= 10) datedReturnSeries.push({ weight: value, returns });
247
+ }
248
+ }
249
+ }
250
+ const portfolioReturns = computeWeightedPortfolioReturns(datedReturnSeries);
251
+ const previewAnalytics = getSyncedProfileAnalytics(portfolio.id);
252
+ output[portfolio.id] = {
253
+ oneYearReturn: previewAnalytics?.oneYearReturn
254
+ ?? (returnWeight > 0 ? weightedReturn / returnWeight : oneYearReturnFromSeries(datedReturnSeries)),
255
+ spyBeta: previewAnalytics?.spyBeta
256
+ ?? (
257
+ portfolioReturns.length > 0 && spyReturns.length > 0
258
+ ? computeDatedBeta(portfolioReturns, spyReturns)
259
+ : null
260
+ ),
261
+ };
262
+ }
263
+ return output;
264
+ }
265
+
266
+ function collectCoreCollectionsPayload(
267
+ config: AppConfig,
268
+ tickers: Map<string, TickerRecord>,
269
+ financials: Map<string, TickerFinancials>,
270
+ exchangeRates: Map<string, number>,
271
+ ) {
272
+ const records = [...tickers.values()]
273
+ .map((ticker) => sanitizeTickerMetadata(ticker.metadata, financials.get(ticker.metadata.ticker)))
274
+ .filter((metadata) => (
275
+ metadata.portfolios.length > 0 ||
276
+ metadata.watchlists.length > 0 ||
277
+ metadata.positions.length > 0
278
+ ));
279
+
280
+ return {
281
+ baseCurrency: config.baseCurrency,
282
+ exchangeRates: Object.fromEntries(
283
+ [...exchangeRates.entries()]
284
+ .filter(([currency, rate]) => (
285
+ typeof currency === "string" &&
286
+ Number.isFinite(rate) &&
287
+ rate > 0
288
+ ))
289
+ .map(([currency, rate]) => [currency.trim().toUpperCase(), rate]),
290
+ ),
291
+ portfolios: config.portfolios.map(sanitizePortfolio),
292
+ watchlists: config.watchlists.map(sanitizeWatchlist),
293
+ analyticsByPortfolio: collectAnalyticsByPortfolio(config, tickers, financials, exchangeRates),
294
+ tickers: records,
295
+ };
296
+ }
297
+
298
+ function mergeConfigPayload(config: AppConfig, payload: unknown): AppConfig | null {
299
+ if (!isPlainObject(payload)) return null;
300
+ const next: AppConfig = { ...config };
301
+ const assign = <K extends keyof AppConfig>(key: K) => {
302
+ if (key in payload) {
303
+ next[key] = payload[key as string] as AppConfig[K];
304
+ }
305
+ };
306
+
307
+ assign("baseCurrency");
308
+ assign("refreshIntervalMinutes");
309
+ assign("layout");
310
+ assign("layouts");
311
+ assign("activeLayoutIndex");
312
+ assign("disabledPlugins");
313
+ assign("disabledSources");
314
+ assign("pluginConfig");
315
+ assign("theme");
316
+ assign("chartPreferences");
317
+ assign("valueFlashingEnabled");
318
+ assign("recentTickers");
319
+ assign("onboardingComplete");
320
+
321
+ if (Array.isArray(payload.portfolios)) next.portfolios = payload.portfolios as Portfolio[];
322
+ if (Array.isArray(payload.watchlists)) next.watchlists = payload.watchlists as Watchlist[];
323
+ if (Array.isArray(payload.brokerInstances)) {
324
+ const incoming = payload.brokerInstances as Array<Partial<BrokerInstanceConfig>>;
325
+ const existingById = new Map(config.brokerInstances.map((instance) => [instance.id, instance]));
326
+ next.brokerInstances = incoming.map((instance) => {
327
+ const current = instance.id ? existingById.get(instance.id) : undefined;
328
+ return {
329
+ id: instance.id ?? current?.id ?? crypto.randomUUID(),
330
+ brokerType: instance.brokerType ?? current?.brokerType ?? "",
331
+ label: instance.label ?? current?.label ?? "",
332
+ connectionMode: instance.connectionMode ?? current?.connectionMode,
333
+ enabled: instance.enabled ?? current?.enabled,
334
+ lastSyncedAt: instance.lastSyncedAt ?? current?.lastSyncedAt,
335
+ config: current?.config ?? {},
336
+ };
337
+ });
338
+ }
339
+ return next;
340
+ }
341
+
342
+ export const coreConfigSyncContributor: SyncContributor = {
343
+ id: "core.config",
344
+ schemaVersion: 1,
345
+ collect: ({ state }) => collectCoreConfigPayload(state.config),
346
+ apply: (payload, { state, dispatch }) => {
347
+ const nextConfig = mergeConfigPayload(state.config, payload);
348
+ if (!nextConfig) return;
349
+ dispatch({ type: "SET_CONFIG", config: nextConfig });
350
+ scheduleConfigSave(nextConfig);
351
+ },
352
+ };
353
+
354
+ export const coreCollectionsSyncContributor: SyncContributor = {
355
+ id: "core.collections",
356
+ schemaVersion: 1,
357
+ collect: ({ state }) => collectCoreCollectionsPayload(
358
+ state.config,
359
+ state.tickers,
360
+ state.financials,
361
+ state.exchangeRates,
362
+ ),
363
+ apply: async (payload, { state, dispatch, tickerRepository }) => {
364
+ if (!isPlainObject(payload)) return;
365
+ const nextConfig = { ...state.config };
366
+ if (Array.isArray(payload.portfolios)) nextConfig.portfolios = payload.portfolios as Portfolio[];
367
+ if (Array.isArray(payload.watchlists)) nextConfig.watchlists = payload.watchlists as Watchlist[];
368
+
369
+ const nextTickers = new Map(state.tickers);
370
+ const rawTickers = Array.isArray(payload.tickers) ? payload.tickers : [];
371
+ for (const rawTicker of rawTickers) {
372
+ if (!isPlainObject(rawTicker)) continue;
373
+ const current = typeof rawTicker.ticker === "string"
374
+ ? nextTickers.get(rawTicker.ticker)
375
+ : null;
376
+ const metadata = hydrateTickerMetadata({
377
+ ...current?.metadata,
378
+ ...rawTicker,
379
+ broker_contracts: current?.metadata.broker_contracts ?? [],
380
+ });
381
+ const record: TickerRecord = { metadata };
382
+ await tickerRepository.saveTicker(record);
383
+ nextTickers.set(metadata.ticker, record);
384
+ }
385
+
386
+ dispatch({ type: "SET_CONFIG", config: nextConfig });
387
+ dispatch({ type: "SET_TICKERS", tickers: nextTickers });
388
+ scheduleConfigSave(nextConfig);
389
+ },
390
+ };
391
+
392
+ export function createCoreSyncContributors(): SyncContributor[] {
393
+ return [coreConfigSyncContributor, coreCollectionsSyncContributor];
394
+ }
395
+
396
+ export const __syncContributorInternalsForTests = {
397
+ sanitizeUnknown,
398
+ collectCoreConfigPayload,
399
+ collectCoreCollectionsPayload,
400
+ };
@@ -0,0 +1,39 @@
1
+ import type { PublicPortfolioAnalytics } from "../api-client";
2
+
3
+ const analyticsByPortfolio = new Map<string, PublicPortfolioAnalytics>();
4
+
5
+ function finiteMetric(value: number | null | undefined): number | null {
6
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
7
+ }
8
+
9
+ function normalizeAnalytics(analytics: PublicPortfolioAnalytics | null | undefined): PublicPortfolioAnalytics | null {
10
+ if (!analytics) return null;
11
+ const normalized = {
12
+ oneYearReturn: finiteMetric(analytics.oneYearReturn),
13
+ spyBeta: finiteMetric(analytics.spyBeta),
14
+ };
15
+ return normalized.oneYearReturn != null || normalized.spyBeta != null ? normalized : null;
16
+ }
17
+
18
+ function sameAnalytics(left: PublicPortfolioAnalytics | null, right: PublicPortfolioAnalytics | null): boolean {
19
+ return left?.oneYearReturn === right?.oneYearReturn && left?.spyBeta === right?.spyBeta;
20
+ }
21
+
22
+ export function setSyncedProfileAnalytics(
23
+ portfolioId: string,
24
+ analytics: PublicPortfolioAnalytics | null | undefined,
25
+ ): boolean {
26
+ const normalized = normalizeAnalytics(analytics);
27
+ const current = analyticsByPortfolio.get(portfolioId) ?? null;
28
+ if (sameAnalytics(current, normalized)) return false;
29
+ if (normalized) {
30
+ analyticsByPortfolio.set(portfolioId, normalized);
31
+ } else {
32
+ analyticsByPortfolio.delete(portfolioId);
33
+ }
34
+ return true;
35
+ }
36
+
37
+ export function getSyncedProfileAnalytics(portfolioId: string): PublicPortfolioAnalytics | null {
38
+ return analyticsByPortfolio.get(portfolioId) ?? null;
39
+ }
@@ -0,0 +1,53 @@
1
+ import { useEffect, useSyncExternalStore, type Dispatch } from "react";
2
+ import type { AppAction, AppState } from "../core/state/app/state";
3
+ import type { TickerRepository } from "../data/ticker-repository";
4
+ import type { PluginRegistry } from "../plugins/registry";
5
+ import { cloudSyncController } from "./controller";
6
+
7
+ interface CloudSyncRuntimeOptions {
8
+ state: AppState;
9
+ dispatch: Dispatch<AppAction>;
10
+ tickerRepository: TickerRepository;
11
+ pluginRegistry: PluginRegistry;
12
+ initialized: boolean;
13
+ }
14
+
15
+ export function useCloudSyncRuntime({
16
+ state,
17
+ dispatch,
18
+ tickerRepository,
19
+ pluginRegistry,
20
+ initialized,
21
+ }: CloudSyncRuntimeOptions): void {
22
+ useEffect(() => {
23
+ cloudSyncController.setRuntime({
24
+ state,
25
+ dispatch,
26
+ tickerRepository,
27
+ getContributors: () => pluginRegistry.getEnabledSyncContributors(),
28
+ getTransport: () => pluginRegistry.getActiveSyncTransport(),
29
+ });
30
+ }, [dispatch, pluginRegistry, state, tickerRepository]);
31
+
32
+ useEffect(() => () => {
33
+ cloudSyncController.clearRuntime();
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ if (!initialized) return;
38
+ void cloudSyncController.pullLatest();
39
+ }, [initialized, pluginRegistry]);
40
+
41
+ useEffect(() => {
42
+ if (!initialized) return;
43
+ cloudSyncController.schedulePush("state-change");
44
+ }, [initialized, state.config, state.tickers]);
45
+ }
46
+
47
+ export function useCloudSyncStatus() {
48
+ return useSyncExternalStore(
49
+ (listener) => cloudSyncController.subscribe(listener),
50
+ () => cloudSyncController.getStatus(),
51
+ () => cloudSyncController.getStatus(),
52
+ );
53
+ }
@@ -0,0 +1,77 @@
1
+ import type { Dispatch } from "react";
2
+ import type { AppAction, AppState } from "../core/state/app/state";
3
+ import type { TickerRepository } from "../data/ticker-repository";
4
+
5
+ export const SYNC_SNAPSHOT_SCHEMA_VERSION = 1;
6
+
7
+ export interface SyncContributorPayload {
8
+ schemaVersion: number;
9
+ updatedAt: string;
10
+ payload: unknown;
11
+ }
12
+
13
+ export interface SyncSnapshot {
14
+ schemaVersion: typeof SYNC_SNAPSHOT_SCHEMA_VERSION;
15
+ appId: "gloomberb";
16
+ clientId: string;
17
+ createdAt: string;
18
+ appVersion?: string;
19
+ contributors: Record<string, SyncContributorPayload>;
20
+ }
21
+
22
+ export interface SyncCollectContext {
23
+ state: AppState;
24
+ }
25
+
26
+ export interface SyncApplyContext {
27
+ snapshot: SyncSnapshot;
28
+ state: AppState;
29
+ dispatch: Dispatch<AppAction>;
30
+ tickerRepository: TickerRepository;
31
+ }
32
+
33
+ export interface SyncContributor {
34
+ id: string;
35
+ schemaVersion: number;
36
+ collect(context: SyncCollectContext): unknown | Promise<unknown>;
37
+ apply?(payload: unknown, context: SyncApplyContext): void | Promise<void>;
38
+ }
39
+
40
+ export interface SyncSnapshotResponse {
41
+ snapshot: SyncSnapshot | null;
42
+ revision: number | null;
43
+ updatedAt: string | null;
44
+ settings?: SyncSettings;
45
+ }
46
+
47
+ export interface SyncPushResult {
48
+ revision: number;
49
+ updatedAt: string;
50
+ settings?: SyncSettings;
51
+ }
52
+
53
+ export interface SyncTransport {
54
+ id: string;
55
+ isAvailable(): boolean;
56
+ pullSnapshot(): Promise<SyncSnapshotResponse>;
57
+ pushSnapshot(snapshot: SyncSnapshot, options?: { baseRevision?: number | null }): Promise<SyncPushResult>;
58
+ }
59
+
60
+ export interface SyncSettings {
61
+ syncEnabled: boolean;
62
+ weeklyRoundupEnabled: boolean;
63
+ positionAlertsEnabled: boolean;
64
+ selectedSharedPortfolioId?: string | null;
65
+ lastSyncAt?: string | null;
66
+ lastRoundupEmailAt?: string | null;
67
+ }
68
+
69
+ export interface RegisteredSyncContributor {
70
+ pluginId: string;
71
+ contributor: SyncContributor;
72
+ }
73
+
74
+ export interface RegisteredSyncTransport {
75
+ pluginId: string;
76
+ transport: SyncTransport;
77
+ }
@@ -14,7 +14,6 @@ export function createTestDataProvider(overrides: Partial<DataProvider> = {}): D
14
14
  getQuote: async () => unused<Quote>("getQuote"),
15
15
  getExchangeRate: async () => 1,
16
16
  search: async () => [] satisfies InstrumentSearchResult[],
17
- getNews: async () => [],
18
17
  getArticleSummary: async () => null,
19
18
  getPriceHistory: async () => [],
20
19
  ...overrides,
@@ -0,0 +1,7 @@
1
+ export interface DesktopDeepLink {
2
+ url: string;
3
+ }
4
+
5
+ export interface DesktopDeepLinkBridge {
6
+ subscribe(listener: (deeplink: DesktopDeepLink) => void): () => void;
7
+ }
@@ -20,6 +20,7 @@ import type { TickerFinancials } from "./financials";
20
20
  import type { CachePolicy, PersistedResourceValue } from "./persistence";
21
21
  import type { TickerRecord } from "./ticker";
22
22
  import type { InstrumentSearchResult } from "./instrument";
23
+ import type { SyncContributor, SyncTransport } from "../sync/types";
23
24
 
24
25
  export interface GloomSlots {
25
26
  "ticker-research:tab": { ticker: TickerRecord; financials: TickerFinancials | null };
@@ -467,6 +468,8 @@ export interface GloomPluginContext {
467
468
  registerShortcut(shortcut: KeyboardShortcut): void;
468
469
  registerTickerAction(action: TickerAction): void;
469
470
  registerContextMenuProvider(provider: ContextMenuProviderDef): void;
471
+ registerSyncContributor(contributor: SyncContributor): () => void;
472
+ registerSyncTransport(transport: SyncTransport): () => void;
470
473
  watchNewsQuery?(
471
474
  query: import("./news-source").NewsQuery,
472
475
  listener: (state: import("./news-source").NewsQueryState) => void,
package/src/ui/host.tsx CHANGED
@@ -85,7 +85,7 @@ export interface SyntaxStyleLike {
85
85
  export interface BoxRenderable {
86
86
  x?: number;
87
87
  y?: number;
88
- width?: number;
88
+ width?: number | string;
89
89
  height?: number;
90
90
  absoluteX?: number;
91
91
  absoluteY?: number;
@@ -101,6 +101,7 @@ interface ScrollBarRenderable {
101
101
  }
102
102
 
103
103
  export interface ScrollBoxRenderable {
104
+ width?: number;
104
105
  scrollTop: number;
105
106
  scrollLeft?: number;
106
107
  scrollHeight: number;
@@ -264,6 +265,18 @@ export interface HostTabsProps {
264
265
  palette: HostTabsPalette;
265
266
  }
266
267
 
268
+ export interface HostCheckboxProps {
269
+ label: string;
270
+ displayLabel?: string;
271
+ checked: boolean;
272
+ onChange?: (checked: boolean) => void;
273
+ disabled?: boolean;
274
+ active?: boolean;
275
+ description?: string;
276
+ width?: number | string;
277
+ variant?: "default" | "desktop";
278
+ }
279
+
267
280
  export interface UiHost {
268
281
  kind?: "opentui" | "desktop-web";
269
282
  capabilities?: {
@@ -299,6 +312,7 @@ export interface UiHost {
299
312
  DialogFrame?: ComponentType<any>;
300
313
  PageStackView?: ComponentType<any>;
301
314
  Tabs?: ComponentType<HostTabsProps>;
315
+ Checkbox?: ComponentType<HostCheckboxProps>;
302
316
  DataTable?: ComponentType<any>;
303
317
  createSyntaxStyle?(): SyntaxStyleLike;
304
318
  colorFromHex?(hex: string): unknown;
package/src/ui/index.tsx CHANGED
@@ -33,8 +33,10 @@ export {
33
33
  } from "./context-menu";
34
34
  export { contextMenuDivider } from "../types/context-menu";
35
35
  export type {
36
+ BitmapSurface,
36
37
  BoxRenderable,
37
38
  ChartSurfaceProps,
39
+ HostCheckboxProps,
38
40
  Highlight,
39
41
  ImageSurfaceProps,
40
42
  InputRenderable,
@@ -4,7 +4,6 @@ import { tmpdir } from "os";
4
4
  import { join } from "path";
5
5
  import { gzipSync } from "zlib";
6
6
  import {
7
- canSelfUpdate,
8
7
  checkForUpdate,
9
8
  checkForUpdateDetailed,
10
9
  detectUpdateAction,
@@ -153,26 +152,6 @@ describe("resolveSelfUpdateTargetPath", () => {
153
152
  });
154
153
  });
155
154
 
156
- describe("canSelfUpdate", () => {
157
- test("returns false for manual update releases", () => {
158
- expect(canSelfUpdate({
159
- updateAction: { kind: "manual", command: "bun install -g gloomberb@latest" },
160
- })).toBe(false);
161
- });
162
-
163
- test("returns true for self-update releases", () => {
164
- expect(canSelfUpdate({
165
- updateAction: { kind: "self" },
166
- })).toBe(true);
167
- });
168
-
169
- test("returns true for desktop update releases", () => {
170
- expect(canSelfUpdate({
171
- updateAction: { kind: "desktop" },
172
- })).toBe(true);
173
- });
174
- });
175
-
176
155
  describe("update host", () => {
177
156
  test("delegates update checks to an installed host", async () => {
178
157
  setUpdateHost({