gloomberb 0.11.0 → 0.11.2

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 (392) hide show
  1. package/README.md +38 -15
  2. package/README.zh-CN.md +12 -12
  3. package/package.json +10 -3
  4. package/src/api-client/auth.ts +14 -10
  5. package/src/api-client/data.ts +54 -0
  6. package/src/api-client/index.test.ts +124 -0
  7. package/src/api-client/index.ts +139 -4
  8. package/src/api-client/paths.ts +89 -0
  9. package/src/api-client/request.ts +15 -2
  10. package/src/api-client/socket-health.test.ts +1 -0
  11. package/src/api-client/socket.ts +2 -1
  12. package/src/api-client/types.ts +163 -0
  13. package/src/app/pane-runtime/index.tsx +4 -1
  14. package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +25 -4
  16. package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
  17. package/src/app/runtime/desktop-deeplink.test.ts +16 -0
  18. package/src/app/runtime/desktop-deeplink.ts +66 -2
  19. package/src/app/runtime/desktop-menu.ts +3 -0
  20. package/src/app/runtime/plugin-bindings.ts +30 -28
  21. package/src/app/runtime/update.ts +8 -6
  22. package/src/app.tsx +21 -7
  23. package/src/architecture/import-boundaries.test.ts +5 -1
  24. package/src/capabilities/factories.ts +4 -0
  25. package/src/cli/commands/automation.test.ts +9 -0
  26. package/src/cli/commands/automation.ts +22 -73
  27. package/src/cli/index.ts +10 -6
  28. package/src/cli/registry.ts +1 -1
  29. package/src/components/chart/composite/composite-chart.test.tsx +45 -2
  30. package/src/components/chart/composite/composite-chart.tsx +16 -12
  31. package/src/components/chart/composite/format.test.ts +34 -0
  32. package/src/components/chart/composite/format.ts +33 -10
  33. package/src/components/chart/composite/scene.ts +0 -21
  34. package/src/components/chart/composite/tools.ts +49 -0
  35. package/src/components/chart/core/axis-format.ts +13 -3
  36. package/src/components/chart/core/index.test.ts +7 -0
  37. package/src/components/chart/core/renderer.ts +3 -4
  38. package/src/components/command-bar/commands/direct/index.ts +4 -0
  39. package/src/components/command-bar/commands/registry.ts +8 -1
  40. package/src/components/command-bar/layout-items/current-layout.ts +1 -1
  41. package/src/components/command-bar/pane-templates/workflow.ts +4 -0
  42. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
  43. package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
  44. package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
  45. package/src/components/command-bar/surface/index.test.tsx +6 -6
  46. package/src/components/command-bar/surface/index.tsx +4 -1
  47. package/src/components/command-bar/surface/pane.test.tsx +66 -2
  48. package/src/components/command-bar/view-model.test.ts +2 -1
  49. package/src/components/command-bar/workflow/ops.test.ts +54 -0
  50. package/src/components/command-bar/workflow/ops.ts +1 -1
  51. package/src/components/company-logo.test.ts +19 -0
  52. package/src/components/company-logo.tsx +60 -0
  53. package/src/components/feed-data-table/stack-view.tsx +7 -1
  54. package/src/components/index.ts +1 -0
  55. package/src/components/layout/detached-pane-shell.tsx +85 -15
  56. package/src/components/layout/header.tsx +5 -5
  57. package/src/components/layout/pane/title.ts +31 -3
  58. package/src/components/layout/shell/active-drag.ts +4 -11
  59. package/src/components/layout/shell/drag/index.ts +4 -20
  60. package/src/components/layout/shell/drag/runtime.ts +3 -4
  61. package/src/components/layout/shell/index.test.tsx +17 -3
  62. package/src/components/layout/shell/index.tsx +89 -31
  63. package/src/components/layout/shell/menu.test.ts +37 -1
  64. package/src/components/layout/shell/menu.ts +14 -0
  65. package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
  66. package/src/components/layout/shell/pane/actions.ts +0 -5
  67. package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
  68. package/src/components/layout/shell/shortcuts.ts +6 -3
  69. package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
  70. package/src/components/layout/shell/ticker-link-menu.ts +78 -0
  71. package/src/components/layout/status-bar.test.tsx +62 -61
  72. package/src/components/layout/status-bar.tsx +53 -75
  73. package/src/components/onboarding/account-step/form-panel.tsx +4 -2
  74. package/src/components/onboarding/account-step/index.tsx +1 -1
  75. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  76. package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
  77. package/src/components/onboarding/onboarding-wizard.tsx +13 -5
  78. package/src/components/onboarding/wizard-account.ts +4 -23
  79. package/src/components/table-view-shared.test.ts +11 -0
  80. package/src/components/table-view-shared.tsx +26 -0
  81. package/src/components/ui/data-table/index.tsx +19 -1
  82. package/src/components/ui/fields.tsx +5 -1
  83. package/src/core/state/app/layout-reducer.ts +36 -0
  84. package/src/core/state/app/layout.ts +10 -5
  85. package/src/core/state/app/state.ts +0 -13
  86. package/src/core/state/app/types.ts +6 -4
  87. package/src/data/config/chart-settings.ts +1 -1
  88. package/src/data/config/layout.ts +2 -0
  89. package/src/data/config/store/index.test.ts +27 -6
  90. package/src/data/config/store/normalize.ts +3 -5
  91. package/src/data/plugin-state-store.ts +11 -0
  92. package/src/data/sqlite/cache.test.ts +10 -0
  93. package/src/i18n/es.ts +13 -5
  94. package/src/i18n/ja.ts +13 -5
  95. package/src/i18n/ko.ts +13 -5
  96. package/src/i18n/zh-cn.ts +13 -5
  97. package/src/i18n/zh-tw.ts +13 -5
  98. package/src/layout-marketplace/api.test.ts +54 -0
  99. package/src/layout-marketplace/api.ts +65 -0
  100. package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
  101. package/src/layout-marketplace/gallery-desktop.tsx +375 -0
  102. package/src/layout-marketplace/gallery-terminal.tsx +376 -0
  103. package/src/layout-marketplace/gallery.test.tsx +84 -0
  104. package/src/layout-marketplace/gallery.tsx +323 -0
  105. package/src/layout-marketplace/geometry.test.ts +47 -0
  106. package/src/layout-marketplace/geometry.ts +80 -0
  107. package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
  108. package/src/layout-marketplace/mini-workspace.tsx +428 -0
  109. package/src/layout-marketplace/model.test.ts +118 -0
  110. package/src/layout-marketplace/model.ts +171 -0
  111. package/src/layout-marketplace/name-dialog.tsx +60 -0
  112. package/src/layout-marketplace/pane-imagery.ts +85 -0
  113. package/src/layout-marketplace/pane.tsx +20 -0
  114. package/src/layout-marketplace/payload.test.ts +246 -0
  115. package/src/layout-marketplace/payload.ts +624 -0
  116. package/src/layout-marketplace/use-marketplace.ts +63 -0
  117. package/src/market-data/coordinator/index.test.ts +2 -2
  118. package/src/market-data/coordinator/index.ts +1 -1
  119. package/src/market-data/hooks.tsx +0 -41
  120. package/src/market-data/market/format.test.ts +7 -0
  121. package/src/market-data/market/freshness.ts +5 -2
  122. package/src/market-data/quotes/freshness.test.ts +16 -0
  123. package/src/news/aggregator.test.ts +92 -3
  124. package/src/news/aggregator.ts +98 -18
  125. package/src/news/hooks.test.tsx +2 -0
  126. package/src/news/hooks.ts +27 -1
  127. package/src/news/news-model.ts +5 -2
  128. package/src/news/types.ts +7 -0
  129. package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
  130. package/src/plugins/broker-sync/normalize.ts +42 -5
  131. package/src/plugins/builtin/account-management/pane.tsx +5 -5
  132. package/src/plugins/builtin/ai/index.tsx +18 -2
  133. package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
  134. package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
  135. package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
  136. package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
  137. package/src/plugins/builtin/alerts/index.test.tsx +49 -6
  138. package/src/plugins/builtin/alerts/index.tsx +11 -6
  139. package/src/plugins/builtin/alerts/pane.tsx +51 -9
  140. package/src/plugins/builtin/alerts/quotes.ts +11 -3
  141. package/src/plugins/builtin/alerts/types.ts +1 -0
  142. package/src/plugins/builtin/analytics/index.tsx +11 -1
  143. package/src/plugins/builtin/analytics/pane-model.ts +6 -4
  144. package/src/plugins/builtin/broker-manager/index.tsx +3 -0
  145. package/src/plugins/builtin/buildout/model/index.ts +1 -1
  146. package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
  147. package/src/plugins/builtin/cds/client.test.ts +83 -0
  148. package/src/plugins/builtin/cds/client.ts +67 -0
  149. package/src/plugins/builtin/cds/index.tsx +41 -0
  150. package/src/plugins/builtin/cds/model.test.ts +232 -0
  151. package/src/plugins/builtin/cds/model.ts +401 -0
  152. package/src/plugins/builtin/cds/pane.test.tsx +132 -0
  153. package/src/plugins/builtin/cds/pane.tsx +358 -0
  154. package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
  155. package/src/plugins/builtin/chart-composer/index.tsx +88 -2
  156. package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
  157. package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
  158. package/src/plugins/builtin/chat/content/index.tsx +4 -1
  159. package/src/plugins/builtin/chat/controller/channels.ts +7 -1
  160. package/src/plugins/builtin/chat/controller/index.ts +14 -9
  161. package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
  162. package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
  163. package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
  164. package/src/plugins/builtin/chat/controller/send.ts +1 -3
  165. package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
  166. package/src/plugins/builtin/chat/controller/state.ts +1 -3
  167. package/src/plugins/builtin/chat/controller/storage.ts +0 -2
  168. package/src/plugins/builtin/chat/controller/utils.ts +4 -6
  169. package/src/plugins/builtin/chat/controller/view.ts +2 -2
  170. package/src/plugins/builtin/chat/controller.test.ts +142 -14
  171. package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
  172. package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
  173. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
  174. package/src/plugins/builtin/chat/profile-popover.ts +53 -22
  175. package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
  176. package/src/plugins/builtin/chat/sidebar.tsx +63 -0
  177. package/src/plugins/builtin/chat/test-harness.tsx +29 -1
  178. package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
  179. package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
  180. package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
  181. package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
  182. package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
  183. package/src/plugins/builtin/cloud/browser.tsx +9 -0
  184. package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
  185. package/src/plugins/builtin/cloud/device-signin.ts +2 -8
  186. package/src/plugins/builtin/cloud/index.tsx +2 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +14 -24
  188. package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
  189. package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
  190. package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
  191. package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
  192. package/src/plugins/builtin/composite-plugins.ts +3 -1
  193. package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
  194. package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
  195. package/src/plugins/builtin/congress-trades/model.ts +69 -1
  196. package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
  197. package/src/plugins/builtin/connections/index.ts +3 -0
  198. package/src/plugins/builtin/dividend-yield/client.ts +18 -0
  199. package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
  200. package/src/plugins/builtin/earnings/index.tsx +5 -3
  201. package/src/plugins/builtin/earnings/model.test.ts +19 -1
  202. package/src/plugins/builtin/earnings/model.ts +17 -15
  203. package/src/plugins/builtin/econ/calendar-model.ts +0 -5
  204. package/src/plugins/builtin/econ/index.test.tsx +6 -1
  205. package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
  206. package/src/plugins/builtin/help/index.tsx +2 -2
  207. package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
  208. package/src/plugins/builtin/insider/index.tsx +27 -11
  209. package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
  210. package/src/plugins/builtin/layout-manager/index.tsx +17 -4
  211. package/src/plugins/builtin/news/index.tsx +11 -6
  212. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
  213. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
  214. package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
  215. package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
  216. package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
  217. package/src/plugins/builtin/news/wire/index.tsx +3 -3
  218. package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
  219. package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
  220. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
  221. package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
  222. package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
  223. package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
  224. package/src/plugins/builtin/notes/files.ts +80 -0
  225. package/src/plugins/builtin/notes/index.tsx +3 -0
  226. package/src/plugins/builtin/notes/sync.test.ts +49 -0
  227. package/src/plugins/builtin/notes/sync.ts +98 -0
  228. package/src/plugins/builtin/options/index.tsx +1 -0
  229. package/src/plugins/builtin/options/view.test.tsx +25 -3
  230. package/src/plugins/builtin/options/view.tsx +1 -0
  231. package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
  232. package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
  233. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  234. package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
  235. package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
  236. package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
  237. package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
  238. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
  239. package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
  240. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
  241. package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
  242. package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
  243. package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
  244. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
  245. package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
  246. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
  247. package/src/plugins/builtin/research/index.test.ts +14 -0
  248. package/src/plugins/builtin/research/index.tsx +29 -0
  249. package/src/plugins/builtin/sec/index.tsx +24 -9
  250. package/src/plugins/builtin/sectors/index.tsx +14 -0
  251. package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
  252. package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
  253. package/src/plugins/builtin/shared/public-pane.ts +9 -0
  254. package/src/plugins/builtin/shared/public-share.ts +23 -0
  255. package/src/plugins/builtin/shared/ticker-source.ts +32 -0
  256. package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
  257. package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
  258. package/src/plugins/builtin/substack/api/cache.ts +7 -3
  259. package/src/plugins/builtin/substack/api/loaders.ts +36 -12
  260. package/src/plugins/builtin/substack/api/types.ts +2 -0
  261. package/src/plugins/builtin/substack/pane-state.ts +1 -1
  262. package/src/plugins/builtin/substack/pane.tsx +55 -12
  263. package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
  264. package/src/plugins/builtin/thirteenf/api.ts +40 -18
  265. package/src/plugins/builtin/thirteenf/data.ts +15 -7
  266. package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
  267. package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
  268. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
  269. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
  270. package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
  271. package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
  272. package/src/plugins/builtin/world-indices/index.tsx +2 -0
  273. package/src/plugins/catalog-browser.test.ts +78 -0
  274. package/src/plugins/catalog-browser.ts +136 -0
  275. package/src/plugins/gridlock-notification.ts +2 -1
  276. package/src/plugins/ibkr/flex/index.test.ts +19 -0
  277. package/src/plugins/ibkr/flex/index.ts +1 -1
  278. package/src/plugins/pane-manager/gridlock.ts +87 -3
  279. package/src/plugins/pane-manager.test.ts +30 -0
  280. package/src/plugins/pane-manager.ts +6 -1
  281. package/src/plugins/prediction-markets/chart.tsx +2 -2
  282. package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
  283. package/src/plugins/prediction-markets/controller/data.ts +7 -1
  284. package/src/plugins/prediction-markets/controller/index.ts +9 -2
  285. package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
  286. package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
  287. package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
  288. package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
  289. package/src/plugins/prediction-markets/detail.test.tsx +29 -2
  290. package/src/plugins/prediction-markets/index.tsx +8 -4
  291. package/src/plugins/prediction-markets/navigation.ts +0 -1
  292. package/src/plugins/prediction-markets/pane.test.tsx +77 -0
  293. package/src/plugins/prediction-markets/pane.tsx +12 -0
  294. package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
  295. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
  296. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
  297. package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
  298. package/src/plugins/registry/index.ts +2 -0
  299. package/src/plugins/ticker-navigation.test.ts +24 -0
  300. package/src/plugins/ticker-navigation.ts +40 -0
  301. package/src/remote/schema.ts +1 -1
  302. package/src/renderers/browser/app-services.ts +61 -0
  303. package/src/renderers/browser/cloud-transport.test.ts +68 -0
  304. package/src/renderers/browser/cloud-transport.ts +48 -0
  305. package/src/renderers/browser/config-host.ts +58 -0
  306. package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
  307. package/src/renderers/browser/deeplink-bridge.ts +22 -0
  308. package/src/renderers/browser/error-boundary.tsx +23 -0
  309. package/src/renderers/browser/main.tsx +64 -0
  310. package/src/renderers/browser/persistence.ts +74 -0
  311. package/src/renderers/browser/storage.test.ts +66 -0
  312. package/src/renderers/browser/storage.ts +50 -0
  313. package/src/renderers/browser/ticker-repository.ts +59 -0
  314. package/src/renderers/browser/ui-host.tsx +40 -0
  315. package/src/renderers/cloudflare/worker.test.ts +136 -0
  316. package/src/renderers/cloudflare/worker.ts +116 -0
  317. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  318. package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
  319. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
  320. package/src/renderers/electrobun/view/app-services.ts +14 -3
  321. package/src/renderers/electrobun/view/build-assets.ts +12 -10
  322. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  323. package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
  324. package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
  325. package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
  326. package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
  327. package/src/renderers/electrobun/view/host/box.tsx +11 -6
  328. package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
  329. package/src/renderers/electrobun/view/host/input.tsx +11 -7
  330. package/src/renderers/electrobun/view/host/native.ts +0 -5
  331. package/src/renderers/electrobun/view/main.tsx +2 -0
  332. package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
  333. package/src/renderers/electrobun/view/styles.css +107 -7
  334. package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
  335. package/src/renderers/electrobun/view/toast-host.tsx +69 -43
  336. package/src/renderers/electrobun/view/ui-host.tsx +6 -137
  337. package/src/renderers/opentui/start.tsx +2 -0
  338. package/src/renderers/share/layout-view.test.tsx +58 -0
  339. package/src/renderers/share/layout-view.tsx +133 -0
  340. package/src/renderers/share/main.tsx +102 -0
  341. package/src/renderers/share/styles.css +54 -0
  342. package/src/renderers/share/view.test.tsx +100 -0
  343. package/src/renderers/share/view.tsx +150 -0
  344. package/src/shares/api.test.ts +105 -0
  345. package/src/shares/api.ts +127 -0
  346. package/src/shares/chart-snapshot.test.ts +34 -0
  347. package/src/shares/chart-snapshot.ts +28 -0
  348. package/src/shares/location.test.ts +19 -0
  349. package/src/shares/location.ts +11 -0
  350. package/src/shares/pane.test.ts +110 -0
  351. package/src/shares/pane.ts +54 -0
  352. package/src/shares/payload.ts +177 -0
  353. package/src/sources/gloomberb-cloud/index.test.ts +61 -1
  354. package/src/sources/gloomberb-cloud/index.ts +86 -14
  355. package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
  356. package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
  357. package/src/sources/provider-router/history.test.ts +2 -2
  358. package/src/sources/provider-router/news.test.ts +34 -0
  359. package/src/sources/provider-router/news.ts +15 -1
  360. package/src/sources/sec-edgar.test.ts +59 -0
  361. package/src/sources/sec-edgar.ts +63 -23
  362. package/src/sources/yahoo-finance/mappers.ts +3 -0
  363. package/src/sources/yahoo-finance/snapshots.ts +10 -2
  364. package/src/sources/yahoo-finance/symbols.test.ts +10 -0
  365. package/src/sources/yahoo-finance/symbols.ts +13 -8
  366. package/src/sources/yahoo-finance.test.ts +1 -1
  367. package/src/sources/yahoo-finance.ts +6 -2
  368. package/src/state/app/bootstrap.ts +6 -7
  369. package/src/state/app/context/index.test.ts +41 -1
  370. package/src/state/app/context/index.test.tsx +34 -16
  371. package/src/state/selectors-ui.ts +0 -8
  372. package/src/sync/baseline.ts +32 -0
  373. package/src/sync/controller.test.ts +124 -1
  374. package/src/sync/controller.ts +54 -12
  375. package/src/sync/core-contributors.test.ts +44 -0
  376. package/src/sync/core-contributors.ts +59 -10
  377. package/src/sync/react.ts +22 -2
  378. package/src/sync/types.ts +12 -0
  379. package/src/theme/font-scale.ts +0 -11
  380. package/src/tickers/search/index.test.ts +27 -0
  381. package/src/tickers/search/upsert.ts +10 -0
  382. package/src/types/capability-route-source.ts +2 -1
  383. package/src/types/config.ts +35 -5
  384. package/src/types/desktop-menu.ts +1 -0
  385. package/src/types/plugin.ts +36 -0
  386. package/src/ui/context-menu.tsx +1 -1
  387. package/src/ui/host.tsx +4 -0
  388. package/src/ui/toast.tsx +2 -0
  389. package/src/utils/back-navigation.ts +0 -4
  390. package/src/utils/price-history.ts +11 -10
  391. package/src/utils/sec.test.ts +6 -0
  392. package/src/utils/sec.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  listThirteenFFormHoldings,
3
+ listThirteenFFormHoldingsPage,
3
4
  listThirteenFForms,
4
5
  listThirteenFFilings,
5
6
  listTopThirteenFFunds,
@@ -81,8 +82,17 @@ export async function loadBrowserRows(
81
82
  const ticker = query.trim().toUpperCase();
82
83
  if (!ticker) return { rows: [], quarter };
83
84
  const tickers = await lookupThirteenFTickers([ticker], signal, apiOptions);
84
- const cusip = tickers[0]?.cusip;
85
- if (!cusip) return { rows: [], quarter, warning: `No CUSIP found for ${ticker}` };
85
+ const exact = tickers.filter((item) => item.ticker === ticker);
86
+ const candidates = exact.length > 0 ? exact : tickers;
87
+ const cusips = [...new Set(candidates.map((item) => item.cusip).filter(Boolean))];
88
+ const cusip = cusips.length === 1 ? cusips[0] : undefined;
89
+ if (!cusip) {
90
+ return {
91
+ rows: [],
92
+ quarter,
93
+ warning: cusips.length === 0 ? `No CUSIP found for ${ticker}` : `Ambiguous CUSIP for ${ticker}`,
94
+ };
95
+ }
86
96
  const periodOfReport = quarterToPeriod(quarter);
87
97
  try {
88
98
  const holders = await lookupThirteenFHoldersByCusip(cusip, periodOfReport, signal, apiOptions);
@@ -186,11 +196,9 @@ export async function loadFilingPositions(
186
196
  cik: string,
187
197
  accessionNumber: string,
188
198
  signal?: AbortSignal,
189
- options: { forceRefresh?: boolean } = {},
190
- ): Promise<ThirteenFHoldingRecord[]> {
191
- return listThirteenFFormHoldings(cik, accessionNumber, signal, {
192
- forceRefresh: options.forceRefresh,
193
- });
199
+ options: { forceRefresh?: boolean; offset?: number; limit?: number } = {},
200
+ ): Promise<{ rows: ThirteenFHoldingRecord[]; hasMore: boolean }> {
201
+ return listThirteenFFormHoldingsPage(cik, accessionNumber, signal, options);
194
202
  }
195
203
 
196
204
  function quarterToPeriod(quarter: string): string {
@@ -10,6 +10,7 @@ import {
10
10
  DataTableStackView,
11
11
  DataTableView,
12
12
  EmptyState,
13
+ useTableLoadMore,
13
14
  InputSearchBar,
14
15
  Spinner,
15
16
  Tabs,
@@ -238,13 +239,7 @@ export function ThirteenFPane({ focused, width, height }: PaneProps) {
238
239
  setSelectedId(sortedRows[0]?.id ?? null);
239
240
  }, [selectedId, setSelectedId, sortedRows]);
240
241
 
241
- const loadMoreFromScroll = useCallback(() => {
242
- const scrollBox = tableScrollRef.current;
243
- if (!scrollBox?.viewport) return;
244
- const viewportHeight = Math.max(1, scrollBox.viewport.height);
245
- if (scrollBox.scrollTop + viewportHeight < scrollBox.scrollHeight - LOAD_MORE_THRESHOLD) return;
246
- loadMore();
247
- }, [loadMore]);
242
+ const loadMoreFromScroll = useTableLoadMore(tableScrollRef, hasMore && !loadingMore && status === "loaded", loadMore, LOAD_MORE_THRESHOLD);
248
243
 
249
244
  const refresh = useCallback(() => load(true), [load]);
250
245
  const focusSearch = useCallback(() => {
@@ -672,19 +667,29 @@ function FilingDetailView({
672
667
  const [holdings, setHoldings] = useState<ThirteenFHoldingRecord[]>([]);
673
668
  const [status, setStatus] = useState<LoadStatus>("loading");
674
669
  const [error, setError] = useState<string | null>(null);
670
+ const [hasMore, setHasMore] = useState(false);
671
+ const [nextOffset, setNextOffset] = useState(0);
672
+ const [loadingMore, setLoadingMore] = useState(false);
675
673
  const abortRef = useRef<AbortController | null>(null);
674
+ const moreAbortRef = useRef<AbortController | null>(null);
675
+ const positionScrollRef = useRef<ScrollBoxRenderable | null>(null);
676
676
 
677
677
  const load = useCallback((refresh = false) => {
678
678
  abortRef.current?.abort();
679
+ moreAbortRef.current?.abort();
679
680
  const controller = new AbortController();
680
681
  abortRef.current = controller;
681
682
  setStatus("loading");
682
683
  setError(null);
683
684
  setHoldings([]);
684
- void loadFilingPositions(filing.cik, filing.accessionNumber, controller.signal, { forceRefresh: refresh })
685
- .then((nextHoldings) => {
685
+ setHasMore(false);
686
+ setNextOffset(0);
687
+ void loadFilingPositions(filing.cik, filing.accessionNumber, controller.signal, { forceRefresh: refresh, offset: 0 })
688
+ .then((result) => {
686
689
  if (abortRef.current !== controller) return;
687
- setHoldings(nextHoldings);
690
+ setHoldings(result.rows);
691
+ setHasMore(result.hasMore);
692
+ setNextOffset(result.rows.length);
688
693
  setStatus("loaded");
689
694
  })
690
695
  .catch((loadError) => {
@@ -695,11 +700,39 @@ function FilingDetailView({
695
700
  });
696
701
  }, [filing.accessionNumber, filing.cik]);
697
702
 
703
+ const loadMore = useCallback(() => {
704
+ if (loadingMore || !hasMore || status !== "loaded") return;
705
+ moreAbortRef.current?.abort();
706
+ const controller = new AbortController();
707
+ moreAbortRef.current = controller;
708
+ setLoadingMore(true);
709
+ void loadFilingPositions(filing.cik, filing.accessionNumber, controller.signal, { offset: nextOffset })
710
+ .then((result) => {
711
+ if (moreAbortRef.current !== controller) return;
712
+ setHoldings((current) => [...current, ...result.rows]);
713
+ setHasMore(result.hasMore);
714
+ setNextOffset(nextOffset + result.rows.length);
715
+ })
716
+ .catch((loadError) => {
717
+ if (moreAbortRef.current !== controller) return;
718
+ if (loadError instanceof Error && loadError.name === "AbortError") return;
719
+ setError(loadError instanceof Error ? loadError.message : String(loadError));
720
+ })
721
+ .finally(() => {
722
+ if (moreAbortRef.current !== controller) return;
723
+ setLoadingMore(false);
724
+ });
725
+ }, [filing.accessionNumber, filing.cik, hasMore, loadingMore, nextOffset, status]);
726
+
727
+ const onBodyScrollActivity = useTableLoadMore(positionScrollRef, hasMore && !loadingMore && status === "loaded", loadMore);
728
+
698
729
  useEffect(() => {
699
730
  load(false);
700
731
  return () => {
701
732
  abortRef.current?.abort();
702
733
  abortRef.current = null;
734
+ moreAbortRef.current?.abort();
735
+ moreAbortRef.current = null;
703
736
  };
704
737
  }, [load]);
705
738
 
@@ -769,6 +802,8 @@ function FilingDetailView({
769
802
  onRootKeyDown={handlePositionKeyDown}
770
803
  rootWidth={width}
771
804
  rootBefore={summary}
805
+ scrollRef={positionScrollRef}
806
+ onBodyScrollActivity={onBodyScrollActivity}
772
807
  resetScrollKey={filing.accessionNumber}
773
808
  columns={columns}
774
809
  items={positionRows}
@@ -100,6 +100,14 @@ export const tickerDetailModule: PluginModule = {
100
100
  }
101
101
  : {}
102
102
  ),
103
+ publicShare: {
104
+ serialize: ({ pane }) => pane.binding?.kind === "fixed" && pane.binding.symbol.trim()
105
+ ? { title: pane.title?.trim() || pane.binding.symbol, data: { symbol: pane.binding.symbol.trim().toUpperCase() } }
106
+ : null,
107
+ restore: (data) => Object.keys(data).length === 1 && typeof data.symbol === "string" && data.symbol.trim()
108
+ ? { symbol: data.symbol.trim().toUpperCase() }
109
+ : null,
110
+ },
103
111
  },
104
112
  {
105
113
  id: "quote-monitor-pane",
@@ -147,6 +155,7 @@ export const tickerDetailModule: PluginModule = {
147
155
  description: "Open a historical OHLCV table for a ticker.",
148
156
  keywords: ["historical", "prices", "hp", "ohlc", "volume"],
149
157
  shortcut: "HP",
158
+ publicShare: true,
150
159
  }),
151
160
  createProviderSearchPaneTemplate(),
152
161
  createTickerSurfacePaneTemplate({
@@ -156,6 +165,7 @@ export const tickerDetailModule: PluginModule = {
156
165
  description: "Open financial statements for a ticker.",
157
166
  keywords: ["fa", "financial", "analysis", "statements"],
158
167
  shortcut: "FA",
168
+ publicShare: true,
159
169
  titlePrefix: "FA",
160
170
  }),
161
171
  ],
@@ -14,6 +14,7 @@ import {
14
14
  } from "../../../market-data/market/status";
15
15
  import { selectEffectiveExchangeRates } from "../../../utils/exchange-rate-map";
16
16
  import { EmptyState } from "../../../components";
17
+ import { CompanyLogo } from "../../../components/company-logo";
17
18
  import {
18
19
  CompositeChart,
19
20
  pricePointsToResolvedSeries,
@@ -48,7 +49,7 @@ export function OverviewTab({
48
49
  const baseCurrency = useAppSelector((state) => state.config.baseCurrency);
49
50
  const exchangeRatesState = useAppSelector((state) => state.exchangeRates);
50
51
  const { width: termWidth } = useViewport();
51
- const { fractionalViewport = false } = useUiCapabilities();
52
+ const { fractionalViewport = false, nativePaneChrome } = useUiCapabilities();
52
53
 
53
54
  if (!ticker) return <EmptyState title={t("No ticker selected.")} />;
54
55
 
@@ -136,14 +137,20 @@ export function OverviewTab({
136
137
 
137
138
  return (
138
139
  <ScrollBox flexGrow={1} flexBasis={0} scrollY focusable={false}>
139
- <Box flexDirection="column" paddingX={1} paddingBottom={1} gap={1}>
140
+ <Box flexDirection="column" paddingX={1} paddingTop={nativePaneChrome ? 1 : 0} paddingBottom={1} gap={1}>
140
141
  <Box flexDirection={quoteBookInline ? "row" : "column"} gap={quoteBookInline ? 2 : 0} width={contentWidth}>
141
- <Box flexDirection="column" width={quoteSummaryWidth}>
142
+ <Box flexDirection="row" width={quoteSummaryWidth}>
143
+ <CompanyLogo
144
+ symbol={ticker.metadata.ticker}
145
+ assetCategory={ticker.metadata.assetCategory}
146
+ name={ticker.metadata.name || quote?.name}
147
+ />
148
+ <Box flexDirection="column" flexGrow={1} flexShrink={1} minWidth={0}>
142
149
  <Box flexDirection="row">
143
150
  <Text attributes={TextAttributes.BOLD} fg={colors.textBright}>
144
151
  {ticker.metadata.ticker}
145
152
  </Text>
146
- {ticker.metadata.name && ticker.metadata.name !== ticker.metadata.ticker && (
153
+ {(ticker.metadata.name || quote?.name) && (ticker.metadata.name || quote?.name) !== ticker.metadata.ticker && (
147
154
  <Text fg={colors.textDim}>
148
155
  {" "}- {ticker.metadata.name || quote?.name || ""}
149
156
  </Text>
@@ -193,6 +200,7 @@ export function OverviewTab({
193
200
  {metadataParts.length > 0 && (
194
201
  <Text fg={colors.textDim}>{metadataParts.join(" | ")}</Text>
195
202
  )}
203
+ </Box>
196
204
  </Box>
197
205
 
198
206
  {quote && hasBidAsk && (
@@ -82,7 +82,8 @@ export function QuoteMonitorCard({
82
82
  const flashDirection = useQuoteFlashDirection(flashFinancials, valueFlashingEnabled);
83
83
  const display = getActiveQuoteDisplay(quote);
84
84
  const quoteStatus = resolveQuoteStatus(quoteEntry, symbol);
85
- const changeColor = priceColor(display?.change ?? 0);
85
+ const quoteFailed = quoteStatus.failed && !!display;
86
+ const changeColor = quoteFailed ? colors.textDim : priceColor(display?.change ?? 0);
86
87
  const priceAttributes = flashDirection ? TextAttributes.DIM : TextAttributes.BOLD;
87
88
  const changeAttributes = flashDirection ? TextAttributes.DIM : TextAttributes.NONE;
88
89
  const currency = quote?.currency ?? ticker?.metadata.currency ?? "USD";
@@ -192,7 +193,12 @@ export function QuoteMonitorCard({
192
193
  <Text attributes={TextAttributes.BOLD} fg={colors.textBright} style={desktopSymbolStyle}>
193
194
  {symbol}
194
195
  </Text>
195
- {ticker?.metadata.name && (
196
+ {quoteFailed && (
197
+ <Text fg={colors.negative} style={{ fontSize: "12px", lineHeight: "14px" }}>
198
+ {quoteStatus.text}
199
+ </Text>
200
+ )}
201
+ {!quoteFailed && ticker?.metadata.name && (
196
202
  <Text
197
203
  fg={colors.textDim}
198
204
  style={{
@@ -73,6 +73,18 @@ export function buildTickerResearchSettingsDef(settings: TickerResearchPaneSetti
73
73
  options: tabs.map((tab) => ({ value: tab.id, label: tab.name })),
74
74
  }]
75
75
  : []),
76
+ {
77
+ key: "chainRefreshMinutes",
78
+ label: "Options chain refresh",
79
+ description: "How often the Options tab refetches the whole chain snapshot.",
80
+ type: "select" as const,
81
+ options: [
82
+ { value: "1", label: "Every minute" },
83
+ { value: "5", label: "Every 5 minutes" },
84
+ { value: "10", label: "Every 10 minutes" },
85
+ { value: "30", label: "Every 30 minutes" },
86
+ ],
87
+ },
76
88
  ],
77
89
  };
78
90
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from "./cache";
8
8
  import { TreasuryAuctionsPane } from "./pane";
9
9
  import { TREASURY_AUCTIONS_PANE_ID } from "./types";
10
+ import { createPublicPaneShare } from "../shared/public-pane";
10
11
 
11
12
  let disposeConnection: (() => void) | null = null;
12
13
 
@@ -85,6 +86,7 @@ export const treasuryAuctionsModule: PluginModule = {
85
86
  ],
86
87
  shortcut: { prefix: "AUCT" },
87
88
  createInstance: () => ({ placement: "floating" }),
89
+ publicShare: createPublicPaneShare("Treasury Auctions"),
88
90
  },
89
91
  ],
90
92
  };
@@ -8,6 +8,7 @@ import { useAssetData, usePluginTickerActions } from "../../runtime";
8
8
  import { useQuoteBoard } from "../shared/use-quote-board";
9
9
  import { WORLD_INDICES, REGION_LABELS, getIndicesByRegion, resolveIndexEntries } from "./indices";
10
10
  import { useWorldIndicesFooter } from "./footer";
11
+ import { createPublicPaneShare } from "../shared/public-pane";
11
12
  import {
12
13
  buildFlatRows,
13
14
  DEFAULT_SORT_PREFERENCE,
@@ -158,6 +159,7 @@ export const worldIndicesModule: PluginModule = {
158
159
  description: "Monitor global equity indices grouped by region.",
159
160
  keywords: ["world", "indices", "global", "equity", "markets", "international"],
160
161
  shortcut: { prefix: "WEI" },
162
+ publicShare: createPublicPaneShare("World Equity Indices"),
161
163
  },
162
164
  ],
163
165
  };
@@ -0,0 +1,78 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { browserBuiltinPlugins } from "./catalog-browser";
3
+ import { getLoadablePlugins } from "./catalog";
4
+
5
+ const ids = browserBuiltinPlugins.map((plugin) => plugin.id);
6
+ const paneIds = browserBuiltinPlugins.flatMap((plugin) => plugin.panes?.map((pane) => pane.id) ?? []);
7
+ const templateIds = new Set(browserBuiltinPlugins.flatMap((plugin) => plugin.paneTemplates?.map((template) => template.id) ?? []));
8
+
9
+ describe("browser plugin catalog", () => {
10
+ test("contains the reviewed cloud, local, market, and research plugins", () => {
11
+ expect(ids).toEqual([
12
+ "gloomberb-cloud",
13
+ "portfolio",
14
+ "ticker-research",
15
+ "application",
16
+ "news",
17
+ "market-overview",
18
+ "macro",
19
+ "alerts",
20
+ ]);
21
+ });
22
+
23
+ test("excludes native, filesystem, local AI, debug, updater, and external plugins", () => {
24
+ for (const forbidden of [
25
+ "broker",
26
+ "ibkr",
27
+ "public",
28
+ "robinhood",
29
+ "simplefin",
30
+ "notes",
31
+ "substack",
32
+ "ai",
33
+ "debug",
34
+ "yahoo",
35
+ "prediction-markets",
36
+ "polls",
37
+ "updater",
38
+ ]) {
39
+ expect(ids).not.toContain(forbidden);
40
+ }
41
+ });
42
+
43
+ test("keeps every public pane handoff restorable in the hosted browser", () => {
44
+ const unavailable = getLoadablePlugins()
45
+ .flatMap((plugin) => plugin.paneTemplates ?? [])
46
+ .filter((template) => template.publicShare && !templateIds.has(template.id))
47
+ .map((template) => template.id);
48
+ expect(unavailable).toEqual([]);
49
+ });
50
+
51
+ test("omits unsupported modules inside otherwise browser-safe product areas", () => {
52
+ expect(paneIds).toEqual(expect.arrayContaining([
53
+ "ticker-research",
54
+ "chart-composer",
55
+ "options-calculator",
56
+ "ticker-news",
57
+ "world-indices",
58
+ "econ-calendar",
59
+ "treasury-auctions",
60
+ ]));
61
+ for (const forbidden of [
62
+ "buildout",
63
+ "market-heatmap",
64
+ "market-movers",
65
+ "market-halts",
66
+ "earnings-calendar",
67
+ "ipo-calendar",
68
+ "tv",
69
+ "dividend-yield",
70
+ "short-interest",
71
+ "thirteenf",
72
+ "sec",
73
+ "insider",
74
+ ]) {
75
+ expect(paneIds).not.toContain(forbidden);
76
+ }
77
+ });
78
+ });
@@ -0,0 +1,136 @@
1
+ import {
2
+ attachFredSeriesPersistence,
3
+ resetFredSeriesPersistence,
4
+ } from "../data/fred-series";
5
+ import type { GloomPlugin } from "../types/plugin";
6
+ import { portfolioAnalyticsModule } from "./builtin/analytics";
7
+ import { alertsPlugin } from "./builtin/alerts";
8
+ import { browserGloomberbCloudPlugin } from "./builtin/cloud/browser";
9
+ import { changelogModule } from "./builtin/changelog";
10
+ import { chartComposerModule } from "./builtin/chart-composer";
11
+ import { connectionsModule } from "./builtin/connections";
12
+ import { correlationModule } from "./builtin/correlation";
13
+ import { cdsModule } from "./builtin/cds";
14
+ import { creditConditionsModule } from "./builtin/credit-conditions";
15
+ import { economicCalendarModule } from "./builtin/econ";
16
+ import { futuresModule } from "./builtin/futures";
17
+ import { fxMatrixModule } from "./builtin/fx-matrix";
18
+ import { helpModule } from "./builtin/help";
19
+ import { positionSizerModule } from "./builtin/kelly-sizer";
20
+ import { layoutManagerModule } from "./builtin/layout-manager";
21
+ import { tickerNewsModule } from "./builtin/news";
22
+ import { optionsModule } from "./builtin/options";
23
+ import { optionsCalculatorModule } from "./builtin/options-calculator";
24
+ import { composeBuiltinPlugin, type PluginModule } from "./builtin/plugin-module";
25
+ import { portfolioListModule } from "./builtin/portfolio-list";
26
+ import { researchModule } from "./builtin/research";
27
+ import { scannerModule } from "./builtin/scanner";
28
+ import { sectorsModule } from "./builtin/sectors";
29
+ import { tickerDetailModule } from "./builtin/ticker-detail";
30
+ import { treasuryAuctionsModule } from "./builtin/treasury-auctions";
31
+ import { volatilityModule } from "./builtin/volatility";
32
+ import { worldIndicesModule } from "./builtin/world-indices";
33
+ import { yieldCurveModule } from "./builtin/yield-curve";
34
+
35
+ const browserApplicationPlugin = composeBuiltinPlugin({
36
+ id: "application",
37
+ name: "Application",
38
+ version: "1.0.0",
39
+ description: "Core layout, help, and release information.",
40
+ modules: [layoutManagerModule, helpModule, changelogModule, connectionsModule],
41
+ });
42
+
43
+ const browserPortfolioPlugin = composeBuiltinPlugin({
44
+ id: "portfolio",
45
+ name: "Portfolio",
46
+ version: "1.0.0",
47
+ description: "Portfolio and watchlist management, analytics, and position sizing.",
48
+ toggleable: true,
49
+ modules: [portfolioListModule, portfolioAnalyticsModule, positionSizerModule],
50
+ });
51
+
52
+ const browserTickerResearchPlugin = composeBuiltinPlugin({
53
+ id: "ticker-research",
54
+ name: "Ticker Research",
55
+ version: "1.0.0",
56
+ description: "Company overview, charts, financials, options, and research.",
57
+ toggleable: true,
58
+ modules: [
59
+ tickerDetailModule,
60
+ chartComposerModule,
61
+ optionsModule,
62
+ optionsCalculatorModule,
63
+ researchModule,
64
+ ],
65
+ });
66
+
67
+ const browserNewsPlugin = composeBuiltinPlugin({
68
+ id: "news",
69
+ name: "News",
70
+ version: "1.0.0",
71
+ description: "View latest news for each ticker.",
72
+ toggleable: true,
73
+ modules: [tickerNewsModule],
74
+ });
75
+
76
+ const browserMarketOverviewPlugin = composeBuiltinPlugin({
77
+ id: "market-overview",
78
+ name: "Market Overview",
79
+ version: "1.0.0",
80
+ description: "Global indices, scanners, sectors, FX, futures, and correlations.",
81
+ toggleable: true,
82
+ modules: [
83
+ correlationModule,
84
+ worldIndicesModule,
85
+ scannerModule,
86
+ sectorsModule,
87
+ fxMatrixModule,
88
+ futuresModule,
89
+ ],
90
+ });
91
+
92
+ const browserFredResourcesModule: PluginModule = {
93
+ setup(ctx) {
94
+ attachFredSeriesPersistence(ctx.persistence);
95
+ },
96
+ dispose() {
97
+ resetFredSeriesPersistence();
98
+ },
99
+ };
100
+
101
+ const browserMacroPlugin = composeBuiltinPlugin({
102
+ id: "macro",
103
+ name: "Macro",
104
+ version: "1.0.0",
105
+ description: "Economic calendar, rates, volatility, credit spreads, single-name CDS, and Treasury auctions.",
106
+ toggleable: true,
107
+ modules: [
108
+ browserFredResourcesModule,
109
+ economicCalendarModule,
110
+ yieldCurveModule,
111
+ volatilityModule,
112
+ creditConditionsModule,
113
+ cdsModule,
114
+ treasuryAuctionsModule,
115
+ ],
116
+ });
117
+
118
+ /**
119
+ * Reviewed browser catalog. Native brokers, filesystem/local-process plugins,
120
+ * and modules whose data path is not available through Gloom Cloud or a
121
+ * browser-safe public API are absent rather than registered behind stubs.
122
+ */
123
+ export const browserBuiltinPlugins: readonly GloomPlugin[] = [
124
+ browserGloomberbCloudPlugin,
125
+ browserPortfolioPlugin,
126
+ browserTickerResearchPlugin,
127
+ browserApplicationPlugin,
128
+ browserNewsPlugin,
129
+ browserMarketOverviewPlugin,
130
+ browserMacroPlugin,
131
+ alertsPlugin,
132
+ ];
133
+
134
+ export function getBrowserBuiltinPlugins(): readonly GloomPlugin[] {
135
+ return browserBuiltinPlugins;
136
+ }
@@ -3,9 +3,10 @@ import type { AppNotificationRequest } from "../types/plugin";
3
3
  export function notifyGridlockComplete(
4
4
  notify: (notification: AppNotificationRequest) => void,
5
5
  onRevert: () => void,
6
+ body = "Windows tidied",
6
7
  ): void {
7
8
  notify({
8
- body: "Retiled all panes",
9
+ body,
9
10
  type: "success",
10
11
  action: {
11
12
  label: "Revert",
@@ -27,6 +27,7 @@ describe("parseFlexPositions", () => {
27
27
  const positions = parseFlexPositions(xml);
28
28
  expect(positions).toHaveLength(1);
29
29
  expect(positions[0]?.ticker).toBe("SPY 260619C00500000");
30
+ expect(positions[0]?.side).toBe("long");
30
31
  expect(positions[0]?.brokerContract).toEqual({
31
32
  brokerId: "ibkr",
32
33
  conId: 123456,
@@ -43,6 +44,24 @@ describe("parseFlexPositions", () => {
43
44
  tradingClass: "SPY",
44
45
  });
45
46
  });
47
+
48
+ test("treats a negative Flex position without a side attribute as a short", () => {
49
+ const xml = `
50
+ <FlexQueryResponse>
51
+ <OpenPositions>
52
+ <OpenPosition accountId="DU12345" symbol="TSLA" assetCategory="STK" position="-50" costBasisPrice="200" currency="USD" listingExchange="NASDAQ" />
53
+ </OpenPositions>
54
+ </FlexQueryResponse>
55
+ `;
56
+
57
+ const positions = parseFlexPositions(xml);
58
+ expect(positions).toHaveLength(1);
59
+ expect(positions[0]).toMatchObject({
60
+ ticker: "TSLA",
61
+ shares: 50,
62
+ side: "short",
63
+ });
64
+ });
46
65
  });
47
66
 
48
67
  describe("parseFlexAccounts", () => {
@@ -162,7 +162,7 @@ export function parseFlexPositions(xml: string): BrokerPosition[] {
162
162
  name: description || undefined,
163
163
  assetCategory: assetCategory || undefined,
164
164
  isin: isin || undefined,
165
- side: side === "long" || side === "short" ? side : undefined,
165
+ side: side === "long" || side === "short" ? side : quantity < 0 ? "short" : "long",
166
166
  markPrice: numAttr("markPrice"),
167
167
  marketValue: numAttr("positionValue"),
168
168
  unrealizedPnl: numAttr("fifoPnlUnrealized") ?? numAttr("unrealizedCapitalGainsPnl"),
@@ -1,4 +1,4 @@
1
- import type { LayoutConfig } from "../../types/config";
1
+ import type { FloatingPaneEntry, LayoutConfig } from "../../types/config";
2
2
  import {
3
3
  boundsForRects,
4
4
  inferDockTreeFromRects,
@@ -14,9 +14,85 @@ import {
14
14
  type PaneTypeAvailability,
15
15
  } from "./layout-state";
16
16
 
17
- export function gridlockAllPanes(
17
+ const BURIED_VISIBLE_RATIO = 0.2;
18
+ const TITLE_BAR_HEIGHT_RATIO = 0.12;
19
+ const TIDY_FLOATING_THRESHOLD = 3;
20
+
21
+ type Rect = Pick<FloatingPaneEntry, "x" | "y" | "width" | "height">;
22
+
23
+ export interface FloatingPaneVisibility {
24
+ instanceId: string;
25
+ buried: boolean;
26
+ visibleRatio: number;
27
+ titleBarVisibleRatio: number;
28
+ }
29
+
30
+ function rectArea(rect: Rect): number {
31
+ return Math.max(0, rect.width) * Math.max(0, rect.height);
32
+ }
33
+
34
+ function subtractRect(rect: Rect, cover: Rect): Rect[] {
35
+ const left = Math.max(rect.x, cover.x);
36
+ const top = Math.max(rect.y, cover.y);
37
+ const right = Math.min(rect.x + rect.width, cover.x + cover.width);
38
+ const bottom = Math.min(rect.y + rect.height, cover.y + cover.height);
39
+ if (left >= right || top >= bottom) return [rect];
40
+
41
+ return [
42
+ { x: rect.x, y: rect.y, width: rect.width, height: top - rect.y },
43
+ { x: rect.x, y: bottom, width: rect.width, height: rect.y + rect.height - bottom },
44
+ { x: rect.x, y: top, width: left - rect.x, height: bottom - top },
45
+ { x: right, y: top, width: rect.x + rect.width - right, height: bottom - top },
46
+ ].filter((piece) => piece.width > 0 && piece.height > 0);
47
+ }
48
+
49
+ function visibleRatio(rect: Rect, covers: Rect[]): number {
50
+ const area = rectArea(rect);
51
+ if (area === 0) return 0;
52
+ let pieces = [rect];
53
+ for (const cover of covers) {
54
+ pieces = pieces.flatMap((piece) => subtractRect(piece, cover));
55
+ if (pieces.length === 0) return 0;
56
+ }
57
+ return Math.min(1, pieces.reduce((sum, piece) => sum + rectArea(piece), 0) / area);
58
+ }
59
+
60
+ export function analyzeFloatingPaneVisibility(layout: LayoutConfig): FloatingPaneVisibility[] {
61
+ const frontToBack = layout.floating
62
+ .map((entry, index) => ({ entry, index }))
63
+ .sort((left, right) => (
64
+ (right.entry.zIndex ?? 50) - (left.entry.zIndex ?? 50)
65
+ || right.index - left.index
66
+ ));
67
+ const covers: Rect[] = [];
68
+
69
+ return frontToBack.map(({ entry }) => {
70
+ const paneVisibleRatio = visibleRatio(entry, covers);
71
+ const titleBar = {
72
+ x: entry.x,
73
+ y: entry.y,
74
+ width: entry.width,
75
+ height: Math.min(entry.height, Math.max(1, entry.height * TITLE_BAR_HEIGHT_RATIO)),
76
+ };
77
+ const titleBarVisibleRatio = visibleRatio(titleBar, covers);
78
+ covers.push(entry);
79
+ return {
80
+ instanceId: entry.instanceId,
81
+ buried: paneVisibleRatio < BURIED_VISIBLE_RATIO || titleBarVisibleRatio < BURIED_VISIBLE_RATIO,
82
+ visibleRatio: paneVisibleRatio,
83
+ titleBarVisibleRatio,
84
+ };
85
+ });
86
+ }
87
+
88
+ export function shouldShowTidyWindows(layout: LayoutConfig): boolean {
89
+ return layout.floating.length >= TIDY_FLOATING_THRESHOLD
90
+ || analyzeFloatingPaneVisibility(layout).some((pane) => pane.buried);
91
+ }
92
+
93
+ function gridlockPanes(
18
94
  layout: LayoutConfig,
19
- bounds: LayoutBounds = { x: 0, y: 0, width: 120, height: 40 },
95
+ bounds: LayoutBounds,
20
96
  paneTypes?: PaneTypeAvailability,
21
97
  ): LayoutConfig {
22
98
  const visibleLayout = paneTypes
@@ -40,3 +116,11 @@ export function gridlockAllPanes(
40
116
  floating: [],
41
117
  });
42
118
  }
119
+
120
+ export function gridlockAllPanes(
121
+ layout: LayoutConfig,
122
+ bounds: LayoutBounds = { x: 0, y: 0, width: 120, height: 40 },
123
+ paneTypes?: PaneTypeAvailability,
124
+ ): LayoutConfig {
125
+ return gridlockPanes(layout, bounds, paneTypes);
126
+ }