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
@@ -0,0 +1,358 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
2
+ import {
3
+ DataTableStackView,
4
+ DataTableView,
5
+ EmptyState,
6
+ Spinner,
7
+ usePaneTicker,
8
+ type DataTableCell,
9
+ type DataTableKeyEvent,
10
+ type PaneFooterSegment,
11
+ } from "../../../components";
12
+ import { colors } from "../../../theme/colors";
13
+ import type { PaneProps } from "../../../types/plugin";
14
+ import { Box, Text, TextAttributes } from "../../../ui";
15
+ import { isPlainKey } from "../../../utils/keyboard";
16
+ import { cycleSortPreference } from "../../../utils/sort-values";
17
+ import { useAutoRefresh } from "../shared/auto-refresh";
18
+ import { usePaneStatusFooter } from "../shared/pane-footer";
19
+ import { loadCdsActivity, type CdsActivity, type CdsActivityLoader } from "./client";
20
+ import {
21
+ buildIssuerColumns,
22
+ buildTradeColumns,
23
+ DEFAULT_ISSUER_SORT,
24
+ DEFAULT_TRADE_SORT,
25
+ formatAsOf,
26
+ formatBp,
27
+ formatEventTime,
28
+ formatMaturity,
29
+ formatNotional,
30
+ formatUpfront,
31
+ ISSUER_SORT_COLUMN_IDS,
32
+ nextSort,
33
+ resolveIssuerQuery,
34
+ sortIssuers,
35
+ sortTrades,
36
+ summarizeIssuers,
37
+ TRADE_SORT_COLUMN_IDS,
38
+ tradesForIssuer,
39
+ type CdsIssuerSummary,
40
+ type CdsTrade,
41
+ type IssuerColumn,
42
+ type IssuerColumnId,
43
+ type IssuerSortPreference,
44
+ type TradeColumn,
45
+ type TradeColumnId,
46
+ type TradeSortPreference,
47
+ } from "./model";
48
+
49
+ function renderIssuerCell(
50
+ row: CdsIssuerSummary,
51
+ column: IssuerColumn,
52
+ selected: boolean,
53
+ ): DataTableCell {
54
+ const selectedColor = selected ? colors.selectedText : undefined;
55
+ switch (column.id) {
56
+ case "issuer":
57
+ return {
58
+ text: row.issuer,
59
+ color: selectedColor ?? colors.textBright,
60
+ attributes: TextAttributes.BOLD,
61
+ };
62
+ case "trades":
63
+ return { text: String(row.trades), color: selectedColor ?? colors.text };
64
+ case "last":
65
+ return { text: formatEventTime(row.lastTradeAt), color: selectedColor ?? colors.textMuted };
66
+ case "spread":
67
+ return {
68
+ text: formatBp(row.latestSpreadBp),
69
+ color: selectedColor ?? (row.latestSpreadBp == null ? colors.textDim : colors.text),
70
+ };
71
+ }
72
+ }
73
+
74
+ function renderTradeCell(row: CdsTrade, column: TradeColumn, selected: boolean): DataTableCell {
75
+ const selectedColor = selected ? colors.selectedText : undefined;
76
+ switch (column.id) {
77
+ case "time":
78
+ return { text: formatEventTime(row.eventAt), color: selectedColor ?? colors.textMuted };
79
+ case "maturity":
80
+ return { text: formatMaturity(row.maturity), color: selectedColor ?? colors.text };
81
+ case "notional":
82
+ return { text: formatNotional(row), color: selectedColor ?? colors.textBright };
83
+ case "currency":
84
+ return { text: row.currency ?? "--", color: selectedColor ?? colors.textDim };
85
+ case "coupon":
86
+ return { text: formatBp(row.couponBp), color: selectedColor ?? colors.text };
87
+ case "spread":
88
+ return {
89
+ text: formatBp(row.spreadBp),
90
+ color: selectedColor ?? (row.spreadBp == null ? colors.textDim : colors.textBright),
91
+ };
92
+ case "upfront":
93
+ return {
94
+ text: formatUpfront(row),
95
+ color: selectedColor ?? (row.upfront == null ? colors.textDim : colors.text),
96
+ };
97
+ }
98
+ }
99
+
100
+ function CdsTradeTable({
101
+ trades,
102
+ focused,
103
+ width,
104
+ height,
105
+ sort,
106
+ onSort,
107
+ selectedId,
108
+ onSelect,
109
+ onKeyDown,
110
+ before,
111
+ }: {
112
+ trades: CdsTrade[];
113
+ focused: boolean;
114
+ width: number;
115
+ height?: number;
116
+ sort: TradeSortPreference;
117
+ onSort: (columnId: TradeColumnId) => void;
118
+ selectedId: string | null;
119
+ onSelect: (id: string | null) => void;
120
+ onKeyDown: (event: DataTableKeyEvent) => boolean;
121
+ before?: ReactNode;
122
+ }) {
123
+ const columns = useMemo(() => buildTradeColumns(width), [width]);
124
+ return (
125
+ <DataTableView<CdsTrade, TradeColumn>
126
+ focused={focused}
127
+ rootWidth={width}
128
+ rootHeight={height}
129
+ rootBefore={before}
130
+ selection={{
131
+ kind: "id",
132
+ selectedId,
133
+ getId: (trade) => trade.id,
134
+ onChange: (id) => onSelect(id),
135
+ }}
136
+ onRootKeyDown={onKeyDown}
137
+ columns={columns}
138
+ items={trades}
139
+ sortColumnId={sort.columnId}
140
+ sortDirection={sort.direction}
141
+ onHeaderClick={(columnId) => onSort(columnId as TradeColumnId)}
142
+ getItemKey={(trade) => trade.id}
143
+ renderCell={(trade, column, _index, state) => renderTradeCell(trade, column, state.selected)}
144
+ emptyStateTitle="No reported trades."
145
+ />
146
+ );
147
+ }
148
+
149
+ const NO_TRADES: CdsTrade[] = [];
150
+
151
+ interface CdsPaneProps extends PaneProps {
152
+ /** Injected by tests so the pane renders without the cloud backend. */
153
+ loadActivity?: CdsActivityLoader;
154
+ }
155
+
156
+ export function CdsPane({
157
+ paneId,
158
+ focused,
159
+ width,
160
+ height,
161
+ loadActivity = loadCdsActivity,
162
+ }: CdsPaneProps) {
163
+ const { symbol, ticker } = usePaneTicker();
164
+ const issuerQuery = useMemo(() => resolveIssuerQuery(symbol, ticker), [symbol, ticker]);
165
+
166
+ const [activity, setActivity] = useState<CdsActivity | null>(null);
167
+ const [status, setStatus] = useState<"loading" | "loaded" | "error">("loading");
168
+ const [error, setError] = useState<string | null>(null);
169
+ const [fetchedAt, setFetchedAt] = useState<number | null>(null);
170
+ const [issuerSort, setIssuerSort] = useState<IssuerSortPreference>(DEFAULT_ISSUER_SORT);
171
+ const [tradeSort, setTradeSort] = useState<TradeSortPreference>(DEFAULT_TRADE_SORT);
172
+ const [selectedIssuerKey, setSelectedIssuerKey] = useState<string | null>(null);
173
+ const [selectedTradeId, setSelectedTradeId] = useState<string | null>(null);
174
+ const [detailOpen, setDetailOpen] = useState(false);
175
+ const generation = useRef(0);
176
+ // Held in a ref so an inline loader prop cannot turn every render into a fetch.
177
+ const loadActivityRef = useRef(loadActivity);
178
+ loadActivityRef.current = loadActivity;
179
+
180
+ const load = useCallback(() => {
181
+ generation.current += 1;
182
+ const current = generation.current;
183
+ setStatus((previous) => (previous === "loaded" ? "loaded" : "loading"));
184
+ setError(null);
185
+ loadActivityRef.current(issuerQuery)
186
+ .then((next) => {
187
+ if (generation.current !== current) return;
188
+ setActivity(next);
189
+ setStatus("loaded");
190
+ setFetchedAt(Date.now());
191
+ })
192
+ .catch((loadError: unknown) => {
193
+ if (generation.current !== current) return;
194
+ setError(loadError instanceof Error ? loadError.message : String(loadError));
195
+ setStatus("error");
196
+ });
197
+ }, [issuerQuery]);
198
+
199
+ useEffect(() => { load(); }, [load]);
200
+ useAutoRefresh(fetchedAt, load);
201
+
202
+ const trades = activity?.trades ?? NO_TRADES;
203
+ const issuers = useMemo(
204
+ () => (issuerQuery ? [] : sortIssuers(summarizeIssuers(trades), issuerSort)),
205
+ [issuerQuery, issuerSort, trades],
206
+ );
207
+ const visibleTrades = useMemo(() => sortTrades(
208
+ issuerQuery ? trades : selectedIssuerKey ? tradesForIssuer(trades, selectedIssuerKey) : [],
209
+ tradeSort,
210
+ ), [issuerQuery, selectedIssuerKey, tradeSort, trades]);
211
+ const selectedSummary = issuers.find((row) => row.key === selectedIssuerKey) ?? null;
212
+
213
+ useEffect(() => {
214
+ if (issuerQuery) return;
215
+ if (selectedIssuerKey && issuers.some((row) => row.key === selectedIssuerKey)) return;
216
+ setSelectedIssuerKey(issuers[0]?.key ?? null);
217
+ setDetailOpen(false);
218
+ }, [issuerQuery, issuers, selectedIssuerKey]);
219
+
220
+ const cycleTradeSort = useCallback((step: 1 | -1) => {
221
+ setTradeSort((current) => {
222
+ const next = cycleSortPreference<TradeColumnId>(TRADE_SORT_COLUMN_IDS, current, step);
223
+ return { columnId: next.columnId ?? DEFAULT_TRADE_SORT.columnId, direction: next.direction };
224
+ });
225
+ }, []);
226
+ const cycleIssuerSort = useCallback((step: 1 | -1) => {
227
+ setIssuerSort((current) => {
228
+ const next = cycleSortPreference<IssuerColumnId>(ISSUER_SORT_COLUMN_IDS, current, step);
229
+ return { columnId: next.columnId ?? DEFAULT_ISSUER_SORT.columnId, direction: next.direction };
230
+ });
231
+ }, []);
232
+
233
+ const handleKey = useCallback((event: DataTableKeyEvent, cycle: (step: 1 | -1) => void): boolean => {
234
+ if (isPlainKey(event, "r")) {
235
+ event.preventDefault?.();
236
+ event.stopPropagation?.();
237
+ load();
238
+ return true;
239
+ }
240
+ if (isPlainKey(event, "]", "[")) {
241
+ event.preventDefault?.();
242
+ event.stopPropagation?.();
243
+ cycle(event.name === "]" ? 1 : -1);
244
+ return true;
245
+ }
246
+ return false;
247
+ }, [load]);
248
+ const handleIssuerKey = useCallback(
249
+ (event: DataTableKeyEvent) => handleKey(event, cycleIssuerSort),
250
+ [cycleIssuerSort, handleKey],
251
+ );
252
+ const handleTradeKey = useCallback(
253
+ (event: DataTableKeyEvent) => handleKey(event, cycleTradeSort),
254
+ [cycleTradeSort, handleKey],
255
+ );
256
+
257
+ const asOfLabel = formatAsOf(activity?.asOf ?? null);
258
+ const footerInfo = useMemo<PaneFooterSegment[]>(() => [
259
+ ...(asOfLabel ? [{ id: "as-of", parts: [{ text: `as of ${asOfLabel}`, tone: "muted" as const }] }] : []),
260
+ ...(activity ? [{ id: "delayed", parts: [{ text: "delayed", tone: "muted" as const }] }] : []),
261
+ ], [activity, asOfLabel]);
262
+ usePaneStatusFooter({
263
+ registrationId: paneId,
264
+ loading: status === "loading",
265
+ error,
266
+ info: footerInfo,
267
+ });
268
+
269
+ if (status === "loading" && !activity) {
270
+ return (
271
+ <Box width={width} height={height} justifyContent="center" alignItems="center">
272
+ <Spinner label="Loading CDS activity..." />
273
+ </Box>
274
+ );
275
+ }
276
+ if (!activity) {
277
+ return (
278
+ <Box width={width} height={height} padding={1} flexDirection="column">
279
+ {/* The reason lives in the footer, so the body never repeats it. */}
280
+ <EmptyState title="CDS activity unavailable." />
281
+ </Box>
282
+ );
283
+ }
284
+
285
+ if (issuerQuery) {
286
+ // The pane title already carries the ticker, so the body leads with the
287
+ // issuer name the backend was actually queried for, which is the expanded
288
+ // company name once instrument search has resolved a bare symbol.
289
+ const resolved = (
290
+ <Box height={1} paddingX={1}>
291
+ <Text fg={colors.textMuted} wrapMode="ellipsis">
292
+ {`${activity.issuer ?? issuerQuery} · ${activity.source}`}
293
+ </Text>
294
+ </Box>
295
+ );
296
+ return (
297
+ <CdsTradeTable
298
+ trades={visibleTrades}
299
+ focused={focused}
300
+ width={width}
301
+ height={height}
302
+ sort={tradeSort}
303
+ onSort={(columnId) => setTradeSort((current) => nextSort(current, columnId, DEFAULT_TRADE_SORT))}
304
+ selectedId={selectedTradeId}
305
+ onSelect={setSelectedTradeId}
306
+ onKeyDown={handleTradeKey}
307
+ before={resolved}
308
+ />
309
+ );
310
+ }
311
+
312
+ const issuerColumns = buildIssuerColumns(width);
313
+ return (
314
+ <DataTableStackView<CdsIssuerSummary, IssuerColumn>
315
+ focused={focused}
316
+ detailOpen={detailOpen && !!selectedSummary}
317
+ onBack={() => setDetailOpen(false)}
318
+ detailTitle={selectedSummary?.issuer}
319
+ detailContent={selectedSummary ? (
320
+ <CdsTradeTable
321
+ trades={visibleTrades}
322
+ focused={focused && detailOpen}
323
+ width={width}
324
+ sort={tradeSort}
325
+ onSort={(columnId) => setTradeSort((current) => nextSort(current, columnId, DEFAULT_TRADE_SORT))}
326
+ selectedId={selectedTradeId}
327
+ onSelect={setSelectedTradeId}
328
+ onKeyDown={handleTradeKey}
329
+ />
330
+ ) : null}
331
+ onDetailKeyDown={handleTradeKey}
332
+ rootWidth={width}
333
+ rootHeight={height}
334
+ selection={{
335
+ kind: "id",
336
+ selectedId: selectedIssuerKey,
337
+ getId: (row) => row.key,
338
+ onChange: (id) => setSelectedIssuerKey(id),
339
+ }}
340
+ onActivate={(row) => {
341
+ setSelectedIssuerKey(row.key);
342
+ setSelectedTradeId(null);
343
+ setDetailOpen(true);
344
+ }}
345
+ onRootKeyDown={handleIssuerKey}
346
+ columns={issuerColumns}
347
+ items={issuers}
348
+ sortColumnId={issuerSort.columnId}
349
+ sortDirection={issuerSort.direction}
350
+ onHeaderClick={(columnId) => setIssuerSort((current) => (
351
+ nextSort(current, columnId as IssuerColumnId, DEFAULT_ISSUER_SORT)
352
+ ))}
353
+ getItemKey={(row) => row.key}
354
+ renderCell={(row, column, _index, state) => renderIssuerCell(row, column, state.selected)}
355
+ emptyStateTitle={error ? "CDS activity unavailable." : "No reported single-name CDS trades."}
356
+ />
357
+ );
358
+ }
@@ -8,8 +8,39 @@ import {
8
8
  } from "../../../time-series/spec";
9
9
 
10
10
  export const CHART_SPEC_SETTING_KEY = "chartSpec";
11
+ export const CHART_INTERACTION_VIEWPORT_SETTING_KEY = "chartInteractionViewport";
11
12
  export const MAX_CHART_COMPOSER_SERIES = MAX_CHART_SERIES;
12
13
 
14
+ export interface ChartInteractionViewport {
15
+ authoredViewportKey: string;
16
+ start: string;
17
+ end: string;
18
+ adaptive: boolean;
19
+ }
20
+
21
+ export function parseChartInteractionViewport(value: unknown): ChartInteractionViewport | null {
22
+ if (!isRecord(value)) return null;
23
+ if (!Object.keys(value).every((key) => ["authoredViewportKey", "start", "end", "adaptive"].includes(key))) return null;
24
+ if (
25
+ typeof value.authoredViewportKey !== "string"
26
+ || value.authoredViewportKey.length === 0
27
+ || value.authoredViewportKey.length > 16_384
28
+ || typeof value.start !== "string"
29
+ || typeof value.end !== "string"
30
+ || typeof value.adaptive !== "boolean"
31
+ ) return null;
32
+ const start = Date.parse(value.start);
33
+ const end = Date.parse(value.end);
34
+ return Number.isFinite(start) && Number.isFinite(end) && start < end
35
+ ? {
36
+ authoredViewportKey: value.authoredViewportKey,
37
+ start: new Date(start).toISOString(),
38
+ end: new Date(end).toISOString(),
39
+ adaptive: value.adaptive,
40
+ }
41
+ : null;
42
+ }
43
+
13
44
  export function canToggleChartSeries(spec: ChartSpec, seriesId: string): boolean {
14
45
  const target = spec.series.find((series) => series.id === seriesId);
15
46
  if (!target) return false;
@@ -14,7 +14,18 @@ import {
14
14
  DATA_CATALOG_PANE_ID,
15
15
  DATA_CATALOG_TEMPLATE_ID,
16
16
  } from "./catalog-inventory";
17
- import { CHART_SPEC_SETTING_KEY } from "./chart-spec";
17
+ import {
18
+ CHART_INTERACTION_VIEWPORT_SETTING_KEY,
19
+ CHART_SPEC_SETTING_KEY,
20
+ parseChartInteractionViewport,
21
+ parseChartSpec,
22
+ type ChartInteractionViewport,
23
+ } from "./chart-spec";
24
+ import {
25
+ CHART_DRAWINGS_SETTING_KEY,
26
+ parseChartDrawings,
27
+ type ChartDrawing,
28
+ } from "../../../components/chart/composite/tools";
18
29
  import {
19
30
  buildComparisonChartPreset,
20
31
  buildCustomChartPreset,
@@ -76,7 +87,16 @@ function chartTitle(spec: ChartSpec, prefix = "G"): string {
76
87
  return `${prefix} ${labels.join(" · ")}${remaining > 0 ? ` +${remaining}` : ""}`;
77
88
  }
78
89
 
79
- function instanceFor(spec: ChartSpec, prefix: string) {
90
+ function isRecord(value: unknown): value is Record<string, unknown> {
91
+ return value !== null && typeof value === "object" && !Array.isArray(value);
92
+ }
93
+
94
+ interface SharedChartState {
95
+ drawings?: ChartDrawing[];
96
+ viewport?: ChartInteractionViewport;
97
+ }
98
+
99
+ function instanceFor(spec: ChartSpec, prefix: string, shared: SharedChartState = {}) {
80
100
  const symbol = primarySecuritySymbol(spec);
81
101
  return {
82
102
  title: chartTitle(spec, prefix),
@@ -84,6 +104,12 @@ function instanceFor(spec: ChartSpec, prefix: string) {
84
104
  ...(symbol ? { binding: { kind: "fixed" as const, symbol } } : {}),
85
105
  settings: {
86
106
  [CHART_SPEC_SETTING_KEY]: spec,
107
+ ...(shared.drawings?.length
108
+ ? { [CHART_DRAWINGS_SETTING_KEY]: shared.drawings }
109
+ : {}),
110
+ ...(shared.viewport
111
+ ? { [CHART_INTERACTION_VIEWPORT_SETTING_KEY]: shared.viewport }
112
+ : {}),
87
113
  },
88
114
  };
89
115
  }
@@ -148,9 +174,59 @@ const chartComposerTemplates: PaneTemplateDef[] = [
148
174
  }],
149
175
  canCreate: () => true,
150
176
  createInstance: (context, options) => {
177
+ const sharedData = isRecord(options?.shareData) ? options.shareData : null;
178
+ const sharedSpec = parseChartSpec(sharedData?.chartSpec ?? options?.shareData);
179
+ if (sharedSpec) {
180
+ const drawings = parseChartDrawings(sharedData?.chartDrawings);
181
+ const viewport = parseChartInteractionViewport(sharedData?.chartInteractionViewport);
182
+ return instanceFor(sharedSpec, "G", {
183
+ ...(drawings.length > 0 ? { drawings } : {}),
184
+ ...(viewport ? { viewport } : {}),
185
+ });
186
+ }
151
187
  const expression = options?.arg?.trim() || options?.values?.series?.trim() || context.activeTicker || "";
152
188
  return instanceFor(buildCustomChartPreset(expression, context.activeTicker), "G");
153
189
  },
190
+ publicShare: {
191
+ serialize: ({ pane }) => {
192
+ const spec = parseChartSpec(pane.settings?.[CHART_SPEC_SETTING_KEY]);
193
+ if (!spec) return null;
194
+ const drawings = parseChartDrawings(pane.settings?.[CHART_DRAWINGS_SETTING_KEY]);
195
+ const viewport = parseChartInteractionViewport(
196
+ pane.settings?.[CHART_INTERACTION_VIEWPORT_SETTING_KEY],
197
+ );
198
+ return {
199
+ title: pane.title?.trim() || chartTitle(spec),
200
+ data: {
201
+ chartSpec: spec,
202
+ ...(drawings.length > 0 ? { chartDrawings: drawings } : {}),
203
+ ...(viewport ? { chartInteractionViewport: viewport } : {}),
204
+ },
205
+ };
206
+ },
207
+ restore: (data) => {
208
+ if (!Object.keys(data).every((key) => [
209
+ "chartSpec",
210
+ "chartDrawings",
211
+ "chartInteractionViewport",
212
+ ].includes(key))) return null;
213
+ const spec = parseChartSpec(data.chartSpec);
214
+ if (!spec) return null;
215
+ const drawings = parseChartDrawings(data.chartDrawings);
216
+ if (data.chartDrawings !== undefined && (!Array.isArray(data.chartDrawings) || (data.chartDrawings.length > 0 && drawings.length === 0))) return null;
217
+ const viewport = data.chartInteractionViewport === undefined
218
+ ? null
219
+ : parseChartInteractionViewport(data.chartInteractionViewport);
220
+ if (data.chartInteractionViewport !== undefined && !viewport) return null;
221
+ return {
222
+ shareData: {
223
+ chartSpec: spec,
224
+ ...(drawings.length > 0 ? { chartDrawings: drawings } : {}),
225
+ ...(viewport ? { chartInteractionViewport: viewport } : {}),
226
+ },
227
+ };
228
+ },
229
+ },
154
230
  },
155
231
  {
156
232
  id: DATA_CATALOG_TEMPLATE_ID,
@@ -179,6 +255,16 @@ const chartComposerTemplates: PaneTemplateDef[] = [
179
255
  ...(query ? { settings: { query } } : {}),
180
256
  };
181
257
  },
258
+ publicShare: {
259
+ serialize: ({ pane }) => {
260
+ const query = typeof pane.settings?.query === "string" ? pane.settings.query.trim() : "";
261
+ return { title: pane.title?.trim() || "Data Catalog", data: query ? { query } : {} };
262
+ },
263
+ restore: (data) => Object.keys(data).every((key) => key === "query")
264
+ && (data.query === undefined || typeof data.query === "string")
265
+ ? { arg: typeof data.query === "string" ? data.query : "" }
266
+ : null,
267
+ },
182
268
  },
183
269
  securityTemplate({
184
270
  id: "graph-price-pane",
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { PaneTemplateContext } from "../../../types/plugin";
3
+ import { chartComposerModule } from "./index";
4
+ import {
5
+ CHART_INTERACTION_VIEWPORT_SETTING_KEY,
6
+ CHART_SPEC_SETTING_KEY,
7
+ parseChartInteractionViewport,
8
+ parseChartSpec,
9
+ } from "./chart-spec";
10
+ import {
11
+ CHART_DRAWINGS_SETTING_KEY,
12
+ parseChartDrawings,
13
+ } from "../../../components/chart/composite/tools";
14
+ import { buildPriceChartPreset } from "./presets";
15
+
16
+ const context: PaneTemplateContext = {
17
+ config: {} as PaneTemplateContext["config"],
18
+ layout: { dockRoot: null, instances: [], floating: [], detached: [] },
19
+ focusedPaneId: null,
20
+ activeTicker: null,
21
+ activeCollectionId: null,
22
+ };
23
+
24
+ describe("chart pane sharing", () => {
25
+ test("round-trips chart setup, drawings, and the panned viewport", async () => {
26
+ const template = chartComposerModule.paneTemplates?.find((entry) => entry.id === "chart-composer-pane");
27
+ const spec = buildPriceChartPreset("AAPL");
28
+ const drawings = [{
29
+ id: "line-1",
30
+ panelId: "main",
31
+ color: "#f5a524",
32
+ points: [
33
+ { time: 1_779_000_000_000, value: 182.4 },
34
+ { time: 1_780_000_000_000, value: 205.8 },
35
+ ],
36
+ }];
37
+ const viewport = {
38
+ authoredViewportKey: "AAPL:1Y:1d",
39
+ start: "2026-04-01T00:00:00.000Z",
40
+ end: "2026-07-01T00:00:00.000Z",
41
+ adaptive: false,
42
+ };
43
+ const shared = template?.publicShare?.serialize({
44
+ pane: {
45
+ instanceId: "chart-1",
46
+ paneId: "chart-composer",
47
+ title: "AAPL Price",
48
+ settings: {
49
+ [CHART_SPEC_SETTING_KEY]: spec,
50
+ [CHART_DRAWINGS_SETTING_KEY]: drawings,
51
+ [CHART_INTERACTION_VIEWPORT_SETTING_KEY]: viewport,
52
+ privateCursor: "not-shared",
53
+ },
54
+ },
55
+ paneState: { selectedPoint: 12 },
56
+ });
57
+
58
+ expect(shared?.title).toBe("AAPL Price");
59
+ expect(shared?.data).toEqual({
60
+ chartSpec: parseChartSpec(spec),
61
+ chartDrawings: drawings,
62
+ chartInteractionViewport: viewport,
63
+ });
64
+ const options = template?.publicShare?.restore(shared!.data);
65
+ const instance = await template?.createInstance?.(context, options ?? undefined);
66
+ expect(parseChartSpec(instance?.settings?.[CHART_SPEC_SETTING_KEY])).toEqual(parseChartSpec(spec));
67
+ expect(parseChartDrawings(instance?.settings?.[CHART_DRAWINGS_SETTING_KEY])).toEqual(drawings);
68
+ expect(parseChartInteractionViewport(instance?.settings?.[CHART_INTERACTION_VIEWPORT_SETTING_KEY])).toEqual(viewport);
69
+ expect(JSON.stringify(shared)).not.toContain("privateCursor");
70
+ });
71
+ });