@gloom-sh/terminal 0.18.5 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) 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 +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  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 +19 -7
  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/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  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/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -7,7 +7,14 @@ const WS_SYSMENU = 0x00080000n;
7
7
  const WS_THICKFRAME = 0x00040000n;
8
8
  const WS_MINIMIZEBOX = 0x00020000n;
9
9
  const WS_MAXIMIZEBOX = 0x00010000n;
10
- const WINDOWS_NATIVE_CONTROL_STYLE_MASK = WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
10
+ /**
11
+ * Electrobun's "hiddenInset" window is captioned and sizable, with the caption
12
+ * cut out of its frame: its edges and corners resize it, it maximizes to the
13
+ * work area and keeps its shadow, while the header draws the buttons. Windows
14
+ * still reads the box bits: they let a click on the taskbar button minimize
15
+ * the window, and Win+Up, snapping and the window menu maximize it.
16
+ */
17
+ const WINDOWS_FRAME_STYLE = WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
11
18
  const SWP_NOSIZE = 0x0001;
12
19
  const SWP_NOMOVE = 0x0002;
13
20
  const SWP_NOZORDER = 0x0004;
@@ -22,7 +29,7 @@ function styleValue(value: number | bigint): bigint {
22
29
 
23
30
  function applyCustomChromeToWindow(win32: Win32, windowHandle: Pointer): boolean {
24
31
  const currentStyle = styleValue(win32.symbols.GetWindowLongPtrW(windowHandle, GWL_STYLE));
25
- const nextStyle = currentStyle & ~WINDOWS_NATIVE_CONTROL_STYLE_MASK;
32
+ const nextStyle = currentStyle | WINDOWS_FRAME_STYLE;
26
33
  if (nextStyle !== currentStyle) {
27
34
  win32.symbols.SetWindowLongPtrW(windowHandle, GWL_STYLE, nextStyle);
28
35
  }
@@ -41,6 +41,22 @@ function loadWin32() {
41
41
  args: [FFIType.ptr, FFIType.ptr, FFIType.int, FFIType.int, FFIType.int, FFIType.int, FFIType.u32],
42
42
  returns: FFIType.bool,
43
43
  },
44
+ GetCursorPos: {
45
+ args: [FFIType.ptr],
46
+ returns: FFIType.bool,
47
+ },
48
+ GetClientRect: {
49
+ args: [FFIType.ptr, FFIType.ptr],
50
+ returns: FFIType.bool,
51
+ },
52
+ PostMessageW: {
53
+ args: [FFIType.ptr, FFIType.u32, FFIType.u64, FFIType.i64],
54
+ returns: FFIType.bool,
55
+ },
56
+ GetGUIThreadInfo: {
57
+ args: [FFIType.u32, FFIType.ptr],
58
+ returns: FFIType.bool,
59
+ },
44
60
  });
45
61
  }
46
62
 
@@ -56,6 +72,68 @@ function win32OrNull(): Win32 | null {
56
72
  return cachedWin32;
57
73
  }
58
74
 
75
+ const WM_SIZE = 0x0005;
76
+
77
+ /**
78
+ * Electrobun opens a window's page at the size of the whole window and fits it
79
+ * to the client area on each WM_SIZE. A framed window's client area is smaller
80
+ * than the window, so until the window first changed size its page ran past
81
+ * the right and bottom edges, the Close button with it. Sends the window the
82
+ * WM_SIZE that fits the page. False off Windows.
83
+ */
84
+ export function fitPageToClientArea(windowHandle: Pointer): boolean {
85
+ const win32 = win32OrNull();
86
+ if (!win32) return false;
87
+ const client = new Int32Array(4);
88
+ if (!win32.symbols.GetClientRect(windowHandle, ptr(client))) return false;
89
+ const size = (((client[3]! - client[1]!) & 0xffff) << 16) | ((client[2]! - client[0]!) & 0xffff);
90
+ return win32.symbols.PostMessageW(windowHandle, WM_SIZE, 0n, BigInt(size >>> 0));
91
+ }
92
+
93
+ const WM_NCLBUTTONDOWN = 0x00a1;
94
+ const WM_CANCELMODE = 0x001f;
95
+ const HTCAPTION = 2;
96
+ /** GUITHREADINFO on 64-bit Windows: two DWORDs, six window handles, a RECT. */
97
+ const GUI_THREAD_INFO_SIZE = 72;
98
+ const GUI_THREAD_INFO_CAPTURE_OFFSET = 24;
99
+
100
+ /**
101
+ * The window holding the mouse on `windowHandle`'s thread, if any. Chromium
102
+ * takes the mouse on every press, and Windows will not start a move while
103
+ * another window holds it.
104
+ */
105
+ function mouseCaptureOnThreadOf(win32: Win32, windowHandle: Pointer): Pointer | null {
106
+ const thread = win32.symbols.GetWindowThreadProcessId(windowHandle, null);
107
+ if (!thread) return null;
108
+ const info = new DataView(new ArrayBuffer(GUI_THREAD_INFO_SIZE));
109
+ info.setUint32(0, GUI_THREAD_INFO_SIZE, true);
110
+ if (!win32.symbols.GetGUIThreadInfo(thread, ptr(info))) return null;
111
+ const capture = Number(info.getBigUint64(GUI_THREAD_INFO_CAPTURE_OFFSET, true));
112
+ return capture ? capture as Pointer : null;
113
+ }
114
+
115
+ /**
116
+ * Hands a press on the header to Windows as a press on a native title bar, from
117
+ * where the pointer is. Its move loop then moves the window until the button
118
+ * comes up: it snaps to the edges and corners of the screen and comes out of
119
+ * maximized, which a window moved by hand does not. Electrobun's own mover
120
+ * only moves. False off Windows.
121
+ *
122
+ * The page holds the mouse from the press, so it is asked to let go first, as
123
+ * a native title bar's window would have. Both messages go to the window's
124
+ * thread, which handles them in order.
125
+ */
126
+ export function startSystemWindowMove(windowHandle: Pointer): boolean {
127
+ const win32 = win32OrNull();
128
+ if (!win32) return false;
129
+ const point = new Int32Array(2);
130
+ if (!win32.symbols.GetCursorPos(ptr(point))) return false;
131
+ const capture = mouseCaptureOnThreadOf(win32, windowHandle);
132
+ if (capture && capture !== windowHandle) win32.symbols.PostMessageW(capture, WM_CANCELMODE, 0n, 0n);
133
+ const position = ((point[1]! & 0xffff) << 16) | (point[0]! & 0xffff);
134
+ return win32.symbols.PostMessageW(windowHandle, WM_NCLBUTTONDOWN, BigInt(HTCAPTION), BigInt(position >>> 0));
135
+ }
136
+
59
137
  export function wideString(value: string): Uint8Array {
60
138
  return Uint8Array.from(Buffer.from(`${value}\0`, "utf16le"));
61
139
  }
@@ -0,0 +1,57 @@
1
+ import { dlopen, FFIType, ptr, type Pointer } from "bun:ffi";
2
+ import Electrobun from "electrobun/bun";
3
+ import { wideString } from "./windows-native";
4
+
5
+ const WM_QUIT = 0x0012;
6
+ /** The class Electrobun registers for its windows. */
7
+ const WINDOW_CLASS = "BasicWindowClass";
8
+ /** How long a quit waits for this app's windows to be destroyed. */
9
+ const WINDOWS_CLOSED_TIMEOUT_MS = 1_000;
10
+
11
+ function loadUser32() {
12
+ return dlopen("user32.dll", {
13
+ GetWindowThreadProcessId: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.u32 },
14
+ FindWindowExW: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
15
+ PostThreadMessageW: { args: [FFIType.u32, FFIType.u32, FFIType.u64, FFIType.i64], returns: FFIType.bool },
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Electrobun ends its Windows event loop with PostQuitMessage, from the thread
21
+ * the app's code runs on rather than the one running the loop. The loop never
22
+ * ends: the app sits out a 5 s timeout with Chromium never shut down, and a
23
+ * Gloom opened meanwhile finds Chromium's profile still held, hands itself to
24
+ * the closing app and crashes. The quit message goes to the loop's own thread
25
+ * instead, once this app's windows are gone; with one still open, Electrobun's
26
+ * timeout ends the app as before. Does nothing off Windows.
27
+ */
28
+ export function quitWindowsEventLoopOnItsThread(window: { ptr: Pointer | null }): void {
29
+ if (process.platform !== "win32" || !window.ptr) return;
30
+ let user32: ReturnType<typeof loadUser32>;
31
+ try {
32
+ user32 = loadUser32();
33
+ } catch {
34
+ return;
35
+ }
36
+ const loopThread = user32.symbols.GetWindowThreadProcessId(window.ptr, null);
37
+ if (!loopThread) return;
38
+ const className = wideString(WINDOW_CLASS);
39
+ const processId = new Uint32Array(1);
40
+ const ownWindowOpen = (): boolean => {
41
+ let handle: Pointer | null = null;
42
+ while ((handle = user32.symbols.FindWindowExW(null, handle, ptr(className), null))) {
43
+ user32.symbols.GetWindowThreadProcessId(handle, ptr(processId));
44
+ if (processId[0] === process.pid) return true;
45
+ }
46
+ return false;
47
+ };
48
+ Electrobun.events.on("before-quit", () => {
49
+ // Quitting blocks this thread until the loop has ended, so the wait is here.
50
+ const deadline = Date.now() + WINDOWS_CLOSED_TIMEOUT_MS;
51
+ while (ownWindowOpen()) {
52
+ if (Date.now() > deadline) return;
53
+ Bun.sleepSync(16);
54
+ }
55
+ user32.symbols.PostThreadMessageW(loopThread, WM_QUIT, 0n, 0n);
56
+ });
57
+ }
@@ -15,3 +15,16 @@ export const LEGACY_DESKTOP_APP_NAME = "Gloomberb";
15
15
  * to it.
16
16
  */
17
17
  export const DESKTOP_APP_IDENTIFIER = "com.vincelwt.gloomberb";
18
+
19
+ /**
20
+ * The identifiers a build of this app carries: the one above, and the one a
21
+ * later release moves to. Until every install has moved, either one can be
22
+ * on disk (an app bundle, an install the script or Homebrew replaces), so
23
+ * anything that asks whether a copy is this app accepts both.
24
+ */
25
+ export const DESKTOP_APP_IDENTIFIERS: readonly string[] = [DESKTOP_APP_IDENTIFIER, "sh.gloom.desktop"];
26
+
27
+ /** Whether `identifier` is one a build of this app carries. */
28
+ export function isDesktopAppIdentifier(identifier: string | null | undefined): boolean {
29
+ return !!identifier && DESKTOP_APP_IDENTIFIERS.includes(identifier);
30
+ }
@@ -10,6 +10,7 @@ import type { CapabilityManifest } from "../../../capabilities";
10
10
  import type { PluginOperationResult, PluginPin } from "../../../plugins/marketplace/store";
11
11
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../remote/types";
12
12
  import type { HttpProxyRequestEnvelope, HttpProxyResponseEnvelope } from "../../../utils/http-proxy-response";
13
+ import type { StallEvent } from "../../../utils/main-thread-stall";
13
14
  import type { BootFrame } from "../../dom/boot-frame-page";
14
15
 
15
16
  export const ELECTROBUN_CONTEXT_MENU_ACTION = "gloom.context-menu.select";
@@ -32,8 +33,13 @@ export interface ElectrobunBackendInit {
32
33
  pluginSeedNotice?: { body: string; type: "error" | "info" };
33
34
  /** What the view needs to send crash reports and usage counts the same way the Bun process would. */
34
35
  telemetry: ElectrobunTelemetryInit;
36
+ /** The copy on disk of the ids the view keeps in its own storage (view/view-ids.ts). */
37
+ viewIds: DesktopViewIds;
35
38
  }
36
39
 
40
+ /** Ids the view keeps in its WebView storage, by storage key. */
41
+ export type DesktopViewIds = Record<string, string>;
42
+
37
43
  interface ElectrobunTelemetryInit {
38
44
  /** Random per-install id from `<dataDir>/install-id`; null when the file could not be written. */
39
45
  installId: string | null;
@@ -206,6 +212,8 @@ interface DesktopBackendRequestMap {
206
212
  "host.exit": { request: null; response: null };
207
213
  "host.windowControl": { request: { action: DesktopWindowControlAction }; response: null };
208
214
  "host.windowFullscreen": { request: null; response: boolean };
215
+ /** Windows: moves the window with the pointer through the system's own move loop. */
216
+ "host.startWindowDrag": { request: null; response: null };
209
217
  "host.openExternal": { request: { url: string }; response: boolean };
210
218
  "host.copyText": { request: { text: string }; response: null };
211
219
  "host.focusWindow": { request: null; response: null };
@@ -222,6 +230,8 @@ interface DesktopBackendRequestMap {
222
230
  "host.showContextMenu": { request: { menu: DesktopContextMenuItem[] }; response: boolean };
223
231
  /** The layout the main window draws at the next launch before its script loads (dom/boot-frame.ts). Only the main window's is kept. */
224
232
  "host.saveBootFrame": { request: { frame: BootFrame }; response: null };
233
+ /** The ids the view keeps in its storage, for the copy on disk. Only the main window's are kept. */
234
+ "host.saveViewIds": { request: { ids: DesktopViewIds }; response: null };
225
235
  /** The unread count for the Dock icon badge: `""` (none), `"1"` to `"99"` or `"99+"`. Only the main window's is applied. */
226
236
  "host.setAppBadge": { request: { label: string }; response: null };
227
237
  /** `channel` absent (an older view) means stable. */
@@ -337,6 +347,8 @@ export interface ElectrobunDesktopRpcSchema {
337
347
  };
338
348
  messages: {
339
349
  "host.restart": DesktopRestartMessage;
350
+ /** A window froze; the Bun process keeps the stall log and its report. */
351
+ "runtime.stall": StallEvent;
340
352
  };
341
353
  };
342
354
  webview: {
@@ -17,6 +17,7 @@ import { subscribeCapability, type CapabilitySubscriptionOptions } from "./capab
17
17
  import { RpcTimeoutError } from "../../../utils/rpc-timeout-error";
18
18
  import { nameRpcTimeout, RPC_MAX_REQUEST_TIME_MS } from "./rpc-timeout";
19
19
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../remote/types";
20
+ import type { StallEvent } from "../../../utils/main-thread-stall";
20
21
 
21
22
  type BackendMessages = ElectrobunDesktopRpcSchema["webview"]["messages"];
22
23
  type BackendMessageName = keyof BackendMessages;
@@ -153,6 +154,11 @@ export function requestElectrobunRestart(message: DesktopRestartMessage = {}): v
153
154
  rpc.send["host.restart"](message);
154
155
  }
155
156
 
157
+ /** The Bun process keeps the stall log and sends its report. */
158
+ export function sendElectrobunStall(event: StallEvent): void {
159
+ rpc.send["runtime.stall"](event);
160
+ }
161
+
156
162
  export function getElectrobunBackendInitSnapshot(): ElectrobunBackendInit | null {
157
163
  return initSnapshot;
158
164
  }
@@ -5,12 +5,14 @@ import { App } from "../../../app";
5
5
  import { applyLanguageFromConfig } from "../../../i18n";
6
6
  import { debugLog } from "../../../utils/debug-log";
7
7
  import { capturePaneImage } from "../../../utils/dom-screenshot";
8
+ import { setStallSink } from "../../../utils/main-thread-stall";
8
9
  import { measurePerfAsync } from "../../../utils/perf-marks";
9
10
  import {
10
11
  backendRequest,
11
12
  initElectrobunBackend,
12
13
  onBackendMessage,
13
14
  replaceElectrobunCapabilityManifests,
15
+ sendElectrobunStall,
14
16
  setElectrobunRemoteRequestHandler,
15
17
  } from "./backend-rpc";
16
18
  import { installElectrobunCapabilityStreamClient } from "./capability-stream-client";
@@ -49,10 +51,13 @@ import { loadOfficialPluginIds } from "../../../plugins/marketplace/feed";
49
51
  import { setPluginSeedNotice } from "../../../plugins/seed-notice";
50
52
  import { crashReportsEnabled, installCrashReporter, reportCrash } from "../../../telemetry/crash-reports";
51
53
  import { browserDoNotTrack, installWindowCrashListeners } from "../../../telemetry/crash-reports-dom";
54
+ import { forwardAttendedStalls } from "../../../telemetry/stall-reports-dom";
52
55
  import { currentTelemetryConfig } from "../../../telemetry/live-config";
53
56
  import { attentionCountsEnabled, installAttentionCounter } from "../../../telemetry/attention-counts";
54
57
  import { installUsageCounter, usageCountsEnabled } from "../../../telemetry/usage-counts";
55
58
  import { installWindowUsageFlush } from "../../../telemetry/usage-counts-dom";
59
+ import { cloudSyncController } from "../../../sync/controller";
60
+ import { createViewIdMirror, restoreViewIds, viewIdStorage } from "./view-ids";
56
61
 
57
62
  // Declared here rather than sniffed: the desktop view and the hosted browser
58
63
  // app are both browser contexts but differ in what plugins may do.
@@ -156,6 +161,8 @@ async function boot() {
156
161
  officialPluginIds: loadOfficialPluginIds,
157
162
  });
158
163
  installAttentionCounter(() => !init.telemetry.optedOut && !browserDoNotTrack() && attentionCountsEnabled(currentTelemetryConfig(init.config)));
164
+ // Desktop has no Debug Log: this window's freezes go to the Bun process, which keeps the stall log.
165
+ setStallSink(forwardAttendedStalls(sendElectrobunStall).sink);
159
166
  installElectrobunCapabilityStreamClient();
160
167
  installFocusScopeRelease();
161
168
  installElectrobunWindowFullscreenTracking();
@@ -173,10 +180,26 @@ async function boot() {
173
180
  const desktopApplicationMenuBridge: DesktopApplicationMenuBridge = {
174
181
  subscribe: (listener) => onBackendMessage("application-menu.select", ({ command }) => listener(command)),
175
182
  };
183
+ // Before anything reads them: ids this view's storage lacks come back from
184
+ // the copy on disk, and the main window keeps that copy up to date. The sync
185
+ // id is made at the first push, and a gloom.sh link can bring a visitor id.
186
+ const viewStorage = viewIdStorage();
187
+ if (viewStorage) restoreViewIds(viewStorage, init.viewIds);
188
+ const mirrorViewIds = viewStorage && init.windowKind === "main"
189
+ ? createViewIdMirror(viewStorage, init.viewIds, (ids) => backendRequest("host.saveViewIds", { ids }))
190
+ : () => {};
191
+ mirrorViewIds();
192
+ cloudSyncController.subscribe(mirrorViewIds);
176
193
  initializeDesktopResearchActivity();
177
- const desktopDeepLinkBridge = observeDesktopDeepLinks({
194
+ const observedDeepLinks = observeDesktopDeepLinks({
178
195
  subscribe: (listener) => onBackendMessage("desktop.deepLink", listener),
179
196
  });
197
+ const desktopDeepLinkBridge: typeof observedDeepLinks = {
198
+ subscribe: (listener) => observedDeepLinks.subscribe((deeplink) => {
199
+ listener(deeplink);
200
+ mirrorViewIds();
201
+ }),
202
+ };
180
203
  const webUiHost = createWebUiHost(init.desktopPlatform);
181
204
  // Compiled by the Bun process, which owns the filesystem. A failure here must
182
205
  // not stop the app from starting: the marketplace reports broken plugins, and
@@ -2,6 +2,7 @@ import { apiClient } from "../../../api-client";
2
2
  import { newsProvider, type NewsCapability } from "../../../capabilities";
3
3
  import { cloudNewsReadArticles, mapCloudNewsArticle, readCloudNews } from "../../../sources/gloomberb-cloud/news";
4
4
  import type { NewsArticle, NewsPage, NewsQuery } from "../../../news/types";
5
+ import { readCompanyFrom } from "../../../types/company-from";
5
6
 
6
7
  interface BackendNews {
7
8
  id: string;
@@ -10,7 +11,8 @@ interface BackendNews {
10
11
  getNewsPage(query: NewsQuery): Promise<NewsPage>;
11
12
  }
12
13
 
13
- type CloudNewsClient = Pick<typeof apiClient, "getCloudNews" | "getCloudNewsStory" | "searchCloudNews">;
14
+ type CloudNewsClient = Pick<typeof apiClient, "getCloudNews" | "getCloudNewsStory" | "searchCloudNews">
15
+ & Partial<Pick<typeof apiClient, "hasSessionCredential">>;
14
16
 
15
17
  /**
16
18
  * The view's only news source: the desktop view runs without capability
@@ -32,6 +34,8 @@ export function createDesktopNewsSource(backend: BackendNews, client: CloudNewsC
32
34
  return {
33
35
  articles: cloudNewsReadArticles(read, query.ticker),
34
36
  nextCursor: read.nextCursor ?? null,
37
+ access: read.access ?? null,
38
+ companyFrom: readCompanyFrom(read.companyFrom),
35
39
  };
36
40
  } catch {
37
41
  // The aggregate page keeps RSS failures beside any usable stories.
@@ -1,5 +1,6 @@
1
1
  import type { TickerMetadata, TickerRecord } from "../../../../types/ticker";
2
2
  import { backendRequest } from "../backend-rpc";
3
+ import { runTickerWrite } from "../../../../data/ticker-writes";
3
4
 
4
5
  export class RemoteTickerRepository {
5
6
  async loadAllTickers(): Promise<TickerRecord[]> {
@@ -10,17 +11,23 @@ export class RemoteTickerRepository {
10
11
  return backendRequest("ticker.load", { symbol });
11
12
  }
12
13
 
13
- async saveTicker(ticker: TickerRecord): Promise<void> {
14
- await backendRequest("ticker.save", { ticker });
14
+ saveTicker(ticker: TickerRecord): Promise<void> {
15
+ return runTickerWrite(this, ticker.metadata.ticker, async () => {
16
+ await backendRequest("ticker.save", { ticker });
17
+ });
15
18
  }
16
19
 
17
- async createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
20
+ createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
18
21
  const ticker: TickerRecord = { metadata };
19
- await this.saveTicker(ticker);
20
- return ticker;
22
+ return runTickerWrite(this, metadata.ticker, async () => {
23
+ await backendRequest("ticker.save", { ticker });
24
+ return ticker;
25
+ });
21
26
  }
22
27
 
23
- async deleteTicker(symbol: string): Promise<void> {
24
- await backendRequest("ticker.delete", { symbol });
28
+ deleteTicker(symbol: string): Promise<void> {
29
+ return runTickerWrite(this, symbol, async () => {
30
+ await backendRequest("ticker.delete", { symbol });
31
+ });
25
32
  }
26
33
  }
@@ -5,6 +5,7 @@ import { backendRequest } from "./backend-rpc";
5
5
  import { NATIVE_CONTEXT_MENU_SUPPORTED, showDesktopContextMenu } from "./host/native";
6
6
  import { startElectrobunWindowDrag } from "./host/window-move";
7
7
  import { createDomUiHost } from "../../dom/dom-ui-host";
8
+ import { detectPlatform } from "../../../utils/platform";
8
9
 
9
10
  export function createWebUiHost(desktopPlatform?: string): UiHost {
10
11
  return createDomUiHost(desktopPlatform, {
@@ -25,6 +26,13 @@ export const webRendererHost: RendererHost = {
25
26
  });
26
27
  },
27
28
  startWindowDrag() {
29
+ // Windows moves the window itself, as it does from a native title bar.
30
+ if (detectPlatform() === "win32") {
31
+ void backendRequest("host.startWindowDrag").catch((error: unknown) => {
32
+ console.error("[desktop] could not start moving the window", error instanceof Error ? error.message : String(error));
33
+ });
34
+ return;
35
+ }
28
36
  startElectrobunWindowDrag();
29
37
  },
30
38
  async controlWindow(action) {
@@ -0,0 +1,85 @@
1
+ import { ANONYMOUS_ID_STORAGE_KEY, ATTRIBUTION_STORAGE_KEY } from "../../../api-client/research-activity";
2
+ import { SYNC_CLIENT_ID_STORAGE_KEY } from "../../../sync/controller";
3
+ import { logFailure } from "../../../utils/log-failure";
4
+ import type { DesktopViewIds } from "../shared/protocol";
5
+
6
+ /**
7
+ * What the desktop view keeps only in its WebView storage: the id Cloud sync
8
+ * knows this device by, and the visitor id and first touch a gloom.sh link
9
+ * handed over. macOS and Windows keep that storage under the app's bundle
10
+ * identifier, so a build under another identifier would start without them.
11
+ * The Bun process keeps a copy in the data folder (bun/desktop/view-ids.ts),
12
+ * and a view whose storage lacks one takes it back from there.
13
+ */
14
+ const VIEW_ID_KEYS = [SYNC_CLIENT_ID_STORAGE_KEY, ANONYMOUS_ID_STORAGE_KEY, ATTRIBUTION_STORAGE_KEY] as const;
15
+
16
+ interface ViewIdStorage {
17
+ getItem(key: string): string | null;
18
+ setItem(key: string, value: string): void;
19
+ }
20
+
21
+ /** The view's WebView storage, or null where it is refused. */
22
+ export function viewIdStorage(): ViewIdStorage | null {
23
+ try {
24
+ return globalThis.localStorage ?? null;
25
+ } catch {
26
+ // Intentional ignore: without storage there is nothing to restore or keep.
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function readViewIds(storage: ViewIdStorage): DesktopViewIds {
32
+ const ids: DesktopViewIds = {};
33
+ for (const key of VIEW_ID_KEYS) {
34
+ const value = storage.getItem(key);
35
+ if (value) ids[key] = value;
36
+ }
37
+ return ids;
38
+ }
39
+
40
+ function sameIds(left: DesktopViewIds, right: DesktopViewIds): boolean {
41
+ const keys = Object.keys(left);
42
+ return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
43
+ }
44
+
45
+ /** Puts the ids kept on disk into `storage` where it has none. One it has is never replaced. */
46
+ export function restoreViewIds(storage: ViewIdStorage, kept: DesktopViewIds | undefined): void {
47
+ try {
48
+ for (const key of VIEW_ID_KEYS) {
49
+ const value = kept?.[key];
50
+ if (value && !storage.getItem(key)) storage.setItem(key, value);
51
+ }
52
+ } catch (error) {
53
+ logFailure("desktop-view-ids", "Could not restore the view's ids")(error);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * A function that sends the ids in `storage` to be kept on disk when they
59
+ * differ from the copy there (`kept`, from `init`). It reads three keys, so it
60
+ * can run whenever one of them may have been written. A save that fails is
61
+ * tried again at the next call.
62
+ */
63
+ export function createViewIdMirror(
64
+ storage: ViewIdStorage,
65
+ kept: DesktopViewIds | undefined,
66
+ save: (ids: DesktopViewIds) => Promise<unknown>,
67
+ ): () => void {
68
+ let onDisk: DesktopViewIds = { ...kept };
69
+ return () => {
70
+ let ids: DesktopViewIds;
71
+ try {
72
+ ids = readViewIds(storage);
73
+ } catch (error) {
74
+ logFailure("desktop-view-ids", "Could not read the view's ids")(error);
75
+ return;
76
+ }
77
+ if (sameIds(ids, onDisk)) return;
78
+ const previous = onDisk;
79
+ onDisk = ids;
80
+ save(ids).catch((error: unknown) => {
81
+ if (onDisk === ids) onDisk = previous;
82
+ logFailure("desktop-view-ids", "Could not send the view's ids to be kept")(error);
83
+ });
84
+ };
85
+ }
@@ -38,6 +38,9 @@ import {
38
38
  } from "../../telemetry/crash-reports-node";
39
39
  import { currentTelemetryConfig } from "../../telemetry/live-config";
40
40
  import { installUpdateFailureReporter } from "../../telemetry/update-failures";
41
+ import { installStallReporter, recordStall } from "../../telemetry/stall-reports";
42
+ import { createNodeStallReporterHost } from "../../telemetry/stall-reports-node";
43
+ import { setStallSink } from "../../utils/main-thread-stall";
41
44
  import { createNodeUpdateFailureHost } from "../../telemetry/update-failures-node";
42
45
  import { terminalInstallKind } from "../../updater";
43
46
  import { flushUsageCounts, installUsageCounter, usageCountsEnabled } from "../../telemetry/usage-counts";
@@ -124,6 +127,8 @@ export async function startOpenTuiApp({ externalPlugins, cliLaunchRequest }: Sta
124
127
  installKind: terminalInstallKind(),
125
128
  getDataDir: () => config.dataDir,
126
129
  }));
130
+ installStallReporter(createNodeStallReporterHost({ getDataDir: () => config.dataDir }));
131
+ setStallSink((event) => recordStall("terminal", event));
127
132
  installUsageCounter({
128
133
  surface: "terminal",
129
134
  os: describeNodeOs(),
@@ -63,6 +63,7 @@ export function seriesCatalogEntry(series: RegisteredSeries): SeriesCatalogEntry
63
63
  ...(definition.description ? { description: definition.description } : {}),
64
64
  ...(definition.publisher ? { publisher: definition.publisher } : {}),
65
65
  ...(definition.url ? { url: definition.url } : {}),
66
+ ...(definition.limitation ? { limitation: definition.limitation } : {}),
66
67
  ...(definition.input ? { input: definition.input } : {}),
67
68
  ...(definition.placement ? { placement: definition.placement } : {}),
68
69
  ...(definition.lines?.length ? {
@@ -133,6 +133,11 @@ interface SeriesDefinitionBase {
133
133
  publisher?: string;
134
134
  /** The publisher's page for the series. */
135
135
  url?: string;
136
+ /**
137
+ * A limit on the data the publisher sets, such as a cap on how far back its
138
+ * history goes. One sentence: the catalog lists it beside the series.
139
+ */
140
+ limitation?: string;
136
141
  /** Ask for a ticker, an option or a crypto pair before charting. */
137
142
  input?: SeriesInput;
138
143
  /**
@@ -203,6 +208,7 @@ export interface SeriesCatalogEntry {
203
208
  description?: string;
204
209
  publisher?: string;
205
210
  url?: string;
211
+ limitation?: string;
206
212
  input?: SeriesInput;
207
213
  placement?: SeriesPlacement;
208
214
  /** The lines of a series that draws several, each with the expression that charts it alone. */
@@ -136,6 +136,7 @@ export function validateSeriesDefinition(definition: SeriesDefinition, context:
136
136
  checkLabel(id, "group", definition.group, 32, context);
137
137
  checkLabel(id, "description", definition.description, 300, context);
138
138
  checkLabel(id, "publisher", definition.publisher, 60, context);
139
+ checkLabel(id, "limitation", definition.limitation, 160, context);
139
140
  if (definition.tags !== undefined) {
140
141
  if (!Array.isArray(definition.tags) || definition.tags.length > 16) fail(id, "tags must be a list of at most 16 words.");
141
142
  for (const tag of definition.tags) checkLabel(id, "tag", tag, 40, context, false);
@@ -42,6 +42,8 @@ export interface FredSeriesCacheEntry {
42
42
  export interface FredSeriesLoadResult extends FredSeriesCacheEntry {
43
43
  source: "cache" | "network" | "stale-fallback";
44
44
  refreshError?: string;
45
+ /** What the failed refresh threw, when a saved copy answered (`stale-fallback`). */
46
+ error?: unknown;
45
47
  }
46
48
 
47
49
  const cache = createPluginCache<FredSeriesData>({
@@ -197,6 +199,6 @@ export async function loadCachedFredSeries(
197
199
  if (!options?.force && !pending && cached && !cached.stale) return { ...cached, source: "cache" };
198
200
  const result = await cache.load(cacheKey(request), loader, pending ? { ...options, force: true } : options);
199
201
  // An early re-read is opportunistic: a failure keeps the still-fresh copy as it was.
200
- if (pending && result.refreshError && cached && !cached.stale && !options?.force) return { ...cached, source: "cache" };
202
+ if (pending && result.source === "stale-fallback" && cached && !cached.stale && !options?.force) return { ...cached, source: "cache" };
201
203
  return withFredSourceFreshness(result);
202
204
  }