gloomberb 0.17.4 → 0.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/package.json +3 -1
  2. package/patches/electrobun@1.18.1.patch +80 -0
  3. package/src/api-client/chat.ts +9 -0
  4. package/src/api-client/commodity-balances.ts +77 -0
  5. package/src/api-client/data.ts +35 -3
  6. package/src/api-client/earnings.ts +10 -0
  7. package/src/api-client/index.ts +5 -0
  8. package/src/api-client/insider-signals.ts +75 -0
  9. package/src/api-client/market-discovery.ts +1 -1
  10. package/src/api-client/normalizers.ts +1 -0
  11. package/src/api-client/paths.ts +9 -0
  12. package/src/api-client/research-activity.ts +10 -3
  13. package/src/api-client/socket.ts +10 -0
  14. package/src/api-client/types.ts +25 -1
  15. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
  16. package/src/app/runtime/broker-import.ts +23 -21
  17. package/src/app/runtime/plugin-bindings.ts +13 -7
  18. package/src/app/runtime/update.ts +64 -4
  19. package/src/app.tsx +2 -0
  20. package/src/architecture/core-builtin-allowlist.json +2 -10
  21. package/src/ask-gloom/pane-context.ts +11 -1
  22. package/src/brokers/broker-portfolio-sync.ts +66 -0
  23. package/src/brokers/sync-broker-instance.ts +75 -41
  24. package/src/cli/commands/market.ts +101 -34
  25. package/src/cli/commands/options-chain.ts +20 -6
  26. package/src/cli/commands/overview.ts +54 -13
  27. package/src/cli/commands/search.ts +1 -1
  28. package/src/cli/commands/system.ts +19 -1
  29. package/src/cli/commands/ticker.ts +24 -4
  30. package/src/cli/context.ts +13 -0
  31. package/src/cli/errors.ts +15 -2
  32. package/src/cli/fx-pair.ts +77 -0
  33. package/src/cli/glossary.ts +34 -4
  34. package/src/cli/history-rows.ts +25 -11
  35. package/src/cli/index.ts +30 -12
  36. package/src/cli/listing-arg.ts +61 -138
  37. package/src/cli/listing-suggestion.ts +3 -21
  38. package/src/cli/pane-functions/catalog.ts +50 -0
  39. package/src/cli/pane-functions/freshness.ts +44 -9
  40. package/src/cli/pane-functions/function-help.ts +9 -9
  41. package/src/cli/pane-functions/headless.ts +19 -7
  42. package/src/cli/pane-functions/index.ts +63 -5
  43. package/src/cli/pane-functions/market-session.ts +18 -3
  44. package/src/cli/pane-functions/resolver.ts +29 -8
  45. package/src/cli/pane-functions/screenshot.ts +2 -0
  46. package/src/cli/report-tables.ts +11 -4
  47. package/src/cli/result.ts +2 -0
  48. package/src/cli/series-search.ts +6 -1
  49. package/src/cli/ticker-resolution.ts +1 -2
  50. package/src/cli/unknown-command.ts +18 -0
  51. package/src/components/chart/composite/composite-chart.tsx +80 -19
  52. package/src/components/chart/composite/format.ts +17 -0
  53. package/src/components/chart/composite/legend.tsx +26 -15
  54. package/src/components/chart/composite/panel-labels.ts +38 -0
  55. package/src/components/chart/composite/panel-series.ts +1 -0
  56. package/src/components/chart/composite/panel-surface.tsx +25 -7
  57. package/src/components/chart/composite/price-axis-labels.tsx +16 -3
  58. package/src/components/chart/composite/scene.ts +60 -2
  59. package/src/components/chart/composite/series-axis-tags.tsx +67 -0
  60. package/src/components/chart/composite/tools.ts +2 -2
  61. package/src/components/chart/composite/types.ts +28 -0
  62. package/src/components/chart/core/view-reset.ts +46 -0
  63. package/src/components/command-bar/commands/collection.ts +1 -1
  64. package/src/components/command-bar/commands/direct/index.ts +48 -1
  65. package/src/components/command-bar/commands/registry.ts +8 -0
  66. package/src/components/command-bar/list/model.ts +7 -0
  67. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  68. package/src/components/command-bar/panel/prompt-binding.ts +8 -0
  69. package/src/components/command-bar/routes/root/command-items.ts +17 -2
  70. package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
  71. package/src/components/command-bar/routes/root/provider-search.ts +6 -0
  72. package/src/components/command-bar/routes/root/runtime.ts +3 -0
  73. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
  74. package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
  75. package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
  76. package/src/components/command-bar/surface/index.tsx +7 -1
  77. package/src/components/command-bar/workflow/collection-actions.ts +6 -5
  78. package/src/components/command-bar/workflow/ops.ts +8 -5
  79. package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
  80. package/src/components/command-bar/workflow/tickers.ts +1 -1
  81. package/src/components/form-modal/confirm.tsx +1 -0
  82. package/src/components/form-modal/request.ts +2 -0
  83. package/src/components/layout/detached-pane-shell.tsx +6 -1
  84. package/src/components/layout/pane/content.tsx +11 -8
  85. package/src/components/layout/pane/sidebar.tsx +24 -11
  86. package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
  87. package/src/components/layout/shell/index.tsx +5 -1
  88. package/src/components/layout/shell/menu.ts +8 -0
  89. package/src/components/layout/shell/pane/capture.ts +29 -12
  90. package/src/components/onboarding/wizard-model.ts +1 -1
  91. package/src/components/onboarding/wizard-positions.ts +18 -10
  92. package/src/components/ticker/badge/format.ts +64 -15
  93. package/src/components/ticker/badge/index.tsx +32 -11
  94. package/src/components/ticker/badge/list.tsx +4 -3
  95. package/src/components/ticker/badge/text.tsx +2 -2
  96. package/src/components/ticker/list-table-view.tsx +8 -1
  97. package/src/components/ui/confirm-dialog.tsx +30 -4
  98. package/src/components/ui/data-table/opentui/index.tsx +61 -20
  99. package/src/components/ui/data-table/opentui/model.ts +104 -0
  100. package/src/components/ui/fields.tsx +5 -0
  101. package/src/components/ui/inline-quick-add.tsx +4 -1
  102. package/src/components/ui/query-bar-keys.tsx +29 -5
  103. package/src/components/ui/query-bar.tsx +29 -4
  104. package/src/components/ui/windowed-list.tsx +27 -0
  105. package/src/data/config/store/normalize.ts +8 -0
  106. package/src/docs/snapshot.json +248 -5
  107. package/src/i18n/es.ts +3 -1
  108. package/src/i18n/ja.ts +3 -1
  109. package/src/i18n/ko.ts +3 -1
  110. package/src/i18n/zh-cn.ts +3 -1
  111. package/src/i18n/zh-tw.ts +3 -1
  112. package/src/layout-marketplace/cloud.ts +16 -5
  113. package/src/layout-marketplace/gallery.tsx +4 -4
  114. package/src/layout-marketplace/linked-sync.ts +9 -3
  115. package/src/layout-marketplace/linked.ts +24 -3
  116. package/src/layout-marketplace/model.ts +39 -4
  117. package/src/market-data/coordinator/events.ts +5 -1
  118. package/src/market-data/coordinator/index.ts +38 -1
  119. package/src/market-data/coordinator/quotes.ts +4 -0
  120. package/src/market-data/futures-contract.ts +53 -0
  121. package/src/market-data/hooks.tsx +225 -30
  122. package/src/market-data/market/bar-period.ts +71 -0
  123. package/src/market-data/market/format.ts +14 -2
  124. package/src/market-data/market/freshness.ts +28 -1
  125. package/src/market-data/market/status.ts +152 -13
  126. package/src/market-data/model-dividend-yield.ts +76 -0
  127. package/src/market-data/quotes/freshness.ts +30 -1
  128. package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
  129. package/src/market-data/quotes/regular-session-valuation.ts +32 -1
  130. package/src/plugins/builtin/analytics/metrics.ts +8 -4
  131. package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
  132. package/src/plugins/builtin/analytics/risk-model.ts +74 -15
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
  134. package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
  135. package/src/plugins/builtin/broker-manager/model.ts +2 -2
  136. package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
  137. package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
  138. package/src/plugins/builtin/chart-composer/formula.ts +233 -0
  139. package/src/plugins/builtin/chart-composer/headless.ts +27 -2
  140. package/src/plugins/builtin/chart-composer/index.tsx +3 -2
  141. package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
  142. package/src/plugins/builtin/chart-composer/presets.ts +91 -2
  143. package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
  144. package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
  145. package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
  146. package/src/plugins/builtin/chart-composer/settings.ts +16 -4
  147. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  148. package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
  149. package/src/plugins/builtin/chat/content/footer.tsx +5 -2
  150. package/src/plugins/builtin/chat/content/index.tsx +12 -5
  151. package/src/plugins/builtin/chat/content/mentions.ts +50 -21
  152. package/src/plugins/builtin/chat/content/scroll.ts +12 -5
  153. package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
  154. package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
  155. package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
  156. package/src/plugins/builtin/chat/controller/index.ts +88 -17
  157. package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
  158. package/src/plugins/builtin/chat/controller/messages.ts +14 -2
  159. package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
  160. package/src/plugins/builtin/chat/controller/state.ts +25 -0
  161. package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
  162. package/src/plugins/builtin/chat/controller/view.ts +4 -0
  163. package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
  164. package/src/plugins/builtin/chat/conversations.ts +12 -5
  165. package/src/plugins/builtin/chat/index.tsx +1 -1
  166. package/src/plugins/builtin/chat/layout.ts +23 -5
  167. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  168. package/src/plugins/builtin/chat/share/flow.tsx +18 -9
  169. package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
  170. package/src/plugins/builtin/chat/sidebar.tsx +6 -1
  171. package/src/plugins/builtin/chat/unread-badge.ts +38 -7
  172. package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
  173. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
  174. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
  175. package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
  176. package/src/plugins/builtin/commodity-balances/client.ts +51 -193
  177. package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
  178. package/src/plugins/builtin/commodity-balances/model.ts +5 -180
  179. package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
  180. package/src/plugins/builtin/custom-view/index.tsx +1 -1
  181. package/src/plugins/builtin/custom-view/loader.ts +57 -7
  182. package/src/plugins/builtin/custom-view/sources.ts +146 -0
  183. package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
  184. package/src/plugins/builtin/earnings/board-model.ts +33 -3
  185. package/src/plugins/builtin/earnings/board-table.ts +59 -14
  186. package/src/plugins/builtin/earnings/board.tsx +36 -3
  187. package/src/plugins/builtin/earnings/headless.ts +24 -5
  188. package/src/plugins/builtin/earnings/history-model.ts +9 -1
  189. package/src/plugins/builtin/earnings/history-table.ts +16 -5
  190. package/src/plugins/builtin/earnings/history.tsx +40 -10
  191. package/src/plugins/builtin/earnings/index.tsx +3 -1
  192. package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
  193. package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
  194. package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
  195. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  196. package/src/plugins/builtin/econ-statistics/view.ts +4 -0
  197. package/src/plugins/builtin/futures/model.ts +5 -4
  198. package/src/plugins/builtin/futures/series.ts +38 -1
  199. package/src/plugins/builtin/futures-curve/client.ts +4 -4
  200. package/src/plugins/builtin/futures-curve/headless.ts +33 -8
  201. package/src/plugins/builtin/futures-curve/index.tsx +4 -4
  202. package/src/plugins/builtin/futures-curve/model.ts +109 -6
  203. package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
  204. package/src/plugins/builtin/insider/headless.ts +74 -4
  205. package/src/plugins/builtin/insider/index.tsx +99 -5
  206. package/src/plugins/builtin/insider/signals-client.ts +69 -0
  207. package/src/plugins/builtin/insider/signals-model.ts +146 -0
  208. package/src/plugins/builtin/insider/signals.tsx +176 -0
  209. package/src/plugins/builtin/iv-history/client.ts +41 -2
  210. package/src/plugins/builtin/iv-history/headless.ts +24 -16
  211. package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
  212. package/src/plugins/builtin/jodi/client.ts +42 -205
  213. package/src/plugins/builtin/jodi/headless.ts +13 -4
  214. package/src/plugins/builtin/jodi/model.ts +18 -43
  215. package/src/plugins/builtin/jodi/pane.tsx +5 -0
  216. package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
  217. package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
  218. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  219. package/src/plugins/builtin/macro-day/release-table.ts +3 -3
  220. package/src/plugins/builtin/market-movers/client.ts +32 -6
  221. package/src/plugins/builtin/market-movers/headless.ts +7 -2
  222. package/src/plugins/builtin/market-movers/model.ts +64 -3
  223. package/src/plugins/builtin/market-movers/screener.ts +20 -9
  224. package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
  225. package/src/plugins/builtin/market-valuation/headless.ts +17 -4
  226. package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
  227. package/src/plugins/builtin/market-valuation/view.ts +10 -0
  228. package/src/plugins/builtin/mna/model.ts +2 -1
  229. package/src/plugins/builtin/options-positioning/client.ts +10 -0
  230. package/src/plugins/builtin/options-positioning/headless.ts +88 -13
  231. package/src/plugins/builtin/options-positioning/index.tsx +2 -2
  232. package/src/plugins/builtin/options-positioning/model.ts +47 -0
  233. package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
  234. package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
  235. package/src/plugins/builtin/options-scenario/client.ts +111 -17
  236. package/src/plugins/builtin/options-scenario/collar.ts +141 -0
  237. package/src/plugins/builtin/options-scenario/csp.ts +1 -1
  238. package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
  239. package/src/plugins/builtin/options-scenario/headless.ts +28 -12
  240. package/src/plugins/builtin/options-scenario/index.tsx +1 -1
  241. package/src/plugins/builtin/options-scenario/model.ts +55 -5
  242. package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
  243. package/src/plugins/builtin/perps/client.ts +77 -6
  244. package/src/plugins/builtin/perps/headless.ts +25 -14
  245. package/src/plugins/builtin/perps/index.tsx +1 -1
  246. package/src/plugins/builtin/perps/model.ts +32 -0
  247. package/src/plugins/builtin/perps/pane.tsx +5 -4
  248. package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
  249. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
  250. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  251. package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
  252. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
  253. package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
  254. package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
  255. package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
  256. package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
  257. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  258. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
  259. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
  260. package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
  261. package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
  262. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  263. package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
  264. package/src/plugins/builtin/rate-path/model.ts +1 -1
  265. package/src/plugins/builtin/rate-path/pane.tsx +2 -1
  266. package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
  267. package/src/plugins/builtin/research/event-model.ts +41 -49
  268. package/src/plugins/builtin/research/event-sources.ts +1 -1
  269. package/src/plugins/builtin/research/headless.ts +1 -1
  270. package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
  271. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  272. package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
  273. package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
  274. package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
  275. package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
  276. package/src/plugins/builtin/sectors/client.ts +46 -19
  277. package/src/plugins/builtin/sectors/sector-model.ts +22 -10
  278. package/src/plugins/builtin/shared/earnings-report.ts +48 -1
  279. package/src/plugins/builtin/shared/history-span.ts +55 -0
  280. package/src/plugins/builtin/shared/report-freshness.ts +20 -11
  281. package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
  282. package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
  283. package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
  284. package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
  285. package/src/plugins/builtin/themes/headless.ts +15 -2
  286. package/src/plugins/builtin/thirteenf/api.ts +60 -43
  287. package/src/plugins/builtin/thirteenf/data.ts +76 -35
  288. package/src/plugins/builtin/thirteenf/headless.ts +2 -0
  289. package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
  290. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
  291. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
  292. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
  293. package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
  294. package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
  295. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  296. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  297. package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
  298. package/src/plugins/builtin/yield-curve/curves.ts +15 -1
  299. package/src/plugins/builtin/yield-curve/headless.ts +17 -7
  300. package/src/plugins/builtin/yield-curve/history.ts +2 -0
  301. package/src/plugins/builtin/yield-curve/index.tsx +23 -18
  302. package/src/plugins/builtin/yield-curve/series.ts +19 -9
  303. package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
  304. package/src/plugins/builtin/yield-curve/world.ts +4 -0
  305. package/src/plugins/registry/host-actions.ts +3 -0
  306. package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
  307. package/src/portfolio/valuation-basis.ts +4 -21
  308. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  309. package/src/remote/controller.ts +27 -7
  310. package/src/remote/schema.ts +25 -9
  311. package/src/renderers/browser/app-services.ts +7 -1
  312. package/src/renderers/browser/config-host.ts +55 -2
  313. package/src/renderers/browser/first-screen.ts +193 -0
  314. package/src/renderers/browser/main.tsx +59 -7
  315. package/src/renderers/cloudflare/http-proxy.ts +21 -13
  316. package/src/renderers/dom/desktop/controls.tsx +2 -0
  317. package/src/renderers/dom/desktop/query-bar.tsx +1 -1
  318. package/src/renderers/dom/dom-ui-host.tsx +2 -0
  319. package/src/renderers/dom/windowed-list.tsx +201 -0
  320. package/src/renderers/dom/windowed-rows.ts +65 -0
  321. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
  322. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
  323. package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
  324. package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
  325. package/src/renderers/electrobun/shared/protocol.ts +7 -4
  326. package/src/renderers/electrobun/view/http-fetch.ts +12 -1
  327. package/src/renderers/electrobun/view/update-host.ts +2 -2
  328. package/src/shares/bug-report-dialog.tsx +162 -0
  329. package/src/shares/bug-report-flow.tsx +84 -0
  330. package/src/shares/bug-report.ts +42 -0
  331. package/src/shares/capture-flow.ts +19 -0
  332. package/src/shares/capture-preview.tsx +129 -0
  333. package/src/shares/chat.ts +10 -1
  334. package/src/shares/dialog.tsx +16 -91
  335. package/src/shares/flow.tsx +12 -16
  336. package/src/shares/personal-pane.ts +35 -1
  337. package/src/sources/listing-symbols.ts +8 -4
  338. package/src/state/app/context/index.tsx +13 -0
  339. package/src/state/app/tab-cursor.ts +85 -0
  340. package/src/sync/core-contributors.ts +206 -59
  341. package/src/telemetry/usage-counts.ts +16 -0
  342. package/src/test-support/config-store.ts +31 -0
  343. package/src/test-support/moved-symbols.ts +64 -0
  344. package/src/theme/colors.ts +30 -0
  345. package/src/tickers/listing-fate.ts +242 -0
  346. package/src/tickers/open-target.ts +61 -11
  347. package/src/tickers/search/index.ts +45 -3
  348. package/src/tickers/search/ranking.ts +19 -2
  349. package/src/tickers/search/types.ts +3 -0
  350. package/src/tickers/search/upsert.ts +22 -7
  351. package/src/tickers/ticker-changes.ts +26 -9
  352. package/src/time-series/bar-spacing.ts +19 -0
  353. package/src/time-series/chart-data.ts +37 -13
  354. package/src/time-series/field-catalog.ts +5 -2
  355. package/src/time-series/forward-valuation.ts +10 -7
  356. package/src/time-series/fundamentals.ts +49 -11
  357. package/src/time-series/hooks.ts +2 -0
  358. package/src/time-series/live-bars.ts +3 -1
  359. package/src/time-series/parsed-history-cache.ts +2 -0
  360. package/src/time-series/price-units.ts +69 -0
  361. package/src/time-series/resolve.ts +71 -11
  362. package/src/time-series/session-bar.ts +138 -0
  363. package/src/time-series/spec.ts +20 -3
  364. package/src/time-series/studies.ts +211 -12
  365. package/src/time-series/transforms.ts +2 -0
  366. package/src/time-series/types.ts +11 -1
  367. package/src/time-series/valuation-price.ts +13 -0
  368. package/src/types/config.ts +24 -1
  369. package/src/types/financials.ts +2 -0
  370. package/src/types/headless.ts +27 -0
  371. package/src/types/plugin.ts +7 -0
  372. package/src/ui/host.tsx +5 -0
  373. package/src/updater/channel.ts +51 -0
  374. package/src/updater/github-releases.ts +85 -16
  375. package/src/updater/index.ts +128 -17
  376. package/src/utils/asset-category.ts +20 -0
  377. package/src/utils/datetime-format.ts +7 -1
  378. package/src/utils/decimal-comma.ts +83 -3
  379. package/src/utils/fundamentals.ts +7 -2
  380. package/src/utils/http-proxy-response.ts +66 -9
  381. package/src/utils/plugin-proxy-hosts.json +1 -5
  382. package/src/utils/semver.ts +47 -2
  383. package/src/utils/typo-distance.ts +22 -0
  384. package/src/plugins/builtin/shared/zip-text.ts +0 -46
@@ -14,7 +14,7 @@ import {
14
14
  import { clampFontSize, MIN_FONT_SIZE_PX } from "../../../../theme/font-scale";
15
15
  import type { AppAction, AppState } from "../../../../state/app/context";
16
16
  import { openBrokerAddFlow } from "../../../../plugins/builtin/broker-manager/add-request";
17
- import { isManualPortfolio } from "../../../../plugins/builtin/portfolio-list/mutations";
17
+ import { isManualPortfolio } from "../../../../portfolio/mutations";
18
18
  import { CHART_RENDERER_PREFERENCES } from "../../../chart/core/types";
19
19
  import type { Command } from "../registry";
20
20
  import type { AppConfig, BrokerInstanceConfig } from "../../../../types/config";
@@ -29,6 +29,9 @@ import { parseWindowModeCommandArg } from "../../layout-items";
29
29
  import type { CommandBarRoute } from "../../workflow/types";
30
30
  import { recordFunctionOpen } from "../../../../telemetry/usage-counts";
31
31
  import { reportTelemetryConfig } from "../../../../telemetry/live-config";
32
+ import { NIGHTLY_CHANNEL_SUMMARY, withUpdateChannel } from "../../../../updater/channel";
33
+ import { isPrereleaseVersion } from "../../../../utils/semver";
34
+ import { VERSION } from "../../../../version";
32
35
 
33
36
  type NotifyFn = (body: string, options?: { type?: "info" | "success" | "error" }) => void;
34
37
 
@@ -373,6 +376,50 @@ export function runDirectCommandAction(options: {
373
376
  void onCheckForUpdates?.();
374
377
  closeAll({ revertThemePreview: false });
375
378
  return;
379
+ case "release-channel": {
380
+ // The runtime checks again as soon as the channel changes.
381
+ const setChannel = (channel: "stable" | "nightly") => {
382
+ const nextConfig = withUpdateChannel(getState().config, channel);
383
+ dispatch({ type: "SET_CONFIG", config: nextConfig });
384
+ persistConfig(nextConfig);
385
+ notify(`${t(command.label)}: ${channel}`, { type: "success" });
386
+ };
387
+ // The saved setting flips; GLOOMBERB_UPDATE_CHANNEL still wins for the run it is set on.
388
+ // Back to stable asks too, to say a nightly build already running stays.
389
+ if (state.config.updates?.channel === "nightly") {
390
+ openInlineConfirm({
391
+ confirmId: "switch-to-stable",
392
+ title: "Switch to Stable?",
393
+ body: [
394
+ isPrereleaseVersion(VERSION)
395
+ ? "Updates come from stable releases only. This nightly build stays until a newer stable release is out."
396
+ : "Updates come from stable releases only.",
397
+ ],
398
+ confirmLabel: "Switch to Stable",
399
+ cancelLabel: "Stay on Nightly",
400
+ tone: "default",
401
+ onConfirm: async () => { setChannel("stable"); },
402
+ });
403
+ return;
404
+ }
405
+ openInlineConfirm({
406
+ confirmId: "switch-to-nightly",
407
+ title: "Switch to Nightly?",
408
+ body: [
409
+ `Nightly is ${NIGHTLY_CHANNEL_SUMMARY}. Stable releases still reach you when they are newer.`,
410
+ isPrereleaseVersion(VERSION)
411
+ ? "Switch back to Stable at any time."
412
+ : "Switch back to Stable at any time: the nightly build stays until a newer stable release is out.",
413
+ ],
414
+ confirmLabel: "Switch to Nightly",
415
+ cancelLabel: "Stay on Stable",
416
+ tone: "default",
417
+ // An opt-in to builds that can break: a stray Enter stays on stable.
418
+ defaultAction: "cancel",
419
+ onConfirm: async () => { setChannel("nightly"); },
420
+ });
421
+ return;
422
+ }
376
423
  case "theme":
377
424
  setRootThemeBaseId(getState().config.theme);
378
425
  setRootQuery(arg ? `TH ${arg}` : "TH ");
@@ -250,6 +250,14 @@ export const commands: Command[] = [
250
250
  description: "Check GitHub releases for a newer version",
251
251
  category: "Config",
252
252
  },
253
+ {
254
+ // Not "Update ...": "update" has to land on Check for Updates first.
255
+ id: "release-channel",
256
+ prefix: "",
257
+ label: "Release Channel",
258
+ description: "Update from stable releases, or from nightly builds as well",
259
+ category: "Config",
260
+ },
253
261
  {
254
262
  id: "toggle-crash-reports",
255
263
  prefix: "",
@@ -1,6 +1,7 @@
1
1
  import type { CommandBarResultLine } from "../../../types/plugin";
2
2
  import type { TickerRecord } from "../../../types/ticker";
3
3
  import type { BrokerContractRef, TickerListingRef } from "../../../types/instrument";
4
+ import type { InstrumentRef } from "../../../market-data/request-types";
4
5
  import {
5
6
  buildSections,
6
7
  type CommandBarCategoryPriorities,
@@ -39,6 +40,12 @@ export interface ResultItem {
39
40
  listing?: TickerListingRef;
40
41
  /** Materialize this exact listing when another command consumes the row. */
41
42
  resolveTicker?: () => Promise<TickerRecord>;
43
+ /**
44
+ * What the pane this row opens will load, so the bar can warm it while the
45
+ * row is the pick. Unset when only opening can tell (a broker contract, a
46
+ * saved ticker with several).
47
+ */
48
+ prefetchInstrument?: InstrumentRef;
42
49
  shortcutQuery?: string;
43
50
  searchText?: string;
44
51
  /** Tints the trailing marker and the section heading with the AI accent. */
@@ -139,6 +139,11 @@ export function useCommandBarPaneTemplateActions({
139
139
  { preserveListingKey: true },
140
140
  );
141
141
  if (!resolvedTicker) {
142
+ // Typed alone with no active ticker: the pane's own view without one (INS's insider buying).
143
+ if (!trimmedArg && template.shortcut?.openWithoutArg) {
144
+ await openPaneTemplateDirect(template);
145
+ return;
146
+ }
142
147
  openModeRoute("ticker-search", trimmedArg, {
143
148
  action: "pane-template",
144
149
  templateId: template.id,
@@ -52,3 +52,11 @@ function getSnapshot(): CommandBarPromptBinding | null {
52
52
  export function useCommandBarPromptBinding(): CommandBarPromptBinding | null {
53
53
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
54
54
  }
55
+
56
+ /**
57
+ * Calls `listener` with the open screen's query each time the bar publishes
58
+ * its prompt, and with an empty one when it closes. For code outside React.
59
+ */
60
+ export function subscribeCommandBarQuery(listener: (query: string) => void): () => void {
61
+ return subscribe(() => listener(binding?.query ?? ""));
62
+ }
@@ -1,9 +1,12 @@
1
1
  import type { AppState } from "../../../../state/app/context";
2
2
  import type { TickerRecord } from "../../../../types/ticker";
3
- import { isManualPortfolio } from "../../../../plugins/builtin/portfolio-list/mutations";
3
+ import { isManualPortfolio } from "../../../../portfolio/mutations";
4
4
  import type { Command } from "../../commands/registry";
5
5
  import type { ResultItem } from "../../list/model";
6
6
  import { canRestartToApply } from "../../../../updater";
7
+ import { NIGHTLY_CHANNEL_SUMMARY, resolveUpdateChannel } from "../../../../updater/channel";
8
+ import { isPrereleaseVersion } from "../../../../utils/semver";
9
+ import { VERSION } from "../../../../version";
7
10
 
8
11
  interface RootCommandItemBuilderOptions {
9
12
  activeCollectionId: string | null;
@@ -157,6 +160,11 @@ export function createRootCommandItemBuilder({
157
160
  return state.config.telemetry?.attention === true
158
161
  ? "Currently on: share ticker research counts"
159
162
  : "Currently off: opt in to sharing ticker research counts";
163
+ case "release-channel":
164
+ if (resolveUpdateChannel(state.config) === "nightly") return `Currently nightly: ${NIGHTLY_CHANNEL_SUMMARY}`;
165
+ return isPrereleaseVersion(VERSION)
166
+ ? "Currently stable: this nightly build stays until a newer stable release"
167
+ : "Currently stable";
160
168
  case "font-size-increase":
161
169
  case "font-size-decrease":
162
170
  return `Currently ${state.config.fontSize ?? 12}px`;
@@ -176,6 +184,8 @@ export function createRootCommandItemBuilder({
176
184
  return state.config.telemetry?.usage === false ? "off" : "on";
177
185
  case "toggle-attention-counts":
178
186
  return state.config.telemetry?.attention === true ? "on" : "off";
187
+ case "release-channel":
188
+ return resolveUpdateChannel(state.config);
179
189
  default:
180
190
  return command.prefix || undefined;
181
191
  }
@@ -185,8 +195,13 @@ export function createRootCommandItemBuilder({
185
195
  switch (command.id) {
186
196
  case "set-portfolio-position":
187
197
  return "edit position update position modify position manual position portfolio position";
198
+ // "update" is a whole word here so it lands on this command before Set
199
+ // Portfolio Position (whose keywords say "update position") and Release
200
+ // Channel, which keeps the word further back.
188
201
  case "check-for-updates":
189
- return state.updateProgress?.phase === "ready" ? "check for updates restart relaunch" : "";
202
+ return state.updateProgress?.phase === "ready" ? "update check for updates restart relaunch" : "update";
203
+ case "release-channel":
204
+ return "nightly stable beta prerelease update updates channel";
190
205
  default:
191
206
  return "";
192
207
  }
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { getSharedMarketDataCoordinator } from "../../../../market-data/coordinator";
3
+ import type { InstrumentRef } from "../../../../market-data/request-types";
4
+ import { instrumentIdentityKey } from "../../../../utils/instrument-identity";
5
+ import type { ResultItem } from "../../list/model";
6
+
7
+ /** Typing passes through rows faster than this; reading the list and pressing Enter takes longer. */
8
+ const PICK_REST_MS = 200;
9
+
10
+ function warmInstrument(instrument: InstrumentRef): void {
11
+ getSharedMarketDataCoordinator()?.warmTickerGaps(instrument);
12
+ }
13
+
14
+ /**
15
+ * Warms the quote and chart of the instrument Enter would open once the pick
16
+ * rests, so its pane paints with data instead of loading. Only the row Enter
17
+ * runs, only an instrument row, and each instrument once per visit to the bar:
18
+ * Gloom Cloud rate-limits the free plan.
19
+ */
20
+ export function useRootPickPrefetch(
21
+ pick: ResultItem | null | undefined,
22
+ prefetch: (instrument: InstrumentRef) => void = warmInstrument,
23
+ ): void {
24
+ const instrument = pick?.prefetchInstrument ?? null;
25
+ const latest = useRef(instrument);
26
+ latest.current = instrument;
27
+ const warmed = useRef(new Set<string>());
28
+ const key = instrument ? instrumentIdentityKey(instrument) : null;
29
+
30
+ useEffect(() => {
31
+ if (!key || warmed.current.has(key)) return;
32
+ const timer = setTimeout(() => {
33
+ warmed.current.add(key);
34
+ if (latest.current) prefetch(latest.current);
35
+ }, PICK_REST_MS);
36
+ return () => clearTimeout(timer);
37
+ }, [key, prefetch]);
38
+ }
@@ -41,6 +41,8 @@ export function useRootProviderSearch(options: {
41
41
  }): {
42
42
  activeRootProviderResultsKey: string | null;
43
43
  orderedRootResults: ResultItem[];
44
+ /** False while the list is the local stand-in for a symbol search that has not answered. */
45
+ rootResultsCurrent: boolean;
44
46
  rootSearching: boolean;
45
47
  rootSectionOrder: CommandBarSectionOrder;
46
48
  } {
@@ -231,9 +233,13 @@ export function useRootProviderSearch(options: {
231
233
  ].join("\n");
232
234
  }, [rootPlainTickerSearchArg, rootProviderResults, rootProviderResultsQuery, rootTickerSearchArg]);
233
235
 
236
+ // The rows before the answer are for whatever the query matches locally, not the rows Enter will land on.
237
+ const rootResultsCurrent = !rootTickerSearchArg || (rootProviderResultsQuery === rootTickerSearchArg && !!rootProviderResults);
238
+
234
239
  return {
235
240
  activeRootProviderResultsKey,
236
241
  orderedRootResults,
242
+ rootResultsCurrent,
237
243
  rootSearching,
238
244
  rootSectionOrder,
239
245
  };
@@ -146,6 +146,7 @@ export function useCommandBarRootRuntime({
146
146
  orderedRootResults: ResultItem[];
147
147
  rootGhostSuffix: string | null;
148
148
  rootResultModel: RootResultModel;
149
+ rootResultsCurrent: boolean;
149
150
  rootSearching: boolean;
150
151
  rootSectionOrder: ReturnType<typeof useRootProviderSearch>["rootSectionOrder"];
151
152
  rootShortcutFeedback: string | null;
@@ -282,6 +283,7 @@ export function useCommandBarRootRuntime({
282
283
  const {
283
284
  activeRootProviderResultsKey,
284
285
  orderedRootResults,
286
+ rootResultsCurrent,
285
287
  rootSearching: tickerSearching,
286
288
  rootSectionOrder,
287
289
  } = useRootProviderSearch({
@@ -371,6 +373,7 @@ export function useCommandBarRootRuntime({
371
373
  orderedRootResults,
372
374
  rootGhostSuffix,
373
375
  rootResultModel,
376
+ rootResultsCurrent,
374
377
  rootSearching,
375
378
  rootSectionOrder,
376
379
  rootShortcutFeedback,
@@ -43,7 +43,9 @@ export function buildRootShortcutFeedback({
43
43
  ? `Shortcut: ${rootShortcutIntent.label} for ${symbol} · Tab to accept`
44
44
  : `Shortcut: ${rootShortcutIntent.label} for ${symbol}`;
45
45
  }
46
- return `Shortcut: ${rootShortcutIntent.label} · Enter to choose ticker`;
46
+ return rootShortcutIntent.template.shortcut?.openWithoutArg && !rootShortcutIntent.argText
47
+ ? `Shortcut: ${rootShortcutIntent.label}`
48
+ : `Shortcut: ${rootShortcutIntent.label} · Enter to choose ticker`;
47
49
  }
48
50
  if (argKind === "ticker-list") {
49
51
  if (rootShortcutIntent.argText) {
@@ -4,7 +4,7 @@ import type {
4
4
  PaneTemplateCreateOptions,
5
5
  PaneTemplateDef,
6
6
  } from "../../../../types/plugin";
7
- import { isManualPortfolio } from "../../../../plugins/builtin/portfolio-list/mutations";
7
+ import { isManualPortfolio } from "../../../../portfolio/mutations";
8
8
  import { normalizeTickerInput } from "../../../../tickers/search";
9
9
  import {
10
10
  getCollectionCommandAction,
@@ -4,8 +4,12 @@ import type { AppState } from "../../../../state/app/context";
4
4
  import type { AppTickerRepositoryPort } from "../../../../core/app-service-ports";
5
5
  import type { PluginRegistry } from "../../../../plugins/registry";
6
6
  import type { InstrumentSearchResult } from "../../../../types/instrument";
7
+ import type { TickerRecord } from "../../../../types/ticker";
7
8
  import type { PinTickerOptions } from "../../../../types/plugin";
8
9
  import { publicTickerKey } from "../../../../utils/exchanges";
10
+ import { instrumentFromTicker, type InstrumentRef } from "../../../../market-data/request-types";
11
+ import { getSearchResultSymbol } from "../../../../tickers/search/result";
12
+ import { otherListingTickerKey } from "../../../../tickers/search/upsert";
9
13
  import {
10
14
  rankTickerSearchItems,
11
15
  upsertTickerFromSearchResult,
@@ -37,6 +41,9 @@ export function useCommandBarTickerSearchActions({
37
41
  tickers,
38
42
  }: UseCommandBarTickerSearchActionsOptions) {
39
43
  const tickerSearchCacheRef = useRef<Map<string, TickerSearchCandidate[]>>(new Map());
44
+ // Read when a row is built; a ticker saved meanwhile must not rebuild every row.
45
+ const tickersRef = useRef(tickers);
46
+ tickersRef.current = tickers;
40
47
 
41
48
  const resolveSearchTicker = useCallback(async (result: InstrumentSearchResult, tickerSymbol?: string) => {
42
49
  const { ticker, created } = await upsertTickerFromSearchResult(tickerRepository, result, { tickerSymbol });
@@ -82,6 +89,7 @@ export function useCommandBarTickerSearchActions({
82
89
  category: candidate.category,
83
90
  kind: "ticker",
84
91
  resolveTicker: async () => candidate.ticker!,
92
+ prefetchInstrument: rowPrefetchInstrument(candidate, tickersRef.current),
85
93
  secondaryAction: () => {
86
94
  focusTicker(candidate.ticker!.metadata.ticker, { forceNewPane: true, ...tickerSelectionFromSearchResult(candidate.result) });
87
95
  closeAll({ revertThemePreview: false });
@@ -111,6 +119,7 @@ export function useCommandBarTickerSearchActions({
111
119
  candidate.result!.exchange === "SMART" ? candidate.result!.primaryExchange
112
120
  : candidate.result!.exchange || candidate.result!.primaryExchange,
113
121
  )),
122
+ prefetchInstrument: rowPrefetchInstrument(candidate, tickersRef.current),
114
123
  secondaryAction: () => openTickerResearch(candidate.result!, { forceNewPane: true }),
115
124
  action: () => openTickerResearch(candidate.result!),
116
125
  };
@@ -181,6 +190,31 @@ export function useCommandBarTickerSearchActions({
181
190
  };
182
191
  }
183
192
 
193
+ /**
194
+ * The instrument the pane reads once this row opens: a search listing is
195
+ * pinned by its venue, a saved ticker without one goes by its record. A broker
196
+ * contract, a saved ticker that carries one, or a choice among several stays
197
+ * with the pane, which resolves the route and its venue.
198
+ */
199
+ function rowPrefetchInstrument(
200
+ candidate: TickerSearchCandidate,
201
+ tickers: ReadonlyMap<string, TickerRecord>,
202
+ ): InstrumentRef | undefined {
203
+ const { result, ticker } = candidate;
204
+ if (result) {
205
+ if (result.brokerContract) return undefined;
206
+ // Opened, a listing whose symbol is saved for another venue is saved under its own key.
207
+ const symbol = getSearchResultSymbol(result);
208
+ return {
209
+ symbol: ticker?.metadata.ticker ?? otherListingTickerKey(result, symbol, tickers.get(symbol)) ?? symbol,
210
+ exchange: result.exchange || ticker?.metadata.exchange || "",
211
+ instrument: null,
212
+ };
213
+ }
214
+ if (!ticker || ticker.metadata.broker_contracts?.length) return undefined;
215
+ return instrumentFromTicker(ticker, ticker.metadata.ticker) ?? undefined;
216
+ }
217
+
184
218
  function formatTickerSearchDetail(candidate: TickerSearchCandidate): string {
185
219
  const metadata = [
186
220
  formatInstrumentClass(candidate.instrumentClass),
@@ -17,6 +17,7 @@ import {
17
17
  } from "../routes/root/search-providers";
18
18
  import { useRouteListState } from "../routing/list-state";
19
19
  import { useCommandBarRootRuntime } from "../routes/root/runtime";
20
+ import { useRootPickPrefetch } from "../routes/root/pick-prefetch";
20
21
  import { createQuickLookTickerCandidates } from "../routes/ticker-search/results";
21
22
  import { useCommandSearchReport } from "../routes/root/search-report";
22
23
  import { useAppStateRef } from "../../../state/app/context";
@@ -77,9 +78,10 @@ export function CommandBar({
77
78
  titleBarOverlay,
78
79
  visibleListStateRef,
79
80
  } = useCommandBarEnvironment();
81
+ // Without an updater (the web terminal) neither checking nor a channel means anything.
80
82
  const availableCommands = useMemo(() => onCheckForUpdates
81
83
  ? allAvailableCommands
82
- : allAvailableCommands.filter((command) => command.id !== "check-for-updates"), [allAvailableCommands, onCheckForUpdates]);
84
+ : allAvailableCommands.filter((command) => command.id !== "check-for-updates" && command.id !== "release-channel"), [allAvailableCommands, onCheckForUpdates]);
83
85
  const {
84
86
  applyThemePreview,
85
87
  clearThemePreview,
@@ -405,6 +407,7 @@ export function CommandBar({
405
407
  activeMatch,
406
408
  orderedRootResults,
407
409
  rootGhostSuffix,
410
+ rootResultsCurrent,
408
411
  rootSearching,
409
412
  rootSectionOrder,
410
413
  rootShortcutFeedback,
@@ -458,6 +461,9 @@ export function CommandBar({
458
461
  tickerActionItems,
459
462
  writeTickerSearchCache,
460
463
  });
464
+ // The row plain Enter runs: the best match while the list is untouched. Not
465
+ // the stand-in rows shown while the search for the typed query is out.
466
+ useRootPickPrefetch(currentRoute || !rootResultsCurrent ? null : orderedRootResults[rootSelectedIdx]);
461
467
  const themePickerActive = !currentRoute && activeMatch?.command.id === "theme";
462
468
  const themePickerFilter = themePickerActive ? activeMatch.arg : "";
463
469
  // A theme picked from the root ("TH dracula", Enter) is the typed shortcut
@@ -23,11 +23,12 @@ import {
23
23
  isManualPortfolio,
24
24
  resolveManualPositionCurrency,
25
25
  setManualPortfolioPosition,
26
- } from "../../../plugins/builtin/portfolio-list/mutations";
26
+ } from "../../../portfolio/mutations";
27
27
  import type { CommandBarFieldValue } from "./types";
28
28
  import type { WorkflowStringValues } from "./broker";
29
29
  import { coerceFieldString } from "../helpers";
30
30
  import { slugifyName } from "../../../utils/slugify";
31
+ import { readTypedNumber } from "../../../utils/decimal-comma";
31
32
  import { resolveTickerInputOrThrow } from "./ops";
32
33
  import { resolveCollectionTicker } from "./collection-ticker";
33
34
 
@@ -232,14 +233,14 @@ export function createCommandBarCollectionWorkflowActions(options: {
232
233
  throw new Error("Choose a manual portfolio.");
233
234
  }
234
235
 
235
- const shares = Number(coerceFieldString(values.shares));
236
+ // 18,50, 1.234,5 and 123.456 read as typed; 1,234 could be either reading and fails naming both.
237
+ const shares = readTypedNumber(coerceFieldString(values.shares), "Shares");
236
238
  if (!Number.isFinite(shares) || shares <= 0) {
237
239
  throw new Error("Shares must be greater than 0.");
238
240
  }
239
241
 
240
- const rawAvgCost = coerceFieldString(values.avgCost).trim();
241
- const avgCost = Number(rawAvgCost);
242
- if (!rawAvgCost || !Number.isFinite(avgCost)) {
242
+ const avgCost = readTypedNumber(coerceFieldString(values.avgCost), "Avg Cost");
243
+ if (!Number.isFinite(avgCost)) {
243
244
  throw new Error("Avg Cost must be a valid number.");
244
245
  }
245
246
 
@@ -2,7 +2,7 @@ import { type LayoutConfig, type PaneBinding, type PaneInstanceConfig } from "..
2
2
  import type { PaneSettingField, PaneTemplateContext, PaneTemplateCreateOptions, PaneTemplateInstanceConfig, PaneTemplateDef } from "../../../types/plugin";
3
3
  import { getFocusedCollectionId, getFocusedTickerSymbol } from "../../../state/app/context";
4
4
  import { resolveInstrumentForPane, resolveListingForPane } from "../../../core/state/app/instrument";
5
- import { resolveTickerOpenTarget } from "../../../tickers/open-target";
5
+ import { resolveTickerOpenTarget, tickerOpenFailureMessage } from "../../../tickers/open-target";
6
6
  import { instrumentFromTicker } from "../../../market-data/request-types";
7
7
  import { scopedBrokerContractIdentityKey } from "../../../utils/instrument-identity";
8
8
  import { tickerSelectionFromSearchResult } from "../../../tickers/selection";
@@ -115,14 +115,17 @@ async function resolvePaneTemplateOptions(
115
115
  // nothing was named, so they must not resolve a ticker at all: there is no
116
116
  // input to match, and silently falling back to the active ticker would bind a
117
117
  // pane the caller deliberately left unbound.
118
+ // INS opens its own view without one (the market's insider buying) when
119
+ // nothing is named and no ticker is active; with one active it opens on it.
118
120
  const tickerNamed = !!(resolvedOptions?.symbol?.trim() || resolvedOptions?.arg?.trim() || resolvedOptions?.ticker);
119
- const resolvesTicker = template.shortcut?.argPlaceholder === "ticker"
120
- && (tickerNamed || !template.shortcut.argOptional);
121
+ const opensWithoutTicker = !!template.shortcut?.argOptional || (!!template.shortcut?.openWithoutArg && !baseContext.activeTicker);
122
+ const resolvesTicker = template.shortcut?.argPlaceholder === "ticker" && (tickerNamed || !opensWithoutTicker);
121
123
  if (resolvesTicker) {
122
124
  if (resolvedOptions?.instrument === null && !resolvedOptions.ticker) {
123
- const publicTarget = await resolveTickerOpenTarget({ query: resolvedOptions.symbol ?? resolvedOptions.arg ?? baseContext.activeTicker ?? "",
125
+ const publicQuery = resolvedOptions.symbol ?? resolvedOptions.arg ?? baseContext.activeTicker ?? "";
126
+ const publicTarget = await resolveTickerOpenTarget({ query: publicQuery,
124
127
  tickers: state.tickers, tickerRepository: deps.tickerRepository, dataProvider: deps.dataProvider, publicOnly: true });
125
- if (!publicTarget) throw new Error("Could not resolve the selected public listing.");
128
+ if (!publicTarget) throw new Error(tickerOpenFailureMessage(publicQuery.trim().toUpperCase()));
126
129
  deps.dispatch({ type: "UPDATE_TICKER", ticker: publicTarget.ticker });
127
130
  if (publicTarget.created) deps.pluginRegistry.events.emit("ticker:added", { symbol: publicTarget.symbol, ticker: publicTarget.ticker });
128
131
  resolvedOptions = { ...resolvedOptions, symbol: publicTarget.symbol, ticker: publicTarget.ticker, listing: resolvedOptions.listing ?? publicTarget.listing };
@@ -104,7 +104,7 @@ interface OtherSettings {
104
104
  identity: readonly string[] | undefined;
105
105
  /** The template is the only one that opens its pane type. */
106
106
  onlyTemplate: boolean;
107
- /** The spec names a ticker or params, or the form takes no argument or an optional one. */
107
+ /** The spec names a ticker or params, or the form takes no argument, an optional one, or opens without one. */
108
108
  bareFormOk: boolean;
109
109
  }
110
110
 
@@ -150,7 +150,8 @@ export function findReusablePaneInstance(
150
150
  const other: OtherSettings = {
151
151
  identity: options.paneDef?.identitySettings,
152
152
  onlyTemplate: isOnlyTemplateForPane(template, templates),
153
- bareFormOk: namesATarget(spec) || !template.shortcut?.argPlaceholder || template.shortcut.argOptional === true,
153
+ bareFormOk: namesATarget(spec) || !template.shortcut?.argPlaceholder || template.shortcut.argOptional === true
154
+ || template.shortcut.openWithoutArg === true,
154
155
  };
155
156
  const matches = candidates.filter((instance) => showsWhatSpecNames(instance, spec, other));
156
157
  return matches.find((instance) => instance.instanceId === options.focusedPaneId) ?? matches[0] ?? null;
@@ -2,7 +2,7 @@ import type { Dispatch } from "react";
2
2
  import type { AppAction, AppState } from "../../../state/app/context";
3
3
  import type { AppTickerRepositoryPort } from "../../../core/app-service-ports";
4
4
  import type { PluginRegistry } from "../../../plugins/registry";
5
- import { isManualPortfolio } from "../../../plugins/builtin/portfolio-list/mutations";
5
+ import { isManualPortfolio } from "../../../portfolio/mutations";
6
6
  import type { DataProvider } from "../../../types/data-provider";
7
7
  import type { Portfolio, TickerRecord, Watchlist } from "../../../types/ticker";
8
8
  import { AmbiguousTickerError, resolveTickerSearch, upsertTickerFromSearchResult } from "../../../tickers/search";
@@ -137,6 +137,7 @@ export function ConfirmModalContent({
137
137
  confirmLabel={t(confirm.confirmLabel)}
138
138
  cancelLabel={t(confirmCancelLabel(confirm.cancelLabel))}
139
139
  confirmVariant={confirm.tone === "default" ? "primary" : "danger"}
140
+ defaultAction={confirm.defaultAction}
140
141
  width={contentWidth}
141
142
  status={status}
142
143
  busy={pending}
@@ -12,6 +12,8 @@ export interface ConfirmModalOptions {
12
12
  cancelLabel?: string;
13
13
  /** Danger unless said otherwise: most confirms guard a deletion. */
14
14
  tone?: "default" | "danger";
15
+ /** What Enter answers: `cancel` for an opt-in a stray Enter must not accept (ConfirmDialog). */
16
+ defaultAction?: "confirm" | "cancel";
15
17
  /** Reads what it acts on when it runs, not when the confirm opened. */
16
18
  onConfirm: () => void | Promise<void>;
17
19
  /** "stay" keeps the confirm open after it ran; "back" closes it, as "close" does. */
@@ -38,7 +38,8 @@ import {
38
38
  paneManagementAccelerators,
39
39
  resolvePaneManagementShortcut,
40
40
  } from "./shell/shortcuts";
41
- import { menuItemsForFallback, paneFooterMenuItems } from "./shell/menu";
41
+ import { menuItemsForFallback, paneFooterMenuItems, REPORT_BUG_MENU_LABEL } from "./shell/menu";
42
+ import { canReportPaneBug } from "../../shares/bug-report-flow";
42
43
  import { useShellPaneCapture } from "./shell/pane/capture";
43
44
  import { captureStampFooter, usePaneCaptureStamp } from "./pane/capture-stamp";
44
45
  import { pluginContextMenuSlot, withPluginContextMenuItems } from "../../ui/context-menu";
@@ -252,6 +253,10 @@ export function DetachedPaneShell({ pluginRegistry, desktopWindowBridge }: Detac
252
253
  }
253
254
  items.push({ id: "pane-docs", label: "Docs", onSelect: openDocs });
254
255
  items.push({ id: "share-pane", label: "Share", accelerator: accelerators.share, onSelect: sharePane });
256
+ // A popped-out window paints its pane's DOM, so it can always picture it.
257
+ if (canReportPaneBug(true)) {
258
+ items.push({ id: "report-bug", label: REPORT_BUG_MENU_LABEL, onSelect: () => pluginRegistry.reportPaneBug(desktopWindowBridge.paneId) });
259
+ }
255
260
  items.push(pluginContextMenuSlot("pane"));
256
261
  if (dockPane) {
257
262
  items.push({ id: "dock", label: "Dock in Main Window", accelerator: accelerators.toggleFloating, onSelect: dockPane });
@@ -9,6 +9,7 @@ import { PaneKeyboardScrollController } from "../../../state/pane-scroll-registr
9
9
  import type { PaneDef } from "../../../types/plugin";
10
10
  import { Box } from "../../../ui";
11
11
  import { PaneErrorBoundary } from "./error-boundary";
12
+ import { PaneTickerFateGate } from "./ticker-fate-gate";
12
13
 
13
14
  // Under GLOOMBERB_PERF_TRACE every pane commit is reported by pane, which is
14
15
  // how a store update that fans out to the whole layout gets attributed. The
@@ -72,14 +73,16 @@ export const PaneContent = memo(function PaneContent({
72
73
  const body = useMemo(() => (
73
74
  <PaneErrorBoundary paneType={paneType} title={title} onClose={closePane ? closeFailedPane : undefined}>
74
75
  <PaneRenderTrace paneId={paneId} paneType={paneType}>
75
- <Component
76
- paneId={paneId}
77
- paneType={paneType}
78
- focused={focused}
79
- width={width}
80
- height={height}
81
- close={onClose ? close : undefined}
82
- />
76
+ <PaneTickerFateGate paneId={paneId}>
77
+ <Component
78
+ paneId={paneId}
79
+ paneType={paneType}
80
+ focused={focused}
81
+ width={width}
82
+ height={height}
83
+ close={onClose ? close : undefined}
84
+ />
85
+ </PaneTickerFateGate>
83
86
  </PaneRenderTrace>
84
87
  </PaneErrorBoundary>
85
88
  ), [Component, close, closeFailedPane, closePane, focused, height, language, onClose, paneId, paneType, theme, title, width]);
@@ -178,9 +178,11 @@ export function PaneSidebar({
178
178
  (resize?.onResizeEnd ?? resize?.onResize)?.(next);
179
179
  }, [resize, resolveDragWidth, stopLiveDrag]);
180
180
 
181
- // The keyboard moves the divider too: `<` and `>` while the pane is focused,
182
- // shown in the footer and listed in the pane menu. Each press is a finished
183
- // resize, so it persists.
181
+ // The keyboard moves the divider too: `<` and `>` while the pane is focused.
182
+ // They are bound and listed in the pane menu, not drawn in the footer: the
183
+ // width of a list is not one of the pane's actions, and the divider shows
184
+ // where it is (on the desktop its tooltip names the keys). Each press is a
185
+ // finished resize, so it persists.
184
186
  const resizeRegistrationId = `pane-sidebar-resize:${useId()}`;
185
187
  const canNarrow = !!resize && width > resize.min;
186
188
  const canWiden = !!resize && width < resize.max;
@@ -189,14 +191,25 @@ export function PaneSidebar({
189
191
  const next = Math.min(resize.max, Math.max(resize.min, width + delta));
190
192
  if (next !== width) (resize.onResizeEnd ?? resize.onResize)(next);
191
193
  };
192
- usePaneFooter(resizeRegistrationId, () => (resize ? {
193
- // After the pane's own actions, which register at order 0.
194
- order: 1,
195
- hints: [
196
- { id: "sidebar-narrow", key: NARROW_KEY, label: " sidebar", title: t("Narrow Sidebar"), onPress: () => stepResize(-KEYBOARD_RESIZE_STEP), disabled: !canNarrow },
197
- { id: "sidebar-widen", key: WIDEN_KEY, label: "", title: t("Widen Sidebar"), onPress: () => stepResize(KEYBOARD_RESIZE_STEP), disabled: !canWiden },
198
- ],
199
- } : null), [!!resize, canNarrow, canWiden, resize?.min, resize?.max, width]);
194
+ usePaneFooter(resizeRegistrationId, () => {
195
+ if (!resize) return null;
196
+ const steps = [
197
+ { id: "sidebar-narrow", key: NARROW_KEY, title: "Narrow Sidebar", delta: -KEYBOARD_RESIZE_STEP, disabled: !canNarrow },
198
+ { id: "sidebar-widen", key: WIDEN_KEY, title: "Widen Sidebar", delta: KEYBOARD_RESIZE_STEP, disabled: !canWiden },
199
+ ];
200
+ return {
201
+ // After the pane's own actions, which register at order 0.
202
+ order: 1,
203
+ keys: steps.map(({ id, key, delta, disabled }) => ({ id, key, label: "", onPress: () => stepResize(delta), disabled })),
204
+ // A bound that is reached drops out, as a disabled footer hint did.
205
+ menu: steps.filter((step) => !step.disabled).map(({ id, key, title, delta }) => ({
206
+ id: `pane-sidebar:${id}`,
207
+ label: title,
208
+ accelerator: key,
209
+ onSelect: () => stepResize(delta),
210
+ })),
211
+ };
212
+ }, [!!resize, canNarrow, canWiden, resize?.min, resize?.max, width]);
200
213
 
201
214
  const borderWidth = nativePaneChrome ? 0 : width > 1 ? 1 : 0;
202
215
  const listWidth = Math.max(width - borderWidth, 1);