gloomberb 0.16.3 → 0.16.4

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 (451) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -10,7 +10,9 @@ import { CompositeLegend } from "./legend";
10
10
  import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
11
11
  import { AsciiText, Box, ChartSurface, Text, useUiCapabilities } from "../../../ui";
12
12
  import { useOptionalPaneInstanceId } from "../../../state/app/context";
13
- import { useThemeColors } from "../../../theme/theme-context";
13
+ import { useThemeColors, useThemeId } from "../../../theme/theme-context";
14
+ import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
15
+ import { assignSeriesLineCues } from "./series-cues";
14
16
  import { chartSurfaceBackground, usePaneSurface } from "../../layout/pane/surface";
15
17
  import { CHART_WATERMARK_ROLE } from "../../../utils/screenshot-watermark";
16
18
  import type { ResolvedSeries } from "../../../time-series/types";
@@ -56,7 +58,28 @@ function chartWatermarkScale(plotWidthPx: number, plotHeightPx: number): number
56
58
  }
57
59
 
58
60
  const NO_X_MARKERS: readonly CompositeChartXMarker[] = [];
59
- export function CompositeChart({
61
+
62
+ /** Default-palette series in the theme's own palette, where it has one; the same list otherwise. */
63
+ function useThemedSeriesList(list: ResolvedSeries[], themeId: string): ResolvedSeries[];
64
+ function useThemedSeriesList(list: ResolvedSeries[] | undefined, themeId: string): ResolvedSeries[] | undefined;
65
+ function useThemedSeriesList(list: ResolvedSeries[] | undefined, themeId: string): ResolvedSeries[] | undefined {
66
+ return useMemo(() => {
67
+ if (!list || !themeSeriesColors(themeId)) return list;
68
+ return list.map((entry) => {
69
+ const color = themedSeriesColor(entry.color, themeId);
70
+ return color === entry.color ? entry : { ...entry, color };
71
+ });
72
+ }, [list, themeId]);
73
+ }
74
+
75
+ export function CompositeChart(props: CompositeChartProps) {
76
+ const themeId = useThemeId();
77
+ const series = useThemedSeriesList(props.series, themeId);
78
+ const legendSeries = useThemedSeriesList(props.legendSeries, themeId);
79
+ return <ThemedCompositeChart {...props} series={series} legendSeries={legendSeries} />;
80
+ }
81
+
82
+ function ThemedCompositeChart({
60
83
  series,
61
84
  legendSeries,
62
85
  timelineSeries,
@@ -155,6 +178,17 @@ export function CompositeChart({
155
178
  () => new Set(visibleSeries.map((entry) => entry.id)),
156
179
  [visibleSeries],
157
180
  );
181
+ // Assigned over the legend's list, hidden lines included, so toggling one
182
+ // never restyles the rest.
183
+ const cueSource = legendSeries ?? stableSeries;
184
+ const cueKey = cueSource.map((entry) => `${entry.id}\u0000${entry.style}\u0000${entry.profile ? 1 : 0}`).join("\u0001");
185
+ const cueSourceRef = useRef(cueSource);
186
+ cueSourceRef.current = cueSource;
187
+ // Keyed by ids and styles: a live tick hands over new series objects.
188
+ const lineCues = useMemo(() => {
189
+ void cueKey;
190
+ return assignSeriesLineCues(cueSourceRef.current);
191
+ }, [cueKey]);
158
192
  const {
159
193
  navigationFrame, activeUserViewport, effectiveViewport,
160
194
  panViewport, panViewportByRatio, zoomViewport, setViewportRange, resetViewport,
@@ -239,8 +273,11 @@ export function CompositeChart({
239
273
  plotHeight,
240
274
  );
241
275
  // Terminal labels snap to rows; a fractional viewport places them exactly.
242
- return projectedScene.panels.map((panel) => resizeCompositePanel(panel, panelHeights.get(panel.id) ?? 1, !fractionalViewport));
243
- }, [fractionalViewport, panels, plotHeight, projectedScene]);
276
+ return projectedScene.panels.map((panel) => {
277
+ const resized = resizeCompositePanel(panel, panelHeights.get(panel.id) ?? 1, !fractionalViewport);
278
+ return lineCues.size > 0 ? { ...resized, lineCues } : resized;
279
+ });
280
+ }, [fractionalViewport, lineCues, panels, plotHeight, projectedScene]);
244
281
  // Static overview charts also pin the latest price with cursor precision.
245
282
  const includeCursorLabels = interactive || cursorDate !== undefined || !!scenePanels?.some((panel) => panel.lastPrice);
246
283
  const resolvedAxisWidth = useMemo(() => maximumAxisWidth === 0 ? 0 : Math.min(
@@ -479,6 +516,8 @@ export function CompositeChart({
479
516
  scene={scene}
480
517
  series={visibleLegendSeries}
481
518
  visibleSeriesIds={visibleSeriesIds}
519
+ lineCues={lineCues}
520
+ cellPlot={showTextFallback && !isDesktopWeb}
482
521
  width={totalWidth}
483
522
  accessory={legendAccessory}
484
523
  accessoryWidth={legendAccessoryWidth}
@@ -7,8 +7,15 @@ import type { ResolvedSeries } from "../../../time-series/types";
7
7
  import { consumeChartMouseEvent, type ChartMouseEvent } from "../core/pointer";
8
8
  import { formatCompositePointDetails, formatCompositeSeriesValue, seriesPriceReference } from "./format";
9
9
  import type { CompositeChartProps, CompositeChartScene } from "./types";
10
+ import { seriesCueCssBackground, type SeriesLineCue } from "./series-cues";
10
11
 
11
12
  const LEGEND_WHEEL_DELTA_PER_CELL = 8;
13
+ const NO_LINE_CUES: ReadonlyMap<string, SeriesLineCue> = new Map();
14
+ /** The dot every legend entry had before lines carried cues, and still has where they do not. */
15
+ const DOT_MARKER = "● ";
16
+ /** Cells the desktop and web marker takes: an 8px dot, or a 16px line sample, plus its margin. */
17
+ const DOM_DOT_CELLS = 2;
18
+ const DOM_LINE_CELLS = 3;
12
19
 
13
20
  function legendValue(
14
21
  series: ResolvedSeries,
@@ -50,6 +57,8 @@ export function CompositeLegend({
50
57
  scene,
51
58
  series,
52
59
  visibleSeriesIds,
60
+ lineCues = NO_LINE_CUES,
61
+ cellPlot = false,
53
62
  width,
54
63
  accessory,
55
64
  accessoryWidth,
@@ -63,6 +72,10 @@ export function CompositeLegend({
63
72
  scene: CompositeChartScene | null;
64
73
  series: ResolvedSeries[];
65
74
  visibleSeriesIds: ReadonlySet<string>;
75
+ /** Each line's cue on a chart that draws several; a chart with one keeps its dot. */
76
+ lineCues?: ReadonlyMap<string, SeriesLineCue>;
77
+ /** The plot is cell text, so a line's cue is its mark rather than its dash. */
78
+ cellPlot?: boolean;
66
79
  width: number;
67
80
  accessory: CompositeChartProps["legendAccessory"];
68
81
  accessoryWidth: CompositeChartProps["legendAccessoryWidth"];
@@ -79,6 +92,9 @@ export function CompositeLegend({
79
92
  scene?.cursorValues.map((entry) => [entry.seriesId, entry] as const) ?? [],
80
93
  );
81
94
  const entries = series.map((entry) => {
95
+ const cue = lineCues.get(entry.id) ?? null;
96
+ const marker = cue ? `${cellPlot ? cue.glyph : cue.sample} ` : DOT_MARKER;
97
+ const markerWidth = isDesktopWeb ? cue ? DOM_LINE_CELLS : DOM_DOT_CELLS : displayWidth(marker);
82
98
  const toggleable = !!onToggleSeries && (isSeriesToggleable?.(entry) ?? true);
83
99
  const cursorValue = cursorValueById.get(entry.id);
84
100
  const changeText = showLatestChangePercent
@@ -107,10 +123,13 @@ export function CompositeLegend({
107
123
  const text = [label, valueText].filter(Boolean).join(" ");
108
124
  return {
109
125
  entry,
126
+ cue,
127
+ marker,
128
+ markerWidth,
110
129
  label,
111
130
  compactLabel,
112
131
  text,
113
- width: Math.max(1, displayWidth(text)) + 2,
132
+ width: Math.max(1, displayWidth(text)) + markerWidth,
114
133
  labelWidth,
115
134
  valueText,
116
135
  toggleable,
@@ -122,7 +141,7 @@ export function CompositeLegend({
122
141
  target.label = label;
123
142
  target.labelWidth = labelWidth;
124
143
  target.text = [truncateToDisplayWidth(label, labelWidth), target.valueText].filter(Boolean).join(" ");
125
- target.width = Math.max(1, displayWidth(target.text)) + 2;
144
+ target.width = Math.max(1, displayWidth(target.text)) + target.markerWidth;
126
145
  };
127
146
  const measureSeriesWidth = () => entries.reduce(
128
147
  (total, entry, index) => total + entry.width + (index > 0 ? 1 : 0),
@@ -146,7 +165,7 @@ export function CompositeLegend({
146
165
  const minimumSeriesWidth = entries.reduce((total, entry, index) => {
147
166
  const text = [truncateToDisplayWidth(entry.compactLabel, Math.min(8, displayWidth(entry.compactLabel))), entry.valueText]
148
167
  .filter(Boolean).join(" ");
149
- return total + Math.max(1, displayWidth(text)) + 2 + (index > 0 ? 1 : 0);
168
+ return total + Math.max(1, displayWidth(text)) + entry.markerWidth + (index > 0 ? 1 : 0);
150
169
  }, 0);
151
170
  if (minimumSeriesWidth <= widthBeforeAccessory) {
152
171
  while (measureSeriesWidth() > widthBeforeAccessory) {
@@ -247,7 +266,7 @@ export function CompositeLegend({
247
266
  data-gloom-role="composite-chart-legend-scroll"
248
267
  >
249
268
  <Box flexDirection="row" width={desiredSeriesWidth} height={1} gap={1}>
250
- {entries.map(({ entry, text, toggleable, tooltip, width: entryWidth }, index) => {
269
+ {entries.map(({ entry, cue, marker, text, toggleable, tooltip, width: entryWidth }, index) => {
251
270
  const entryVisible = visibleSeriesIds.has(entry.id);
252
271
  return (
253
272
  <Box
@@ -274,7 +293,21 @@ export function CompositeLegend({
274
293
  data-visible={entryVisible ? "true" : "false"}
275
294
  title={isDesktopWeb ? tooltip : undefined}
276
295
  >
277
- {isDesktopWeb ? (
296
+ {isDesktopWeb && cue ? (
297
+ // The line as the plot strokes it: solid, dashed, dotted or heavy.
298
+ <Box
299
+ flexShrink={0}
300
+ style={{
301
+ width: 16,
302
+ height: Math.max(2, Math.round(cue.thickness)),
303
+ marginInlineEnd: 6,
304
+ background: seriesCueCssBackground(cue, entry.color),
305
+ opacity: entryVisible ? 1 : 0.35,
306
+ }}
307
+ data-gloom-role="composite-chart-legend-marker"
308
+ data-gloom-cue={cue.id}
309
+ />
310
+ ) : isDesktopWeb ? (
278
311
  <Box
279
312
  flexShrink={0}
280
313
  style={{
@@ -288,7 +321,7 @@ export function CompositeLegend({
288
321
  data-gloom-role="composite-chart-legend-marker"
289
322
  />
290
323
  ) : (
291
- <Text fg={entryVisible ? entry.color : themeColors.textMuted}>● </Text>
324
+ <Text fg={entryVisible ? entry.color : themeColors.textMuted}>{marker}</Text>
292
325
  )}
293
326
  {/* The filled/hollow marker already says whether a series is
294
327
  shown; the word only repeated it in every legend slot. */}
@@ -17,6 +17,7 @@ import { compositeGridRatios } from "./format";
17
17
  import { projectCompositeValue } from "./scene";
18
18
  import { paintVolumeProfile } from "./volume-profile-paint";
19
19
  import { paintExtendedHours } from "./session-shading";
20
+ import type { SeriesLineCue } from "./series-cues";
20
21
  import type {
21
22
  CompositeAxisDomain,
22
23
  CompositeChartColors,
@@ -168,6 +169,55 @@ function fillBand(
168
169
  }
169
170
  }
170
171
 
172
+ /**
173
+ * Strokes a line's segments with a dash pattern that runs on along the whole
174
+ * line, so a dense series with segments shorter than one dash still shows the
175
+ * pattern rather than restarting it at every point.
176
+ */
177
+ class DashedStroke {
178
+ private index = 0;
179
+ private remaining: number;
180
+
181
+ constructor(
182
+ private readonly data: Uint8Array,
183
+ private readonly width: number,
184
+ private readonly height: number,
185
+ private readonly color: RgbaColor,
186
+ private readonly cue: SeriesLineCue,
187
+ ) {
188
+ this.remaining = cue.dash[0] ?? 0;
189
+ }
190
+
191
+ segment(x0: number, y0: number, x1: number, y1: number): void {
192
+ const { dash, thickness } = this.cue;
193
+ const length = Math.hypot(x1 - x0, y1 - y0);
194
+ if (dash.length === 0 || length === 0) {
195
+ drawLine(this.data, this.width, this.height, x0, y0, x1, y1, this.color, thickness);
196
+ return;
197
+ }
198
+ let travelled = 0;
199
+ while (travelled < length) {
200
+ const run = Math.min(this.remaining, length - travelled);
201
+ if (this.index % 2 === 0) {
202
+ const from = travelled / length;
203
+ const to = (travelled + run) / length;
204
+ drawLine(
205
+ this.data, this.width, this.height,
206
+ x0 + (x1 - x0) * from, y0 + (y1 - y0) * from,
207
+ x0 + (x1 - x0) * to, y0 + (y1 - y0) * to,
208
+ this.color, thickness,
209
+ );
210
+ }
211
+ travelled += run;
212
+ this.remaining -= run;
213
+ if (this.remaining <= 0) {
214
+ this.index = (this.index + 1) % dash.length;
215
+ this.remaining = dash[this.index]!;
216
+ }
217
+ }
218
+ }
219
+ }
220
+
171
221
  function drawConnectedSeries(
172
222
  data: Uint8Array,
173
223
  width: number,
@@ -176,6 +226,7 @@ function drawConnectedSeries(
176
226
  domain: CompositeAxisDomain,
177
227
  color: RgbaColor,
178
228
  area: boolean,
229
+ cue?: SeriesLineCue,
179
230
  ): void {
180
231
  const points = series.points.map((point) => pixelPoint(point, width, height));
181
232
  if (points.length === 0) return;
@@ -184,11 +235,19 @@ function drawConnectedSeries(
184
235
 
185
236
  if (area) fillAreaGradient(data, width, height, series, points, baseline, step, color);
186
237
 
238
+ const stroke = cue ? new DashedStroke(data, width, height, color, cue) : null;
187
239
  for (let index = 1; index < points.length; index += 1) {
188
240
  const previous = points[index - 1]!;
189
241
  const current = points[index]!;
190
242
  if (series.points[index]?.breakBefore) continue;
191
- if (step) {
243
+ if (stroke) {
244
+ if (step) {
245
+ stroke.segment(previous.x, previous.y, current.x, previous.y);
246
+ stroke.segment(current.x, previous.y, current.x, current.y);
247
+ } else {
248
+ stroke.segment(previous.x, previous.y, current.x, current.y);
249
+ }
250
+ } else if (step) {
192
251
  drawLine(data, width, height, previous.x, previous.y, current.x, previous.y, color, 1.4);
193
252
  drawLine(data, width, height, current.x, previous.y, current.x, current.y, color, 1.4);
194
253
  } else {
@@ -459,6 +518,7 @@ export function renderCompositePanelBitmap(
459
518
  if (!domain) continue;
460
519
  const color = parseHex(series.source.color);
461
520
  const negativeColor = series.source.negativeColor ? parseHex(series.source.negativeColor) : color;
521
+ const cue = panel.lineCues?.get(series.source.id);
462
522
  switch (series.source.style) {
463
523
  case "columns":
464
524
  drawColumns(
@@ -475,11 +535,11 @@ export function renderCompositePanelBitmap(
475
535
  );
476
536
  break;
477
537
  case "area":
478
- drawConnectedSeries(data, width, height, series, domain, color, true);
538
+ drawConnectedSeries(data, width, height, series, domain, color, true, cue);
479
539
  break;
480
540
  case "band":
481
541
  fillBand(data, width, height, series, domain, color);
482
- drawConnectedSeries(data, width, height, series, domain, color, false);
542
+ drawConnectedSeries(data, width, height, series, domain, color, false, cue);
483
543
  break;
484
544
  case "points":
485
545
  for (const point of series.points) {
@@ -494,7 +554,7 @@ export function renderCompositePanelBitmap(
494
554
  break;
495
555
  case "line":
496
556
  case "step":
497
- drawConnectedSeries(data, width, height, series, domain, color, false);
557
+ drawConnectedSeries(data, width, height, series, domain, color, false, cue);
498
558
  break;
499
559
  }
500
560
  }
@@ -0,0 +1,71 @@
1
+ import type { ResolvedSeries, SeriesStyle } from "../../../time-series/types";
2
+
3
+ /**
4
+ * What tells one line from another besides its colour, so a chart still reads
5
+ * in greyscale and for colour-blind eyes. Each renderer draws its own form of
6
+ * the same cue: the bitmap (kitty, desktop, web, `shot`) a dash pattern or a
7
+ * heavier stroke, the cell fallback a different mark along the line, and the
8
+ * legend a sample of whichever the plot shows next to the series name.
9
+ */
10
+ export interface SeriesLineCue {
11
+ id: "solid" | "dashed" | "dotted" | "dash-dot" | "heavy";
12
+ /** On and off lengths along the line in bitmap pixels; empty is a solid line. */
13
+ dash: readonly number[];
14
+ /** Stroke width in bitmap pixels. */
15
+ thickness: number;
16
+ /** The mark the cell renderer draws the line with. */
17
+ glyph: string;
18
+ /** Two terminal cells of the line, for the legend beside a bitmap plot. */
19
+ sample: string;
20
+ }
21
+
22
+ /** The plain line every single-line chart keeps. */
23
+ const SOLID_LINE_THICKNESS = 1.5;
24
+
25
+ /**
26
+ * Dash lengths allow for the stroke's round ends, which eat about its width
27
+ * out of every gap: a 6 on, 5 off pattern reads as even dashes.
28
+ */
29
+ const LINE_CUES: readonly SeriesLineCue[] = [
30
+ { id: "solid", dash: [], thickness: SOLID_LINE_THICKNESS, glyph: "•", sample: "──" },
31
+ { id: "dashed", dash: [7, 6], thickness: SOLID_LINE_THICKNESS, glyph: "+", sample: "╌╌" },
32
+ { id: "dotted", dash: [1, 4.5], thickness: 2, glyph: "×", sample: "┈┈" },
33
+ { id: "dash-dot", dash: [9, 4.5, 1, 4.5], thickness: SOLID_LINE_THICKNESS, glyph: "o", sample: "─·" },
34
+ { id: "heavy", dash: [], thickness: 3, glyph: "*", sample: "━━" },
35
+ ];
36
+
37
+ const LINE_STYLES = new Set<SeriesStyle>(["line", "step", "area", "band"]);
38
+
39
+ function isLineCueStyle(style: SeriesStyle): boolean {
40
+ return LINE_STYLES.has(style);
41
+ }
42
+
43
+ /**
44
+ * A cue for each line of a chart that draws two or more. Lines are counted in
45
+ * legend order, hidden ones included, so hiding one does not restyle the
46
+ * others; the first keeps the plain solid line. A chart with one line gets
47
+ * none and looks as it always has.
48
+ */
49
+ export function assignSeriesLineCues(series: readonly ResolvedSeries[]): ReadonlyMap<string, SeriesLineCue> {
50
+ const lines = series.filter((entry) => isLineCueStyle(entry.style) && !entry.profile);
51
+ const cues = new Map<string, SeriesLineCue>();
52
+ if (lines.length < 2) return cues;
53
+ for (const entry of lines) {
54
+ if (!cues.has(entry.id)) cues.set(entry.id, LINE_CUES[cues.size % LINE_CUES.length]!);
55
+ }
56
+ return cues;
57
+ }
58
+
59
+ /** A repeating CSS gradient that draws the cue's dashes, for the desktop and web legend. */
60
+ export function seriesCueCssBackground(cue: SeriesLineCue, color: string): string {
61
+ if (cue.dash.length === 0) return color;
62
+ // Half the bitmap lengths: the legend sample is drawn in CSS pixels at its own size.
63
+ const stops: string[] = [];
64
+ let at = 0;
65
+ cue.dash.forEach((length, index) => {
66
+ const end = at + Math.max(1, length / 2 + (index % 2 === 0 ? 0.5 : -0.5));
67
+ stops.push(`${index % 2 === 0 ? color : "transparent"} ${at}px ${end}px`);
68
+ at = end;
69
+ });
70
+ return `repeating-linear-gradient(to right, ${stops.join(", ")})`;
71
+ }
@@ -5,6 +5,7 @@ import { buildCompositeColumnLayout, type CompositeColumnLayout } from "./column
5
5
  import { projectCompositeValue } from "./scene";
6
6
  import { writeVolumeProfileText } from "./volume-profile-paint";
7
7
  import { writeSessionBreaksText } from "./session-shading";
8
+ import type { SeriesLineCue } from "./series-cues";
8
9
  import type {
9
10
  CompositeAxisDomain,
10
11
  CompositePanelScene,
@@ -59,19 +60,23 @@ function renderLineLike(
59
60
  series: CompositeProjectedSeries,
60
61
  width: number,
61
62
  height: number,
63
+ cue?: SeriesLineCue,
62
64
  ): void {
63
65
  const points = series.points.map((point) => ({ ...cellPoint(point, width, height), point }));
64
66
  if (points.length === 0) return;
65
67
  const step = series.source.style === "step" || series.source.interpolation === "step-after";
68
+ // Beside other lines, each line keeps its own mark the whole way, steps
69
+ // included, since cell text has no colour of its own here.
70
+ const mark = cue && cue.id !== "solid" ? cue.glyph : null;
66
71
  for (let index = 1; index < points.length; index += 1) {
67
72
  const previous = points[index - 1]!;
68
73
  const current = points[index]!;
69
74
  if (current.point.breakBefore) continue;
70
75
  if (step) {
71
- drawLine(rows, previous.x, previous.y, current.x, previous.y, "─");
72
- drawLine(rows, current.x, previous.y, current.x, current.y, "│");
76
+ drawLine(rows, previous.x, previous.y, current.x, previous.y, mark ?? "─");
77
+ drawLine(rows, current.x, previous.y, current.x, current.y, mark ?? "│");
73
78
  } else {
74
- drawLine(rows, previous.x, previous.y, current.x, current.y, "•");
79
+ drawLine(rows, previous.x, previous.y, current.x, current.y, mark ?? "•");
75
80
  }
76
81
  }
77
82
  for (let index = 0; index < points.length; index += 1) {
@@ -102,6 +107,7 @@ function renderArea(
102
107
  domain: CompositeAxisDomain,
103
108
  width: number,
104
109
  height: number,
110
+ cue?: SeriesLineCue,
105
111
  ): void {
106
112
  const baseline = valueRow(0, domain, height) ?? height - 1;
107
113
  const points = series.points.map((point) => cellPoint(point, width, height));
@@ -110,7 +116,7 @@ function renderArea(
110
116
  const bottom = Math.max(point.y, baseline);
111
117
  for (let row = top; row <= bottom; row += 1) setCell(rows, point.x, row, "░");
112
118
  }
113
- renderLineLike(rows, series, width, height);
119
+ renderLineLike(rows, series, width, height, cue);
114
120
  }
115
121
 
116
122
  /** A band's range as shade between its points' low and high rows, under its line. */
@@ -120,6 +126,7 @@ function renderBand(
120
126
  domain: CompositeAxisDomain,
121
127
  width: number,
122
128
  height: number,
129
+ cue?: SeriesLineCue,
123
130
  ): void {
124
131
  const edges = series.points.map((projected) => {
125
132
  const { high, low } = projected.point;
@@ -150,7 +157,7 @@ function renderBand(
150
157
  shade(x, previous.top + (current.top - previous.top) * t, previous.bottom + (current.bottom - previous.bottom) * t);
151
158
  }
152
159
  }
153
- renderLineLike(rows, series, width, height);
160
+ renderLineLike(rows, series, width, height, cue);
154
161
  }
155
162
 
156
163
  function renderColumns(
@@ -267,15 +274,16 @@ export function renderCompositePanelText(
267
274
  for (const series of orderedSeries) {
268
275
  const domain = panel.axes[series.source.axis];
269
276
  if (!domain) continue;
277
+ const cue = panel.lineCues?.get(series.source.id);
270
278
  switch (series.source.style) {
271
279
  case "columns":
272
280
  renderColumns(rows, series, domain, plotWidth, height, columnLayout);
273
281
  break;
274
282
  case "area":
275
- renderArea(rows, series, domain, plotWidth, height);
283
+ renderArea(rows, series, domain, plotWidth, height, cue);
276
284
  break;
277
285
  case "band":
278
- renderBand(rows, series, domain, plotWidth, height);
286
+ renderBand(rows, series, domain, plotWidth, height, cue);
279
287
  break;
280
288
  case "candles":
281
289
  case "ohlc":
@@ -284,7 +292,7 @@ export function renderCompositePanelText(
284
292
  break;
285
293
  case "line":
286
294
  case "step":
287
- renderLineLike(rows, series, plotWidth, height);
295
+ renderLineLike(rows, series, plotWidth, height, cue);
288
296
  break;
289
297
  case "points":
290
298
  renderPoints(rows, series, plotWidth, height);
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { CompositeChartLevels } from "./levels";
3
+ import type { SeriesLineCue } from "./series-cues";
3
4
  import type {
4
5
  ChartPanelSpec,
5
6
  PanelScale,
@@ -134,6 +135,8 @@ export interface CompositePanelScene {
134
135
  extendedHours?: readonly CompositeExtendedHoursSpan[];
135
136
  /** The previous session's close on a one-day chart, drawn as a dotted reference line. */
136
137
  priorClose?: { axis: CompositeAxisSide; value: number; yRatio: number };
138
+ /** How each line tells itself apart besides colour, by series id; absent on a chart with one line. */
139
+ lineCues?: ReadonlyMap<string, SeriesLineCue>;
137
140
  }
138
141
 
139
142
  export interface CompositeCursorValue {
@@ -17,7 +17,7 @@ import { openBrokerAddFlow } from "../../../../plugins/builtin/broker-manager/ad
17
17
  import { isManualPortfolio } from "../../../../plugins/builtin/portfolio-list/mutations";
18
18
  import { CHART_RENDERER_PREFERENCES } from "../../../chart/core/types";
19
19
  import type { Command } from "../registry";
20
- import type { BrokerInstanceConfig } from "../../../../types/config";
20
+ import type { AppConfig, BrokerInstanceConfig } from "../../../../types/config";
21
21
  import { requestFeedbackDialog } from "../../../feedback-dialog";
22
22
  import type { OpenInlineConfirm } from "../../routing/confirm";
23
23
  import {
@@ -288,6 +288,14 @@ export function runDirectCommandAction(options: {
288
288
  closeAll({ revertThemePreview: false });
289
289
  return;
290
290
  }
291
+ case "toggle-presentation-mode": {
292
+ const { presentationMode, ...rest } = state.config;
293
+ const nextConfig: AppConfig = presentationMode ? rest : { ...rest, presentationMode: true };
294
+ dispatch({ type: "SET_CONFIG", config: nextConfig });
295
+ persistConfig(nextConfig);
296
+ closeAll({ revertThemePreview: false });
297
+ return;
298
+ }
291
299
  case "toggle-value-flashing": {
292
300
  const nextConfig = {
293
301
  ...state.config,
@@ -156,8 +156,10 @@ export function buildPluginCommandResultItem(options: {
156
156
  detail: options.result.detail ?? options.command.description ?? "",
157
157
  category,
158
158
  kind: "command",
159
+ badge: options.result.badge,
160
+ name: options.result.name,
159
161
  right: (options.result.right ?? options.command.shortcut?.trim()) || undefined,
160
- searchText: `${options.result.label} ${options.result.detail || ""} ${(options.result.keywords ?? []).join(" ")} ${options.command.label} ${options.command.description || ""} ${(options.command.keywords ?? []).join(" ")}`,
162
+ searchText: `${options.result.label} ${options.result.detail || ""} ${options.result.name || ""} ${(options.result.keywords ?? []).join(" ")} ${options.command.label} ${options.command.description || ""} ${(options.command.keywords ?? []).join(" ")}`,
161
163
  disabled: options.result.disabled,
162
164
  current: options.result.current,
163
165
  action: async () => {
@@ -207,6 +207,13 @@ export const commands: Command[] = [
207
207
  dispatch({ type: "TOGGLE_STATUS_BAR" });
208
208
  },
209
209
  },
210
+ {
211
+ id: "toggle-presentation-mode",
212
+ prefix: "",
213
+ label: "Toggle Presentation Mode",
214
+ description: "Let the panes fill the window, without the header or the status bar",
215
+ category: "Config",
216
+ },
210
217
  {
211
218
  id: "toggle-value-flashing",
212
219
  prefix: "VF",
@@ -4,7 +4,10 @@ import {
4
4
  getDockedPaneIds,
5
5
  removePane,
6
6
  } from "../../../layout/pane-manager";
7
+ import { paneMoveBackTarget, paneNewLayoutBlock } from "../../../layout/pane-layout-move";
8
+ import { resolveTickerForPane } from "../../../state/app/context";
7
9
  import { findPaneInstance } from "../../../types/config";
10
+ import { tf } from "../../../i18n";
8
11
  import type { ResultItem } from "../list/model";
9
12
  import type { LayoutItemsContext } from "./types";
10
13
  import { WINDOW_MODE_COMMAND_OPTIONS } from "./window-mode";
@@ -20,6 +23,7 @@ export function buildFocusedPaneLayoutItems({
20
23
  persistLayoutChange,
21
24
  pluginRegistry,
22
25
  pushRoute,
26
+ state,
23
27
  }: LayoutItemsContext): ResultItem[] {
24
28
  const focusedPane = focusedPaneId ? findPaneInstance(currentLayout, focusedPaneId) : null;
25
29
  const focusedPaneDef = focusedPane ? pluginRegistry.panes.get(focusedPane.paneId) : null;
@@ -37,6 +41,12 @@ export function buildFocusedPaneLayoutItems({
37
41
 
38
42
  const dockedPaneIds = getDockedPaneIds(currentLayout);
39
43
  const focusedFloating = currentLayout.floating.find((entry) => entry.instanceId === focusedPane.instanceId);
44
+ const newLayoutBlock = paneNewLayoutBlock(
45
+ currentLayout,
46
+ focusedPane.instanceId,
47
+ (instanceId) => resolveTickerForPane(state, instanceId),
48
+ );
49
+ const moveBack = paneMoveBackTarget(state.config, focusedPane.instanceId);
40
50
 
41
51
  return [
42
52
  {
@@ -65,6 +75,34 @@ export function buildFocusedPaneLayoutItems({
65
75
  pluginRegistry.togglePaneFullscreen(focusedPane.instanceId);
66
76
  },
67
77
  },
78
+ {
79
+ id: "layout-move-to-new-layout",
80
+ label: "Move to New Layout",
81
+ detail: newLayoutBlock === "only-pane"
82
+ ? "Already the only pane in this layout"
83
+ : "Give the focused pane a layout of its own that it fills",
84
+ searchText: "own layout tab fullscreen",
85
+ category: "Focused Pane",
86
+ kind: "action",
87
+ disabled: newLayoutBlock === "only-pane" || newLayoutBlock === "detached",
88
+ action: () => {
89
+ closeAll({ revertThemePreview: false });
90
+ pluginRegistry.movePaneToNewLayout(focusedPane.instanceId);
91
+ },
92
+ },
93
+ ...(moveBack ? [{
94
+ id: "layout-move-back",
95
+ label: tf("Move Back to {layout}", { layout: moveBack.name }),
96
+ detail: moveBack.original
97
+ ? "Return the focused pane to the layout it came from"
98
+ : "The layout it came from is gone, so it docks in this one instead",
99
+ category: "Focused Pane",
100
+ kind: "action" as const,
101
+ action: () => {
102
+ closeAll({ revertThemePreview: false });
103
+ pluginRegistry.movePaneBack(focusedPane.instanceId);
104
+ },
105
+ }] : []),
68
106
  ...WINDOW_MODE_COMMAND_OPTIONS.map((option) => ({
69
107
  id: `layout-window-mode:${option.mode}`,
70
108
  label: option.label,
@@ -29,6 +29,7 @@ import {
29
29
  type SharedWorkflowDeps,
30
30
  } from "../workflow/ops";
31
31
  import type { PluginRegistry } from "../../../plugins/registry";
32
+ import { PluginOffError, notifyPluginOff } from "../../../plugins/plugin-off";
32
33
  import { shouldOpenPaneTemplateConfig } from "./workflow-route";
33
34
 
34
35
  type CloseAllFn = (options?: { revertThemePreview?: boolean }) => void;
@@ -107,6 +108,10 @@ export function useCommandBarPaneTemplateActions({
107
108
  await pluginRegistry.createPaneFromTemplateAsync(template.id, createOptions);
108
109
  closeAll({ revertThemePreview: false });
109
110
  } catch (error) {
111
+ if (error instanceof PluginOffError) {
112
+ notifyPluginOff(pluginRegistry, error, () => { void openPaneTemplateDirect(template, createOptions); });
113
+ return;
114
+ }
110
115
  const displayLabel = getPaneTemplateDisplayLabel(template);
111
116
  notify(
112
117
  error instanceof Error ? error.message : `Could not create ${displayLabel.toLowerCase()}.`,
@@ -147,6 +147,8 @@ export function createRootCommandItemBuilder({
147
147
  return command.description;
148
148
  case "toggle-value-flashing":
149
149
  return state.config.valueFlashingEnabled ? "Currently on" : "Currently off";
150
+ case "toggle-presentation-mode":
151
+ return state.config.presentationMode ? "Currently on" : "Currently off";
150
152
  case "toggle-crash-reports":
151
153
  return state.config.telemetry?.crashReports === false ? "Currently off" : "Currently on";
152
154
  case "toggle-usage-counts":