gloomberb 0.11.2 → 0.12.0

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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -1,140 +0,0 @@
1
- import type { NewsArticle, NewsQuery } from "../../../../news/types";
2
- import { getSharedNewsService } from "../../../../news/hooks";
3
-
4
- const ARTICLE_SEARCH_LIMIT = 8;
5
-
6
- const STOPWORDS = new Set([
7
- "a",
8
- "an",
9
- "the",
10
- "on",
11
- "of",
12
- "in",
13
- "to",
14
- "for",
15
- "and",
16
- "or",
17
- "from",
18
- "with",
19
- "about",
20
- "by",
21
- "at",
22
- "as",
23
- "is",
24
- "are",
25
- "was",
26
- "be",
27
- ]);
28
-
29
- const INTENT_WORDS = new Set([
30
- "article",
31
- "articles",
32
- "news",
33
- "headline",
34
- "headlines",
35
- "story",
36
- "stories",
37
- "rss",
38
- "feed",
39
- "feeds",
40
- "press",
41
- ]);
42
-
43
- const COMMAND_WORDS = new Set([
44
- "open",
45
- "read",
46
- "show",
47
- "find",
48
- "search",
49
- "please",
50
- "me",
51
- "latest",
52
- "recent",
53
- "lookup",
54
- "look",
55
- "up",
56
- ]);
57
-
58
- /** Pulls the freshest slice of the global feed to search over. */
59
- export const ARTICLE_SEARCH_QUERY: NewsQuery = { feed: "latest", limit: 200 };
60
-
61
- export function tokenizeArticleQuery(query: string): string[] {
62
- // `ART trump` is the command prefix plus a topic — "art" is not a search term.
63
- const withoutArtPrefix = query.replace(/^\s*art\b/i, " ");
64
- return withoutArtPrefix
65
- .toLowerCase()
66
- .split(/[^a-z0-9]+/)
67
- .filter((token) => (
68
- token.length >= 2
69
- && !STOPWORDS.has(token)
70
- && !INTENT_WORDS.has(token)
71
- && !COMMAND_WORDS.has(token)
72
- ));
73
- }
74
-
75
- export function looksLikeArticleQuery(query: string): boolean {
76
- const tokens = query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
77
- if (tokens.some((token) => INTENT_WORDS.has(token))) return true;
78
- return /^\s*art\b/i.test(query);
79
- }
80
-
81
- export function scoreArticleMatch(article: NewsArticle, tokens: readonly string[]): number {
82
- if (tokens.length === 0) return 0;
83
- const title = article.title.toLowerCase();
84
- const source = article.source.toLowerCase();
85
- const haystack = [
86
- title,
87
- source,
88
- article.summary ?? "",
89
- ...article.topics,
90
- ...article.categories,
91
- ...article.tickers,
92
- ].join(" ").toLowerCase();
93
-
94
- let score = 0;
95
- for (const token of tokens) {
96
- if (title.includes(token)) score += 10;
97
- else if (source.includes(token)) score += 6;
98
- else if (haystack.includes(token)) score += 3;
99
- else return 0;
100
- }
101
- return score;
102
- }
103
-
104
- export function searchNewsArticles(
105
- articles: readonly NewsArticle[],
106
- query: string,
107
- limit = ARTICLE_SEARCH_LIMIT,
108
- ): NewsArticle[] {
109
- const tokens = tokenizeArticleQuery(query);
110
- if (tokens.length === 0) {
111
- return [...articles]
112
- .sort((left, right) => right.publishedAt.getTime() - left.publishedAt.getTime())
113
- .slice(0, limit);
114
- }
115
-
116
- return articles
117
- .map((article) => ({ article, score: scoreArticleMatch(article, tokens) }))
118
- .filter((entry) => entry.score > 0)
119
- .sort((left, right) => {
120
- if (right.score !== left.score) return right.score - left.score;
121
- return right.article.publishedAt.getTime() - left.article.publishedAt.getTime();
122
- })
123
- .slice(0, limit)
124
- .map((entry) => entry.article);
125
- }
126
-
127
- /** Headlines already sitting in the shared news pool (firehose/RSS/breaking). */
128
- export function cachedNewsArticles(): NewsArticle[] {
129
- const service = getSharedNewsService();
130
- if (!service) return [];
131
- const pooled = service.getFirehose(undefined, 500);
132
- if (pooled.length > 0) return pooled;
133
- return service.getQueryState(ARTICLE_SEARCH_QUERY).articles;
134
- }
135
-
136
- export async function loadNewsArticles(): Promise<NewsArticle[]> {
137
- const service = getSharedNewsService();
138
- if (!service) return [];
139
- return (await service.load(ARTICLE_SEARCH_QUERY)).articles;
140
- }
@@ -1,185 +0,0 @@
1
- import type { SubstackAuthState } from "./types";
2
- import {
3
- parseErrorText,
4
- resolveSubstackUrl,
5
- storeSubstackAuth,
6
- substackFetch,
7
- SUBSTACK_ORIGIN,
8
- } from "./store";
9
-
10
- export async function requestSubstackMagicLink(email: string): Promise<void> {
11
- const normalizedEmail = email.trim();
12
- if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(normalizedEmail)) {
13
- throw new Error("Enter a valid email address");
14
- }
15
- const response = await substackFetch(`${SUBSTACK_ORIGIN}/api/v1/email-login`, {
16
- method: "POST",
17
- headers: {
18
- "content-type": "application/json",
19
- },
20
- body: JSON.stringify({ email: normalizedEmail }),
21
- });
22
- if (!response.ok) {
23
- const detail = await parseErrorText(response);
24
- throw new Error(`Substack login link failed (${response.status})${detail ? `: ${detail}` : ""}`);
25
- }
26
- }
27
-
28
- export function splitSetCookieHeader(header: string): string[] {
29
- const parts: string[] = [];
30
- let start = 0;
31
- let inExpires = false;
32
- for (let i = 0; i < header.length; i += 1) {
33
- const char = header[i];
34
- if (char === ",") {
35
- const fragment = header.slice(start, i);
36
- inExpires = /expires=/i.test(fragment) && !/;\s*(?:max-age|domain|path|secure|httponly|samesite)\b/i.test(fragment);
37
- if (!inExpires) {
38
- parts.push(fragment.trim());
39
- start = i + 1;
40
- }
41
- } else if (char === ";") {
42
- inExpires = false;
43
- }
44
- }
45
- const tail = header.slice(start).trim();
46
- if (tail) parts.push(tail);
47
- return parts;
48
- }
49
-
50
- function getSetCookieHeaders(headers: Headers): string[] {
51
- const withGetSetCookie = headers as Headers & { getSetCookie?: () => string[] };
52
- const direct = withGetSetCookie.getSetCookie?.();
53
- if (direct && direct.length > 0) return direct;
54
- const header = headers.get("set-cookie");
55
- return header ? splitSetCookieHeader(header) : [];
56
- }
57
-
58
- export function parseCookiesFromSetCookie(headers: string[]): Map<string, string> {
59
- const cookies = new Map<string, string>();
60
- for (const header of headers) {
61
- const first = header.split(";")[0] ?? "";
62
- const eq = first.indexOf("=");
63
- if (eq <= 0) continue;
64
- const name = first.slice(0, eq).trim();
65
- const value = first.slice(eq + 1).trim();
66
- if (!name) continue;
67
- cookies.set(name, value);
68
- }
69
- return cookies;
70
- }
71
-
72
- function cookieHeaderFromJar(jar: Map<string, string>): string {
73
- return [...jar.entries()].map(([name, value]) => `${name}=${value}`).join("; ");
74
- }
75
-
76
- function mergeSetCookies(cookieJar: Map<string, string>, response: Response): void {
77
- for (const [name, value] of parseCookiesFromSetCookie(getSetCookieHeaders(response.headers))) {
78
- cookieJar.set(name, value);
79
- }
80
- }
81
-
82
- async function followRedirectsForCookies(startUrl: string, cookieJar: Map<string, string>): Promise<void> {
83
- let currentUrl = startUrl;
84
- for (let redirectCount = 0; redirectCount < 8; redirectCount += 1) {
85
- const response = await substackFetch(currentUrl, {
86
- method: "GET",
87
- redirect: "manual",
88
- headers: cookieJar.size > 0 ? { cookie: cookieHeaderFromJar(cookieJar) } : undefined,
89
- });
90
- mergeSetCookies(cookieJar, response);
91
-
92
- if (response.status < 300 || response.status >= 400) break;
93
- const location = response.headers.get("location");
94
- if (!location) break;
95
- const nextUrl = resolveSubstackUrl(location, currentUrl);
96
- if (!nextUrl) break;
97
- currentUrl = nextUrl;
98
- }
99
- }
100
-
101
- export async function completeSubstackMagicLink(link: string, email: string): Promise<SubstackAuthState> {
102
- const currentUrl = resolveSubstackUrl(link);
103
- if (!currentUrl) throw new Error("Paste the full Substack magic-link URL");
104
-
105
- const cookieJar = new Map<string, string>();
106
- await followRedirectsForCookies(currentUrl, cookieJar);
107
-
108
- const sid = cookieJar.get("substack.sid");
109
- if (!sid) {
110
- throw new Error("Substack did not return a session cookie. Request a new login link and paste the full URL.");
111
- }
112
- const auth: SubstackAuthState = {
113
- email: email.trim(),
114
- sid,
115
- lli: cookieJar.get("substack.lli") ?? "1",
116
- loggedInAt: Date.now(),
117
- };
118
- storeSubstackAuth(auth);
119
- return auth;
120
- }
121
-
122
- export async function completeSubstackOtpLogin(code: string, email: string): Promise<SubstackAuthState> {
123
- const normalizedCode = code.trim();
124
- const normalizedEmail = email.trim();
125
- if (!/^\d{6}$/.test(normalizedCode)) {
126
- throw new Error("Enter the 6-digit Substack email code");
127
- }
128
- if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(normalizedEmail)) {
129
- throw new Error("Enter a valid email address");
130
- }
131
-
132
- const cookieJar = new Map<string, string>();
133
- const response = await substackFetch(`${SUBSTACK_ORIGIN}/api/v1/email-otp-login/complete`, {
134
- method: "POST",
135
- redirect: "manual",
136
- credentials: "include",
137
- headers: {
138
- "content-type": "application/json",
139
- },
140
- body: JSON.stringify({
141
- code: normalizedCode,
142
- email: normalizedEmail,
143
- redirect: "",
144
- for_pub: null,
145
- island_magic_signin: false,
146
- }),
147
- });
148
- mergeSetCookies(cookieJar, response);
149
-
150
- const isRedirect = response.status >= 300 && response.status < 400;
151
- if (!response.ok && !isRedirect) {
152
- const detail = await parseErrorText(response);
153
- throw new Error(`Substack code login failed (${response.status})${detail ? `: ${detail}` : ""}`);
154
- }
155
-
156
- let redirectUrl = resolveSubstackUrl(response.headers.get("location"));
157
- if (!redirectUrl && response.ok) {
158
- try {
159
- const payload = await response.clone().json() as { redirect?: unknown };
160
- redirectUrl = resolveSubstackUrl(payload.redirect);
161
- } catch {
162
- redirectUrl = null;
163
- }
164
- }
165
-
166
- if (!cookieJar.get("substack.sid") && redirectUrl) {
167
- await followRedirectsForCookies(redirectUrl, cookieJar);
168
- }
169
-
170
- const sid = cookieJar.get("substack.sid");
171
- if (!sid) {
172
- const capturedCookieNames = [...cookieJar.keys()];
173
- throw new Error(
174
- `Substack accepted the code but did not return a session cookie. status=${response.status} redirect=${redirectUrl ? "yes" : "no"} cookies=${capturedCookieNames.length ? capturedCookieNames.join(",") : "none"}`,
175
- );
176
- }
177
- const auth: SubstackAuthState = {
178
- email: normalizedEmail,
179
- sid,
180
- lli: cookieJar.get("substack.lli") ?? "1",
181
- loggedInAt: Date.now(),
182
- };
183
- storeSubstackAuth(auth);
184
- return auth;
185
- }
@@ -1,82 +0,0 @@
1
- import type { PersistedResourceValue } from "../../../../types/persistence";
2
- import {
3
- sortSubscriptionsByLatest,
4
- } from "../normalize";
5
- import type {
6
- SubstackArticleDetail,
7
- SubstackArticleSummary,
8
- SubstackPublication,
9
- } from "../types";
10
- import type { SubstackCachedData, SubstackHomeData, SubstackPublicationFeedPage } from "./types";
11
- import { readResource } from "./store";
12
-
13
- const PUBLICATION_ARCHIVE_MAX_CACHED_PAGES = 20;
14
-
15
- export function publicationCacheKey(publication: SubstackPublication, offset: number): string {
16
- return `${publication.baseUrl ?? publication.subdomain ?? publication.id}:offset:${offset}`;
17
- }
18
-
19
- function cachedDataFromResource<T>(entry: PersistedResourceValue<T> | null): SubstackCachedData<T> | null {
20
- if (!entry) return null;
21
- return {
22
- data: entry.value,
23
- fetchedAt: entry.fetchedAt,
24
- stale: Boolean(entry.stale),
25
- };
26
- }
27
-
28
- export function getCachedSubstackHome(): SubstackHomeData | null {
29
- const subscriptionsEntry = readResource<SubstackPublication[]>("subscriptions", "me", false);
30
- const feedEntry = readResource<SubstackArticleSummary[] | { items: SubstackArticleSummary[]; nextCursor?: string | null }>("feed", "subscribed", false);
31
- if (!subscriptionsEntry || !feedEntry) return null;
32
- const feed = Array.isArray(feedEntry.value) ? feedEntry.value : feedEntry.value.items;
33
- const nextCursor = Array.isArray(feedEntry.value) ? null : feedEntry.value.nextCursor ?? null;
34
- return {
35
- subscriptions: sortSubscriptionsByLatest(subscriptionsEntry.value, feed),
36
- feed,
37
- fetchedAt: Math.max(subscriptionsEntry.fetchedAt, feedEntry.fetchedAt),
38
- stale: Boolean(subscriptionsEntry.stale) || Boolean(feedEntry.stale),
39
- hasMore: !!nextCursor,
40
- nextCursor,
41
- };
42
- }
43
-
44
- export function getCachedSubstackPublicationFeed(
45
- publication: SubstackPublication,
46
- ): SubstackCachedData<SubstackPublicationFeedPage> | null {
47
- let offset: number | null = 0;
48
- let pageCount = 0;
49
- let merged: SubstackPublicationFeedPage | null = null;
50
- let fetchedAt = 0;
51
- let stale = false;
52
-
53
- while (offset != null && pageCount < PUBLICATION_ARCHIVE_MAX_CACHED_PAGES) {
54
- const entry: PersistedResourceValue<SubstackPublicationFeedPage> | null = readResource<SubstackPublicationFeedPage>(
55
- "publication",
56
- publicationCacheKey(publication, offset),
57
- false,
58
- );
59
- if (!entry) break;
60
- const itemsById = new Map<string, SubstackArticleSummary>();
61
- for (const article of merged?.items ?? []) itemsById.set(article.id, article);
62
- for (const article of entry.value.items) itemsById.set(article.id, article);
63
- merged = {
64
- items: [...itemsById.values()],
65
- nextOffset: entry.value.nextOffset,
66
- hasMore: entry.value.hasMore,
67
- };
68
- fetchedAt = Math.max(fetchedAt, entry.fetchedAt);
69
- stale = stale || Boolean(entry.stale);
70
- offset = entry.value.nextOffset;
71
- pageCount += 1;
72
- }
73
-
74
- if (!merged) return null;
75
- return { data: merged, fetchedAt, stale };
76
- }
77
-
78
- export function getCachedSubstackArticleDetail(
79
- article: Pick<SubstackArticleSummary, "id">,
80
- ): SubstackCachedData<SubstackArticleDetail> | null {
81
- return cachedDataFromResource(readResource<SubstackArticleDetail>("post", article.id, false));
82
- }
@@ -1,170 +0,0 @@
1
- import {
2
- normalizeFeedItems,
3
- normalizePostDetail,
4
- normalizeSubscriptions,
5
- sortSubscriptionsByLatest,
6
- } from "../normalize";
7
- import type {
8
- SubstackArticleSummary,
9
- SubstackPublication,
10
- } from "../types";
11
- import { SubstackAuthError, type SubstackCachedData, type SubstackHomeData, type SubstackPublicationFeedPage } from "./types";
12
- import { publicationCacheKey } from "./cache";
13
- import {
14
- fetchJsonAuthenticated,
15
- loadCachedResource,
16
- requireAuth,
17
- SUBSTACK_ORIGIN,
18
- } from "./store";
19
-
20
- const READER_FEED_TARGET_ITEMS = 50;
21
- const READER_FEED_MAX_PAGES = 12;
22
- const PUBLICATION_ARCHIVE_PAGE_LIMIT = 12;
23
-
24
- export async function loadSubstackHome(force = false): Promise<SubstackHomeData> {
25
- const auth = requireAuth();
26
- const [subscriptionsEntry, feedEntry] = await Promise.all([
27
- loadCachedResource("subscriptions", "me", force, async () => {
28
- const payload = await fetchJsonAuthenticated<unknown>(`${SUBSTACK_ORIGIN}/api/v1/subscriptions/page_v2`, auth);
29
- return normalizeSubscriptions(payload);
30
- }),
31
- loadCachedResource("feed", "subscribed", force, async () => {
32
- return fetchReaderFeedItems(auth, "subscribed");
33
- }),
34
- ]);
35
- return {
36
- subscriptions: sortSubscriptionsByLatest(subscriptionsEntry.data, feedEntry.data.items),
37
- feed: feedEntry.data.items,
38
- fetchedAt: Math.max(subscriptionsEntry.fetchedAt, feedEntry.fetchedAt),
39
- stale: subscriptionsEntry.stale || feedEntry.stale,
40
- hasMore: !!feedEntry.data.nextCursor,
41
- nextCursor: feedEntry.data.nextCursor,
42
- };
43
- }
44
-
45
- export async function loadSubstackHomeMore(cursor: string): Promise<{ items: SubstackArticleSummary[]; nextCursor: string | null }> {
46
- return fetchReaderFeedPage(requireAuth(), "subscribed", cursor);
47
- }
48
-
49
- function payloadCursor(payload: unknown): string | null {
50
- const record = payload && typeof payload === "object" && !Array.isArray(payload)
51
- ? payload as Record<string, unknown>
52
- : null;
53
- const cursor = record?.nextCursor ?? record?.next_cursor;
54
- return typeof cursor === "string" && cursor.trim() ? cursor : null;
55
- }
56
-
57
- async function fetchReaderFeedPage(
58
- auth: ReturnType<typeof requireAuth>,
59
- tab: string,
60
- cursor: string | null = null,
61
- ): Promise<{ items: SubstackArticleSummary[]; nextCursor: string | null }> {
62
- const url = new URL(`${SUBSTACK_ORIGIN}/api/v1/reader/feed`);
63
- url.searchParams.set("tab", tab);
64
- if (cursor) url.searchParams.set("cursor", cursor);
65
- const payload = await fetchJsonAuthenticated<unknown>(url.toString(), auth);
66
- const nextCursor = payloadCursor(payload);
67
- return {
68
- items: normalizeFeedItems(payload),
69
- nextCursor: nextCursor && nextCursor !== cursor ? nextCursor : null,
70
- };
71
- }
72
-
73
- async function fetchReaderFeedItems(
74
- auth: ReturnType<typeof requireAuth>,
75
- tab: string,
76
- ): Promise<{ items: SubstackArticleSummary[]; nextCursor: string | null }> {
77
- const collected = new Map<string, SubstackArticleSummary>();
78
- let cursor: string | null = null;
79
- for (let page = 0; page < READER_FEED_MAX_PAGES && collected.size < READER_FEED_TARGET_ITEMS; page += 1) {
80
- const result = await fetchReaderFeedPage(auth, tab, cursor);
81
- for (const article of result.items) {
82
- collected.set(article.id, article);
83
- if (collected.size >= READER_FEED_TARGET_ITEMS) break;
84
- }
85
- if (!result.nextCursor) {
86
- cursor = null;
87
- break;
88
- }
89
- cursor = result.nextCursor;
90
- }
91
- return { items: [...collected.values()], nextCursor: cursor };
92
- }
93
-
94
- export async function loadSubstackPublicationFeed(
95
- publication: SubstackPublication,
96
- force = false,
97
- offset = 0,
98
- ): Promise<SubstackCachedData<SubstackPublicationFeedPage>> {
99
- const auth = requireAuth();
100
- const baseUrl = publication.baseUrl;
101
- if (!baseUrl) {
102
- return {
103
- data: { items: [], nextOffset: null, hasMore: false },
104
- fetchedAt: Date.now(),
105
- stale: false,
106
- };
107
- }
108
- return loadCachedResource("publication", publicationCacheKey(publication, offset), force, async () => {
109
- const url = new URL("/api/v1/archive", baseUrl);
110
- url.searchParams.set("sort", "new");
111
- url.searchParams.set("limit", String(PUBLICATION_ARCHIVE_PAGE_LIMIT));
112
- if (offset > 0) url.searchParams.set("offset", String(offset));
113
- const payload = await fetchJsonAuthenticated<unknown>(url.toString(), auth);
114
- const items = normalizeFeedItems(payload, publication);
115
- const hasMore = items.length >= PUBLICATION_ARCHIVE_PAGE_LIMIT;
116
- return {
117
- items,
118
- nextOffset: hasMore ? offset + PUBLICATION_ARCHIVE_PAGE_LIMIT : null,
119
- hasMore,
120
- };
121
- });
122
- }
123
-
124
- function detailCandidateUrls(article: SubstackArticleSummary): string[] {
125
- const urls: string[] = [];
126
- if (/^\d+$/.test(article.id)) {
127
- urls.push(new URL(`/api/v1/posts/by-id/${article.id}`, SUBSTACK_ORIGIN).toString());
128
- }
129
- const baseUrl = article.publicationBaseUrl
130
- ?? (article.url ? new URL(article.url).origin : null);
131
- if (baseUrl) {
132
- if (/^\d+$/.test(article.id)) {
133
- urls.push(new URL(`/api/v1/posts/by-id/${article.id}`, baseUrl).toString());
134
- }
135
- if (article.slug) {
136
- urls.push(new URL(`/api/v1/posts/${article.slug}`, baseUrl).toString());
137
- }
138
- if (article.url) {
139
- try {
140
- const pathSlug = new URL(article.url).pathname.match(/\/p\/([^/?#]+)/)?.[1];
141
- if (pathSlug) urls.push(new URL(`/api/v1/posts/${decodeURIComponent(pathSlug)}`, baseUrl).toString());
142
- } catch {
143
- // Ignore malformed article URLs; the direct post id/slug paths above still apply.
144
- }
145
- }
146
- }
147
- return [...new Set(urls)];
148
- }
149
-
150
- export async function loadSubstackArticleDetail(
151
- article: SubstackArticleSummary,
152
- force = false,
153
- ): Promise<SubstackCachedData<ReturnType<typeof normalizePostDetail>>> {
154
- const auth = requireAuth();
155
- return loadCachedResource("post", article.id, force, async () => {
156
- const urls = detailCandidateUrls(article);
157
- const errors: string[] = [];
158
- for (const url of urls) {
159
- try {
160
- const payload = await fetchJsonAuthenticated<unknown>(url, auth);
161
- return normalizePostDetail(payload, article);
162
- } catch (error) {
163
- if (error instanceof SubstackAuthError) throw error;
164
- errors.push(error instanceof Error ? error.message : String(error));
165
- }
166
- }
167
- if (article.bodyHtml || urls.length === 0) return normalizePostDetail(article, article);
168
- throw new Error(errors[0] ?? "Substack post content unavailable");
169
- });
170
- }