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
@@ -18,6 +18,8 @@ export interface SelectControl {
18
18
  export interface SelectButtonOption<T extends string = string> {
19
19
  value: T;
20
20
  label: string;
21
+ /** Heading for a contiguous group of choices; never selectable itself. */
22
+ group?: string;
21
23
  /** Trigger wording when the full label is too wide for a pane header. */
22
24
  short?: string;
23
25
  description?: string;
@@ -130,6 +132,7 @@ export function SelectButton<T extends string = string>({
130
132
  choices={options.map((option) => ({
131
133
  id: option.value,
132
134
  label: option.label,
135
+ group: option.group,
133
136
  description: option.description,
134
137
  disabled: option.disabled,
135
138
  color: option.color,
@@ -158,7 +161,7 @@ export function SelectButton<T extends string = string>({
158
161
  selectRef={(element) => { selectFieldRef.current = element; }}
159
162
  onFocus={onFocus}
160
163
  value={value}
161
- options={options.map((option) => ({ value: option.value, label: option.label, disabled: option.disabled }))}
164
+ options={options.map((option) => ({ value: option.value, label: option.label, group: option.group, disabled: option.disabled }))}
162
165
  disabled={disabled}
163
166
  onChange={selectValue}
164
167
  />
@@ -180,6 +183,7 @@ export function SelectButton<T extends string = string>({
180
183
  options={options.map((option) => ({
181
184
  value: option.value,
182
185
  label: option.label,
186
+ group: option.group,
183
187
  disabled: option.disabled,
184
188
  }))}
185
189
  disabled={disabled}
@@ -9,6 +9,7 @@ export interface SelectFieldHandle {
9
9
 
10
10
  interface SelectFieldOption {
11
11
  label: string;
12
+ group?: string;
12
13
  value: string;
13
14
  description?: string;
14
15
  disabled?: boolean;
@@ -106,18 +106,21 @@ export function getTableWidth(
106
106
  * The columns that fit in `width` cells: whole columns in `dropOrder` (least
107
107
  * needed first) give way until the rest fit, rather than the table's right
108
108
  * edge cutting a value, so a narrow pane drops a name that repeats its ticker
109
- * before it cuts a date. Columns not listed stay at every width. A table that
110
- * scrolls vertically passes its width less the scrollbar's cell.
109
+ * before it cuts a date. An entry that lists several ids drops them together,
110
+ * for columns that mean little apart (a bid's venue and an ask's). Columns not
111
+ * listed stay at every width. A table that scrolls vertically passes its width
112
+ * less the scrollbar's cell.
111
113
  */
112
114
  export function dropColumnsToFit<C extends TableWidthColumn & { id: string }>(
113
115
  columns: readonly C[],
114
116
  width: number,
115
- dropOrder: readonly string[],
117
+ dropOrder: readonly (string | readonly string[])[],
116
118
  ): C[] {
117
119
  let shown = [...columns];
118
- for (const id of dropOrder) {
120
+ for (const step of dropOrder) {
119
121
  if (getTableWidth(shown) <= width) break;
120
- shown = shown.filter((column) => column.id !== id);
122
+ const ids = typeof step === "string" ? [step] : step;
123
+ shown = shown.filter((column) => !ids.includes(column.id));
121
124
  }
122
125
  return shown;
123
126
  }
@@ -1,5 +1,6 @@
1
1
  import { MarketDataCoordinator, getSharedMarketDataCoordinator, setSharedMarketDataCoordinator } from "../market-data/coordinator";
2
2
  import { NewsService, type NewsServiceOptions } from "../news/aggregator";
3
+ import { cloudNewsSession } from "../news/cloud-session";
3
4
  import { getSharedNewsService, setSharedNewsService } from "../news/hooks";
4
5
  import { PluginRegistry } from "../plugins/registry";
5
6
  import { releaseSharedRegistry } from "../plugins/registry/shared";
@@ -61,6 +62,7 @@ export function createAppRuntime({
61
62
  const newsService = new NewsService({
62
63
  connectionHealth: pluginRegistry.connectionHealth,
63
64
  pollIntervalMs: () => Math.max(1, config.refreshIntervalMinutes) * 60_000,
65
+ session: cloudNewsSession,
64
66
  ...newsOptions,
65
67
  });
66
68
  const modules = { pluginRegistry, marketData, newsService };
@@ -27,8 +27,8 @@ export function createAppServices({ config, plugins, externalPlugins }: AppServi
27
27
  provider: {
28
28
  fetchNews: async (query) => (await newsService.load(query)).articles,
29
29
  fetchNewsPage: async (query) => {
30
- const { articles, error, nextCursor } = await newsService.load(query);
31
- return { articles, error, nextCursor };
30
+ const { articles, error, nextCursor, access } = await newsService.load(query);
31
+ return { articles, error, nextCursor, access };
32
32
  },
33
33
  getCachedNews: (query) => newsService.getQueryState(query).articles,
34
34
  },
@@ -594,6 +594,7 @@ export function withFocusedPane(
594
594
  config: syncedConfig,
595
595
  paneState: nextPaneState,
596
596
  brokerAccounts: reconcileBrokerAccounts(syncedConfig, state.brokerAccounts),
597
+ brokerSyncErrors: Object.fromEntries(Object.entries(state.brokerSyncErrors).filter(([id]) => syncedConfig.brokerInstances.some((instance) => instance.id === id))),
597
598
  focusedPaneId,
598
599
  previousFocusedPaneId: state.focusedPaneId && state.focusedPaneId !== focusedPaneId
599
600
  ? state.focusedPaneId
@@ -605,6 +606,7 @@ export function hydrateDesktopSnapshot(state: AppState, snapshot: DesktopSharedS
605
606
  return withFocusedPane({
606
607
  ...state,
607
608
  paneState: snapshot.paneState,
609
+ brokerSyncErrors: snapshot.brokerSyncErrors ?? {},
608
610
  focusedPaneId: snapshot.focusedPaneId,
609
611
  statusBarVisible: snapshot.statusBarVisible,
610
612
  }, snapshot.config);
@@ -203,6 +203,14 @@ export function appReducer(state: AppState, action: AppAction): AppState {
203
203
  },
204
204
  };
205
205
 
206
+ case "SET_BROKER_SYNC_ERROR": {
207
+ if (!state.config.brokerInstances.some((instance) => instance.id === action.instanceId)) return state;
208
+ const brokerSyncErrors = { ...state.brokerSyncErrors };
209
+ if (action.error) brokerSyncErrors[action.instanceId] = action.error;
210
+ else delete brokerSyncErrors[action.instanceId];
211
+ return { ...state, brokerSyncErrors };
212
+ }
213
+
206
214
  case "SET_INITIALIZED":
207
215
  return { ...state, initialized: true };
208
216
 
@@ -371,6 +379,7 @@ export function createInitialState(config: AppConfig, sessionSnapshot: AppSessio
371
379
  financials: new Map(),
372
380
  exchangeRates: new Map([["USD", 1]]),
373
381
  brokerAccounts: {},
382
+ brokerSyncErrors: {},
374
383
  focusedPaneId,
375
384
  previousFocusedPaneId: null,
376
385
  paneState,
@@ -68,6 +68,8 @@ export interface AppState {
68
68
  */
69
69
  exchangeRates: Map<string, number>;
70
70
  brokerAccounts: Record<string, BrokerAccount[]>;
71
+ /** A failed position import stays visible until this profile syncs successfully. */
72
+ brokerSyncErrors: Record<string, string>;
71
73
  /**
72
74
  * @deprecated Never set. The app has no left or right panel state; read
73
75
  * `focusedPaneId` for the focused pane.
@@ -131,6 +133,7 @@ export type AppAction =
131
133
  | { type: "RECORD_COMMAND"; id: string; label: string; arg?: string }
132
134
  | { type: "SET_REFRESHING"; symbol: string; refreshing: boolean }
133
135
  | { type: "SET_BROKER_ACCOUNTS"; instanceId: string; accounts: BrokerAccount[] }
136
+ | { type: "SET_BROKER_SYNC_ERROR"; instanceId: string; error: string | null }
134
137
  | { type: "SET_INITIALIZED" }
135
138
  | { type: "TOGGLE_STATUS_BAR" }
136
139
  | { type: "SET_THEME"; theme: string }
@@ -20,7 +20,7 @@ import {
20
20
  import { recordOrNull } from "../../utils/guards";
21
21
 
22
22
  const CHART_RANGES = new Set<TimeRange>(TIME_RANGES);
23
- const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
23
+ const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "hollow", "heikin-ashi", "ohlc", "hlc"]);
24
24
  const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
25
25
  "volume",
26
26
  "sma20",
@@ -4,6 +4,7 @@ import { hydrateTickerMetadata } from "../tickers/metadata";
4
4
  import type { TickerMetadata, TickerRecord } from "../types/ticker";
5
5
  import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./json-storage";
6
6
  import { isRecord } from "../utils/guards";
7
+ import { runTickerWrite } from "./ticker-writes";
7
8
 
8
9
  type StoredTickers = Record<string, TickerMetadata>;
9
10
 
@@ -39,20 +40,28 @@ export class JsonTickerRepository implements AppTickerRepositoryPort {
39
40
  return metadata ? hydrate(metadata) : null;
40
41
  }
41
42
 
42
- async saveTicker(ticker: TickerRecord): Promise<void> {
43
+ private writeTicker(ticker: TickerRecord): void {
43
44
  const symbol = normalizeSymbol(ticker.metadata.ticker);
44
45
  this.data.set({ ...this.data.get(), [symbol]: ticker.metadata });
45
46
  }
46
47
 
47
- async createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
48
+ saveTicker(ticker: TickerRecord): Promise<void> {
49
+ return runTickerWrite(this, ticker.metadata.ticker, () => this.writeTicker(ticker));
50
+ }
51
+
52
+ createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
48
53
  const ticker = { metadata };
49
- await this.saveTicker(ticker);
50
- return ticker;
54
+ return runTickerWrite(this, metadata.ticker, () => {
55
+ this.writeTicker(ticker);
56
+ return ticker;
57
+ });
51
58
  }
52
59
 
53
- async deleteTicker(symbol: string): Promise<void> {
54
- const tickers = { ...this.data.get() };
55
- delete tickers[normalizeSymbol(symbol)];
56
- this.data.set(tickers);
60
+ deleteTicker(symbol: string): Promise<void> {
61
+ return runTickerWrite(this, symbol, () => {
62
+ const tickers = { ...this.data.get() };
63
+ delete tickers[normalizeSymbol(symbol)];
64
+ this.data.set(tickers);
65
+ });
57
66
  }
58
67
  }
@@ -1,27 +1,33 @@
1
1
  /**
2
- * Answers a CLI command gave from a copy saved earlier because refreshing it
3
- * failed: offline, a refused proxy, a service error. A command answers once,
4
- * so the line it ends with says "couldn't refresh; saved <time>" instead of
5
- * reading as live or delayed, but only when what it shows came from such a
6
- * copy: a saved copy of something else it loaded on the way (a fallback for a
7
- * pane it does not print) says nothing about the data on screen.
2
+ * Answers given from a copy saved earlier because refreshing it failed:
3
+ * offline, a hung or refused proxy, a service error.
4
+ *
5
+ * A CLI command answers once, so the line it ends with says "couldn't
6
+ * refresh; saved copy from <time>" instead of reading as live or delayed, but
7
+ * only when what it shows came from such a copy: a saved copy of something
8
+ * else it loaded on the way (a fallback for a pane it does not print) says
9
+ * nothing about the data on screen. The app says the same of a quote, in the
10
+ * footer of a pane showing one (`savedCopyAt`), until a refresh replaces it.
8
11
  *
9
12
  * The copy served is marked, by a symbol property that object spreads carry
10
13
  * along and JSON never writes, and the command's output is searched for the
11
- * marks. Nothing is marked outside `collectSavedCopies`: the app and its panes
12
- * keep running for hours and say how current their data is in their own
13
- * footers.
14
+ * marks. Outside a command only quotes are marked (`quoteServedFromSavedCopy`):
15
+ * the app keeps running for hours, and its other panes say how current their
16
+ * data is in their own footers. A quote built from a newer answer must not
17
+ * inherit a mark through a spread: `withSavedCopyMarkOf` gives it the mark of
18
+ * what was served instead.
14
19
  */
15
20
  const SAVED_AT = Symbol("gloomberb.savedCopyAt");
16
21
 
17
22
  let collecting = 0;
18
- let marked = 0;
23
+ /** Whether anything was ever marked, so output is not searched for marks that cannot be there. */
24
+ let anyMarked = false;
19
25
 
20
26
  type Marked = { [SAVED_AT]?: number };
21
27
 
22
28
  function mark<T>(value: T, savedAt: number): T {
23
29
  if (!value || typeof value !== "object" || Array.isArray(value)) return value;
24
- marked += 1;
30
+ anyMarked = true;
25
31
  return { ...value, [SAVED_AT]: savedAt };
26
32
  }
27
33
 
@@ -36,13 +42,48 @@ export function servedFromSavedCopy<T>(value: T, savedAt: number): T {
36
42
  return (Array.isArray(value) ? value.map((item) => mark(item, savedAt)) : mark(value, savedAt)) as T;
37
43
  }
38
44
 
45
+ /** A quote answered from a copy saved at `savedAt` (ms) after its refresh failed: marked, in a command and in the app. */
46
+ export function quoteServedFromSavedCopy<T extends object>(quote: T, savedAt: number): T {
47
+ return Number.isFinite(savedAt) ? mark(quote, savedAt) : quote;
48
+ }
49
+
39
50
  /**
40
51
  * The saved-copy mark of `value`, to spread into what is drawn from it (a
41
52
  * report row built from a quote's fields), or nothing when it has none.
42
53
  */
43
54
  export function savedCopyMark(value: unknown): object {
55
+ const savedAt = savedCopyAt(value);
56
+ return savedAt != null ? { [SAVED_AT]: savedAt } : {};
57
+ }
58
+
59
+ /** When `value` itself was saved, if it is a saved copy served after a failed refresh; null otherwise. */
60
+ export function savedCopyAt(value: unknown): number | null {
44
61
  const savedAt = value && typeof value === "object" ? (value as Marked)[SAVED_AT] : undefined;
45
- return typeof savedAt === "number" ? { [SAVED_AT]: savedAt } : {};
62
+ return typeof savedAt === "number" ? savedAt : null;
63
+ }
64
+
65
+ /** The oldest saved copy among `values` (a pane's quotes), by when it was saved; null when none is one. */
66
+ export function oldestSavedCopyAt(values: Iterable<unknown>): number | null {
67
+ let oldest: number | null = null;
68
+ for (const value of values) {
69
+ const savedAt = savedCopyAt(value);
70
+ if (savedAt != null) oldest = Math.min(oldest ?? savedAt, savedAt);
71
+ }
72
+ return oldest;
73
+ }
74
+
75
+ /**
76
+ * `value` carrying the saved-copy mark of `served` and no other: what is
77
+ * built from an answer (resolved, merged onto an older quote) is a saved copy
78
+ * exactly when that answer was.
79
+ */
80
+ export function withSavedCopyMarkOf<T extends object>(value: T, served: unknown): T {
81
+ const savedAt = savedCopyAt(served);
82
+ const own = savedCopyAt(value);
83
+ if (own === savedAt) return value;
84
+ if (savedAt != null) return mark(value, savedAt);
85
+ const { [SAVED_AT]: _dropped, ...rest } = value as T & Marked;
86
+ return rest as T;
46
87
  }
47
88
 
48
89
  /** How far into a command's output the marks are looked for, so a large report is not walked whole. */
@@ -55,7 +96,7 @@ const MAX_NODES = 50_000;
55
96
  * came from one. The oldest, since that is how old what is shown can be.
56
97
  */
57
98
  export function savedCopyShownAt(shown: unknown): number | null {
58
- if (collecting === 0 || marked === 0) return null;
99
+ if (collecting === 0 || !anyMarked) return null;
59
100
  let oldest: number | null = null;
60
101
  let budget = MAX_NODES;
61
102
  const seen = new Set<object>();
@@ -71,14 +112,12 @@ export function savedCopyShownAt(shown: unknown): number | null {
71
112
  return oldest;
72
113
  }
73
114
 
74
- /** Runs one command, marking the saved copies it answers from. */
115
+ /** Runs one command, whose output `savedCopyShownAt` then searches for saved copies. */
75
116
  export async function collectSavedCopies<T>(run: () => Promise<T>): Promise<T> {
76
- if (collecting === 0) marked = 0;
77
117
  collecting += 1;
78
118
  try {
79
119
  return await run();
80
120
  } finally {
81
121
  collecting -= 1;
82
- if (collecting === 0) marked = 0;
83
122
  }
84
123
  }
@@ -2,6 +2,7 @@ import { logFailure } from "../utils/log-failure";
2
2
  import type { TickerRecord, TickerMetadata } from "../types/ticker";
3
3
  import type { TickerStore } from "./ticker-store";
4
4
  import { hydrateTickerMetadata } from "../tickers/metadata";
5
+ import { runTickerWrite } from "./ticker-writes";
5
6
 
6
7
  function parseTickerMetadata(json: string): TickerMetadata {
7
8
  return hydrateTickerMetadata(JSON.parse(json) as Record<string, unknown>);
@@ -32,22 +33,28 @@ export class TickerRepository {
32
33
  }
33
34
  }
34
35
 
35
- async saveTicker(ticker: TickerRecord): Promise<void> {
36
- this.tickers.save(ticker.metadata.ticker, ticker.metadata);
36
+ saveTicker(ticker: TickerRecord): Promise<void> {
37
+ return runTickerWrite(this, ticker.metadata.ticker, () => {
38
+ this.tickers.save(ticker.metadata.ticker, ticker.metadata);
39
+ });
37
40
  }
38
41
 
39
42
  /** Saves several tickers in one write. */
40
- async saveTickers(tickers: readonly TickerRecord[]): Promise<void> {
41
- this.tickers.saveMany(tickers.map((ticker) => ({ symbol: ticker.metadata.ticker, metadata: ticker.metadata })));
43
+ saveTickers(tickers: readonly TickerRecord[]): Promise<void> {
44
+ return runTickerWrite(this, tickers.map((ticker) => ticker.metadata.ticker), () => {
45
+ this.tickers.saveMany(tickers.map((ticker) => ({ symbol: ticker.metadata.ticker, metadata: ticker.metadata })));
46
+ });
42
47
  }
43
48
 
44
- async createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
49
+ createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
45
50
  const ticker: TickerRecord = { metadata };
46
- await this.saveTicker(ticker);
47
- return ticker;
51
+ return runTickerWrite(this, metadata.ticker, () => {
52
+ this.tickers.save(metadata.ticker, metadata);
53
+ return ticker;
54
+ });
48
55
  }
49
56
 
50
- async deleteTicker(symbol: string): Promise<void> {
51
- this.tickers.delete(symbol);
57
+ deleteTicker(symbol: string): Promise<void> {
58
+ return runTickerWrite(this, symbol, () => this.tickers.delete(symbol));
52
59
  }
53
60
  }
@@ -0,0 +1,81 @@
1
+ interface TickerWrites {
2
+ revisions: Map<string, number>;
3
+ tails: Map<string, Promise<unknown>>;
4
+ pending: Set<Promise<unknown>>;
5
+ }
6
+
7
+ const repositories = new WeakMap<object, TickerWrites>();
8
+
9
+ function writesFor(repository: object): TickerWrites {
10
+ let writes = repositories.get(repository);
11
+ if (!writes) {
12
+ writes = { revisions: new Map(), tails: new Map(), pending: new Set() };
13
+ repositories.set(repository, writes);
14
+ }
15
+ return writes;
16
+ }
17
+
18
+ function tickerKey(symbol: string): string {
19
+ return symbol.trim().toUpperCase();
20
+ }
21
+
22
+ /** Reserve writes when requested, including ones waiting for an earlier acknowledgement. */
23
+ export function runTickerWrite<Result>(
24
+ repository: object,
25
+ symbols: string | readonly string[],
26
+ write: () => Result | PromiseLike<Result>,
27
+ ): Promise<Result> {
28
+ const writes = writesFor(repository);
29
+ const keys = [...new Set((typeof symbols === "string" ? [symbols] : symbols).map(tickerKey))];
30
+ const previous = [...new Set(keys.flatMap((key) => {
31
+ const tail = writes.tails.get(key);
32
+ return tail ? [tail] : [];
33
+ }))];
34
+ let resolve!: (value: Result | PromiseLike<Result>) => void;
35
+ let reject!: (error: unknown) => void;
36
+ const result = new Promise<Result>((accept, fail) => {
37
+ resolve = accept;
38
+ reject = fail;
39
+ });
40
+ for (const key of keys) {
41
+ writes.revisions.set(key, (writes.revisions.get(key) ?? 0) + 1);
42
+ writes.tails.set(key, result);
43
+ }
44
+ writes.pending.add(result);
45
+ const finished = () => {
46
+ writes.pending.delete(result);
47
+ for (const key of keys) {
48
+ if (writes.tails.get(key) === result) writes.tails.delete(key);
49
+ }
50
+ };
51
+ void result.then(finished, finished);
52
+ const start = () => {
53
+ try {
54
+ resolve(write());
55
+ } catch (error) {
56
+ reject(error);
57
+ }
58
+ };
59
+ if (previous.length) void Promise.allSettled(previous).then(start);
60
+ else start();
61
+ return result;
62
+ }
63
+
64
+ /** Changes immediately when a save or deletion is requested, before its acknowledgement. */
65
+ export function tickerWriteRevision(repository: object, symbol: string): number {
66
+ return repositories.get(repository)?.revisions.get(tickerKey(symbol)) ?? 0;
67
+ }
68
+
69
+ /** Whether a write arrived after a caller last waited for acknowledgements. */
70
+ export function hasPendingTickerWrites(repository: object): boolean {
71
+ return (repositories.get(repository)?.pending.size ?? 0) > 0;
72
+ }
73
+
74
+ /** Let save-await callers publish their edits before cleanup reads the current rows. */
75
+ export async function settleTickerWrites(repository: object): Promise<void> {
76
+ const writes = writesFor(repository);
77
+ do {
78
+ // Each writer handles its own failure; cleanup still needs the last published row.
79
+ await Promise.allSettled([...writes.pending]);
80
+ } while (writes.pending.size);
81
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * A keyed in-memory cache that serves a loaded value for a while and shares a
3
+ * load that is still out. It is what a plugin reaches for instead of keeping
4
+ * its own `Map<key, { at, value }>` with a TTL constant and `Date.now() - at`
5
+ * checks, each copy with its own rule for a concurrent call, a forced reload
6
+ * and a failed load.
7
+ *
8
+ * It holds nothing across a restart, and `load` never answers with an expired
9
+ * value. A pane whose data must outlive a restart, or fall back to the saved
10
+ * copy when a refresh fails, wants `createPluginCache` (src/data/plugin-cache.ts).
11
+ *
12
+ * The rules, for every key:
13
+ * - A fresh value is served as it is; a load that is still out is shared, so
14
+ * two callers never start two requests for one key. A load is shared for
15
+ * no longer than `ttlMs` (a number), so one that hangs cannot hold the key.
16
+ * - `force` skips a fresh value and starts a load, except that it joins one
17
+ * that is already out when that load began after the value it would
18
+ * replace: panes that refresh together make one request, not one each.
19
+ * - The last good value stays until a load brings a newer one. A forced
20
+ * load that fails, or brings a value `keep` refuses, leaves it in place,
21
+ * and `peek(key, { stale: true })` reads it past its TTL while a reload is
22
+ * out, for a view that keeps drawing the last copy.
23
+ * - A load that rejects reaches every caller waiting on it. By default
24
+ * nothing is kept, so the next call asks again; `rejectionMs` keeps the
25
+ * rejection that long, for a load a poll would otherwise repeat each tick.
26
+ * - A load runs as soon as `load` is called, not on a later tick.
27
+ */
28
+ export interface TtlCacheOptions<V> {
29
+ /** How long a loaded value is served, from when it arrives; a function sets it per value. */
30
+ ttlMs: number | ((value: V) => number);
31
+ /**
32
+ * Whether a loaded value is kept. One that is not still answers the callers
33
+ * waiting on it, and the next call loads again. Default: every value.
34
+ */
35
+ keep?: (value: V) => boolean;
36
+ /** How long a rejected load is kept and served again. Default 0: it is dropped. */
37
+ rejectionMs?: number;
38
+ /** The oldest keys go first past this many. Default: unbounded. */
39
+ maxEntries?: number;
40
+ /** The clock, for tests. */
41
+ now?: () => number;
42
+ }
43
+
44
+ export interface TtlCache<V> {
45
+ /** The key's fresh value or the load already out for it; otherwise starts `load`. */
46
+ load(key: string, load: () => V | PromiseLike<V>, options?: { force?: boolean }): Promise<V>;
47
+ /**
48
+ * The key's fresh value, `undefined` when it has none. `stale` also reads
49
+ * the last good value past its TTL, for a view that keeps drawing it until
50
+ * the next load lands.
51
+ */
52
+ peek(key: string, options?: { stale?: boolean }): V | undefined;
53
+ /** Whether the key's last load was rejected and `rejectionMs` still keeps that rejection. */
54
+ failed(key: string): boolean;
55
+ /** Keeps a value that arrived by another road, for the TTL. */
56
+ set(key: string, value: V): void;
57
+ /**
58
+ * Forgets the key. With `only`, a promise `load` returned, forgets just the
59
+ * load or kept rejection it stands for, and only while it is still the
60
+ * key's: a caller that gives up on a load cannot drop a newer one.
61
+ */
62
+ delete(key: string, only?: Promise<V>): void;
63
+ clear(): void;
64
+ }
65
+
66
+ interface Slot<V> {
67
+ /** The last good value: fresh until `expiresAt`, readable as stale after. */
68
+ held?: { value: V; arrivedAt: number; expiresAt: number };
69
+ /** The load that is out. */
70
+ loading?: { promise: Promise<V>; startedAt: number };
71
+ /** A rejected load kept for `rejectionMs`. */
72
+ failed?: { promise: Promise<V>; expiresAt: number };
73
+ }
74
+
75
+ export function createTtlCache<V>(options: TtlCacheOptions<V>): TtlCache<V> {
76
+ const { keep = () => true, rejectionMs = 0, maxEntries = Infinity, now: clock = () => Date.now() } = options;
77
+ const ttlFor = (value: V) => typeof options.ttlMs === "function" ? options.ttlMs(value) : options.ttlMs;
78
+ const shareMs = typeof options.ttlMs === "number" ? options.ttlMs : Infinity;
79
+ const slots = new Map<string, Slot<V>>();
80
+
81
+ const store = (key: string, slot: Slot<V>) => {
82
+ slots.delete(key);
83
+ slots.set(key, slot);
84
+ while (slots.size > maxEntries) slots.delete(slots.keys().next().value!);
85
+ };
86
+ /** A slot with nothing in it is not kept. */
87
+ const tidy = (key: string, slot: Slot<V>) => {
88
+ if (!slot.held && !slot.loading && !slot.failed && slots.get(key) === slot) slots.delete(key);
89
+ };
90
+ const hold = (slot: Slot<V>, value: V) => {
91
+ const arrivedAt = clock();
92
+ slot.held = { value, arrivedAt, expiresAt: arrivedAt + ttlFor(value) };
93
+ slot.failed = undefined;
94
+ };
95
+
96
+ return {
97
+ load(key, load, { force = false } = {}) {
98
+ const at = clock();
99
+ const slot = slots.get(key) ?? {};
100
+ const { held, loading, failed } = slot;
101
+ if (!force) {
102
+ if (held && held.expiresAt > at) return Promise.resolve(held.value);
103
+ if (failed && failed.expiresAt > at) return failed.promise;
104
+ }
105
+ if (loading && at - loading.startedAt < shareMs && (!force || !held || loading.startedAt >= held.arrivedAt)) return loading.promise;
106
+
107
+ const run: NonNullable<Slot<V>["loading"]> = { promise: undefined as unknown as Promise<V>, startedAt: at };
108
+ const current = () => slots.get(key)?.loading === run;
109
+ run.promise = new Promise<V>((resolve) => resolve(load())).then((value) => {
110
+ if (current()) {
111
+ slot.loading = undefined;
112
+ try {
113
+ // A `keep` or TTL function that throws rejects for the callers, like the load.
114
+ if (keep(value)) hold(slot, value);
115
+ } finally {
116
+ tidy(key, slot);
117
+ }
118
+ }
119
+ return value;
120
+ }, (error: unknown) => {
121
+ if (current()) {
122
+ slot.loading = undefined;
123
+ const fresh = slot.held && slot.held.expiresAt > clock();
124
+ if (rejectionMs > 0 && !fresh) slot.failed = { promise: run.promise, expiresAt: clock() + rejectionMs };
125
+ tidy(key, slot);
126
+ }
127
+ throw error;
128
+ });
129
+ // A caller that drops the promise must not raise an unhandled rejection.
130
+ run.promise.then(undefined, () => { /* Intentional ignore: every caller holds this promise and sees the rejection. */ });
131
+ slot.loading = run;
132
+ slot.failed = undefined;
133
+ store(key, slot);
134
+ return run.promise;
135
+ },
136
+ peek(key, { stale = false } = {}) {
137
+ const held = slots.get(key)?.held;
138
+ return held && (stale || held.expiresAt > clock()) ? held.value : undefined;
139
+ },
140
+ failed(key) {
141
+ const failed = slots.get(key)?.failed;
142
+ return !!failed && failed.expiresAt > clock();
143
+ },
144
+ set(key, value) {
145
+ if (!keep(value)) return;
146
+ const slot = slots.get(key) ?? {};
147
+ hold(slot, value);
148
+ store(key, slot);
149
+ },
150
+ delete(key, only) {
151
+ const slot = slots.get(key);
152
+ if (!slot) return;
153
+ if (!only) {
154
+ slots.delete(key);
155
+ return;
156
+ }
157
+ if (slot.loading?.promise === only) slot.loading = undefined;
158
+ if (slot.failed?.promise === only) slot.failed = undefined;
159
+ tidy(key, slot);
160
+ },
161
+ clear() {
162
+ slots.clear();
163
+ },
164
+ };
165
+ }