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
@@ -4,7 +4,7 @@ import type { MarketDataRequestContext } from "../../types/data-provider";
4
4
  import type { Quote, TickerFinancials } from "../../types/financials";
5
5
  import { normalizeTickerFinancialsPriceHistory } from "../../utils/price-history";
6
6
  import { resolveTickerFinancialsQuoteState } from "../../market-data/quotes/resolution";
7
- import { shouldLogProviderError } from "../provider-errors";
7
+ import { noteProviderAnswer, noteProviderMiss, shouldLogProviderError, type ProviderMissNote } from "../provider-errors";
8
8
  import { quoteMetadataFromQuote } from "../../market-data/quotes/metadata";
9
9
  import {
10
10
  dropUnusableProviderQuote,
@@ -14,12 +14,19 @@ import {
14
14
  needsFinancialProfile,
15
15
  profileForSameListing,
16
16
  isProviderQuoteUsableForCurrentSession,
17
+ lastKnownProviderQuote,
17
18
  providerFinancialsMatchTarget,
18
19
  mergeMissingStatementArrays,
19
20
  mergeFinancials,
20
21
  } from "./financials";
21
22
  import type { ProviderRouterCoreDeps, SourceResult } from "./route-types";
22
23
 
24
+ /** What the providers of one quote request said when none had a current quote. */
25
+ export interface ProviderQuoteMissNote extends ProviderMissNote {
26
+ /** The newest priced answer for the target that was not current for its session, flagged stale. */
27
+ lastKnownQuote?: Quote;
28
+ }
29
+
23
30
  export class ProviderRouterPrimaryRoutes {
24
31
  constructor(private readonly options: ProviderRouterCoreDeps) {}
25
32
 
@@ -61,6 +68,7 @@ export class ProviderRouterPrimaryRoutes {
61
68
  ticker: string,
62
69
  exchange?: string,
63
70
  context?: MarketDataRequestContext,
71
+ misses?: ProviderMissNote,
64
72
  ): Promise<SourceResult<TickerFinancials> | null> {
65
73
  const entityKey = this.options.getEntityKey(ticker, context?.instrument);
66
74
  const variantKey = financialHistoryVariants(this.options.getTickerVariantCandidates(exchange), context)[0] ?? "";
@@ -70,11 +78,11 @@ export class ProviderRouterPrimaryRoutes {
70
78
  for (const provider of this.options.providersInPriorityOrder()) {
71
79
  try {
72
80
  const rawValue = await provider.getTickerFinancials(ticker, exchange, context);
73
- if (rawValue && !context?.instrument && !providerFinancialsMatchTarget(rawValue, ticker, exchange)) continue;
81
+ if (rawValue && !context?.instrument && !providerFinancialsMatchTarget(rawValue, ticker, exchange)) { noteProviderAnswer(misses); continue; }
74
82
  const resolvedValue = resolveTickerFinancialsQuoteState(normalizeTickerFinancialsPriceHistory(rawValue));
75
- if (resolvedValue && !context?.instrument && !providerFinancialsMatchTarget(resolvedValue, ticker, exchange)) continue;
83
+ if (resolvedValue && !context?.instrument && !providerFinancialsMatchTarget(resolvedValue, ticker, exchange)) { noteProviderAnswer(misses); continue; }
76
84
  let value = resolvedValue ? dropUnusableProviderQuote(resolvedValue, exchange, { recentAnswer: true }) : null;
77
- if (!value) continue;
85
+ if (!value) { noteProviderAnswer(misses); continue; }
78
86
  const sourceKey = this.options.providerSourceKey(provider);
79
87
  if (context?.statementHistory === "extended" && !hasReusableExtendedHistory(value)) {
80
88
  const previous = selectCachedResource<TickerFinancials>(this.options.resources, "financials", entityKey, [variantKey], [sourceKey], true);
@@ -131,6 +139,7 @@ export class ProviderRouterPrimaryRoutes {
131
139
  if (!needsFinancialProfile(primaryResult.value) && (context?.statementHistory === "extended" ? primaryResult.value.statementHistory?.status === "available" : hasDetailedStatementRows(primaryResult.value) && hasDeepStatementHistory(primaryResult.value))) return primaryResult;
132
140
  }
133
141
  } catch (error) {
142
+ noteProviderMiss(misses, error);
134
143
  if (shouldLogProviderError(error)) {
135
144
  this.options.logProviderError(`${provider.id} failed: ${error}`);
136
145
  }
@@ -181,13 +190,21 @@ export class ProviderRouterPrimaryRoutes {
181
190
  ticker: string,
182
191
  exchange?: string,
183
192
  context?: MarketDataRequestContext,
193
+ misses?: ProviderQuoteMissNote,
184
194
  ): Promise<SourceResult<Quote> | null> {
185
195
  const entityKey = this.options.getEntityKey(ticker, context?.instrument);
186
196
  const variantKey = this.options.getTickerVariantCandidates(exchange)[0] ?? "";
187
197
  for (const provider of this.options.providersInPriorityOrder()) {
188
198
  try {
189
199
  const quote = await provider.getQuote(ticker, exchange, context);
190
- if (!isProviderQuoteUsableForCurrentSession(quote, exchange, ticker, { recentAnswer: true })) continue;
200
+ if (!isProviderQuoteUsableForCurrentSession(quote, exchange, ticker, { recentAnswer: true })) {
201
+ noteProviderAnswer(misses);
202
+ const lastKnown = misses && lastKnownProviderQuote(quote, exchange, ticker);
203
+ if (lastKnown && (misses.lastKnownQuote?.lastUpdated ?? -Infinity) < lastKnown.lastUpdated) {
204
+ misses.lastKnownQuote = lastKnown;
205
+ }
206
+ continue;
207
+ }
191
208
  const sourceKey = this.options.providerSourceKey(provider);
192
209
  this.options.cacheResource(
193
210
  "quote",
@@ -199,6 +216,7 @@ export class ProviderRouterPrimaryRoutes {
199
216
  );
200
217
  return { sourceKey, value: quote };
201
218
  } catch (error) {
219
+ noteProviderMiss(misses, error);
202
220
  if (shouldLogProviderError(error)) {
203
221
  this.options.logProviderError(`${provider.id} failed: ${error}`);
204
222
  }
@@ -39,6 +39,8 @@ type CompanyFactsEntry = {
39
39
  form?: string;
40
40
  filed?: string;
41
41
  frame?: string;
42
+ /** A fact reporting the same figure for this span carries SEC's calendar-quarter frame. */
43
+ framedQuarter?: boolean;
42
44
  };
43
45
 
44
46
  type CompanyFactsStatementField = {
@@ -398,6 +400,23 @@ function companyFactsEntries(payload: unknown, tag: string, units: string[]): Co
398
400
  return [];
399
401
  }
400
402
 
403
+ /**
404
+ * SEC frames one fact per span, the latest filed: a 10-K's own fourth quarter
405
+ * is framed only on the next 10-K that repeats it. An earlier fact reporting
406
+ * the framed figure for that span is that quarter too, so it keeps its date.
407
+ */
408
+ function withFramedQuarters(entries: CompanyFactsEntry[], sameFigure?: SameFigure): CompanyFactsEntry[] {
409
+ const span = (entry: CompanyFactsEntry) => `${entry.start}:${entry.end}`;
410
+ const framed = new Map<string, CompanyFactsEntry[]>();
411
+ for (const entry of entries) {
412
+ if (!entry.start || !/^CY\d{4}Q[1-4]$/.test(entry.frame ?? "")) continue;
413
+ framed.set(span(entry), [...framed.get(span(entry)) ?? [], entry]);
414
+ }
415
+ if (!framed.size) return entries;
416
+ return entries.map((entry) => entry.start && framed.get(span(entry))?.some((quarter) =>
417
+ Object.is(quarter.val, entry.val) || sameFigure?.(entry, quarter)) ? { ...entry, framedQuarter: true } : entry);
418
+ }
419
+
401
420
  function secFormRank(form: string | undefined): number {
402
421
  const normalized = normalize(form);
403
422
  if (normalized === "10-K/A") return 4;
@@ -415,6 +434,7 @@ function companyFactsFiledRank(value: string | undefined): number {
415
434
  function shouldReplaceCompanyFact(
416
435
  candidate: CompanyFactsEntry,
417
436
  selected: CompanyFactsEntry | undefined,
437
+ repeats?: SameFigure,
418
438
  ): boolean {
419
439
  if (!selected) return true;
420
440
  const left = candidate;
@@ -433,13 +453,45 @@ function shouldReplaceCompanyFact(
433
453
  if (filedDiff < 0) return false;
434
454
  // Later comparative repeats do not move the original disclosure date.
435
455
  // A later value change is a restatement and replaces the selected fact.
436
- return !Object.is(left.val, right.val);
456
+ return !Object.is(left.val, right.val) && !repeats?.(left, right);
437
457
  }
438
458
  const formDiff = secFormRank(left.form) - secFormRank(right.form);
439
459
  if (formDiff !== 0) return formDiff > 0;
440
460
  return String(left.frame ?? "").localeCompare(String(right.frame ?? "")) > 0;
441
461
  }
442
462
 
463
+ /** Whether two facts for one span report the same figure under different values. */
464
+ type SameFigure = (left: CompanyFactsEntry, right: CompanyFactsEntry) => boolean;
465
+
466
+ /**
467
+ * A split re-expresses EPS on the new share count: two values are one figure
468
+ * when the share-basis evidence puts them on different bases and they agree
469
+ * on the current one within each filing's cent rounding.
470
+ */
471
+ function splitEquivalent(resolveEps: ReturnType<typeof createSecEpsBasisResolver>): SameFigure {
472
+ return (left, right) => {
473
+ const [a, b] = [resolveEps(left), resolveEps(right)];
474
+ return a.basis?.status === "split-adjusted" && b.basis?.status === "split-adjusted" && a.basis.factor !== b.basis.factor
475
+ && a.value !== undefined && b.value !== undefined
476
+ && Math.abs(a.value - b.value) <= .005 / a.basis.factor! + .005 / b.basis.factor! + 1e-12;
477
+ };
478
+ }
479
+
480
+ /**
481
+ * A later split re-expression of the selected EPS, or a later repeat of one,
482
+ * is a comparative repeat and not a restatement. The original disclosure
483
+ * stays selected and dated; the resolver carries it to the current basis.
484
+ */
485
+ function splitRepeats(sameFigure: SameFigure): SameFigure {
486
+ const repeated = new WeakMap<CompanyFactsEntry, Set<number>>();
487
+ return (candidate, selected) => {
488
+ if (repeated.get(selected)?.has(candidate.val!)) return true;
489
+ if (!sameFigure(candidate, selected)) return false;
490
+ repeated.set(selected, (repeated.get(selected) ?? new Set()).add(candidate.val!));
491
+ return true;
492
+ };
493
+ }
494
+
443
495
  function compareCompanyFactsChronologically(
444
496
  left: CompanyFactsEntry,
445
497
  right: CompanyFactsEntry,
@@ -484,7 +536,7 @@ function isQuarterlyCompanyFact(entry: CompanyFactsEntry, periodType: "duration"
484
536
  || new Date(start).toISOString().slice(0, 10) !== entry.start
485
537
  || new Date(end).toISOString().slice(0, 10) !== entry.end) return false;
486
538
  }
487
- if (/^CY\d{4}Q[1-4]$/.test(entry.frame ?? "")) return true;
539
+ if (/^CY\d{4}Q[1-4]$/.test(entry.frame ?? "") || entry.framedQuarter) return true;
488
540
  const form = normalize(entry.form);
489
541
  const fiscalPeriod = normalize(entry.fp ?? undefined);
490
542
  if ((form !== "10-Q" && form !== "10-Q/A") || !/^Q[1-3]$/.test(fiscalPeriod)) return false;
@@ -505,9 +557,10 @@ function setCompanyFactValue(
505
557
  field: keyof FinancialStatement,
506
558
  value: number,
507
559
  entry: CompanyFactsEntry,
560
+ repeats?: SameFigure,
508
561
  ): void {
509
562
  const selectionKey = `${date}:${String(field)}`;
510
- if (!shouldReplaceCompanyFact(entry, selectedFacts.get(selectionKey))) return;
563
+ if (!shouldReplaceCompanyFact(entry, selectedFacts.get(selectionKey), repeats)) return;
511
564
  const row = rows.get(date) ?? { date, dateSource: "sec", currency: "USD" };
512
565
  (row as unknown as Record<string, unknown>)[field] = value;
513
566
  if (entry.filed) {
@@ -532,6 +585,7 @@ function fillCompanyFactsStatementRows(
532
585
  field: CompanyFactsStatementField,
533
586
  period: "annual" | "quarterly",
534
587
  annualPeriodEnds: ReadonlySet<string>,
588
+ repeats?: SameFigure,
535
589
  ): void {
536
590
  for (const entry of [...entries].sort(compareCompanyFactsChronologically)) {
537
591
  if (!entry.end || typeof entry.val !== "number") continue;
@@ -548,6 +602,7 @@ function fillCompanyFactsStatementRows(
548
602
  field.field,
549
603
  field.transform ? field.transform(entry.val) : entry.val,
550
604
  entry,
605
+ repeats,
551
606
  );
552
607
  }
553
608
  }
@@ -648,10 +703,14 @@ export function parseCompanyFactsFinancialStatements(payload: unknown): SecCompa
648
703
  const quarterlyRows = new Map<string, FinancialStatement>();
649
704
  const annualSelectedFacts = new Map<string, CompanyFactsEntry>();
650
705
  const quarterlySelectedFacts = new Map<string, CompanyFactsEntry>();
706
+ const resolveEps = createSecEpsBasisResolver(payload);
707
+ // Diluted EPS only: share counts are kept only on the current basis.
708
+ const sameEps = splitEquivalent(resolveEps);
651
709
  const fieldEntries = COMPANY_FACTS_STATEMENT_FIELDS.map((field) => ({
652
710
  field,
653
- entries: withoutDifferentMeasureFallback(field, field.tags.flatMap((tag, tagPriority) => companyFactsEntries(payload, tag, field.units)
654
- .map((entry) => ({ ...entry, tagPriority, concept: tag })))),
711
+ entries: withoutDifferentMeasureFallback(field, field.tags.flatMap((tag, tagPriority) => withFramedQuarters(
712
+ companyFactsEntries(payload, tag, field.units), field.field === "eps" ? sameEps : undefined,
713
+ ).map((entry) => ({ ...entry, tagPriority, concept: tag })))),
655
714
  }));
656
715
  // Balance-sheet facts have no duration. Anchor their dates to actual annual
657
716
  // periods, so quarterly comparative snapshots in a 10-K stay quarterly.
@@ -659,13 +718,14 @@ export function parseCompanyFactsFinancialStatements(payload: unknown): SecCompa
659
718
  ? entries.filter(isAnnualDurationFact).map((entry) => entry.end!)
660
719
  : []));
661
720
 
721
+ const epsRepeats = splitRepeats(sameEps);
662
722
  for (const { field, entries } of fieldEntries) {
663
723
  if (entries.length === 0) continue;
664
- fillCompanyFactsStatementRows(annualRows, annualSelectedFacts, entries, field, "annual", annualPeriodEnds);
665
- fillCompanyFactsStatementRows(quarterlyRows, quarterlySelectedFacts, entries, field, "quarterly", annualPeriodEnds);
724
+ const repeats = field.field === "eps" ? epsRepeats : undefined;
725
+ fillCompanyFactsStatementRows(annualRows, annualSelectedFacts, entries, field, "annual", annualPeriodEnds, repeats);
726
+ fillCompanyFactsStatementRows(quarterlyRows, quarterlySelectedFacts, entries, field, "quarterly", annualPeriodEnds, repeats);
666
727
  }
667
728
 
668
- const resolveEps = createSecEpsBasisResolver(payload);
669
729
  const fourthQuarters = FOURTH_QUARTER_LINES.flatMap(({ fields, source }) => secFourthQuarters(fields, typeof source === "string"
670
730
  ? fieldEntries.find(({ field }) => field.field === source)?.entries ?? []
671
731
  : source.flatMap((tag, tagPriority) => companyFactsEntries(payload, tag, ["USD"]).map((entry) => ({ ...entry, tagPriority, concept: tag })))));
@@ -24,7 +24,8 @@ import {
24
24
  import {
25
25
  addLegacyBuiltinDisabledPluginAliases,
26
26
  addLegacyBuiltinPluginOwnerAliases,
27
- normalizeBuiltinDisabledPluginIds,
27
+ decodeBuiltinDisabledPluginIds,
28
+ encodeBuiltinDisabledPluginIds,
28
29
  normalizeBuiltinPluginStateMap,
29
30
  } from "../plugins/ownership";
30
31
  import { isRecord } from "../utils/guards";
@@ -363,7 +364,7 @@ function collectCoreConfigPayload(config: AppConfig) {
363
364
  // This build merges profiles one by one before it pushes, so a profile
364
365
  // missing from its list was removed, not one it never pulled.
365
366
  brokerInstancesMergedById: true,
366
- disabledPlugins: addLegacyBuiltinDisabledPluginAliases(config.disabledPlugins),
367
+ disabledPlugins: addLegacyBuiltinDisabledPluginAliases(encodeBuiltinDisabledPluginIds(config.disabledPlugins)),
367
368
  disabledSources: config.disabledSources,
368
369
  pluginConfig: addLegacyBuiltinPluginOwnerAliases(config.pluginConfig),
369
370
  theme: config.theme,
@@ -678,7 +679,7 @@ function mergeConfigPayload(
678
679
  && Array.isArray(payload.disabledPlugins)
679
680
  && payload.disabledPlugins.every((pluginId) => typeof pluginId === "string")
680
681
  ) {
681
- next.disabledPlugins = normalizeBuiltinDisabledPluginIds(payload.disabledPlugins);
682
+ next.disabledPlugins = decodeBuiltinDisabledPluginIds(payload.disabledPlugins);
682
683
  }
683
684
  if (canApply("pluginConfig") && isPluginStateMap(payload.pluginConfig)) {
684
685
  next.pluginConfig = withLocalPluginSecrets(
@@ -8,7 +8,7 @@ export interface PrintedCliResult {
8
8
  }
9
9
 
10
10
  /**
11
- * A command context whose `initMarketData` and `initConfigData` resolve to `market` (config, store, dataProvider, ...),
11
+ * A command context whose `initMarketData`, `initConfigData` and `initServices` resolve to `market` (config, store, dataProvider, ...),
12
12
  * with an empty ticker store unless `market` brings one.
13
13
  * `fail` throws, every printResult call is kept in `printed`, and `closeCount` reports how many
14
14
  * times the command closed its persistence.
@@ -29,6 +29,7 @@ export function createTestCliContext(
29
29
  cliOptions: { ...DEFAULT_CLI_OPTIONS, ...cliOptions },
30
30
  initMarketData: init,
31
31
  initConfigData: init,
32
+ initServices: async () => ({ ...await init(), destroy: () => { closed += 1; } }),
32
33
  printResult: (result: CliResult<any>, options?: CliResultRenderOptions<any, any>) => { printed.push({ result, options }); },
33
34
  fail: (message: string): never => { throw new Error(message); },
34
35
  } as unknown as CliCommandContext;
@@ -1,3 +1,4 @@
1
+ import { createProviderMiss } from "../sources/provider-errors";
1
2
  import type { DataProvider } from "../types/data-provider";
2
3
  import type { InstrumentSearchResult } from "../types/instrument";
3
4
  import type { Quote, TickerFinancials } from "../types/financials";
@@ -20,6 +21,22 @@ export function createTestDataProvider(overrides: Partial<DataProvider> = {}): D
20
21
  };
21
22
  }
22
23
 
24
+ /**
25
+ * A source that answers every request the way Gloom Cloud answers a symbol no listing carries:
26
+ * an empty status with the reason code NOT_FOUND.
27
+ */
28
+ export function createNotFoundProvider(overrides: Partial<DataProvider> = {}): DataProvider {
29
+ const miss = () => Promise.reject(createProviderMiss("NOT_FOUND", undefined, { notFound: true }));
30
+ return createTestDataProvider({
31
+ id: "not-found",
32
+ priority: 100,
33
+ getQuote: miss,
34
+ getTickerFinancials: miss,
35
+ getPriceHistory: miss,
36
+ ...overrides,
37
+ });
38
+ }
39
+
23
40
  export function createTestQuote(overrides: Partial<Quote> = {}): Quote {
24
41
  return {
25
42
  symbol: "AAPL",
@@ -49,3 +66,20 @@ export const fallbackProvider: DataProvider = createTestDataProvider({
49
66
  getTickerFinancials: async () => createTestFinancials(),
50
67
  getQuote: async () => createTestQuote(),
51
68
  });
69
+
70
+ /**
71
+ * A source that has nothing for any request, the way Gloom Cloud answers an empty status.
72
+ * `reason` is the sentence the service gave for it; without one the miss is bare.
73
+ */
74
+ export function createEmptyAnswerProvider(reason?: string, overrides: Partial<DataProvider> = {}): DataProvider {
75
+ const miss = () => Promise.reject(createProviderMiss("NOT_FOUND", reason));
76
+ return createTestDataProvider({
77
+ id: "empty-answer",
78
+ priority: 100,
79
+ getQuote: miss,
80
+ getTickerFinancials: miss,
81
+ getExchangeRate: miss,
82
+ getExchangeRateSnapshot: miss,
83
+ ...overrides,
84
+ });
85
+ }
@@ -28,6 +28,7 @@ export function TestShellPaneKeys(options: Partial<ShellPaneKeyOptions> & { focu
28
28
  startWindowMode: () => {},
29
29
  toggleFocusedPaneFullscreen: none,
30
30
  toggleFocusedPaneFloating: none,
31
+ toggleFocusedPaneNewLayout: none,
31
32
  ...options,
32
33
  });
33
34
  return null;
@@ -0,0 +1,46 @@
1
+ /** Colours for series told apart by name (compared tickers, markets). */
2
+ export const SERIES_COLORS = [
3
+ "#4dabf7",
4
+ "#63e6be",
5
+ "#f6c85f",
6
+ "#b197fc",
7
+ "#ff8787",
8
+ "#ffa94d",
9
+ "#74c0fc",
10
+ "#e599f7",
11
+ "#8ce99a",
12
+ "#ffd43b",
13
+ ] as const;
14
+
15
+ /**
16
+ * Okabe-Ito on a dark page, ordered so the first slots, the ones most charts
17
+ * use, sit furthest apart under deuteranopia, protanopia and tritanopia.
18
+ */
19
+ const COLORBLIND_DARK_SERIES = ["#56b4e9", "#e69f00", "#f0e442", "#d55e00", "#009e73", "#cc79a7", "#0072b2"] as const;
20
+
21
+ /** The same family on a light page: yellow and sky blue fade into white, so ink and a dark ochre stand in. */
22
+ const COLORBLIND_LIGHT_SERIES = ["#0072b2", "#d55e00", "#009e73", "#1f2328", "#7a5c00", "#cc79a7"] as const;
23
+
24
+ const THEME_SERIES_COLORS: Readonly<Record<string, readonly string[]>> = {
25
+ colorblind: COLORBLIND_DARK_SERIES,
26
+ "colorblind-light": COLORBLIND_LIGHT_SERIES,
27
+ "high-contrast": COLORBLIND_DARK_SERIES,
28
+ };
29
+
30
+ /** The theme's own palette for named series, or null where it draws them in `SERIES_COLORS`. */
31
+ export function themeSeriesColors(themeId: string): readonly string[] | null {
32
+ return THEME_SERIES_COLORS[themeId] ?? null;
33
+ }
34
+
35
+ const DEFAULT_SLOT = new Map<string, number>(SERIES_COLORS.map((color, index) => [color, index]));
36
+
37
+ /**
38
+ * A series colour as the theme draws it. A slot of the default palette moves
39
+ * to the same slot of the theme's palette; any other colour, chosen on purpose
40
+ * by a pane or a person, stays as it is.
41
+ */
42
+ export function themedSeriesColor(color: string, themeId: string): string {
43
+ const palette = THEME_SERIES_COLORS[themeId];
44
+ const slot = palette ? DEFAULT_SLOT.get(color.toLowerCase()) : undefined;
45
+ return slot === undefined ? color : palette![slot % palette!.length]!;
46
+ }
@@ -633,6 +633,81 @@ const rawThemes: Record<string, Theme> = {
633
633
  commandBg: "#000033",
634
634
  commandBorder: "#ff6600",
635
635
  },
636
+
637
+ // Gains, losses, warnings and chart series stay apart for deuteranopia,
638
+ // protanopia and tritanopia: up is blue, down is vermillion (the Okabe-Ito
639
+ // set), never red against green. The chrome is grey so hue only ever means
640
+ // data. themes.test.ts simulates the three deficiencies on these palettes.
641
+ colorblind: {
642
+ name: "Colorblind",
643
+ description: "Blue gains and vermillion losses, safe for color-blind eyes",
644
+ bg: "#0e1013",
645
+ panel: "#15181c",
646
+ border: "#3b4149",
647
+ borderFocused: "#d7dade",
648
+ text: "#e4e6e9",
649
+ textDim: "#a3a9b2",
650
+ textBright: "#ffffff",
651
+ textMuted: "#858c96",
652
+ positive: "#56b4e9",
653
+ negative: "#e66a1f",
654
+ neutral: "#9aa0a8",
655
+ warning: "#f0e442",
656
+ header: "#1c2026",
657
+ headerText: "#ffffff",
658
+ selected: "#2b3540",
659
+ selectedText: "#ffffff",
660
+ commandBg: "#15181c",
661
+ commandBorder: "#a3a9b2",
662
+ },
663
+
664
+ "colorblind-light": {
665
+ name: "Colorblind Light",
666
+ description: "Light, blue gains and vermillion losses, safe for color-blind eyes",
667
+ bg: "#ffffff",
668
+ panel: "#f5f6f8",
669
+ border: "#c8cdd4",
670
+ borderFocused: "#1f2328",
671
+ text: "#1f2328",
672
+ textDim: "#4d545e",
673
+ textBright: "#000000",
674
+ textMuted: "#646b75",
675
+ positive: "#0072b2",
676
+ negative: "#a84100",
677
+ neutral: "#5f6670",
678
+ warning: "#a27409",
679
+ header: "#e9ecef",
680
+ headerText: "#1f2328",
681
+ selected: "#d3e3f0",
682
+ selectedText: "#000000",
683
+ commandBg: "#f5f6f8",
684
+ commandBorder: "#1f2328",
685
+ },
686
+
687
+ // Every text role clears 7:1 (WCAG AAA) on the page, and the up, down and
688
+ // warning colours are the colour-blind safe ones.
689
+ "high-contrast": {
690
+ name: "High Contrast",
691
+ description: "White on black at AAA contrast, color-blind safe",
692
+ bg: "#000000",
693
+ panel: "#000000",
694
+ border: "#9a9a9a",
695
+ borderFocused: "#ffffff",
696
+ text: "#ffffff",
697
+ textDim: "#e0e0e0",
698
+ textBright: "#ffffff",
699
+ textMuted: "#c8c8c8",
700
+ positive: "#56b4e9",
701
+ negative: "#ff8c42",
702
+ neutral: "#c8c8c8",
703
+ warning: "#f0e442",
704
+ header: "#1a1a1a",
705
+ headerText: "#ffffff",
706
+ selected: "#3d3d3d",
707
+ selectedText: "#ffffff",
708
+ commandBg: "#000000",
709
+ commandBorder: "#ffffff",
710
+ },
636
711
  };
637
712
 
638
713
  export const themes: Record<string, Theme> = Object.fromEntries(
@@ -661,3 +736,77 @@ export function isDarkTheme(themeId: string): boolean {
661
736
  export function getTheme(id: string): Theme {
662
737
  return themes[id] ?? themes[DEFAULT_THEME]!;
663
738
  }
739
+
740
+ type ThemeTrait = "colorblind-safe" | "high-contrast";
741
+
742
+ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
743
+ colorblind: ["colorblind-safe"],
744
+ "colorblind-light": ["colorblind-safe"],
745
+ "high-contrast": ["high-contrast", "colorblind-safe"],
746
+ };
747
+
748
+ export interface ThemeSummary {
749
+ id: string;
750
+ name: string;
751
+ appearance: "dark" | "light";
752
+ traits: readonly ThemeTrait[];
753
+ }
754
+
755
+ /** Every theme in registry order: what `config themes`, help and the docs list. */
756
+ export function listThemes(): ThemeSummary[] {
757
+ return getThemeIds().map((id) => ({
758
+ id,
759
+ name: themes[id]!.name,
760
+ appearance: isDarkTheme(id) ? "dark" : "light",
761
+ traits: THEME_TRAITS[id] ?? [],
762
+ }));
763
+ }
764
+
765
+ export type ThemeLookup =
766
+ | { ok: true; id: string }
767
+ | { ok: false; suggestion: string | null };
768
+
769
+ /** `White Phosphor`, `white_phosphor` and `WHITE` all read as `white-phosphor` / `white`. */
770
+ function themeKey(value: string): string {
771
+ return value.trim().toLowerCase().replace(/[\s_]+/g, "-");
772
+ }
773
+
774
+ function editDistance(left: string, right: string): number {
775
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
776
+ for (let row = 1; row <= left.length; row += 1) {
777
+ const current = [row];
778
+ for (let column = 1; column <= right.length; column += 1) {
779
+ const substitution = previous[column - 1]! + (left[row - 1] === right[column - 1] ? 0 : 1);
780
+ current.push(Math.min(previous[column]! + 1, current[column - 1]! + 1, substitution));
781
+ }
782
+ previous = current;
783
+ }
784
+ return previous[right.length]!;
785
+ }
786
+
787
+ /**
788
+ * The theme id a person meant. An id or a display name, in any case, is that
789
+ * theme; anything else is unknown, with the closest id when the input is a
790
+ * typo of one (or of a name), or names part of exactly one theme.
791
+ */
792
+ export function resolveThemeId(input: string): ThemeLookup {
793
+ const key = themeKey(input);
794
+ const ids = getThemeIds();
795
+ const exact = ids.find((id) => id === key) ?? ids.find((id) => themeKey(themes[id]!.name) === key);
796
+ if (exact) return { ok: true, id: exact };
797
+ if (!key) return { ok: false, suggestion: null };
798
+
799
+ let best: { id: string; distance: number } | null = null;
800
+ for (const id of ids) {
801
+ for (const candidate of [id, themeKey(themes[id]!.name)]) {
802
+ const distance = editDistance(key, candidate);
803
+ if (!best || distance < best.distance) best = { id, distance };
804
+ }
805
+ }
806
+ const tolerance = Math.max(2, Math.floor(key.length / 3));
807
+ if (best && best.distance <= tolerance) return { ok: false, suggestion: best.id };
808
+ const partial = key.length >= 3
809
+ ? ids.filter((id) => id.includes(key) || themeKey(themes[id]!.name).includes(key))
810
+ : [];
811
+ return { ok: false, suggestion: partial.length === 1 ? partial[0]! : null };
812
+ }
@@ -9,6 +9,7 @@ import { assetClassMarketSymbol, parseAssetClassQuery } from "./asset-classes";
9
9
  import { searchContractKey, searchInstrumentKey } from "./identity";
10
10
  import { tickerInstrumentLabel } from "../instrument-label";
11
11
  import {
12
+ answersOnlyByKeyword,
12
13
  buildSymbolAliases,
13
14
  classifyInstrumentKind,
14
15
  findExactTickerSearchMatch,
@@ -178,6 +179,7 @@ function createProviderTickerSearchCandidates(
178
179
  instrumentClass: classifyInstrumentKind(result.brokerContract?.secType || result.type),
179
180
  instrumentType: result.brokerContract?.secType || result.type,
180
181
  searchAliases: buildSearchResultAliases(result),
182
+ ...(searchResultKeywords(result) ? { searchKeywords: searchResultKeywords(result) } : {}),
181
183
  result,
182
184
  }];
183
185
  });
@@ -251,7 +253,7 @@ export function buildTickerSearchCandidates({
251
253
  );
252
254
  const providerItems = createProviderTickerSearchCandidates(filteredProviderResults, tickers, candidateOptions);
253
255
  const ranked = rankTickerSearchItems([...localItems, ...providerItems], query);
254
- return limitTickerSearchCandidates(assignTickerSearchCategories(ranked), totalLimit, localLimit);
256
+ return limitTickerSearchCandidates(assignTickerSearchCategories(ranked, query), totalLimit, localLimit);
255
257
  }
256
258
 
257
259
  interface ResolveTickerSearchOptions {
@@ -589,10 +591,22 @@ function getProviderHintScore(result: InstrumentSearchResult, preferredExchange?
589
591
  return getProviderHintRichness(result) + (matchesPreferredExchange ? 10_000 : 0);
590
592
  }
591
593
 
592
- function assignTickerSearchCategories<T extends TickerSearchCandidate>(items: T[]): T[] {
594
+ /** Older servers send none; anything malformed is ignored. */
595
+ function searchResultKeywords(result: InstrumentSearchResult): string[] | undefined {
596
+ const keywords = Array.isArray(result.searchKeywords)
597
+ ? result.searchKeywords.filter((keyword): keyword is string => typeof keyword === "string" && !!keyword.trim())
598
+ : [];
599
+ return keywords.length > 0 ? keywords : undefined;
600
+ }
601
+
602
+ function assignTickerSearchCategories<T extends TickerSearchCandidate>(items: T[], query: string): T[] {
593
603
  let assignedPrimaryListing = false;
594
604
 
595
605
  return items.map((item) => {
606
+ // A company the theme's funds hold is not another listing of the one searched for.
607
+ if (!item.saved && item.kind === "search" && item.instrumentClass === "equity" && answersOnlyByKeyword(item, query)) {
608
+ return { ...item, category: "Related" };
609
+ }
596
610
  if (item.saved || item.kind === "ticker") {
597
611
  if (item.instrumentClass !== "fund" && item.instrumentClass !== "derivative") {
598
612
  assignedPrimaryListing = true;
@@ -669,5 +683,11 @@ function buildSearchResultAliases(result: InstrumentSearchResult): string[] {
669
683
  if (result.brokerContract?.symbol) {
670
684
  for (const alias of buildSymbolAliases(result.brokerContract.symbol)) aliases.add(alias);
671
685
  }
686
+ // Older servers send none. Anything else malformed is ignored.
687
+ if (Array.isArray(result.searchAliases)) {
688
+ for (const alias of result.searchAliases) {
689
+ if (typeof alias === "string" && alias.trim()) aliases.add(alias.trim());
690
+ }
691
+ }
672
692
  return [...aliases];
673
693
  }