gloomberb 0.16.2 → 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 (527) 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/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -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 })))));
@@ -5,9 +5,12 @@ import { useAppSelector, useOptionalPaneInstanceId } from "./app/context";
5
5
 
6
6
  type ScrollDirection = "up" | "down";
7
7
 
8
+ /** A scroll position: rows (or cells), or device pixels where the host measures in them. */
9
+ type ScrollTarget = { cells: number } | { pixels: number };
10
+
8
11
  interface PaneScrollAction {
9
12
  direction: ScrollDirection;
10
- resolveTarget(scrollBox: ScrollBoxRenderable): number | null;
13
+ resolveTarget(scrollBox: ScrollBoxRenderable): ScrollTarget | null;
11
14
  }
12
15
 
13
16
  interface PaneScrollEntry {
@@ -57,17 +60,58 @@ function isVisibleScrollBox(scrollBox: ScrollBoxRenderable): boolean {
57
60
  return !hasHiddenAncestor(scrollBox) && !hasCollapsedBounds(scrollBox);
58
61
  }
59
62
 
60
- function clampScrollTop(scrollBox: ScrollBoxRenderable, target: number): number | null {
63
+ function maxScrollTop(scrollBox: ScrollBoxRenderable): number {
61
64
  const viewportHeight = Math.max(0, scrollBox.viewport?.height ?? 0);
62
- if (viewportHeight <= 0) return null;
63
- const maxScrollTop = Math.max(0, scrollBox.scrollHeight - viewportHeight);
64
- if (maxScrollTop <= 0) return null;
65
- return Math.max(0, Math.min(maxScrollTop, target));
65
+ if (viewportHeight <= 0) return 0;
66
+ return Math.max(0, scrollBox.scrollHeight - viewportHeight);
67
+ }
68
+
69
+ /**
70
+ * The desktop host rounds its viewport and content to whole rows, which can
71
+ * leave up to half a row of content past the last scrollable row. Where it
72
+ * also reports pixels, the ends are measured in them.
73
+ */
74
+ function pixelRange(scrollBox: ScrollBoxRenderable): { top: number; max: number } | null {
75
+ const { scrollTopPx, scrollHeightPx, viewportPx } = scrollBox;
76
+ if (!scrollBox.scrollToPixels || typeof scrollTopPx !== "number" || typeof scrollHeightPx !== "number" || !viewportPx) {
77
+ return null;
78
+ }
79
+ return { top: scrollTopPx, max: Math.max(0, scrollHeightPx - viewportPx.height) };
66
80
  }
67
81
 
68
- function scrollTargetByDelta(scrollBox: ScrollBoxRenderable, delta: number): number | null {
69
- const target = clampScrollTop(scrollBox, scrollBox.scrollTop + delta);
70
- return target == null || target === scrollBox.scrollTop ? null : target;
82
+ function topTarget(scrollBox: ScrollBoxRenderable): ScrollTarget | null {
83
+ if ((scrollBox.viewport?.height ?? 0) <= 0) return null;
84
+ const pixels = pixelRange(scrollBox);
85
+ if (pixels) return pixels.top >= 1 ? { pixels: 0 } : null;
86
+ return scrollBox.scrollTop > 0 ? { cells: 0 } : null;
87
+ }
88
+
89
+ function bottomTarget(scrollBox: ScrollBoxRenderable): ScrollTarget | null {
90
+ const max = maxScrollTop(scrollBox);
91
+ // A body that overflows by less than a row is not worth capturing the keys for.
92
+ if (max <= 0) return null;
93
+ const pixels = pixelRange(scrollBox);
94
+ if (pixels) return pixels.max - pixels.top >= 1 ? { pixels: pixels.max } : null;
95
+ return max !== scrollBox.scrollTop ? { cells: max } : null;
96
+ }
97
+
98
+ function scrollTargetByDelta(scrollBox: ScrollBoxRenderable, delta: number): ScrollTarget | null {
99
+ if ((scrollBox.viewport?.height ?? 0) <= 0) return null;
100
+ const wanted = scrollBox.scrollTop + delta;
101
+ if (delta < 0 && wanted <= 0) return topTarget(scrollBox);
102
+ if (delta > 0 && wanted >= maxScrollTop(scrollBox)) return bottomTarget(scrollBox);
103
+ return delta === 0 ? null : { cells: wanted };
104
+ }
105
+
106
+ function applyScrollTarget(scrollBox: ScrollBoxRenderable, target: ScrollTarget): void {
107
+ if ("pixels" in target) {
108
+ scrollBox.scrollToPixels?.(target.pixels);
109
+ return;
110
+ }
111
+ scrollBox.scrollTo(target.cells);
112
+ if (scrollBox.scrollTop !== target.cells) {
113
+ scrollBox.scrollTop = target.cells;
114
+ }
71
115
  }
72
116
 
73
117
  /**
@@ -76,7 +120,7 @@ function scrollTargetByDelta(scrollBox: ScrollBoxRenderable, delta: number): num
76
120
  */
77
121
  export function scrollByLines(scrollBox: ScrollBoxRenderable, delta: number): void {
78
122
  const target = scrollTargetByDelta(scrollBox, delta);
79
- if (target != null) scrollBox.scrollTop = target;
123
+ if (target) applyScrollTarget(scrollBox, target);
80
124
  }
81
125
 
82
126
  function arrowScrollLines(scrollBox: ScrollBoxRenderable): number {
@@ -117,20 +161,9 @@ function resolveKeyScrollAction(event: PaneScrollKeyEvent): PaneScrollAction | n
117
161
  ),
118
162
  };
119
163
  case "home":
120
- return {
121
- direction: "up",
122
- resolveTarget: (scrollBox) => (
123
- scrollBox.scrollTop > 0 ? 0 : null
124
- ),
125
- };
164
+ return { direction: "up", resolveTarget: topTarget };
126
165
  case "end":
127
- return {
128
- direction: "down",
129
- resolveTarget: (scrollBox) => {
130
- const target = clampScrollTop(scrollBox, Number.MAX_SAFE_INTEGER);
131
- return target == null || target === scrollBox.scrollTop ? null : target;
132
- },
133
- };
166
+ return { direction: "down", resolveTarget: bottomTarget };
134
167
  default:
135
168
  return null;
136
169
  }
@@ -145,12 +178,12 @@ function getEntryScore(entry: PaneScrollEntry, scrollBox: ScrollBoxRenderable):
145
178
  function findScrollableEntry(paneId: string, action: PaneScrollAction): {
146
179
  entry: PaneScrollEntry;
147
180
  scrollBox: ScrollBoxRenderable;
148
- target: number;
181
+ target: ScrollTarget;
149
182
  } | null {
150
183
  const entries = paneScrollBoxes.get(paneId);
151
184
  if (!entries) return null;
152
185
 
153
- let best: { entry: PaneScrollEntry; scrollBox: ScrollBoxRenderable; target: number; score: number } | null = null;
186
+ let best: { entry: PaneScrollEntry; scrollBox: ScrollBoxRenderable; target: ScrollTarget; score: number } | null = null;
154
187
  for (const entry of entries.values()) {
155
188
  const scrollBox = entry.ref.current;
156
189
  if (!scrollBox) continue;
@@ -176,17 +209,16 @@ function scrollPaneByKey(paneId: string, event: PaneScrollKeyEvent): boolean {
176
209
  const match = findScrollableEntry(paneId, action);
177
210
  if (!match) return false;
178
211
 
179
- const previousTop = match.scrollBox.scrollTop;
180
- match.scrollBox.scrollTo(match.target);
181
- if (match.scrollBox.scrollTop !== match.target) {
182
- match.scrollBox.scrollTop = match.target;
183
- }
212
+ const { scrollBox } = match;
213
+ const position = () => scrollBox.scrollTopPx ?? scrollBox.scrollTop;
214
+ const previousTop = scrollBox.scrollTop;
215
+ const previousPosition = position();
216
+ applyScrollTarget(scrollBox, match.target);
184
217
 
185
- const delta = Math.abs(match.scrollBox.scrollTop - previousTop);
186
- if (delta > 0) {
187
- match.entry.onScrollActivity?.({ scroll: { direction: action.direction, delta } });
188
- }
189
- return delta > 0;
218
+ if (position() === previousPosition) return false;
219
+ const delta = Math.max(1, Math.abs(scrollBox.scrollTop - previousTop));
220
+ match.entry.onScrollActivity?.({ scroll: { direction: action.direction, delta } });
221
+ return true;
190
222
  }
191
223
 
192
224
  export function useForwardedScrollBoxRef<T>(
@@ -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
+ }
@@ -0,0 +1,108 @@
1
+ import type { Quote, TickerFinancials } from "../types/financials";
2
+ import type { Portfolio, TickerRecord } from "../types/ticker";
3
+ import type { BrokerAccount } from "../types/trading";
4
+ import { createTestTicker } from "./ticker";
5
+
6
+ /**
7
+ * An invented margin account: 1.0M of equity carrying 1.6M of positions, the
8
+ * difference borrowed in three currencies. It holds a US stock, a listing
9
+ * quoted in euros and an option, which no quote prices, so it stays at its
10
+ * broker mark. Every figure is made up.
11
+ */
12
+ export const MARGIN_PORTFOLIO: Portfolio = {
13
+ id: "broker:margin-test:M1",
14
+ name: "Margin test",
15
+ currency: "USD",
16
+ brokerId: "test",
17
+ brokerInstanceId: "margin-test",
18
+ brokerAccountId: "M1",
19
+ };
20
+
21
+ /** Dollars per unit of each currency. */
22
+ export const MARGIN_EXCHANGE_RATES = new Map([["USD", 1], ["EUR", 1.2], ["JPY", 0.008]]);
23
+
24
+ const OPTION = "CCC 280121C00050000";
25
+
26
+ function holding(symbol: string, details: {
27
+ exchange: string;
28
+ currency: string;
29
+ shares: number;
30
+ mark: number;
31
+ multiplier?: number;
32
+ assetCategory?: string;
33
+ }): TickerRecord {
34
+ return createTestTicker(symbol, symbol, {
35
+ exchange: details.exchange,
36
+ currency: details.currency,
37
+ assetCategory: details.assetCategory,
38
+ portfolios: [MARGIN_PORTFOLIO.id],
39
+ positions: [{
40
+ portfolio: MARGIN_PORTFOLIO.id,
41
+ shares: details.shares,
42
+ avgCost: details.mark,
43
+ markPrice: details.mark,
44
+ marketValue: details.shares * details.mark * (details.multiplier ?? 1),
45
+ unrealizedPnl: 0,
46
+ multiplier: details.multiplier,
47
+ currency: details.currency,
48
+ side: "long",
49
+ broker: "test",
50
+ brokerInstanceId: "margin-test",
51
+ brokerAccountId: "M1",
52
+ }],
53
+ });
54
+ }
55
+
56
+ export const MARGIN_TICKERS: TickerRecord[] = [
57
+ holding("AAA", { exchange: "NASDAQ", currency: "USD", shares: 4_000, mark: 200 }),
58
+ holding("BBB", { exchange: "XETRA", currency: "EUR", shares: 5_000, mark: 100 }),
59
+ holding(OPTION, { exchange: "", currency: "USD", shares: 20, mark: 100, multiplier: 100, assetCategory: "OPT" }),
60
+ ];
61
+
62
+ export function createMarginAccount(overrides: Partial<BrokerAccount> = {}): BrokerAccount {
63
+ return {
64
+ accountId: "M1",
65
+ name: "Margin test",
66
+ currency: "USD",
67
+ source: "cloud",
68
+ updatedAt: Date.now() - 60_000,
69
+ netLiquidation: 1_000_000,
70
+ grossPositionValue: 1_600_000,
71
+ totalCashValue: -600_000,
72
+ cashBalances: [
73
+ { currency: "EUR", quantity: -250_000, baseValue: -300_000, baseCurrency: "USD" },
74
+ { currency: "JPY", quantity: -25_000_000, baseValue: -200_000, baseCurrency: "USD" },
75
+ { currency: "USD", quantity: -100_000, baseValue: -100_000, baseCurrency: "USD" },
76
+ ],
77
+ ...overrides,
78
+ };
79
+ }
80
+
81
+ function liveQuote(symbol: string, price: number, previousClose: number, currency: string, exchange: string): TickerFinancials {
82
+ const quote: Quote = {
83
+ symbol,
84
+ price,
85
+ change: price - previousClose,
86
+ changePercent: (price / previousClose - 1) * 100,
87
+ previousClose,
88
+ currency,
89
+ lastUpdated: Date.now() - 1_000,
90
+ listingExchangeName: exchange,
91
+ marketState: "REGULAR",
92
+ dataSource: "live",
93
+ };
94
+ return { annualStatements: [], quarterlyStatements: [], priceHistory: [], quote };
95
+ }
96
+
97
+ /**
98
+ * Real-time quotes for the stock and the euro listing, the stock `move`
99
+ * dollars above its mark. With `option`, the option is quoted at its mark too,
100
+ * so every position has a live price.
101
+ */
102
+ export function marginQuotes({ move = 0, option = false }: { move?: number; option?: boolean } = {}): Map<string, TickerFinancials> {
103
+ return new Map([
104
+ ["AAA", liveQuote("AAA", 200 + move, 200, "USD", "NASDAQ")],
105
+ ["BBB", liveQuote("BBB", 100, 100, "EUR", "XETRA")],
106
+ ...(option ? [[OPTION, liveQuote(OPTION, 100, 100, "USD", "CBOE")] as const] : []),
107
+ ]);
108
+ }
@@ -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
+ }