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,6 @@
1
1
  import { saveConfigImmediately, scheduleConfigSave } from "../state/config-save-scheduler";
2
+ import { persistTickerCleanup } from "../state/ticker-cleanup";
3
+ import { hasPendingTickerWrites } from "../data/ticker-writes";
2
4
  import { normalizeStoredBaseCurrency } from "../utils/base-currency";
3
5
  import { debugLog } from "../utils/debug-log";
4
6
  import { fnv1aHex, stableStringify } from "../utils/hash";
@@ -1184,6 +1186,23 @@ function holdingIn(positions: readonly TickerPosition[], links: readonly unknown
1184
1186
  };
1185
1187
  }
1186
1188
 
1189
+ /** Without a shared baseline, keep both devices' manual lots, matching identical copies one for one. */
1190
+ function mergeUntrackedManualPositions(local: TickerPosition[], pulled: TickerPosition[]): TickerPosition[] {
1191
+ const unmatched = new Map<string, number>();
1192
+ for (const position of local) {
1193
+ const key = JSON.stringify(sanitizePosition(position));
1194
+ unmatched.set(key, (unmatched.get(key) ?? 0) + 1);
1195
+ }
1196
+ const merged = [...local];
1197
+ for (const position of pulled) {
1198
+ const key = JSON.stringify(sanitizePosition(position));
1199
+ const count = unmatched.get(key) ?? 0;
1200
+ if (count > 0) unmatched.set(key, count - 1);
1201
+ else merged.push(position);
1202
+ }
1203
+ return merged;
1204
+ }
1205
+
1187
1206
  interface PulledTickersMerge {
1188
1207
  lastSyncedTickers: Map<string, Record<string, unknown>> | null;
1189
1208
  /** This device's live broker portfolios, each with the side holding its latest broker sync. */
@@ -1274,8 +1293,25 @@ function mergePulledTicker(
1274
1293
  holdingIn(baselinePositions, baselineLinks, portfolioId),
1275
1294
  );
1276
1295
  const [sidePositions, sideLinks] = takePulled ? [pulledPositions, pulledLinks] : [localPositions, localLinks];
1277
- positions.push(...sidePositions.filter((position) => position.portfolio === portfolioId));
1278
- if (sideLinks.includes(portfolioId)) portfolios.push(portfolioId);
1296
+ if (brokerSide && !merge.localOnly?.portfolioIds.has(portfolioId)) {
1297
+ // A broker's timestamp governs imported holdings, never hand-entered
1298
+ // lots. Those use the same edit comparison as a manual portfolio.
1299
+ const manualIn = (entries: readonly TickerPosition[]) => entries.filter((position) => (
1300
+ position.portfolio === portfolioId && position.broker === "manual"
1301
+ ));
1302
+ const localManual = manualIn(localPositions);
1303
+ const manualEdited = comparable && !sameAsStored(
1304
+ localManual.map(sanitizePosition), manualIn(baselinePositions).map(sanitizePosition),
1305
+ );
1306
+ const pulledManual = manualIn(pulledPositions);
1307
+ const manual = !comparable ? mergeUntrackedManualPositions(localManual, pulledManual)
1308
+ : manualEdited ? localManual : pulledManual;
1309
+ positions.push(...sidePositions.filter((position) => position.portfolio === portfolioId && position.broker !== "manual"), ...manual);
1310
+ if (sideLinks.includes(portfolioId) || manual.length > 0) portfolios.push(portfolioId);
1311
+ } else {
1312
+ positions.push(...sidePositions.filter((position) => position.portfolio === portfolioId));
1313
+ if (sideLinks.includes(portfolioId)) portfolios.push(portfolioId);
1314
+ }
1279
1315
  }
1280
1316
 
1281
1317
  const record: TickerRecord = {
@@ -1297,8 +1333,9 @@ function mergePulledTicker(
1297
1333
  function withoutUnpulledHoldings(ticker: TickerRecord, merge: PulledTickersMerge): TickerRecord | null {
1298
1334
  const sold = (portfolioId: string) => merge.brokerPortfolios.get(portfolioId) === "pulled";
1299
1335
  const { metadata } = ticker;
1300
- const positions = metadata.positions.filter((position) => !sold(position.portfolio));
1301
- const portfolios = metadata.portfolios.filter((portfolioId) => !sold(portfolioId));
1336
+ const positions = metadata.positions.filter((position) => position.broker === "manual" || !sold(position.portfolio));
1337
+ const held = new Set(positions.map((position) => position.portfolio));
1338
+ const portfolios = metadata.portfolios.filter((portfolioId) => held.has(portfolioId) || !sold(portfolioId));
1302
1339
  const kept = positions.length === metadata.positions.length && portfolios.length === metadata.portfolios.length
1303
1340
  ? ticker
1304
1341
  : { ...ticker, metadata: { ...metadata, positions, portfolios } };
@@ -1322,12 +1359,57 @@ export const coreConfigSyncContributor: SyncContributor = {
1322
1359
  },
1323
1360
  apply: async (payload, { snapshot, baselinePayload, baselineState, state, getState, isCurrent, dispatch, tickerRepository, memo }) => {
1324
1361
  const pushedAt = snapshot?.contributors[CORE_CONFIG_CONTRIBUTOR_ID]?.updatedAt;
1325
- const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config, baselinePayload, {
1362
+ const merge = (config: AppConfig) => mergeConfigPayload(config, payload, baselineState.config, baselinePayload, {
1326
1363
  memo: memo?.get(),
1327
1364
  pushedAt,
1328
- }, localOnlyScope(state.config));
1365
+ }, localOnlyScope(config));
1366
+ let nextConfig = merge(state.config);
1329
1367
  if (!nextConfig) return;
1330
- const configChanged = !valuesEqual(nextConfig, state.config);
1368
+ const removed = removedBrokerHoldings(state.config, nextConfig);
1369
+ if (removed) {
1370
+ // Keep the profile until row and config writes succeed, so a failed
1371
+ // cleanup remains visible to the next attempt.
1372
+ const cleanTickers = () => persistTickerCleanup({
1373
+ tickerRepository,
1374
+ getTickers: () => getState().tickers,
1375
+ transform: (ticker) => withoutRemovedBrokerHoldings(ticker, removed),
1376
+ publish: (symbol, ticker) => {
1377
+ const tickers = new Map(getState().tickers);
1378
+ if (ticker) tickers.set(symbol, ticker);
1379
+ else tickers.delete(symbol);
1380
+ dispatch({ type: "SET_TICKERS", tickers });
1381
+ },
1382
+ isCurrent,
1383
+ });
1384
+ let savedRemoval = false;
1385
+ for (;;) {
1386
+ const cleaned = await cleanTickers().catch(async (error: unknown) => {
1387
+ // Restore the profile if a late row fails after config was saved,
1388
+ // so retry can also recover after restarting the app.
1389
+ if (savedRemoval) await saveConfigImmediately(getState().config);
1390
+ throw error;
1391
+ });
1392
+ if (!cleaned || !isCurrent()) return;
1393
+ if (hasPendingTickerWrites(tickerRepository) || getState().tickers !== cleaned) continue;
1394
+ const source = getState().config;
1395
+ const merged = merge(source);
1396
+ if (!merged) return;
1397
+ // Preserve unrelated edits while retaining the removal already applied
1398
+ // to rows, including pulls from builds that merged whole profile lists.
1399
+ nextConfig = {
1400
+ ...merged,
1401
+ brokerInstances: merged.brokerInstances.filter((instance) => !removed.instanceIds.has(instance.id)),
1402
+ portfolios: merged.portfolios.filter((portfolio) => !removed.portfolioIds.has(portfolio.id)),
1403
+ };
1404
+ await saveConfigImmediately(nextConfig);
1405
+ savedRemoval = true;
1406
+ if (!isCurrent()) return;
1407
+ if (getState().config !== source || getState().tickers !== cleaned
1408
+ || hasPendingTickerWrites(tickerRepository)) continue;
1409
+ break;
1410
+ }
1411
+ }
1412
+ const configChanged = !valuesEqual(nextConfig, getState().config);
1331
1413
  if (configChanged) {
1332
1414
  // Adopting another device's workspace rearranges the screen under the
1333
1415
  // user, so it is worth a line in the log when a report says "my panes
@@ -1339,32 +1421,9 @@ export const coreConfigSyncContributor: SyncContributor = {
1339
1421
  });
1340
1422
  }
1341
1423
  dispatch({ type: "SET_CONFIG", config: nextConfig });
1342
- scheduleConfigSave(nextConfig);
1424
+ if (!removed) scheduleConfigSave(nextConfig);
1343
1425
  }
1344
1426
  if (memo && isRecord(payload)) rememberCloudLayout(payload, pushedAt, getState().config, memo, nextConfig.layouts !== state.config.layouts);
1345
- if (!configChanged) return;
1346
-
1347
- // A profile removed on another device takes its positions with it here
1348
- // too; kept, this device would push them back and recreate the tickers
1349
- // that removal deleted.
1350
- const removed = removedBrokerHoldings(state.config, nextConfig);
1351
- if (!removed) return;
1352
- const nextTickers = new Map(getState().tickers);
1353
- let changed = false;
1354
- for (const ticker of getState().tickers.values()) {
1355
- if (!isCurrent()) return;
1356
- const next = withoutRemovedBrokerHoldings(ticker, removed);
1357
- if (next === ticker) continue;
1358
- changed = true;
1359
- if (next) {
1360
- await tickerRepository.saveTicker(next);
1361
- nextTickers.set(next.metadata.ticker, next);
1362
- } else {
1363
- await tickerRepository.deleteTicker(ticker.metadata.ticker);
1364
- nextTickers.delete(ticker.metadata.ticker);
1365
- }
1366
- }
1367
- if (changed && isCurrent()) dispatch({ type: "SET_TICKERS", tickers: nextTickers });
1368
1427
  },
1369
1428
  };
1370
1429
 
package/src/sync/react.ts CHANGED
@@ -39,6 +39,8 @@ export function useCloudSyncRuntime({
39
39
  dispatch,
40
40
  tickerRepository,
41
41
  baselineStore,
42
+ getSessionIdentity: () => apiClient.getVerifiedSessionIdentity(),
43
+ getAccountId: () => apiClient.getVerifiedSessionIdentity() ? apiClient.getCurrentUser()?.id ?? null : null,
42
44
  getContributors: () => pluginRegistry.getEnabledSyncContributors(),
43
45
  getTransport: () => pluginRegistry.getActiveSyncTransport(),
44
46
  isLocalMarketData: (providerId) => {
@@ -69,7 +71,8 @@ export function useCloudSyncRuntime({
69
71
  // Email verification makes the Cloud transport available. Force the first
70
72
  // sync so a workspace completed before verification is uploaded promptly.
71
73
  void cloudSyncController.requestSync({ reason: "session-verified", force: true });
72
- }), [initialized]);
74
+ }, (accountId) => cloudSyncController.resetAccount(accountId),
75
+ () => cloudSyncController.retireSession()), [initialized]);
73
76
 
74
77
  useEffect(() => {
75
78
  if (!initialized) return;
package/src/sync/types.ts CHANGED
@@ -121,6 +121,9 @@ export interface SyncSettings {
121
121
 
122
122
  /** Durable record of the payloads this device last pushed to the cloud. */
123
123
  export interface SyncBaselineStore {
124
+ /** Account that owns this merge history; absent on stores written before account tracking. */
125
+ loadAccountId?(): string | null;
126
+ saveAccountId?(accountId: string): void;
124
127
  load(): Record<string, unknown> | null;
125
128
  save(payloads: Record<string, unknown>): void;
126
129
  /** Contributor memos by contributor id; see `SyncContributorMemo`. */
@@ -0,0 +1,38 @@
1
+ import type { StallEvent } from "../utils/main-thread-stall";
2
+
3
+ /**
4
+ * Only the freezes a user sat through. The system slows the timers of a
5
+ * window behind others or out of view, sometimes to one a second, and the
6
+ * stall monitor cannot tell that from a freeze: a timer back a second late
7
+ * reads the same either way. A window that has been in front, with focus,
8
+ * since before the stall began ran at full speed, so its late timers are
9
+ * freezes. Anything else is dropped, including a real freeze the user
10
+ * clicked into the window during: missing one beats reporting a false one.
11
+ */
12
+ export function forwardAttendedStalls(
13
+ forward: (event: StallEvent) => void,
14
+ target: Window = window,
15
+ now: () => number = () => performance.now(),
16
+ ): { sink: (event: StallEvent) => void; dispose: () => void } {
17
+ const doc = target.document;
18
+ const attended = () => doc.visibilityState === "visible" && doc.hasFocus();
19
+ // Since when the window has been attended; never while it is not.
20
+ let attendedSince = attended() ? now() : Number.POSITIVE_INFINITY;
21
+ const update = () => {
22
+ if (!attended()) attendedSince = Number.POSITIVE_INFINITY;
23
+ else if (attendedSince === Number.POSITIVE_INFINITY) attendedSince = now();
24
+ };
25
+ target.addEventListener("focus", update);
26
+ target.addEventListener("blur", update);
27
+ doc.addEventListener("visibilitychange", update);
28
+ return {
29
+ sink: (event) => {
30
+ if (attended() && attendedSince <= now() - event.blockedMs) forward(event);
31
+ },
32
+ dispose: () => {
33
+ target.removeEventListener("focus", update);
34
+ target.removeEventListener("blur", update);
35
+ doc.removeEventListener("visibilitychange", update);
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,75 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import type { StallReportState, StallReporterHost } from "./stall-reports";
4
+
5
+ /** Next to config.json: one line per stall, newest last. */
6
+ const STALL_LOG_FILE = "stall-log.txt";
7
+ /** Next to config.json: when each kind of stall was last reported, and the counts since. */
8
+ const STATE_FILE = "stall-reports.json";
9
+ /** Past this the log keeps its newer half. */
10
+ const LOG_MAX_BYTES = 256 * 1024;
11
+
12
+ function readCounts(value: unknown): StallReportState["pending"] {
13
+ const pending: StallReportState["pending"] = {};
14
+ if (!value || typeof value !== "object") return pending;
15
+ for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
16
+ const { count, longestMs } = (entry ?? {}) as Record<string, unknown>;
17
+ if (typeof count === "number" && Number.isFinite(count) && typeof longestMs === "number" && Number.isFinite(longestMs)) {
18
+ pending[key] = { count, longestMs };
19
+ }
20
+ }
21
+ return pending;
22
+ }
23
+
24
+ /** The saved state, keeping only entries of the right shape. */
25
+ function parseStallReportState(text: string): StallReportState {
26
+ const parsed: unknown = JSON.parse(text);
27
+ const record = parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
28
+ const sent: Record<string, number> = {};
29
+ if (record.sent && typeof record.sent === "object") {
30
+ for (const [key, at] of Object.entries(record.sent as Record<string, unknown>)) {
31
+ if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
32
+ }
33
+ }
34
+ return { sent, pending: readCounts(record.pending) };
35
+ }
36
+
37
+ function trimLog(path: string): void {
38
+ if (statSync(path).size <= LOG_MAX_BYTES) return;
39
+ const text = readFileSync(path, "utf-8");
40
+ const keep = text.slice(text.length - LOG_MAX_BYTES / 2);
41
+ writeFileSync(path, keep.slice(keep.indexOf("\n") + 1), "utf-8");
42
+ }
43
+
44
+ /** The stall reporter for a Bun process (the terminal app, the desktop app's main process). */
45
+ export function createNodeStallReporterHost(options: {
46
+ /** The profile's data folder, or null before the config is loaded. */
47
+ getDataDir(): string | null;
48
+ }): StallReporterHost {
49
+ const pathFor = (file: string) => {
50
+ const dir = options.getDataDir();
51
+ return dir ? join(dir, file) : null;
52
+ };
53
+ return {
54
+ platform: process.platform,
55
+ arch: process.arch,
56
+ appendLog: (line) => {
57
+ const path = pathFor(STALL_LOG_FILE);
58
+ if (!path) return;
59
+ mkdirSync(join(path, ".."), { recursive: true });
60
+ appendFileSync(path, `${line}\n`, "utf-8");
61
+ trimLog(path);
62
+ },
63
+ load: () => {
64
+ const path = pathFor(STATE_FILE);
65
+ if (!path || !existsSync(path)) return { sent: {}, pending: {} };
66
+ return parseStallReportState(readFileSync(path, "utf-8"));
67
+ },
68
+ save: (state) => {
69
+ const path = pathFor(STATE_FILE);
70
+ if (!path) return;
71
+ mkdirSync(join(path, ".."), { recursive: true });
72
+ writeFileSync(path, JSON.stringify(state), "utf-8");
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,214 @@
1
+ import { logFailure } from "../utils/log-failure";
2
+ import type { StallEvent } from "../utils/main-thread-stall";
3
+ import { VERSION } from "../version";
4
+ import { reportCrash } from "./crash-reports";
5
+
6
+ /**
7
+ * Freezes, made visible outside the app's own Debug Log.
8
+ *
9
+ * The stall monitor (utils/main-thread-stall.ts) notices when a process could
10
+ * not answer the user for a while and names the last measured section, and
11
+ * hands the stall to its sink. The terminal app and the desktop app's Bun
12
+ * process record their own stalls here; the desktop window sends the stalls
13
+ * it had in front of the user (stall-reports-dom.ts) to the Bun process,
14
+ * which records them too. Where a host is installed, each
15
+ * stall:
16
+ *
17
+ * - goes to a plain-text stall log in the profile folder, one line each, so a
18
+ * user or support can read what froze and when;
19
+ * - from a second up, counts toward a report sent through the crash reporter
20
+ * (and so behind the same off switch), the way update failures are: the
21
+ * duration bucket, the section's fixed name, the process, the longest
22
+ * stall and how many there were. No free text: a section name keeps only
23
+ * its fixed leading part, so a ticker in a queue key never leaves.
24
+ *
25
+ * One report per process, bucket, section and app version a day; stalls of
26
+ * the same kind after that are counted and carried in the next report.
27
+ */
28
+
29
+ /** Which process stalled. */
30
+ export type StallProcess = "window" | "app" | "terminal";
31
+
32
+ /** What error tracking gets: fixed values only. */
33
+ export interface StallReport {
34
+ process: StallProcess;
35
+ bucket: StallBucket;
36
+ section: string;
37
+ appVersion: string;
38
+ platform: "macos" | "windows" | "linux" | "other";
39
+ arch: "arm64" | "x64" | "other";
40
+ /** Stalls of this kind since the last report, this one included. */
41
+ count: number;
42
+ /** The longest of them, rounded to 100 ms. */
43
+ longestMs: number;
44
+ }
45
+
46
+ type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
47
+
48
+ /** What the host keeps on disk between launches. */
49
+ export interface StallReportState {
50
+ /** When each kind was last reported, in ms. */
51
+ sent: Record<string, number>;
52
+ /** Stalls counted since each kind was last reported. */
53
+ pending: Record<string, { count: number; longestMs: number }>;
54
+ }
55
+
56
+ export interface StallReporterHost {
57
+ /** The running version; defaults to this build's. */
58
+ appVersion?: string;
59
+ platform: string;
60
+ arch: string;
61
+ /** Appends one line to the stall log. */
62
+ appendLog(line: string): void;
63
+ load(): StallReportState;
64
+ save(state: StallReportState): void;
65
+ now?(): number;
66
+ /** Defaults to the crash reporter. */
67
+ send?(report: StallReport): void;
68
+ }
69
+
70
+ const DAY_MS = 24 * 60 * 60 * 1000;
71
+ /** Shorter freezes go to the log only. */
72
+ const STALL_REPORT_MIN_MS = 1_000;
73
+ const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
74
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
75
+ /** Kinds with counts waiting for their next report; the oldest go first past this. */
76
+ const MAX_KINDS_KEPT = 200;
77
+
78
+ let host: StallReporterHost | null = null;
79
+
80
+ /** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
81
+ function stallSectionName(name: string | undefined): string {
82
+ if (!name) return "unmeasured";
83
+ const match = SECTION_PATTERN.exec(name);
84
+ const section = match?.[0].replace(/\.$/, "");
85
+ return section && section.length <= 80 ? section : "unmeasured";
86
+ }
87
+
88
+ function stallBucket(blockedMs: number): StallBucket {
89
+ if (blockedMs < 2_000) return "1-2s";
90
+ if (blockedMs < 5_000) return "2-5s";
91
+ if (blockedMs < 15_000) return "5-15s";
92
+ return "15s+";
93
+ }
94
+
95
+ /** A stall another process sent over, kept only when it has the right shape. */
96
+ export function parseStallEvent(value: unknown): StallEvent | null {
97
+ if (!value || typeof value !== "object") return null;
98
+ const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
99
+ if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
100
+ return {
101
+ blockedMs,
102
+ ...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
103
+ ...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
104
+ };
105
+ }
106
+
107
+ function formatSeconds(ms: number): string {
108
+ return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
109
+ }
110
+
111
+ /** One line of the stall log: when, which process, how long, and what ran last. */
112
+ function formatStallLogLine(process: StallProcess, event: StallEvent, at: number): string {
113
+ const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
114
+ const last = event.lastMeasured
115
+ ? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
116
+ : "nothing measured before it";
117
+ return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}`;
118
+ }
119
+
120
+ function reportPlatform(platform: string): StallReport["platform"] {
121
+ if (platform === "darwin") return "macos";
122
+ if (platform === "win32") return "windows";
123
+ if (platform === "linux") return "linux";
124
+ return "other";
125
+ }
126
+
127
+ function reportArch(arch: string): StallReport["arch"] {
128
+ return arch === "arm64" || arch === "x64" ? arch : "other";
129
+ }
130
+
131
+ function sendThroughCrashReporter(report: StallReport): void {
132
+ const details = [
133
+ `process=${report.process}`,
134
+ `count=${report.count}`,
135
+ `longest=${report.longestMs}ms`,
136
+ `platform=${report.platform}-${report.arch}`,
137
+ ].join(" ");
138
+ // A plain object, not an Error: it has no stack, so no file path goes along.
139
+ // The message is the bucket and section, so error tracking keeps one issue per kind.
140
+ reportCrash(
141
+ { name: "MainThreadStall", message: `${report.bucket} after ${report.section}` },
142
+ { kind: "plugin", plugin: "stall-monitor", pane: details },
143
+ );
144
+ }
145
+
146
+ function loadState(current: StallReporterHost): StallReportState {
147
+ try {
148
+ const state = current.load();
149
+ return { sent: { ...state.sent }, pending: { ...state.pending } };
150
+ } catch (error) {
151
+ logFailure("telemetry-stalls", "Could not read stall report state")(error);
152
+ return { sent: {}, pending: {} };
153
+ }
154
+ }
155
+
156
+ function prune(state: StallReportState, now: number): void {
157
+ for (const [key, at] of Object.entries(state.sent)) {
158
+ if (!(now - at < DAY_MS) || at > now) delete state.sent[key];
159
+ }
160
+ const kinds = Object.keys(state.pending);
161
+ for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
162
+ }
163
+
164
+ /** Records one stall of `process`. Does nothing where no host is installed; never throws. */
165
+ export function recordStall(process: StallProcess, event: StallEvent): void {
166
+ const current = host;
167
+ if (!current || !(event.blockedMs > 0)) return;
168
+ try {
169
+ const now = current.now?.() ?? Date.now();
170
+ current.appendLog(formatStallLogLine(process, event, now));
171
+ if (event.blockedMs < STALL_REPORT_MIN_MS) return;
172
+ const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
173
+ const section = stallSectionName(event.lastMeasured);
174
+ const bucket = stallBucket(event.blockedMs);
175
+ const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
176
+ const key = `${process}|${bucket}|${section}|${version}`;
177
+ const state = loadState(current);
178
+ prune(state, now);
179
+ const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
180
+ pending.count += 1;
181
+ pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
182
+ if (state.sent[key] !== undefined) {
183
+ state.pending[key] = pending;
184
+ } else {
185
+ delete state.pending[key];
186
+ state.sent[key] = now;
187
+ (current.send ?? sendThroughCrashReporter)({
188
+ process,
189
+ bucket,
190
+ section,
191
+ appVersion: version,
192
+ platform: reportPlatform(current.platform),
193
+ arch: reportArch(current.arch),
194
+ count: pending.count,
195
+ longestMs: pending.longestMs,
196
+ });
197
+ }
198
+ current.save(state);
199
+ } catch (error) {
200
+ logFailure("telemetry-stalls", "Could not record a stall")(error);
201
+ }
202
+ }
203
+
204
+ export function installStallReporter(next: StallReporterHost): () => void {
205
+ host = next;
206
+ return () => {
207
+ if (host === next) host = null;
208
+ };
209
+ }
210
+
211
+ /** Test seam. */
212
+ export function resetStallReporterForTests(): void {
213
+ host = null;
214
+ }
@@ -97,7 +97,6 @@ let officialIds: ReadonlySet<string> | null = null;
97
97
  let officialLookup: Promise<void> | null = null;
98
98
  let automationDepth = 0;
99
99
  const builtinPlugins = new WeakSet<object>();
100
- const openListeners = new Set<() => void>();
101
100
 
102
101
  /** Reads the config switch and the environment. Absent means on. */
103
102
  export function usageCountsEnabled(
@@ -197,27 +196,10 @@ export function automationActive(): boolean {
197
196
  return automationDepth > 0;
198
197
  }
199
198
 
200
- /**
201
- * Calls `listener` each time the user opens a function, with nothing about
202
- * which one. The web terminal's first-screen measure uses it; automation never
203
- * reaches it.
204
- */
205
- export function subscribeFunctionOpens(listener: () => void): () => void {
206
- openListeners.add(listener);
207
- return () => {
208
- openListeners.delete(listener);
209
- };
210
- }
211
-
212
199
  /** Counts one open of a function by the user. Never throws. */
213
200
  export function recordFunctionOpen(fn: UsageFunction): void {
214
201
  try {
215
202
  if (automationDepth > 0) return;
216
- for (const listener of openListeners) {
217
- try { listener(); } catch (error) {
218
- logFailure("telemetry-usage-counts", "Could not notify function-open listener")(error);
219
- }
220
- }
221
203
  if (host && !host.isEnabled()) return;
222
204
  count(fn, "opened");
223
205
  scheduleFlush();
@@ -0,0 +1,14 @@
1
+ import { beforeEach } from "bun:test";
2
+ import { resetCrashReporterForTests } from "../telemetry/crash-reports";
3
+
4
+ /**
5
+ * `bun test` runs every file in one process, so module state outlives the file
6
+ * that set it. A crash report queued while no reporter is installed, such as a
7
+ * plugin that fails to register in an app runtime test, would wait in the
8
+ * queue and go out with the next reporter another file installs, and its key
9
+ * would keep a later identical report from being queued at all. Every test
10
+ * starts with no reporter, an empty queue and nothing seen.
11
+ */
12
+ beforeEach(() => {
13
+ resetCrashReporterForTests();
14
+ });
@@ -291,6 +291,14 @@ export function priceColor(value: number, palette: ThemeColors = colors): string
291
291
  return palette.neutral;
292
292
  }
293
293
 
294
+ /**
295
+ * `priceColor` for a figure as it is printed, to `digits` decimals: a move that
296
+ * rounds to zero (0.00%, never -0.00%) is neutral, not red or green.
297
+ */
298
+ export function shownPriceColor(value: number, digits: number, palette: ThemeColors = colors): string {
299
+ return priceColor(Number(value.toFixed(digits)), palette);
300
+ }
301
+
294
302
  const TONE_TINT = 0.18;
295
303
 
296
304
  /**
@@ -745,6 +745,16 @@ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
745
745
  "high-contrast": ["high-contrast", "colorblind-safe"],
746
746
  };
747
747
 
748
+ /**
749
+ * Whether a theme's gains and losses are told apart by something other than
750
+ * the red and green everyone reads: a color-blind safe theme's blue and
751
+ * vermillion, which a chart keys (`▲▼` beside a candle series) so a reader
752
+ * knows which is which.
753
+ */
754
+ export function themeKeysDirection(id: string): boolean {
755
+ return (THEME_TRAITS[id] ?? []).includes("colorblind-safe");
756
+ }
757
+
748
758
  export interface ThemeSummary {
749
759
  id: string;
750
760
  name: string;