@gloom-sh/terminal 0.18.5 → 0.19.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 (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -36,7 +36,7 @@ import { resolvePortfolioTotalsCurrency } from "../../../portfolio/totals-curren
36
36
  import { createBaseConverter } from "../../../cli/base-converter";
37
37
  import { heldCurrencyUnit } from "../../../utils/currency-units";
38
38
  import { positionCostTermsPrefill } from "./portfolio-position";
39
- import { wholeUnitQuantityProblem } from "../../../portfolio/position-quantity";
39
+ import { ambiguousQuantityProblem } from "../../../portfolio/position-quantity";
40
40
  import type { CommandBarFieldValue } from "./types";
41
41
  import type { WorkflowStringValues } from "./broker";
42
42
  import { coerceFieldString } from "../helpers";
@@ -259,8 +259,8 @@ export function createCommandBarCollectionWorkflowActions(options: {
259
259
  // Negative for a short; zero is no position. A future counts contracts, and the messages say so.
260
260
  const quantityLabel = positionQuantityLabel(ticker);
261
261
  const shares = readTypedNumber(coerceFieldString(values.shares), quantityLabel);
262
- // 1.250 on a listing held in whole lots could be 1,250 as well, and asks the same way.
263
- const lots = wholeUnitQuantityProblem(coerceFieldString(values.shares).trim(), ticker, { label: quantityLabel });
262
+ // 1.250 could be 1,250 as well, and asks the same way.
263
+ const lots = ambiguousQuantityProblem(coerceFieldString(values.shares).trim(), ticker, { label: quantityLabel });
264
264
  if (lots) throw new Error(lots);
265
265
  const sharesError = validatePositionShares(shares, quantityLabel);
266
266
  if (sharesError) {
@@ -28,10 +28,10 @@ export function usePaneCaptureStamp(paneId: string): string | null {
28
28
 
29
29
  export function captureStampFooter(footer: CombinedPaneFooter, text: string | null): CombinedPaneFooter {
30
30
  if (!text) return footer;
31
- // Set apart from what the pane says about its data, which may itself be a time.
31
+ // The footer sets it apart from what the pane says about its data, which may itself be a time.
32
32
  const segment: PaneFooterSegment = {
33
33
  id: "capture-stamp",
34
- parts: [...(footer.info.length > 0 ? [{ text: "· ", tone: "muted" as const }] : []), { text, tone: "label" }],
34
+ parts: [{ text, tone: "label" }],
35
35
  };
36
36
  return {
37
37
  ...footer,
@@ -11,7 +11,9 @@ import { useOptionalDialog, type PromptContext } from "../../../../ui/dialog";
11
11
  import { nativePaneFooterRows } from "../sizing";
12
12
  import {
13
13
  EMPTY_FOOTER,
14
+ PANE_FOOTER_SEPARATOR,
14
15
  fitPaneFooterInfo,
16
+ separatesPaneFooterSegments,
15
17
  hasPaneFooterContent,
16
18
  layoutPaneFooterHintRow,
17
19
  totalHintsWidth,
@@ -22,6 +24,7 @@ import {
22
24
  } from "./model";
23
25
 
24
26
  export {
27
+ companyDataFooterSegment,
25
28
  datesPaneData,
26
29
  DELAYED_SEGMENT,
27
30
  delayedFooterSegment,
@@ -267,6 +270,7 @@ function FooterContent({
267
270
  // A segment with shorter forms takes the longest that fits beside the hints, gaps and terminal keys included.
268
271
  const keysWidth = nativePaneChrome ? 0 : footer.info.reduce((total, segment) => total + terminalSegmentKey(segment, focused).length, 0);
269
272
  const info = infoWidth != null ? fitPaneFooterInfo(footer.info, infoWidth - keysWidth, { besideHints: hasHints }) : footer.info;
273
+ const lastTextIndex = info.findLastIndex((segment) => !segment.icon);
270
274
 
271
275
  if (!hasInfo && !hasHints) {
272
276
  return <Box flexGrow={1} height={1} />;
@@ -291,12 +295,24 @@ function FooterContent({
291
295
  flexShrink={1}
292
296
  {...(infoWidth != null ? { width: infoWidth } : {})}
293
297
  >
294
- {info.map((segment, index) => (
295
- // An icon is a few cells that mean nothing cut: the text beside it gives way first.
296
- <Box key={segment.id} flexDirection="row" flexShrink={segment.icon ? 0 : undefined} marginRight={index === info.length - 1 ? 0 : 1}>
297
- <SegmentView segment={segment} focused={focused} />
298
- </Box>
299
- ))}
298
+ {info.map((segment, index) => {
299
+ // Every pane's segments are set apart the same way, so two never read as one sentence.
300
+ const separated = index > 0 && separatesPaneFooterSegments(info[index - 1]!, segment);
301
+ return (
302
+ <Box
303
+ key={segment.id}
304
+ flexDirection="row"
305
+ overflow="hidden"
306
+ // A row too narrow cuts from its end: the last text segment gives way, then the row clips the
307
+ // one before it. An icon is a few cells that mean nothing cut, and earlier text says more.
308
+ flexShrink={!segment.icon && index === lastTextIndex ? 1 : 0}
309
+ marginLeft={index > 0 && !separated ? 1 : 0}
310
+ >
311
+ {separated && <Box flexShrink={0}><Text fg={colors.textMuted}>{PANE_FOOTER_SEPARATOR}</Text></Box>}
312
+ <SegmentView segment={segment} focused={focused} />
313
+ </Box>
314
+ );
315
+ })}
300
316
  </Box>
301
317
  )}
302
318
  {hasHints && (
@@ -1,5 +1,6 @@
1
1
  import type { ContextMenuItem } from "../../../../types/context-menu";
2
- import { t } from "../../../../i18n";
2
+ import { t, tf } from "../../../../i18n";
3
+ import { type CompanyFrom, companyFromLabel } from "../../../../types/company-from";
3
4
  import { clipToDisplayWidth, displayWidth } from "../../../../utils/format";
4
5
  import { formatShortDate } from "../../../../utils/datetime-format";
5
6
  import { getShortcutHintWidth } from "../../../ui/shortcut-hint";
@@ -44,8 +45,9 @@ export interface PaneFooterSegment {
44
45
  */
45
46
  quoteStatus?: boolean;
46
47
  /**
47
- * The segment says what the rows leave out (releases a filter hides), which
48
- * a picture of the rows cannot: a `gloom shot` keeps it, as it keeps a
48
+ * The segment says what the rows leave out (releases a filter hides), or
49
+ * whose they are (a receipt's company data from its home line), which a
50
+ * picture of the rows cannot: a `gloom shot` keeps it, as it keeps a
49
51
  * warning.
50
52
  */
51
53
  capture?: boolean;
@@ -117,6 +119,26 @@ export function savedCopyFooterSegment(id: string, savedAt: number, now = Date.n
117
119
  };
118
120
  }
119
121
 
122
+ /**
123
+ * The footer's one form for company data a pane shows from another listing
124
+ * of the company: a receipt or a foreign line (SKHY, HY9H in Frankfurt) reads
125
+ * its home line's news, filings or holders, `company data from 000660 KRX`.
126
+ * Null when the data is the listing's own.
127
+ */
128
+ export function companyDataFooterSegment(from: CompanyFrom | null | undefined): PaneFooterSegment | null {
129
+ if (!from) return null;
130
+ const listing = companyFromLabel(from);
131
+ const text = tf("company data from {listing}", { listing });
132
+ return {
133
+ id: "company-data",
134
+ title: from.name ? `${text} (${from.name})` : text,
135
+ // A capture of the rows keeps it: the picture cannot say whose they are.
136
+ capture: true,
137
+ parts: [{ text, tone: "muted" }],
138
+ variants: [[{ text: tf("from {listing}", { listing }), tone: "muted" }]],
139
+ };
140
+ }
141
+
120
142
  /** A pane whose market data is all delayed, by no one figure: the clock alone, its tooltip saying so. */
121
143
  export const DELAYED_SEGMENT: PaneFooterSegment = delayedFooterSegment({ id: "delayed", detail: "Delayed market data" });
122
144
 
@@ -202,6 +224,25 @@ const INFO_FLOOR_CHARS = 10;
202
224
  const WARNING_FLOOR_CHARS = 24;
203
225
  const HINT_GAP = 1;
204
226
 
227
+ /**
228
+ * What the footer draws between two info segments, muted, as a status draws
229
+ * between its own parts ("Newest story Mon 14:23 · 12h delayed"): two
230
+ * segments side by side never read as one sentence ("company data from PBR ·
231
+ * as of 2026-06-30"). An icon (`⚠[!]`, `◷15m`) is a mark, not a phrase: a
232
+ * column sets it apart.
233
+ */
234
+ export const PANE_FOOTER_SEPARATOR = " · ";
235
+ const SEPARATOR_WIDTH = displayWidth(PANE_FOOTER_SEPARATOR);
236
+
237
+ /** Whether the footer draws {@link PANE_FOOTER_SEPARATOR} between two adjacent segments, else a one-column gap. */
238
+ export function separatesPaneFooterSegments(before: Pick<PaneFooterSegment, "icon">, after: Pick<PaneFooterSegment, "icon">): boolean {
239
+ return !before.icon && !after.icon;
240
+ }
241
+
242
+ function segmentGapWidth(before: PaneFooterSegment, after: PaneFooterSegment): number {
243
+ return separatesPaneFooterSegments(before, after) ? SEPARATOR_WIDTH : 1;
244
+ }
245
+
205
246
  function paneHintWidth(hint: Pick<PaneHint, "key" | "label">, prefix = ""): number {
206
247
  return getShortcutHintWidth(hint.key, hint.label, prefix);
207
248
  }
@@ -216,15 +257,12 @@ function infoTextWidth(segments: readonly PaneFooterSegment[]): number {
216
257
  const text = segment.parts.reduce((sum, part, partIndex) => (
217
258
  sum + (partIndex > 0 ? 1 : 0) + displayWidth(part.text)
218
259
  ), 0);
219
- return total + (index > 0 ? 1 : 0) + text;
260
+ return total + (index > 0 ? segmentGapWidth(segments[index - 1]!, segment) : 0) + text;
220
261
  }, 0);
221
262
  }
222
263
 
223
- /**
224
- * The info segments as they fit `width`: segments with `variants` step down
225
- * together, one variant at a time, until the row fits or none is left.
226
- */
227
- export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number, { besideHints = true }: { besideHints?: boolean } = {}): PaneFooterSegment[] {
264
+ /** Segments with `variants` step down together, one variant at a time, until they fit or none is left. */
265
+ function stepDownTogether(info: PaneFooterSegment[], width: number): PaneFooterSegment[] {
228
266
  const stages = Math.max(0, ...info.map((segment) => segment.variants?.length ?? 0));
229
267
  let fitted = info;
230
268
  for (let stage = 0; stage < stages && infoTextWidth(fitted) > width; stage += 1) {
@@ -237,20 +275,50 @@ export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number, { be
237
275
  } : segment;
238
276
  });
239
277
  }
240
- // A trailing status cut mid-word reads as part of the hints beside it ("quote 12h t[x] put"): one that does not
241
- // fit ends in an ellipsis a column short of them, or is left out when less than half of it would show. A
242
- // warning, an icon or a pressable segment stays as it is, clipped by the row if it must be. With no hints beside it
243
- // (a pane picture's footer), the row clips it, so a frame that fits its own status line sees it overflow.
244
- while (besideHints && fitted.length > 1 && infoTextWidth(fitted) > width) {
278
+ return fitted;
279
+ }
280
+
281
+ /**
282
+ * A trailing status cut mid-word reads as part of the hints beside it ("quote
283
+ * 12h t[x] put"): one that does not fit ends in an ellipsis a column short of
284
+ * them, or is left out when less than half of it would show, until the row
285
+ * fits or `keep` segments are left. A warning, an icon or a pressable segment
286
+ * stays as it is, clipped by the row if it must be.
287
+ */
288
+ function trimTrailing(info: PaneFooterSegment[], width: number, keep: number): PaneFooterSegment[] {
289
+ let fitted = info;
290
+ while (fitted.length > keep && infoTextWidth(fitted) > width) {
245
291
  const last = fitted.at(-1)!;
246
292
  if (last.icon || last.onPress || last.parts.some((part) => part.tone === "warning" || part.tone === "negative")) break;
247
- const room = width - infoTextWidth(fitted.slice(0, -1)) - 2;
293
+ // After the separator, a column short of the hints.
294
+ const room = width - infoTextWidth(fitted.slice(0, -1)) - segmentGapWidth(fitted.at(-2)!, last) - 1;
248
295
  if (room * 2 >= infoTextWidth([last])) return [...fitted.slice(0, -1), clipSegment(last, room)];
249
296
  fitted = fitted.slice(0, -1);
250
297
  }
251
298
  return fitted;
252
299
  }
253
300
 
301
+ /**
302
+ * The info segments as they fit `width`: segments with `variants` step down
303
+ * together, one variant at a time, until the row fits or none is left; beside
304
+ * the hints, the trailing status that still does not fit is then cut or left
305
+ * out (trimTrailing). A status after every segment with shorter forms gives
306
+ * way first, before any of them shortens: a pane puts what goes first last
307
+ * (an update time after a note that explains the row in hand).
308
+ *
309
+ * With no hints beside it (a pane picture's footer), the row clips the rest,
310
+ * so a frame that fits its own status line sees it overflow.
311
+ */
312
+ export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number, { besideHints = true }: { besideHints?: boolean } = {}): PaneFooterSegment[] {
313
+ if (!besideHints) return stepDownTogether(info, width);
314
+ const lastWithVariants = info.findLastIndex((segment) => (segment.variants?.length ?? 0) > 0);
315
+ if (lastWithVariants >= 0 && lastWithVariants < info.length - 1 && infoTextWidth(info) > width) {
316
+ const trimmed = trimTrailing(info, width, lastWithVariants + 1);
317
+ if (infoTextWidth(trimmed) <= width) return trimmed;
318
+ }
319
+ return trimTrailing(stepDownTogether(info, width), width, 1);
320
+ }
321
+
254
322
  /** A segment cut to `width` behind an ellipsis, each part keeping its tone; the full text stays as its title. */
255
323
  function clipSegment(segment: PaneFooterSegment, width: number): PaneFooterSegment {
256
324
  const full = segment.parts.map((part) => part.text).join(" ");
@@ -1,5 +1,7 @@
1
- import { useMemo } from "react";
2
- import { usePaneFooter, type PaneFooterSegment, type PaneHint } from "./footer";
1
+ import { useId, useMemo } from "react";
2
+ import { companyDataFooterSegment, usePaneFooter, type PaneFooterSegment, type PaneHint } from "./footer";
3
+ import type { CompanyFrom } from "../../../types/company-from";
4
+ import { useAppLanguage } from "../../../i18n/react";
3
5
  import { useExternalLinkFooter } from "../../use-external-link-footer";
4
6
  import { loadingErrorFooterInfo } from "../../data-table/table-pane";
5
7
 
@@ -28,6 +30,37 @@ function buildPaneStatusInfo({
28
30
  return [...info, ...(stale ? [STALE_SEGMENT] : []), ...loadingErrorFooterInfo(loading, error)];
29
31
  }
30
32
 
33
+ /**
34
+ * Where the company data note sits: after the warning indicator (-10) and
35
+ * before the pane's own status (0 and the ticker quote's -1), so it leads what
36
+ * the pane says about its data and a narrow footer cuts that status first.
37
+ */
38
+ const COMPANY_DATA_ORDER = -5;
39
+
40
+ /**
41
+ * A company pane on a receipt or a foreign line that shows its company's data
42
+ * from the home line says which: `company data from 000660 KRX · as of
43
+ * 2026-06-30`, before the pane's own status (after a warning). Nothing when
44
+ * the data is the listing's own. Every company pane registers it here, so it
45
+ * reads, looks and sits the same in each.
46
+ */
47
+ export function useCompanyDataFooter(companyFrom: CompanyFrom | null | undefined, enabled = true) {
48
+ const language = useAppLanguage();
49
+ const segment = useMemo(() => companyDataFooterSegment(companyFrom), [
50
+ language,
51
+ companyFrom?.symbol,
52
+ companyFrom?.exchange,
53
+ companyFrom?.name,
54
+ ]);
55
+ // Its own registration, so a view nested in a pane (13D/G under holders) never clears the pane's.
56
+ const id = useId();
57
+ usePaneFooter(
58
+ `company-data:${id}`,
59
+ () => enabled && segment ? { order: COMPANY_DATA_ORDER, info: [segment] } : null,
60
+ [enabled, segment],
61
+ );
62
+ }
63
+
31
64
  export function usePaneStatusFooter({
32
65
  registrationId,
33
66
  loading = false,
@@ -53,6 +53,15 @@ export function usePublishStatusWidgetRoom(columns: number): void {
53
53
  }, []);
54
54
  }
55
55
 
56
+ /**
57
+ * Extra columns the other widgets claim, for a widget that sizes itself to
58
+ * what they leave rather than run into them.
59
+ */
60
+ export function useOtherStatusWidgetClaims(id: string): number {
61
+ const others = () => claimedColumns() - (claims.get(id) ?? 0);
62
+ return useSyncExternalStore(subscribe, others, others);
63
+ }
64
+
56
65
  /** The columns the widgets can use, as the status bar last published them. */
57
66
  export function useStatusWidgetRoom(): number {
58
67
  return useSyncExternalStore(subscribe, () => room, () => room);
@@ -421,7 +421,10 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
421
421
  {persistenceError}
422
422
  </Text>
423
423
  ) : null}
424
- <OnboardingActions hint={portfolioSub === "positions" && desktop ? tf("Later: {shortcut}, then AP.", { shortcut: commandBarShortcut }) : undefined}>
424
+ <OnboardingActions hint={portfolioSub === "positions" && desktop
425
+ // The step needs one ticker; once it has one, more can be added later.
426
+ ? positionCount === 0 ? t("Add one ticker to continue.") : tf("Later: {shortcut}, then AP.", { shortcut: commandBarShortcut })
427
+ : undefined}>
425
428
  {portfolioSub === "positions" ? (
426
429
  <>
427
430
  {brokerOptions.length > 0 && positionCount > 0 ? (
@@ -279,6 +279,17 @@ function TuiFieldRow({
279
279
  );
280
280
  }
281
281
 
282
+ /**
283
+ * The line under the form: what Enter does, and how the step goes on. It
284
+ * needs one ticker, and while a field has the keyboard Enter adds, so Esc
285
+ * comes first.
286
+ */
287
+ function positionsHint(editing: boolean, count: number): string {
288
+ if (!editing && count > 0) return tf("Enter continues · a adds another{broker}", { broker: "" });
289
+ const enter = editing ? t("Enter: next field, then add") : t("Enter to add a ticker");
290
+ return `${enter} · ${count > 0 ? t("Esc, then Enter: continue") : t("Add one ticker to continue")}`;
291
+ }
292
+
282
293
  /** The size held: negative for a short. */
283
294
  function SignedNumberField(props: ComponentProps<typeof NumberField>) {
284
295
  return <NumberField {...props} allowNegative />;
@@ -302,13 +313,7 @@ function TuiPositionsPanel({ state, inputRef, editing, selectedSymbol, shortcut
302
313
  ) : state.submitting ? (
303
314
  <Text fg={colors.textDim}>{t("adding...")}</Text>
304
315
  ) : (
305
- <Text fg={colors.textMuted}>
306
- {editing
307
- ? t("Enter: next field, then add.")
308
- : state.positions.length > 0
309
- ? tf("Enter continues · a adds another{broker}", { broker: "" })
310
- : t("Enter to add a ticker.")}
311
- </Text>
316
+ <Text fg={colors.textMuted}>{positionsHint(editing, state.positions.length)}</Text>
312
317
  )}
313
318
  </Box>
314
319
  <Box height={1} />
@@ -160,7 +160,6 @@ export function useOnboardingBrokerSync({
160
160
  draftConfig?: AppConfig,
161
161
  ) => importBrokerPositions(instanceId, undefined, {
162
162
  config: draftConfig,
163
- persistResolvedBrokerConfig: true,
164
163
  signal,
165
164
  onCommitStart: () => {
166
165
  if (brokerSyncAttemptRef.current !== attemptId) return;
@@ -12,7 +12,7 @@ import {
12
12
  } from "../../portfolio/mutations";
13
13
  import { isFuturesTicker } from "../../portfolio/futures-positions";
14
14
  import { loadFuturesEntrySize } from "../../portfolio/futures-terms";
15
- import { wholeUnitQuantityProblem } from "../../portfolio/position-quantity";
15
+ import { ambiguousQuantityProblem } from "../../portfolio/position-quantity";
16
16
  import { useAppDispatch, useAppSelector, useAppStateRef } from "../../state/app/context";
17
17
  import { saveConfigImmediately } from "../../state/config-save-scheduler";
18
18
  import { resolveTickerSearch, upsertTickerFromSearchResult, type ResolvedTickerSearch } from "../../tickers/search";
@@ -292,8 +292,8 @@ export function useOnboardingPositions({
292
292
  if (shares === null) {
293
293
  nextTicker = addTickerToPortfolio(ticker, portfolioId).ticker;
294
294
  } else {
295
- // 1.250 on a listing held in whole lots could be 1,250 as well, and asks as 1,250 does.
296
- const lots = wholeUnitQuantityProblem(current.shares.trim(), ticker, { label: quantityLabel });
295
+ // 1.250 could be 1,250 as well, and asks as 1,250 does.
296
+ const lots = ambiguousQuantityProblem(current.shares.trim(), ticker, { label: quantityLabel });
297
297
  if (lots) {
298
298
  setFieldIdx(1);
299
299
  setError(lots);
@@ -29,11 +29,11 @@ import type { QuoteFlashDirection } from "../quote-flash";
29
29
  import { objectVersion } from "../../utils/object-version";
30
30
  import { getCompletedRegularSessionDisplay } from "../../market-data/market/status";
31
31
  import { usePaneFooter } from "../layout/pane/footer";
32
- import { Button } from "../ui/button";
33
- import { PaneStatusBody } from "../ui/status";
34
32
 
35
33
  export interface TickerTableCell {
36
34
  text: string;
35
+ /** Muted context after `text`, shown whole only when both fit on one line. */
36
+ note?: string;
37
37
  color?: string;
38
38
  /** The full-precision number behind a rounded `text`, which exports and rendered-view reports keep. */
39
39
  value?: number | null;
@@ -97,11 +97,10 @@ export interface TickerListTableViewProps {
97
97
  emptyTitle?: string;
98
98
  emptyHint?: string;
99
99
  /**
100
- * Starts the pane's add-ticker flow. Given, the empty table offers a
101
- * "+ ticker" button for it and says so before the key.
100
+ * The pane's own key for adding a ticker, which the empty table names. The
101
+ * command bar's key when absent. The pane shows its add control once, in its
102
+ * own row, so the empty table points at the key and offers no second one.
102
103
  */
103
- onAddTicker?: () => void;
104
- /** The pane's key for the same flow, named after the click. The command bar's key when absent. */
105
104
  addTickerShortcut?: string;
106
105
  virtualize?: boolean;
107
106
  overscan?: number;
@@ -235,24 +234,12 @@ export function TickerListTableView({
235
234
  rowMenuAccelerators,
236
235
  emptyTitle = t("No tickers."),
237
236
  emptyHint,
238
- onAddTicker,
239
237
  addTickerShortcut,
240
238
  virtualize = true,
241
239
  overscan = 4,
242
240
  }: TickerListTableViewProps) {
243
241
  const commandBarShortcut = useCommandBarShortcut();
244
- const addLabel = `+ ${t("ticker")}`;
245
- const resolvedEmptyHint = emptyHint ?? (onAddTicker
246
- ? tf("Click {button} to add one, or press {shortcut}.", { button: addLabel, shortcut: addTickerShortcut ?? commandBarShortcut })
247
- : tf("Press {shortcut} to add one.", { shortcut: commandBarShortcut }));
248
- const emptyContent = onAddTicker ? (
249
- <PaneStatusBody
250
- empty
251
- emptyTitle={emptyTitle}
252
- emptyMessage={resolvedEmptyHint}
253
- actions={<Button label="Add ticker" displayLabel={addLabel} compact onPress={onAddTicker} />}
254
- />
255
- ) : undefined;
242
+ const resolvedEmptyHint = emptyHint ?? tf("Press {shortcut} to add one.", { shortcut: addTickerShortcut ?? commandBarShortcut });
256
243
  const renderer = useRendererHost();
257
244
  const { showContextMenu } = useContextMenu();
258
245
  const { nativeContextMenu } = useUiCapabilities();
@@ -293,11 +280,12 @@ export function TickerListTableView({
293
280
  };
294
281
  }
295
282
 
296
- const { text, color, onPress, value, backgroundColor } = resolveCellRef.current.resolve(column, ticker, financials);
283
+ const { text, note, color, onPress, value, backgroundColor } = resolveCellRef.current.resolve(column, ticker, financials);
297
284
  const shouldFlash = flashSymbolsRef.current.has(ticker.metadata.ticker)
298
285
  && flashesOnQuoteTick(column.id, financials?.quote);
299
286
  return {
300
287
  text,
288
+ ...(note ? { note } : {}),
301
289
  ...(value !== undefined ? { value } : {}),
302
290
  color: color || (rowState.selected ? colors.selectedText : undefined),
303
291
  attributes: shouldFlash ? TextAttributes.DIM : TextAttributes.NONE,
@@ -395,7 +383,6 @@ export function TickerListTableView({
395
383
  getRowVersion={getRowVersion}
396
384
  emptyStateTitle={emptyTitle}
397
385
  emptyStateHint={resolvedEmptyHint}
398
- emptyContent={emptyContent}
399
386
  virtualize={virtualize}
400
387
  overscan={overscan}
401
388
  rootBefore={rootBefore}
@@ -6,10 +6,13 @@ import { t } from "../../i18n";
6
6
  import { DialogFrame, fitDialogWidth } from "./frame";
7
7
  import { ListView, listCursorMove, type ListViewItem } from "./list-view";
8
8
  import { clampIndex } from "../../utils/math";
9
+ import { withChoiceGroupHeadings } from "./choice-groups";
9
10
 
10
11
  export interface ChoiceDialogChoice {
11
12
  id: string;
12
13
  label: string;
14
+ /** Heading for consecutive choices in the same group. */
15
+ group?: string;
13
16
  description?: string;
14
17
  detail?: string;
15
18
  disabled?: boolean;
@@ -31,10 +34,10 @@ function getChoiceDescription(choice: ChoiceDialogChoice | undefined): string {
31
34
  return choice?.description ?? "";
32
35
  }
33
36
 
34
- function getInitialChoiceIndex(choices: ChoiceDialogChoice[], selectedChoiceId: string | undefined): number {
37
+ function getInitialChoiceIndex(choices: ListViewItem[], selectedChoiceId: string | undefined): number {
35
38
  if (!selectedChoiceId) return choices.findIndex((choice) => !choice.disabled);
36
- const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId);
37
- return selectedIndex >= 0 ? selectedIndex : 0;
39
+ const selectedIndex = choices.findIndex((choice) => choice.id === selectedChoiceId && choice.kind !== "heading");
40
+ return selectedIndex >= 0 ? selectedIndex : choices.findIndex((choice) => choice.kind !== "heading");
38
41
  }
39
42
 
40
43
  function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number {
@@ -42,6 +45,7 @@ function choiceDialogWidth(title: string, choices: ChoiceDialogChoice[]): number
42
45
  title.length,
43
46
  ...choices.map((choice) => choice.label.length + (choice.detail ? choice.detail.length + 4 : 0)),
44
47
  ...choices.map((choice) => getChoiceDescription(choice).length),
48
+ ...choices.map((choice) => choice.group?.length ?? 0),
45
49
  );
46
50
  return Math.max(34, Math.min(76, contentWidth + 4));
47
51
  }
@@ -56,27 +60,28 @@ export function ChoiceDialog({
56
60
  }: ChoiceDialogProps) {
57
61
  const colors = useThemeColors();
58
62
  const desktop = useUiHost().kind === "desktop-web";
59
- const [index, setIndex] = useState(() =>
60
- clampIndex(getInitialChoiceIndex(choices, selectedChoiceId), choices.length, -1)
61
- );
62
- const selectedIndex = clampIndex(index, choices.length, -1);
63
- const selectedChoice = selectedIndex >= 0 ? choices[selectedIndex] : undefined;
64
- // Terminal input can deliver navigation and Enter before React renders.
65
- const cursor = useRef(selectedIndex);
66
- const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
67
- const items = useMemo<ListViewItem[]>(() => choices.map((choice) => ({
63
+ const items = useMemo<ListViewItem[]>(() => withChoiceGroupHeadings(choices.map((choice) => ({
68
64
  id: choice.id,
69
65
  label: choice.label,
66
+ group: choice.group,
70
67
  description: getChoiceDescription(choice),
71
68
  detail: choice.detail,
72
69
  disabled: choice.disabled,
73
70
  color: choice.color,
74
- })), [choices]);
71
+ }))), [choices]);
72
+ const [index, setIndex] = useState(() =>
73
+ clampIndex(getInitialChoiceIndex(items, selectedChoiceId), items.length, -1)
74
+ );
75
+ const selectedIndex = clampIndex(index, items.length, -1);
76
+ const selectedChoice = selectedIndex >= 0 ? items[selectedIndex] : undefined;
77
+ // Terminal input can deliver navigation and Enter before React renders.
78
+ const cursor = useRef(selectedIndex);
79
+ const selectIndex = (next: number) => { cursor.current = next; setIndex(next); };
75
80
  const width = useMemo(() => choiceDialogWidth(title, choices), [choices, title]);
76
81
 
77
82
  useEffect(() => {
78
- selectIndex(clampIndex(cursor.current, choices.length, -1));
79
- }, [choices.length]);
83
+ selectIndex(clampIndex(cursor.current, items.length, -1));
84
+ }, [items.length]);
80
85
 
81
86
  const activateChoice = (choice: ChoiceDialogChoice | undefined) => {
82
87
  if (!choice || choice.disabled) return;
@@ -87,9 +92,9 @@ export function ChoiceDialog({
87
92
  event.stopPropagation();
88
93
  const move = listCursorMove(event, MAX_VISIBLE_CHOICE_ROWS - 1);
89
94
  if (move) {
90
- selectIndex(move(choices, cursor.current));
95
+ selectIndex(move(items, cursor.current));
91
96
  } else if (event.name === "enter" || event.name === "return") {
92
- activateChoice(choices[clampIndex(cursor.current, choices.length, -1)]);
97
+ activateChoice(items[clampIndex(cursor.current, items.length, -1)]);
93
98
  } else if (event.name === "escape") {
94
99
  resolve("");
95
100
  }
@@ -110,7 +115,7 @@ export function ChoiceDialog({
110
115
  scrollable={items.length > MAX_VISIBLE_CHOICE_ROWS}
111
116
  selectOnHover
112
117
  onSelect={selectIndex}
113
- onActivate={(_, nextIndex) => activateChoice(choices[nextIndex])}
118
+ onActivate={activateChoice}
114
119
  />
115
120
  <Box height={1} />
116
121
  <Text fg={colors.textDim} wrapText>{getChoiceDescription(selectedChoice)}</Text>
@@ -0,0 +1,18 @@
1
+ interface ChoiceGroupHeading {
2
+ id: string;
3
+ label: string;
4
+ kind: "heading";
5
+ disabled: true;
6
+ }
7
+
8
+ /** Label each contiguous group without changing the order or identity of its choices. */
9
+ export function withChoiceGroupHeadings<T extends { group?: string }>(choices: readonly T[]): Array<T | ChoiceGroupHeading> {
10
+ let previousGroup: string | undefined;
11
+ return choices.flatMap((choice, index): Array<T | ChoiceGroupHeading> => {
12
+ const heading = choice.group && choice.group !== previousGroup
13
+ ? { id: `\u0000group:${index}`, label: choice.group, kind: "heading" as const, disabled: true as const }
14
+ : null;
15
+ previousGroup = choice.group;
16
+ return heading ? [heading, choice] : [choice];
17
+ });
18
+ }
@@ -1,5 +1,5 @@
1
1
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { Box, ScrollBox, Text, TextAttributes, useNativeRenderer } from "../../../../ui";
2
+ import { Box, ScrollBox, Span, Text, TextAttributes, useNativeRenderer } from "../../../../ui";
3
3
  import { hoverBg } from "../../../../theme/colors";
4
4
  import { useThemeColors } from "../../../../theme/theme-context";
5
5
  import { usePaneInstance } from "../../../../state/app/context";
@@ -12,7 +12,6 @@ import { EmptyState } from "../../status";
12
12
  import { observeScrollBoxContentSize, observeScrollBoxViewportSize } from "../../../../renderers/opentui/scrollbox-layout";
13
13
  import {
14
14
  expandTableColumns,
15
- fitTableCellText,
16
15
  fitTableHeaderText,
17
16
  getTableWidth,
18
17
  hasMeaningfulTableHorizontalOverflow,
@@ -30,6 +29,7 @@ import { useFocusOwningPane, useVisibleRangeEmitter } from "../hooks";
30
29
  import { useStableColumns } from "../stable-columns";
31
30
  import {
32
31
  cutColumnText,
32
+ fitColumnCellText,
33
33
  resolveDataTableColumnClips,
34
34
  resolveDataTableVisibleWindow,
35
35
  resolveMoreColumnsHint,
@@ -212,10 +212,8 @@ function OpenTuiDataTableRowInner<
212
212
  const shownWidth = clip ? clip.width : column.width - inset;
213
213
  // Cut text is fitted to what shows; a number the edge cuts stays blank in its own place,
214
214
  // and its cell keeps its background and its press.
215
- const shownText = clip
216
- ? cutColumnText(cell.text, column.align, column.width, clip, (text, width) => fitTableCellText(text, width, column.align))
217
- : shownWidth > 0 ? fitTableCellText(cell.text, shownWidth, column.align) : "";
218
- const contentCut = clip !== null && column.align === "right" && shownText.trim() === "";
215
+ const fitted = fitColumnCellText(cell.text, clip ? column.width : shownWidth, column.align, clip, cell.note);
216
+ const contentCut = clip !== null && column.align === "right" && fitted.text.trim() === "";
219
217
  return (
220
218
  <Box
221
219
  key={column.id}
@@ -255,7 +253,11 @@ function OpenTuiDataTableRowInner<
255
253
  ? colors.selectedText
256
254
  : cell.color ?? colors.text}
257
255
  >
258
- {`${frozen ? " ".repeat(horizontalPadding) : ""}${" ".repeat(inset)}${shownText}${frozen ? " ".repeat(columnGap) : ""}`}
256
+ {fitted.note ? <>
257
+ {`${frozen ? " ".repeat(horizontalPadding) : ""}${" ".repeat(inset)}${fitted.text}`}
258
+ <Span fg={colors.textDim} attributes={TextAttributes.NONE}>{fitted.note}</Span>
259
+ {`${fitted.padding ?? ""}${frozen ? " ".repeat(columnGap) : ""}`}
260
+ </> : `${frozen ? " ".repeat(horizontalPadding) : ""}${" ".repeat(inset)}${fitted.text}${frozen ? " ".repeat(columnGap) : ""}`}
259
261
  </Text>
260
262
  )}
261
263
  </Box>