gloomberb 0.18.4 → 0.19.0

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 (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -1,4 +1,4 @@
1
- import { readdir, rm } from "fs/promises";
1
+ import { open, readdir, rm } from "fs/promises";
2
2
  import { basename, dirname, join } from "path";
3
3
  import type Electrobun from "electrobun/bun";
4
4
  import type { UpdateStatusEntry } from "electrobun/bun";
@@ -33,13 +33,14 @@ import {
33
33
  import { logFailure } from "../../../../utils/log-failure";
34
34
  import { compareSemver, isPrereleaseVersion } from "../../../../utils/semver";
35
35
  import { VERSION } from "../../../../version";
36
+ import { DESKTOP_APP_NAME, LEGACY_DESKTOP_APP_NAME } from "../../shared/app-name";
36
37
 
37
38
  type ElectrobunUpdater = typeof Electrobun.Updater;
38
39
 
39
40
  /**
40
41
  * The part of Electrobun's updater this flow uses. Tests pass a fake, because
41
42
  * the real module cannot be loaded or exercised outside the packaged app.
42
- * `setUpdateSource` is Gloomberb's patch (patches/electrobun@*.patch).
43
+ * `setUpdateSource` is this app's patch (patches/electrobun@*.patch).
43
44
  */
44
45
  export type ElectrobunUpdaterApi = Pick<
45
46
  ElectrobunUpdater,
@@ -51,7 +52,7 @@ export type ElectrobunUpdaterApi = Pick<
51
52
  | "getStatusHistory"
52
53
  | "onStatusChange"
53
54
  | "setUpdateSource"
54
- > & {
55
+ > & Partial<Pick<ElectrobunUpdater, "appDataFolder">> & {
55
56
  localInfo: Pick<ElectrobunUpdater["localInfo"], "channel" | "baseUrl">;
56
57
  };
57
58
 
@@ -81,6 +82,8 @@ function isUpdateAside(entry: string, appName: string): boolean {
81
82
  * process runs from: starting from it means the install that left them worked.
82
83
  * macOS keeps the app in its bundle, Linux in `<app data>/app` (`bin/bun`
83
84
  * inside it); Windows installs through its own script and leaves none.
85
+ * Beside `Gloom.app` it also takes `Gloomberb.app.previous`: the copy the
86
+ * update that brought the new name moved aside, under the old one.
84
87
  * Resolves to the paths it removed.
85
88
  */
86
89
  export async function removeUpdateAsides(
@@ -92,9 +95,12 @@ export async function removeUpdateAsides(
92
95
  : platform === "linux" && basename(dirname(execPath)) === "bin" ? dirname(dirname(execPath)) : undefined;
93
96
  if (!app) return [];
94
97
  const folder = dirname(app);
98
+ const names = platform === "darwin" && basename(app) === `${DESKTOP_APP_NAME}.app`
99
+ ? [basename(app), `${LEGACY_DESKTOP_APP_NAME}.app`]
100
+ : [basename(app)];
95
101
  const removed: string[] = [];
96
102
  for (const entry of await readdir(folder).catch(() => [] as string[])) {
97
- if (!isUpdateAside(entry, basename(app))) continue;
103
+ if (!names.some((name) => isUpdateAside(entry, name))) continue;
98
104
  const path = join(folder, entry);
99
105
  // One that still cannot be removed (another user's) is tried again at the next start.
100
106
  const done = await rm(path, { recursive: true, force: true }).then(() => true, logFailure("desktop-update", "Could not remove an old copy of the app", false));
@@ -103,6 +109,142 @@ export async function removeUpdateAsides(
103
109
  return removed;
104
110
  }
105
111
 
112
+ const TAR_BLOCK = 512;
113
+
114
+ /**
115
+ * The first path in a tar, read from its leading headers: a ustar name and
116
+ * prefix, or the long name a pax (`x`) or GNU (`L`) header gives it. Null when
117
+ * `bytes` ends before one.
118
+ */
119
+ export function firstTarPath(bytes: Uint8Array): string | null {
120
+ const decoder = new TextDecoder();
121
+ const field = (start: number, length: number) => {
122
+ const raw = bytes.subarray(start, start + length);
123
+ const end = raw.indexOf(0);
124
+ return decoder.decode(end === -1 ? raw : raw.subarray(0, end));
125
+ };
126
+ let longName: string | null = null;
127
+ for (let header = 0; header + TAR_BLOCK <= bytes.length;) {
128
+ if (bytes[header] === 0) return null;
129
+ const type = String.fromCharCode(bytes[header + 156] ?? 0);
130
+ const size = Number.parseInt(field(header + 124, 12).trim() || "0", 8);
131
+ const data = header + TAR_BLOCK;
132
+ if (type === "x" || type === "L" || type === "g" || type === "K") {
133
+ if (!Number.isFinite(size) || data + size > bytes.length) return null;
134
+ if (type === "L") longName = field(data, size);
135
+ if (type === "x") longName = /(?:^|\n)\d+ path=([^\n]*)\n/.exec(decoder.decode(bytes.subarray(data, data + size)))?.[1] ?? longName;
136
+ header = data + Math.ceil(size / TAR_BLOCK) * TAR_BLOCK;
137
+ continue;
138
+ }
139
+ if (longName) return longName;
140
+ const name = field(header, 100);
141
+ // POSIX ustar ("ustar\0") only: GNU tar keeps other fields where the prefix would be.
142
+ const prefix = field(header + 257, 6) === "ustar" ? field(header + 345, 155) : "";
143
+ return prefix ? `${prefix}/${name}` : name;
144
+ }
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * An octal number field of a tar header, read as tar readers do: the digits
150
+ * after any leading spaces, up to the first other character. The checksum is
151
+ * what tells a header from noise.
152
+ */
153
+ function tarOctal(header: Uint8Array, start: number, length: number): number {
154
+ const digits = /^[\0 ]*([0-7]*)/.exec(new TextDecoder().decode(header.subarray(start, start + length)))?.[1];
155
+ return digits ? Number.parseInt(digits, 8) : 0;
156
+ }
157
+
158
+ /** Whether a header's checksum matches it: its bytes summed with the checksum field as spaces. */
159
+ function tarHeaderChecksumMatches(header: Uint8Array): boolean {
160
+ const stored = tarOctal(header, 148, 8);
161
+ let unsigned = 0;
162
+ let signed = 0;
163
+ for (let index = 0; index < TAR_BLOCK; index += 1) {
164
+ const byte = index >= 148 && index < 156 ? 0x20 : header[index]!;
165
+ unsigned += byte;
166
+ signed += byte > 127 ? byte - 256 : byte;
167
+ }
168
+ return stored === unsigned || stored === signed;
169
+ }
170
+
171
+ /**
172
+ * Whether the file at `path` is a whole tar: every header from the first
173
+ * matches its checksum, and the entries reach the end-of-archive block before
174
+ * the file ends. A tar cut off partway (a decompress that failed, an app that
175
+ * quit while writing it) or no tar at all is not. It reads the headers only,
176
+ * so it says nothing about the files' contents.
177
+ */
178
+ export async function isWholeTar(path: string): Promise<boolean> {
179
+ const file = await open(path, "r").catch(() => null);
180
+ if (!file) return false;
181
+ try {
182
+ const { size } = await file.stat();
183
+ const header = new Uint8Array(TAR_BLOCK);
184
+ for (let offset = 0; offset + TAR_BLOCK <= size;) {
185
+ const { bytesRead } = await file.read(header, 0, TAR_BLOCK, offset);
186
+ if (bytesRead < TAR_BLOCK) return false;
187
+ if (header.every((byte) => byte === 0)) return offset > 0;
188
+ if (!tarHeaderChecksumMatches(header)) return false;
189
+ // Octal: the base-256 sizes past 8 GB are not in an app's archive.
190
+ const entrySize = tarOctal(header, 124, 12);
191
+ offset += TAR_BLOCK + Math.ceil(entrySize / TAR_BLOCK) * TAR_BLOCK;
192
+ }
193
+ return false;
194
+ } catch {
195
+ return false;
196
+ } finally {
197
+ await file.close().catch(() => {});
198
+ }
199
+ }
200
+
201
+ /** Whether `path` holds a file that is not a whole tar. A missing one is not damaged. */
202
+ async function isDamagedTar(path: string): Promise<boolean> {
203
+ return await Bun.file(path).exists() && !(await isWholeTar(path));
204
+ }
205
+
206
+ /** Where Electrobun keeps the update with build hash `hash` once it is downloaded. */
207
+ function stagedArchivePath(extractionFolder: string, hash: string | undefined): string | null {
208
+ return hash ? join(extractionFolder, `${hash}.tar`) : null;
209
+ }
210
+
211
+ async function removeStagedArchive(path: string): Promise<void> {
212
+ await rm(path, { force: true }).catch(logFailure("desktop-update", "Could not remove a damaged update archive"));
213
+ }
214
+
215
+ /**
216
+ * Removes the unpacked update archives in Electrobun's `self-extraction`
217
+ * folder it must not build on. Electrobun takes `<hash>.tar` there as the
218
+ * update already downloaded without reading it, and a damaged one (part of a
219
+ * tar, from a decompress that failed) then failed to unpack at every install:
220
+ * without it, the download fetches a fresh copy. Also removed are the ones
221
+ * whose app carries its name from before the rename. The one that matters is
222
+ * the Windows archive an install from then updated from: it is packed again
223
+ * with the folder renamed (scripts/write-legacy-desktop-files.ts), so its
224
+ * bytes are not those of the archive the next release's patch is made from,
225
+ * and bspatch would turn it into a broken build. Without it the updater
226
+ * downloads the full archive instead. Resolves to the paths it removed.
227
+ */
228
+ async function removeUnusableUpdateArchives(folder: string): Promise<string[]> {
229
+ const removed: string[] = [];
230
+ for (const entry of await readdir(folder).catch(() => [] as string[])) {
231
+ if (!entry.endsWith(".tar")) continue;
232
+ const path = join(folder, entry);
233
+ let reason: string;
234
+ if (!(await isWholeTar(path))) {
235
+ reason = "Could not remove a damaged update archive";
236
+ } else {
237
+ const head = await Bun.file(path).slice(0, 64 * 1024).bytes().catch(() => null);
238
+ const top = head ? firstTarPath(head)?.replace(/^\.\//, "").split("/")[0] : undefined;
239
+ if (top !== LEGACY_DESKTOP_APP_NAME && top !== `${LEGACY_DESKTOP_APP_NAME}.app`) continue;
240
+ reason = "Could not remove an update archive from before the rename";
241
+ }
242
+ const done = await rm(path, { force: true }).then(() => true, logFailure("desktop-update", reason, false));
243
+ if (done) removed.push(path);
244
+ }
245
+ return removed;
246
+ }
247
+
106
248
  /**
107
249
  * Where the running app sits, for update failure reports and for what an
108
250
  * install that cannot replace it means. Electrobun replaces the macOS bundle
@@ -278,6 +420,12 @@ export function createElectrobunDesktopUpdater(
278
420
  /** An apply is under way, or finished and the app is quitting. */
279
421
  let applyInProgress = false;
280
422
 
423
+ /** Electrobun's `self-extraction` folder, where the download leaves the update as `<hash>.tar`. */
424
+ async function extractionFolder(): Promise<string | null> {
425
+ const appData = await updater.appDataFolder?.().catch(() => null);
426
+ return appData ? join(appData, "self-extraction") : null;
427
+ }
428
+
281
429
  /** The error progress for a failed step, reported to error tracking on the way. */
282
430
  function failed(failure: UpdateFailure, error: string, release?: { version?: string; channel?: UpdateChannel } | null): UpdateProgress {
283
431
  reportUpdateFailure(failure, {
@@ -464,6 +612,8 @@ export function createElectrobunDesktopUpdater(
464
612
  }
465
613
  target = result.release;
466
614
 
615
+ const extraction = await extractionFolder();
616
+ if (extraction) await removeUnusableUpdateArchives(extraction);
467
617
  await updater.downloadUpdate();
468
618
  const updateInfo = updater.updateInfo();
469
619
  if (updateInfo?.error) {
@@ -474,6 +624,15 @@ export function createElectrobunDesktopUpdater(
474
624
  onProgress(failed({ phase: "download", code: "interrupted" }, "Desktop update did not finish downloading.", result.release));
475
625
  return;
476
626
  }
627
+ // However Electrobun came by it (a patch, the full download), an archive
628
+ // that is not whole is never offered for a restart. It goes, so the retry
629
+ // downloads it again.
630
+ const staged = extraction ? stagedArchivePath(extraction, updateInfo.hash) : null;
631
+ if (staged && await isDamagedTar(staged)) {
632
+ await removeStagedArchive(staged);
633
+ onProgress(failed({ phase: "verify", code: "corrupt" }, "The downloaded update is damaged.", result.release));
634
+ return;
635
+ }
477
636
 
478
637
  // Downloaded, not installed: applying relaunches the app, and that waits
479
638
  // for the user's explicit request (`apply`).
@@ -503,9 +662,12 @@ export function createElectrobunDesktopUpdater(
503
662
  }
504
663
 
505
664
  const target = readyRelease;
665
+ const stagedHash = updater.updateInfo()?.hash;
506
666
  applyInProgress = true;
507
667
  let relaunching = false;
508
668
  let reportedError = false;
669
+ /** The install failed on the archive itself: it does not unpack. */
670
+ let damaged = false;
509
671
  const report = (progress: UpdateProgress) => {
510
672
  if (progress.phase === "error") reportedError = true;
511
673
  onProgress(progress);
@@ -513,6 +675,7 @@ export function createElectrobunDesktopUpdater(
513
675
  const installFailed = (error: unknown, message: string) => {
514
676
  // Electrobun names the system error in its message ("Failed to replace app: EACCES: ...").
515
677
  const code = installFailureCode(classifyUpdateError(error), installKind);
678
+ if (code === "corrupt") damaged = true;
516
679
  report(failed({ phase: relaunching ? "relaunch" : "apply", code }, message, target));
517
680
  };
518
681
  // Kept before Electrobun quits the app, so the next launch can tell whether it took.
@@ -543,9 +706,14 @@ export function createElectrobunDesktopUpdater(
543
706
  }
544
707
 
545
708
  // Past this point the app is quitting, so the guard stays up. A failed apply
546
- // drops the staged release: the retry starts from the download again.
709
+ // drops the staged release: the retry starts from the download again. An
710
+ // archive that does not unpack goes too, or that download would take it as
711
+ // done once more and the install would fail on it again.
547
712
  if (!relaunching) {
548
713
  readyRelease = null;
714
+ const extraction = damaged ? await extractionFolder() : null;
715
+ const staged = extraction ? stagedArchivePath(extraction, stagedHash) : null;
716
+ if (staged) await removeStagedArchive(staged);
549
717
  applyInProgress = false;
550
718
  }
551
719
  }
@@ -0,0 +1,52 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import type { DesktopViewIds } from "../../shared/protocol";
4
+
5
+ /**
6
+ * The copy on disk of the ids the desktop view keeps in its WebView storage
7
+ * (view/view-ids.ts), in the data folder beside the install id. That storage
8
+ * belongs to the app's bundle identifier, and this copy is what a build under
9
+ * another identifier starts from.
10
+ */
11
+ const FILE_NAME = "desktop-view-ids.json";
12
+ const KEY = /^gloomberb\.[a-z0-9.-]{1,64}$/i;
13
+ const MAX_IDS = 16;
14
+ const MAX_VALUE_LENGTH = 4096;
15
+
16
+ function viewIdsPath(dataDir: string): string {
17
+ return join(dataDir, FILE_NAME);
18
+ }
19
+
20
+ /** The entries of `raw` shaped like view ids: a storage key of this app's and a short string. */
21
+ function parseViewIds(raw: unknown): DesktopViewIds {
22
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
23
+ const ids: DesktopViewIds = {};
24
+ for (const [key, value] of Object.entries(raw).slice(0, MAX_IDS)) {
25
+ if (KEY.test(key) && typeof value === "string" && value.length > 0 && value.length <= MAX_VALUE_LENGTH) ids[key] = value;
26
+ }
27
+ return ids;
28
+ }
29
+
30
+ /** What the data folder keeps; nothing when the file is missing or unreadable. */
31
+ export function readDesktopViewIds(dataDir: string): DesktopViewIds {
32
+ try {
33
+ return parseViewIds(JSON.parse(readFileSync(viewIdsPath(dataDir), "utf8")));
34
+ } catch {
35
+ // Intentional ignore: no copy yet (a first launch, or a build from before it was kept), so the view keeps what it has.
36
+ return {};
37
+ }
38
+ }
39
+
40
+ /** Replaces the copy with what the view sent, written whole or not at all. Throws when it cannot, so the view tries again. */
41
+ export function saveDesktopViewIds(dataDir: string, ids: unknown): void {
42
+ const path = viewIdsPath(dataDir);
43
+ const temporary = `${path}.${process.pid}.tmp`;
44
+ try {
45
+ mkdirSync(dataDir, { recursive: true });
46
+ writeFileSync(temporary, JSON.stringify(parseViewIds(ids)));
47
+ renameSync(temporary, path);
48
+ } catch (error) {
49
+ rmSync(temporary, { force: true });
50
+ throw error;
51
+ }
52
+ }
@@ -151,6 +151,7 @@ export function createDesktopWorkspace(
151
151
  paneState: clonePaneStateMap(sharedState.paneState),
152
152
  focusedPaneId: sharedState.focusedPaneId,
153
153
  statusBarVisible: sharedState.statusBarVisible,
154
+ brokerSyncErrors: { ...sharedState.brokerSyncErrors },
154
155
  mainStateRevision: sharedState.mainStateRevision,
155
156
  layoutChanged: sharedState.layoutChanged,
156
157
  });
@@ -179,6 +180,7 @@ export function createDesktopWorkspace(
179
180
  paneState: filterPaneState(syncedConfig.layout, clonePaneStateMap(snapshot.paneState)),
180
181
  focusedPaneId: snapshot.focusedPaneId,
181
182
  statusBarVisible: snapshot.statusBarVisible,
183
+ brokerSyncErrors: { ...snapshot.brokerSyncErrors },
182
184
  mainStateRevision: snapshot.mainStateRevision ?? currentRevision,
183
185
  layoutChanged: snapshot.layoutChanged,
184
186
  };
@@ -1,4 +1,5 @@
1
- import Electrobun, { ApplicationMenu, Screen } from "electrobun/bun";
1
+ import Electrobun, { ApplicationMenu, Screen, Utils } from "electrobun/bun";
2
+ import { homedir } from "os";
2
3
  import { debugLog } from "../../../utils/debug-log";
3
4
  import { setConfigStoreHost } from "../../../data/config/store";
4
5
  import * as nodeConfigStoreHost from "../../../data/config/store/node";
@@ -25,6 +26,8 @@ import {
25
26
  } from "./window/remembered-frame";
26
27
  import { reapStaleTerminalMedia } from "../../opentui/terminal-media";
27
28
  import { installProcessCrashListeners } from "../../../telemetry/crash-reports-node";
29
+ import { renameLegacyMacApp } from "./desktop/app-name";
30
+ import { scheduleDesktopRelaunch } from "./desktop/relaunch";
28
31
 
29
32
  console.log = (...args) => console.error(...args);
30
33
  console.info = (...args) => console.error(...args);
@@ -42,6 +45,27 @@ setConfigStoreHost(nodeConfigStoreHost);
42
45
  // before the config, and its off switch, has been read.
43
46
  installProcessCrashListeners();
44
47
 
48
+ // An install from before the rename runs from Gloomberb.app, where its updater
49
+ // put this build. It takes the new name and opens again from there before any
50
+ // window shows. Anywhere else this returns without touching the disk.
51
+ const appRename = await renameLegacyMacApp({
52
+ execPath: process.execPath,
53
+ platform: process.platform,
54
+ homeDir: homedir(),
55
+ pathEnv: process.env.PATH ?? "",
56
+ }).catch((error: unknown) => {
57
+ console.error("[desktop] could not give the app its new name", error instanceof Error ? error.message : String(error));
58
+ return null;
59
+ });
60
+ if (appRename?.kind === "renamed") {
61
+ scheduleDesktopRelaunch(appRename.bundle);
62
+ Utils.quit();
63
+ // The process is exiting; nothing below may open a window meanwhile.
64
+ await new Promise<never>(() => {});
65
+ } else if (appRename?.kind === "skipped") {
66
+ console.error(`[desktop] kept the app's old name: ${appRename.reason}`);
67
+ }
68
+
45
69
  function readOpenUrlEvent(event: unknown): string | null {
46
70
  const data = event && typeof event === "object" ? (event as { data?: unknown }).data : null;
47
71
  if (!data || typeof data !== "object") return null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The desktop app is Gloom. Builds up to 0.18.4 were named Gloomberb, and an
3
+ * install from then still is: `Gloomberb.app` on a Mac, a `Gloomberb` folder
4
+ * inside the Windows update archive, and an updater that asks the release for
5
+ * `<channel>-<os>-<arch>-Gloomberb.*` files. The release keeps publishing
6
+ * those under the old name (scripts/write-legacy-desktop-files.ts), and a Mac
7
+ * bundle still named Gloomberb.app renames itself (bun/desktop/app-name.ts).
8
+ */
9
+ export const DESKTOP_APP_NAME = "Gloom";
10
+ export const LEGACY_DESKTOP_APP_NAME = "Gloomberb";
11
+
12
+ /**
13
+ * Kept through the rename. Electrobun names the app's data and update folders
14
+ * after it, and macOS ties the app's settings, WebKit storage and permissions
15
+ * to it.
16
+ */
17
+ export const DESKTOP_APP_IDENTIFIER = "com.vincelwt.gloomberb";
18
+
19
+ /**
20
+ * The identifiers a build of this app carries: the one above, and the one a
21
+ * later release moves to. Until every install has moved, either one can be
22
+ * on disk (an app bundle, an install the script or Homebrew replaces), so
23
+ * anything that asks whether a copy is this app accepts both.
24
+ */
25
+ export const DESKTOP_APP_IDENTIFIERS: readonly string[] = [DESKTOP_APP_IDENTIFIER, "sh.gloom.desktop"];
26
+
27
+ /** Whether `identifier` is one a build of this app carries. */
28
+ export function isDesktopAppIdentifier(identifier: string | null | undefined): boolean {
29
+ return !!identifier && DESKTOP_APP_IDENTIFIERS.includes(identifier);
30
+ }
@@ -10,6 +10,7 @@ import type { CapabilityManifest } from "../../../capabilities";
10
10
  import type { PluginOperationResult, PluginPin } from "../../../plugins/marketplace/store";
11
11
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../remote/types";
12
12
  import type { HttpProxyRequestEnvelope, HttpProxyResponseEnvelope } from "../../../utils/http-proxy-response";
13
+ import type { StallEvent } from "../../../utils/main-thread-stall";
13
14
  import type { BootFrame } from "../../dom/boot-frame-page";
14
15
 
15
16
  export const ELECTROBUN_CONTEXT_MENU_ACTION = "gloom.context-menu.select";
@@ -32,8 +33,13 @@ export interface ElectrobunBackendInit {
32
33
  pluginSeedNotice?: { body: string; type: "error" | "info" };
33
34
  /** What the view needs to send crash reports and usage counts the same way the Bun process would. */
34
35
  telemetry: ElectrobunTelemetryInit;
36
+ /** The copy on disk of the ids the view keeps in its own storage (view/view-ids.ts). */
37
+ viewIds: DesktopViewIds;
35
38
  }
36
39
 
40
+ /** Ids the view keeps in its WebView storage, by storage key. */
41
+ export type DesktopViewIds = Record<string, string>;
42
+
37
43
  interface ElectrobunTelemetryInit {
38
44
  /** Random per-install id from `<dataDir>/install-id`; null when the file could not be written. */
39
45
  installId: string | null;
@@ -222,6 +228,8 @@ interface DesktopBackendRequestMap {
222
228
  "host.showContextMenu": { request: { menu: DesktopContextMenuItem[] }; response: boolean };
223
229
  /** The layout the main window draws at the next launch before its script loads (dom/boot-frame.ts). Only the main window's is kept. */
224
230
  "host.saveBootFrame": { request: { frame: BootFrame }; response: null };
231
+ /** The ids the view keeps in its storage, for the copy on disk. Only the main window's are kept. */
232
+ "host.saveViewIds": { request: { ids: DesktopViewIds }; response: null };
225
233
  /** The unread count for the Dock icon badge: `""` (none), `"1"` to `"99"` or `"99+"`. Only the main window's is applied. */
226
234
  "host.setAppBadge": { request: { label: string }; response: null };
227
235
  /** `channel` absent (an older view) means stable. */
@@ -337,6 +345,8 @@ export interface ElectrobunDesktopRpcSchema {
337
345
  };
338
346
  messages: {
339
347
  "host.restart": DesktopRestartMessage;
348
+ /** A window froze; the Bun process keeps the stall log and its report. */
349
+ "runtime.stall": StallEvent;
340
350
  };
341
351
  };
342
352
  webview: {
@@ -17,6 +17,7 @@ import { subscribeCapability, type CapabilitySubscriptionOptions } from "./capab
17
17
  import { RpcTimeoutError } from "../../../utils/rpc-timeout-error";
18
18
  import { nameRpcTimeout, RPC_MAX_REQUEST_TIME_MS } from "./rpc-timeout";
19
19
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../remote/types";
20
+ import type { StallEvent } from "../../../utils/main-thread-stall";
20
21
 
21
22
  type BackendMessages = ElectrobunDesktopRpcSchema["webview"]["messages"];
22
23
  type BackendMessageName = keyof BackendMessages;
@@ -153,6 +154,11 @@ export function requestElectrobunRestart(message: DesktopRestartMessage = {}): v
153
154
  rpc.send["host.restart"](message);
154
155
  }
155
156
 
157
+ /** The Bun process keeps the stall log and sends its report. */
158
+ export function sendElectrobunStall(event: StallEvent): void {
159
+ rpc.send["runtime.stall"](event);
160
+ }
161
+
156
162
  export function getElectrobunBackendInitSnapshot(): ElectrobunBackendInit | null {
157
163
  return initSnapshot;
158
164
  }
@@ -125,8 +125,8 @@ function useShotStatusLine(enabled: boolean, hasAsOf: () => boolean): ShotStatus
125
125
  }
126
126
 
127
127
  /**
128
- * Static captures retain warnings and optional source status without action
129
- * shortcuts. With `statusLine` the footer also carries the capture's dated
128
+ * Static captures retain warnings, what a filter leaves out (`capture`) and
129
+ * optional source status without action shortcuts. With `statusLine` the footer also carries the capture's dated
130
130
  * status line (`Fri 9 Oct close · 15 min delayed · markets closed until Mon`),
131
131
  * inside the requested size: the body gives up the footer row for it. The
132
132
  * pane's own text that says the same goes, so the date is said once: its `as
@@ -149,7 +149,7 @@ export function PaneShotFrame({ paneId, title, width, height, preserveStatus = f
149
149
  const datesData = statusLine && dates === "data";
150
150
  return <PaneFooterProvider>{(registeredFooter) => {
151
151
  const status: PaneFooterSegment[] = statusLine ? [{ id: "shot-status", parts: [{ text: statusText, tone: "label" }] }] : [];
152
- const info = registeredFooter.info.filter((segment) => (preserveStatus || segment.icon === "warning")
152
+ const info = registeredFooter.info.filter((segment) => (preserveStatus || segment.icon === "warning" || segment.capture)
153
153
  && !(datesPaneData(segment) && (datesData || crowded))
154
154
  && !(segment.quoteStatus && statusLine && dates !== null));
155
155
  shownAsOf.current = info.some(datesPaneData);
@@ -32,6 +32,7 @@ export function detachedSnapshotKey(snapshot: DesktopSharedStateSnapshot, paneId
32
32
  portfolios: snapshot.config.portfolios,
33
33
  watchlists: snapshot.config.watchlists,
34
34
  brokerInstances: snapshot.config.brokerInstances,
35
+ brokerSyncErrors: snapshot.brokerSyncErrors ?? {},
35
36
  pluginConfig: snapshot.config.pluginConfig,
36
37
  disabledPlugins: snapshot.config.disabledPlugins,
37
38
  chartPreferences: snapshot.config.chartPreferences,
@@ -5,12 +5,14 @@ import { App } from "../../../app";
5
5
  import { applyLanguageFromConfig } from "../../../i18n";
6
6
  import { debugLog } from "../../../utils/debug-log";
7
7
  import { capturePaneImage } from "../../../utils/dom-screenshot";
8
+ import { setStallSink } from "../../../utils/main-thread-stall";
8
9
  import { measurePerfAsync } from "../../../utils/perf-marks";
9
10
  import {
10
11
  backendRequest,
11
12
  initElectrobunBackend,
12
13
  onBackendMessage,
13
14
  replaceElectrobunCapabilityManifests,
15
+ sendElectrobunStall,
14
16
  setElectrobunRemoteRequestHandler,
15
17
  } from "./backend-rpc";
16
18
  import { installElectrobunCapabilityStreamClient } from "./capability-stream-client";
@@ -49,10 +51,13 @@ import { loadOfficialPluginIds } from "../../../plugins/marketplace/feed";
49
51
  import { setPluginSeedNotice } from "../../../plugins/seed-notice";
50
52
  import { crashReportsEnabled, installCrashReporter, reportCrash } from "../../../telemetry/crash-reports";
51
53
  import { browserDoNotTrack, installWindowCrashListeners } from "../../../telemetry/crash-reports-dom";
54
+ import { forwardAttendedStalls } from "../../../telemetry/stall-reports-dom";
52
55
  import { currentTelemetryConfig } from "../../../telemetry/live-config";
53
56
  import { attentionCountsEnabled, installAttentionCounter } from "../../../telemetry/attention-counts";
54
57
  import { installUsageCounter, usageCountsEnabled } from "../../../telemetry/usage-counts";
55
58
  import { installWindowUsageFlush } from "../../../telemetry/usage-counts-dom";
59
+ import { cloudSyncController } from "../../../sync/controller";
60
+ import { createViewIdMirror, restoreViewIds, viewIdStorage } from "./view-ids";
56
61
 
57
62
  // Declared here rather than sniffed: the desktop view and the hosted browser
58
63
  // app are both browser contexts but differ in what plugins may do.
@@ -156,6 +161,8 @@ async function boot() {
156
161
  officialPluginIds: loadOfficialPluginIds,
157
162
  });
158
163
  installAttentionCounter(() => !init.telemetry.optedOut && !browserDoNotTrack() && attentionCountsEnabled(currentTelemetryConfig(init.config)));
164
+ // Desktop has no Debug Log: this window's freezes go to the Bun process, which keeps the stall log.
165
+ setStallSink(forwardAttendedStalls(sendElectrobunStall).sink);
159
166
  installElectrobunCapabilityStreamClient();
160
167
  installFocusScopeRelease();
161
168
  installElectrobunWindowFullscreenTracking();
@@ -173,10 +180,26 @@ async function boot() {
173
180
  const desktopApplicationMenuBridge: DesktopApplicationMenuBridge = {
174
181
  subscribe: (listener) => onBackendMessage("application-menu.select", ({ command }) => listener(command)),
175
182
  };
183
+ // Before anything reads them: ids this view's storage lacks come back from
184
+ // the copy on disk, and the main window keeps that copy up to date. The sync
185
+ // id is made at the first push, and a gloom.sh link can bring a visitor id.
186
+ const viewStorage = viewIdStorage();
187
+ if (viewStorage) restoreViewIds(viewStorage, init.viewIds);
188
+ const mirrorViewIds = viewStorage && init.windowKind === "main"
189
+ ? createViewIdMirror(viewStorage, init.viewIds, (ids) => backendRequest("host.saveViewIds", { ids }))
190
+ : () => {};
191
+ mirrorViewIds();
192
+ cloudSyncController.subscribe(mirrorViewIds);
176
193
  initializeDesktopResearchActivity();
177
- const desktopDeepLinkBridge = observeDesktopDeepLinks({
194
+ const observedDeepLinks = observeDesktopDeepLinks({
178
195
  subscribe: (listener) => onBackendMessage("desktop.deepLink", listener),
179
196
  });
197
+ const desktopDeepLinkBridge: typeof observedDeepLinks = {
198
+ subscribe: (listener) => observedDeepLinks.subscribe((deeplink) => {
199
+ listener(deeplink);
200
+ mirrorViewIds();
201
+ }),
202
+ };
180
203
  const webUiHost = createWebUiHost(init.desktopPlatform);
181
204
  // Compiled by the Bun process, which owns the filesystem. A failure here must
182
205
  // not stop the app from starting: the marketplace reports broken plugins, and
@@ -10,7 +10,8 @@ interface BackendNews {
10
10
  getNewsPage(query: NewsQuery): Promise<NewsPage>;
11
11
  }
12
12
 
13
- type CloudNewsClient = Pick<typeof apiClient, "getCloudNews" | "getCloudNewsStory" | "searchCloudNews">;
13
+ type CloudNewsClient = Pick<typeof apiClient, "getCloudNews" | "getCloudNewsStory" | "searchCloudNews">
14
+ & Partial<Pick<typeof apiClient, "hasSessionCredential">>;
14
15
 
15
16
  /**
16
17
  * The view's only news source: the desktop view runs without capability
@@ -32,6 +33,7 @@ export function createDesktopNewsSource(backend: BackendNews, client: CloudNewsC
32
33
  return {
33
34
  articles: cloudNewsReadArticles(read, query.ticker),
34
35
  nextCursor: read.nextCursor ?? null,
36
+ access: read.access ?? null,
35
37
  };
36
38
  } catch {
37
39
  // The aggregate page keeps RSS failures beside any usable stories.
@@ -1,5 +1,6 @@
1
1
  import type { TickerMetadata, TickerRecord } from "../../../../types/ticker";
2
2
  import { backendRequest } from "../backend-rpc";
3
+ import { runTickerWrite } from "../../../../data/ticker-writes";
3
4
 
4
5
  export class RemoteTickerRepository {
5
6
  async loadAllTickers(): Promise<TickerRecord[]> {
@@ -10,17 +11,23 @@ export class RemoteTickerRepository {
10
11
  return backendRequest("ticker.load", { symbol });
11
12
  }
12
13
 
13
- async saveTicker(ticker: TickerRecord): Promise<void> {
14
- await backendRequest("ticker.save", { ticker });
14
+ saveTicker(ticker: TickerRecord): Promise<void> {
15
+ return runTickerWrite(this, ticker.metadata.ticker, async () => {
16
+ await backendRequest("ticker.save", { ticker });
17
+ });
15
18
  }
16
19
 
17
- async createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
20
+ createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
18
21
  const ticker: TickerRecord = { metadata };
19
- await this.saveTicker(ticker);
20
- return ticker;
22
+ return runTickerWrite(this, metadata.ticker, async () => {
23
+ await backendRequest("ticker.save", { ticker });
24
+ return ticker;
25
+ });
21
26
  }
22
27
 
23
- async deleteTicker(symbol: string): Promise<void> {
24
- await backendRequest("ticker.delete", { symbol });
28
+ deleteTicker(symbol: string): Promise<void> {
29
+ return runTickerWrite(this, symbol, async () => {
30
+ await backendRequest("ticker.delete", { symbol });
31
+ });
25
32
  }
26
33
  }