@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
@@ -14,7 +14,7 @@
14
14
  * Where the status bar has no room for it, as on a phone, it never shows and
15
15
  * neither arm is counted.
16
16
  */
17
- import { useCallback, useEffect, useRef, useState } from "react";
17
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
18
18
  import { apiClient } from "../../../api-client";
19
19
  import { usePlanAccess } from "../../../api-client/plan-access";
20
20
  import { exposeWebExperiment } from "../../../api-client/research-activity";
@@ -88,6 +88,27 @@ interface TrialOfferStatusWidgetProps {
88
88
  delayMs?: number;
89
89
  }
90
90
 
91
+ let offerShowing = false;
92
+ const offerListeners = new Set<() => void>();
93
+
94
+ function setOfferShowing(next: boolean): void {
95
+ if (offerShowing === next) return;
96
+ offerShowing = next;
97
+ for (const listener of offerListeners) listener();
98
+ }
99
+
100
+ function subscribeOfferShowing(listener: () => void): () => void {
101
+ offerListeners.add(listener);
102
+ return () => {
103
+ offerListeners.delete(listener);
104
+ };
105
+ }
106
+
107
+ /** Whether the offer line is in the status bar, so the upgrade chip beside it does not pitch Pro twice. */
108
+ export function useTrialOfferShowing(): boolean {
109
+ return useSyncExternalStore(subscribeOfferShowing, () => offerShowing, () => offerShowing);
110
+ }
111
+
91
112
  export function TrialOfferStatusWidget(props: TrialOfferStatusWidgetProps) {
92
113
  if (getCurrentPluginTarget() !== "web") return null;
93
114
  return <WebTrialOffer {...props} />;
@@ -150,8 +171,10 @@ function WebTrialOffer({ expose = exposeWebExperiment, delayMs = TRIAL_OFFER_DEL
150
171
  useEffect(() => {
151
172
  if (!showing) return;
152
173
  hideShowingOffer = hide;
174
+ setOfferShowing(true);
153
175
  return () => {
154
176
  if (hideShowingOffer === hide) hideShowingOffer = null;
177
+ setOfferShowing(false);
155
178
  };
156
179
  }, [hide, showing]);
157
180
 
@@ -3,24 +3,29 @@ import { apiClient } from "../../../api-client";
3
3
  import { t, tf } from "../../../i18n";
4
4
  import { useAppLanguage } from "../../../i18n/react";
5
5
  import { Button } from "../../../components/ui/button";
6
- import { useCommandBarShortcut } from "../../../ui";
6
+ import { DelayedMark } from "../../../components/ui/delayed-mark";
7
+ import { Box, useCommandBarShortcut, useUiCapabilities } from "../../../ui";
7
8
  import { chatController, type ChatController } from "../chat/controller";
8
9
  import { useCloudPlanAction, useCloudUpgradeAction } from "../shared/cloud-upgrade";
9
- import { resolvePlanAccess } from "../../../api-client/plan-access";
10
+ import { CLOUD_QUOTE_DELAY_MINUTES, resolvePlanAccess } from "../../../api-client/plan-access";
11
+ import { useTrialOfferShowing } from "./trial-offer-status-widget";
10
12
 
11
13
  interface CloudUpgradeStatusWidgetProps {
12
14
  controller?: Pick<ChatController, "getSnapshot" | "subscribe">;
13
15
  }
14
16
 
15
17
  /**
16
- * Global entitlement status: the trial countdown while Pro is on loan, or the
17
- * delay free accounts get on cloud data. Signed-out users already have sign-in
18
- * affordances next to it, and paying subscribers have nothing to report.
18
+ * Global entitlement status: the trial countdown while Pro is on loan, or,
19
+ * signed out or on the free plan, the delayed mark the app's prices carry and
20
+ * the upgrade. While the trial offer line already pitches Pro, the mark stands
21
+ * alone. Pro accounts have nothing to report.
19
22
  */
20
23
  export function CloudUpgradeStatusWidget({ controller = chatController }: CloudUpgradeStatusWidgetProps) {
21
24
  useAppLanguage();
22
25
  const openUpgrade = useCloudUpgradeAction("status-widget");
23
26
  const openPlan = useCloudPlanAction();
27
+ const offerShowing = useTrialOfferShowing();
28
+ const { nativePaneChrome } = useUiCapabilities();
24
29
  // The status bar takes no keyboard focus; the chip names the command instead.
25
30
  const commandBarKey = useCommandBarShortcut();
26
31
  const [access, setAccess] = useState(() => resolvePlanAccess(apiClient.getCurrentUser()));
@@ -31,15 +36,34 @@ export function CloudUpgradeStatusWidget({ controller = chatController }: CloudU
31
36
  [controller],
32
37
  );
33
38
 
34
- if (!access.signedIn || access.isPayingPro) return null;
39
+ if (access.isTrialActive) {
40
+ return (
41
+ <Button
42
+ label={tf("Pro trial {days}d", { days: access.trialDaysLeft })}
43
+ title={tf("Your plan ({key}, then {command})", { key: commandBarKey, command: "ACM" })}
44
+ variant="plain"
45
+ compact
46
+ stopPropagation
47
+ onPress={openPlan}
48
+ />
49
+ );
50
+ }
51
+ if (access.hasProAccess) return null;
35
52
 
36
- const trial = access.isTrialActive;
37
- const label = trial
38
- ? tf("Pro trial {days}d", { days: access.trialDaysLeft })
39
- : t("delayed data · upgrade");
40
-
41
- const title = trial
42
- ? tf("Your plan ({key}, then {command})", { key: commandBarKey, command: "ACM" })
43
- : tf("Upgrade to Pro ({key}, then {command})", { key: commandBarKey, command: "UPGRADE" });
44
- return <Button label={label} title={title} variant="plain" compact stopPropagation onPress={trial ? openPlan : openUpgrade} />;
53
+ const mark = <DelayedMark minutes={CLOUD_QUOTE_DELAY_MINUTES} />;
54
+ return (
55
+ <Box flexDirection="row" alignItems="center" gap={1} paddingRight={offerShowing || !nativePaneChrome ? 1 : 0} data-gloom-role="delayed-status">
56
+ {mark}
57
+ {offerShowing ? null : (
58
+ <Button
59
+ label={t("delayed data · upgrade")}
60
+ title={tf("Upgrade to Pro ({key}, then {command})", { key: commandBarKey, command: "UPGRADE" })}
61
+ variant="plain"
62
+ compact
63
+ stopPropagation
64
+ onPress={openUpgrade}
65
+ />
66
+ )}
67
+ </Box>
68
+ );
45
69
  }
@@ -1,5 +1,4 @@
1
1
  import type { GloomPlugin, PaneTemplateCreateOptions } from "../../../types/plugin";
2
- import { CommentLettersPane } from "./pane";
3
2
  import { COMMENT_LETTERS_PANE_ID, COMMENT_LETTERS_PLUGIN_ID } from "./types";
4
3
 
5
4
  const createCommentLettersPaneInstance = (options?: PaneTemplateCreateOptions) => {
@@ -37,7 +36,7 @@ export const commentLettersPlugin: GloomPlugin = {
37
36
  reportFreshness: { source: "SEC EDGAR", status: "not-a-feed", basis: "comment letters" },
38
37
  name: "Comment Letters",
39
38
  icon: "L",
40
- component: CommentLettersPane,
39
+ get component(): typeof import("./pane").CommentLettersPane { const { CommentLettersPane } = require("./pane"); return CommentLettersPane; },
41
40
  defaultPosition: "right",
42
41
  defaultMode: "floating",
43
42
  defaultFloatingSize: { width: 100, height: 30 },
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../../api-client";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import type { PsdBoardPayload } from "../../../api-client/commodity-balances";
3
4
  import { unavailableOnServer } from "../shared/cloud-resource";
4
5
  import type { BalanceRow, PriceForecast } from "./model";
@@ -37,7 +38,9 @@ function readBoard(payload: PsdBoardPayload): CommodityBalances {
37
38
  || (payload.marketYear != null && !text(payload.marketYear))) {
38
39
  throw new Error("The server returned an invalid crop balance board");
39
40
  }
40
- if (payload.status === "unavailable" || payload.rows.length === 0) throw new Error(CROPS_NOT_AVAILABLE);
41
+ // The server's source did not answer this time, which may pass; a board with no rows has none yet.
42
+ if (payload.status === "unavailable") throw sourceDidNotAnswer(CROPS_NOT_AVAILABLE);
43
+ if (payload.rows.length === 0) throw new Error(CROPS_NOT_AVAILABLE);
41
44
  return {
42
45
  rows: payload.rows,
43
46
  prices: payload.prices,
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { commandRequestParams } from "../shared/command-tab";
3
3
  import { commodityBalancesHeadless } from "./headless";
4
4
  import { PSD_CROPS, psdCrop, unknownCropMessage } from "./model";
5
- import { CommodityBalancesPane } from "./pane";
6
5
 
7
6
  export const commodityBalancesModule: PluginModule = {
8
7
  panes: [
@@ -10,7 +9,7 @@ export const commodityBalancesModule: PluginModule = {
10
9
  id: "commodity-balances",
11
10
  name: "Crop Balances",
12
11
  icon: "B",
13
- component: CommodityBalancesPane,
12
+ get component(): typeof import("./pane").CommodityBalancesPane { const { CommodityBalancesPane } = require("./pane"); return CommodityBalancesPane; },
14
13
  defaultPosition: "right",
15
14
  defaultMode: "floating",
16
15
  defaultFloatingSize: { width: 108, height: 28 },
@@ -25,7 +24,8 @@ export const commodityBalancesModule: PluginModule = {
25
24
  paneId: "commodity-balances",
26
25
  label: "Crop Balances",
27
26
  description: "World balances for wheat, corn, soybeans, cotton, coffee and sugar, with season-average price forecasts when a table is published.",
28
- keywords: ["wheat", "corn", "soybeans", "cotton", "coffee", "sugar", "crop", "balances", "production", "exports", "stocks", "harvest"],
27
+ keywords: ["wheat", "corn", "soybeans", "cotton", "coffee", "sugar", "crop", "balances", "production", "exports", "stocks", "harvest",
28
+ "usda", "wasde", "grain", "grains", "fertilizer", "fertiliser", "potash"],
29
29
  shortcut: {
30
30
  prefix: "PSD",
31
31
  argKind: "text",
@@ -9,7 +9,7 @@ import {
9
9
  type DataTableColumn,
10
10
  } from "../../../components";
11
11
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
12
- import type { PaneFooterSegment } from "../../../components/layout/pane/footer";
12
+ import { ageFooterSegment, type PaneFooterSegment } from "../../../components/layout/pane/footer";
13
13
  import { useAsyncResource } from "../../../react/async-resource";
14
14
  import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
15
15
  import { usePaneInstance } from "../../../state/app/context";
@@ -96,7 +96,7 @@ export function CommodityBalancesPane({ width, height, focused }: PaneProps) {
96
96
  const updatedAgo = useUpdatedAgo(updatedAt);
97
97
  const info = useMemo<PaneFooterSegment[]>(() => {
98
98
  const year = data?.marketYear;
99
- const updated: PaneFooterSegment[] = updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" }] }] : [];
99
+ const updated: PaneFooterSegment[] = updatedAgo ? [ageFooterSegment(updatedAgo)] : [];
100
100
  return year ? [...updated, { id: "year", parts: [{ text: year, tone: "muted" }] }] : updated;
101
101
  }, [data?.marketYear, updatedAgo]);
102
102
 
@@ -1,5 +1,11 @@
1
1
  import type { CloudCongressMemberPayload, CloudCongressTickerPayload, CloudCongressTradePayload } from "../../../api-client";
2
2
 
3
+ function median(values: number[]): number | null {
4
+ if (!values.length) return null;
5
+ const sorted = [...values].sort((a, b) => a - b);
6
+ return (sorted[Math.floor((sorted.length - 1) / 2)]! + sorted[Math.floor(sorted.length / 2)]!) / 2;
7
+ }
8
+
3
9
  /** Aggregates exactly the distinct trades loaded, including appended years. */
4
10
  export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], metadata: CloudCongressMemberPayload[] = []) {
5
11
  const members = new Map<string, CloudCongressMemberPayload>();
@@ -7,6 +13,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
7
13
  const lags = new Map<string, number[]>();
8
14
  const returns = new Map<string, number[]>();
9
15
  const buys = new Map<string, number[]>();
16
+ const excess = new Map<string, number[]>();
10
17
  const known = new Map(metadata.map((member) => [member.id, member]));
11
18
  const seen = new Set<string>();
12
19
  for (const trade of trades) {
@@ -22,6 +29,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
22
29
  lags.set(memberId, []);
23
30
  returns.set(memberId, []);
24
31
  buys.set(memberId, []);
32
+ excess.set(memberId, []);
25
33
  }
26
34
  const summaries: (CloudCongressMemberPayload | CloudCongressTickerPayload)[] = [member];
27
35
  if (trade.ticker) {
@@ -39,6 +47,7 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
39
47
  returns.get(memberId)!.push(trade.returnSinceTx);
40
48
  if (trade.side === "BUY") buys.get(memberId)!.push(trade.returnSinceTx);
41
49
  }
50
+ if (trade.excessSinceFiling != null && Number.isFinite(trade.excessSinceFiling)) excess.get(memberId)!.push(trade.excessSinceFiling);
42
51
  if (!member.lastFilingDate || trade.filingDate >= member.lastFilingDate) {
43
52
  member.party = trade.party ?? member.party ?? null;
44
53
  member.committees = trade.committees ?? member.committees ?? [];
@@ -57,11 +66,13 @@ export function aggregateLoadedCongress(trades: CloudCongressTradePayload[], met
57
66
  }
58
67
  for (const [id, member] of members) {
59
68
  const values = lags.get(id)!;
60
- const priced = returns.get(id)!.sort((a, b) => a - b);
69
+ const priced = returns.get(id)!;
61
70
  const pricedBuys = buys.get(id)!;
62
71
  member.pricedTradeCount = priced.length;
63
72
  member.pricedBuyCount = pricedBuys.length;
64
- member.medianReturn = priced.length ? (priced[Math.floor((priced.length - 1) / 2)]! + priced[Math.floor(priced.length / 2)]!) / 2 : null;
73
+ member.excessTradeCount = excess.get(id)!.length;
74
+ member.medianReturn = median(priced);
75
+ member.medianExcessSinceFiling = median(excess.get(id)!);
65
76
  member.buyHitRate = pricedBuys.length ? 100 * pricedBuys.filter(value => value > 0).length / pricedBuys.length : null;
66
77
  member.avgLagDays = values.length ? Math.round(values.reduce((a, b) => a + b, 0) / values.length) : null;
67
78
  }
@@ -40,11 +40,24 @@ import {
40
40
  type TradeColumn,
41
41
  type TradeColumnId,
42
42
  } from "./model";
43
- import { renderCongressTradeCell } from "./table";
43
+ import { congressReturnColor, renderCongressTradeCell } from "./table";
44
44
  import { loadCongressHouse } from "./client";
45
45
 
46
46
  const DETAIL_ROW = { labelWidth: 16, emphasis: false } as const;
47
47
 
48
+ /** A window's return, or its excess with SPY's own return beside it. */
49
+ function ReturnRow({ label, value, spy }: { label: string; value: number | null | undefined; spy?: number | null }) {
50
+ return (
51
+ <KeyValueRow
52
+ {...DETAIL_ROW}
53
+ label={label}
54
+ value={formatCongressReturn(value)}
55
+ color={congressReturnColor(value)}
56
+ detail={spy == null ? undefined : `SPY ${formatCongressReturn(spy)}`}
57
+ />
58
+ );
59
+ }
60
+
48
61
  export function TradeDetail({
49
62
  trade,
50
63
  width,
@@ -68,8 +81,10 @@ export function TradeDetail({
68
81
  <KeyValueRow {...DETAIL_ROW} label="asset" value={fit(trade.assetName)} />
69
82
  <KeyValueRow {...DETAIL_ROW} label="amount" value={trade.amount} color={colors.textBright} />
70
83
  <KeyValueRow {...DETAIL_ROW} label="owner" value={trade.owner} />
71
- <KeyValueRow {...DETAIL_ROW} label="tx return" value={formatCongressReturn(trade.returnSinceTx)} />
72
- <KeyValueRow {...DETAIL_ROW} label="filed return" value={formatCongressReturn(trade.returnSinceFiling)} />
84
+ <ReturnRow label="tx return" value={trade.returnSinceTx} />
85
+ <ReturnRow label="tx vs SPY" value={trade.excessSinceTx} spy={trade.spyReturnSinceTx} />
86
+ <ReturnRow label="filed return" value={trade.returnSinceFiling} />
87
+ <ReturnRow label="filed vs SPY" value={trade.excessSinceFiling} spy={trade.spyReturnSinceFiling} />
73
88
  <KeyValueRow {...DETAIL_ROW} label="price as of" value={trade.returnAsOf ?? "--"} />
74
89
  <KeyValueRow {...DETAIL_ROW} label="tx date" value={trade.transactionDate ?? "--"} />
75
90
  <KeyValueRow {...DETAIL_ROW} label="notification" value={trade.notificationDate ?? "--"} />
@@ -292,7 +307,8 @@ export function MemberTradesDetail({
292
307
  // detail adds what the row lacks: how many priced trades stand behind the
293
308
  // return figures, and the committees.
294
309
  const summaryItems: StatItem[] = [
295
- { id: "median", label: "Median return", value: formatCongressReturn(summaryMember.medianReturn), detail: `${summaryMember.pricedTradeCount ?? 0} priced` },
310
+ { id: "median", label: "Median return", value: formatCongressReturn(summaryMember.medianReturn), color: congressReturnColor(summaryMember.medianReturn), detail: `${summaryMember.pricedTradeCount ?? 0} priced` },
311
+ { id: "vs-spy", label: "Median vs SPY", value: formatCongressReturn(summaryMember.medianExcessSinceFiling), color: congressReturnColor(summaryMember.medianExcessSinceFiling), detail: `since filing, ${summaryMember.excessTradeCount ?? 0} priced`, detailVariants: ["since filing"] },
296
312
  { id: "hit", label: "Buy hit rate", value: summaryMember.buyHitRate == null ? "--" : `${summaryMember.buyHitRate.toFixed(0)}%`, detail: `${summaryMember.pricedBuyCount ?? 0} priced buys` },
297
313
  ...(summaryMember.committees ?? []).map((committee): StatItem => ({ id: `committee:${committee}`, label: "Committee", value: committee, wide: true })),
298
314
  ];
@@ -51,6 +51,7 @@ const TRADE_COLUMNS: HeadlessPaneColumn[] = [
51
51
  { key: "owner", header: "Owner" },
52
52
  { key: "returnSinceTx", header: "Tx return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
53
53
  { key: "returnSinceFiling", header: "Filed return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
54
+ { key: "excessSinceFiling", header: "Vs SPY", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
54
55
  ];
55
56
 
56
57
  const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
@@ -73,6 +74,7 @@ const MEMBER_COLUMNS: HeadlessPaneColumn[] = [
73
74
  { key: "avgLagDays", header: "Avg", align: "right", format: (value) => formatLag(typeof value === "number" ? value : null) },
74
75
  { key: "medianReturn", header: "Median return", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
75
76
  { key: "pricedTradeCount", header: "Priced trades" },
77
+ { key: "medianExcessSinceFiling", header: "Median vs SPY", format: value => formatCongressReturn(typeof value === "number" ? value : null) },
76
78
  { key: "buyHitRate", header: "Buy hit", align: "right", format: value => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "--" },
77
79
  { key: "pricedBuyCount", header: "Priced buys" },
78
80
  ];
@@ -1,4 +1,5 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
+ import { dropColumnsToFit, getTableWidth } from "../../../components/ui/table-layout";
2
3
  import type { ShortDateOptions } from "../../../utils/datetime-format";
3
4
  import { formatCompact } from "../../../utils/format";
4
5
  import type { SortDirection } from "../../../utils/sort-values";
@@ -28,6 +29,7 @@ export type DetailMode =
28
29
  export type TradeColumnId =
29
30
  | "returnSinceTx"
30
31
  | "returnSinceFiling"
32
+ | "excessSinceFiling"
31
33
  | "filed"
32
34
  | "tx"
33
35
  | "lag"
@@ -45,6 +47,7 @@ export type TradeColumn = DataTableColumn & {
45
47
  export type MemberColumnId =
46
48
  | "party"
47
49
  | "medianReturn"
50
+ | "medianExcessSinceFiling"
48
51
  | "buyHitRate"
49
52
  | "member"
50
53
  | "district"
@@ -151,6 +154,7 @@ function compareTrade(
151
154
  switch (columnId) {
152
155
  case "returnSinceTx":
153
156
  case "returnSinceFiling":
157
+ case "excessSinceFiling":
154
158
  return compareOptionalNumber(left[columnId], right[columnId]);
155
159
  case "filed":
156
160
  return dateValue(left.filingDate) - dateValue(right.filingDate);
@@ -182,6 +186,7 @@ function compareMember(
182
186
  case "party":
183
187
  return compareText(left.party ?? "", right.party ?? "");
184
188
  case "medianReturn":
189
+ case "medianExcessSinceFiling":
185
190
  case "buyHitRate":
186
191
  return compareOptionalNumber(left[columnId], right[columnId]);
187
192
  case "member":
@@ -203,87 +208,102 @@ function compareMember(
203
208
  }
204
209
  }
205
210
 
211
+ const VS_SPY_COLUMN: TradeColumn = { id: "excessSinceFiling", label: "VS SPY", width: 10, align: "right" };
212
+ const TRADE_MEMBER_MIN_WIDTH = 14;
213
+ /** Narrower than this cuts most member names, so other columns give way first. */
214
+ const MEMBER_READABLE_WIDTH = 18;
215
+
216
+ /**
217
+ * Short of room, the trades give way in this order. The return since the
218
+ * trade goes first: FILE RET% and VS SPY read over the same days, from the
219
+ * filing a follower could act on. Then the dates lose their year, the lag
220
+ * (it follows from the two dates), the owner and the trade date go, and VS
221
+ * SPY outlasts FILE RET%. The detail keeps every figure.
222
+ */
223
+ const TRADE_DROP_ORDER = ["returnSinceTx", "lag", "owner", "tx", "returnSinceFiling"] as const;
224
+
225
+ function tradeColumns(tickerView: boolean, dateWidth: number, memberWidth: number, assetWidth: number | null): TradeColumn[] {
226
+ return [
227
+ { id: "filed", label: "FILED", width: dateWidth, align: "left" },
228
+ { id: "tx", label: "TX", width: dateWidth, align: "left" },
229
+ { id: "lag", label: "LAG", width: 5, align: "right" },
230
+ { id: "member", label: "MEMBER", width: memberWidth, align: "left" },
231
+ { id: "side", label: "SIDE", width: 5, align: "left" },
232
+ ...(!tickerView ? [{ id: "ticker" as const, label: "TICKER", width: 12, align: "left" as const, assetFallback: assetWidth == null }] : []),
233
+ ...(assetWidth != null ? [{ id: "asset" as const, label: "ASSET", width: assetWidth, align: "left" as const }] : []),
234
+ { id: "amount", label: "AMOUNT", width: 14, align: "right" },
235
+ { id: "owner", label: "OWNER", width: 8, align: "left" },
236
+ { id: "returnSinceTx", label: "TX RET%", width: 10, align: "right" },
237
+ { id: "returnSinceFiling", label: "FILE RET%", width: 10, align: "right" },
238
+ VS_SPY_COLUMN,
239
+ ];
240
+ }
241
+
206
242
  export function buildTradeColumns(width: number, tickerView = false): TradeColumn[] {
207
- const filedWidth = 10;
208
- const txWidth = 10;
209
- const lagWidth = 5;
210
- const sideWidth = 5;
211
- const tickerWidth = tickerView ? 0 : 12;
212
- const amountWidth = 14;
213
- const ownerWidth = 8;
214
- const flexWidth = Math.max(
215
- 14,
216
- width - filedWidth - txWidth - lagWidth - sideWidth - tickerWidth - amountWidth - ownerWidth - 36,
217
- );
243
+ // Full dates while dropping TX RET% is enough, then short ones and the rest of the order.
244
+ const long = dropColumnsToFit(tradeColumns(tickerView, 10, MEMBER_READABLE_WIDTH, null), width, TRADE_DROP_ORDER.slice(0, 1));
245
+ const fitted = getTableWidth(long) <= width ? long : dropColumnsToFit(tradeColumns(tickerView, 7, MEMBER_READABLE_WIDTH, null), width, TRADE_DROP_ORDER);
246
+ const shown = new Set(fitted.map((column) => column.id));
247
+ const dateWidth = fitted[0]!.width;
248
+ const layout = (memberWidth: number, assetWidth: number | null) => tradeColumns(tickerView, dateWidth, memberWidth, assetWidth)
249
+ .filter((column) => shown.has(column.id) || column.id === "asset");
250
+ // What the other columns leave, as the table draws them, names the member and then the asset.
251
+ const flexWidth = Math.max(TRADE_MEMBER_MIN_WIDTH, MEMBER_READABLE_WIDTH + width - getTableWidth(fitted));
218
252
  // Member names rarely pass 22 cells; the rest names the asset, which is the
219
253
  // only identification for trades without a ticker (bonds, T-bills, funds).
220
254
  const memberWidth = Math.min(22, Math.max(18, flexWidth - 13));
221
255
  const assetWidth = flexWidth - memberWidth - 1;
222
- const showAsset = assetWidth >= 12;
223
- return [
224
- { id: "filed", label: "FILED", width: filedWidth, align: "left" },
225
- { id: "tx", label: "TX", width: txWidth, align: "left" },
226
- { id: "lag", label: "LAG", width: lagWidth, align: "right" },
227
- { id: "member", label: "MEMBER", width: showAsset ? memberWidth : flexWidth, align: "left" },
228
- { id: "side", label: "SIDE", width: sideWidth, align: "left" },
229
- ...(!tickerView ? [{ id: "ticker" as const, label: "TICKER", width: tickerWidth, align: "left" as const, assetFallback: !showAsset }] : []),
230
- ...(showAsset ? [{ id: "asset" as const, label: "ASSET", width: assetWidth, align: "left" as const }] : []),
231
- { id: "amount", label: "AMOUNT", width: amountWidth, align: "right" },
232
- { id: "owner", label: "OWNER", width: ownerWidth, align: "left" },
233
- { id: "returnSinceTx", label: "TX RET%", width: 10, align: "right" },
234
- { id: "returnSinceFiling", label: "FILE RET%", width: 10, align: "right" },
235
- ];
256
+ return assetWidth >= 12 ? layout(memberWidth, assetWidth) : layout(flexWidth, null);
236
257
  }
237
258
 
259
+ const MEMBER_TRADE_ASSET_MIN_WIDTH = 18;
260
+ /** As on the trades; the member row above also carries their average lag. */
261
+ const MEMBER_TRADE_DROP_ORDER = ["lag", "owner", "tx"] as const;
262
+
238
263
  export function buildMemberTradeColumns(width: number): TradeColumn[] {
239
- const filedWidth = 7;
240
- const txWidth = 7;
241
- const sideWidth = 8;
242
- const tickerWidth = 12;
243
- const amountWidth = 14;
244
- const ownerWidth = 8;
245
- const lagWidth = 5;
246
- const assetWidth = Math.max(
247
- 18,
248
- width - filedWidth - txWidth - sideWidth - tickerWidth - amountWidth - ownerWidth - lagWidth - 9,
249
- );
250
- return [
251
- { id: "filed", label: "FILED", width: filedWidth, align: "left" },
252
- { id: "tx", label: "TX", width: txWidth, align: "left" },
253
- { id: "side", label: "SIDE", width: sideWidth, align: "left" },
254
- { id: "ticker", label: "TICKER", width: tickerWidth, align: "left" },
255
- { id: "amount", label: "AMOUNT", width: amountWidth, align: "right" },
264
+ const layout = (assetWidth: number): TradeColumn[] => [
265
+ { id: "filed", label: "FILED", width: 7, align: "left" },
266
+ { id: "tx", label: "TX", width: 7, align: "left" },
267
+ { id: "side", label: "SIDE", width: 8, align: "left" },
268
+ { id: "ticker", label: "TICKER", width: 12, align: "left" },
269
+ { id: "amount", label: "AMOUNT", width: 14, align: "right" },
256
270
  { id: "asset", label: "ASSET", width: assetWidth, align: "left" },
257
- { id: "owner", label: "OWNER", width: ownerWidth, align: "left" },
258
- { id: "lag", label: "LAG", width: lagWidth, align: "right" },
271
+ { id: "owner", label: "OWNER", width: 8, align: "left" },
272
+ { id: "lag", label: "LAG", width: 5, align: "right" },
273
+ VS_SPY_COLUMN,
259
274
  ];
275
+ const fitted = dropColumnsToFit(layout(MEMBER_TRADE_ASSET_MIN_WIDTH), width, MEMBER_TRADE_DROP_ORDER);
276
+ const shown = new Set(fitted.map((column) => column.id));
277
+ const assetWidth = Math.max(MEMBER_TRADE_ASSET_MIN_WIDTH, MEMBER_TRADE_ASSET_MIN_WIDTH + width - getTableWidth(fitted));
278
+ return layout(assetWidth).filter((column) => shown.has(column.id));
260
279
  }
261
280
 
281
+ /**
282
+ * As on the trades, the median return since the trade gives way before the one
283
+ * against SPY; then the average lag, the last filing, the district and the
284
+ * buy hit rate, which the member's detail repeats.
285
+ */
286
+ const MEMBER_DROP_ORDER = ["medianReturn", "lag", "last", "district", "buyHitRate"] as const;
287
+
262
288
  export function buildMemberColumns(width: number): MemberColumn[] {
263
- const districtWidth = 6;
264
- const tradesWidth = 7;
265
- const buysWidth = 5;
266
- const sellsWidth = 6;
267
- const rangeWidth = 17;
268
- const lastWidth = 7;
269
- const lagWidth = 6;
270
- const memberWidth = Math.max(
271
- 18,
272
- width - districtWidth - tradesWidth - buysWidth - sellsWidth - rangeWidth - lastWidth - lagWidth - 47,
273
- );
274
- return [
289
+ const layout = (memberWidth: number): MemberColumn[] => [
275
290
  { id: "member", label: "MEMBER", width: memberWidth, align: "left" },
276
- { id: "district", label: "DIST", width: districtWidth, align: "left" },
291
+ { id: "district", label: "DIST", width: 6, align: "left" },
277
292
  { id: "party", label: "PARTY", width: 11, align: "left" },
278
- { id: "trades", label: "TRADES", width: tradesWidth, align: "right" },
279
- { id: "buys", label: "BUY", width: buysWidth, align: "right" },
280
- { id: "sells", label: "SELL", width: sellsWidth, align: "right" },
281
- { id: "range", label: "EST RANGE", width: rangeWidth, align: "right" },
282
- { id: "last", label: "LAST", width: lastWidth, align: "left" },
283
- { id: "lag", label: "AVG", width: lagWidth, align: "right" },
293
+ { id: "trades", label: "TRADES", width: 7, align: "right" },
294
+ { id: "buys", label: "BUY", width: 5, align: "right" },
295
+ { id: "sells", label: "SELL", width: 6, align: "right" },
296
+ { id: "range", label: "EST RANGE", width: 17, align: "right" },
297
+ { id: "last", label: "LAST", width: 7, align: "left" },
298
+ { id: "lag", label: "AVG", width: 6, align: "right" },
284
299
  { id: "medianReturn", label: "MED RET%", width: 9, align: "right" },
300
+ { id: "medianExcessSinceFiling", label: "MED VS SPY", width: 10, align: "right" },
285
301
  { id: "buyHitRate", label: "BUY HIT%", width: 9, align: "right" },
286
302
  ];
303
+ const fitted = dropColumnsToFit(layout(MEMBER_READABLE_WIDTH), width, MEMBER_DROP_ORDER);
304
+ const shown = new Set(fitted.map((column) => column.id));
305
+ const memberWidth = Math.max(MEMBER_READABLE_WIDTH, MEMBER_READABLE_WIDTH + width - getTableWidth(fitted));
306
+ return layout(memberWidth).filter((column) => shown.has(column.id));
287
307
  }
288
308
 
289
309
  export function sortedTrades(
@@ -394,6 +414,14 @@ export function mergeCongressPages(
394
414
  return { ...next, trades: [...byTradeId.values()], members };
395
415
  }
396
416
 
417
+ /** Percent to one decimal; a figure that rounds to zero reads 0.0%, never -0.0%. */
397
418
  export function formatCongressReturn(value: number | null | undefined): string {
398
- return value == null || !Number.isFinite(value) ? "--" : `${value > 0 ? "+" : ""}${value.toFixed(1)}%`;
419
+ if (value == null || !Number.isFinite(value)) return "--";
420
+ const shown = roundCongressReturn(value);
421
+ return `${shown > 0 ? "+" : ""}${shown === 0 ? "0.0" : shown.toFixed(1)}%`;
422
+ }
423
+
424
+ /** The figure as shown, so its colour follows the digits: SPY at -0.04% is flat, not red. */
425
+ export function roundCongressReturn(value: number): number {
426
+ return Math.round(value * 10) / 10 || 0;
399
427
  }
@@ -1,6 +1,7 @@
1
1
  import { TextAttributes } from "../../../ui";
2
2
  import { TickerBadgeList, type DataTableCell } from "../../../components";
3
3
  import { colors } from "../../../theme/colors";
4
+ import { signedColor } from "../shared/research-cells";
4
5
  import { formatShortDate } from "../../../utils/datetime-format";
5
6
  import type {
6
7
  CloudCongressMemberPayload,
@@ -11,6 +12,7 @@ import {
11
12
  formatAmountRange,
12
13
  formatCongressReturn,
13
14
  formatLag,
15
+ roundCongressReturn,
14
16
  FILING_DAY_FORMAT,
15
17
  tradeAssetLabel,
16
18
  type MemberColumn,
@@ -18,6 +20,15 @@ import {
18
20
  type TickerColumn,
19
21
  } from "./model";
20
22
 
23
+ /** A return or its excess over SPY takes the shared signed colours wherever CG shows one, by its digits. */
24
+ export function congressReturnColor(value: number | null | undefined): string {
25
+ return signedColor(value == null ? value : roundCongressReturn(value), colors);
26
+ }
27
+
28
+ function congressReturnCell(value: number | null | undefined): DataTableCell {
29
+ return { text: formatCongressReturn(value), color: congressReturnColor(value) };
30
+ }
31
+
21
32
  function sideColor(side: CloudCongressTradePayload["side"]): string {
22
33
  if (side === "BUY") return colors.positive;
23
34
  if (side === "SELL") return colors.negative;
@@ -32,7 +43,7 @@ export function renderCongressTradeCell(
32
43
  rowState: { selected: boolean },
33
44
  ): DataTableCell {
34
45
  switch (column.id) {
35
- case "returnSinceTx": case "returnSinceFiling": return { text: formatCongressReturn(trade[column.id]), color: trade[column.id] == null ? colors.textDim : trade[column.id]! >= 0 ? colors.positive : colors.negative };
46
+ case "returnSinceTx": case "returnSinceFiling": case "excessSinceFiling": return congressReturnCell(trade[column.id]);
36
47
  case "filed":
37
48
  return { text: column.width >= 10 ? trade.filingDate : formatShortDate(trade.filingDate, FILING_DAY_FORMAT), color: colors.textDim };
38
49
  case "tx":
@@ -81,7 +92,8 @@ export function renderCongressMemberCell(
81
92
  ): DataTableCell {
82
93
  switch (column.id) {
83
94
  case "party": return { text: member.party ?? "--", color: colors.textDim };
84
- case "medianReturn": case "buyHitRate": return { text: column.id === "buyHitRate" ? member.buyHitRate == null ? "--" : `${member.buyHitRate.toFixed(0)}%` : formatCongressReturn(member.medianReturn), color: colors.text };
95
+ case "medianReturn": case "medianExcessSinceFiling": return congressReturnCell(member[column.id]);
96
+ case "buyHitRate": return { text: member.buyHitRate == null ? "--" : `${member.buyHitRate.toFixed(0)}%`, color: colors.text };
85
97
  case "member":
86
98
  return { text: member.memberName, color: colors.text };
87
99
  case "district":
@@ -1,7 +1,6 @@
1
1
  import type { PluginCapability } from "../../../capabilities";
2
2
  import type { ConnectionHealthRegistry } from "../../../core/connection-health";
3
3
  import type { PluginModule } from "../plugin-module";
4
- import { ConnectionsPane } from "./pane";
5
4
 
6
5
  export const CONNECTION_HEALTH_CAPABILITY_ID = "application.connection-health";
7
6
 
@@ -35,8 +34,8 @@ export const connectionsModule: PluginModule = {
35
34
  name: "Connections",
36
35
  personalData: "account",
37
36
  icon: "C",
38
- component: ConnectionsPane,
39
- reportFreshness: { status: "not-a-feed", basis: "connection health" },
37
+ get component(): typeof import("./pane").ConnectionsPane { const { ConnectionsPane } = require("./pane"); return ConnectionsPane; },
38
+ reportFreshness: { status: "not-a-feed", basis: "connection health", local: true },
40
39
  defaultPosition: "right",
41
40
  defaultMode: "floating",
42
41
  defaultFloatingSize: { width: 80, height: 24 },