gloomberb 0.16.6 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/package.json +2 -1
  2. package/src/api-client/chat-pane-link.ts +47 -0
  3. package/src/api-client/chat.ts +9 -1
  4. package/src/api-client/data.ts +42 -2
  5. package/src/api-client/doe.ts +19 -0
  6. package/src/api-client/equity-screener.ts +3 -0
  7. package/src/api-client/europe-energy.ts +91 -0
  8. package/src/api-client/index.ts +7 -0
  9. package/src/api-client/letters.ts +41 -0
  10. package/src/api-client/normalizers.ts +36 -6
  11. package/src/api-client/organization-tickers.ts +44 -0
  12. package/src/api-client/request.ts +3 -1
  13. package/src/api-client/types.ts +27 -1
  14. package/src/app/runtime/desktop-deeplink.ts +48 -15
  15. package/src/app/runtime/update.ts +16 -0
  16. package/src/architecture/core-builtin-allowlist.json +1 -1
  17. package/src/architecture/dom-cell-metrics-allowlist.json +102 -0
  18. package/src/ask-gloom/pane-context.ts +23 -19
  19. package/src/ask-gloom/pane-request.ts +2 -2
  20. package/src/cli/commands/fred-series.ts +77 -11
  21. package/src/cli/commands/market.ts +134 -25
  22. package/src/cli/commands/options-chain.ts +75 -1
  23. package/src/cli/commands/overview.ts +29 -20
  24. package/src/cli/commands/ticker.ts +82 -20
  25. package/src/cli/desktop-pane-shot-view.ts +114 -0
  26. package/src/cli/desktop-pane-shot.ts +30 -31
  27. package/src/cli/entry.ts +7 -0
  28. package/src/cli/export-source.ts +43 -0
  29. package/src/cli/freshness.ts +18 -3
  30. package/src/cli/glossary.ts +17 -0
  31. package/src/cli/history-rows.ts +174 -13
  32. package/src/cli/index.ts +14 -6
  33. package/src/cli/known-gaps.ts +14 -3
  34. package/src/cli/listing-arg.ts +20 -2
  35. package/src/cli/listing-suggestion.ts +159 -0
  36. package/src/cli/native-smoke.ts +13 -0
  37. package/src/cli/not-a-ticker.ts +13 -1
  38. package/src/cli/pane-functions/capabilities.ts +16 -8
  39. package/src/cli/pane-functions/catalog.ts +39 -7
  40. package/src/cli/pane-functions/dom.ts +39 -13
  41. package/src/cli/pane-functions/function-help.ts +54 -7
  42. package/src/cli/pane-functions/headless.ts +38 -8
  43. package/src/cli/pane-functions/index.ts +85 -3
  44. package/src/cli/pane-functions/lock-note.ts +56 -0
  45. package/src/cli/pane-functions/report-notices.ts +6 -3
  46. package/src/cli/pane-functions/report.ts +16 -2
  47. package/src/cli/pane-functions/resolver.ts +7 -4
  48. package/src/cli/report-tables.ts +31 -7
  49. package/src/cli/result.ts +54 -11
  50. package/src/cli/symbol-rows.ts +18 -1
  51. package/src/components/chart/composite/axis-overlays.tsx +2 -0
  52. package/src/components/chart/composite/composite-chart.tsx +4 -21
  53. package/src/components/chart/composite/legend.tsx +10 -6
  54. package/src/components/chart/composite/price-axis-labels.tsx +1 -0
  55. package/src/components/command-bar/list/model.ts +2 -0
  56. package/src/components/command-bar/list/view.tsx +2 -1
  57. package/src/components/command-bar/pane-templates/items.ts +1 -1
  58. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
  59. package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
  60. package/src/components/command-bar/workflow/ops.ts +8 -97
  61. package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
  62. package/src/components/data-table/view.tsx +1 -0
  63. package/src/components/feed-data-table/stack-view.tsx +18 -42
  64. package/src/components/index.ts +3 -2
  65. package/src/components/layout/header.tsx +58 -14
  66. package/src/components/layout/missing-pane.tsx +2 -0
  67. package/src/components/layout/shell/chrome.ts +10 -4
  68. package/src/components/layout/shell/index.tsx +1 -0
  69. package/src/components/layout/shell/pane/ask.ts +12 -7
  70. package/src/components/layout/shell/pane/capture.ts +8 -34
  71. package/src/components/markdown-text.tsx +37 -6
  72. package/src/components/reader/article-reader.tsx +91 -266
  73. package/src/components/reader/detail-reader.tsx +210 -0
  74. package/src/components/reader/reader-text.tsx +239 -0
  75. package/src/components/table-view-shared.tsx +8 -4
  76. package/src/components/ticker/badge/list.tsx +15 -4
  77. package/src/components/ui/choice-dialog.tsx +3 -0
  78. package/src/components/ui/column-grid.tsx +206 -0
  79. package/src/components/ui/data-table/types.ts +7 -0
  80. package/src/components/ui/detail-scroll-body.tsx +31 -4
  81. package/src/components/ui/display.tsx +86 -4
  82. package/src/components/ui/fill.ts +8 -0
  83. package/src/components/ui/icon.tsx +11 -1
  84. package/src/components/ui/index.ts +2 -0
  85. package/src/components/ui/list-view.tsx +3 -1
  86. package/src/components/ui/query-bar-keys.tsx +2 -0
  87. package/src/components/ui/query-bar.tsx +9 -1
  88. package/src/components/ui/select-button.tsx +3 -0
  89. package/src/data/config/store/fold-panes.ts +202 -11
  90. package/src/data/config/store/migrations.ts +13 -1
  91. package/src/docs/snapshot.json +29 -0
  92. package/src/i18n/es.ts +14 -0
  93. package/src/i18n/ja.ts +14 -0
  94. package/src/i18n/ko.ts +14 -0
  95. package/src/i18n/zh-cn.ts +14 -0
  96. package/src/i18n/zh-tw.ts +14 -0
  97. package/src/layout/desk-keys.ts +7 -0
  98. package/src/layout/desks.ts +2 -2
  99. package/src/market-data/market/status.ts +67 -6
  100. package/src/market-data/quotes/contributions.ts +15 -0
  101. package/src/market-data/quotes/regular-session-valuation.ts +131 -0
  102. package/src/market-data/quotes/resolution.ts +4 -0
  103. package/src/market-data/quotes/screener-live-quotes.ts +31 -7
  104. package/src/market-data/snapshot-provider.ts +6 -3
  105. package/src/notifications/app-badge.ts +30 -0
  106. package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
  107. package/src/plugins/builtin/account-management/form-components.tsx +57 -8
  108. package/src/plugins/builtin/account-management/keyboard.ts +5 -0
  109. package/src/plugins/builtin/account-management/model.ts +27 -0
  110. package/src/plugins/builtin/account-management/pane.tsx +62 -2
  111. package/src/plugins/builtin/account-management/password-dialog.tsx +3 -2
  112. package/src/plugins/builtin/alerts/cli.ts +6 -4
  113. package/src/plugins/builtin/analytics/index.tsx +1 -1
  114. package/src/plugins/builtin/awards/pane.tsx +29 -18
  115. package/src/plugins/builtin/backtest/pane.tsx +11 -4
  116. package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
  117. package/src/plugins/builtin/buildout/detail/company.tsx +11 -7
  118. package/src/plugins/builtin/buildout/detail/ui.tsx +9 -12
  119. package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
  120. package/src/plugins/builtin/catalysts/model.ts +92 -10
  121. package/src/plugins/builtin/catalysts/pane.tsx +6 -4
  122. package/src/plugins/builtin/cds/index.tsx +21 -30
  123. package/src/plugins/builtin/changelog/index.tsx +11 -12
  124. package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
  125. package/src/plugins/builtin/chart-composer/headless.ts +87 -5
  126. package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
  127. package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
  128. package/src/plugins/builtin/chat/command-bar.ts +6 -8
  129. package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
  130. package/src/plugins/builtin/chat/content/composer.tsx +13 -4
  131. package/src/plugins/builtin/chat/content/footer.tsx +15 -5
  132. package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
  133. package/src/plugins/builtin/chat/content/index.tsx +113 -9
  134. package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
  135. package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
  136. package/src/plugins/builtin/chat/content/scroll.ts +6 -1
  137. package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
  138. package/src/plugins/builtin/chat/ghost-user.ts +1 -1
  139. package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
  140. package/src/plugins/builtin/chat/index.tsx +44 -21
  141. package/src/plugins/builtin/chat/layout.ts +7 -14
  142. package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
  143. package/src/plugins/builtin/chat/message/header.tsx +69 -21
  144. package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
  145. package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
  146. package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
  147. package/src/plugins/builtin/chat/message/render-state.ts +15 -2
  148. package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
  149. package/src/plugins/builtin/chat/message/types.ts +3 -0
  150. package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
  151. package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
  152. package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
  153. package/src/plugins/builtin/chat/pane-state.ts +5 -8
  154. package/src/plugins/builtin/chat/people.ts +202 -0
  155. package/src/plugins/builtin/chat/profile-popover.ts +18 -2
  156. package/src/plugins/builtin/chat/status-widget.tsx +3 -8
  157. package/src/plugins/builtin/chat/unread-badge.ts +65 -0
  158. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
  159. package/src/plugins/builtin/clinical-trials/client.ts +26 -1
  160. package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
  161. package/src/plugins/builtin/clinical-trials/model.ts +269 -0
  162. package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
  163. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
  164. package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
  165. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
  166. package/src/plugins/builtin/cloud/askg/pane.tsx +2 -1
  167. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +3 -2
  168. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
  169. package/src/plugins/builtin/cloud/thesis/detail.tsx +11 -9
  170. package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
  171. package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
  172. package/src/plugins/builtin/commodity-balances/client.ts +208 -0
  173. package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
  174. package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
  175. package/src/plugins/builtin/commodity-balances/model.ts +261 -0
  176. package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
  177. package/src/plugins/builtin/composite-plugins.ts +26 -1
  178. package/src/plugins/builtin/congress-trades/detail.tsx +8 -4
  179. package/src/plugins/builtin/connections/pane.tsx +16 -5
  180. package/src/plugins/builtin/cpi/pane.tsx +5 -4
  181. package/src/plugins/builtin/credit-boards/index.tsx +21 -42
  182. package/src/plugins/builtin/credit-boards/model.ts +0 -2
  183. package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
  184. package/src/plugins/builtin/credit-documents/index.tsx +27 -10
  185. package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
  186. package/src/plugins/builtin/debug/index.tsx +2 -2
  187. package/src/plugins/builtin/dividend-yield/client.ts +3 -1
  188. package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
  189. package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
  190. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  191. package/src/plugins/builtin/docs/blocks.tsx +4 -3
  192. package/src/plugins/builtin/docs/pane.tsx +16 -8
  193. package/src/plugins/builtin/doe/index.tsx +11 -4
  194. package/src/plugins/builtin/doe/pane.tsx +5 -4
  195. package/src/plugins/builtin/doe/prices.ts +84 -0
  196. package/src/plugins/builtin/doe/series.ts +4 -1
  197. package/src/plugins/builtin/earnings/index.tsx +2 -0
  198. package/src/plugins/builtin/earnings-calls/headless.ts +7 -21
  199. package/src/plugins/builtin/earnings-calls/index.tsx +2 -0
  200. package/src/plugins/builtin/earnings-calls/lock-note.ts +17 -0
  201. package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
  202. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  203. package/src/plugins/builtin/equity-screener/headless.ts +95 -3
  204. package/src/plugins/builtin/equity-screener/live.ts +82 -31
  205. package/src/plugins/builtin/equity-screener/model.ts +121 -68
  206. package/src/plugins/builtin/equity-screener/pane.tsx +60 -23
  207. package/src/plugins/builtin/estimate-revisions/client.ts +1 -1
  208. package/src/plugins/builtin/estimate-revisions/headless.ts +9 -2
  209. package/src/plugins/builtin/estimate-revisions/index.tsx +3 -0
  210. package/src/plugins/builtin/estimate-revisions/lock-note.ts +23 -0
  211. package/src/plugins/builtin/executives/index.tsx +18 -0
  212. package/src/plugins/builtin/executives/pane.tsx +3 -1
  213. package/src/plugins/builtin/exposure/charts.tsx +4 -3
  214. package/src/plugins/builtin/filing-events/index.tsx +18 -0
  215. package/src/plugins/builtin/fixed-income/index.tsx +46 -0
  216. package/src/plugins/builtin/fixed-income/model.ts +27 -0
  217. package/src/plugins/builtin/fixed-income/pane.tsx +163 -0
  218. package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
  219. package/src/plugins/builtin/futures/index.tsx +24 -7
  220. package/src/plugins/builtin/futures/series.ts +23 -2
  221. package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
  222. package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
  223. package/src/plugins/builtin/gpu/equities.tsx +9 -6
  224. package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
  225. package/src/plugins/builtin/insider/detail.tsx +20 -12
  226. package/src/plugins/builtin/insider/index.tsx +2 -2
  227. package/src/plugins/builtin/iv-history/format.ts +3 -0
  228. package/src/plugins/builtin/iv-history/headless.ts +3 -1
  229. package/src/plugins/builtin/iv-history/index.tsx +6 -1
  230. package/src/plugins/builtin/iv-history/model.ts +3 -1
  231. package/src/plugins/builtin/iv-history/screen-pane.tsx +16 -6
  232. package/src/plugins/builtin/iv-history/universe.ts +1 -1
  233. package/src/plugins/builtin/jobs/headless.ts +6 -20
  234. package/src/plugins/builtin/jobs/index.tsx +2 -0
  235. package/src/plugins/builtin/jobs/lock-note.ts +16 -0
  236. package/src/plugins/builtin/jodi/client.ts +220 -0
  237. package/src/plugins/builtin/jodi/headless.ts +63 -0
  238. package/src/plugins/builtin/jodi/index.tsx +32 -0
  239. package/src/plugins/builtin/jodi/model.ts +75 -0
  240. package/src/plugins/builtin/jodi/pane.tsx +141 -0
  241. package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
  242. package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
  243. package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
  244. package/src/plugins/builtin/market-heatmap/live.ts +2 -1
  245. package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
  246. package/src/plugins/builtin/market-movers/table.ts +2 -2
  247. package/src/plugins/builtin/market-valuation/align.ts +10 -9
  248. package/src/plugins/builtin/mna/distress/model.ts +52 -1
  249. package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
  250. package/src/plugins/builtin/mna/index.tsx +2 -0
  251. package/src/plugins/builtin/news/wire/index.tsx +2 -2
  252. package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
  253. package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
  254. package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
  255. package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
  256. package/src/plugins/builtin/news/wire/mutes.ts +2 -1
  257. package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
  258. package/src/plugins/builtin/openfda/client.ts +123 -15
  259. package/src/plugins/builtin/openfda/model.ts +163 -0
  260. package/src/plugins/builtin/openfda/pane.tsx +109 -65
  261. package/src/plugins/builtin/openfda/types.ts +25 -0
  262. package/src/plugins/builtin/options/index.tsx +2 -1
  263. package/src/plugins/builtin/options/model-spot.ts +94 -0
  264. package/src/plugins/builtin/options/open-interest-as-of.ts +63 -0
  265. package/src/plugins/builtin/options/view.tsx +56 -10
  266. package/src/plugins/builtin/options-scenario/client.ts +33 -9
  267. package/src/plugins/builtin/options-scenario/csp.ts +168 -0
  268. package/src/plugins/builtin/options-scenario/evidence.ts +3 -0
  269. package/src/plugins/builtin/options-scenario/headless.ts +24 -8
  270. package/src/plugins/builtin/options-scenario/index.tsx +3 -1
  271. package/src/plugins/builtin/options-scenario/pane.tsx +32 -6
  272. package/src/plugins/builtin/pe-band/model.ts +3 -2
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
  274. package/src/plugins/builtin/plugin-module.ts +3 -1
  275. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
  276. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
  277. package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
  278. package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
  279. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
  280. package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
  281. package/src/plugins/builtin/power/evidence.ts +32 -3
  282. package/src/plugins/builtin/power/gas-client.ts +110 -0
  283. package/src/plugins/builtin/power/gas-model.ts +194 -0
  284. package/src/plugins/builtin/power/gas.tsx +145 -0
  285. package/src/plugins/builtin/power/headless.ts +4 -1
  286. package/src/plugins/builtin/power/index.tsx +19 -9
  287. package/src/plugins/builtin/power/pane.tsx +2 -3
  288. package/src/plugins/builtin/power/prices-headless.ts +75 -7
  289. package/src/plugins/builtin/power/prices.tsx +36 -13
  290. package/src/plugins/builtin/primary-dealers/client.ts +108 -0
  291. package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
  292. package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
  293. package/src/plugins/builtin/primary-dealers/model.ts +206 -0
  294. package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
  295. package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
  296. package/src/plugins/builtin/research/analyst-model.ts +9 -5
  297. package/src/plugins/builtin/research/headless-schema.ts +2 -1
  298. package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
  299. package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
  300. package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
  301. package/src/plugins/builtin/risk-factors/index.tsx +18 -0
  302. package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
  303. package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
  304. package/src/plugins/builtin/scanner/headless.ts +9 -2
  305. package/src/plugins/builtin/scanner/index.ts +3 -0
  306. package/src/plugins/builtin/scanner/lock-note.ts +17 -0
  307. package/src/plugins/builtin/sec/filing-display.tsx +3 -36
  308. package/src/plugins/builtin/sec/index.tsx +131 -43
  309. package/src/plugins/builtin/sec/model.ts +148 -2
  310. package/src/plugins/builtin/sec/row-summaries.ts +105 -0
  311. package/src/plugins/builtin/sectors/index.tsx +2 -0
  312. package/src/plugins/builtin/sectors/sector-model.ts +2 -2
  313. package/src/plugins/builtin/series-inventory.ts +2 -1
  314. package/src/plugins/builtin/shared/cloud-denied.ts +39 -8
  315. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  316. package/src/plugins/builtin/shared/pro-lock-note.ts +58 -0
  317. package/src/plugins/builtin/shared/series-list-detail.tsx +10 -5
  318. package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
  319. package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
  320. package/src/plugins/builtin/shared/zip-text.ts +46 -0
  321. package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
  322. package/src/plugins/builtin/social-mentions/client.ts +3 -3
  323. package/src/plugins/builtin/social-mentions/headless.ts +6 -2
  324. package/src/plugins/builtin/social-mentions/index.tsx +2 -1
  325. package/src/plugins/builtin/social-mentions/lock-note.ts +15 -0
  326. package/src/plugins/builtin/supply-chain/flow.tsx +15 -13
  327. package/src/plugins/builtin/supply-chain/graph.tsx +6 -4
  328. package/src/plugins/builtin/supply-chain/pane.tsx +8 -5
  329. package/src/plugins/builtin/themes/model.ts +2 -2
  330. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +25 -1
  331. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +87 -23
  332. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
  333. package/src/plugins/builtin/ticker-detail/headless.ts +6 -3
  334. package/src/plugins/builtin/ticker-detail/overview/components.tsx +105 -104
  335. package/src/plugins/builtin/ticker-detail/overview/model.ts +12 -3
  336. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -13
  337. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
  338. package/src/plugins/builtin/time-sales/headless.ts +22 -0
  339. package/src/plugins/builtin/trace-bonds/client.ts +196 -0
  340. package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
  341. package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
  342. package/src/plugins/builtin/trace-bonds/model.ts +115 -0
  343. package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
  344. package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
  345. package/src/plugins/builtin/treasury-daily/client.ts +96 -0
  346. package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
  347. package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
  348. package/src/plugins/builtin/treasury-daily/model.ts +69 -0
  349. package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
  350. package/src/plugins/builtin/vol-surface/pane.tsx +6 -3
  351. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
  352. package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
  353. package/src/plugins/builtin-editorial.ts +1 -1
  354. package/src/plugins/catalog-browser.ts +25 -2
  355. package/src/plugins/marketplace/model.ts +57 -21
  356. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  357. package/src/renderers/dom/build-assets.ts +14 -5
  358. package/src/renderers/dom/data-table/index.tsx +53 -18
  359. package/src/renderers/dom/data-table/row.tsx +1 -1
  360. package/src/renderers/dom/desktop/controls.tsx +2 -1
  361. package/src/renderers/dom/desktop/icons.tsx +25 -0
  362. package/src/renderers/dom/desktop/list-view.tsx +1 -1
  363. package/src/renderers/dom/desktop/menu.tsx +1 -1
  364. package/src/renderers/dom/desktop/popover.tsx +6 -15
  365. package/src/renderers/dom/desktop/query-bar.tsx +3 -1
  366. package/src/renderers/dom/host/chart-surface.tsx +7 -2
  367. package/src/renderers/dom/host/input.tsx +27 -3
  368. package/src/renderers/dom/host/resize-rows.ts +111 -0
  369. package/src/renderers/dom/host/sticky-scroll.ts +22 -3
  370. package/src/renderers/dom/host/style.ts +14 -0
  371. package/src/renderers/dom/host/surface-3d.tsx +8 -5
  372. package/src/renderers/dom/host/surface-fit.ts +10 -6
  373. package/src/renderers/dom/host/text.tsx +2 -2
  374. package/src/renderers/dom/styles.css +85 -10
  375. package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
  376. package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
  377. package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
  378. package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
  379. package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
  380. package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
  381. package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
  382. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  383. package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
  384. package/src/renderers/electrobun/view/app-services.ts +2 -22
  385. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
  386. package/src/renderers/electrobun/view/main.tsx +2 -0
  387. package/src/renderers/electrobun/view/news-source.ts +42 -0
  388. package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
  389. package/src/series/validate.ts +6 -1
  390. package/src/shares/api.ts +5 -1
  391. package/src/shares/dialog.tsx +2 -1
  392. package/src/shares/location.ts +31 -0
  393. package/src/shares/open.ts +26 -0
  394. package/src/shares/pane-image.ts +18 -6
  395. package/src/shares/personal-pane.ts +42 -54
  396. package/src/sources/gloomberb-cloud/index.ts +7 -2
  397. package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
  398. package/src/sources/history-coverage.ts +22 -4
  399. package/src/sources/history-result.ts +3 -1
  400. package/src/sources/provider-errors.ts +2 -1
  401. package/src/sources/provider-router/batches.ts +18 -2
  402. package/src/sources/provider-router/financials.ts +5 -0
  403. package/src/sources/provider-router/index.ts +2 -1
  404. package/src/state/app/context/index.tsx +3 -2
  405. package/src/test-support/builtin-panes.ts +31 -0
  406. package/src/tickers/listing-venues.ts +22 -0
  407. package/src/tickers/search/index.ts +7 -2
  408. package/src/tickers/search/ranking.ts +19 -0
  409. package/src/time-series/chart-data.ts +2 -0
  410. package/src/time-series/parsed-history-cache.ts +41 -11
  411. package/src/time-series/resolve.ts +47 -15
  412. package/src/types/config.ts +1 -1
  413. package/src/types/data-provider.ts +13 -1
  414. package/src/types/financials.ts +35 -0
  415. package/src/types/headless.ts +22 -0
  416. package/src/types/plugin.ts +46 -6
  417. package/src/types/price-history.ts +2 -0
  418. package/src/ui/host.tsx +6 -0
  419. package/src/utils/crypto-pair.ts +1 -1
  420. package/src/utils/currency-pair.ts +66 -0
  421. package/src/utils/decimal-comma.ts +57 -0
  422. package/src/utils/financial-statements.ts +25 -0
  423. package/src/utils/fundamentals.ts +41 -1
  424. package/src/utils/guards.ts +25 -0
  425. package/src/utils/local-utc-offset.ts +37 -0
  426. package/src/utils/plugin-proxy-hosts.json +8 -1
  427. package/src/components/chart/composite/crosshair-readout.tsx +0 -150
  428. package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
  429. package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
@@ -6,16 +6,21 @@ import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator
6
6
  import { useShortcut } from "../../../react/input";
7
7
  import { isPlainKey } from "../../../utils/keyboard";
8
8
  import { instrumentFromTicker } from "../../../market-data/request-types";
9
- import { useDebouncedPluginPaneState } from "../../runtime";
9
+ import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
10
10
  import { usePaneSettingValue } from "../../../state/app/context";
11
11
  import type { ScrollBoxRenderable } from "../../../ui";
12
- import { EmptyState, FeedDataTableStackView, Spinner, StatGrid, usePaneNoticeFooter, useTableLoadMore, type FeedDataTableItem, type StatItem } from "../../../components";
12
+ import { EmptyState, FeedDataTableStackView, QueryBar, Spinner, TickerBadgeList, usePaneNoticeFooter, useTableLoadMore, type FeedDataTableItem, type PaneHint, type QueryBarFilter } from "../../../components";
13
+ import { displayWidth } from "../../../utils/format";
14
+ import type { DetailFact } from "../../../components/reader/detail-reader";
15
+ import type { ThemeColors } from "../../../theme/colors";
16
+ import { useThemeColors } from "../../../theme/theme-context";
17
+ import { useOpenDescription } from "../shared/open-description";
18
+ import { toneColor } from "../shared/research-cells";
13
19
  import { mayBeUsEquityOrFundTicker, mayBeUsEquityTicker, secFilingItemCodes } from "../../../utils/sec";
14
- import { parseForm4Xml, transactionTypeLabel } from "../insider/insider-data";
20
+ import { parseForm4Xml } from "../insider/insider-data";
15
21
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
16
22
  import {
17
23
  formatFilingMetaDate,
18
- buildInsiderTransactionTitle,
19
24
  buildInsiderTransactionDetailBody,
20
25
  renderFilingNotice,
21
26
  } from "./filing-display";
@@ -37,6 +42,11 @@ import { SignInWall } from "../cloud/auth-actions";
37
42
  import { ETF_FILING_FORMS, ETF_FORMS_SETTING, filterFilingsByForms, parseFormsSetting, SEC_FILING_FETCH_LIMIT } from "./forms";
38
43
  import { createSecHeadless, fundFilingsEmpty, secHeadless } from "./headless";
39
44
  import {
45
+ ALL_FORMS,
46
+ COMPANY_FORM_GROUPS,
47
+ filingTone,
48
+ filterFilingsByGroup,
49
+ FUND_FORM_GROUPS,
40
50
  getFilingDisplayTitle,
41
51
  getFilingColumnText,
42
52
  getFormDescription,
@@ -45,6 +55,7 @@ import {
45
55
  secIssuerLabel,
46
56
  secReportedAcceptance,
47
57
  } from "./model";
58
+ import { rowSummary, rowSummaryTarget } from "./row-summaries";
48
59
  import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
49
60
  import { createSecFilingSearchProvider, resetSecFilingFocusRequests, useSecFilingFocusRequest } from "./command-bar-search";
50
61
 
@@ -53,6 +64,8 @@ export { secHeadless } from "./headless";
53
64
  const SEC_FILING_PAGE_SIZE = 50;
54
65
  const NO_FILINGS: SecFilingItem[] = [];
55
66
  const OWNERSHIP_FORMS = new Set(["3", "4", "5"]);
67
+ /** At most this many rows on screen have their insider forms and fund flow notices read for their row text. */
68
+ const OWNERSHIP_ROWS_READ = 40;
56
69
 
57
70
  function formatFiledAt(filing: SecFilingItem): string {
58
71
  return formatFilingMetaDate(filing.filingDate);
@@ -122,16 +135,6 @@ function buildDetailBodyWithDocuments({
122
135
  return lines.join("\n");
123
136
  }
124
137
 
125
- function buildForm4Preview(content: string | null): string | null {
126
- if (!content) return null;
127
- const transactions = parseForm4Xml(content);
128
- const tx = transactions[0];
129
- if (!tx) return null;
130
- if (transactions.length === 1) return `${tx.reportedName} — ${buildInsiderTransactionTitle(tx)}`;
131
- const types = [...new Set(transactions.map((transaction) => transactionTypeLabel(transaction.transactionType)))];
132
- return `${tx.reportedName} — ${transactions.length} transactions: ${types.join(", ")}`;
133
- }
134
-
135
138
  function buildForm4Detail(content: string | null, filing: SecFilingItem): string {
136
139
  if (!content) return buildDetailBody(filing);
137
140
  const transactions = parseForm4Xml(content);
@@ -149,6 +152,9 @@ function buildForm4Detail(content: string | null, filing: SecFilingItem): string
149
152
  }
150
153
 
151
154
  function toFeedItems(
155
+ colors: ThemeColors,
156
+ issuerTicker: { symbol: string; open: () => void } | null,
157
+ showIssuer: boolean,
152
158
  filings: SecFilingItem[],
153
159
  selectedAccessionNumber: string | undefined,
154
160
  contentCache: Map<string, string | null>,
@@ -169,10 +175,10 @@ function toFeedItems(
169
175
  ? `${buildDetailBody(filing)}\n\nReadable filing content was not available for this document.`
170
176
  : buildDetailBody(filing);
171
177
 
172
- // For Form 4s, build structured preview and detail from parsed XML
173
- const form4Preview = isOwnership && hasFetchedContent
174
- ? buildForm4Preview(fetchedContent ?? null)
175
- : null;
178
+ // An insider form read once says who traded and how, a fund's 24F-2NT its
179
+ // year's flows; the row says it too.
180
+ const summaryTarget = rowSummaryTarget(filing);
181
+ const readSummary = summaryTarget ? rowSummary(filing.form, contentCache.get(summaryTarget.accessionNumber)) : null;
176
182
  const form4Detail = isOwnership && hasFetchedContent
177
183
  ? buildForm4Detail(fetchedContent ?? null, filing)
178
184
  : null;
@@ -197,13 +203,28 @@ function toFeedItems(
197
203
  const enrichedTitle = formDesc
198
204
  ? `${displayTitle} — ${formDesc}`
199
205
  : displayTitle;
206
+ const summary = readSummary?.title ?? getFilingColumnText(filing);
207
+ const items = secFilingItemCodes(filing.items);
208
+ const facts: DetailFact[] = [
209
+ { label: "Issuer", value: filing.companyName || "Name unavailable" },
210
+ ...(issuerTicker ? [{ label: "Ticker", value: issuerTicker.symbol, link: { label: `Open ${issuerTicker.symbol}`, onOpen: issuerTicker.open } }] : []),
211
+ { label: "CIK", value: filing.cik },
212
+ { label: "Form", value: formDesc ? `${filing.form} · ${formDesc}` : filing.form, color: toneColor(filingTone(filing, readSummary?.direction), colors) },
213
+ { label: "Filed", value: formatFiledAt(filing) },
214
+ ...(acceptedAt ? [{ label: "SEC-reported acceptance", value: acceptedAt }] : []),
215
+ ...(items ? [{ label: "Items", value: items }] : []),
216
+ { label: "Accession", value: filing.accessionNumber },
217
+ ];
200
218
 
201
219
  return {
202
220
  id: filing.accessionNumber,
203
221
  eyebrow: filing.form,
204
- title: form4Preview || getFilingColumnText(filing),
222
+ eyebrowColor: toneColor(filingTone(filing, readSummary?.direction), colors),
223
+ // A ticker shared by several filers names the filer on each row.
224
+ title: showIssuer && filing.companyName ? `${filing.companyName}: ${summary}` : summary,
205
225
  timestamp: filing.filingDate,
206
226
  detailTitle: enrichedTitle,
227
+ detailFacts: facts,
207
228
  detailMeta: [
208
229
  secIssuerLabel(filing),
209
230
  `Filed ${formatFiledAt(filing)}`,
@@ -218,8 +239,11 @@ function toFeedItems(
218
239
  });
219
240
  }
220
241
 
221
- function SecView({ width, height, focused }: { width: number; height: number; focused: boolean }) {
242
+ /** `paneType` is set for the standalone pane and absent in the DES tab, whose header already names the security. */
243
+ function SecView({ width, height, focused, paneType }: { width: number; height: number; focused: boolean; paneType?: string }) {
222
244
  const { ticker } = usePaneTickerIdentity();
245
+ const colors = useThemeColors();
246
+ const openDescription = useOpenDescription();
223
247
  const selectionKey = `selectedIdx:${ticker?.metadata.ticker ?? "none"}`;
224
248
  const [selectedIdx, setSelectedIdx] = useDebouncedPluginPaneState<number>(selectionKey, 0);
225
249
  // The open filing is what the pane shows, so it is pane state: it restores
@@ -251,10 +275,22 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
251
275
  const issuerFilings = useResolvedEntryValue(filingsEntry) ?? NO_FILINGS;
252
276
  // The filter runs over every filing the service returned for the issuer,
253
277
  // the same list the unfiltered pane shows, not over a first page of it.
254
- const filings = useMemo(
278
+ const formFilings = useMemo(
255
279
  () => (forms ? filterFilingsByForms(issuerFilings, forms) : issuerFilings),
256
280
  [forms, issuerFilings],
257
281
  );
282
+ // The fund view (ETF) has its own groups; each view keeps its own choice.
283
+ const groups = forms ? FUND_FORM_GROUPS : COMPANY_FORM_GROUPS;
284
+ const [storedGroup, setFormGroup] = usePluginPaneState<string>(forms ? "fundFormGroup" : "formGroup", ALL_FORMS);
285
+ const formGroup = groups.some((group) => group.value === storedGroup) ? storedGroup : ALL_FORMS;
286
+ // A ticker can map to more than one filer (CIK); each can be listed alone.
287
+ const issuers = useMemo(() => secFilingIssuers(formFilings), [formFilings]);
288
+ const [storedIssuer, setIssuer] = usePluginPaneState<string>("issuerCik", ALL_FORMS);
289
+ const issuerCik = issuers.some((issuer) => issuer.cik === storedIssuer) ? storedIssuer : ALL_FORMS;
290
+ const filings = useMemo(() => {
291
+ const grouped = filterFilingsByGroup(formFilings, groups, formGroup);
292
+ return issuerCik === ALL_FORMS ? grouped : grouped.filter((filing) => filing.cik === issuerCik);
293
+ }, [formFilings, formGroup, groups, issuerCik]);
258
294
  const filterHistoryCapped = !!forms && issuerFilings.length >= SEC_FILING_FETCH_LIMIT;
259
295
  const [visibleCount, setVisibleCount] = useState(SEC_FILING_PAGE_SIZE);
260
296
  const visibleFilings = useMemo(() => filings.slice(0, visibleCount), [filings, visibleCount]);
@@ -266,7 +302,7 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
266
302
  );
267
303
  useEffect(() => {
268
304
  setVisibleCount(SEC_FILING_PAGE_SIZE);
269
- }, [ticker?.metadata.ticker, ticker?.metadata.exchange, formsSetting]);
305
+ }, [ticker?.metadata.ticker, ticker?.metadata.exchange, formsSetting, formGroup, issuerCik]);
270
306
  const loading = filingsEntry?.phase === "loading" || filingsEntry?.phase === "refreshing";
271
307
  // A restored or shared open filing can sit past the first page, or be gone
272
308
  // from the feed entirely.
@@ -284,7 +320,7 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
284
320
  const error = filingsEntry?.error && filingsEntry.error.reasonCode !== "NO_DATA" ? filingsEntry.error.message : null;
285
321
  // Hosted, the only filings source is Gloom Cloud, which needs an account.
286
322
  const cloudSession = useResearchCloudSession();
287
- const authWall = filings.length === 0 && isCloudSessionRequired(error);
323
+ const authWall = formFilings.length === 0 && isCloudSessionRequired(error);
288
324
 
289
325
  const openFiling = openItemId
290
326
  ? filings.find((filing) => filing.accessionNumber === openItemId) ?? null
@@ -298,14 +334,30 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
298
334
  || documentsEntry?.phase === "refreshing"
299
335
  );
300
336
 
301
- // Only the filing in view needs its content; queueing every ownership form in
302
- // the list fires dozens of SEC requests the user never looks at.
337
+ // The open filing's content comes first. Insider forms say who traded only
338
+ // in their text, so the ones around the cursor are read after it, one at a
339
+ // time, for their rows; the rest of the list is never queued, which would
340
+ // fire dozens of requests for rows nobody looks at. Read text is cached.
303
341
  const selectedFiling = visibleFilings[selectedIdx];
304
- const contentTargets = useMemo(() => [
342
+ // The rows on screen, as a share of the list so it holds for terminal rows and desktop pixels alike.
343
+ const [onScreen, setOnScreen] = useState({ first: 0, count: height });
344
+ const trackScroll = useCallback(() => {
345
+ loadMore();
346
+ const box = scrollRef.current;
347
+ if (!box?.viewport || !(box.scrollHeight > 0) || visibleFilings.length === 0) return;
348
+ const first = Math.floor((box.scrollTop / box.scrollHeight) * visibleFilings.length);
349
+ const count = Math.ceil((box.viewport.height / box.scrollHeight) * visibleFilings.length) + 1;
350
+ setOnScreen((current) => (current.first === first && current.count === count ? current : { first, count }));
351
+ }, [loadMore, visibleFilings.length]);
352
+ const nearbyOwnership = useMemo(() => [
353
+ ...(selectedFiling ? [selectedFiling] : []),
354
+ ...visibleFilings.slice(onScreen.first, onScreen.first + Math.min(OWNERSHIP_ROWS_READ, onScreen.count)),
355
+ ].map(rowSummaryTarget).filter((target): target is SecFilingItem => !!target), [onScreen, selectedFiling, visibleFilings]);
356
+ const viewedTargets = useMemo(() => [
305
357
  ...(openFiling ? [openFiling] : []),
306
358
  ...buildInlineFilingContentTargets(openFiling, openDocuments),
307
- ...(selectedFiling && OWNERSHIP_FORMS.has(selectedFiling.form.trim()) ? [selectedFiling] : []),
308
- ], [openDocuments, openFiling, selectedFiling]);
359
+ ], [openDocuments, openFiling]);
360
+ const contentTargets = useMemo(() => [...viewedTargets, ...nearbyOwnership], [nearbyOwnership, viewedTargets]);
309
361
  const { contentCache, contentErrors, retry: retryContent, loadingKey } = useSecFilingContentCache({
310
362
  scopeKey: `${ticker?.metadata.ticker ?? "none"}:${ticker?.metadata.exchange ?? ""}:${eligibleTicker}`,
311
363
  targets: contentTargets,
@@ -352,14 +404,24 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
352
404
  }
353
405
  }, [selectedIdx, setSelectedIdx, visibleFilings.length]);
354
406
 
407
+ // The pane's own ticker opens its description; a fund or equity both have one.
408
+ const issuerTicker = useMemo(() => (ticker && paneType ? {
409
+ symbol: ticker.metadata.ticker,
410
+ open: () => openDescription(ticker.metadata.ticker, ticker.metadata.exchange || undefined),
411
+ } : null), [openDescription, paneType, ticker]);
412
+ const hints = useMemo<PaneHint[]>(() => (issuerTicker
413
+ ? [{ id: "sec-description", key: "d", label: "es", title: `Open ${issuerTicker.symbol}`, onPress: issuerTicker.open }]
414
+ : []), [issuerTicker]);
355
415
  usePaneStatusLinkFooter({
356
416
  registrationId: "sec",
357
417
  focused,
358
418
  url: openFiling?.filingUrl,
359
419
  source: openFiling?.form,
360
420
  label: "filing",
361
- loading: loading || loadingDocuments || !!loadingKey,
421
+ // Rows read in the background for their insiders do not hold up the pane.
422
+ loading: loading || loadingDocuments || (!!loadingKey && viewedTargets.some((target) => target.accessionNumber === loadingKey)),
362
423
  error: authWall ? null : error ?? detailError,
424
+ hints,
363
425
  showOpenHint: !!openFiling?.filingUrl,
364
426
  });
365
427
 
@@ -373,25 +435,48 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
373
435
  title: "Fund filings",
374
436
  });
375
437
 
376
- // One cell per filer: a ticker can map to more than one CIK.
377
- const issuerItems = useMemo<StatItem[]>(() => secFilingIssuers(filings).map((issuer) => ({
378
- id: issuer.cik,
379
- label: "Issuer",
380
- value: issuer.companyName || "Name unavailable",
381
- detail: `CIK ${issuer.cik}`,
382
- })), [filings]);
438
+ const queryFilters = useMemo<QueryBarFilter[]>(() => [
439
+ {
440
+ id: "form",
441
+ label: "Form",
442
+ value: formGroup,
443
+ defaultValue: ALL_FORMS,
444
+ options: [{ value: ALL_FORMS, label: forms ? "All fund forms" : "All forms" }, ...groups.map((group) => ({ value: group.value, label: group.label }))],
445
+ onChange: (value: string) => { setFormGroup(value); setSelectedIdx(0, { immediate: true }); setOpenItemId(null); },
446
+ },
447
+ ...(issuers.length > 1 ? [{
448
+ id: "issuer",
449
+ label: "Filer",
450
+ value: issuerCik,
451
+ defaultValue: ALL_FORMS,
452
+ options: [{ value: ALL_FORMS, label: "All filers" }, ...issuers.map((issuer) => ({ value: issuer.cik, label: issuer.companyName || `CIK ${issuer.cik}` }))],
453
+ onChange: (value: string) => { setIssuer(value); setSelectedIdx(0, { immediate: true }); setOpenItemId(null); },
454
+ }] : []),
455
+ ], [formGroup, forms, groups, issuerCik, issuers, setFormGroup, setIssuer, setOpenItemId, setSelectedIdx]);
456
+ // The ticker and the filer once, beside the filters: every row is their filing.
457
+ const issuerMeta = issuers.length === 1 ? secIssuerLabel(issuers[0]!).replace(/CIK 0+/, "CIK ") : undefined;
458
+ const queryBar = (
459
+ <QueryBar
460
+ width={width}
461
+ filters={queryFilters}
462
+ meta={issuerMeta}
463
+ leading={issuerTicker
464
+ ? <TickerBadgeList symbols={[issuerTicker.symbol]} width={displayWidth(issuerTicker.symbol) + 2} listed onOpen={issuerTicker.open} />
465
+ : undefined}
466
+ />
467
+ );
383
468
 
384
469
  if (!ticker) {
385
470
  return <EmptyState title="No ticker selected." message="Select a ticker to view SEC filings." />;
386
471
  }
387
472
  if (!eligibleTicker) {
388
- return renderFilingNotice(forms ? "Fund filings are only shown for US-listed funds and equities." : "SEC filings are only shown for US equities.", width);
473
+ return renderFilingNotice(forms ? "Fund filings are only shown for US-listed funds and equities." : "SEC filings are only shown for US equities.");
389
474
  }
390
475
  if (authWall) return <SignInWall placement="sec-signin" action="view SEC filings" needsVerification={cloudSession.needsVerification} />;
391
476
  if (loading && filings.length === 0) return <Spinner label="Loading SEC filings..." />;
392
- if (error && filings.length === 0) return <EmptyState title="SEC filings unavailable." message={error} />;
393
- if (filings.length === 0) {
394
- return renderFilingNotice(forms ? `No fund filings for ${ticker.metadata.ticker}.` : `No recent SEC filings for ${ticker.metadata.ticker}.`, width);
477
+ if (error && formFilings.length === 0) return <EmptyState title="SEC filings unavailable." message={error} />;
478
+ if (formFilings.length === 0) {
479
+ return renderFilingNotice(forms ? `No fund filings for ${ticker.metadata.ticker}.` : `No recent SEC filings for ${ticker.metadata.ticker}.`);
395
480
  }
396
481
 
397
482
  return (
@@ -400,6 +485,9 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
400
485
  height={height}
401
486
  focused={focused}
402
487
  items={toFeedItems(
488
+ colors,
489
+ issuerTicker,
490
+ issuers.length > 1 && issuerCik === ALL_FORMS,
403
491
  visibleFilings,
404
492
  openFiling?.accessionNumber,
405
493
  contentCache,
@@ -414,12 +502,12 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
414
502
  openItemId={openItemId}
415
503
  onOpenItemIdChange={setOpenItemId}
416
504
  prefetchDetail={prefetchDocuments}
417
- rootBefore={<StatGrid items={issuerItems} width={width} />}
505
+ rootBefore={queryBar}
418
506
  sourceLabel="Form"
419
507
  titleLabel="Filing"
420
- emptyStateTitle="No SEC filings."
508
+ emptyStateTitle={formGroup === ALL_FORMS ? "No SEC filings." : "No filings of these forms."}
421
509
  scrollRef={scrollRef}
422
- onBodyScrollActivity={loadMore}
510
+ onBodyScrollActivity={trackScroll}
423
511
  />
424
512
  );
425
513
  }
@@ -46,11 +46,77 @@ export function getFilingDisplayTitle(filing: SecFilingItem): string {
46
46
  return description ? `${formLabel} | ${description}` : formLabel;
47
47
  }
48
48
 
49
- /** The adjacent FORM column already identifies the form. Keep its meaning here. */
49
+ /**
50
+ * 8-K items in a few words, as a reader scans them: what happened, not the
51
+ * SEC's full caption. 9.01 (exhibits) comes with most reports and says little.
52
+ */
53
+ const CURRENT_REPORT_ITEMS: Record<string, string> = {
54
+ "1.01": "Material agreement",
55
+ "1.02": "Agreement terminated",
56
+ "1.03": "Bankruptcy or receivership",
57
+ "1.04": "Mine safety",
58
+ "1.05": "Cybersecurity incident",
59
+ "2.01": "Acquisition or disposal",
60
+ "2.02": "Results of operations",
61
+ "2.03": "New financial obligation",
62
+ "2.04": "Obligation accelerated",
63
+ "2.05": "Exit or restructuring costs",
64
+ "2.06": "Impairment",
65
+ "3.01": "Listing standard notice",
66
+ "3.02": "Unregistered equity sale",
67
+ "3.03": "Shareholder rights modified",
68
+ "4.01": "Auditor change",
69
+ "4.02": "Non-reliance on financials",
70
+ "5.01": "Change in control",
71
+ "5.02": "Officer or director change",
72
+ "5.03": "Charter or bylaws amended",
73
+ "5.04": "Benefit plan trading suspension",
74
+ "5.05": "Code of ethics change",
75
+ "5.06": "Shell company status change",
76
+ "5.07": "Shareholder vote results",
77
+ "5.08": "Director nominations",
78
+ "7.01": "Regulation FD disclosure",
79
+ "8.01": "Other events",
80
+ "9.01": "Exhibits",
81
+ };
82
+
83
+ const isCurrentReport = (form: string) => /^(?:8-K|6-K)(?:\/A)?$/i.test(form.trim());
84
+
85
+ /** `Results of operations; Exhibits` from `2.02,9.01`; exhibits only when nothing else is reported. */
86
+ function currentReportItemsText(items: string | null | undefined): string | null {
87
+ const codes = (secFilingItemCodes(items) ?? "").split(",").filter(Boolean);
88
+ const named = codes.filter((code) => code !== "9.01" || codes.length === 1).map((code) => CURRENT_REPORT_ITEMS[code] ?? `Item ${code}`);
89
+ return named.length > 0 ? named.join("; ") : null;
90
+ }
91
+
92
+ /**
93
+ * The period a 10-K or 10-Q covers, read from its inline XBRL document name
94
+ * (`aapl-20260627.htm`), which by convention carries the period end. A date
95
+ * after the filing, or more than 15 months before it, is not taken.
96
+ */
97
+ function periodicReportPeriod(filing: Pick<SecFilingItem, "form" | "primaryDocument" | "filingDate">): string | null {
98
+ if (!/^(?:10-K|10-Q|10-KT|10-QT|20-F|40-F)(?:\/A)?$/i.test(filing.form.trim())) return null;
99
+ const match = /^[a-z0-9.]+-(\d{4})(\d{2})(\d{2})(?:x\d+)?\.html?$/i.exec(filing.primaryDocument?.split("/").pop() ?? "");
100
+ if (!match) return null;
101
+ const end = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
102
+ const iso = `${match[1]}-${match[2]}-${match[3]}`;
103
+ const filed = new Date(filing.filingDate).getTime();
104
+ if (!Number.isFinite(end.getTime()) || end.toISOString().slice(0, 10) !== iso || !Number.isFinite(filed)) return null;
105
+ const days = (filed - end.getTime()) / 86_400_000;
106
+ return days >= 0 && days <= 460 ? iso : null;
107
+ }
108
+
109
+ /** The adjacent FORM column already identifies the form. Keep its meaning here: what the filing reports. */
50
110
  export function getFilingColumnText(filing: SecFilingItem): string {
51
111
  const description = getMeaningfulPrimaryDescription(filing);
52
112
  const meaning = getFormDescription(filing.form);
53
- if (!description) return meaning || "Filing";
113
+ if (!description) {
114
+ const items = isCurrentReport(filing.form) ? currentReportItemsText(filing.items) : null;
115
+ if (items) return /\/A$/i.test(filing.form.trim()) ? `${items} (amended)` : items;
116
+ const period = periodicReportPeriod(filing);
117
+ if (period) return `${meaning || "Report"}, period ended ${period}`;
118
+ return meaning || "Filing";
119
+ }
54
120
  return meaning && normalizeComparableText(description) !== normalizeComparableText(meaning)
55
121
  ? `${description} | ${meaning}`
56
122
  : description;
@@ -99,6 +165,22 @@ export function getFormDescription(form: string): string {
99
165
  case "NPORT-P": return "Monthly Portfolio Holdings Report";
100
166
  case "NPORT-EX": return "Portfolio Holdings Exhibit";
101
167
  case "24F-2NT": return "Annual Notice of Securities Sold";
168
+ case "SD": return "Conflict Minerals Report";
169
+ case "S-3": return "Shelf Registration";
170
+ case "S-8": return "Employee Plan Registration";
171
+ case "FWP": return "Free Writing Prospectus";
172
+ case "DEFA14A": return "Additional Proxy Materials";
173
+ case "PX14A6G": return "Shareholder Exempt Solicitation";
174
+ case "11-K": return "Employee Plan Annual Report";
175
+ case "ARS": return "Annual Report to Shareholders";
176
+ case "NT 10-K": return "Late Annual Report Notice";
177
+ case "NT 10-Q": return "Late Quarterly Report Notice";
178
+ case "25-NSE": return "Delisting Notice";
179
+ case "6-K": return "Foreign Issuer Current Report";
180
+ case "40-F": return "Annual Report (Canadian)";
181
+ case "424B2":
182
+ case "424B3":
183
+ case "424B5": return "Prospectus";
102
184
  default: {
103
185
  const base = normalized.endsWith("/A") ? getFormDescription(normalized.slice(0, -2)) : "";
104
186
  return base ? `${base} (Amended)` : "";
@@ -106,6 +188,70 @@ export function getFormDescription(form: string): string {
106
188
  }
107
189
  }
108
190
 
191
+ /** The groups of the Form filter: a reader looks for reports, events or insiders, not one form code. */
192
+ export interface FormGroup {
193
+ value: string;
194
+ label: string;
195
+ matches: (form: string) => boolean;
196
+ }
197
+
198
+ const upper = (form: string) => form.trim().toUpperCase().replace(/\/A$/, "");
199
+ const oneOf = (...forms: string[]) => (form: string) => forms.includes(upper(form));
200
+
201
+ export const ALL_FORMS = "all";
202
+
203
+ export const COMPANY_FORM_GROUPS: readonly FormGroup[] = [
204
+ { value: "periodic", label: "Annual & quarterly", matches: oneOf("10-K", "10-Q", "10-KT", "10-QT", "20-F", "40-F", "NT 10-K", "NT 10-Q", "NT 20-F") },
205
+ { value: "current", label: "Current reports", matches: (form) => isCurrentReport(form) || upper(form) === "8-K12B" },
206
+ { value: "insider", label: "Insider", matches: oneOf("3", "4", "5", "144") },
207
+ { value: "ownership", label: "13D / 13G", matches: (form) => /^(?:SC|SCHEDULE) 13[DG]$/.test(upper(form)) },
208
+ { value: "proxy", label: "Proxy", matches: (form) => /^(?:DEF|DEFA|DEFM|PRE|PREM|DFAN)14[AC]$/.test(upper(form).replace(/\s+/g, "")) },
209
+ { value: "offerings", label: "Registrations & offerings", matches: (form) => /^(?:S-\d|F-\d|424B\d|FWP|S-8 POS|POS AM|425)/.test(upper(form)) },
210
+ ];
211
+
212
+ export const FUND_FORM_GROUPS: readonly FormGroup[] = [
213
+ { value: "reports", label: "Shareholder reports", matches: oneOf("N-CSR", "N-CSRS", "N-30D") },
214
+ { value: "holdings", label: "Holdings", matches: oneOf("NPORT-P", "NPORT-EX") },
215
+ { value: "prospectus", label: "Prospectus & registration", matches: oneOf("N-1A", "N-2", "485APOS", "485BPOS", "485BXT", "497", "497J", "497K") },
216
+ { value: "census", label: "Flows & census", matches: oneOf("N-CEN", "24F-2NT") },
217
+ ];
218
+
219
+ /** Filings in a group; `all` and an unknown group keep every filing. */
220
+ export function filterFilingsByGroup<T extends { form: string }>(filings: readonly T[], groups: readonly FormGroup[], value: string): T[] {
221
+ const group = groups.find((entry) => entry.value === value);
222
+ return group ? filings.filter((filing) => group.matches(filing.form)) : [...filings];
223
+ }
224
+
225
+ export type FilingTone = "positive" | "negative" | "warning" | "accent" | "muted" | "text";
226
+
227
+ /**
228
+ * How much a filing matters, in the tone every research pane gives a state:
229
+ * events that can sink a company (bankruptcy, delisting, non-reliance, a late
230
+ * report) red, an insider sale red and a purchase green, a planned sale, an
231
+ * activist stake, a management or auditor change amber, the periodic reports,
232
+ * earnings and fund shareholder reports in the accent, and routine paperwork
233
+ * quiet. A fund's annual notice of securities sold reads green for a year of
234
+ * net creations and red for net redemptions. The form text never changes, so
235
+ * the colour is not the only signal.
236
+ */
237
+ export function filingTone(filing: Pick<SecFilingItem, "form" | "items">, insiderDirection?: "buy" | "sell" | null): FilingTone {
238
+ const form = upper(filing.form);
239
+ if (/^NT /.test(form)) return "negative";
240
+ if (isCurrentReport(filing.form)) {
241
+ const codes = (secFilingItemCodes(filing.items) ?? "").split(",");
242
+ if (codes.some((code) => code === "1.03" || code === "3.01" || code === "4.02")) return "negative";
243
+ if (codes.some((code) => ["2.05", "2.06", "4.01", "5.01", "5.02", "1.05"].includes(code))) return "warning";
244
+ if (codes.includes("2.02")) return "accent";
245
+ return "text";
246
+ }
247
+ if (form === "4" || form === "5" || form === "24F-2NT") return insiderDirection === "sell" ? "negative" : insiderDirection === "buy" ? "positive" : form === "24F-2NT" ? "muted" : "text";
248
+ if (form === "144") return "warning";
249
+ if (/13D$/.test(form)) return "warning";
250
+ if (/^(?:10-K|10-Q|10-KT|10-QT|20-F|40-F|N-CSR|N-CSRS|N-30D|N-1A|N-2)$/.test(form)) return "accent";
251
+ if (/^(?:3|SC 13G|SCHEDULE 13G|SD|497|497J|497K|485BPOS|485BXT|NPORT-P|NPORT-EX)$/.test(form)) return "muted";
252
+ return "text";
253
+ }
254
+
109
255
  export function buildSecFilingRows(filings: readonly SecFilingItem[]) {
110
256
  return filings.map((filing) => {
111
257
  return {
@@ -0,0 +1,105 @@
1
+ import type { SecFilingItem } from "../../../types/data-provider";
2
+ import { decodeHtmlEntities } from "../../../utils/html-entities";
3
+ import { formatCompact } from "../../../utils/format";
4
+ import { parseForm4Xml, transactionTypeLabel } from "../insider/insider-data";
5
+ import { buildInsiderTransactionTitle } from "./filing-display";
6
+
7
+ /**
8
+ * What a small XML filing's own text says, for its row: who traded and which
9
+ * way on an insider form, how much money came in or went out on a fund's
10
+ * annual notice of securities sold.
11
+ */
12
+ export interface RowSummary {
13
+ title: string;
14
+ /** An open-market purchase or sale, or a fund's net creations or redemptions. */
15
+ direction: "buy" | "sell" | null;
16
+ }
17
+
18
+ const OWNERSHIP_FORMS = /^(?:3|4|5|144)(?:\/A)?$/i;
19
+ const FUND_FLOW_FORM = /^24F-2NT(?:\/A)?$/i;
20
+
21
+ const isOwnershipForm = (form: string) => OWNERSHIP_FORMS.test(form.trim());
22
+
23
+ /**
24
+ * The document a row is read from. Gloom Cloud returns a Form 3, 4 or 5 as
25
+ * its XML; a Form 144 and a 24F-2NT come as the SEC's rendered page unless
26
+ * asked for their XML, which a separate key keeps apart from the page the
27
+ * open filing shows. Other forms are not read for their rows.
28
+ */
29
+ export function rowSummaryTarget(filing: SecFilingItem): SecFilingItem | null {
30
+ const form = filing.form.trim();
31
+ if (!isOwnershipForm(form) && !FUND_FLOW_FORM.test(form)) return null;
32
+ if (/^[345](?:\/A)?$/i.test(form)) return filing;
33
+ const url = filing.primaryDocumentUrl?.replace(/\/xsl[^/]+\//, "/");
34
+ if (!url || url === filing.primaryDocumentUrl) return null;
35
+ const document = url.split("/").pop() ?? "primary_doc.xml";
36
+ return { ...filing, accessionNumber: `${filing.accessionNumber}:${document}`, primaryDocument: document, primaryDocumentUrl: url, filingUrl: url };
37
+ }
38
+
39
+ function tagText(xml: string, tag: string): string {
40
+ const match = new RegExp(`<(?:[\\w.-]+:)?${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:[\\w.-]+:)?${tag}\\s*>`, "i").exec(xml);
41
+ return decodeHtmlEntities((match?.[1] ?? "").replace(/<[^>]*>/g, "").trim());
42
+ }
43
+
44
+ const money = (value: number) => `$${formatCompact(value)}`;
45
+
46
+ /**
47
+ * A Form 144 is a notice that someone means to sell: who, how many shares
48
+ * and what they were worth when it was filed.
49
+ */
50
+ function form144Summary(xml: string): RowSummary | null {
51
+ const name = tagText(xml, "nameOfPersonForWhoseAccountTheSecuritiesAreToBeSold");
52
+ if (!name) return null;
53
+ const shares = Number(tagText(xml, "noOfUnitsSold"));
54
+ const value = Number(tagText(xml, "aggregateMarketValue"));
55
+ const amount = [
56
+ Number.isFinite(shares) && shares > 0 ? `${formatCompact(shares)} shares` : "",
57
+ Number.isFinite(value) && value > 0 ? `(${money(value)})` : "",
58
+ ].filter(Boolean).join(" ");
59
+ return { title: `${name}: proposed sale${amount ? ` of ${amount}` : ""}`, direction: null };
60
+ }
61
+
62
+ /** The reporting owner of a Form 3, which lists holdings and no transactions. */
63
+ function initialOwnershipSummary(xml: string): RowSummary | null {
64
+ const name = tagText(xml, "rptOwnerName");
65
+ return name ? { title: `${name}: initial holdings`, direction: null } : null;
66
+ }
67
+
68
+ const signedMoney = (value: number) => `${value < 0 ? "-" : "+"}${money(Math.abs(value))}`;
69
+
70
+ /**
71
+ * A fund's 24F-2NT: what it sold (created) and redeemed over its fiscal year,
72
+ * and the difference, which is the year's net flow.
73
+ */
74
+ function fundFlowSummary(xml: string): RowSummary | null {
75
+ const sold = Number(tagText(xml, "aggregateSalePriceOfSecuritiesSold"));
76
+ const redeemed = Number(tagText(xml, "aggregatePriceOfSecuritiesRedeemedOrRepurchasedInFiscalYear"));
77
+ if (!tagText(xml, "aggregateSalePriceOfSecuritiesSold") || !Number.isFinite(sold) || !Number.isFinite(redeemed)) return null;
78
+ const yearEnd = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(tagText(xml, "lastDayOfFiscalYear"));
79
+ const net = sold - redeemed;
80
+ return {
81
+ title: `${yearEnd ? `Year to ${yearEnd[3]}-${yearEnd[1]}-${yearEnd[2]}: ` : ""}net flow ${signedMoney(net)} (sold ${money(sold)}, redeemed ${money(redeemed)})`,
82
+ direction: net > 0 ? "buy" : net < 0 ? "sell" : null,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * The row text of a filing whose content has been read, or null when it was
88
+ * not read or says nothing a row can use. An insider form with one
89
+ * transaction says it in full; one with several lists their kinds.
90
+ */
91
+ export function rowSummary(form: string, content: string | null | undefined): RowSummary | null {
92
+ if (!content) return null;
93
+ if (FUND_FLOW_FORM.test(form.trim())) return fundFlowSummary(content);
94
+ if (!isOwnershipForm(form)) return null;
95
+ const base = form.trim().replace(/\/A$/i, "");
96
+ if (base === "144") return form144Summary(content);
97
+ const transactions = parseForm4Xml(content);
98
+ const first = transactions[0];
99
+ if (!first) return base === "3" ? initialOwnershipSummary(content) : null;
100
+ const codes = new Set(transactions.map((transaction) => transaction.transactionType));
101
+ const direction = codes.has("P") && !codes.has("S") ? "buy" : codes.has("S") && !codes.has("P") ? "sell" : null;
102
+ if (transactions.length === 1) return { title: `${first.reportedName}: ${buildInsiderTransactionTitle(first)}`, direction };
103
+ const kinds = [...new Set(transactions.map((transaction) => transactionTypeLabel(transaction.transactionType)))];
104
+ return { title: `${first.reportedName}: ${transactions.length} transactions, ${kinds.join(", ")}`, direction };
105
+ }
@@ -395,6 +395,8 @@ export const sectorsModule: PluginModule = {
395
395
  name: "Sector Performance",
396
396
  icon: "S",
397
397
  component: SectorPerformancePane,
398
+ // BI and THEM open this pane: THEM sets the theme param; the ETF lists are preferences.
399
+ identitySettings: [],
398
400
  defaultPosition: "right",
399
401
  defaultMode: "floating",
400
402
  defaultFloatingSize: { width: 82, height: 18 },
@@ -7,7 +7,7 @@ import { mergePriceHistoryIntegrity, pricePointIntegrity, type PriceHistoryInteg
7
7
  import {
8
8
  getExtendedSessionDisplay,
9
9
  getRegularSessionDisplay,
10
- marketStateLabel,
10
+ extendedSessionStateLabel,
11
11
  type ExtendedSession,
12
12
  } from "../../../market-data/market/status";
13
13
  import {
@@ -359,7 +359,7 @@ export function buildSectorColumns(width: number, extendedSessions: readonly Ext
359
359
  const extendedColumns = extendedSessions.map((session): SectorColumn => ({
360
360
  id: session === "PRE" ? "preMarket" : "afterHours",
361
361
  // Named for the session it trades in, as the header labels it.
362
- label: marketStateLabel(session),
362
+ label: extendedSessionStateLabel(session),
363
363
  width: EXTENDED_COLUMN_WIDTH,
364
364
  align: "right",
365
365
  }));