@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
@@ -1,4 +1,5 @@
1
1
  import { classifyInstrumentType, isCryptoVenuePerpetual } from "../../tickers/instrument-kind";
2
+ import type { PriceBasis } from "../../types/instrument";
2
3
 
3
4
  /**
4
5
  * The unit an index level is in, as a chart's axis and series name it. A
@@ -28,11 +29,12 @@ export function quotePriceCurrency(quote: { currency?: string | null; instrument
28
29
  }
29
30
 
30
31
  /**
31
- * What a price chart of the instrument reads in: points for an index level,
32
- * otherwise its currency, so an axis never puts a dollar sign on ^VIX.
32
+ * What a price chart reads in: the quote's price basis, points for an index
33
+ * level, otherwise its currency.
33
34
  */
34
- export function priceSeriesUnit(currency: string, instrumentType: string | null | undefined): string {
35
- return isIndexLevel(instrumentType) ? INDEX_POINTS_UNIT : currency;
35
+ export function priceSeriesUnit(currency: string, instrumentType: string | null | undefined, priceBasis?: PriceBasis): string {
36
+ if (priceBasis === "thirty-seconds") return "32nds";
37
+ return priceBasis === "points" || isIndexLevel(instrumentType) ? INDEX_POINTS_UNIT : currency;
36
38
  }
37
39
 
38
40
  /**
@@ -21,6 +21,9 @@
21
21
  // EGX closes on Egypt's public holidays, often moved to the Thursday of their
22
22
  // week (Armed Forces Day, Tuesday 6 Oct 2026, to Thursday 8 Oct, by the
23
23
  // exchange's statement of 5 Oct). Its week is Sunday to Thursday.
24
+ // JSE closes on South Africa's public holidays, a Sunday one moving to the Monday (Women's Day, 9 Aug 2026):
25
+ // JSE Market Notice 380/25, JSE Markets Calendar 2026 (clientportal.jse.co.za), and the
26
+ // local government election day, Wed 4 Nov 2026, declared a public holiday on 15 Sep 2026 (gov.za).
24
27
  const KRX_CLOSURES: Record<number, readonly string[]> = {
25
28
  2026: [
26
29
  "01-01", "02-16", "02-17", "02-18", "03-02", "05-01", "05-05", "05-25", "06-03",
@@ -79,6 +82,9 @@ export const PUBLISHED_APAC_CLOSURES: Readonly<Record<string, Record<number, rea
79
82
  2026: ["01-01", "01-26", "04-03", "04-06", "06-08", "12-25", "12-28"],
80
83
  2027: ["01-01", "01-26", "03-26", "03-29", "06-14", "12-27", "12-28"],
81
84
  },
85
+ JSE: {
86
+ 2026: ["01-01", "04-03", "04-06", "04-27", "05-01", "06-16", "08-10", "09-24", "11-04", "12-16", "12-25"],
87
+ },
82
88
  EGX: {
83
89
  2026: [
84
90
  "01-01", "01-07", "01-29", "03-19", "03-22", "03-23", "04-12", "04-13", "05-07",
@@ -3,6 +3,7 @@ import { barsPerYear } from "./bars-per-year";
3
3
  import {
4
4
  mergePriceHistoryIntegrity,
5
5
  pricePointIntegrity,
6
+ pricePointWicks,
6
7
  type PriceHistoryIntegrity,
7
8
  } from "../utils/price-history-integrity";
8
9
 
@@ -115,8 +116,9 @@ function observations(points: readonly PricePoint[]): Observation[] | null {
115
116
  time,
116
117
  close: positiveLog(point.close),
117
118
  open: positiveLog(point.open),
118
- high: positiveLog(point.high),
119
- low: positiveLog(point.low),
119
+ // A wick too far from the bar's body to have traded is missing, not a range.
120
+ high: positiveLog(pricePointWicks(point).high),
121
+ low: positiveLog(pricePointWicks(point).low),
120
122
  integrity: pricePointIntegrity(point),
121
123
  }));
122
124
  }
@@ -17,6 +17,7 @@ import {
17
17
  normalizeNewsFeed,
18
18
  normalizeNewsQuery,
19
19
  } from "./news-model";
20
+ import { storyIdMatches } from "./story-id";
20
21
 
21
22
  export { buildNewsQueryKey } from "./news-model";
22
23
 
@@ -343,16 +344,17 @@ export class NewsService {
343
344
  const sources = this.enabledSources({ feed: "latest" })
344
345
  .filter((source) => !!source.provider.fetchNewsStory);
345
346
 
347
+ // A short id (`6067684e`, as `news` prints it) names the story whose id starts with it.
346
348
  const requestedRevisions = new Map<NewsQueryEntry, string>();
347
349
  for (const entry of this.queries.values()) {
348
- const article = entry.state.articles.find((article) => article.id === storyId);
350
+ const article = entry.state.articles.find((article) => storyIdMatches(article.id, storyId));
349
351
  if (article) requestedRevisions.set(entry, newsArticleRevision(article));
350
352
  }
351
353
  let failed = false;
352
354
  let olderDetail: NewsArticle | null = null;
353
355
  const commitDetail = (article: NewsArticle) => {
354
356
  this.mergeStoryDetail(article, requestedRevisions);
355
- return this.articles.find((current) => current.id === storyId) ?? article;
357
+ return this.articles.find((current) => current.id === article.id) ?? article;
356
358
  };
357
359
  for (const source of sources) {
358
360
  try {
@@ -362,8 +364,9 @@ export class NewsService {
362
364
  () => source.provider.fetchNewsStory?.(storyId) ?? Promise.resolve(null),
363
365
  );
364
366
  if (!article) continue;
365
- if (article.id !== storyId) throw new Error("Story detail identity mismatch.");
366
- const current = this.articles.find((current) => current.id === storyId);
367
+ // The source answers a short id with the full id of the story it starts.
368
+ if (!storyIdMatches(article.id, storyId)) throw new Error("Story detail identity mismatch.");
369
+ const current = this.articles.find((current) => current.id === article.id);
367
370
  if (current && article.publishedAt < current.publishedAt) {
368
371
  if (!olderDetail || article.publishedAt > olderDetail.publishedAt) olderDetail = article;
369
372
  continue;
@@ -245,3 +245,14 @@ export function filterNewsArticlesForQuery(items: NewsArticle[], query: NewsQuer
245
245
  // Page size is a fetch hint. Accumulated load-more results keep MAX_ARTICLES.
246
246
  return filtered.slice(0, MAX_ARTICLES);
247
247
  }
248
+
249
+ /**
250
+ * A story's summary as one line under its row in a text list (`news`,
251
+ * `fn CN`): its whitespace and line breaks run together, null when it has
252
+ * none or only repeats the headline.
253
+ */
254
+ export function newsSummaryLine(article: Pick<NewsArticle, "title" | "summary">): string | null {
255
+ const summary = article.summary?.replace(/\s+/g, " ").trim();
256
+ if (!summary || summary.toLowerCase() === article.title.trim().toLowerCase()) return null;
257
+ return summary;
258
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A Gloom Cloud story's short id: the first eight characters of its id
3
+ * (`6067684e` for `6067684e-2e13-41c5-a7cf-5052b3cefc07`), which `news`
4
+ * prints on each row and the story pane opens (`fn news-story-pane 6067684e`).
5
+ * Gloom Cloud opens the one story whose id starts with it.
6
+ */
7
+ const SHORT_STORY_ID_LENGTH = 8;
8
+
9
+ const STORY_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10
+ const SHORT_ID = /^[0-9a-f]{8}$/i;
11
+
12
+ /** The id a row prints: a Gloom Cloud story's short id, any other source's id as it is. */
13
+ export function shortStoryId(id: string): string {
14
+ return STORY_UUID.test(id) ? id.slice(0, SHORT_STORY_ID_LENGTH).toLowerCase() : id;
15
+ }
16
+
17
+ /** Whether `id` is the story `wanted` names, by its full id or its short one, in any case. */
18
+ export function storyIdMatches(id: string, wanted: string): boolean {
19
+ const typed = wanted.trim().toLowerCase();
20
+ const own = id.toLowerCase();
21
+ return own === typed || (SHORT_ID.test(typed) && STORY_UUID.test(own) && own.startsWith(typed));
22
+ }
@@ -1,7 +1,23 @@
1
+ import { isPredictionMarketExchange, isUsListingExchange, parsePublicTickerKey } from "../utils/exchanges";
2
+
3
+ /** US over-the-counter venues: OTC Markets' tiers (OTC, OTCM, OTCQX...), the Pink market (PINX) and the Expert Market (EXPM). */
4
+ const OTC_VENUE = /^(?:OTC|PINX|PINK|PNK|EXPM)/;
5
+
6
+ /**
7
+ * A story's tag for a listing: the bare symbol on a US venue (or over the
8
+ * counter) and for a coin, the symbol and its venue elsewhere. PIK on the JSE
9
+ * is Pick n Pay, and bare PIK opens Kidpik on NASDAQ.
10
+ */
1
11
  function newsDisplayTickerSymbol(value: string | null | undefined): string | null {
2
12
  const normalized = value?.trim().toUpperCase();
3
13
  if (!normalized) return null;
4
- return normalized.split(":")[0] || null;
14
+ const listing = parsePublicTickerKey(normalized);
15
+ const venue = listing.exchange ?? "";
16
+ if (!listing.symbol) return null;
17
+ if (isPredictionMarketExchange(venue)) return normalized;
18
+ return !venue || isUsListingExchange(venue) || OTC_VENUE.test(venue) || venue === "CRYPTO"
19
+ ? listing.symbol
20
+ : `${listing.symbol}:${venue}`;
5
21
  }
6
22
 
7
23
  export function collectNewsDisplayTickers(
@@ -16,14 +16,17 @@ function getRuntimePlatform(): NodeJS.Platform {
16
16
  export interface DesktopNotificationCommand {
17
17
  command: string;
18
18
  args: string[];
19
+ /** Added to the environment of the process, for text a command line must not carry. */
20
+ env?: Record<string, string>;
21
+ }
22
+
23
+ interface DesktopNotificationRunOptions {
24
+ env?: Record<string, string>;
25
+ onError?: (error: NodeJS.ErrnoException) => void;
19
26
  }
20
27
 
21
28
  export interface DesktopNotificationRunner {
22
- run(
23
- command: string,
24
- args: string[],
25
- handlers?: { onError?: (error: NodeJS.ErrnoException) => void },
26
- ): void;
29
+ run(command: string, args: string[], options?: DesktopNotificationRunOptions): void;
27
30
  }
28
31
 
29
32
  export interface DesktopNotificationSink {
@@ -46,8 +49,14 @@ function escapeAppleScriptString(value: string): string {
46
49
  .replace(/"/g, "\\\"")}"`;
47
50
  }
48
51
 
49
- function escapePowerShellString(value: string): string {
50
- return `'${value.replace(/'/g, "''")}'`;
52
+ const WINDOWS_NOTIFICATION_TITLE_ENV = "GLOOM_NOTIFICATION_TITLE";
53
+ const WINDOWS_NOTIFICATION_TEXT_ENV = "GLOOM_NOTIFICATION_TEXT";
54
+ /** The most of a title or a body handed to Windows; a balloon shows far less, and an environment value has a hard limit. */
55
+ const WINDOWS_NOTIFICATION_TEXT_LIMIT = 4_000;
56
+
57
+ /** NUL ends an environment value early, and spawning with one in it fails. */
58
+ function windowsEnvText(value: string): string {
59
+ return value.replaceAll("\0", "").slice(0, WINDOWS_NOTIFICATION_TEXT_LIMIT);
51
60
  }
52
61
 
53
62
  function mapLinuxUrgency(type: AppNotificationType | undefined): "low" | "normal" | "critical" {
@@ -105,6 +114,8 @@ export function buildDesktopNotificationCommand(
105
114
  DEFAULT_NOTIFICATION_TITLE,
106
115
  "--urgency",
107
116
  mapLinuxUrgency(notification.type),
117
+ // Text that starts with `-` is the title or the body, not an option.
118
+ "--",
108
119
  title,
109
120
  ...(linuxBody ? [linuxBody] : []),
110
121
  ],
@@ -112,14 +123,16 @@ export function buildDesktopNotificationCommand(
112
123
  }
113
124
 
114
125
  if (platform === "win32") {
115
- const balloonBody = buildWindowsBody(notification);
126
+ // The script is the same for every notification. The title and the text
127
+ // reach it in the environment of the process, where PowerShell reads them
128
+ // as plain strings and never as code, whatever characters they hold.
116
129
  const script = [
117
130
  "Add-Type -AssemblyName System.Windows.Forms;",
118
131
  "Add-Type -AssemblyName System.Drawing;",
119
132
  "$notify = New-Object System.Windows.Forms.NotifyIcon;",
120
133
  `$notify.Icon = [System.Drawing.SystemIcons]::${mapWindowsIcon(notification.type)};`,
121
- `$notify.BalloonTipTitle = ${escapePowerShellString(title)};`,
122
- `$notify.BalloonTipText = ${escapePowerShellString(balloonBody)};`,
134
+ `$notify.BalloonTipTitle = $env:${WINDOWS_NOTIFICATION_TITLE_ENV};`,
135
+ `$notify.BalloonTipText = $env:${WINDOWS_NOTIFICATION_TEXT_ENV};`,
123
136
  "$notify.Visible = $true;",
124
137
  "$notify.ShowBalloonTip(5000);",
125
138
  "Start-Sleep -Seconds 6;",
@@ -128,6 +141,10 @@ export function buildDesktopNotificationCommand(
128
141
  return {
129
142
  command: "powershell.exe",
130
143
  args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
144
+ env: {
145
+ [WINDOWS_NOTIFICATION_TITLE_ENV]: windowsEnvText(title),
146
+ [WINDOWS_NOTIFICATION_TEXT_ENV]: windowsEnvText(buildWindowsBody(notification)),
147
+ },
131
148
  };
132
149
  }
133
150
 
@@ -137,19 +154,22 @@ export function buildDesktopNotificationCommand(
137
154
  function defaultDesktopNotificationRunner(
138
155
  command: string,
139
156
  args: string[],
140
- handlers?: { onError?: (error: NodeJS.ErrnoException) => void },
157
+ options?: DesktopNotificationRunOptions,
141
158
  ): void {
142
159
  if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
143
160
  try {
144
- const child = Bun.spawn([command, ...args], { stdio: ["ignore", "ignore", "ignore"] });
161
+ const child = Bun.spawn([command, ...args], {
162
+ stdio: ["ignore", "ignore", "ignore"],
163
+ ...(options?.env ? { env: { ...process.env, ...options.env } } : {}),
164
+ });
145
165
  child.unref();
146
166
  } catch (error) {
147
- handlers?.onError?.(error as NodeJS.ErrnoException);
167
+ options?.onError?.(error as NodeJS.ErrnoException);
148
168
  }
149
169
  return;
150
170
  }
151
171
 
152
- handlers?.onError?.(Object.assign(new Error("Desktop notifications require a native process host."), {
172
+ options?.onError?.(Object.assign(new Error("Desktop notifications require a native process host."), {
153
173
  code: "ENOSYS",
154
174
  }) as NodeJS.ErrnoException);
155
175
  }
@@ -203,6 +223,7 @@ export function createDesktopNotifier(
203
223
  if (desktopCommand && disabledCommand !== desktopCommand.command) {
204
224
  let requestFailed = false;
205
225
  runner.run(desktopCommand.command, desktopCommand.args, {
226
+ env: desktopCommand.env,
206
227
  onError: (error) => {
207
228
  requestFailed = true;
208
229
  if (error.code === "ENOENT") {
@@ -37,24 +37,28 @@ function readJson(path: string): Record<string, unknown> | null {
37
37
  }
38
38
 
39
39
  async function readCheckouts(): Promise<PluginCheckout[]> {
40
- const { installedPluginDirectories, readPluginRemote } = await installer();
40
+ const { installedPluginDirectories, readPluginRemoteAsync } = await installer();
41
41
  const pluginsDir = getPluginsDir();
42
- return installedPluginDirectories().map((directory) => {
42
+ const checkouts: PluginCheckout[] = [];
43
+ // One git at a time: starting a process holds the thread for a moment, and
44
+ // starting them all at once adds those moments up into one pause.
45
+ for (const directory of installedPluginDirectories()) {
43
46
  const dir = join(pluginsDir, directory);
44
47
  const pkg = readJson(join(dir, "package.json"));
45
48
  const dependencies = pkg?.dependencies;
46
- return {
49
+ checkouts.push({
47
50
  directory,
48
51
  id: readPluginManifest(dir).id,
49
- repo: existsSync(join(dir, ".git")) ? readPluginRemote(dir) : null,
52
+ repo: existsSync(join(dir, ".git")) ? await readPluginRemoteAsync(dir) : null,
50
53
  commit: readPluginCommit(dir),
51
54
  version: typeof pkg?.version === "string" ? pkg.version : null,
52
55
  linked: lstatSync(dir, { throwIfNoEntry: false })?.isSymbolicLink() === true,
53
56
  needsGloomberb: checkPluginCompatibility(dir)?.needsGloomberb ?? null,
54
57
  peers: installedPeerPlugins(dir, pluginsDir).map((peer) => peer.directory),
55
58
  hasDependencies: !!dependencies && typeof dependencies === "object" && Object.keys(dependencies).length > 0,
56
- };
57
- });
59
+ });
60
+ }
61
+ return checkouts;
58
62
  }
59
63
 
60
64
  function createNodeAutoUpdateDeps(manager: PluginManager): PluginAutoUpdateDeps {
@@ -157,7 +157,7 @@ export function PublicAnalyticsGroup({
157
157
 
158
158
  /**
159
159
  * The desks chat shows beside your name, once What I Trade is on: pre-filled
160
- * from your answer to "What do you trade?", coarse words only.
160
+ * from your answer to "What do you trade most?", coarse words only.
161
161
  */
162
162
  export function DeskChoiceRow({
163
163
  desks,
@@ -1,5 +1,4 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
- import { AccountManagementPane } from "./pane";
3
2
 
4
3
  export const accountManagementModule: PluginModule = {
5
4
  panes: [{
@@ -7,8 +6,8 @@ export const accountManagementModule: PluginModule = {
7
6
  name: "ACM",
8
7
  icon: "A",
9
8
  personalData: "account",
10
- component: AccountManagementPane,
11
- reportFreshness: { status: "not-a-feed", basis: "account data" },
9
+ get component(): typeof import("./pane").AccountManagementPane { const { AccountManagementPane } = require("./pane"); return AccountManagementPane; },
10
+ reportFreshness: { status: "not-a-feed", basis: "account data", local: true },
12
11
  defaultPosition: "right",
13
12
  defaultMode: "floating",
14
13
  defaultFloatingSize: { width: 72, height: 36 },
@@ -44,7 +44,7 @@ export interface AccountDraft {
44
44
  showLocalTime: boolean;
45
45
  /** Chat shows what you trade: off until you turn it on. */
46
46
  showDesks: boolean;
47
- /** The desks to show, pre-filled from your answer to "What do you trade?". */
47
+ /** The desks to show, pre-filled from your answer to "What do you trade most?". */
48
48
  desks: string[];
49
49
  weeklyRoundupEnabled: boolean;
50
50
  positionAlertsEnabled: boolean;
@@ -33,6 +33,7 @@ export const ALERT_ADD_FORMS: readonly AlertAddForm[] = [
33
33
  { name: "short interest", usage: "<symbol> short interest [increase|decrease] [<percent>%]" },
34
34
  { name: "analyst change", usage: "<symbol> analyst [upgrade|downgrade]" },
35
35
  { name: "hiring move", usage: "<symbol> hiring [accelerating|slowing]" },
36
+ { name: "IPO", usage: "ipo <company or keyword>" },
36
37
  { name: "perpetual market", usage: "perps <market> <funding|oi|premium|closed-premium> <above|below> <percent>" },
37
38
  { name: "catalyst", usage: "catalyst [type <type> | agency <agency> | country <code>]" },
38
39
  { name: "catalyst", usage: "<symbol> catalyst" },
@@ -173,6 +174,17 @@ function perpsRule(words: readonly string[]): RuleAddRequest {
173
174
  return { kind: "rule", event: "perps_threshold", symbol: null, listing: "any", values: { marketId, metric, direction, threshold } };
174
175
  }
175
176
 
177
+ /**
178
+ * `ipo Stripe`, `ipo "Space Exploration"`: a company or keyword on the IPO calendar, followed until its deal
179
+ * is on the calendar, dated, priced and trading. Kept as typed, 2 to 80 characters, as the form takes it.
180
+ */
181
+ function ipoRule(words: readonly string[]): RuleAddRequest {
182
+ const keyword = words.join(" ").trim().replace(/\s+/g, " ");
183
+ if (!keyword) throw new Error("Name the company or a keyword: gloom alerts add ipo Stripe.");
184
+ if (keyword.length < 2 || keyword.length > 80) throw new Error(`An IPO alert follows a company or keyword of 2 to 80 characters, not "${keyword}": gloom alerts add ipo Stripe.`);
185
+ return { kind: "rule", event: "ipo", symbol: null, listing: "any", values: { keyword } };
186
+ }
187
+
176
188
  /** `catalyst`, `catalyst type clinical`, `catalyst agency FDA`, `catalyst country US`: the catalysts followed with no company. */
177
189
  function catalystRule(words: readonly string[]): RuleAddRequest {
178
190
  if (words.length === 0) return { kind: "rule", event: "catalyst-watched", symbol: null, listing: "any", values: {} };
@@ -192,7 +204,7 @@ function catalystRule(words: readonly string[]): RuleAddRequest {
192
204
  * The forms of `alerts add` for the rest of what Add Event Alert offers:
193
205
  * 52-week range, unusual volume, filings, insider trades, short interest,
194
206
  * analyst changes and hiring on a symbol, perpetual markets, catalysts,
195
- * Congress trades and 13F filings. Null for any other words; throws, naming
207
+ * IPOs, Congress trades and 13F filings. Null for any other words; throws, naming
196
208
  * the form, when one of them is mistyped. A symbol that spells a form word
197
209
  * (`PERPS above 10`) is still a price alert.
198
210
  */
@@ -202,6 +214,8 @@ export function parseRuleAddArgs(args: readonly string[]): RuleAddRequest | null
202
214
  if (!CONDITION_WORDS.has(word(rest[0]))) {
203
215
  if (lead === "perps" || lead === "perp") return perpsRule(rest);
204
216
  if (lead === "catalyst" || lead === "catalysts") return catalystRule(rest);
217
+ // The IPO ETF keeps its other alerts written with its venue (IPO:NYSEARCA 52-week high).
218
+ if (lead === "ipo") return ipoRule(rest);
205
219
  if (lead === "congress") {
206
220
  const member = rest.join(" ").trim();
207
221
  return { kind: "rule", event: member ? "congress-member" : "congress-watched", symbol: null, listing: "any", values: member ? { value: member } : {} };
@@ -17,6 +17,7 @@ import { usePluginAppActions, usePluginConfigState, usePluginState } from "../..
17
17
  import {
18
18
  CALENDAR_ALERTS_KEY,
19
19
  EVENT_ALERTS_KEY,
20
+ IPO_ALERTS_KEY,
20
21
  IV_LEVEL_ALERTS_KEY,
21
22
  eventAlertStoreKey,
22
23
  eventAlertTarget,
@@ -54,12 +55,13 @@ const columnsFor = (width: number, lastCheck: boolean, needsSignIn: boolean): Da
54
55
  export function EventAlertsPane({ focused, width, height }: PaneProps) {
55
56
  const [catalystStatus] = usePluginConfigState<string>(CATALYST_POLL_STATUS, "Catalyst alerts check every five minutes while the app is open");
56
57
  const [json, setJson] = usePluginConfigState<string>(EVENT_ALERTS_KEY, "[]");
57
- // IV levels and calendar events are kept in their own lists, which released versions never read.
58
+ // IV levels, calendar events and IPOs are kept in their own lists, which released versions never read.
58
59
  const [ivJson, setIvJson] = usePluginConfigState<string>(IV_LEVEL_ALERTS_KEY, "[]");
59
60
  const [calendarJson, setCalendarJson] = usePluginConfigState<string>(CALENDAR_ALERTS_KEY, "[]");
61
+ const [ipoJson, setIpoJson] = usePluginConfigState<string>(IPO_ALERTS_KEY, "[]");
60
62
  const { rules, error } = useMemo(
61
- () => readEventAlertStores((key) => key === IV_LEVEL_ALERTS_KEY ? ivJson : key === CALENDAR_ALERTS_KEY ? calendarJson : json),
62
- [json, ivJson, calendarJson],
63
+ () => readEventAlertStores((key) => key === IV_LEVEL_ALERTS_KEY ? ivJson : key === CALENDAR_ALERTS_KEY ? calendarJson : key === IPO_ALERTS_KEY ? ipoJson : json),
64
+ [json, ivJson, calendarJson, ipoJson],
63
65
  );
64
66
  // Their readings stay on this device.
65
67
  const [savedChecks] = usePluginState<unknown>(IV_LEVEL_CHECKS_KEY, {});
@@ -70,9 +72,9 @@ export function EventAlertsPane({ focused, width, height }: PaneProps) {
70
72
  const updateRules = useCallback(
71
73
  (rule: EventAlertRule, update: (rules: EventAlertRule[]) => EventAlertRule[]) => {
72
74
  const key = eventAlertStoreKey(rule);
73
- (key === IV_LEVEL_ALERTS_KEY ? setIvJson : key === CALENDAR_ALERTS_KEY ? setCalendarJson : setJson)((current) => updateEventAlerts(current, update));
75
+ (key === IV_LEVEL_ALERTS_KEY ? setIvJson : key === CALENDAR_ALERTS_KEY ? setCalendarJson : key === IPO_ALERTS_KEY ? setIpoJson : setJson)((current) => updateEventAlerts(current, update));
74
76
  },
75
- [setJson, setIvJson, setCalendarJson],
77
+ [setJson, setIvJson, setCalendarJson, setIpoJson],
76
78
  );
77
79
  const [selected, setSelected] = useState(0);
78
80
  const [sort, setSort] = useState<{ columnId: string; direction: SortDirection }>({ columnId: "created", direction: "desc" });
@@ -16,14 +16,22 @@ export const IV_LEVEL_ALERTS_KEY = "ivLevelAlerts";
16
16
  * every version reads.
17
17
  */
18
18
  export const CALENDAR_ALERTS_KEY = "calendarAlerts";
19
+ /**
20
+ * IPO rules, kept apart for the same reason. Gloom Cloud checks them, so it
21
+ * reads this list beside `eventAlerts`.
22
+ */
23
+ export const IPO_ALERTS_KEY = "ipoAlerts";
19
24
  export const MAX_EVENT_ALERTS = 40;
20
25
 
21
26
  /** The config key a rule is kept under. */
22
27
  export const eventAlertStoreKey = (rule: Pick<EventAlertRule, "kind">) =>
23
- rule.kind === "iv_level" ? IV_LEVEL_ALERTS_KEY : rule.kind === "calendar_event" ? CALENDAR_ALERTS_KEY : EVENT_ALERTS_KEY;
28
+ rule.kind === "iv_level" ? IV_LEVEL_ALERTS_KEY
29
+ : rule.kind === "calendar_event" ? CALENDAR_ALERTS_KEY
30
+ : rule.kind === "ipo" ? IPO_ALERTS_KEY
31
+ : EVENT_ALERTS_KEY;
24
32
 
25
33
  /** Every config key event alerts are kept under. */
26
- export const EVENT_ALERT_STORE_KEYS = [EVENT_ALERTS_KEY, IV_LEVEL_ALERTS_KEY, CALENDAR_ALERTS_KEY] as const;
34
+ export const EVENT_ALERT_STORE_KEYS = [EVENT_ALERTS_KEY, IV_LEVEL_ALERTS_KEY, CALENDAR_ALERTS_KEY, IPO_ALERTS_KEY] as const;
27
35
  type EventAlertStoreKey = (typeof EVENT_ALERT_STORE_KEYS)[number];
28
36
 
29
37
  export interface EventAlertRule {
@@ -38,9 +38,8 @@ import {
38
38
  parseAlertCommandValues,
39
39
  parseAlertShortcutValues,
40
40
  } from "./command";
41
- import { POLL_INTERVAL_MS, POLL_SECONDS_KEY } from "./constants";
41
+ import { ALERTS_PLUGIN_ID, POLL_INTERVAL_MS, POLL_SECONDS_KEY } from "./constants";
42
42
  import { alertHistoryLockNote, alertsHeadless } from "./headless";
43
- import { AlertsPane } from "./pane";
44
43
  import { alertsCliCommand } from "./cli";
45
44
  import {
46
45
  createQuoteErrorMessage,
@@ -81,7 +80,7 @@ function slashSpellsListing(symbol: string): boolean {
81
80
  }
82
81
 
83
82
  export const alertsPlugin: GloomPlugin = {
84
- id: "alerts",
83
+ id: ALERTS_PLUGIN_ID,
85
84
  name: "Alerts",
86
85
  version: "1.0.0",
87
86
  description: "Price, valuation, daily technical and event alerts",
@@ -278,11 +277,12 @@ export const alertsPlugin: GloomPlugin = {
278
277
  ctx.registerCommand({
279
278
  id: "set-event-alert",
280
279
  label: "Add Event Alert",
281
- description: "Create an alert before earnings, CPI or FOMC, when earnings results are out, or for filings, news, IV levels, hiring and market moves",
280
+ description: "Create an alert before earnings, CPI or FOMC, when earnings results are out, or for filings, news, IPOs, IV levels, hiring and market moves",
282
281
  keywords: [
283
282
  "alert", "event", "congress", "13f", "fund", "member", "follow", "earnings", "cpi", "fomc", "days before",
284
283
  "filing", "filings", "sec", "8-k", "news", "keyword", "analyst", "52-week", "unusual volume", "volume",
285
284
  "short interest", "insider", "catalyst", "implied volatility", "iv spike", "iv level", "risk reversal", "skew", "options flow", "perps", "funding", "hiring", "jobs",
285
+ "ipo", "going public", "listing",
286
286
  ],
287
287
  category: "data",
288
288
  wizardLayout: "form",
@@ -463,7 +463,7 @@ export const alertsPlugin: GloomPlugin = {
463
463
  reportLockNote: alertHistoryLockNote,
464
464
  name: "Alerts",
465
465
  icon: "A",
466
- component: AlertsPane,
466
+ get component(): typeof import("./pane").AlertsPane { const { AlertsPane } = require("./pane"); return AlertsPane; },
467
467
  defaultPosition: "right",
468
468
  defaultMode: "floating",
469
469
  defaultFloatingSize: { width: 82, height: 20 },
@@ -6,7 +6,7 @@ import type { EventAlertRule } from "./events";
6
6
  export const MACRO_EVENT_NAMES: Record<MacroAlertEvent, string> = { cpi: "CPI release", fomc: "FOMC decision" };
7
7
  /** CPI and FOMC and any other calendar release are one kind of alert to the reader, whichever list keeps the rule. */
8
8
  export const RESEARCH_LABELS: Record<ResearchAlertKind, string> = {
9
- perps_threshold: "Perpetual threshold", earnings_date: "Earnings date", macro_event: "Macro event", calendar_event: "Macro event", filing_type: "SEC filing", news_keyword: "News keyword", analyst_change: "Analyst change", fifty_two_week: "52-week range", unusual_volume: "Unusual volume", short_interest_change: "Short interest", insider_trade: "Insider trade", iv_spike: "IV spike", iv_level: "IV level", options_flow: "Options flow", hiring_move: "Hiring move",
9
+ perps_threshold: "Perpetual threshold", earnings_date: "Earnings date", macro_event: "Macro event", calendar_event: "Macro event", ipo: "IPO", filing_type: "SEC filing", news_keyword: "News keyword", analyst_change: "Analyst change", fifty_two_week: "52-week range", unusual_volume: "Unusual volume", short_interest_change: "Short interest", insider_trade: "Insider trade", iv_spike: "IV spike", iv_level: "IV level", options_flow: "Options flow", hiring_move: "Hiring move",
10
10
  };
11
11
  /** What an IV level rule follows, as the form, a list and a notification name it. */
12
12
  export const IV_LEVEL_NAMES: Record<IvLevelMetric, string> = { atm: "30d ATM IV", rr: "30d 25D RR" };
@@ -46,6 +46,8 @@ export const researchWizardFields: Field[] = RESEARCH_FORM_KINDS.flatMap(kind =>
46
46
  : kind === "macro_event" ? [{key:"event", label:"Release", type:"select", defaultValue:"cpi", options:MACRO_ALERT_EVENTS.map((value) => ({value, label:MACRO_EVENT_NAMES[value]}))}]
47
47
  // HIRE covers listings worldwide; a non-US one is written with its venue.
48
48
  : kind === "hiring_move" ? [{key:"symbol", label:"Symbol", type:"text", placeholder:"RIVN or ADYEN:AMS", required:true}]
49
+ // A deal on the IPO calendar has no listing to name yet: it is followed by its name.
50
+ : kind === "ipo" ? [{key:"keyword", label:"Company or keyword", type:"text", placeholder:"Stripe", required:true}]
49
51
  : [{key:"symbol",label:kind === "news_keyword" ? "Symbol (optional)" : kind === "options_flow" ? "Symbol (blank: portfolio and watchlists)" : "Symbol",type:"text",placeholder:"AAPL", required:kind !== "news_keyword" && kind !== "options_flow"},
50
52
  {key:"exchange",label:"US exchange",type:"select",defaultValue:"US",options:choices(["US","NASDAQ","NYSE","ARCA","AMEX","BATS","OTC"])}];
51
53
  if (kind === "earnings_date") {
@@ -92,6 +94,7 @@ export function createResearchAlert(kind: string, values: Record<string,string>,
92
94
  if (!value) throw new Error(kind === "options_flow"
93
95
  ? "Check the symbol and US exchange. The minimum premium is $50,000."
94
96
  : kind === "hiring_move" ? "Check the symbol. Write a non-US listing with its exchange, as ADYEN:AMS."
97
+ : kind === "ipo" ? "Name a company or keyword of 2 to 80 characters, such as Stripe."
95
98
  : kind === "iv_level"
96
99
  ? "Check the symbol and US exchange. An ATM IV level is 1 to 500 (%), a risk reversal -50 to 50 (vol pts)."
97
100
  : kind === "iv_spike"
@@ -142,6 +145,7 @@ export function researchAlertDescription(rule: EventAlertRule): string {
142
145
  case "news_keyword": return `${symbol} · ${config.keyword}`;
143
146
  case "analyst_change": case "insider_trade": return `${symbol} · ${config.direction}`;
144
147
  case "hiring_move": return `${symbol} · ${config.direction === "either" ? "accelerating or slowing" : config.direction}`;
148
+ case "ipo": return config.keyword ?? "";
145
149
  case "fifty_two_week": return `${symbol} · 52w ${config.direction}`;
146
150
  case "unusual_volume": return `${symbol} · ≥${config.threshold}× 20d`;
147
151
  case "short_interest_change": return `${symbol} · ${config.direction} ≥${config.threshold}%`;
@@ -15,6 +15,7 @@ const RESEARCH_ALERT_KINDS = [
15
15
  "options_flow",
16
16
  "perps_threshold",
17
17
  "hiring_move",
18
+ "ipo",
18
19
  ] as const
19
20
  export type ResearchAlertKind = (typeof RESEARCH_ALERT_KINDS)[number]
20
21
  export interface ResearchAlertConfig {
@@ -162,6 +163,8 @@ export function normalizeResearchRule(
162
163
  : ""
163
164
  if (!/^[A-Z0-9][A-Z0-9.:-]{0,31}$/.test(symbol)) return null
164
165
  result.symbol = symbol
166
+ } else if (kind === "ipo") {
167
+ // A company or keyword looked for on the IPO calendar: no listing yet, so no symbol.
165
168
  } else if (
166
169
  (kind !== "news_keyword" && kind !== "options_flow") ||
167
170
  input.symbol
@@ -253,6 +256,21 @@ export function normalizeResearchRule(
253
256
  result.keyword = keyword
254
257
  break
255
258
  }
259
+ case "ipo": {
260
+ // Kept as typed, for the lists; Gloom Cloud matches it in any case.
261
+ const keyword =
262
+ typeof input.keyword === "string"
263
+ ? input.keyword.trim().replace(/\s+/g, " ")
264
+ : ""
265
+ if (
266
+ keyword.length < 2 ||
267
+ keyword.length > 80 ||
268
+ /[\u0000-\u001f]/.test(keyword)
269
+ )
270
+ return null
271
+ result.keyword = keyword
272
+ break
273
+ }
256
274
  case "analyst_change":
257
275
  direction(["any", "upgrade", "downgrade"], "any")
258
276
  break
@@ -36,7 +36,8 @@ const pane = (templateId: string, options?: PaneTemplateCreateOptions): AlertTar
36
36
  * The one map from what an alert follows to what it opens, for every rule kind and every delivered alert:
37
37
  * a perpetual opens PERP on its market; a dated event the pane its date comes from (ERN, CPI, WIRP, ECO); catalyst,
38
38
  * Congress and 13F rules their calendar or list, on the company or fund they name; an IV level the symbol's
39
- * OVDV, whose fits it reads; a hiring move HIRE on the company; any other rule on a security
39
+ * OVDV, whose fits it reads; a hiring move HIRE on the company; an IPO the IPO calendar searched for its
40
+ * company or keyword; any other rule on a security
40
41
  * that listing's DES. A rule on no security opens the pane it watches (SRCH for a news keyword, FLOW for flow
41
42
  * on the user's lists, FIRST for breaking news). Null when there is nothing to open.
42
43
  */
@@ -58,6 +59,8 @@ export function alertTarget(subject: AlertSubject): AlertTarget | null {
58
59
  case "breaking_news": if (!symbol) return pane("news-breaking-pane"); break;
59
60
  // A hiring move opens HIRE on the company, where its Moves tab says why.
60
61
  case "hiring_move": return symbol ? pane("hiring-pane", { symbol }) : null;
62
+ // An IPO opens the calendar searched for what the rule follows, its deal there whether it trades yet or not.
63
+ case "ipo": return pane("ipo-calendar-pane", subject.keyword ? { arg: subject.keyword } : undefined);
61
64
  }
62
65
  return symbol ? { kind: "description", symbol, ...(subject.exchange ? { exchange: subject.exchange } : {}) } : null;
63
66
  }
@@ -6,7 +6,7 @@ export const attentionHeadless: HeadlessPaneDefinition<"bundle"> = {
6
6
  options: [{ key: "tab", type: "enum", values: ATTENTION_TABS.map(({ value }) => ({ value })), defaultValue: "ranking", description: "Rankings, abnormal attention, groups, history or evidence." },
7
7
  { key: "window", type: "enum", values: WINDOWS.map(({ value }) => ({ value })), defaultValue: "now", description: "Latest completed publication hour, UTC day or trailing week." }],
8
8
  discovery: { aliases: ["ATTN"], dataRequirements: ["Privacy-qualified published research counts"],
9
- limitations: ["Pro dataset with three-row preview and latest history point.", "Suppressed hours are absent, never zero.", "Activity is opt-in and is not representative of all investors."] },
9
+ limitations: ["Pro dataset with three-row preview and latest history point.", "Suppressed hours are absent, never zero.", "Counts come from signed-in Gloom users and are not representative of all investors."] },
10
10
  describe: "Gloom research attention (Pro)",
11
11
  async load(args, ctx) {
12
12
  const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
@@ -2,14 +2,12 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { attentionCache } from "./client";
3
3
  import { attentionWindow } from "./model";
4
4
  import { attentionHeadless } from "./headless";
5
- import { TrendingPane } from "./trending";
6
- import { AttentionPane } from "./pane";
7
5
  import { ATTENTION_SERIES } from "./series";
8
6
  import { ATTENTION_LISTED } from "./listing";
9
7
  export const attentionModule: PluginModule = {
10
- panes: [{ id: "attention", name: "Research Attention", icon: "A", component: AttentionPane, defaultPosition: "right", defaultMode: "floating",
8
+ panes: [{ id: "attention", name: "Research Attention", icon: "A", get component(): typeof import("./pane").AttentionPane { const { AttentionPane } = require("./pane"); return AttentionPane; }, defaultPosition: "right", defaultMode: "floating",
11
9
  defaultFloatingSize: { width: 126, height: 32 }, tableExport: true, headless: attentionHeadless },
12
- { id: "attention-trending", reportFreshness: { status: "not-a-feed", basis: "hourly publication" }, name: "Gloom Trending", icon: "A", component: TrendingPane, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 58, height: 12 } }],
10
+ { id: "attention-trending", reportFreshness: { status: "not-a-feed", basis: "hourly publication" }, name: "Gloom Trending", icon: "A", get component(): typeof import("./trending").TrendingPane { const { TrendingPane } = require("./trending"); return TrendingPane; }, defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 58, height: 12 } }],
13
11
  paneTemplates: [{ id: "attention-pane", paneId: "attention", label: "Research Attention", listed: ATTENTION_LISTED,
14
12
  description: "Pro research attention: rankings, abnormal activity, sectors, countries and privacy-qualified hourly history.",
15
13
  keywords: ["attention", "trending", "research", "activity"], shortcut: { prefix: "ATTN", argKind: "text", argPlaceholder: "TICKER", argOptional: true },