@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,54 @@
1
+ import type { FinancialStatement } from "../types/financials";
2
+
3
+ /**
4
+ * How net debt treats the lease liabilities a listing's balance sheets report (docs/financial-ratios.md#leases):
5
+ *
6
+ * - `included`: added to total debt. IFRS 16 puts every lessee lease on the balance sheet and reports EBITDA before
7
+ * lease costs, so leverage with the leases in is like for like (IAG's June 2026 balance sheet: 6.96bn of
8
+ * borrowings, 6.91bn of leases, 9.18bn of cash).
9
+ * - `excluded`: a company reporting under US GAAP, whatever form it files (a 10-K, or a 20-F as BABA, PDD and JD
10
+ * do). Its finance leases are already in total debt and its operating lease cost is in EBITDA, so its operating
11
+ * leases stay out.
12
+ *
13
+ * The standard is the one Gloom Cloud reads from the issuer's latest annual report to the SEC
14
+ * (`accountingStandard`), the signal its enterprise value check bridges by. Where no row states it, a row dated by
15
+ * a 10-K or 10-Q (`dateSource: "sec"`) is US GAAP, and a company that files none with the SEC is IFRS.
16
+ */
17
+ export type LeaseBasis = "included" | "excluded";
18
+
19
+ type LeaseLines = Pick<FinancialStatement, "capitalLeaseObligations" | "dateSource" | "accountingStandard">;
20
+
21
+ const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
22
+
23
+ /** The listing's lease basis, or undefined when no balance sheet on file reports lease liabilities. */
24
+ export function leaseBasis(statements: Iterable<LeaseLines>): LeaseBasis | undefined {
25
+ let leases = false;
26
+ let standard: FinancialStatement["accountingStandard"];
27
+ let secDated = false;
28
+ for (const statement of statements) {
29
+ if (finite(statement.capitalLeaseObligations) && statement.capitalLeaseObligations > 0) leases = true;
30
+ standard ??= statement.accountingStandard;
31
+ if (statement.dateSource === "sec") secDated = true;
32
+ }
33
+ if (!leases) return undefined;
34
+ return (standard ? standard === "us-gaap" : secDated) ? "excluded" : "included";
35
+ }
36
+
37
+ /** Amounts are whole units; some providers round a leg to the thousand. */
38
+ const close = (a: number, b: number) => Math.abs(a - b) <= Math.max(1_000, Math.abs(a) * 1e-6);
39
+
40
+ /**
41
+ * A balance sheet's total debt and lease liabilities. A total that already holds the lease line (long-term and
42
+ * current debt plus leases) is read as the borrowings beside it, so the leases count once. Undefined without both
43
+ * lines: a missing lease line is never zero.
44
+ */
45
+ export function debtAndLeases(
46
+ statement: Pick<FinancialStatement, "totalDebt" | "capitalLeaseObligations" | "longTermDebt" | "currentDebt">,
47
+ ): { totalDebt: number; leases: number } | undefined {
48
+ const total = statement.totalDebt;
49
+ const leases = statement.capitalLeaseObligations;
50
+ if (!finite(total) || !finite(leases) || leases < 0) return undefined;
51
+ const { longTermDebt: long, currentDebt: current } = statement;
52
+ const holds = leases > 0 && (finite(long) || finite(current)) && close(total, (long ?? 0) + (current ?? 0) + leases);
53
+ return { totalDebt: holds ? total - leases : total, leases };
54
+ }
@@ -1,4 +1,4 @@
1
- import { lastPerfSample, recordPerfSample } from "./perf-marks";
1
+ import { lastPerfSample, recordPerfSample, withoutLastStep } from "./perf-marks";
2
2
 
3
3
  /** How often the monitor checks in. Short enough to date a stall precisely. */
4
4
  const SAMPLE_INTERVAL_MS = 250;
@@ -9,15 +9,54 @@ const SAMPLE_INTERVAL_MS = 250;
9
9
  */
10
10
  const STALL_THRESHOLD_MS = 400;
11
11
 
12
+ /** The facts a `sqlite.blocked` step was measured with (data/sqlite/retry.ts). */
13
+ interface StallSqliteDetail {
14
+ /** The statement's fixed name, such as "touch cached resource". */
15
+ operation: string;
16
+ attempts: number;
17
+ /** Whether it waited on another connection's lock, rather than being slow on its own. */
18
+ lockWait: boolean;
19
+ }
20
+
21
+ /**
22
+ * What a platform's own clocks say at one instant (telemetry/stall-clocks.ts),
23
+ * in ms from any origin. Null where the platform cannot say.
24
+ */
25
+ export interface StallClockReading {
26
+ /** Time the computer was awake: sleep and suspend do not count. */
27
+ awakeMs: number | null;
28
+ /** CPU time this thread has used. */
29
+ threadCpuMs: number | null;
30
+ /** CPU time the whole process has used, every thread together. */
31
+ processCpuMs: number | null;
32
+ }
33
+
12
34
  export interface StallEvent {
35
+ /** How long the thread could not run while the computer was awake. */
13
36
  blockedMs: number;
14
37
  /** The last measured section before the stall (perf-marks), if any. */
15
38
  lastMeasured?: string;
16
39
  /** How long before the stall was noticed that section ended. */
17
40
  lastMeasuredEndedMsAgo?: number;
41
+ /** When that section was a SQLite statement that held the thread. */
42
+ sqlite?: StallSqliteDetail;
43
+ /** How long the process had been running when the stall was noticed. */
44
+ uptimeMs?: number;
45
+ /** Whether the process had finished starting (`markStartupComplete`). */
46
+ phase?: "startup" | "steady";
47
+ /**
48
+ * Share of the stall the thread spent on the CPU, 0 to 1: near 1 it was
49
+ * busy (script, a collection), near 0 it waited (the disk, a lock, a
50
+ * native call answered on another thread).
51
+ */
52
+ threadCpu?: number;
53
+ /** CPU the process's other threads used meanwhile, in cores (1: one core throughout). */
54
+ otherCpu?: number;
18
55
  }
19
56
 
20
57
  let stallSink: ((event: StallEvent) => void) | null = null;
58
+ let stallClocks: (() => StallClockReading) | null = null;
59
+ let startupComplete = false;
21
60
 
22
61
  /**
23
62
  * Where this process's stalls go besides the Debug Log: the stall log and its
@@ -31,14 +70,62 @@ export function setStallSink(next: ((event: StallEvent) => void) | null): () =>
31
70
  };
32
71
  }
33
72
 
73
+ /**
74
+ * How this process reads its platform's clocks (telemetry/stall-clocks.ts):
75
+ * a stall then says whether the thread was busy or waiting, and the time
76
+ * the computer slept is not counted as one.
77
+ */
78
+ export function setStallClocks(next: (() => StallClockReading) | null): () => void {
79
+ stallClocks = next;
80
+ return () => {
81
+ if (stallClocks === next) stallClocks = null;
82
+ };
83
+ }
84
+
85
+ function readStallClocks(read: (() => StallClockReading) | null): StallClockReading | null {
86
+ if (!read) return null;
87
+ try {
88
+ return read();
89
+ } catch {
90
+ // Intentional ignore: a clock that cannot be read leaves the stall's cause unknown, never the stall unseen.
91
+ return null;
92
+ }
93
+ }
94
+
95
+ function delta(after: number | null | undefined, before: number | null | undefined): number | null {
96
+ return after == null || before == null ? null : after - before;
97
+ }
98
+
99
+ /**
100
+ * The process is up: its first screen has its data, or (the desktop app's
101
+ * Bun process) the main window has what it asked for. Stalls before this are
102
+ * reported as startup ones.
103
+ */
104
+ export function markStartupComplete(): void {
105
+ startupComplete = true;
106
+ }
107
+
108
+ /** Test seam. */
109
+ export function resetStartupCompleteForTests(): void {
110
+ startupComplete = false;
111
+ }
112
+
34
113
  function noteStall(event: StallEvent): void {
35
114
  try {
36
- stallSink?.(event);
115
+ withoutLastStep(() => stallSink?.(event));
37
116
  } catch {
38
117
  // Intentional ignore: reporting a freeze must never become one.
39
118
  }
40
119
  }
41
120
 
121
+ /** The facts of a `sqlite.blocked` sample, when they have the shape retry.ts gives them. */
122
+ function sqliteDetail(metadata: Record<string, unknown> | undefined): StallSqliteDetail | undefined {
123
+ if (!metadata) return undefined;
124
+ const { operation, attempts, lockWait } = metadata;
125
+ if (typeof operation !== "string" || typeof attempts !== "number") return undefined;
126
+ return { operation, attempts, lockWait: lockWait === true };
127
+ }
128
+
42
129
  export interface MainThreadStallMonitorOptions {
43
130
  intervalMs?: number;
44
131
  thresholdMs?: number;
@@ -46,6 +133,8 @@ export interface MainThreadStallMonitorOptions {
46
133
  schedule?: (callback: () => void, delayMs: number) => unknown;
47
134
  cancel?: (handle: unknown) => void;
48
135
  report?: (blockedMs: number, context: Omit<StallEvent, "blockedMs">) => void;
136
+ /** Defaults to the clocks `setStallClocks` installed. */
137
+ clocks?: () => StallClockReading;
49
138
  }
50
139
 
51
140
  function defaultNow(): number {
@@ -60,7 +149,15 @@ function defaultNow(): number {
60
149
  * comes back late, the app was unresponsive for exactly that long. The report
61
150
  * carries the last measured section and how long before the stall it ended,
62
151
  * which is usually enough to tell "the cache read did it" from "the render
63
- * did it" in a report that only says the app hung for a few seconds.
152
+ * did it" in a report that only says the app hung for a few seconds, and how
153
+ * long the process had been up, so a slow start reads apart from a hang
154
+ * hours in.
155
+ *
156
+ * Where the platform's clocks can be read, the time the computer slept is
157
+ * taken out (a timer cannot fire then either, and a laptop's lid is not a
158
+ * freeze), and the stall says how much of it the thread spent on the CPU
159
+ * and how busy the process's other threads were: busy is script or a
160
+ * collection, idle on both is a wait on the disk or another program.
64
161
  */
65
162
  export function startMainThreadStallMonitor(
66
163
  options: MainThreadStallMonitorOptions = {},
@@ -72,21 +169,41 @@ export function startMainThreadStallMonitor(
72
169
  const cancel = options.cancel ?? ((handle) => clearInterval(handle as ReturnType<typeof setInterval>));
73
170
  const report = options.report
74
171
  ?? ((blockedMs, context) => {
75
- recordPerfSample("runtime.stall", blockedMs, context);
172
+ // Logged, but not a step: the next stall still names the work before it.
173
+ recordPerfSample("runtime.stall", blockedMs, context, { lastStep: false });
76
174
  noteStall({ blockedMs, ...context });
77
175
  });
78
176
 
177
+ const clocks = () => readStallClocks(options.clocks ?? stallClocks);
79
178
  let previous = now();
179
+ let previousClocks = clocks();
80
180
  const handle = schedule(() => {
81
181
  const current = now();
82
- const blockedMs = current - previous - intervalMs;
182
+ const currentClocks = clocks();
183
+ const elapsedMs = current - previous;
184
+ const before = previousClocks;
83
185
  previous = current;
186
+ previousClocks = currentClocks;
187
+ const awakeMs = delta(currentClocks?.awakeMs, before?.awakeMs);
188
+ // Asleep, the computer runs nothing; only the awake part of the gap was a freeze.
189
+ const asleepMs = awakeMs == null ? 0 : Math.max(0, elapsedMs - awakeMs);
190
+ const blockedMs = elapsedMs - asleepMs - intervalMs;
84
191
  if (blockedMs < thresholdMs) return;
85
192
  const sample = lastPerfSample();
193
+ const sqlite = sample?.name === "sqlite.blocked" ? sqliteDetail(sample.metadata) : undefined;
194
+ const threadCpuMs = delta(currentClocks?.threadCpuMs, before?.threadCpuMs);
195
+ const processCpuMs = delta(currentClocks?.processCpuMs, before?.processCpuMs);
86
196
  report(blockedMs, {
87
197
  ...(sample
88
198
  ? { lastMeasured: sample.name, lastMeasuredEndedMsAgo: Math.round(current - sample.endedAt) }
89
199
  : {}),
200
+ ...(sqlite ? { sqlite } : {}),
201
+ uptimeMs: Math.round(current),
202
+ phase: startupComplete ? "steady" : "startup",
203
+ ...(threadCpuMs == null ? {} : { threadCpu: Math.min(1, Math.max(0, threadCpuMs / blockedMs)) }),
204
+ ...(threadCpuMs == null || processCpuMs == null
205
+ ? {}
206
+ : { otherCpu: Math.max(0, (processCpuMs - threadCpuMs) / (elapsedMs - asleepMs)) }),
90
207
  });
91
208
  }, intervalMs);
92
209
  if (handle && typeof (handle as { unref?: () => void }).unref === "function") {
@@ -20,27 +20,50 @@ function now(): number {
20
20
  : Date.now();
21
21
  }
22
22
 
23
- // Two scalars rather than an object: every measured section writes them, and
24
- // only the stall monitor ever reads them.
23
+ // Scalars rather than an object: every measured section writes them, and
24
+ // only the stall monitor ever reads them. The metadata is the caller's own
25
+ // object, kept by reference.
25
26
  let lastSampleName: string | null = null;
26
27
  let lastSampleEndedAt = 0;
28
+ let lastSampleDurationMs = 0;
29
+ let lastSampleMetadata: Record<string, unknown> | undefined;
30
+ /** Inside `withoutLastStep`: samples are logged, and none becomes the last step. */
31
+ let quietDepth = 0;
27
32
 
28
33
  /**
29
- * The most recent measured section and when it ended. The stall monitor reads
30
- * it so a report of "the app froze" carries the last thing the app is known to
31
- * have been doing.
34
+ * The most recent measured section, when it ended and what it was measured
35
+ * with. The stall monitor reads it so a report of "the app froze" carries the
36
+ * last thing the app is known to have been doing.
32
37
  */
33
- export function lastPerfSample(): { name: string; endedAt: number } | null {
34
- return lastSampleName == null ? null : { name: lastSampleName, endedAt: lastSampleEndedAt };
38
+ export function lastPerfSample(): { name: string; endedAt: number; metadata?: Record<string, unknown> } | null {
39
+ if (lastSampleName == null) return null;
40
+ return { name: lastSampleName, endedAt: lastSampleEndedAt, ...(lastSampleMetadata ? { metadata: lastSampleMetadata } : {}) };
41
+ }
42
+
43
+ /**
44
+ * Whether the last step ended inside a section that started at `startedAt`
45
+ * and took at least half of its `durationMs`. That step says more about the
46
+ * time than the section around it: a SQLite statement that held the thread
47
+ * through most of a window's request keeps its lock and try facts, where a
48
+ * short one before the request's real work does not.
49
+ */
50
+ function lastStepAccountsForSection(startedAt: number, durationMs: number): boolean {
51
+ return lastSampleName != null && lastSampleEndedAt >= startedAt && lastSampleDurationMs * 2 >= durationMs;
35
52
  }
36
53
 
37
54
  function logSlowPerfSample(
38
55
  name: string,
39
56
  durationMs: number,
40
57
  metadata?: Record<string, unknown>,
58
+ lastStep = true,
59
+ startedAt?: number,
41
60
  ): void {
42
- lastSampleName = name;
43
- lastSampleEndedAt = now();
61
+ if (lastStep && quietDepth === 0 && (startedAt === undefined || !lastStepAccountsForSection(startedAt, durationMs))) {
62
+ lastSampleName = name;
63
+ lastSampleEndedAt = now();
64
+ lastSampleDurationMs = durationMs;
65
+ lastSampleMetadata = metadata;
66
+ }
44
67
  const payload = {
45
68
  durationMs: Math.round(durationMs * 10) / 10,
46
69
  ...(metadata ?? {}),
@@ -56,19 +79,42 @@ function logSlowPerfSample(
56
79
  }
57
80
  }
58
81
 
82
+ /**
83
+ * Runs `fn` without any section it measures becoming the step a later stall
84
+ * names: reporting a stall reads the windows, and the next stall must still
85
+ * name the work before it, not the report.
86
+ */
87
+ export function withoutLastStep<T>(fn: () => T): T {
88
+ quietDepth += 1;
89
+ try {
90
+ return fn();
91
+ } finally {
92
+ quietDepth -= 1;
93
+ }
94
+ }
95
+
59
96
  export function isPerfTraceEnabled(): boolean {
60
97
  return traceToStderr;
61
98
  }
62
99
 
63
- /** A sample measured elsewhere (a React Profiler callback, for instance). */
100
+ /**
101
+ * A sample measured elsewhere (a React Profiler callback, for instance).
102
+ * `lastStep: false` logs it without making it the step a later stall names:
103
+ * the stall monitor's own samples, which measure no work.
104
+ */
64
105
  export function recordPerfSample(
65
106
  name: string,
66
107
  durationMs: number,
67
108
  metadata?: Record<string, unknown>,
109
+ options?: { lastStep?: boolean },
68
110
  ): void {
69
- logSlowPerfSample(name, durationMs, metadata);
111
+ logSlowPerfSample(name, durationMs, metadata, options?.lastStep !== false);
70
112
  }
71
113
 
114
+ /**
115
+ * Measures `fn`. A step measured inside it that took most of its time stays
116
+ * the step a stall names (`lastStepAccountsForSection`).
117
+ */
72
118
  export function measurePerf<T>(
73
119
  name: string,
74
120
  fn: () => T,
@@ -78,7 +124,7 @@ export function measurePerf<T>(
78
124
  try {
79
125
  return fn();
80
126
  } finally {
81
- logSlowPerfSample(name, now() - start, metadata);
127
+ logSlowPerfSample(name, now() - start, metadata, true, start);
82
128
  }
83
129
  }
84
130
 
@@ -46,6 +46,50 @@ export function pricePointIntegrityFlags(point: PricePoint): string[] {
46
46
  return flags;
47
47
  }
48
48
 
49
+ /**
50
+ * A wick too far from its bar's body to have traded: a low under half the lower
51
+ * of open and close, or a high over twice the higher, as `low<close/2` or
52
+ * `high>2*open` (IAG on LSE, 2021-11-29: a low of 0.0155 under a close of
53
+ * 1.316). Only that wick is withheld: the open and close stand, so closes,
54
+ * returns and drawdowns read the bar while lows, highs and ranges do not.
55
+ * Empty when the wicks are plausible, or when the bar's prices contradict
56
+ * each other, which blanks it whole (pricePointIntegrityFlags).
57
+ */
58
+ export function pricePointWickFlags(point: PricePoint): string[] {
59
+ const { open, high, low, close } = point;
60
+ if (pricePointIntegrityFlags(point).length > 0) return [];
61
+ const body = [["open", open], ["close", close]].filter((entry): entry is [string, number] => isFiniteNumber(entry[1]));
62
+ if (body.length === 0) return [];
63
+ // On a tie the close names the side, as the price the bar is read by.
64
+ const [bottomName, bottom] = body.reduce((least, entry) => entry[1] <= least[1] ? entry : least);
65
+ const [topName, top] = body.reduce((most, entry) => entry[1] >= most[1] ? entry : most);
66
+ const flags: string[] = [];
67
+ if (isFiniteNumber(low) && bottom > 0 && low < bottom / 2) flags.push(`low<${bottomName}/2`);
68
+ if (isFiniteNumber(high) && top > 0 && high > top * 2) flags.push(`high>2*${topName}`);
69
+ return flags;
70
+ }
71
+
72
+ /** Whether a flag code is a withheld wick (pricePointWickFlags), whose bar keeps its open and close. */
73
+ export function isWickFlag(code: string): boolean {
74
+ return /^(low<(open|close)\/2|high>2\*(open|close))$/.test(code);
75
+ }
76
+
77
+ /** A bar's high and low with a wick too far from its body to have traded (pricePointWickFlags) left out. */
78
+ export function pricePointWicks(point: PricePoint): { high: number | undefined; low: number | undefined } {
79
+ const flags = pricePointWickFlags(point);
80
+ return {
81
+ high: flags.some((flag) => flag.startsWith("high")) ? undefined : point.high,
82
+ low: flags.some((flag) => flag.startsWith("low")) ? undefined : point.low,
83
+ };
84
+ }
85
+
86
+ /** "1 bar has a low or high too far from its open and close to have traded; left out of ranges.", or undefined for none. */
87
+ export function priceHistoryWickNotice(count: number): string | undefined {
88
+ return count > 0
89
+ ? `${count === 1 ? "1 bar has a low or high" : `${count} bars have lows or highs`} too far from ${count === 1 ? "its" : "their"} open and close to have traded; left out of ranges.`
90
+ : undefined;
91
+ }
92
+
49
93
  export function pricePointIntegrity(point: PricePoint): PriceHistoryIntegrity | undefined {
50
94
  if (pricePointIntegrityFlags(point).length === 0) return undefined;
51
95
  const date = new Date(point.date);
@@ -58,8 +102,10 @@ export function pricePointIntegrity(point: PricePoint): PriceHistoryIntegrity |
58
102
  /** Calculation/display fields deliberately exclude the contradictory source row. */
59
103
  export function pricePointValues(point: PricePoint, integrity = pricePointIntegrity(point)) {
60
104
  const value = (number: number | undefined) => !integrity && isFiniteNumber(number) ? number : null;
105
+ // A wick too far from the body to have traded is left out; the rest of the bar stands.
106
+ const wicks = pricePointWicks(point);
61
107
  return {
62
- open: value(point.open), high: value(point.high), low: value(point.low),
108
+ open: value(point.open), high: value(wicks.high), low: value(wicks.low),
63
109
  close: value(point.close), volume: value(point.volume),
64
110
  ...(integrity ? { integrity } : {}),
65
111
  };
@@ -0,0 +1,141 @@
1
+ import { chmodSync, closeSync, constants, fchmodSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, writeSync } from "fs";
2
+ import { chmod, lstat, mkdir, open } from "fs/promises";
3
+
4
+ /**
5
+ * The profile keeps broker credentials (config.json), the Cloud session (the
6
+ * database), a remote-control token and logs of what the user looked at. On
7
+ * POSIX its folders are opened only by the user and its files read only by
8
+ * them, whatever the umask. Windows keeps the profile under the user's own
9
+ * folder, whose access list already does this, and has no such modes: there
10
+ * every call here only creates and writes.
11
+ */
12
+ const PRIVATE_DIR_MODE = 0o700;
13
+ const PRIVATE_FILE_MODE = 0o600;
14
+
15
+ function usesPosixModes(): boolean {
16
+ return process.platform !== "win32";
17
+ }
18
+
19
+ function errorCode(error: unknown): unknown {
20
+ return (error as { code?: unknown } | null)?.code;
21
+ }
22
+
23
+ /** Creates `path` and the folders above it as `mkdir -p` does, each one it creates open to the user alone. */
24
+ export function makePrivateDirSync(path: string): void {
25
+ mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });
26
+ }
27
+
28
+ export async function makePrivateDir(path: string): Promise<void> {
29
+ await mkdir(path, { recursive: true, mode: PRIVATE_DIR_MODE });
30
+ }
31
+
32
+ /** Leaves an existing folder open to the user alone; a missing one stays missing. */
33
+ export async function restrictDir(path: string): Promise<void> {
34
+ if (!usesPosixModes()) return;
35
+ try {
36
+ await chmod(path, PRIVATE_DIR_MODE);
37
+ } catch (error) {
38
+ if (errorCode(error) !== "ENOENT") throw error;
39
+ }
40
+ }
41
+
42
+ /** Leaves an existing file readable by the user alone; a missing one stays missing. */
43
+ export async function restrictFile(path: string): Promise<void> {
44
+ if (!usesPosixModes()) return;
45
+ try {
46
+ await chmod(path, PRIVATE_FILE_MODE);
47
+ } catch (error) {
48
+ if (errorCode(error) !== "ENOENT") throw error;
49
+ }
50
+ }
51
+
52
+ export function restrictFileSync(path: string): void {
53
+ if (!usesPosixModes()) return;
54
+ try {
55
+ chmodSync(path, PRIVATE_FILE_MODE);
56
+ } catch (error) {
57
+ if (errorCode(error) !== "ENOENT") throw error;
58
+ }
59
+ }
60
+
61
+ /** The code of the error a private write throws when its path names a device, a pipe or anything else but a plain file. */
62
+ export const NOT_A_PLAIN_FILE = "ENOTPLAINFILE";
63
+
64
+ function notAPlainFile(path: string): Error {
65
+ return Object.assign(new Error(`${path} is not a plain file.`), { code: NOT_A_PLAIN_FILE });
66
+ }
67
+
68
+ function isALink(path: string): Error {
69
+ return Object.assign(new Error(`${path} is a link.`), { code: "ELOOP" });
70
+ }
71
+
72
+ /**
73
+ * Opened to write without following a link (`O_NOFOLLOW`, which fails with
74
+ * ELOOP) or waiting on a pipe no one reads (`O_NONBLOCK`). Windows has
75
+ * neither flag; a link is refused there by looking first.
76
+ */
77
+ function privateOpenFlags(append: boolean): number {
78
+ return constants.O_WRONLY | constants.O_CREAT | (append ? constants.O_APPEND : 0)
79
+ | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);
80
+ }
81
+
82
+ function refuseWindowsLinkSync(path: string): void {
83
+ if (usesPosixModes()) return;
84
+ try {
85
+ if (lstatSync(path).isSymbolicLink()) throw isALink(path);
86
+ } catch (error) {
87
+ if (errorCode(error) !== "ENOENT") throw error;
88
+ }
89
+ }
90
+
91
+ async function refuseWindowsLink(path: string): Promise<void> {
92
+ if (usesPosixModes()) return;
93
+ try {
94
+ if ((await lstat(path)).isSymbolicLink()) throw isALink(path);
95
+ } catch (error) {
96
+ if (errorCode(error) !== "ENOENT") throw error;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Writes `contents` to a plain file only the user can read: created so, and
102
+ * an existing file made so before anything is written to it, so the new
103
+ * contents are never readable by anyone else. A link, a device or a pipe in
104
+ * its place is refused, never written through. `append` adds to the end.
105
+ */
106
+ export function writePrivateFileSync(path: string, contents: string | Uint8Array, options: { append?: boolean } = {}): void {
107
+ refuseWindowsLinkSync(path);
108
+ const fd = openSync(path, privateOpenFlags(options.append === true), PRIVATE_FILE_MODE);
109
+ try {
110
+ if (!fstatSync(fd).isFile()) throw notAPlainFile(path);
111
+ if (usesPosixModes()) {
112
+ try {
113
+ fchmodSync(fd, PRIVATE_FILE_MODE);
114
+ } catch {
115
+ // Intentional ignore: a disk without POSIX modes (FAT, some network shares) cannot restrict the file; the write still goes through.
116
+ }
117
+ }
118
+ if (!options.append) ftruncateSync(fd, 0);
119
+ const bytes = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
120
+ for (let written = 0; written < bytes.length;) written += writeSync(fd, bytes, written);
121
+ } finally {
122
+ closeSync(fd);
123
+ }
124
+ }
125
+
126
+ export async function writePrivateFile(path: string, contents: string | Uint8Array): Promise<void> {
127
+ await refuseWindowsLink(path);
128
+ const handle = await open(path, privateOpenFlags(false), PRIVATE_FILE_MODE);
129
+ try {
130
+ if (!(await handle.stat()).isFile()) throw notAPlainFile(path);
131
+ if (usesPosixModes()) {
132
+ await handle.chmod(PRIVATE_FILE_MODE).catch(() => {
133
+ // Intentional ignore: a disk without POSIX modes (FAT, some network shares) cannot restrict the file; the write still goes through.
134
+ });
135
+ }
136
+ await handle.truncate(0);
137
+ await handle.writeFile(contents);
138
+ } finally {
139
+ await handle.close();
140
+ }
141
+ }
@@ -0,0 +1,99 @@
1
+ import { isIP } from "net";
2
+
3
+ /**
4
+ * Whether an IP address is on the public internet: not this machine, not a
5
+ * private or link-local network, not a carrier-grade NAT (a VPN's own
6
+ * addresses), documentation, multicast or reserved block. A request made for
7
+ * a page that only reads public data (the pane screenshot's HTTP bridge) is
8
+ * refused for anything else, so a URL it is handed cannot reach a service on
9
+ * the user's machine or network.
10
+ */
11
+ export function isPublicAddress(address: string): boolean {
12
+ const bare = address.replace(/^\[|\]$/g, "").replace(/%.*$/, "");
13
+ const family = isIP(bare);
14
+ if (family === 4) return isPublicIpv4(parseIpv4(bare)!);
15
+ if (family === 6) {
16
+ const groups = parseIpv6(bare);
17
+ return groups !== null && isPublicIpv6(groups);
18
+ }
19
+ return false;
20
+ }
21
+
22
+ function parseIpv4(address: string): number[] | null {
23
+ const parts = address.split(".").map(Number);
24
+ return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
25
+ }
26
+
27
+ /** [first octet, second octet, prefix bits of the second octet that count] per non-public block. */
28
+ const IPV4_BLOCKS: ReadonlyArray<readonly [number, number, number]> = [
29
+ [0, 0, 0], // 0.0.0.0/8, this network
30
+ [10, 0, 0], // 10.0.0.0/8, private
31
+ [100, 64, 2], // 100.64.0.0/10, carrier-grade NAT
32
+ [127, 0, 0], // 127.0.0.0/8, loopback
33
+ [169, 254, 8], // 169.254.0.0/16, link-local
34
+ [172, 16, 4], // 172.16.0.0/12, private
35
+ [192, 168, 8], // 192.168.0.0/16, private
36
+ // 198.18.0.0/15 (benchmarking) stays public: proxies that answer every name with a stand-in address use it.
37
+ ];
38
+
39
+ function isPublicIpv4([a, b, c]: number[]): boolean {
40
+ for (const [first, second, bits] of IPV4_BLOCKS) {
41
+ if (a !== first) continue;
42
+ if (bits === 0) return false;
43
+ const mask = (0xff << (8 - bits)) & 0xff;
44
+ if ((b! & mask) === second) return false;
45
+ }
46
+ // 192.0.0.0/24 protocol assignments, 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 documentation.
47
+ if (a === 192 && b === 0 && (c === 0 || c === 2)) return false;
48
+ if (a === 198 && b === 51 && c === 100) return false;
49
+ if (a === 203 && b === 0 && c === 113) return false;
50
+ // 224.0.0.0/4 multicast, 240.0.0.0/4 reserved and the broadcast address.
51
+ return a! < 224;
52
+ }
53
+
54
+ /** Eight 16-bit groups, or null when the text is not one IPv6 address. */
55
+ function parseIpv6(address: string): number[] | null {
56
+ let text = address.toLowerCase();
57
+ const tail: number[] = [];
58
+ const dotted = /:(\d+\.\d+\.\d+\.\d+)$/.exec(text);
59
+ if (dotted) {
60
+ const v4 = parseIpv4(dotted[1]!);
61
+ if (!v4) return null;
62
+ tail.push((v4[0]! << 8) | v4[1]!, (v4[2]! << 8) | v4[3]!);
63
+ // `::1.2.3.4` keeps its `::`; `1:2:3:4:5:6:1.2.3.4` loses the colon before the dotted part.
64
+ const before = text.slice(0, dotted.index);
65
+ text = before.endsWith(":") ? `${before}:` : before;
66
+ }
67
+ const halves = text.split("::");
68
+ if (halves.length > 2) return null;
69
+ const read = (half: string) => (half ? half.split(":").map((group) => Number.parseInt(group, 16)) : []);
70
+ const head = read(halves[0]!);
71
+ const rest = halves.length === 2 ? read(halves[1]!) : [];
72
+ const known = head.length + rest.length + tail.length;
73
+ if (halves.length === 1 && known !== 8) return null;
74
+ if (halves.length === 2 && known > 7) return null;
75
+ const groups = [...head, ...Array(8 - known).fill(0), ...rest, ...tail];
76
+ return groups.every((group) => Number.isInteger(group) && group >= 0 && group <= 0xffff) ? groups : null;
77
+ }
78
+
79
+ function embeddedIpv4(high: number, low: number): number[] {
80
+ return [high >> 8, high & 0xff, low >> 8, low & 0xff];
81
+ }
82
+
83
+ function isPublicIpv6(groups: number[]): boolean {
84
+ const [g0, g1, g2, g3, g4, g5, g6, g7] = groups as [number, number, number, number, number, number, number, number];
85
+ // ::ffff:a.b.c.d, an IPv4 address written as IPv6: as public as the address it carries.
86
+ if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0xffff) return isPublicIpv4(embeddedIpv4(g6, g7));
87
+ // ::, ::1 and the retired IPv4-compatible block.
88
+ if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) return false;
89
+ // 64:ff9b::/96 NAT64 carries an IPv4 address; 64:ff9b:1::/48 is a local NAT64.
90
+ if (g0 === 0x64 && g1 === 0xff9b) return g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && isPublicIpv4(embeddedIpv4(g6, g7));
91
+ // 2002::/16 6to4 carries an IPv4 address in its next 32 bits.
92
+ if (g0 === 0x2002) return isPublicIpv4(embeddedIpv4(g1, g2));
93
+ // 100::/64 discard.
94
+ if (g0 === 0x100 && g1 === 0 && g2 === 0 && g3 === 0) return false;
95
+ // 2001::/23 protocol assignments (Teredo among them) and 2001:db8::/32 documentation.
96
+ if (g0 === 0x2001 && (g1 < 0x200 || g1 === 0xdb8)) return false;
97
+ // fc00::/7 unique local, fe80::/10 link-local, fec0::/10 site-local, ff00::/8 multicast.
98
+ return (g0 & 0xfe00) !== 0xfc00 && (g0 & 0xffc0) !== 0xfe80 && (g0 & 0xffc0) !== 0xfec0 && (g0 & 0xff00) !== 0xff00;
99
+ }