@gloom-sh/terminal 0.18.5 → 0.19.0

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 (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,8 +1,8 @@
1
1
  import { formatPercentRaw } from "../../../utils/format";
2
2
  import type { ChartVectorShape } from "../../../ui/host";
3
3
  import type { NativeChartBitmap } from "../native/chart-rasterizer";
4
- import { drawCircle, drawLine, fillRect, parseHex } from "../native/raster/primitives";
5
- import { formatCompositeAxisValue, formatCompositeCursorDate } from "./format";
4
+ import { drawCircle, drawLine, fillRect, parseHex, type RgbaColor } from "../native/raster/primitives";
5
+ import { formatCompositeAxisValue, formatCompositeCursorDate, formatCompositeCursorValue } from "./format";
6
6
  import { projectCompositeValue, unprojectCompositeValue } from "./scene";
7
7
  import { projectCompositeTimestamp, unprojectCompositeTimestamp } from "./time-scale";
8
8
  import type {
@@ -13,7 +13,14 @@ import type {
13
13
  import { clamp } from "../../../utils/math";
14
14
 
15
15
  /** Pointer tools that take the drag away from panning while one is picked. */
16
- export type ChartToolKind = "measure" | "zoom" | "line" | "pencil" | "level";
16
+ export type ChartToolKind = "measure" | "zoom" | "line" | "pencil" | "level" | "rect" | "fib" | "ray";
17
+
18
+ /** How a saved drawing is drawn from its points. */
19
+ export type ChartDrawingShape = "line" | "rect" | "fib" | "ray";
20
+ const SHAPED_TOOLS = ["rect", "fib", "ray"] as const;
21
+
22
+ /** Retracement ratios, measured back from the drag's end toward its start. */
23
+ const FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1] as const;
17
24
 
18
25
  export interface ChartDrawingPoint {
19
26
  time: number;
@@ -77,6 +84,18 @@ export function parseChartDrawings(value: unknown): ChartDrawing[] {
77
84
  return drawings;
78
85
  }
79
86
 
87
+ /**
88
+ * The shape rides in the id (`fib:3`) rather than a field of its own: an older
89
+ * build refuses a saved drawing with a key it does not know and would drop every
90
+ * drawing of the pane, while an id it does not know still draws as a line.
91
+ */
92
+ export function chartDrawingShape(drawing: Pick<ChartDrawing, "id" | "points">): ChartDrawingShape {
93
+ const prefix = drawing.id.slice(0, Math.max(0, drawing.id.indexOf(":")));
94
+ return drawing.points.length === 2 && (SHAPED_TOOLS as readonly string[]).includes(prefix)
95
+ ? prefix as ChartDrawingShape
96
+ : "line";
97
+ }
98
+
80
99
  export interface ChartDrawingHit {
81
100
  drawing: ChartDrawing;
82
101
  /** Grabbed endpoint, or null when the whole shape was grabbed. */
@@ -119,7 +138,7 @@ const HOUR_MS = 60 * MINUTE_MS;
119
138
  const DAY_MS = 24 * HOUR_MS;
120
139
 
121
140
  function drawingPointFrom(
122
- scene: CompositeChartScene,
141
+ scene: Pick<CompositeChartScene, "timeScale">,
123
142
  domain: CompositeAxisDomain,
124
143
  xRatio: number,
125
144
  yRatio: number,
@@ -132,14 +151,23 @@ function drawingPointFrom(
132
151
 
133
152
  /** Data anchors for a finished drag, so a drawing survives pan and zoom. */
134
153
  export function resolveDrawingFromDrag(
135
- scene: CompositeChartScene,
154
+ scene: Pick<CompositeChartScene, "timeScale">,
136
155
  panel: CompositePanelScene,
137
156
  drag: ChartToolDrag,
138
157
  color: string,
139
158
  id: string,
140
159
  ): ChartDrawing | null {
141
160
  const domain = resolveMeasureAxisDomain(panel);
142
- if (!domain || !isMeaningfulToolDrag(drag)) return null;
161
+ // A ray needs only its price, so a click places one.
162
+ if (!domain || (drag.kind !== "ray" && !isMeaningfulToolDrag(drag))) return null;
163
+ if (isShapedTool(drag.kind)) {
164
+ // A ray holds its first price, so its stored end is level with it and an
165
+ // older build draws the same horizontal run.
166
+ const endYRatio = drag.kind === "ray" ? drag.startYRatio : drag.endYRatio;
167
+ const start = drawingPointFrom(scene, domain, drag.startXRatio, drag.startYRatio);
168
+ const end = drawingPointFrom(scene, domain, drag.endXRatio, endYRatio);
169
+ return start && end ? { id: shapedDrawingId(drag.kind, id), panelId: panel.id, points: [start, end], color } : null;
170
+ }
143
171
  const source = drag.kind === "pencil" && drag.path.length > 1
144
172
  ? drag.path
145
173
  : [
@@ -152,11 +180,74 @@ export function resolveDrawingFromDrag(
152
180
  return points.length < 2 ? null : { id, panelId: panel.id, points, color };
153
181
  }
154
182
 
183
+ function isShapedTool(kind: ChartToolKind): kind is typeof SHAPED_TOOLS[number] {
184
+ return (SHAPED_TOOLS as readonly string[]).includes(kind);
185
+ }
186
+
187
+ /** The id a drawing of this tool is saved under, see `chartDrawingShape`. */
188
+ export function shapedDrawingId(kind: ChartToolKind, id: string): string {
189
+ return isShapedTool(kind) ? `${kind}:${id}` : id;
190
+ }
191
+
192
+ type RatioPoint = { xRatio: number; yRatio: number };
193
+
194
+ interface DrawingGeometry {
195
+ segments: Array<[RatioPoint, RatioPoint]>;
196
+ /** Corners of a filled box under the segments. */
197
+ box: [RatioPoint, RatioPoint] | null;
198
+ /** The points that can be grabbed and reshaped. */
199
+ anchors: RatioPoint[];
200
+ labels: Array<{ at: RatioPoint; text: string }>;
201
+ }
202
+
203
+ /** What a drawing paints, in plot ratios: shared by the bitmap, the vectors and the hit test. */
204
+ function drawingGeometry(
205
+ scene: Pick<CompositeChartScene, "timeScale">,
206
+ panel: CompositePanelScene,
207
+ drawing: ChartDrawing,
208
+ ): DrawingGeometry | null {
209
+ const projected = projectDrawing(scene, panel, drawing);
210
+ if (!projected) return null;
211
+ const shape = chartDrawingShape(drawing);
212
+ if (shape === "line") {
213
+ return {
214
+ segments: projected.slice(1).map((point, index) => [projected[index]!, point]),
215
+ box: null,
216
+ anchors: projected.length === 2 ? projected : [],
217
+ labels: [],
218
+ };
219
+ }
220
+ const [start, end] = projected as [RatioPoint, RatioPoint];
221
+ if (shape === "ray") {
222
+ return { segments: [[start, { xRatio: 1, yRatio: start.yRatio }]], box: null, anchors: [start], labels: [] };
223
+ }
224
+ if (shape === "rect") {
225
+ const corner = (xRatio: number, yRatio: number) => ({ xRatio, yRatio });
226
+ const a = corner(start.xRatio, end.yRatio);
227
+ const b = corner(end.xRatio, start.yRatio);
228
+ return { segments: [[start, a], [a, end], [end, b], [b, start]], box: [start, end], anchors: [start, end], labels: [] };
229
+ }
230
+ const domain = resolveMeasureAxisDomain(panel)!;
231
+ const left = Math.min(start.xRatio, end.xRatio);
232
+ const right = Math.max(start.xRatio, end.xRatio);
233
+ const [from, to] = drawing.points as [ChartDrawingPoint, ChartDrawingPoint];
234
+ const segments: DrawingGeometry["segments"] = [];
235
+ const labels: DrawingGeometry["labels"] = [];
236
+ for (const level of FIB_LEVELS) {
237
+ const value = to.value + (from.value - to.value) * level;
238
+ const yRatio = projectCompositeValue(value, domain);
239
+ if (yRatio === null) continue;
240
+ segments.push([{ xRatio: left, yRatio }, { xRatio: right, yRatio }]);
241
+ labels.push({ at: { xRatio: left, yRatio }, text: `${level} ${formatCompositeCursorValue(value, domain)}` });
242
+ }
243
+ return { segments, box: null, anchors: [start, end], labels };
244
+ }
245
+
155
246
  function projectDrawing(
156
247
  scene: Pick<CompositeChartScene, "timeScale">,
157
248
  panel: CompositePanelScene,
158
249
  drawing: ChartDrawing,
159
- ): Array<{ xRatio: number; yRatio: number }> | null {
250
+ ): RatioPoint[] | null {
160
251
  const domain = resolveMeasureAxisDomain(panel);
161
252
  if (!domain) return null;
162
253
  const projected: Array<{ xRatio: number; yRatio: number }> = [];
@@ -198,22 +289,21 @@ export function hitTestDrawings(
198
289
  let best: { hit: ChartDrawingHit; distance: number; endpoint: boolean } | null = null;
199
290
  const scale = Math.max(aspect, Number.EPSILON);
200
291
  const target = { x: pointer.xRatio * scale, y: pointer.yRatio };
292
+ const toPlot = (point: RatioPoint) => ({ x: point.xRatio * scale, y: point.yRatio });
201
293
  for (const drawing of drawings) {
202
294
  if (drawing.panelId !== panel.id) continue;
203
- const projected = projectDrawing(scene, panel, drawing);
204
- if (!projected) continue;
205
- const shape = projected.map((point) => ({ x: point.xRatio * scale, y: point.yRatio }));
206
- if (drawing.points.length === 2) {
207
- for (const [index, point] of shape.entries()) {
208
- const distance = Math.hypot(target.x - point.x, target.y - point.y);
209
- if (distance > DRAWING_GRAB_RATIO) continue;
210
- if (!best?.endpoint || best.distance > distance) {
211
- best = { hit: { drawing, pointIndex: index }, distance, endpoint: true };
212
- }
295
+ const geometry = drawingGeometry(scene, panel, drawing);
296
+ if (!geometry) continue;
297
+ for (const [index, anchor] of geometry.anchors.entries()) {
298
+ const point = toPlot(anchor);
299
+ const distance = Math.hypot(target.x - point.x, target.y - point.y);
300
+ if (distance > DRAWING_GRAB_RATIO) continue;
301
+ if (!best?.endpoint || best.distance > distance) {
302
+ best = { hit: { drawing, pointIndex: index }, distance, endpoint: true };
213
303
  }
214
304
  }
215
- for (let index = 1; index < shape.length; index += 1) {
216
- const distance = distanceToSegment(target, shape[index - 1]!, shape[index]!);
305
+ for (const [from, to] of geometry.segments) {
306
+ const distance = distanceToSegment(target, toPlot(from), toPlot(to));
217
307
  if (distance > DRAWING_GRAB_RATIO || best?.endpoint) continue;
218
308
  if (!best || best.distance > distance) {
219
309
  best = { hit: { drawing, pointIndex: null }, distance, endpoint: false };
@@ -235,19 +325,18 @@ export function shiftDrawing(
235
325
  const domain = resolveMeasureAxisDomain(panel);
236
326
  const projected = projectDrawing(scene, panel, drawing);
237
327
  if (!domain || !projected) return drawing;
238
- return {
239
- ...drawing,
240
- points: drawing.points.map((point, index) => {
241
- if (pointIndex !== null && index !== pointIndex) return point;
242
- const source = projected[index]!;
243
- return drawingPointFrom(
244
- scene,
245
- domain,
246
- clamp(source.xRatio + deltaXRatio, 0, 1),
247
- clamp(source.yRatio + deltaYRatio, 0, 1),
248
- ) ?? point;
249
- }),
250
- };
328
+ const points = drawing.points.map((point, index) => {
329
+ if (pointIndex !== null && index !== pointIndex) return point;
330
+ const source = projected[index]!;
331
+ return drawingPointFrom(
332
+ scene,
333
+ domain,
334
+ clamp(source.xRatio + deltaXRatio, 0, 1),
335
+ clamp(source.yRatio + deltaYRatio, 0, 1),
336
+ ) ?? point;
337
+ });
338
+ if (chartDrawingShape(drawing) === "ray") points[1] = { ...points[1]!, value: points[0]!.value };
339
+ return { ...drawing, points };
251
340
  }
252
341
 
253
342
  export function nextDrawingColor(color: string): string {
@@ -268,7 +357,7 @@ export function resolveChartToolKind(modifiers: {
268
357
  }
269
358
 
270
359
  export function isDrawingTool(tool: ChartToolKind | null): boolean {
271
- return tool === "line" || tool === "pencil";
360
+ return tool === "line" || tool === "pencil" || (tool !== null && isShapedTool(tool));
272
361
  }
273
362
 
274
363
  function isMeaningfulToolDrag(drag: ChartToolDrag): boolean {
@@ -458,20 +547,29 @@ export function buildChartToolVectors(input: {
458
547
  }): ChartVectorShape[] {
459
548
  const shapes: ChartVectorShape[] = [];
460
549
  for (const drawing of input.drawings) {
461
- const projected = projectDrawing(input.scene, input.panel, drawing);
462
- if (!projected) continue;
463
550
  const selected = input.selectedId === drawing.id;
464
- shapes.push({
465
- id: drawing.id,
466
- points: projected.map((point) => ({ x: point.xRatio, y: point.yRatio })),
467
- color: drawing.color,
468
- strokeWidth: selected ? 2.4 : 1.6,
469
- handles: selected && drawing.points.length === 2,
470
- });
551
+ if (chartDrawingShape(drawing) === "line") {
552
+ const projected = projectDrawing(input.scene, input.panel, drawing);
553
+ if (!projected) continue;
554
+ shapes.push({
555
+ id: drawing.id,
556
+ points: projected.map((point) => ({ x: point.xRatio, y: point.yRatio })),
557
+ color: drawing.color,
558
+ strokeWidth: selected ? 2.4 : 1.6,
559
+ handles: selected && drawing.points.length === 2,
560
+ });
561
+ continue;
562
+ }
563
+ shapes.push(...shapedDrawingVectors(input.scene, input.panel, drawing, selected));
471
564
  }
472
565
 
473
566
  const drag = input.drag;
474
567
  if (!drag) return shapes;
568
+ if (isShapedTool(drag.kind)) {
569
+ const preview = resolveDrawingFromDrag(input.scene, input.panel, drag, input.colors.draw, "tool:draw");
570
+ if (preview) shapes.push(...shapedDrawingVectors(input.scene, input.panel, preview, false));
571
+ return shapes;
572
+ }
475
573
  const start = { x: drag.startXRatio, y: drag.startYRatio };
476
574
  const end = { x: drag.endXRatio, y: drag.endYRatio };
477
575
  if (drag.kind === "zoom") {
@@ -505,6 +603,55 @@ export function buildChartToolVectors(input: {
505
603
  return shapes;
506
604
  }
507
605
 
606
+ function shapedDrawingVectors(
607
+ scene: Pick<CompositeChartScene, "timeScale">,
608
+ panel: CompositePanelScene,
609
+ drawing: ChartDrawing,
610
+ selected: boolean,
611
+ ): ChartVectorShape[] {
612
+ const geometry = drawingGeometry(scene, panel, drawing);
613
+ if (!geometry) return [];
614
+ const toVector = (point: RatioPoint) => ({ x: point.xRatio, y: point.yRatio });
615
+ const shapes: ChartVectorShape[] = [];
616
+ if (geometry.box) {
617
+ shapes.push({ id: `${drawing.id}:box`, points: geometry.box.map(toVector), color: drawing.color, box: true, fillOpacity: 0.12, strokeWidth: 0 });
618
+ }
619
+ geometry.segments.forEach((segment, index) => shapes.push({
620
+ id: `${drawing.id}:${index}`,
621
+ points: segment.map(toVector),
622
+ color: drawing.color,
623
+ strokeWidth: selected ? 2 : 1.3,
624
+ ...(geometry.labels[index] ? { label: geometry.labels[index]!.text } : {}),
625
+ }));
626
+ if (selected) {
627
+ shapes.push({ id: `${drawing.id}:anchors`, points: geometry.anchors.map(toVector), color: drawing.color, opacity: 0, handles: true });
628
+ }
629
+ return shapes;
630
+ }
631
+
632
+ /** Paints a shaped drawing's segments, box and handles onto a raster. */
633
+ function paintShapedDrawing(
634
+ data: Uint8Array,
635
+ width: number,
636
+ height: number,
637
+ geometry: DrawingGeometry,
638
+ color: RgbaColor,
639
+ selected: boolean,
640
+ ): void {
641
+ const maxX = Math.max(width - 1, 0);
642
+ const maxY = Math.max(height - 1, 0);
643
+ if (geometry.box) {
644
+ const [a, b] = geometry.box;
645
+ fillRect(data, width, height, Math.min(a.xRatio, b.xRatio) * maxX, Math.min(a.yRatio, b.yRatio) * maxY,
646
+ Math.max(a.xRatio, b.xRatio) * maxX, Math.max(a.yRatio, b.yRatio) * maxY, color, 0.12);
647
+ }
648
+ for (const [from, to] of geometry.segments) {
649
+ drawLine(data, width, height, from.xRatio * maxX, from.yRatio * maxY, to.xRatio * maxX, to.yRatio * maxY, color, selected ? 2.2 : 1.4);
650
+ }
651
+ if (!selected) return;
652
+ for (const anchor of geometry.anchors) drawCircle(data, width, height, anchor.xRatio * maxX, anchor.yRatio * maxY, 3.2, color);
653
+ }
654
+
508
655
  /**
509
656
  * Paints drawings and the active tool on a copy of the panel raster. Copying
510
657
  * keeps the cached plot bitmap intact and keeps a single bitmap layer, which is
@@ -529,10 +676,15 @@ export function drawChartToolOverlay(
529
676
  const maxY = Math.max(height - 1, 0);
530
677
 
531
678
  for (const drawing of drawings?.items ?? []) {
532
- const shape = projectDrawing(drawings!.scene, drawings!.panel, drawing);
533
- if (!shape) continue;
534
679
  const selected = drawings!.selectedId === drawing.id;
535
680
  const color = parseHex(drawing.color);
681
+ if (chartDrawingShape(drawing) !== "line") {
682
+ const geometry = drawingGeometry(drawings!.scene, drawings!.panel, drawing);
683
+ if (geometry) paintShapedDrawing(data, width, height, geometry, color, selected);
684
+ continue;
685
+ }
686
+ const shape = projectDrawing(drawings!.scene, drawings!.panel, drawing);
687
+ if (!shape) continue;
536
688
  for (let index = 1; index < shape.length; index += 1) {
537
689
  drawLine(
538
690
  data,
@@ -560,6 +712,13 @@ export function drawChartToolOverlay(
560
712
  const left = Math.min(x0, x1);
561
713
  const right = Math.max(x0, x1);
562
714
 
715
+ if (isShapedTool(drag.kind)) {
716
+ const preview = drawings && resolveDrawingFromDrag(drawings.scene, drawings.panel, drag, colors.draw, "tool:draw");
717
+ const geometry = preview && drawingGeometry(drawings.scene, drawings.panel, preview);
718
+ if (geometry) paintShapedDrawing(data, width, height, geometry, parseHex(colors.draw), false);
719
+ return { width, height, pixels: data };
720
+ }
721
+
563
722
  if (drag.kind === "line" || drag.kind === "pencil") {
564
723
  const color = parseHex(colors.draw);
565
724
  const trail = drag.kind === "pencil" && drag.path.length > 1
@@ -26,6 +26,7 @@ import {
26
26
  resolveZoomTimeRange,
27
27
  isDrawingTool,
28
28
  nextDrawingColor,
29
+ shapedDrawingId,
29
30
  type ChartDrawing,
30
31
  type ChartDrawingPoint,
31
32
  type ChartToolDrag,
@@ -301,7 +302,7 @@ export function useCompositeChartKeyboard({
301
302
  const placement = keyboardPlacement;
302
303
  setKeyboardPlacement(null);
303
304
  // A placement that never left its anchor spans nothing, like a click.
304
- if (!placement || placement.start.time === placement.end.time) return;
305
+ if (!placement || (placement.start.time === placement.end.time && placement.kind !== "ray")) return;
305
306
  if (placement.kind === "zoom") {
306
307
  if (!navigationFrame) return;
307
308
  const range = resolveZoomTimeRange(placement.start.time, placement.end.time, navigationFrame.minimumSpanMs);
@@ -313,9 +314,9 @@ export function useCompositeChartKeyboard({
313
314
  ? placement.path.filter((point, index, path) => (
314
315
  index === 0 || point.time !== path[index - 1]!.time || point.value !== path[index - 1]!.value
315
316
  ))
316
- : [placement.start, placement.end];
317
+ : [placement.start, placement.kind === "ray" ? { ...placement.end, value: placement.start.value } : placement.end];
317
318
  if (points.length < 2) return;
318
- addDrawing({ id: nextDrawingId(), panelId: placement.panelId, points, color: drawColor });
319
+ addDrawing({ id: shapedDrawingId(placement.kind, nextDrawingId()), panelId: placement.panelId, points, color: drawColor });
319
320
  };
320
321
  const stepLegend = (direction: -1 | 1) => {
321
322
  onActivate?.();
@@ -566,7 +567,10 @@ export function useCompositeChartKeyboard({
566
567
  || ((interaction === "arm-measure"
567
568
  || interaction === "arm-zoom"
568
569
  || interaction === "arm-line"
569
- || interaction === "arm-pencil") && !scene)
570
+ || interaction === "arm-pencil"
571
+ || interaction === "arm-ray"
572
+ || interaction === "arm-rect"
573
+ || interaction === "arm-fib") && !scene)
570
574
  || (interaction === "arm-level" && !levelsEditable)
571
575
  || (interaction === "delete-drawing" && !canDeleteDrawing)
572
576
  || (interaction === "cycle-colour" && !isDrawingTool(armedTool) && !selectedDrawingId)
@@ -586,6 +590,9 @@ export function useCompositeChartKeyboard({
586
590
  case "arm-zoom":
587
591
  case "arm-line":
588
592
  case "arm-pencil":
593
+ case "arm-ray":
594
+ case "arm-rect":
595
+ case "arm-fib":
589
596
  case "arm-level":
590
597
  onActivate?.();
591
598
  armTool(ARMED_TOOL_BY_INTERACTION[interaction]);
@@ -119,7 +119,8 @@ export function buildAssistCommandInventory({
119
119
  prefixes: normalizePrefixes(command.shortcut ? [command.shortcut] : []),
120
120
  })),
121
121
  ...paneTemplates.map((template) => ({
122
- descriptor: describe(
122
+ // An unlisted function keeps its codes, so nothing else takes them, but is never suggested.
123
+ descriptor: template.listed === false ? null : describe(
123
124
  template.shortcut?.prefix ?? "",
124
125
  getPaneTemplateDisplayLabel(template),
125
126
  template.shortcut?.aliases?.length ? `${template.description} Also ${template.shortcut.aliases.join(", ")}.` : template.description,
@@ -245,19 +245,34 @@ export function runDirectCommandAction(options: {
245
245
  }
246
246
  case "import-config": {
247
247
  const importPath = getDefaultConfigBackupPath();
248
- void importConfig(state.config.dataDir, importPath)
249
- .then((imported) => {
250
- cancelThemePreview();
251
- clearTransientThemePreview();
252
- dispatch({ type: "SET_CONFIG", config: imported });
253
- applyTheme(imported.theme);
254
- dispatch({ type: "SET_THEME", theme: imported.theme });
255
- notify(`Imported config from ${importPath}.`, { type: "success" });
256
- closeAll({ revertThemePreview: false });
257
- })
258
- .catch((error) => {
259
- notify(error instanceof Error ? error.message : "Import failed.", { type: "error" });
260
- });
248
+ // An import replaces every layout, portfolio and setting at once, so it
249
+ // is never one keystroke away.
250
+ openInlineConfirm({
251
+ confirmId: "import-config",
252
+ title: "Import Config",
253
+ body: [
254
+ `This replaces your layouts, portfolios, watchlists and settings with the ones saved in ${importPath}.`,
255
+ "What you have now is not kept unless you export it first.",
256
+ ],
257
+ confirmLabel: "Import",
258
+ cancelLabel: "Back",
259
+ tone: "danger",
260
+ // A stray Enter keeps what is there.
261
+ defaultAction: "cancel",
262
+ onConfirm: async () => {
263
+ try {
264
+ const imported = await importConfig(getState().config.dataDir, importPath);
265
+ cancelThemePreview();
266
+ clearTransientThemePreview();
267
+ dispatch({ type: "SET_CONFIG", config: imported });
268
+ applyTheme(imported.theme);
269
+ dispatch({ type: "SET_THEME", theme: imported.theme });
270
+ notify(`Imported config from ${importPath}.`, { type: "success" });
271
+ } catch (error) {
272
+ notify(error instanceof Error ? error.message : "Import failed.", { type: "error" });
273
+ }
274
+ },
275
+ });
261
276
  return;
262
277
  }
263
278
  case "cycle-chart-renderer": {
@@ -260,8 +260,9 @@ export function buildPaneShortcutItems(options: {
260
260
  showShortcut?: boolean;
261
261
  }) => ResultItem;
262
262
  }): ResultItem[] {
263
+ // An unlisted template (`listed: false`) still opens when its code is typed, which is resolved apart from these rows.
263
264
  const items = options.templates
264
- .filter((template) => template.shortcut && template.shortcut.showInBrowse !== false)
265
+ .filter((template) => template.shortcut && template.shortcut.showInBrowse !== false && template.listed !== false)
265
266
  .map((template) => options.createItem(template, {
266
267
  category: "Panes",
267
268
  createOptions: options.createOptions,
@@ -279,7 +280,7 @@ export function buildNonShortcutPaneTemplateItems(options: {
279
280
  createItem: (template: PaneTemplateDef, options?: { category?: string }) => ResultItem;
280
281
  }): ResultItem[] {
281
282
  const items = options.templates
282
- .filter((template) => !template.shortcut)
283
+ .filter((template) => !template.shortcut && template.listed !== false)
283
284
  .map((template) => options.createItem(template, { category: "Panes" }));
284
285
 
285
286
  return options.filterQuery
@@ -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) {
@@ -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);
@@ -34,6 +34,8 @@ import { PaneStatusBody } from "../ui/status";
34
34
 
35
35
  export interface TickerTableCell {
36
36
  text: string;
37
+ /** Muted context after `text`, shown whole only when both fit on one line. */
38
+ note?: string;
37
39
  color?: string;
38
40
  /** The full-precision number behind a rounded `text`, which exports and rendered-view reports keep. */
39
41
  value?: number | null;
@@ -293,11 +295,12 @@ export function TickerListTableView({
293
295
  };
294
296
  }
295
297
 
296
- const { text, color, onPress, value, backgroundColor } = resolveCellRef.current.resolve(column, ticker, financials);
298
+ const { text, note, color, onPress, value, backgroundColor } = resolveCellRef.current.resolve(column, ticker, financials);
297
299
  const shouldFlash = flashSymbolsRef.current.has(ticker.metadata.ticker)
298
300
  && flashesOnQuoteTick(column.id, financials?.quote);
299
301
  return {
300
302
  text,
303
+ ...(note ? { note } : {}),
301
304
  ...(value !== undefined ? { value } : {}),
302
305
  color: color || (rowState.selected ? colors.selectedText : undefined),
303
306
  attributes: shouldFlash ? TextAttributes.DIM : TextAttributes.NONE,