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
@@ -307,6 +307,15 @@ function collectAccountsByPortfolio(
307
307
  return output;
308
308
  }
309
309
 
310
+ type SanitizedTickerMetadata = ReturnType<typeof sanitizeTickerMetadata>;
311
+
312
+ /** Only tickers the user actually filed somewhere are worth syncing. */
313
+ function isSyncableTicker(metadata: Pick<SanitizedTickerMetadata, "portfolios" | "watchlists" | "positions">): boolean {
314
+ return metadata.portfolios.length > 0
315
+ || metadata.watchlists.length > 0
316
+ || metadata.positions.length > 0;
317
+ }
318
+
310
319
  function collectCoreCollectionsPayload(
311
320
  config: AppConfig,
312
321
  tickers: Map<string, TickerRecord>,
@@ -316,11 +325,7 @@ function collectCoreCollectionsPayload(
316
325
  ) {
317
326
  const records = [...tickers.values()]
318
327
  .map((ticker) => sanitizeTickerMetadata(ticker.metadata, financials.get(ticker.metadata.ticker)))
319
- .filter((metadata) => (
320
- metadata.portfolios.length > 0 ||
321
- metadata.watchlists.length > 0 ||
322
- metadata.positions.length > 0
323
- ));
328
+ .filter(isSyncableTicker);
324
329
 
325
330
  return {
326
331
  baseCurrency: config.baseCurrency,
@@ -375,11 +380,22 @@ function mergeConfigPayload(
375
380
  config: AppConfig,
376
381
  payload: unknown,
377
382
  baselineConfig: AppConfig = config,
383
+ lastSyncedPayload?: unknown,
378
384
  ): AppConfig | null {
379
385
  if (!isPlainObject(payload)) return null;
380
386
  const next: AppConfig = { ...config };
387
+ // Two guards, both needed. baselineConfig catches edits made while this pull
388
+ // was in flight; lastSyncedPayload catches edits made while the app was not
389
+ // running at all (CLI writes, offline edits), which otherwise look pristine.
390
+ const lastSynced = isPlainObject(lastSyncedPayload) ? lastSyncedPayload : null;
391
+ const localPayload = lastSynced
392
+ ? collectCoreConfigPayload(config) as Record<string, unknown>
393
+ : null;
394
+ const matchesLastSynced = (key: string) => (
395
+ !lastSynced || valuesEqual(localPayload?.[key], lastSynced[key])
396
+ );
381
397
  const canApply = <K extends keyof AppConfig>(key: K) => (
382
- valuesEqual(config[key], baselineConfig[key])
398
+ valuesEqual(config[key], baselineConfig[key]) && matchesLastSynced(key as string)
383
399
  );
384
400
  const assign = <K extends keyof AppConfig>(key: K) => {
385
401
  if (key in payload && canApply(key)) {
@@ -420,7 +436,8 @@ function mergeConfigPayload(
420
436
 
421
437
  const layoutStateUntouched = config.layout === baselineConfig.layout
422
438
  && config.layouts === baselineConfig.layouts
423
- && config.activeLayoutIndex === baselineConfig.activeLayoutIndex;
439
+ && config.activeLayoutIndex === baselineConfig.activeLayoutIndex
440
+ && ["layout", "layouts", "activeLayoutIndex"].every(matchesLastSynced);
424
441
  if (
425
442
  layoutStateUntouched
426
443
  && ["layout", "layouts", "activeLayoutIndex"].every((key) => key in payload)
@@ -450,12 +467,40 @@ function mergeConfigPayload(
450
467
  return next;
451
468
  }
452
469
 
470
+ function lastSyncedTickersById(baselinePayload: unknown): Map<string, Record<string, unknown>> | null {
471
+ if (!isPlainObject(baselinePayload) || !Array.isArray(baselinePayload.tickers)) return null;
472
+ const byId = new Map<string, Record<string, unknown>>();
473
+ for (const entry of baselinePayload.tickers) {
474
+ if (isPlainObject(entry) && typeof entry.ticker === "string") byId.set(entry.ticker, entry);
475
+ }
476
+ return byId;
477
+ }
478
+
479
+ /** Quotes churn on every refresh, so they cannot signal a user edit. */
480
+ function withoutQuote(metadata: Record<string, unknown>): Record<string, unknown> {
481
+ const { quote: _quote, ...rest } = metadata;
482
+ return rest;
483
+ }
484
+
485
+ function tickerChangedSinceLastSync(
486
+ current: TickerRecord | null | undefined,
487
+ lastSyncedTickers: Map<string, Record<string, unknown>> | null,
488
+ ): boolean {
489
+ if (!lastSyncedTickers || !current) return false;
490
+ const local = sanitizeTickerMetadata(current.metadata);
491
+ const baseline = lastSyncedTickers.get(current.metadata.ticker);
492
+ // No baseline entry means this device never uploaded the ticker: filed
493
+ // locally it is unsynced work, unfiled it is not something sync tracks.
494
+ if (!baseline) return isSyncableTicker(local);
495
+ return !valuesEqual(withoutQuote(local), withoutQuote(baseline));
496
+ }
497
+
453
498
  export const coreConfigSyncContributor: SyncContributor = {
454
499
  id: "core.config",
455
500
  schemaVersion: 1,
456
501
  collect: ({ state }) => collectCoreConfigPayload(state.config),
457
- apply: (payload, { baselineState, state, dispatch }) => {
458
- const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config);
502
+ apply: (payload, { baselinePayload, baselineState, state, dispatch }) => {
503
+ const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config, baselinePayload);
459
504
  if (!nextConfig || valuesEqual(nextConfig, state.config)) return;
460
505
  dispatch({ type: "SET_CONFIG", config: nextConfig });
461
506
  scheduleConfigSave(nextConfig);
@@ -472,9 +517,10 @@ export const coreCollectionsSyncContributor: SyncContributor = {
472
517
  state.exchangeRates,
473
518
  state.brokerAccounts,
474
519
  ),
475
- apply: async (payload, { getState, isCurrent, dispatch, tickerRepository }) => {
520
+ apply: async (payload, { baselinePayload, getState, isCurrent, dispatch, tickerRepository }) => {
476
521
  if (!isPlainObject(payload)) return;
477
522
  hydrateProfileAnalytics(payload);
523
+ const lastSyncedTickers = lastSyncedTickersById(baselinePayload);
478
524
  const incomingRecords: TickerRecord[] = [];
479
525
  const rawTickers = Array.isArray(payload.tickers) ? payload.tickers : [];
480
526
  for (const rawTicker of rawTickers) {
@@ -483,6 +529,9 @@ export const coreCollectionsSyncContributor: SyncContributor = {
483
529
  const current = typeof rawTicker.ticker === "string"
484
530
  ? getState().tickers.get(rawTicker.ticker)
485
531
  : null;
532
+ // Local edits the cloud has never seen (CLI positions, offline changes)
533
+ // win here and are uploaded by the push that follows this pull.
534
+ if (tickerChangedSinceLastSync(current, lastSyncedTickers)) continue;
486
535
  const metadata = hydrateTickerMetadata({
487
536
  ...current?.metadata,
488
537
  ...rawTicker,
package/src/sync/react.ts CHANGED
@@ -1,11 +1,14 @@
1
- import { useEffect, useSyncExternalStore, type Dispatch } from "react";
1
+ import { useEffect, useMemo, useSyncExternalStore, type Dispatch } from "react";
2
2
  import type { AppAction, AppState } from "../core/state/app/state";
3
3
  import type { AppTickerRepositoryPort } from "../core/app-service-ports";
4
4
  import { apiClient } from "../api-client";
5
5
  import type { PluginRegistry } from "../plugins/registry";
6
6
  import { subscribeToCloudVerification } from "./auth-transition";
7
+ import { createSyncBaselineStore } from "./baseline";
7
8
  import { cloudSyncController } from "./controller";
8
9
 
10
+ const CLOUD_SYNC_POLL_MS = 15_000;
11
+
9
12
  interface CloudSyncRuntimeOptions {
10
13
  state: AppState;
11
14
  getState: () => AppState;
@@ -13,6 +16,7 @@ interface CloudSyncRuntimeOptions {
13
16
  tickerRepository: AppTickerRepositoryPort;
14
17
  pluginRegistry: PluginRegistry;
15
18
  initialized: boolean;
19
+ appActive?: boolean;
16
20
  }
17
21
 
18
22
  export function useCloudSyncRuntime({
@@ -22,22 +26,38 @@ export function useCloudSyncRuntime({
22
26
  tickerRepository,
23
27
  pluginRegistry,
24
28
  initialized,
29
+ appActive = true,
25
30
  }: CloudSyncRuntimeOptions): void {
31
+ const baselineStore = useMemo(
32
+ () => createSyncBaselineStore(pluginRegistry.persistence.pluginState),
33
+ [pluginRegistry],
34
+ );
35
+
26
36
  useEffect(() => {
27
37
  return cloudSyncController.setRuntime({
28
38
  getState,
29
39
  dispatch,
30
40
  tickerRepository,
41
+ baselineStore,
31
42
  getContributors: () => pluginRegistry.getEnabledSyncContributors(),
32
43
  getTransport: () => pluginRegistry.getActiveSyncTransport(),
33
44
  });
34
- }, [dispatch, getState, pluginRegistry, tickerRepository]);
45
+ }, [baselineStore, dispatch, getState, pluginRegistry, tickerRepository]);
35
46
 
36
47
  useEffect(() => {
37
48
  if (!initialized) return;
38
49
  void cloudSyncController.requestSync({ reason: "startup" });
39
50
  }, [initialized, pluginRegistry]);
40
51
 
52
+ useEffect(() => {
53
+ if (!initialized || !appActive) return;
54
+ void cloudSyncController.requestSync({ reason: "foreground" });
55
+ const timer = setInterval(() => {
56
+ void cloudSyncController.requestSync({ reason: "poll" });
57
+ }, CLOUD_SYNC_POLL_MS);
58
+ return () => clearInterval(timer);
59
+ }, [appActive, initialized]);
60
+
41
61
  useEffect(() => subscribeToCloudVerification(apiClient, () => {
42
62
  if (!initialized) return;
43
63
  // Email verification makes the Cloud transport available. Force the first
package/src/sync/types.ts CHANGED
@@ -26,6 +26,12 @@ export interface SyncCollectContext {
26
26
  export interface SyncApplyContext {
27
27
  snapshot: SyncSnapshot;
28
28
  baselineState: AppState;
29
+ /**
30
+ * The payload this device last pushed for this contributor, or null when it
31
+ * never synced. Anything that differs from it is local work the cloud has
32
+ * never seen (CLI edits, offline edits) and must survive the pull.
33
+ */
34
+ baselinePayload: unknown;
29
35
  state: AppState;
30
36
  getState: () => AppState;
31
37
  isCurrent: () => boolean;
@@ -69,6 +75,12 @@ export interface SyncSettings {
69
75
  lastRoundupEmailAt?: string | null;
70
76
  }
71
77
 
78
+ /** Durable record of the payloads this device last pushed to the cloud. */
79
+ export interface SyncBaselineStore {
80
+ load(): Record<string, unknown> | null;
81
+ save(payloads: Record<string, unknown>): void;
82
+ }
83
+
72
84
  export interface RegisteredSyncContributor {
73
85
  pluginId: string;
74
86
  contributor: SyncContributor;
@@ -28,10 +28,6 @@ export function clampFontSize(value: unknown): number {
28
28
  return Math.min(MAX_FONT_SIZE_PX, Math.max(MIN_FONT_SIZE_PX, Math.round(numeric)));
29
29
  }
30
30
 
31
- export function getFontSize(): number {
32
- return appliedFontSizePx;
33
- }
34
-
35
31
  function roundToHundredth(value: number): number {
36
32
  return Math.round(value * 100) / 100;
37
33
  }
@@ -60,10 +56,3 @@ export function syncFontScale(fontSizePx: unknown): boolean {
60
56
  appliedToDocument = true;
61
57
  return true;
62
58
  }
63
-
64
- export function resetFontScaleForTests(): void {
65
- appliedFontSizePx = BASE_FONT_SIZE_PX;
66
- appliedToDocument = false;
67
- WEB_CELL_WIDTH = BASE_CELL_WIDTH_PX;
68
- WEB_CELL_HEIGHT = BASE_CELL_HEIGHT_PX;
69
- }
@@ -521,6 +521,33 @@ describe("ticker-search utilities", () => {
521
521
  expect(saved).toHaveLength(1);
522
522
  });
523
523
 
524
+ test("replaces a saved listing when search selects a different exchange", async () => {
525
+ const existing = makeTicker("AAPL", "Apple Inc.");
526
+ existing.metadata.exchange = "NASDAQ";
527
+ existing.metadata.currency = "USD";
528
+ const saved: TickerRecord[] = [];
529
+ const repository = {
530
+ loadTicker: async () => existing,
531
+ createTicker: async (metadata: TickerRecord["metadata"]) => ({ metadata }),
532
+ saveTicker: async (ticker: TickerRecord) => {
533
+ saved.push(ticker);
534
+ },
535
+ };
536
+
537
+ const { ticker } = await upsertTickerFromSearchResult(repository as any, {
538
+ providerId: "test",
539
+ symbol: "AAPL",
540
+ name: "Apple Inc.",
541
+ exchange: "BYMA",
542
+ type: "EQUITY",
543
+ currency: "ARS",
544
+ });
545
+
546
+ expect(ticker.metadata.exchange).toBe("BYMA");
547
+ expect(ticker.metadata.currency).toBe("ARS");
548
+ expect(saved).toHaveLength(1);
549
+ });
550
+
524
551
  test("exposes local ticker candidates in saved category", () => {
525
552
  expect(createLocalTickerSearchCandidates([makeTicker("TSLA", "Tesla")])).toEqual([
526
553
  expect.objectContaining({
@@ -8,6 +8,7 @@ import {
8
8
  getSearchResultSymbol,
9
9
  shouldReplaceTickerName,
10
10
  } from "./result";
11
+ import { canonicalExchange } from "../../utils/exchanges";
11
12
 
12
13
  export async function upsertTickerFromSearchResult(
13
14
  tickerRepository: AppTickerRepositoryPort,
@@ -70,6 +71,15 @@ function mergeTickerMetadataFromSearchResult(metadata: TickerMetadata, result: I
70
71
  if (nextExchange && !metadata.exchange) {
71
72
  metadata.exchange = nextExchange;
72
73
  changed = true;
74
+ } else if (
75
+ nextExchange
76
+ && metadata.exchange
77
+ && canonicalExchange(nextExchange) !== canonicalExchange(metadata.exchange)
78
+ ) {
79
+ metadata.exchange = nextExchange;
80
+ if (nextName) metadata.name = nextName;
81
+ if (nextCurrency) metadata.currency = nextCurrency;
82
+ changed = true;
73
83
  }
74
84
  if (nextCurrency && !metadata.currency) {
75
85
  metadata.currency = nextCurrency;
@@ -1,11 +1,12 @@
1
1
  import type { AssetDataProvider } from "./data-provider";
2
- import type { NewsArticle, NewsQuery } from "../news/types";
2
+ import type { NewsArticle, NewsPage, NewsQuery } from "../news/types";
3
3
  import type { CachePolicyMap } from "./persistence";
4
4
 
5
5
  export interface NewsDataProvider {
6
6
  supports?(query: NewsQuery): boolean;
7
7
  getCachedNews?(query: NewsQuery): NewsArticle[];
8
8
  fetchNews(query: NewsQuery): Promise<NewsArticle[]>;
9
+ fetchNewsPage?(query: NewsQuery): Promise<NewsPage>;
9
10
  fetchNewsStory?(storyId: string): Promise<NewsArticle | null>;
10
11
  }
11
12
 
@@ -504,7 +504,11 @@ export function removePaneInstances(layout: LayoutConfig, instanceIds: Iterable<
504
504
 
505
505
  export function normalizePaneLayout(
506
506
  layout: LayoutConfig,
507
- options?: { defaultFollowSourceInstanceId?: string | null },
507
+ options?: {
508
+ defaultFollowSourceInstanceId?: string | null;
509
+ /** Last resolved symbol used to pin an orphaned follower; otherwise it remains safely unlinked. */
510
+ resolveOrphanSymbol?: (instanceId: string) => string | null;
511
+ },
508
512
  ): LayoutConfig {
509
513
  const fallbackSourceId = options?.defaultFollowSourceInstanceId ?? null;
510
514
  const fallbackAvailable = !!fallbackSourceId && layout.instances.some((instance) => instance.instanceId === fallbackSourceId);
@@ -530,14 +534,27 @@ export function normalizePaneLayout(
530
534
  for (;;) {
531
535
  const validInstanceIds = new Set(nextLayout.instances.map((instance) => instance.instanceId));
532
536
  const removedIds = new Set<string>();
537
+ const orphanBindings = new Map<string, PaneBinding>();
533
538
 
534
539
  for (const instance of nextLayout.instances) {
535
540
  if (instance.binding?.kind === "follow" && !validInstanceIds.has(instance.binding.sourceInstanceId)) {
536
- removedIds.add(instance.instanceId);
541
+ // Structural helpers remove the source before the app can resolve its last ticker. Preserve
542
+ // the dangling binding until the runtime normalizes again with a resolver.
543
+ if (!options?.resolveOrphanSymbol) continue;
544
+ const orphanSymbol = options.resolveOrphanSymbol(instance.instanceId)?.trim();
545
+ orphanBindings.set(
546
+ instance.instanceId,
547
+ orphanSymbol ? { kind: "fixed", symbol: orphanSymbol } : { kind: "none" },
548
+ );
537
549
  continue;
538
550
  }
539
551
 
540
- if (isTickerPaneInstance(instance) && instance.binding?.kind !== "follow" && instance.binding?.kind !== "fixed") {
552
+ if (
553
+ isTickerPaneInstance(instance)
554
+ && instance.paneId !== TICKER_RESEARCH_PANE_ID
555
+ && instance.binding?.kind !== "follow"
556
+ && instance.binding?.kind !== "fixed"
557
+ ) {
541
558
  removedIds.add(instance.instanceId);
542
559
  continue;
543
560
  }
@@ -547,8 +564,21 @@ export function normalizePaneLayout(
547
564
  }
548
565
  }
549
566
 
550
- if (removedIds.size === 0) break;
551
- nextLayout = removePaneInstances(nextLayout, removedIds);
567
+ if (orphanBindings.size > 0) {
568
+ nextLayout = {
569
+ ...nextLayout,
570
+ instances: nextLayout.instances.map((instance) => {
571
+ const binding = orphanBindings.get(instance.instanceId);
572
+ return binding ? { ...instance, binding } : instance;
573
+ }),
574
+ };
575
+ }
576
+
577
+ if (removedIds.size > 0) {
578
+ nextLayout = removePaneInstances(nextLayout, removedIds);
579
+ continue;
580
+ }
581
+ if (orphanBindings.size === 0) break;
552
582
  }
553
583
 
554
584
  const validInstanceIds = new Set(nextLayout.instances.map((instance) => instance.instanceId));
@@ -4,6 +4,7 @@ export type DesktopApplicationMenuCommand =
4
4
  | { type: "open-url"; url: string }
5
5
  | { type: "check-for-updates" }
6
6
  | { type: "toggle-status-bar" }
7
+ | { type: "open-layout-gallery" }
7
8
  | { type: "layout-undo" }
8
9
  | { type: "layout-redo" }
9
10
  | { type: "layout-gridlock" };
@@ -44,6 +44,18 @@ export interface PaneProps {
44
44
  close?: () => void;
45
45
  }
46
46
 
47
+ export type PaneSharePrivateFields = true | readonly string[];
48
+
49
+ export interface PanePortableShareDef {
50
+ /** Fields excluded before pane configuration or state leaves the device. */
51
+ private?: {
52
+ title?: boolean;
53
+ params?: PaneSharePrivateFields;
54
+ settings?: PaneSharePrivateFields;
55
+ state?: PaneSharePrivateFields;
56
+ };
57
+ }
58
+
47
59
  export interface PaneDef {
48
60
  id: string;
49
61
  name: string;
@@ -53,7 +65,11 @@ export interface PaneDef {
53
65
  defaultWidth?: string;
54
66
  defaultFloatingSize?: { width: number; height: number };
55
67
  defaultMode?: "docked" | "floating";
68
+ /** Pane publishes its selected symbol as pane-state `cursorSymbol`, so ticker panes can follow it. */
69
+ tickerSource?: boolean;
56
70
  settings?: PaneSettingsDef | ((context: PaneSettingsContext) => PaneSettingsDef | null);
71
+ /** Portable sharing is public by default; list the few pane-owned fields that must remain local. */
72
+ portableShare?: PanePortableShareDef;
57
73
  /** Compact controls surfaced next to the pane title. Toggle keys reference toggle fields in settings. */
58
74
  quickSettings?: readonly PaneQuickSettingDef[];
59
75
  }
@@ -177,6 +193,8 @@ export interface PaneTemplateCreateOptions {
177
193
  symbols?: string[] | null;
178
194
  ticker?: TickerRecord | null;
179
195
  searchResult?: InstrumentSearchResult | null;
196
+ /** Template-owned, validated data restored from a public pane share. */
197
+ shareData?: unknown;
180
198
  }
181
199
 
182
200
  export interface PaneTemplateInstanceConfig {
@@ -190,6 +208,22 @@ export interface PaneTemplateInstanceConfig {
190
208
  relativePosition?: "left" | "right" | "above" | "below";
191
209
  }
192
210
 
211
+ export interface PaneTemplatePublicShareContext {
212
+ pane: PaneInstanceConfig;
213
+ paneState: Record<string, unknown>;
214
+ }
215
+
216
+ export interface PaneTemplatePublicShareSnapshot {
217
+ title: string;
218
+ description?: string;
219
+ data: Record<string, unknown>;
220
+ }
221
+
222
+ export interface PaneTemplatePublicShareDef {
223
+ serialize(context: PaneTemplatePublicShareContext): PaneTemplatePublicShareSnapshot | null;
224
+ restore(data: Record<string, unknown>): PaneTemplateCreateOptions | null;
225
+ }
226
+
193
227
  export interface PaneTemplateDef {
194
228
  id: string;
195
229
  paneId: string;
@@ -203,6 +237,8 @@ export interface PaneTemplateDef {
203
237
  context: PaneTemplateContext,
204
238
  options?: PaneTemplateCreateOptions,
205
239
  ) => PaneTemplateInstanceConfig | null | Promise<PaneTemplateInstanceConfig | null>;
240
+ /** Legacy v1 restoration or an explicit transformed snapshot; normal pane shares use portableShare. */
241
+ publicShare?: PaneTemplatePublicShareDef;
206
242
  }
207
243
 
208
244
  export interface WizardStep {
@@ -189,7 +189,7 @@ function appContextMenuItems(registry: Pick<PluginRegistry, "openCommandBar"> |
189
189
  {
190
190
  id: "app:layout-actions",
191
191
  label: "Layout Actions...",
192
- onSelect: () => registry?.openCommandBar("LAY "),
192
+ onSelect: () => registry?.openCommandBar("LMA "),
193
193
  },
194
194
  contextMenuDivider("app:config-divider"),
195
195
  {
package/src/ui/host.tsx CHANGED
@@ -336,6 +336,10 @@ export interface UiHost {
336
336
  capabilities?: {
337
337
  nativePaneChrome?: boolean;
338
338
  titleBarOverlay?: boolean;
339
+ /** Native drag regions and traffic-light/window-control spacing. */
340
+ nativeWindowChrome?: boolean;
341
+ /** Enables public snapshot sharing controls for this host. */
342
+ publicSharing?: boolean;
339
343
  precisePointer?: boolean;
340
344
  fractionalViewport?: boolean;
341
345
  cellWidthPx?: number;
package/src/ui/toast.tsx CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createContext, useContext, type ComponentType, type ReactNode } from "react";
2
2
 
3
3
  export interface ToastOptions {
4
+ title?: string;
5
+ subtitle?: string;
4
6
  duration?: number;
5
7
  action?: {
6
8
  label: string;
@@ -31,10 +31,6 @@ export function isPlainEscape(event: BackNavigationEventLike): boolean {
31
31
  && !event.shift;
32
32
  }
33
33
 
34
- export function isBackNavigationKey(event: BackNavigationEventLike): boolean {
35
- return isPlainBackspace(event) || isPlainEscape(event);
36
- }
37
-
38
34
  export function isDetailBackNavigationKey(
39
35
  event: BackNavigationEventLike,
40
36
  ): boolean {
@@ -3,6 +3,7 @@ import { resolveExchangeTimeZone } from "./exchanges";
3
3
  import { isTimestampStaleForExchangeSession } from "../market-data/market/freshness";
4
4
 
5
5
  const MAX_CURRENT_INTRADAY_HISTORY_LAG_MS = 18 * 60 * 60 * 1000;
6
+ const MAX_SAME_SESSION_HISTORY_LAG_MS = 30 * 60 * 1000;
6
7
 
7
8
  interface PriceHistoryFreshnessOptions {
8
9
  exchange?: string;
@@ -78,19 +79,19 @@ export function isPriceHistoryStaleForCurrentWindow(
78
79
  if (!latest) return false;
79
80
 
80
81
  const latestTime = getPricePointTimestamp(latest);
81
- if (!Number.isFinite(latestTime) || now - latestTime <= MAX_CURRENT_INTRADAY_HISTORY_LAG_MS) {
82
- return false;
83
- }
84
-
82
+ if (!Number.isFinite(latestTime)) return false;
83
+ const age = now - latestTime;
84
+ if (age <= MAX_SAME_SESSION_HISTORY_LAG_MS) return false;
85
+ const exchange = options.exchange || "NASDAQ";
85
86
  if (
86
- options.exchange
87
- && resolveExchangeTimeZone(options.exchange)
88
- && !isTimestampStaleForExchangeSession(latestTime, options.exchange, now)
87
+ resolveExchangeTimeZone(exchange)
88
+ && isTimestampStaleForExchangeSession(latestTime, exchange, now)
89
89
  ) {
90
- return false;
90
+ return true;
91
91
  }
92
-
93
- return true;
92
+ const hasExchangeSession = Boolean(resolveExchangeTimeZone(exchange));
93
+ if (age <= MAX_CURRENT_INTRADAY_HISTORY_LAG_MS) return hasExchangeSession;
94
+ return !hasExchangeSession;
94
95
  }
95
96
 
96
97
  export function normalizeTickerFinancialsPriceHistory(financials: TickerFinancials): TickerFinancials {
@@ -35,6 +35,12 @@ describe("isUsEquityTicker", () => {
35
35
  }))).toBe(true);
36
36
  });
37
37
 
38
+ test("accepts US-listed ADRs", () => {
39
+ expect(isUsEquityTicker(makeTicker({
40
+ assetCategory: "ADR",
41
+ }))).toBe(true);
42
+ });
43
+
38
44
  test("rejects non-equity instruments", () => {
39
45
  expect(isUsEquityTicker(makeTicker({
40
46
  assetCategory: "OPT",
package/src/utils/sec.ts CHANGED
@@ -24,7 +24,7 @@ function isUsExchange(value?: string): boolean {
24
24
 
25
25
  function isEquityType(value?: string): boolean {
26
26
  const normalized = normalize(value);
27
- return normalized.length === 0 || normalized === "STK" || normalized === "EQUITY";
27
+ return normalized.length === 0 || normalized === "STK" || normalized === "EQUITY" || normalized === "ADR";
28
28
  }
29
29
 
30
30
  export function isUsEquityTicker(ticker: TickerRecord | null | undefined): boolean {