gloomberb 0.17.4 → 0.17.6

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 (384) hide show
  1. package/package.json +3 -1
  2. package/patches/electrobun@1.18.1.patch +80 -0
  3. package/src/api-client/chat.ts +9 -0
  4. package/src/api-client/commodity-balances.ts +77 -0
  5. package/src/api-client/data.ts +35 -3
  6. package/src/api-client/earnings.ts +10 -0
  7. package/src/api-client/index.ts +5 -0
  8. package/src/api-client/insider-signals.ts +75 -0
  9. package/src/api-client/market-discovery.ts +1 -1
  10. package/src/api-client/normalizers.ts +1 -0
  11. package/src/api-client/paths.ts +9 -0
  12. package/src/api-client/research-activity.ts +10 -3
  13. package/src/api-client/socket.ts +10 -0
  14. package/src/api-client/types.ts +25 -1
  15. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
  16. package/src/app/runtime/broker-import.ts +23 -21
  17. package/src/app/runtime/plugin-bindings.ts +13 -7
  18. package/src/app/runtime/update.ts +64 -4
  19. package/src/app.tsx +2 -0
  20. package/src/architecture/core-builtin-allowlist.json +2 -10
  21. package/src/ask-gloom/pane-context.ts +11 -1
  22. package/src/brokers/broker-portfolio-sync.ts +66 -0
  23. package/src/brokers/sync-broker-instance.ts +75 -41
  24. package/src/cli/commands/market.ts +101 -34
  25. package/src/cli/commands/options-chain.ts +20 -6
  26. package/src/cli/commands/overview.ts +54 -13
  27. package/src/cli/commands/search.ts +1 -1
  28. package/src/cli/commands/system.ts +19 -1
  29. package/src/cli/commands/ticker.ts +24 -4
  30. package/src/cli/context.ts +13 -0
  31. package/src/cli/errors.ts +15 -2
  32. package/src/cli/fx-pair.ts +77 -0
  33. package/src/cli/glossary.ts +34 -4
  34. package/src/cli/history-rows.ts +25 -11
  35. package/src/cli/index.ts +30 -12
  36. package/src/cli/listing-arg.ts +61 -138
  37. package/src/cli/listing-suggestion.ts +3 -21
  38. package/src/cli/pane-functions/catalog.ts +50 -0
  39. package/src/cli/pane-functions/freshness.ts +44 -9
  40. package/src/cli/pane-functions/function-help.ts +9 -9
  41. package/src/cli/pane-functions/headless.ts +19 -7
  42. package/src/cli/pane-functions/index.ts +63 -5
  43. package/src/cli/pane-functions/market-session.ts +18 -3
  44. package/src/cli/pane-functions/resolver.ts +29 -8
  45. package/src/cli/pane-functions/screenshot.ts +2 -0
  46. package/src/cli/report-tables.ts +11 -4
  47. package/src/cli/result.ts +2 -0
  48. package/src/cli/series-search.ts +6 -1
  49. package/src/cli/ticker-resolution.ts +1 -2
  50. package/src/cli/unknown-command.ts +18 -0
  51. package/src/components/chart/composite/composite-chart.tsx +80 -19
  52. package/src/components/chart/composite/format.ts +17 -0
  53. package/src/components/chart/composite/legend.tsx +26 -15
  54. package/src/components/chart/composite/panel-labels.ts +38 -0
  55. package/src/components/chart/composite/panel-series.ts +1 -0
  56. package/src/components/chart/composite/panel-surface.tsx +25 -7
  57. package/src/components/chart/composite/price-axis-labels.tsx +16 -3
  58. package/src/components/chart/composite/scene.ts +60 -2
  59. package/src/components/chart/composite/series-axis-tags.tsx +67 -0
  60. package/src/components/chart/composite/tools.ts +2 -2
  61. package/src/components/chart/composite/types.ts +28 -0
  62. package/src/components/chart/core/view-reset.ts +46 -0
  63. package/src/components/command-bar/commands/collection.ts +1 -1
  64. package/src/components/command-bar/commands/direct/index.ts +48 -1
  65. package/src/components/command-bar/commands/registry.ts +8 -0
  66. package/src/components/command-bar/list/model.ts +7 -0
  67. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  68. package/src/components/command-bar/panel/prompt-binding.ts +8 -0
  69. package/src/components/command-bar/routes/root/command-items.ts +17 -2
  70. package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
  71. package/src/components/command-bar/routes/root/provider-search.ts +6 -0
  72. package/src/components/command-bar/routes/root/runtime.ts +3 -0
  73. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
  74. package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
  75. package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
  76. package/src/components/command-bar/surface/index.tsx +7 -1
  77. package/src/components/command-bar/workflow/collection-actions.ts +6 -5
  78. package/src/components/command-bar/workflow/ops.ts +8 -5
  79. package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
  80. package/src/components/command-bar/workflow/tickers.ts +1 -1
  81. package/src/components/form-modal/confirm.tsx +1 -0
  82. package/src/components/form-modal/request.ts +2 -0
  83. package/src/components/layout/detached-pane-shell.tsx +6 -1
  84. package/src/components/layout/pane/content.tsx +11 -8
  85. package/src/components/layout/pane/sidebar.tsx +24 -11
  86. package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
  87. package/src/components/layout/shell/index.tsx +5 -1
  88. package/src/components/layout/shell/menu.ts +8 -0
  89. package/src/components/layout/shell/pane/capture.ts +29 -12
  90. package/src/components/onboarding/wizard-model.ts +1 -1
  91. package/src/components/onboarding/wizard-positions.ts +18 -10
  92. package/src/components/ticker/badge/format.ts +64 -15
  93. package/src/components/ticker/badge/index.tsx +32 -11
  94. package/src/components/ticker/badge/list.tsx +4 -3
  95. package/src/components/ticker/badge/text.tsx +2 -2
  96. package/src/components/ticker/list-table-view.tsx +8 -1
  97. package/src/components/ui/confirm-dialog.tsx +30 -4
  98. package/src/components/ui/data-table/opentui/index.tsx +61 -20
  99. package/src/components/ui/data-table/opentui/model.ts +104 -0
  100. package/src/components/ui/fields.tsx +5 -0
  101. package/src/components/ui/inline-quick-add.tsx +4 -1
  102. package/src/components/ui/query-bar-keys.tsx +29 -5
  103. package/src/components/ui/query-bar.tsx +29 -4
  104. package/src/components/ui/windowed-list.tsx +27 -0
  105. package/src/data/config/store/normalize.ts +8 -0
  106. package/src/docs/snapshot.json +248 -5
  107. package/src/i18n/es.ts +3 -1
  108. package/src/i18n/ja.ts +3 -1
  109. package/src/i18n/ko.ts +3 -1
  110. package/src/i18n/zh-cn.ts +3 -1
  111. package/src/i18n/zh-tw.ts +3 -1
  112. package/src/layout-marketplace/cloud.ts +16 -5
  113. package/src/layout-marketplace/gallery.tsx +4 -4
  114. package/src/layout-marketplace/linked-sync.ts +9 -3
  115. package/src/layout-marketplace/linked.ts +24 -3
  116. package/src/layout-marketplace/model.ts +39 -4
  117. package/src/market-data/coordinator/events.ts +5 -1
  118. package/src/market-data/coordinator/index.ts +38 -1
  119. package/src/market-data/coordinator/quotes.ts +4 -0
  120. package/src/market-data/futures-contract.ts +53 -0
  121. package/src/market-data/hooks.tsx +225 -30
  122. package/src/market-data/market/bar-period.ts +71 -0
  123. package/src/market-data/market/format.ts +14 -2
  124. package/src/market-data/market/freshness.ts +28 -1
  125. package/src/market-data/market/status.ts +152 -13
  126. package/src/market-data/model-dividend-yield.ts +76 -0
  127. package/src/market-data/quotes/freshness.ts +30 -1
  128. package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
  129. package/src/market-data/quotes/regular-session-valuation.ts +32 -1
  130. package/src/plugins/builtin/analytics/metrics.ts +8 -4
  131. package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
  132. package/src/plugins/builtin/analytics/risk-model.ts +74 -15
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
  134. package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
  135. package/src/plugins/builtin/broker-manager/model.ts +2 -2
  136. package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
  137. package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
  138. package/src/plugins/builtin/chart-composer/formula.ts +233 -0
  139. package/src/plugins/builtin/chart-composer/headless.ts +27 -2
  140. package/src/plugins/builtin/chart-composer/index.tsx +3 -2
  141. package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
  142. package/src/plugins/builtin/chart-composer/presets.ts +91 -2
  143. package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
  144. package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
  145. package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
  146. package/src/plugins/builtin/chart-composer/settings.ts +16 -4
  147. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  148. package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
  149. package/src/plugins/builtin/chat/content/footer.tsx +5 -2
  150. package/src/plugins/builtin/chat/content/index.tsx +12 -5
  151. package/src/plugins/builtin/chat/content/mentions.ts +50 -21
  152. package/src/plugins/builtin/chat/content/scroll.ts +12 -5
  153. package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
  154. package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
  155. package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
  156. package/src/plugins/builtin/chat/controller/index.ts +88 -17
  157. package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
  158. package/src/plugins/builtin/chat/controller/messages.ts +14 -2
  159. package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
  160. package/src/plugins/builtin/chat/controller/state.ts +25 -0
  161. package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
  162. package/src/plugins/builtin/chat/controller/view.ts +4 -0
  163. package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
  164. package/src/plugins/builtin/chat/conversations.ts +12 -5
  165. package/src/plugins/builtin/chat/index.tsx +1 -1
  166. package/src/plugins/builtin/chat/layout.ts +23 -5
  167. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  168. package/src/plugins/builtin/chat/share/flow.tsx +18 -9
  169. package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
  170. package/src/plugins/builtin/chat/sidebar.tsx +6 -1
  171. package/src/plugins/builtin/chat/unread-badge.ts +38 -7
  172. package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
  173. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
  174. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
  175. package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
  176. package/src/plugins/builtin/commodity-balances/client.ts +51 -193
  177. package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
  178. package/src/plugins/builtin/commodity-balances/model.ts +5 -180
  179. package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
  180. package/src/plugins/builtin/custom-view/index.tsx +1 -1
  181. package/src/plugins/builtin/custom-view/loader.ts +57 -7
  182. package/src/plugins/builtin/custom-view/sources.ts +146 -0
  183. package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
  184. package/src/plugins/builtin/earnings/board-model.ts +33 -3
  185. package/src/plugins/builtin/earnings/board-table.ts +59 -14
  186. package/src/plugins/builtin/earnings/board.tsx +36 -3
  187. package/src/plugins/builtin/earnings/headless.ts +24 -5
  188. package/src/plugins/builtin/earnings/history-model.ts +9 -1
  189. package/src/plugins/builtin/earnings/history-table.ts +16 -5
  190. package/src/plugins/builtin/earnings/history.tsx +40 -10
  191. package/src/plugins/builtin/earnings/index.tsx +3 -1
  192. package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
  193. package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
  194. package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
  195. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  196. package/src/plugins/builtin/econ-statistics/view.ts +4 -0
  197. package/src/plugins/builtin/futures/model.ts +5 -4
  198. package/src/plugins/builtin/futures/series.ts +38 -1
  199. package/src/plugins/builtin/futures-curve/client.ts +4 -4
  200. package/src/plugins/builtin/futures-curve/headless.ts +33 -8
  201. package/src/plugins/builtin/futures-curve/index.tsx +4 -4
  202. package/src/plugins/builtin/futures-curve/model.ts +109 -6
  203. package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
  204. package/src/plugins/builtin/insider/headless.ts +74 -4
  205. package/src/plugins/builtin/insider/index.tsx +99 -5
  206. package/src/plugins/builtin/insider/signals-client.ts +69 -0
  207. package/src/plugins/builtin/insider/signals-model.ts +146 -0
  208. package/src/plugins/builtin/insider/signals.tsx +176 -0
  209. package/src/plugins/builtin/iv-history/client.ts +41 -2
  210. package/src/plugins/builtin/iv-history/headless.ts +24 -16
  211. package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
  212. package/src/plugins/builtin/jodi/client.ts +42 -205
  213. package/src/plugins/builtin/jodi/headless.ts +13 -4
  214. package/src/plugins/builtin/jodi/model.ts +18 -43
  215. package/src/plugins/builtin/jodi/pane.tsx +5 -0
  216. package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
  217. package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
  218. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  219. package/src/plugins/builtin/macro-day/release-table.ts +3 -3
  220. package/src/plugins/builtin/market-movers/client.ts +32 -6
  221. package/src/plugins/builtin/market-movers/headless.ts +7 -2
  222. package/src/plugins/builtin/market-movers/model.ts +64 -3
  223. package/src/plugins/builtin/market-movers/screener.ts +20 -9
  224. package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
  225. package/src/plugins/builtin/market-valuation/headless.ts +17 -4
  226. package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
  227. package/src/plugins/builtin/market-valuation/view.ts +10 -0
  228. package/src/plugins/builtin/mna/model.ts +2 -1
  229. package/src/plugins/builtin/options-positioning/client.ts +10 -0
  230. package/src/plugins/builtin/options-positioning/headless.ts +88 -13
  231. package/src/plugins/builtin/options-positioning/index.tsx +2 -2
  232. package/src/plugins/builtin/options-positioning/model.ts +47 -0
  233. package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
  234. package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
  235. package/src/plugins/builtin/options-scenario/client.ts +111 -17
  236. package/src/plugins/builtin/options-scenario/collar.ts +141 -0
  237. package/src/plugins/builtin/options-scenario/csp.ts +1 -1
  238. package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
  239. package/src/plugins/builtin/options-scenario/headless.ts +28 -12
  240. package/src/plugins/builtin/options-scenario/index.tsx +1 -1
  241. package/src/plugins/builtin/options-scenario/model.ts +55 -5
  242. package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
  243. package/src/plugins/builtin/perps/client.ts +77 -6
  244. package/src/plugins/builtin/perps/headless.ts +25 -14
  245. package/src/plugins/builtin/perps/index.tsx +1 -1
  246. package/src/plugins/builtin/perps/model.ts +32 -0
  247. package/src/plugins/builtin/perps/pane.tsx +5 -4
  248. package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
  249. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
  250. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  251. package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
  252. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
  253. package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
  254. package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
  255. package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
  256. package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
  257. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  258. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
  259. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
  260. package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
  261. package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
  262. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  263. package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
  264. package/src/plugins/builtin/rate-path/model.ts +1 -1
  265. package/src/plugins/builtin/rate-path/pane.tsx +2 -1
  266. package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
  267. package/src/plugins/builtin/research/event-model.ts +41 -49
  268. package/src/plugins/builtin/research/event-sources.ts +1 -1
  269. package/src/plugins/builtin/research/headless.ts +1 -1
  270. package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
  271. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  272. package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
  273. package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
  274. package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
  275. package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
  276. package/src/plugins/builtin/sectors/client.ts +46 -19
  277. package/src/plugins/builtin/sectors/sector-model.ts +22 -10
  278. package/src/plugins/builtin/shared/earnings-report.ts +48 -1
  279. package/src/plugins/builtin/shared/history-span.ts +55 -0
  280. package/src/plugins/builtin/shared/report-freshness.ts +20 -11
  281. package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
  282. package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
  283. package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
  284. package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
  285. package/src/plugins/builtin/themes/headless.ts +15 -2
  286. package/src/plugins/builtin/thirteenf/api.ts +60 -43
  287. package/src/plugins/builtin/thirteenf/data.ts +76 -35
  288. package/src/plugins/builtin/thirteenf/headless.ts +2 -0
  289. package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
  290. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
  291. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
  292. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
  293. package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
  294. package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
  295. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  296. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  297. package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
  298. package/src/plugins/builtin/yield-curve/curves.ts +15 -1
  299. package/src/plugins/builtin/yield-curve/headless.ts +17 -7
  300. package/src/plugins/builtin/yield-curve/history.ts +2 -0
  301. package/src/plugins/builtin/yield-curve/index.tsx +23 -18
  302. package/src/plugins/builtin/yield-curve/series.ts +19 -9
  303. package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
  304. package/src/plugins/builtin/yield-curve/world.ts +4 -0
  305. package/src/plugins/registry/host-actions.ts +3 -0
  306. package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
  307. package/src/portfolio/valuation-basis.ts +4 -21
  308. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  309. package/src/remote/controller.ts +27 -7
  310. package/src/remote/schema.ts +25 -9
  311. package/src/renderers/browser/app-services.ts +7 -1
  312. package/src/renderers/browser/config-host.ts +55 -2
  313. package/src/renderers/browser/first-screen.ts +193 -0
  314. package/src/renderers/browser/main.tsx +59 -7
  315. package/src/renderers/cloudflare/http-proxy.ts +21 -13
  316. package/src/renderers/dom/desktop/controls.tsx +2 -0
  317. package/src/renderers/dom/desktop/query-bar.tsx +1 -1
  318. package/src/renderers/dom/dom-ui-host.tsx +2 -0
  319. package/src/renderers/dom/windowed-list.tsx +201 -0
  320. package/src/renderers/dom/windowed-rows.ts +65 -0
  321. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
  322. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
  323. package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
  324. package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
  325. package/src/renderers/electrobun/shared/protocol.ts +7 -4
  326. package/src/renderers/electrobun/view/http-fetch.ts +12 -1
  327. package/src/renderers/electrobun/view/update-host.ts +2 -2
  328. package/src/shares/bug-report-dialog.tsx +162 -0
  329. package/src/shares/bug-report-flow.tsx +84 -0
  330. package/src/shares/bug-report.ts +42 -0
  331. package/src/shares/capture-flow.ts +19 -0
  332. package/src/shares/capture-preview.tsx +129 -0
  333. package/src/shares/chat.ts +10 -1
  334. package/src/shares/dialog.tsx +16 -91
  335. package/src/shares/flow.tsx +12 -16
  336. package/src/shares/personal-pane.ts +35 -1
  337. package/src/sources/listing-symbols.ts +8 -4
  338. package/src/state/app/context/index.tsx +13 -0
  339. package/src/state/app/tab-cursor.ts +85 -0
  340. package/src/sync/core-contributors.ts +206 -59
  341. package/src/telemetry/usage-counts.ts +16 -0
  342. package/src/test-support/config-store.ts +31 -0
  343. package/src/test-support/moved-symbols.ts +64 -0
  344. package/src/theme/colors.ts +30 -0
  345. package/src/tickers/listing-fate.ts +242 -0
  346. package/src/tickers/open-target.ts +61 -11
  347. package/src/tickers/search/index.ts +45 -3
  348. package/src/tickers/search/ranking.ts +19 -2
  349. package/src/tickers/search/types.ts +3 -0
  350. package/src/tickers/search/upsert.ts +22 -7
  351. package/src/tickers/ticker-changes.ts +26 -9
  352. package/src/time-series/bar-spacing.ts +19 -0
  353. package/src/time-series/chart-data.ts +37 -13
  354. package/src/time-series/field-catalog.ts +5 -2
  355. package/src/time-series/forward-valuation.ts +10 -7
  356. package/src/time-series/fundamentals.ts +49 -11
  357. package/src/time-series/hooks.ts +2 -0
  358. package/src/time-series/live-bars.ts +3 -1
  359. package/src/time-series/parsed-history-cache.ts +2 -0
  360. package/src/time-series/price-units.ts +69 -0
  361. package/src/time-series/resolve.ts +71 -11
  362. package/src/time-series/session-bar.ts +138 -0
  363. package/src/time-series/spec.ts +20 -3
  364. package/src/time-series/studies.ts +211 -12
  365. package/src/time-series/transforms.ts +2 -0
  366. package/src/time-series/types.ts +11 -1
  367. package/src/time-series/valuation-price.ts +13 -0
  368. package/src/types/config.ts +24 -1
  369. package/src/types/financials.ts +2 -0
  370. package/src/types/headless.ts +27 -0
  371. package/src/types/plugin.ts +7 -0
  372. package/src/ui/host.tsx +5 -0
  373. package/src/updater/channel.ts +51 -0
  374. package/src/updater/github-releases.ts +85 -16
  375. package/src/updater/index.ts +128 -17
  376. package/src/utils/asset-category.ts +20 -0
  377. package/src/utils/datetime-format.ts +7 -1
  378. package/src/utils/decimal-comma.ts +83 -3
  379. package/src/utils/fundamentals.ts +7 -2
  380. package/src/utils/http-proxy-response.ts +66 -9
  381. package/src/utils/plugin-proxy-hosts.json +1 -5
  382. package/src/utils/semver.ts +47 -2
  383. package/src/utils/typo-distance.ts +22 -0
  384. package/src/plugins/builtin/shared/zip-text.ts +0 -46
@@ -58,6 +58,37 @@ async function loadReportsForFunds(
58
58
  return { reports, ...(failures.length ? { warning: failures.join(" ") } : {}) };
59
59
  }
60
60
 
61
+ function copyReportFields(rows: FundBrowserRow[], nextRows: FundBrowserRow[]) {
62
+ const byId = new Map(nextRows.map((row) => [row.id, row]));
63
+ for (const row of rows) {
64
+ const next = byId.get(row.id);
65
+ if (!next) continue;
66
+ row.name = next.name;
67
+ row.periodOfReport = next.periodOfReport;
68
+ row.filedAsOfDate = next.filedAsOfDate;
69
+ row.tableValueTotal = next.tableValueTotal;
70
+ row.tableEntryTotal = next.tableEntryTotal;
71
+ if ("estQuarterReturn" in next) row.estQuarterReturn = next.estQuarterReturn;
72
+ }
73
+ }
74
+
75
+ function fillReports(
76
+ result: BrowserLoadResult,
77
+ rows: FundBrowserRow[],
78
+ pending: Promise<{ reports: Map<string, ThirteenFPeriodReport>; warning?: string }>,
79
+ rebuild: (reports: Map<string, ThirteenFPeriodReport>) => FundBrowserRow[],
80
+ signal?: AbortSignal,
81
+ ) {
82
+ result.reportsLoaded = pending.then(({ reports, warning }) => {
83
+ if (signal?.aborted) return;
84
+ copyReportFields(rows, rebuild(reports));
85
+ if (warning) result.warning = [result.warning, warning].filter(Boolean).join(" ");
86
+ }).catch((error: unknown) => {
87
+ if (signal?.aborted) return;
88
+ throw error;
89
+ });
90
+ }
91
+
61
92
  export interface BrowserLoadResult {
62
93
  rows: FundBrowserRow[];
63
94
  period?: string;
@@ -65,6 +96,7 @@ export interface BrowserLoadResult {
65
96
  warning?: string;
66
97
  hasMore?: boolean;
67
98
  nextOffset?: number;
99
+ reportsLoaded?: Promise<void>;
68
100
  }
69
101
 
70
102
  export async function loadBrowserRows(
@@ -75,7 +107,8 @@ export async function loadBrowserRows(
75
107
  ): Promise<BrowserLoadResult> {
76
108
  const warnings: string[] = [];
77
109
  const result = await loadBrowserPage(tab, query, signal, { ...options, onWarning: warning => warnings.push(warning) });
78
- return { ...result, warning: [...new Set([result.warning, ...warnings].filter(Boolean))].join(" ") || undefined };
110
+ if (warnings.length) result.warning = [...new Set([result.warning, ...warnings].filter(Boolean))].join(" ") || undefined;
111
+ return result;
79
112
  }
80
113
 
81
114
  async function loadBrowserPage(
@@ -106,34 +139,36 @@ async function loadBrowserPage(
106
139
  catch (error) { if (signal?.aborted) throw error; options.onWarning?.(`${previous}: performance ranking unavailable.`); return new Map<string, number | null>(); }
107
140
  }));
108
141
  const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
109
- const { reports, warning } = await loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), {
110
- from, to, signal, ...apiOptions,
111
- periods: new Map(topFunds.map((fund) => [fund.cik, fund.periodOfReport])),
112
- });
113
- return {
114
- rows: buildBrowserRows({ topFunds, reports, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) })),
115
- warning,
142
+ const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
143
+ const result: BrowserLoadResult = {
144
+ rows,
116
145
  quarter,
117
146
  period: topFunds[0]?.periodOfReport,
118
147
  hasMore: topFunds.length >= browserLimit,
119
148
  nextOffset: offset + topFunds.length,
120
149
  };
150
+ fillReports(result, rows, loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), {
151
+ from, to, signal, ...apiOptions,
152
+ periods: new Map(topFunds.map((fund) => [fund.cik, fund.periodOfReport])),
153
+ }), (reports) => buildBrowserRows({ topFunds, reports, source: "performance" }), signal);
154
+ return result;
121
155
  }
122
156
 
123
157
  if (tab === "latest") {
124
158
  const filings = await listThirteenFFilings(isoDateDaysAgo(21, now), to, latestLimit, signal, pageApiOptions);
125
159
  const selected = dedupeLatestForms(filings);
126
- const { reports, warning } = await loadReportsForFunds(selected.map((form) => ({ cik: form.cik, name: form.companyName })), {
127
- from, to, signal, ...apiOptions,
128
- periods: new Map(selected.map((form) => [form.cik, form.periodOfReport])),
129
- });
130
- return {
131
- rows: buildBrowserRows({ latestFilings: filings, reports, source: "latest" }),
132
- warning,
160
+ const rows = buildBrowserRows({ latestFilings: filings, source: "latest" });
161
+ const result: BrowserLoadResult = {
162
+ rows,
133
163
  period: filings[0]?.periodOfReport,
134
164
  hasMore: filings.length >= latestLimit,
135
165
  nextOffset: offset + filings.length,
136
166
  };
167
+ fillReports(result, rows, loadReportsForFunds(selected.map((form) => ({ cik: form.cik, name: form.companyName })), {
168
+ from, to, signal, ...apiOptions,
169
+ periods: new Map(selected.map((form) => [form.cik, form.periodOfReport])),
170
+ }), (reports) => buildBrowserRows({ latestFilings: filings, reports, source: "latest" }), signal);
171
+ return result;
137
172
  }
138
173
 
139
174
  if (tab === "byTicker") {
@@ -155,18 +190,23 @@ async function loadBrowserPage(
155
190
  try {
156
191
  const holders = await lookupThirteenFHoldersByCusip(cusip, periodOfReport, signal, apiOptions);
157
192
  const pageCiks = holders.ciks.slice(offset, offset + browserLimit);
158
- const { reports, warning } = await loadReportsForFunds(pageCiks.map((cik) => ({ cik, name: cik })), {
159
- from, to, signal, ...apiOptions,
160
- periods: new Map(pageCiks.map((cik) => [cik, holders.periodOfReport])),
161
- });
162
- const funds = pageCiks.map((cik) => ({ cik, name: reports.get(cik)?.filings.at(-1)?.companyName || cik }));
163
- return {
164
- rows: buildBrowserRows({ funds, reports, source: "ticker" }),
165
- warning,
193
+ const funds = pageCiks.map((cik) => ({ cik, name: cik }));
194
+ const rows = buildBrowserRows({ funds, source: "ticker" });
195
+ const result: BrowserLoadResult = {
196
+ rows,
166
197
  period: holders.periodOfReport,
167
198
  hasMore: holders.ciks.length > offset + pageCiks.length,
168
199
  nextOffset: offset + pageCiks.length,
169
200
  };
201
+ fillReports(result, rows, loadReportsForFunds(funds, {
202
+ from, to, signal, ...apiOptions,
203
+ periods: new Map(pageCiks.map((cik) => [cik, holders.periodOfReport])),
204
+ }), (reports) => buildBrowserRows({
205
+ funds: pageCiks.map((cik) => ({ cik, name: reports.get(cik)?.filings.at(-1)?.companyName || cik })),
206
+ reports,
207
+ source: "ticker",
208
+ }), signal);
209
+ return result;
170
210
  } catch (error) {
171
211
  return {
172
212
  rows: [],
@@ -195,26 +235,27 @@ async function loadBrowserPage(
195
235
  }
196
236
  const found = await searchThirteenFFunds(trimmed, browserLimit, signal, pageApiOptions);
197
237
  const funds = oneFundPerCik(found);
198
- const [{ reports, warning }, topFunds] = await Promise.all([
199
- loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), { from, to, signal, ...apiOptions }),
200
- listTopThirteenFFunds(quarter, browserLimit, signal, apiOptions).catch(() => []),
201
- ]);
238
+ const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, apiOptions).catch((): ThirteenFTopFund[] => []);
202
239
  const topByCik = new Map(topFunds.map((fund) => [fund.cik, fund]));
203
240
  const matchedTopFunds = funds
204
241
  .map((fund) => topByCik.get(fund.cik))
205
242
  .filter((fund): fund is ThirteenFTopFund => !!fund);
206
- return {
207
- rows: buildBrowserRows({
208
- funds: oneFundPerCik(found, reports),
209
- topFunds: matchedTopFunds,
210
- reports,
211
- source: "funds",
212
- }),
243
+ const rows = buildBrowserRows({ funds: oneFundPerCik(found), topFunds: matchedTopFunds, source: "funds" });
244
+ const result: BrowserLoadResult = {
245
+ rows,
213
246
  quarter,
214
- warning,
215
247
  hasMore: found.length >= browserLimit,
216
248
  nextOffset: offset + found.length,
217
249
  };
250
+ fillReports(result, rows, loadReportsForFunds(funds.slice(0, Math.min(browserLimit, FORM_ENRICHMENT_LIMIT)), {
251
+ from, to, signal, ...apiOptions,
252
+ }), (reports) => buildBrowserRows({
253
+ funds: oneFundPerCik(found, reports),
254
+ topFunds: matchedTopFunds,
255
+ reports,
256
+ source: "funds",
257
+ }), signal);
258
+ return result;
218
259
  }
219
260
 
220
261
  export async function loadFundDetail(
@@ -257,6 +257,7 @@ async function findThirteenFFund(
257
257
  if (isCikQuery(query)) return { cik: normalizeCik(query), name: query };
258
258
  // A small output limit must not hide other matching managers during lookup.
259
259
  const result = await search(query, 25);
260
+ await result.reportsLoaded;
260
261
  const candidates = [...new Map(result.rows.map((row) => [row.cik, row])).values()];
261
262
  const exact = candidates.filter((row) => row.name.toLocaleLowerCase() === query.toLocaleLowerCase());
262
263
  if (exactOnly) return exact.length === 1 ? { cik: exact[0]!.cik, name: exact[0]!.name } : null;
@@ -603,6 +604,7 @@ export function createThirteenFHeadless(
603
604
 
604
605
  const tab = browserTab(view === "ticker-holdings" ? requestedView : view, query);
605
606
  const result = await dependencies.loadBrowser(tab, query, limit, args, ctx);
607
+ await result.reportsLoaded;
606
608
  const more = result.hasMore || result.rows.length > limit;
607
609
  const browserRows = sortBrowserRows(result.rows, browserSortFor(DEFAULT_BROWSER_SORT, tab)).slice(0, limit);
608
610
  // A search finds managers that stopped filing; say which, since the Period column alone reads like any other.
@@ -113,13 +113,21 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
113
113
  // A new query starts from an empty list; a refresh keeps the rows it has.
114
114
  const browser = usePagedRows(showTickerHoldings ? null : loadPage, { getId: rowId });
115
115
  const { loading, loadingMore, rows } = browser;
116
+ const [reportPaint, setReportPaint] = useState(0);
117
+ useEffect(() => {
118
+ let alive = true;
119
+ void Promise.all(browser.pages.map((page) => page.reportsLoaded ?? Promise.resolve())).then(() => {
120
+ if (alive) setReportPaint((value) => value + 1);
121
+ }, () => {});
122
+ return () => { alive = false; };
123
+ }, [browser.pages]);
116
124
  const error = browser.error?.message ?? null;
117
125
  // A refresh clears the pages' warnings until it answers.
118
126
  const pagesSettled = !loading && !browser.error;
119
127
  const warning = useMemo(() => {
120
128
  const notes = [...(pagesSettled ? browser.pages.map((page) => page.warning) : []), browser.moreError?.message];
121
129
  return [...new Set(notes.filter(Boolean))].join(" ") || null;
122
- }, [browser.moreError, browser.pages, pagesSettled]);
130
+ }, [browser.moreError, browser.pages, pagesSettled, reportPaint]);
123
131
  const [detailSeed, setDetailSeed] = useState<FundSeed | null>(() => (
124
132
  initialCik ? { cik: String(initialCik), name: normalizedQuery || String(initialCik) } : null
125
133
  ));
@@ -157,7 +165,7 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
157
165
  }, { allowEditable: true });
158
166
 
159
167
  const browserSort = useMemo(() => browserSortFor(sortPreference, browserMode), [sortPreference, browserMode]);
160
- const sortedRows = useMemo(() => sortBrowserRows(rows, browserSort), [rows, browserSort]);
168
+ const sortedRows = useMemo(() => sortBrowserRows(rows, browserSort), [rows, browserSort, reportPaint]);
161
169
  const columns = useMemo(() => {
162
170
  if (browserMode !== "performance") return buildBrowserColumns(false);
163
171
  const history = rows[0]?.priorReturns ?? [];
@@ -10,6 +10,7 @@ import {
10
10
  type DataTableCell,
11
11
  type DataTableColumn,
12
12
  type DataTableKeyEvent,
13
+ type PaneFooterSegment,
13
14
  } from "../../../../components";
14
15
  import { TIME_RANGES, type TimeRange } from "../../../../time-series/range";
15
16
  import type { PaneProps } from "../../../../types/plugin";
@@ -29,6 +30,18 @@ import {
29
30
  import { usePaneTicker } from "../../../../state/app/context";
30
31
  import { handleRefreshKey, useClampSelectedIndex } from "../../../../components/data-table/table-pane";
31
32
  import { useBoundTicker, useTickerRequest } from "../../shared/ticker-request";
33
+ import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../../time-series/session-bar";
34
+ import {
35
+ formingHistoryBar,
36
+ formingHistoryBarText,
37
+ HISTORY_DEFAULT_RANGE,
38
+ historyBarDate,
39
+ historyBarSize,
40
+ historyBarSizeText,
41
+ historyBarSizeWord,
42
+ historyDateHeader,
43
+ type HistoryBarSize,
44
+ } from "./history-bars";
32
45
 
33
46
  type HistoryColumnId = "date" | "open" | "high" | "low" | "close" | "change" | "changePercent" | "volume";
34
47
  type HistoryColumn = DataTableColumn & { id: HistoryColumnId };
@@ -36,7 +49,14 @@ type HistoryColumn = DataTableColumn & { id: HistoryColumnId };
36
49
  export type HistoricalPriceRow = {
37
50
  key: string;
38
51
  point: ReturnType<typeof pricePointValues>;
52
+ /** The bar's date as the table reads it: `2026-10 MTD` for a month still running, `2026-10-16 DTD` for today's session. */
39
53
  date: string;
54
+ /** What a CSV export writes for the date: the bar's own date, whatever the table says of its period. */
55
+ exportDate: string;
56
+ /** The bar carries a time of day, so the table dates it with one. */
57
+ timed: boolean;
58
+ /** The bar's period had not ended: a week or month still running, or the day's session still trading. */
59
+ partial: boolean;
40
60
  change: number | null;
41
61
  changePercent: number | null;
42
62
  };
@@ -90,25 +110,49 @@ function formatMaybeCompact(value: number | null | undefined): string {
90
110
  return value == null ? "-" : formatCompact(value);
91
111
  }
92
112
 
93
- function buildHistoricalPriceRows(points: PricePoint[]): HistoricalPriceRow[] {
113
+ interface HistoricalPriceTable {
114
+ /** Newest first. */
115
+ rows: HistoricalPriceRow[];
116
+ size: HistoryBarSize;
117
+ /** The newest bar's week or month had not ended, and how far into it the bar runs. */
118
+ forming: ReturnType<typeof formingHistoryBar>;
119
+ /** The newest bar is a day whose session is still trading. */
120
+ dayToDate: boolean;
121
+ /** The newest bar's day, `YYYY-MM-DD`. */
122
+ newestDate: string | undefined;
123
+ }
124
+
125
+ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefined, now: number): HistoricalPriceTable {
94
126
  const sorted = points
95
127
  .flatMap((point, sourceIndex) => {
96
128
  const date = pricePointDate(point);
97
- return date ? [{ point: pricePointValues(point), date, sourceIndex }] : [];
129
+ return date ? [{ point: pricePointValues(point), date, sourceIndex, partial: point.partial === true }] : [];
98
130
  })
99
131
  .sort((left, right) => left.date.getTime() - right.date.getTime());
100
- return sorted.map((entry, index) => {
132
+ const size = historyBarSize(sorted.map((entry) => entry.date.getTime()));
133
+ const newestDate = sorted.at(-1)?.date.toISOString().slice(0, 10);
134
+ const forming = formingHistoryBar(size, newestDate, venue, now);
135
+ const dayToDate = size === "1d" && sorted.at(-1)?.partial === true;
136
+ const rows = sorted.map((entry, index) => {
101
137
  const previous = sorted[index - 1]?.point;
102
138
  const { point, date, sourceIndex } = entry;
139
+ const newest = index === sorted.length - 1;
140
+ const partial = newest && (!!forming || dayToDate);
103
141
  const change = previous?.close != null && point.close != null ? point.close - previous.close : null;
142
+ const shown = formatDateTime(date);
143
+ const timed = shown.length > 10;
104
144
  return {
105
145
  key: `${date.toISOString()}:${sourceIndex}`,
106
146
  point,
107
- date: formatDateTime(date),
147
+ date: timed ? shown : historyBarDate(shown, size, partial),
148
+ exportDate: shown,
149
+ timed,
150
+ partial,
108
151
  change,
109
152
  changePercent: previous?.close && change !== null ? change / previous.close : null,
110
153
  };
111
154
  }).reverse();
155
+ return { rows, size, forming, dayToDate, newestDate };
112
156
  }
113
157
 
114
158
  const HISTORY_PRICE_WIDTH = 10;
@@ -123,8 +167,9 @@ const HISTORY_NUMERIC_IDS = ["open", "high", "low", "close", "change", "changePe
123
167
  * evenly, and give up cells down to a floor when it is narrow, so the volume
124
168
  * stays on screen.
125
169
  */
126
- function buildHistoryColumns(width: number, intraday: boolean): HistoryColumn[] {
127
- const dateWidth = intraday ? 16 : 10;
170
+ function buildHistoryColumns(width: number, intraday: boolean, size: HistoryBarSize, newestDate: string): HistoryColumn[] {
171
+ // A month reads as `2026-10`; the newest bar, when its week or month runs on, adds `MTD` or `WTD`.
172
+ const dateWidth = Math.max(intraday ? 16 : size === "1mo" ? 7 : 10, newestDate.length);
128
173
  const numeric: number[] = HISTORY_NUMERIC_IDS.map((id) => id === "changePercent" ? HISTORY_PERCENT_WIDTH : HISTORY_PRICE_WIDTH);
129
174
  // One gap per column plus the table's inset on both sides.
130
175
  const available = width - dateWidth - HISTORY_VOLUME_WIDTH - 8 - 2;
@@ -141,7 +186,7 @@ function buildHistoryColumns(width: number, intraday: boolean): HistoryColumn[]
141
186
  }
142
187
  const [open, high, low, close, change, changePercent] = numeric as [number, number, number, number, number, number];
143
188
  return [
144
- { id: "date", label: intraday ? "DATE/TIME" : "DATE", width: dateWidth, align: "left" },
189
+ { id: "date", label: intraday ? "DATE/TIME" : historyDateHeader(size)?.toUpperCase() ?? "DATE", width: dateWidth, align: "left" },
145
190
  { id: "open", label: "OPEN", width: open, align: "right", flexGrow: 1 },
146
191
  { id: "high", label: "HIGH", width: high, align: "right", flexGrow: 1 },
147
192
  { id: "low", label: "LOW", width: low, align: "right", flexGrow: 1 },
@@ -162,7 +207,7 @@ function nextHistoryRange(current: TimeRange): TimeRange {
162
207
  export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
163
208
  const dataProvider = useAssetData();
164
209
  const { symbol, exchange, ticker } = useBoundTicker();
165
- const [range, setRange] = usePluginPaneState<TimeRange>("range", "ALL");
210
+ const [range, setRange] = usePluginPaneState<TimeRange>("range", HISTORY_DEFAULT_RANGE);
166
211
  const [selectedIdx, setSelectedIdx] = useDebouncedPluginPaneState<number>("selectedIdx", 0);
167
212
  const loader = useCallback((nextSymbol: string, nextExchange: string, forceRefresh: boolean) => {
168
213
  if (!dataProvider) throw new Error("Market data unavailable");
@@ -174,14 +219,27 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
174
219
  );
175
220
  }, [dataProvider, range]);
176
221
  const { data, loading, error, reload } = useTickerRequest<PricePoint[]>(loader, symbol, exchange);
177
- const rows = useMemo(() => buildHistoricalPriceRows(data ?? []), [data]);
178
- const integrityNotice = priceHistoryIntegrityNotice(rows.filter((row) => row.point.integrity).length);
179
- // Daily and longer bars are dates; only intraday bars carry a time.
180
- const intraday = rows.length > 0 ? rows.some((row) => row.date.length > 10) : range === "1D" || range === "1W";
181
- const columns = useMemo(() => buildHistoryColumns(width, intraday), [intraday, width]);
182
222
  // The quote knows the instrument type even when the saved ticker does not;
183
- // resolve it the way every quote display does.
223
+ // resolve it the way every quote display does. It also names the venue
224
+ // whose sessions say whether a week or month is still running.
184
225
  const { financials } = usePaneTicker();
226
+ const venue = exchange || financials?.quote?.listingExchangeName || financials?.quote?.exchangeName || undefined;
227
+ // While the session trades the day's bar is the quote's: the history keeps a
228
+ // snapshot of it from whenever it was last fetched (withSessionBar).
229
+ const quote = financials?.quote;
230
+ const points = useMemo(
231
+ () => withSessionBar(data ?? [], quote, { now: Date.now(), venue }),
232
+ [data, quote, venue],
233
+ );
234
+ const { rows, size, forming, dayToDate, newestDate } = useMemo(
235
+ () => buildHistoricalPriceRows(points, venue, Date.now()),
236
+ [points, venue],
237
+ );
238
+ const integrityNotice = priceHistoryIntegrityNotice(rows.filter((row) => row.point.integrity).length);
239
+ // Daily and longer bars are dates; only intraday bars carry a time.
240
+ const intraday = rows.length > 0 ? rows.some((row) => row.timed) : range === "1D" || range === "1W";
241
+ const newestRowDate = rows[0]?.date ?? "";
242
+ const columns = useMemo(() => buildHistoryColumns(width, intraday, size, newestRowDate), [intraday, newestRowDate, size, width]);
185
243
  const assetCategory = quoteFormatOptions(
186
244
  financials?.quote,
187
245
  ticker?.metadata.assetCategory,
@@ -217,7 +275,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
217
275
  ): DataTableCell => {
218
276
  switch (column.id) {
219
277
  case "date":
220
- return { text: row.date, color: colors.textDim };
278
+ return { text: row.date, value: row.exportDate, color: colors.textDim };
221
279
  case "open":
222
280
  case "high":
223
281
  case "low":
@@ -236,8 +294,15 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
236
294
  }
237
295
  }, [priceDecimals]);
238
296
 
239
- // The range is picked in the query bar; t still steps through it.
240
- usePaneStatusFooter({ registrationId: "historical-prices", loading, error });
297
+ // The range is picked in the query bar; t still steps through it. The range
298
+ // sets the bar size, which the footer says, and a week or month still
299
+ // running says how far the newest bar goes.
300
+ const barInfo = useMemo<PaneFooterSegment[]>(() => [
301
+ ...rows.length > 0 ? [{ id: "bars", parts: [{ text: historyBarSizeText(size), tone: "muted" as const }] }] : [],
302
+ ...forming && newestDate ? [{ id: "forming", parts: [{ text: formingHistoryBarText(forming, newestDate), tone: "muted" as const }] }] : [],
303
+ ...dayToDate ? [{ id: "forming", parts: [{ text: DAY_SO_FAR_TEXT, tone: "muted" as const }] }] : [],
304
+ ], [dayToDate, forming, newestDate, rows.length, size]);
305
+ usePaneStatusFooter({ registrationId: "historical-prices", loading, error, info: barInfo });
241
306
 
242
307
  usePaneNoticeFooter({
243
308
  registrationId: "historical-prices:integrity",
@@ -280,7 +345,10 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
280
345
  getExportMetadata={() => [
281
346
  ["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
282
347
  ["Requested range", range],
348
+ ...(rows.length > 0 ? [["Bar size", historyBarSizeWord(size)]] : []),
283
349
  ["Time zone", "UTC"],
350
+ ...(forming && newestDate ? [["Partial bar", `${newestDate}, ${formingHistoryBarText(forming, newestDate)}`]] : []),
351
+ ...(dayToDate && newestDate ? [["Partial bar", `${newestDate}, ${DAY_SO_FAR_TEXT}`]] : []),
284
352
  ["Price units", "As served in the history; currency and price-basis metadata are unavailable"],
285
353
  ["Status", error ? (rows.length ? "Retained after refresh failure" : "Unavailable")
286
354
  : loading ? (rows.length ? "Refreshing" : "Loading") : rows.length ? "Available" : "Empty"],
@@ -0,0 +1,92 @@
1
+ import { BAR_INTERVAL_LABELS } from "../../../../cli/history-rows";
2
+ import { periodToDateText } from "../../../../cli/pane-functions/freshness";
3
+ import { barPeriodToDate, type BarPeriod, type BarPeriodToDate } from "../../../../market-data/market/bar-period";
4
+ import { barSizeFromGap, medianBarGap } from "../../../../time-series/bar-spacing";
5
+ import type { TimeRange } from "../../../../time-series/range";
6
+ import { CHART_RESOLUTION_STEP_MS, type ManualChartResolution } from "../../../../time-series/resolution";
7
+
8
+ /**
9
+ * The range a price history opens on, in Historical Prices, `fn HP` and
10
+ * `shot HP`: a recent window in daily bars (RANGE_HISTORY_RESOLUTION serves
11
+ * 1M to 1Y daily). 5Y is weekly and ALL monthly, so neither is a place to start.
12
+ */
13
+ export const HISTORY_DEFAULT_RANGE: TimeRange = "3M";
14
+
15
+ /** The size of one bar of a price history; `intraday` when its minutes are not a size the source serves. */
16
+ export type HistoryBarSize = ManualChartResolution | "intraday";
17
+
18
+ const INTRADAY_SIZES: readonly ManualChartResolution[] = ["1m", "5m", "15m", "30m", "45m", "1h"];
19
+ /** A day's bars can sit a little under a day apart where the clocks change. */
20
+ const INTRADAY_BELOW_MS = 0.8 * CHART_RESOLUTION_STEP_MS["1d"];
21
+
22
+ /**
23
+ * The bar size of a history, read from the usual spacing of its bars
24
+ * (medianBarGap), as `history` reads it, so the gap a holiday leaves does not
25
+ * make daily bars weekly. Daily when there is no gap to read.
26
+ */
27
+ export function historyBarSize(times: readonly number[]): HistoryBarSize {
28
+ const gap = medianBarGap(times);
29
+ if (gap == null) return "1d";
30
+ if (gap < INTRADAY_BELOW_MS) return INTRADAY_SIZES.find((size) => CHART_RESOLUTION_STEP_MS[size] === gap) ?? "intraday";
31
+ return barSizeFromGap(gap);
32
+ }
33
+
34
+ /** Whether a bar of this size covers a day or more, so its date is a day and not a time. */
35
+ export function isDailyOrLongerBar(size: HistoryBarSize): size is "1d" | "1wk" | "1mo" {
36
+ return size === "1d" || size === "1wk" || size === "1mo";
37
+ }
38
+
39
+ /** `daily`, `5-minute`, `hourly`, `weekly`, `monthly`; `intraday` when the minutes are not a known size. */
40
+ export function historyBarSizeWord(size: HistoryBarSize): string {
41
+ return size === "intraday" ? size : BAR_INTERVAL_LABELS[size];
42
+ }
43
+
44
+ /** `daily bars`, `monthly bars`: what a history says its bars are, which the range it was asked for sets. */
45
+ export function historyBarSizeText(size: HistoryBarSize): string {
46
+ return `${historyBarSizeWord(size)} bars`;
47
+ }
48
+
49
+ /**
50
+ * What a date column heads when its bars cover more than a day: `Week of`
51
+ * (each is dated by the day its week starts) or `Month`. Null for a day's bar,
52
+ * whose column is `Date`, or an intraday one, which is dated with a time.
53
+ */
54
+ export function historyDateHeader(size: HistoryBarSize): string | null {
55
+ return size === "1wk" ? "Week of" : size === "1mo" ? "Month" : null;
56
+ }
57
+
58
+ /** The calendar period a weekly or monthly bar covers; null for any other. */
59
+ function historyBarPeriod(size: HistoryBarSize): BarPeriod | null {
60
+ return size === "1mo" ? "month" : size === "1wk" ? "week" : null;
61
+ }
62
+
63
+ /**
64
+ * Whether the newest bar, dated `date`, is a week or month that had not ended
65
+ * at `now` on its `venue` (barPeriodToDate), and how far into it the bar runs.
66
+ * Null for a complete bar, and for any bar that is not weekly or monthly.
67
+ */
68
+ export function formingHistoryBar(
69
+ size: HistoryBarSize,
70
+ date: string | undefined,
71
+ venue: string | undefined,
72
+ now: number,
73
+ ): BarPeriodToDate | null {
74
+ const period = historyBarPeriod(size);
75
+ return period && date ? barPeriodToDate(date, period, venue, now) : null;
76
+ }
77
+
78
+ /**
79
+ * A bar's day as its table dates it: a month's bar as the month (`2026-10`),
80
+ * a week's by the day it starts, and `MTD`, `WTD` or `DTD` after one whose
81
+ * period had not ended, so it never reads as a complete bar dated on its
82
+ * first day. `day` is `YYYY-MM-DD`.
83
+ */
84
+ export function historyBarDate(day: string, size: HistoryBarSize, partial = false): string {
85
+ const text = size === "1mo" ? day.slice(0, 7) : day;
86
+ return partial ? `${text} ${size === "1mo" ? "MTD" : size === "1wk" ? "WTD" : "DTD"}` : text;
87
+ }
88
+
89
+ /** What the newest bar covers so far: `month to date, through Fri 16 Oct 2026`. */
90
+ export function formingHistoryBarText(forming: BarPeriodToDate, date: string): string {
91
+ return periodToDateText({ period: forming.period, ...(forming.through ? { through: forming.through } : {}) }, date);
92
+ }
@@ -42,6 +42,7 @@ import {
42
42
  type ReturnRow,
43
43
  } from "./returns-model";
44
44
  import { loadReturnSeries, type ReturnSeries } from "./returns-history";
45
+ import { formingHistoryBar, formingHistoryBarText, historyBarDate } from "./history-bars";
45
46
 
46
47
  type ReturnColumnId = "time" | "price" | "intervalChange" | "intervalPercent" | "cumulativeChange" | "cumulativePercent";
47
48
  type ReturnColumn = DataTableColumn & { id: ReturnColumnId };
@@ -185,12 +186,21 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
185
186
  const hints = useMemo<PaneHint[]>(() => (
186
187
  table.searchable ? [{ id: "search", key: "/", label: "search", onPress: focusSearch }] : []
187
188
  ), [focusSearch, table.searchable]);
189
+ // A weekly or monthly interval's newest bar is its week or month so far when that has not ended.
190
+ const newestDate = returnRows[0] ? new Date(returnRows[0].timestamp).toISOString().slice(0, 10) : undefined;
191
+ const forming = useMemo(
192
+ () => data ? formingHistoryBar(data.resolution, newestDate, data.venue || undefined, Date.now()) : null,
193
+ [data, newestDate],
194
+ );
195
+ const formingKey = forming ? returnRows[0]?.key : undefined;
196
+ const barSize = data?.resolution ?? shownResolution;
188
197
  // A shut market's window ends at its last session, which the footer names.
189
- const marketInfo = useMemo<PaneFooterSegment[]>(() => (
190
- data?.lastSession
191
- ? [{ id: "market", parts: [{ text: closedMarketNote(data.lastSession, data.visibleTo), tone: "muted" }] }]
192
- : []
193
- ), [data]);
198
+ const marketInfo = useMemo<PaneFooterSegment[]>(() => [
199
+ ...data?.lastSession
200
+ ? [{ id: "market", parts: [{ text: closedMarketNote(data.lastSession, data.visibleTo), tone: "muted" as const }] }]
201
+ : [],
202
+ ...forming && newestDate ? [{ id: "forming", parts: [{ text: formingHistoryBarText(forming, newestDate), tone: "muted" as const }] }] : [],
203
+ ], [data, forming, newestDate]);
194
204
  usePaneStatusFooter({ registrationId: "returns", loading, error, hints, info: marketInfo });
195
205
 
196
206
  const integrityNotice = priceHistoryIntegrityNotice(returnRows.filter((row) => row.inconsistent).length);
@@ -208,8 +218,15 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
208
218
 
209
219
  const renderCell = useCallback((row: ReturnRow, column: ReturnColumn): DataTableCell => {
210
220
  switch (column.id) {
211
- case "time":
212
- return { text: formatDateTime(new Date(row.timestamp)), value: new Date(row.timestamp).toISOString(), color: colors.textDim };
221
+ case "time": {
222
+ const shown = formatDateTime(new Date(row.timestamp));
223
+ // A daily or longer bar is a date; a month's reads as the month, and one still running says so.
224
+ return {
225
+ text: shown.length > 10 ? shown : historyBarDate(shown, barSize, row.key === formingKey),
226
+ value: new Date(row.timestamp).toISOString(),
227
+ color: colors.textDim,
228
+ };
229
+ }
213
230
  case "price":
214
231
  return { text: formatMaybePrice(row.price, priceDecimals, column.width), value: exportPrice(row.price, priceDecimals), color: colors.textBright, attributes: TextAttributes.BOLD };
215
232
  case "intervalChange":
@@ -221,7 +238,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
221
238
  case "cumulativePercent":
222
239
  return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: priceColor(row.cumulativePercent ?? 0) };
223
240
  }
224
- }, [priceDecimals]);
241
+ }, [barSize, formingKey, priceDecimals]);
225
242
 
226
243
  return (
227
244
  <DataTableView<ReturnRow, ReturnColumn>
@@ -270,6 +287,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
270
287
  ["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
271
288
  ["Range", data?.range ?? range],
272
289
  ["Interval", getChartResolutionLabel(data?.resolution ?? shownResolution)],
290
+ ...(forming && newestDate ? [["Partial bar", `${newestDate}, ${formingHistoryBarText(forming, newestDate)}`]] : []),
273
291
  ["Window", data ? `after ${new Date(data.visibleFrom).toISOString()} through ${new Date(data.visibleTo).toISOString()}` : ""],
274
292
  ...(data?.lastSession ? [["Market", closedMarketNote(data.lastSession, data.visibleTo)]] : []),
275
293
  ["Time zone", "UTC, bar open"],
@@ -1,5 +1,7 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/headless";
2
2
  import { FINANCIAL_PERIOD_OPTION, HISTORY_RANGE_VALUES } from "../shared/headless-options";
3
+ import { historyIntervalsByRange } from "../../../cli/history-rows";
4
+ import { HISTORY_DEFAULT_RANGE } from "./data-panes/history-bars";
3
5
  import { RETURN_INTERVAL_VALUES, RETURN_RANGES } from "./data-panes/returns-model";
4
6
 
5
7
  type PaneSchema = Pick<HeadlessPaneDefinition, "argument" | "options" | "discovery">;
@@ -51,11 +53,11 @@ export const paneSchemas = {
51
53
  options: [
52
54
  {
53
55
  key: "range",
54
- description: "Price-history window.",
56
+ description: `Price-history window, which also sets the bar size: ${historyIntervalsByRange()}.`,
55
57
  type: "enum",
56
58
  aliases: ["rangePreset"],
57
59
  values: HISTORY_RANGE_VALUES,
58
- defaultValue: "ALL",
60
+ defaultValue: HISTORY_DEFAULT_RANGE,
59
61
  pluginState: { pluginId: "ticker-research" },
60
62
  },
61
63
  ],