@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 { apiClient } from "../../../api-client";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import type { JodiBoardPayload } from "../../../api-client/commodity-balances";
3
4
  import { unavailableOnServer } from "../shared/cloud-resource";
4
5
  import type { BalanceCommodity, Board } from "./model";
@@ -32,7 +33,9 @@ function readBoard(payload: JodiBoardPayload): Balances {
32
33
  || (payload.gas != null && !validBoard(payload.gas, "gas"))) {
33
34
  throw new Error("The server returned an invalid oil and gas balance board");
34
35
  }
35
- if (payload.status === "unavailable" || payload.oil == null) throw new Error(JODI_NOT_AVAILABLE);
36
+ // The server's source did not answer this time, which may pass; a board with no oil has none yet.
37
+ if (payload.status === "unavailable") throw sourceDidNotAnswer(JODI_NOT_AVAILABLE);
38
+ if (payload.oil == null) throw new Error(JODI_NOT_AVAILABLE);
36
39
  if (!validBoard(payload.oil, "oil")) throw new Error("The server returned an invalid oil and gas balance board");
37
40
  return { oil: payload.oil, gas: payload.gas ?? null, stale: payload.stale === true, gaps: payload.gaps };
38
41
  }
@@ -1,7 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { jodiHeadless } from "./headless";
3
3
  import { JODI_PANE_ID } from "./model";
4
- import { JodiPane } from "./pane";
5
4
 
6
5
  export const jodiModule: PluginModule = {
7
6
  panes: [
@@ -9,7 +8,7 @@ export const jodiModule: PluginModule = {
9
8
  id: JODI_PANE_ID,
10
9
  name: "Oil and Gas Balances",
11
10
  icon: "O",
12
- component: JodiPane,
11
+ get component(): typeof import("./pane").JodiPane { const { JodiPane } = require("./pane"); return JodiPane; },
13
12
  defaultPosition: "right",
14
13
  defaultMode: "floating",
15
14
  defaultFloatingSize: { width: 110, height: 30 },
@@ -1,7 +1,6 @@
1
1
  import type { PaneTemplateContext, PaneTemplateCreateOptions } from "../../../types/plugin";
2
2
  import type { PluginModule } from "../plugin-module";
3
3
  import { DEFAULT_FLOATING_SIZE, KELLY_PANE_ID } from "./constants";
4
- import { KellySizerPane } from "./pane";
5
4
  import { kellyHeadless } from "./headless";
6
5
 
7
6
  function resolveTemplateSymbol(context: PaneTemplateContext, options?: PaneTemplateCreateOptions): string | null {
@@ -21,7 +20,7 @@ export const positionSizerModule: PluginModule = {
21
20
  icon: "K",
22
21
  // The bankroll is the account's value.
23
22
  personalData: "holdings",
24
- component: KellySizerPane,
23
+ get component(): typeof import("./pane").KellySizerPane { const { KellySizerPane } = require("./pane"); return KellySizerPane; },
25
24
  defaultPosition: "right",
26
25
  defaultMode: "floating",
27
26
  defaultFloatingSize: DEFAULT_FLOATING_SIZE,
@@ -2,7 +2,6 @@ import { findPaneInstance, type LayoutConfig } from "../../../types/config";
2
2
  import type { AppNotificationRequest, CommandResultDef, GloomPluginContext } from "../../../types/plugin";
3
3
  import type { PluginModule } from "../plugin-module";
4
4
  import type { AppAction } from "../../../state/app/context";
5
- import { LayoutMarketplacePane } from "../../../layout-marketplace/pane";
6
5
  import { apiClient } from "../../../api-client";
7
6
  import { resolvePlanAccess } from "../../../api-client/plan-access";
8
7
  import { getSharedRegistry } from "../../registry";
@@ -11,6 +10,7 @@ import {
11
10
  deskFunctions,
12
11
  DESKS,
13
12
  isDeskStock,
13
+ MORE_DESKS,
14
14
  pickDeskCompany,
15
15
  type Desk,
16
16
  } from "../../../layout/desks";
@@ -60,10 +60,11 @@ function getFocusedPane(layout: LayoutConfig, focusedPaneId: string | null) {
60
60
  }
61
61
 
62
62
  /** Desks whose key, name or one of whose functions matches what follows DESK. */
63
- function matchDesks(query: string): Desk[] {
63
+ function matchDesks(query: string): Desk<string>[] {
64
64
  const needle = query.trim().toLowerCase();
65
- if (!needle) return [...DESKS];
66
- return DESKS.filter((desk) => (
65
+ const desks: Desk<string>[] = [...DESKS, ...MORE_DESKS];
66
+ if (!needle) return desks;
67
+ return desks.filter((desk) => (
67
68
  desk.key.startsWith(needle)
68
69
  || desk.label.toLowerCase().includes(needle)
69
70
  || desk.name.toLowerCase().includes(needle)
@@ -75,7 +76,7 @@ function matchDesks(query: string): Desk[] {
75
76
  * Opens the desk as a new tab after the others. Its company is the focused
76
77
  * pane's stock, else the most recent stock the user looked at.
77
78
  */
78
- async function addDesk(ctx: GloomPluginContext, desk: Desk): Promise<void> {
79
+ async function addDesk(ctx: GloomPluginContext, desk: Desk<string>): Promise<void> {
79
80
  const registry = getSharedRegistry();
80
81
  if (!registry || !dispatchRef || !getStateRef) return;
81
82
  const config = ctx.getConfig();
@@ -110,8 +111,8 @@ export const layoutManagerModule: PluginModule = {
110
111
  id: "layout-marketplace",
111
112
  name: "Layouts",
112
113
  icon: "L",
113
- component: LayoutMarketplacePane,
114
- reportFreshness: { status: "not-a-feed", basis: "layout listings" },
114
+ get component(): typeof import("../../../layout-marketplace/pane").LayoutMarketplacePane { const { LayoutMarketplacePane } = require("../../../layout-marketplace/pane"); return LayoutMarketplacePane; },
115
+ reportFreshness: { status: "not-a-feed", basis: "layout listings", local: true },
115
116
  defaultPosition: "right",
116
117
  defaultMode: "floating",
117
118
  defaultFloatingSize: { width: 118, height: 34 },
@@ -128,7 +129,7 @@ export const layoutManagerModule: PluginModule = {
128
129
  label: "Add a Desk",
129
130
  description: "Add a ready-made desk for one kind of trading as a new layout tab",
130
131
  keywords: ["desk", "desks", "workspace", "starter", "equities", "stocks", "options", "volatility", "futures",
131
- "commodities", "rates", "credit", "fx", "macro", "active trading", "day trading"],
132
+ "commodities", "rates", "credit", "fx", "macro", "active trading", "day trading", "buildout", "ai buildout", "data centers"],
132
133
  category: "config",
133
134
  shortcut: "DESK",
134
135
  shortcutArg: { placeholder: "desk", kind: "text", parse: (arg) => ({ query: arg.trim() }) },
@@ -152,7 +152,9 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
152
152
  return { text: times(event.multiple), value: event.multiple };
153
153
  }, [colors, model?.basis, release]);
154
154
 
155
- const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width));
155
+ // A figure wraps only into rows the table's least leaves, so the pane never spills.
156
+ const statMaxLines = height - 1 - 4;
157
+ const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width, undefined, { maxLines: statMaxLines }));
156
158
  const key = (event: MacroDayEvent) => `${event.kind}:${event.date}`;
157
159
 
158
160
  return <Box width={width} height={height} flexDirection="column" overflow="hidden">
@@ -164,7 +166,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
164
166
  {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="macro-day moves" loading={history.loading && !model}
165
167
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.events.length}>
166
168
  {model ? <>
167
- <StatGrid items={stats} width={width} />
169
+ <StatGrid items={stats} width={width} maxLines={statMaxLines} />
168
170
  <DataTableView<MacroDayEvent> focused={focused} columns={columns} items={rows} rootWidth={width} rootHeight={contentHeight}
169
171
  getItemKey={key} emptyStateTitle={surprise === "all" ? "No release days." : `No ${MACRO_SURPRISE_LABELS[surprise].toLowerCase()} prints.`} sortColumnId={null} sortDirection="desc" selectedTextOverridesCellColor
170
172
  selection={{ kind: "id", selectedId: selectedRow, getId: key, onChange: (id) => setSelectedRow(id) }}
@@ -1,6 +1,5 @@
1
1
  import type { GloomPlugin } from "../../../types/plugin";
2
2
  import { MARKET_HALTS_PANE_ID } from "./model";
3
- import { MarketHaltsPane } from "./pane";
4
3
 
5
4
  export const marketHaltsPlugin: GloomPlugin = {
6
5
  id: "market-halts",
@@ -20,7 +19,7 @@ export const marketHaltsPlugin: GloomPlugin = {
20
19
  reportFreshness: { source: "Nasdaq Trader" },
21
20
  name: "Market Halts",
22
21
  icon: "H",
23
- component: MarketHaltsPane,
22
+ get component(): typeof import("./pane").MarketHaltsPane { const { MarketHaltsPane } = require("./pane"); return MarketHaltsPane; },
24
23
  defaultPosition: "right",
25
24
  defaultMode: "floating",
26
25
  defaultFloatingSize: { width: 124, height: 26 },
@@ -70,6 +70,7 @@ import {
70
70
  type HeatmapSizeBy,
71
71
  } from "./portfolio";
72
72
  import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
73
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
73
74
  import {
74
75
  LIVE_STREAMING_QUICK_SETTING,
75
76
  useLiveStreamingSetting,
@@ -635,6 +636,13 @@ function MarketHeatmapPane({ focused, width, height }: PaneProps) {
635
636
  useAutoRefresh(board?.checkedAt ?? null, autoRefresh, { intervalMs: heatmapCheckIntervalMs(board) });
636
637
 
637
638
  usePaneStatusFooter({ registrationId: "market-heatmap-retained", stale: hasBoard && stale && !portfolioTab });
639
+ // Signed out or free, a market board is the delayed one, and the holdings' quotes are delayed ones.
640
+ const quoteDelay = useQuoteDelayFooter(
641
+ portfolioTab
642
+ ? Array.from(financials.values(), (entry) => entry?.quote)
643
+ : shownBoard ? [{ dataSource: shownBoard.access === "realtime" ? "live" as const : "delayed" as const }] : [],
644
+ "heatmap-delay",
645
+ );
638
646
  usePaneNoticeFooter({
639
647
  registrationId: "market-heatmap-omitted",
640
648
  notices: portfolioTab && portfolioBoard.omitted > 0
@@ -677,7 +685,7 @@ function MarketHeatmapPane({ focused, width, height }: PaneProps) {
677
685
  // The caption takes the room the rest leaves, with "loading" counted in
678
686
  // always, so a refresh never squeezes the row; a narrow pane drops its
679
687
  // least important parts rather than truncating every segment.
680
- const others = [...selected, ...failed, ...feed];
688
+ const others = [...selected, ...failed, ...feed, ...(quoteDelay ? [quoteDelay] : [])];
681
689
  const room = width - others.reduce((total, segment) => total + footerSegmentWidth(segment) + 1, 0) - LOADING_FOOTER_WIDTH - 1;
682
690
  const caption = shownBoard ? heatmapBoardCaption(shownBoard, { now: Date.now(), checked, failed: failed.length > 0, maxWidth: room }) : null;
683
691
  return {
@@ -687,9 +695,10 @@ function MarketHeatmapPane({ focused, width, height }: PaneProps) {
687
695
  ...(caption ? [{ id: "board", parts: [{ text: caption, tone: "muted" as const }] }] : []),
688
696
  ...feed,
689
697
  ...loadingSegment,
698
+ ...(quoteDelay ? [quoteDelay] : []),
690
699
  ],
691
700
  };
692
- }, [checked, feedStatus, hasBoard, loadError, loading, portfolioTab, selectedAsset, shownBoard, width]);
701
+ }, [checked, feedStatus, hasBoard, loadError, loading, portfolioTab, quoteDelay, selectedAsset, shownBoard, width]);
693
702
 
694
703
  return (
695
704
  <Box flexDirection="column" width={width} height={height}>
@@ -119,6 +119,7 @@ export async function loadMarketMoverTab(
119
119
  ...hydrated,
120
120
  source: "gloom",
121
121
  stale: trending.stale || !!hydrated.unavailableSymbols?.length,
122
+ ...(trending.savedAt != null ? { savedAt: trending.savedAt } : {}),
122
123
  };
123
124
  }
124
125
  const category = CATEGORY_MAP[tab];
@@ -2,6 +2,8 @@ import { Box, useUiCapabilities } from "../../../ui";
2
2
  import { nextHeaderSort } from "../../../utils/sort-values";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { DataTableView, EmptyState, QueryBar, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
5
+ import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
6
+ import { STALE_SEGMENT } from "../../../components/layout/pane/status-footer";
5
7
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
6
8
  import type { PaneProps } from "../../../types/plugin";
7
9
  import type { PluginModule } from "../plugin-module";
@@ -12,6 +14,7 @@ import { useAssetData, usePluginPaneState, usePluginTickerActions } from "../../
12
14
  import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
13
15
  import { useAutoRefresh } from "../../../react/auto-refresh";
14
16
  import { useQuoteBoard } from "../shared/use-quote-board";
17
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
15
18
  import {
16
19
  attachMarketMoversPersistence,
17
20
  MARKET_SUMMARY_SYMBOLS,
@@ -60,6 +63,8 @@ import {
60
63
  const NO_SAVED_SELECTION: string[] = [];
61
64
  const SESSION_CHECK_MS = 30_000;
62
65
  const NO_QUOTES: MoverQuote[] = [];
66
+ /** A list without Pro is the one built a delay before: the delayed clock reads it as one late price. */
67
+ const DELAYED_LIST = [{ dataSource: "delayed" as const }];
63
68
  const moverKey = (row: Pick<ScreenerQuote, "symbol" | "exchange">) => publicTickerKey(row.symbol, row.exchange);
64
69
 
65
70
  /** What is trading in New York, re-read every half minute so the pane follows 04:00, 09:30 and 16:00. */
@@ -167,6 +172,8 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
167
172
  const [selectedSymbol, setSelectedSymbol] = useState<string | null>(null);
168
173
  const [sortPreference, setSortPreference] = useState<MarketMoverSortPreference>(DEFAULT_SORT_PREFERENCE);
169
174
  const [moversStale, setMoversStale] = useState(false);
175
+ // The list's refresh failed and a copy saved at this time (ms) answered instead.
176
+ const [moversSavedAt, setMoversSavedAt] = useState<number | null>(null);
170
177
  const [lastSuccess, setLastSuccess] = useState<{ key: string; at: number } | null>(null);
171
178
 
172
179
  const fetchGenRef = useRef(0);
@@ -241,11 +248,13 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
241
248
  if (!options?.background) setSelectedSymbol(null);
242
249
  }
243
250
  setMoversStale(result.stale);
251
+ setMoversSavedAt(result.savedAt ?? null);
244
252
  setLoadError(quoteError);
245
253
  if (!quoteError && !result.stale) setLastSuccess({ key: `${tab}:${minFloat ?? ""}`, at: Date.now() });
246
254
  } catch {
247
255
  if (fetchGenRef.current !== gen) return;
248
256
  setMoversStale(true);
257
+ setMoversSavedAt(null);
249
258
  setLoadError("Market movers temporarily unavailable");
250
259
  }
251
260
  finally {
@@ -279,6 +288,8 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
279
288
  handleRefreshKey(event, () => loadTab(activeTab, { forceRefresh: true }), { stopPropagation: true })
280
289
  ), [activeTab, loadTab]);
281
290
 
291
+ // Without Pro the lists are the delayed ones (with Pro the real-time ones), and say so.
292
+ const quoteDelay = useQuoteDelayFooter(rows.length > 0 ? DELAYED_LIST : [], "movers-delay");
282
293
  usePaneFooter("market-movers", () => ({
283
294
  info: [
284
295
  ...(loadError ? [{ id: "load-error", parts: [{ text: loadError, tone: "warning" as const }] }] : []),
@@ -288,14 +299,14 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
288
299
  id: "feed",
289
300
  parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
290
301
  }] : []),
291
- ...(moversStale && loadedKey === listKey ? [{
292
- id: "stale",
293
- parts: [{ text: "stale", tone: "muted" as const }],
294
- }] : []),
302
+ // A saved copy answering a failed refresh says when it was saved; other stale rows say only that.
303
+ ...(loadedKey === listKey ? savedCopyFooterInfo([moversSavedAt]) : []),
304
+ ...(moversStale && moversSavedAt == null && loadedKey === listKey ? [STALE_SEGMENT] : []),
295
305
  // The FLOAT column shows while a minimum is set; a float the source does not date says so once.
296
306
  ...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
307
+ ...(quoteDelay ? [quoteDelay] : []),
297
308
  ],
298
- }), [columns, feedStatus, listKey, loadedKey, loadError, loading, moversStale, rows, summaryQuotes]);
309
+ }), [columns, feedStatus, listKey, loadedKey, loadError, loading, moversStale, moversSavedAt, quoteDelay, rows, summaryQuotes]);
299
310
 
300
311
  return (
301
312
  <DataTableView<MarketMoverRow, MarketMoverColumn>
@@ -5,6 +5,7 @@ import { logFailure } from "../../../utils/log-failure";
5
5
  import { savedCopyMark, servedFromSavedCopy } from "../../../data/saved-copies";
6
6
  import type { PluginPersistence } from "../../../types/plugin";
7
7
  import { apiClient } from "../../../api-client";
8
+ import { isTransientFailure, sourceDidNotAnswer } from "../../../api-client/errors";
8
9
  import type {
9
10
  CloudMarketResponse,
10
11
  CloudMarketScreenerCategory,
@@ -96,6 +97,8 @@ function writeCache<T>(key: string, data: T): void {
96
97
  export interface CachedResult<T> {
97
98
  data: T;
98
99
  stale: boolean;
100
+ /** Its refresh failed and the copy saved at this time (ms) answered instead. */
101
+ savedAt?: number;
99
102
  }
100
103
 
101
104
  async function loadCached<T>(
@@ -119,11 +122,13 @@ async function loadCached<T>(
119
122
  return result;
120
123
  })
121
124
  .catch((error) => {
122
- // Serving the expired copy is right; hiding that it is expired is not. A command that shows it
123
- // ends with when the copy was saved.
124
- if (fallback) {
125
- failedFetches.add(key);
126
- return servedFromSavedCopy({ data: fallback.data, stale: true }, fallback.fetchedAt);
125
+ // The next read asks again rather than read the saved copy as current.
126
+ if (fallback) failedFetches.add(key);
127
+ // Serving the expired copy is right for a failure asking again may get past; hiding that it is
128
+ // expired is not. A command that shows it ends with when the copy was saved. A refusal or a
129
+ // reply that fails validation is the error, saved copy or not.
130
+ if (fallback && isTransientFailure(error)) {
131
+ return servedFromSavedCopy({ data: fallback.data, stale: true, savedAt: fallback.fetchedAt }, fallback.fetchedAt);
127
132
  }
128
133
  throw error;
129
134
  })
@@ -146,6 +151,8 @@ export interface MarketMoversResult {
146
151
  quotes: ScreenerQuote[];
147
152
  source: MarketMoversDataSource;
148
153
  stale: boolean;
154
+ /** Its refresh failed and the list saved at this time (ms) answered instead. */
155
+ savedAt?: number;
149
156
  }
150
157
 
151
158
  export interface PreferredMarketMoverSources {
@@ -268,6 +275,8 @@ export async function fetchScreenerResult(
268
275
  const session = category === "most_actives" ? undefined : options?.session;
269
276
  const load = async () => {
270
277
  const response = await api.getMarketMovers(category, count, options?.forceRefresh, session);
278
+ // The service's own source did not answer this time: a saved list answers, as it does a 5xx.
279
+ if (!response.data && response.status === "retryable_error") throw sourceDidNotAnswer("Market movers temporarily unavailable");
271
280
  if (!response.data) throw new Error("Invalid market movers response");
272
281
  return { data: parseScreenerResponse(response.data), stale: response.stale === true || response.data.stale === true };
273
282
  };
@@ -392,7 +401,13 @@ export async function fetchPreferredMarketMovers(
392
401
  const eligibility = sources.isCloudEligible();
393
402
  if (!(typeof eligibility === "boolean" ? eligibility : await eligibility)) {
394
403
  const result = await sources.fetchMarket(category, count, options);
395
- return { ...savedCopyMark(result), quotes: rankScreenerQuotes(category, result.data), source: "gloom", stale: result.stale };
404
+ return {
405
+ ...savedCopyMark(result),
406
+ quotes: rankScreenerQuotes(category, result.data),
407
+ source: "gloom",
408
+ stale: result.stale,
409
+ ...(result.savedAt != null ? { savedAt: result.savedAt } : {}),
410
+ };
396
411
  }
397
412
 
398
413
  const marketMetadata = sources.fetchMarket(
@@ -432,6 +447,7 @@ export async function fetchPreferredMarketMovers(
432
447
  quotes: rankScreenerQuotes(category, fallback.data),
433
448
  source: "gloom",
434
449
  stale: fallback.stale,
450
+ ...(fallback.savedAt != null ? { savedAt: fallback.savedAt } : {}),
435
451
  };
436
452
  }
437
453
 
@@ -448,6 +464,7 @@ export async function fetchTrendingResult(
448
464
  const load = async () => {
449
465
  const response = await (api.getMarketTrending ?? apiClient.getMarketTrending)(count);
450
466
  if (response.status === "empty") return { data: [], stale: response.stale === true };
467
+ if (!response.data && response.status === "retryable_error") throw sourceDidNotAnswer("Market trends temporarily unavailable");
451
468
  if (!response.data) throw new Error("Market trends unavailable");
452
469
  return { data: parseTrendingResponse(response.data), stale: response.stale === true };
453
470
  };
@@ -5,7 +5,9 @@ import type {
5
5
  CloudSessionMoversSide,
6
6
  } from "../../../api-client/market-movers";
7
7
  import { usePlanAccess } from "../../../api-client/plan-access";
8
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
8
9
  import { DataTableView, EmptyState, QueryBar, usePaneFooter, type DataTableKeyEvent } from "../../../components";
10
+ import { STALE_SEGMENT } from "../../../components/layout/pane/status-footer";
9
11
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
10
12
  import {
11
13
  buildScreenerQuoteTargets,
@@ -190,6 +192,7 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
190
192
  const sessionLabel = payload ? earlierSessionLabel(payload.session, session) : null;
191
193
  // While its session trades, a free list says when it was built; a finished one is final and its date says enough.
192
194
  const asOfLabel = payload && delayed && sessionTrading ? listAsOfLabel(payload.asOf, payload.session, session) : null;
195
+ const quoteDelay = useQuoteDelayFooter(payload ? [{ dataSource: delayed ? "delayed" : "live" }] : [], "movers-delay");
193
196
  usePaneFooter("market-movers", () => ({
194
197
  info: [
195
198
  ...(loadError ? [{ id: "load-error", parts: [{ text: loadError, tone: "warning" as const }] }] : []),
@@ -201,11 +204,12 @@ function SessionMoversTable({ view, session, focused, width, summaryQuotes, live
201
204
  id: "feed",
202
205
  parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
203
206
  }] : []),
204
- ...(payload?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "muted" as const }] }] : []),
207
+ ...(payload?.stale ? [STALE_SEGMENT] : []),
205
208
  // A float the source does not date says so once, while the FLOAT column shows.
206
209
  ...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
210
+ ...(quoteDelay ? [quoteDelay] : []),
207
211
  ],
208
- }), [asOfLabel, columns, feedStatus, loadError, loading, payload?.stale, rows, sessionLabel, summaryQuotes]);
212
+ }), [asOfLabel, columns, feedStatus, loadError, loading, payload?.stale, quoteDelay, rows, sessionLabel, summaryQuotes]);
209
213
 
210
214
  const openRow = useCallback((row: SessionMoverRow) => {
211
215
  pinTicker(rowKey(row), { floating: true, paneType: TICKER_RESEARCH_PANE_ID, instrument: null });
@@ -3,7 +3,6 @@ import { valuationCache } from "./cache";
3
3
  import { valuationSeriesDefinitions } from "./chart-series";
4
4
  import { INDICATORS, resolveIndicatorArg } from "./indicators";
5
5
  import { marketValuationHeadless } from "./headless";
6
- import { MarketValuationPane } from "./pane";
7
6
  import { buildValuationSettingsDef, VALUATION_DEFAULTS } from "./settings";
8
7
 
9
8
 
@@ -41,7 +40,7 @@ export const marketValuationModule: PluginModule = {
41
40
  id: MARKET_VALUATION_PANE_ID,
42
41
  name: "Market Valuation",
43
42
  icon: "V",
44
- component: MarketValuationPane,
43
+ get component(): typeof import("./pane").MarketValuationPane { const { MarketValuationPane } = require("./pane"); return MarketValuationPane; },
45
44
  defaultPosition: "right",
46
45
  defaultMode: "floating",
47
46
  defaultFloatingSize: { width: 88, height: 34 },
@@ -1,13 +1,12 @@
1
1
  import type { GloomPlugin } from "../../../types/plugin";
2
2
  import { fundBreadthCache, fundChangesCache, fundListCache, fundMembersCache } from "./client";
3
3
  import { membersHeadless } from "./headless";
4
- import { MembersPane } from "./pane";
5
4
  import { membersTitle, TABS } from "./model";
6
5
  export const membersPlugin: GloomPlugin = {
7
6
  id: "members", name: "Index and ETF members", version: "1.0.0", description: "ETF holdings, member returns, contributions, breadth and index changes", toggleable: true,
8
7
  setup(ctx) { fundBreadthCache.attach(ctx.persistence); fundListCache.attach(ctx.persistence); fundMembersCache.attach(ctx.persistence); fundChangesCache.attach(ctx.persistence); },
9
8
  dispose() { fundBreadthCache.reset(); fundListCache.reset(); fundMembersCache.reset(); fundChangesCache.reset(); },
10
- panes: [{ id: "members", name: "Index and ETF members", icon: "M", component: MembersPane, tickerFollower: true,
9
+ panes: [{ id: "members", name: "Index and ETF members", icon: "M", get component(): typeof import("./pane").MembersPane { const { MembersPane } = require("./pane"); return MembersPane; }, tickerFollower: true,
11
10
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 132, height: 32 }, tableExport: true,
12
11
  settings: { fields: [{ key: "tab", label: "View", type: "select", options: TABS }] } }],
13
12
  paneTemplates: [{ id: "members-pane", paneId: "members", label: "Index and ETF members", description: "ETF holdings, member returns, daily contributions, breadth and index changes.",
@@ -1,12 +1,11 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { moneyMarketsCache } from "./client";
3
3
  import { moneyMarketsHeadless } from "./headless";
4
- import { MoneyMarketsPane } from "./pane";
5
4
  import { MONEY_MARKET_SERIES } from "./series";
6
5
 
7
6
  export const moneyMarketsModule: PluginModule = {
8
7
  series: MONEY_MARKET_SERIES,
9
- panes: [{ id: "money-markets", name: "Money Markets", icon: "$", component: MoneyMarketsPane,
8
+ panes: [{ id: "money-markets", name: "Money Markets", icon: "$", get component(): typeof import("./pane").MoneyMarketsPane { const { MoneyMarketsPane } = require("./pane"); return MoneyMarketsPane; },
10
9
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 88, height: 28 },
11
10
  tableExport: true, headless: moneyMarketsHeadless,
12
11
  settings: { title: "Money Markets", fields: [{ key: "tab", label: "View", type: "select",
@@ -2,6 +2,7 @@ import { useCallback, useMemo } from "react";
2
2
  import { Box } from "../../../ui";
3
3
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
4
4
  import { ChartTableHeader, CompositeChart, CurveSurface, curveGhostColors, EmptyState, formatPercentAxis, MarketBoardStack, PaneStatusBody, useChartTableSelection, usePaneNoticeFooter, usePaneStatusLinkFooter, usePaneTabs, type ChartTableChart, type MarketBoardRow, type StatItem } from "../../../components";
5
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
5
6
  import { curveStrip, curveSurfaceMinRows } from "../../../components/chart/curve";
6
7
  import { colors } from "../../../theme/colors";
7
8
  import { useThemeColors } from "../../../theme/theme-context";
@@ -125,7 +126,7 @@ export function MoneyMarketsPane({ width, height, focused }: PaneProps) {
125
126
  notices: [...(data ? moneyMarketNotices(data) : []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
126
127
  usePaneStatusLinkFooter({ registrationId: "money-markets", focused, loading: resource.loading, error: resource.error,
127
128
  url: selected?.observation.sourceUrl ?? null, showOpenHint: true,
128
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
129
+ info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
129
130
  stale: !!data && resource.data?.stale,
130
131
  });
131
132
  return <Box width={width} height={height} flexDirection="column">
@@ -1,6 +1,6 @@
1
1
  import type { MoneyMarketObservation } from "../../../api-client/money-markets";
2
2
  import type { SeriesDefinition } from "../../../series/types";
3
- import { cachedSeriesWarning } from "../shared/series-warning";
3
+ import { cachedSeriesNote } from "../shared/series-warning";
4
4
  import { loadMoneyMarkets } from "./client";
5
5
 
6
6
  function observations(history: readonly MoneyMarketObservation[]) {
@@ -21,8 +21,7 @@ export const MONEY_MARKET_SERIES: readonly SeriesDefinition[] = [
21
21
  tags: ["liquidity", "Fed balance sheet", "TGA", "RRP", "money markets"],
22
22
  load: async () => {
23
23
  const resource = await loadMoneyMarkets();
24
- const warning = cachedSeriesWarning(resource);
25
- return { points: observations(resource.payload.netLiquidity.history), ...(warning ? { warning } : {}) };
24
+ return { points: observations(resource.payload.netLiquidity.history), ...cachedSeriesNote(resource) };
26
25
  },
27
26
  },
28
27
  {
@@ -34,8 +33,7 @@ export const MONEY_MARKET_SERIES: readonly SeriesDefinition[] = [
34
33
  tags: ["bills", "curve", "money markets"],
35
34
  load: async () => {
36
35
  const resource = await loadMoneyMarkets();
37
- const warning = cachedSeriesWarning(resource);
38
- return { points: observations(resource.payload.billsCurve.slope.history), ...(warning ? { warning } : {}) };
36
+ return { points: observations(resource.payload.billsCurve.slope.history), ...cachedSeriesNote(resource) };
39
37
  },
40
38
  },
41
39
  ];
@@ -14,6 +14,8 @@ import {
14
14
  import { parsePublicTickerKey } from "../../../utils/exchanges";
15
15
  import { companyDataNotes, readCompanyFrom } from "../../../types/company-from";
16
16
  import { formatUtcSeconds } from "../../../utils/utc-time";
17
+ import { newsSummaryLine } from "../../../news/news-model";
18
+ import { shortStoryId } from "../../../news/story-id";
17
19
  import { cloudNewsFreshness, cloudRealtimeAccess } from "../shared/report-freshness";
18
20
  import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
19
21
 
@@ -25,6 +27,11 @@ const NEWS_COLUMNS = [
25
27
  { key: "tickers", header: "Tickers", textFormat: (value: unknown) => Array.isArray(value) ? value.join(", ") : String(value ?? "") },
26
28
  { key: "sentiment", header: "Sentiment" },
27
29
  { key: "importance", header: "Score", align: "right" as const },
30
+ // The short id `fn news-story-pane` opens the story by, and its summary on one line under the row; exports keep both whole.
31
+ { key: "id", header: "ID", textFormat: (value: unknown) => typeof value === "string" ? shortStoryId(value) : "" },
32
+ { key: "summary", header: "Summary", underRow: "line" as const,
33
+ textFormat: (value: unknown, row: Record<string, unknown>) =>
34
+ newsSummaryLine({ title: String(row.headline ?? ""), summary: typeof value === "string" ? value : undefined }) ?? "" },
28
35
  ];
29
36
 
30
37
  export interface NewsHeadlessDependencies {
@@ -92,7 +92,7 @@ const newsWirePanes: PluginModule["panes"] = [
92
92
  {
93
93
  id: NEWS_STORY_PANE_ID,
94
94
  reportFreshness: NEWS_REPORT_FRESHNESS,
95
- reportArgument: { kind: "free-text", placeholder: "story id", description: "The id of the story to open." },
95
+ reportArgument: { kind: "free-text", placeholder: "story id", description: "The id of the story to open.", identifier: true },
96
96
  name: "Story",
97
97
  icon: "A",
98
98
  component: NewsStoryPane,
@@ -7,6 +7,7 @@ import { usePaneInstance } from "../../../../../state/app/context";
7
7
  import type { PaneProps, PaneTemplateCreateOptions, PaneTemplateDef } from "../../../../../types/plugin";
8
8
  import { usePluginAppActions } from "../../../../runtime";
9
9
  import { usePersistedNewsArticles } from "../persisted-articles";
10
+ import { storyIdMatches } from "../../../../../news/story-id";
10
11
  import { NewsDetailView } from "./detail-view";
11
12
  import { useNewsArticleFooter } from "./footer";
12
13
 
@@ -33,7 +34,11 @@ function handOffNewsStory(article: NewsArticle): void {
33
34
 
34
35
  function handedOffStory(articleId: string): NewsArticle | null {
35
36
  if (!articleId) return null;
36
- return handedOffStories.get(articleId) ?? null;
37
+ const exact = handedOffStories.get(articleId);
38
+ if (exact) return exact;
39
+ // A pane opened by a short id (`fn news-story-pane 6067684e`) takes the story whose id starts with it.
40
+ for (const [id, article] of handedOffStories) if (storyIdMatches(id, articleId)) return article;
41
+ return null;
37
42
  }
38
43
 
39
44
  export const openNewsStoryPane = (
@@ -82,9 +87,10 @@ export function NewsStoryPane({ focused, width }: PaneProps) {
82
87
  const [loaded, setLoaded] = useState<NewsArticle | null>(() => handedOffStory(articleId));
83
88
  // The pane keeps a copy of its story, so a relaunch or a shared layout shows
84
89
  // it at once, including a story from an RSS feed that cannot be fetched by id.
85
- const story = useMemo(() => (loaded?.id === articleId ? [loaded] : NO_STORY), [articleId, loaded]);
90
+ // A story opened by its short id (`fn news-story-pane 6067684e`) is the one whose id starts with it.
91
+ const story = useMemo(() => (loaded && storyIdMatches(loaded.id, articleId) ? [loaded] : NO_STORY), [articleId, loaded]);
86
92
  const kept = usePersistedNewsArticles("story", story);
87
- const article = kept.find((entry) => entry.id === articleId) ?? null;
93
+ const article = kept.find((entry) => storyIdMatches(entry.id, articleId)) ?? null;
88
94
  const [loading, setLoading] = useState(false);
89
95
  const [error, setError] = useState<string | null>(null);
90
96
 
@@ -1,3 +1,4 @@
1
+ import { notesPluginMeta } from "../builtin-plugin-meta";
1
2
  import { apiClient } from "../../../api-client";
2
3
  import type { GloomPlugin } from "../../../types/plugin";
3
4
  import { teamStore } from "../../../team/store";
@@ -12,11 +13,7 @@ import { createNotesTab } from "./ticker-notes-tab";
12
13
  let disposeNotes: (() => void) | null = null;
13
14
 
14
15
  export const notesPlugin: GloomPlugin = {
15
- id: "notes",
16
- name: "Notes",
17
- version: "2.0.0",
18
- description: "Markdown notes on tickers and on their own, personal or shared with a team.",
19
- toggleable: true,
16
+ ...notesPluginMeta,
20
17
  cliCommands: [notesCliCommand],
21
18
 
22
19
  setup(ctx) {
@@ -79,7 +76,7 @@ export const notesPlugin: GloomPlugin = {
79
76
 
80
77
  ctx.registerPane({
81
78
  id: "quick-notes",
82
- reportFreshness: { source: "Your notes", status: "not-a-feed", basis: "your notes" },
79
+ reportFreshness: { source: "Your notes", status: "not-a-feed", basis: "your notes", local: true },
83
80
  name: "Notes",
84
81
  personalData: "messages",
85
82
  icon: "N",
@@ -1,5 +1,4 @@
1
1
  import type { GloomPlugin, PaneTemplateCreateOptions } from "../../../types/plugin";
2
- import { OpenFdaPane } from "./pane";
3
2
  import { OPENFDA_PANE_ID, OPENFDA_PLUGIN_ID } from "./types";
4
3
 
5
4
  const createOpenFdaPaneInstance = (options?: PaneTemplateCreateOptions) => {
@@ -35,7 +34,7 @@ export const openFdaPlugin: GloomPlugin = {
35
34
  reportFreshness: { source: "US FDA", status: "not-a-feed", basis: "published reports" },
36
35
  name: "FDA Reports",
37
36
  icon: "F",
38
- component: OpenFdaPane,
37
+ get component(): typeof import("./pane").OpenFdaPane { const { OpenFdaPane } = require("./pane"); return OpenFdaPane; },
39
38
  defaultPosition: "right",
40
39
  defaultMode: "floating",
41
40
  defaultFloatingSize: { width: 100, height: 30 },