gloomberb 0.11.3 → 0.13.1

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 (574) hide show
  1. package/README.md +8 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -2
  4. package/src/api-client/auth.ts +12 -1
  5. package/src/api-client/data.ts +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +139 -3
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/research-activity.ts +61 -0
  11. package/src/api-client/types.ts +197 -0
  12. package/src/app/pane-runtime/index.tsx +15 -12
  13. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  14. package/src/app/pane-runtime/layout-placement.ts +13 -0
  15. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  16. package/src/app.tsx +60 -18
  17. package/src/architecture/browser-entry-graph.test.ts +96 -0
  18. package/src/brokers/profile-form.test.ts +25 -27
  19. package/src/brokers/test-broker.ts +68 -0
  20. package/src/cli/commands/plugins.ts +43 -14
  21. package/src/cli/desktop-pane-shot.ts +362 -24
  22. package/src/cli/index.ts +1 -1
  23. package/src/cli/pane-functions/capabilities.ts +126 -43
  24. package/src/cli/pane-functions/catalog.ts +16 -4
  25. package/src/cli/pane-functions/discovery.ts +1 -0
  26. package/src/cli/pane-functions/dom.test.ts +41 -0
  27. package/src/cli/pane-functions/dom.ts +199 -0
  28. package/src/cli/pane-functions/headless.test.ts +247 -0
  29. package/src/cli/pane-functions/headless.ts +423 -0
  30. package/src/cli/pane-functions/index.test.ts +20 -0
  31. package/src/cli/pane-functions/index.ts +10 -6
  32. package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
  33. package/src/cli/pane-functions/intraday-shot.ts +334 -0
  34. package/src/cli/pane-functions/options.ts +27 -2
  35. package/src/cli/pane-functions/report.test.ts +29 -1
  36. package/src/cli/pane-functions/report.ts +69 -4
  37. package/src/cli/pane-functions/resolver.ts +26 -8
  38. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  39. package/src/cli/pane-functions/screenshot.ts +428 -42
  40. package/src/cli/restore-plugins.ts +41 -0
  41. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  42. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  43. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  44. package/src/components/chart/composite/composite-chart.test.tsx +136 -21
  45. package/src/components/chart/composite/composite-chart.tsx +378 -215
  46. package/src/components/chart/composite/format.ts +8 -2
  47. package/src/components/chart/composite/interactions.test.ts +68 -102
  48. package/src/components/chart/composite/interactions.ts +214 -264
  49. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  50. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  51. package/src/components/chart/composite/rasterizer.ts +33 -0
  52. package/src/components/chart/composite/renderers.test.ts +51 -4
  53. package/src/components/chart/composite/scene.test.ts +128 -2
  54. package/src/components/chart/composite/scene.ts +149 -19
  55. package/src/components/chart/composite/text-renderer.ts +16 -2
  56. package/src/components/chart/composite/time-scale.ts +163 -62
  57. package/src/components/chart/composite/types.ts +51 -1
  58. package/src/components/chart/core/data.ts +9 -13
  59. package/src/components/chart/core/date-window.ts +7 -1
  60. package/src/components/chart/core/pointer.ts +3 -0
  61. package/src/components/chart/core/types.ts +0 -45
  62. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  63. package/src/components/chart/native/kitty/index.test.ts +0 -188
  64. package/src/components/chart/native/raster/crosshair.ts +6 -5
  65. package/src/components/chart/native/raster/primitives.ts +0 -23
  66. package/src/components/chart/native/renderer-selection.ts +81 -31
  67. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  68. package/src/components/chart/static/chart-surface.tsx +150 -0
  69. package/src/components/chart/static/index.ts +8 -0
  70. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  71. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  72. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  73. package/src/components/chart/static/series.ts +49 -0
  74. package/src/components/command-bar/action-runtime.ts +1 -3
  75. package/src/components/command-bar/assist/model.test.ts +17 -2
  76. package/src/components/command-bar/assist/model.ts +40 -9
  77. package/src/components/command-bar/commands/direct/index.ts +1 -1
  78. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  79. package/src/components/command-bar/commands/registry.ts +0 -11
  80. package/src/components/command-bar/helpers.test.ts +2 -2
  81. package/src/components/command-bar/helpers.ts +2 -6
  82. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  83. package/src/components/command-bar/list/badge.test.ts +54 -0
  84. package/src/components/command-bar/list/badge.ts +62 -0
  85. package/src/components/command-bar/list/model.test.ts +103 -0
  86. package/src/components/command-bar/list/model.ts +61 -4
  87. package/src/components/command-bar/list/view.test.tsx +113 -0
  88. package/src/components/command-bar/list/view.tsx +147 -131
  89. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  90. package/src/components/command-bar/panel/body.tsx +51 -65
  91. package/src/components/command-bar/panel/index.tsx +48 -13
  92. package/src/components/command-bar/panel/layout.test.ts +139 -0
  93. package/src/components/command-bar/panel/layout.ts +65 -28
  94. package/src/components/command-bar/panel/native-surface.ts +24 -0
  95. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  96. package/src/components/command-bar/panel/runtime.ts +6 -3
  97. package/src/components/command-bar/panel/state.ts +41 -7
  98. package/src/components/command-bar/panel/types.ts +7 -1
  99. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  100. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  101. package/src/components/command-bar/routes/root/results.ts +30 -28
  102. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  103. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  104. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  105. package/src/components/command-bar/routes/root/selection.ts +1 -12
  106. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  107. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  108. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  109. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  110. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  111. package/src/components/command-bar/routing/actions.ts +1 -81
  112. package/src/components/command-bar/routing/launch-request.ts +1 -1
  113. package/src/components/command-bar/routing/list-state.ts +6 -21
  114. package/src/components/command-bar/selection-runtime.ts +1 -1
  115. package/src/components/command-bar/surface/assist.test.tsx +34 -19
  116. package/src/components/command-bar/surface/environment.ts +8 -1
  117. package/src/components/command-bar/surface/index.test.tsx +84 -68
  118. package/src/components/command-bar/surface/index.tsx +33 -36
  119. package/src/components/command-bar/surface/test-harness.tsx +38 -7
  120. package/src/components/command-bar/theme-picker.tsx +43 -15
  121. package/src/components/command-bar/view-model.test.ts +53 -8
  122. package/src/components/command-bar/view-model.ts +43 -14
  123. package/src/components/command-bar/workflow/tickers.ts +1 -0
  124. package/src/components/command-bar/workflow/types.ts +1 -1
  125. package/src/components/data-table/view.test.tsx +45 -6
  126. package/src/components/data-table/view.tsx +55 -9
  127. package/src/components/index.ts +10 -2
  128. package/src/components/input-search-bar.test.tsx +36 -2
  129. package/src/components/input-search-bar.tsx +18 -1
  130. package/src/components/layout/header.test.tsx +57 -10
  131. package/src/components/layout/header.tsx +292 -149
  132. package/src/components/layout/market-summary.test.ts +81 -0
  133. package/src/components/layout/market-summary.ts +109 -0
  134. package/src/components/layout/shell/chrome.ts +96 -1
  135. package/src/components/layout/shell/index.tsx +11 -3
  136. package/src/components/layout/shell/menu.ts +9 -0
  137. package/src/components/layout/shell/pane/actions.ts +29 -0
  138. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  139. package/src/components/layout/shell/shortcuts.ts +3 -0
  140. package/src/components/layout/status-bar.test.tsx +9 -8
  141. package/src/components/layout/status-bar.tsx +85 -54
  142. package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
  143. package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
  144. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  145. package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
  146. package/src/components/onboarding/onboarding-wizard.tsx +49 -14
  147. package/src/components/price-sparkline/index.ts +61 -34
  148. package/src/components/price-sparkline/view.tsx +1 -1
  149. package/src/components/sign-in-gate.test.tsx +128 -0
  150. package/src/components/sign-in-gate.tsx +149 -0
  151. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  152. package/src/components/ticker/badge/list.tsx +17 -3
  153. package/src/components/ticker/list-table-view.tsx +6 -3
  154. package/src/components/ui/data-table/index.tsx +3 -2
  155. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  156. package/src/components/ui/frame.tsx +28 -0
  157. package/src/components/ui/index.ts +3 -0
  158. package/src/components/ui/native-select.tsx +20 -10
  159. package/src/components/ui/select-button.tsx +138 -0
  160. package/src/components/ui/status.tsx +2 -2
  161. package/src/components/ui/table-layout.ts +1 -1
  162. package/src/components/use-double-click-activation.ts +7 -3
  163. package/src/core/app-services.ts +3 -3
  164. package/src/data/config/layout.ts +12 -6
  165. package/src/data/config/store/index.test.ts +120 -0
  166. package/src/data/config/store/migrations.ts +41 -0
  167. package/src/data/config/store/node.ts +7 -1
  168. package/src/data/config/store/normalize.ts +9 -3
  169. package/src/i18n/es.ts +6 -2
  170. package/src/i18n/ja.ts +6 -2
  171. package/src/i18n/ko.ts +6 -2
  172. package/src/i18n/zh-cn.ts +6 -2
  173. package/src/i18n/zh-tw.ts +6 -2
  174. package/src/layout-marketplace/pane-imagery.ts +1 -0
  175. package/src/layout-marketplace/payload.test.ts +17 -0
  176. package/src/layout-marketplace/payload.ts +40 -5
  177. package/src/market-data/coordinator/index.ts +3 -2
  178. package/src/market-data/coordinator/quotes.ts +173 -66
  179. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  180. package/src/pane-settings.ts +1 -1
  181. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  182. package/src/plugins/builtin/analytics/view.tsx +1 -1
  183. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  184. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  185. package/src/plugins/builtin/cds/client.test.ts +20 -0
  186. package/src/plugins/builtin/cds/client.ts +13 -3
  187. package/src/plugins/builtin/cds/headless.test.ts +30 -0
  188. package/src/plugins/builtin/cds/headless.ts +113 -0
  189. package/src/plugins/builtin/cds/index.tsx +4 -0
  190. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  191. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
  192. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  193. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  194. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  195. package/src/plugins/builtin/cloud/askg/protocol.test.ts +189 -0
  196. package/src/plugins/builtin/cloud/askg/protocol.ts +274 -0
  197. package/src/plugins/builtin/cloud/auth-dialog.tsx +19 -227
  198. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  199. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
  200. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  201. package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
  202. package/src/plugins/builtin/composite-plugins.ts +14 -1
  203. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  204. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  205. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  206. package/src/plugins/builtin/credit-conditions/client.ts +1 -1
  207. package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
  208. package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
  209. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  210. package/src/plugins/builtin/credit-conditions/index.tsx +6 -21
  211. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  212. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  213. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  214. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  215. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  216. package/src/plugins/builtin/earnings-calls/pane.tsx +760 -0
  217. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  218. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  219. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +337 -0
  220. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  221. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  222. package/src/plugins/builtin/econ/index.tsx +2 -1
  223. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  224. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  225. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  226. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  227. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  228. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  229. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  230. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  231. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  232. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  233. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  234. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  235. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  236. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  237. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  238. package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
  239. package/src/plugins/builtin/fear-greed/headless.ts +84 -0
  240. package/src/plugins/builtin/fear-greed/index.tsx +4 -0
  241. package/src/plugins/builtin/help/index.tsx +4 -0
  242. package/src/plugins/builtin/holders/index.tsx +1 -0
  243. package/src/plugins/builtin/insider/index.tsx +1 -0
  244. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  245. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  246. package/src/plugins/builtin/market-movers/client.ts +76 -0
  247. package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
  248. package/src/plugins/builtin/market-movers/headless.ts +73 -0
  249. package/src/plugins/builtin/market-movers/index.tsx +11 -51
  250. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  251. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  252. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  253. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  254. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  255. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  256. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  257. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  258. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  259. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  260. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  261. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  262. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  263. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  264. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  265. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  266. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  267. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  268. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  269. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  270. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  271. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  272. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  273. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  274. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  275. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  276. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  277. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  278. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  279. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  280. package/src/plugins/builtin/options/index.tsx +1 -0
  281. package/src/plugins/builtin/options/view.test.tsx +29 -1
  282. package/src/plugins/builtin/options/view.tsx +1 -22
  283. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  284. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  285. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  286. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  287. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  288. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  289. package/src/plugins/builtin/plugin-module.ts +9 -1
  290. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
  291. package/src/plugins/builtin/research/event-model.ts +245 -0
  292. package/src/plugins/builtin/research/events-headless.test.ts +32 -0
  293. package/src/plugins/builtin/research/events-headless.ts +87 -0
  294. package/src/plugins/builtin/research/index.tsx +8 -0
  295. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  296. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  297. package/src/plugins/builtin/research-search/data.ts +109 -0
  298. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  299. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  300. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  301. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  302. package/src/plugins/builtin/research-search/index.tsx +85 -0
  303. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  304. package/src/plugins/builtin/research-search/model.ts +291 -0
  305. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  306. package/src/plugins/builtin/research-search/pane.tsx +682 -0
  307. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  308. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  309. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  310. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  311. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  312. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  313. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  314. package/src/plugins/builtin/scanner/headless.test.ts +59 -0
  315. package/src/plugins/builtin/scanner/headless.ts +125 -0
  316. package/src/plugins/builtin/scanner/index.ts +4 -0
  317. package/src/plugins/builtin/sec/index.tsx +1 -0
  318. package/src/plugins/builtin/sectors/client.ts +54 -0
  319. package/src/plugins/builtin/sectors/headless.test.ts +28 -0
  320. package/src/plugins/builtin/sectors/headless.ts +80 -0
  321. package/src/plugins/builtin/sectors/index.tsx +6 -51
  322. package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
  323. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  324. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  325. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  326. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  327. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  328. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  329. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
  330. package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
  331. package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
  332. package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
  333. package/src/plugins/builtin/volatility/client.ts +16 -3
  334. package/src/plugins/builtin/volatility/headless.test.ts +35 -0
  335. package/src/plugins/builtin/volatility/headless.ts +75 -0
  336. package/src/plugins/builtin/volatility/index.tsx +5 -1
  337. package/src/plugins/builtin/world-indices/client.ts +43 -0
  338. package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
  339. package/src/plugins/builtin/world-indices/headless.ts +109 -0
  340. package/src/plugins/builtin/world-indices/index.tsx +4 -0
  341. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  342. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  343. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  344. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  345. package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
  346. package/src/plugins/builtin/yield-curve/headless.ts +67 -0
  347. package/src/plugins/builtin/yield-curve/index.tsx +7 -9
  348. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
  349. package/src/plugins/bundle.test.ts +166 -0
  350. package/src/plugins/bundle.ts +141 -0
  351. package/src/plugins/catalog-backend.test.ts +26 -0
  352. package/src/plugins/catalog-backend.ts +9 -2
  353. package/src/plugins/catalog-browser.test.ts +1 -13
  354. package/src/plugins/catalog-browser.ts +14 -0
  355. package/src/plugins/catalog-manifest.test.ts +58 -0
  356. package/src/plugins/catalog-manifest.ts +99 -0
  357. package/src/plugins/catalog-ui.ts +20 -10
  358. package/src/plugins/catalog.ts +9 -2
  359. package/src/plugins/current-target.ts +28 -0
  360. package/src/plugins/host-contract.ts +46 -0
  361. package/src/plugins/host-link.ts +158 -0
  362. package/src/plugins/host-modules.ts +73 -0
  363. package/src/plugins/loader.ts +90 -23
  364. package/src/plugins/ownership.ts +1 -0
  365. package/src/plugins/prediction-markets/index.tsx +1 -0
  366. package/src/plugins/registry/context.ts +3 -0
  367. package/src/plugins/registry/contributions.ts +27 -0
  368. package/src/plugins/registry/index.test.ts +25 -0
  369. package/src/plugins/registry/index.ts +8 -0
  370. package/src/plugins/registry/pane-settings.ts +6 -13
  371. package/src/plugins/seed.test.ts +83 -0
  372. package/src/plugins/seed.ts +83 -0
  373. package/src/public/broker.ts +37 -0
  374. package/src/public/market-data.ts +9 -0
  375. package/src/public/pane-hooks.ts +57 -0
  376. package/src/public/public-api.test.ts +285 -0
  377. package/src/public/react.ts +62 -0
  378. package/src/public/test-support.ts +23 -0
  379. package/src/public/time-series.ts +8 -0
  380. package/src/public/utils.ts +77 -0
  381. package/src/remote/layout-helpers.ts +6 -8
  382. package/src/remote/resources.ts +2 -0
  383. package/src/remote/schema.test.ts +65 -0
  384. package/src/remote/schema.ts +323 -39
  385. package/src/remote/semantic-tree.test.tsx +105 -0
  386. package/src/remote/semantic-tree.tsx +53 -35
  387. package/src/remote/types.ts +49 -2
  388. package/src/renderers/browser/app-services.ts +3 -0
  389. package/src/renderers/browser/config-host.ts +24 -8
  390. package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
  391. package/src/renderers/browser/deeplink-bridge.ts +15 -3
  392. package/src/renderers/browser/main.tsx +8 -0
  393. package/src/renderers/browser/research-entry.test.ts +8 -0
  394. package/src/renderers/browser/research-entry.ts +7 -0
  395. package/src/renderers/cloudflare/worker.test.ts +19 -0
  396. package/src/renderers/cloudflare/worker.ts +1 -1
  397. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
  398. package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
  399. package/src/renderers/electrobun/bun/external-plugins.ts +156 -0
  400. package/src/renderers/electrobun/bun/index.ts +5 -2
  401. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  402. package/src/renderers/electrobun/view/build-assets.ts +0 -3
  403. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +114 -11
  404. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  405. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  406. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  407. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  408. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  409. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  410. package/src/renderers/electrobun/view/main.tsx +27 -2
  411. package/src/renderers/electrobun/view/styles.css +5 -0
  412. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  413. package/src/renderers/opentui/chart-surface.tsx +5 -1
  414. package/src/renderers/opentui/host.tsx +16 -2
  415. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  416. package/src/renderers/opentui/interaction-performance.ts +188 -0
  417. package/src/renderers/opentui/start.tsx +26 -1
  418. package/src/renderers/opentui/test-utils.tsx +13 -0
  419. package/src/renderers/opentui/toast-host.tsx +26 -2
  420. package/src/renderers/opentui/ui-host.tsx +1 -0
  421. package/src/sources/provider-router/search.test.ts +191 -0
  422. package/src/sources/provider-router/search.ts +92 -39
  423. package/src/state/app/context/index.tsx +2 -0
  424. package/src/state/app/context/selector.test.tsx +33 -1
  425. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  426. package/src/state/hooks/quote-streaming.ts +106 -30
  427. package/src/state/pane-table-export-registry.ts +19 -0
  428. package/src/sync/core-contributors.test.ts +111 -0
  429. package/src/sync/core-contributors.ts +155 -1
  430. package/src/theme/color-utils.ts +1 -1
  431. package/src/theme/colors.ts +50 -0
  432. package/src/theme/themes.test.ts +15 -2
  433. package/src/theme/themes.ts +19 -1
  434. package/src/tickers/search/index.test.ts +26 -0
  435. package/src/tickers/search/index.ts +42 -17
  436. package/src/tickers/search/ranking.ts +14 -0
  437. package/src/time-series/chart-data.test.ts +216 -0
  438. package/src/time-series/resolution.test.ts +25 -26
  439. package/src/time-series/resolution.ts +41 -10
  440. package/src/time-series/resolve.test.ts +118 -1
  441. package/src/time-series/resolve.ts +21 -16
  442. package/src/time-series/types.ts +3 -1
  443. package/src/time-series/use-chart-resolution.ts +5 -0
  444. package/src/types/config.ts +125 -3
  445. package/src/types/data-provider.ts +11 -0
  446. package/src/types/headless.ts +161 -0
  447. package/src/types/plugin.ts +102 -0
  448. package/src/ui/host.tsx +12 -1
  449. package/src/ui/index.tsx +1 -0
  450. package/src/ui/toast.tsx +8 -4
  451. package/src/utils/format.ts +35 -0
  452. package/src/utils/fuzzy-search.test.ts +94 -0
  453. package/src/utils/fuzzy-search.ts +81 -22
  454. package/src/utils/shortcut-labels.ts +14 -0
  455. package/src/components/chart/core/axis-format.ts +0 -157
  456. package/src/components/chart/core/index.test.ts +0 -1075
  457. package/src/components/chart/core/projection.ts +0 -357
  458. package/src/components/chart/core/render-utils.ts +0 -4
  459. package/src/components/chart/core/renderer.ts +0 -244
  460. package/src/components/chart/core/scene.ts +0 -180
  461. package/src/components/chart/core/time-axis.ts +0 -330
  462. package/src/components/chart/cursor-motion.ts +0 -47
  463. package/src/components/chart/indicators/types.ts +0 -26
  464. package/src/components/chart/market-session.ts +0 -176
  465. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  466. package/src/components/chart/multi-line/renderer.ts +0 -161
  467. package/src/components/chart/multi-line/scene.ts +0 -188
  468. package/src/components/chart/native/raster/layout.ts +0 -129
  469. package/src/components/chart/native/raster/price-chart.ts +0 -244
  470. package/src/components/chart/rendering/chart-draw.ts +0 -270
  471. package/src/components/chart/rendering/geometry.ts +0 -95
  472. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  473. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  474. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  475. package/src/components/chart/static/chart/surface.tsx +0 -358
  476. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  477. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  478. package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
  479. package/src/plugins/broker-sync/native-loader.ts +0 -10
  480. package/src/plugins/broker-sync/normalize.ts +0 -308
  481. package/src/plugins/broker-sync/public.ts +0 -122
  482. package/src/plugins/broker-sync/robinhood-native.ts +0 -367
  483. package/src/plugins/broker-sync/robinhood.ts +0 -114
  484. package/src/plugins/broker-sync/simplefin-native.ts +0 -201
  485. package/src/plugins/broker-sync/simplefin.ts +0 -70
  486. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  487. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  488. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  489. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  490. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  491. package/src/plugins/builtin/substack/api/store.ts +0 -186
  492. package/src/plugins/builtin/substack/api/types.ts +0 -42
  493. package/src/plugins/builtin/substack/api.test.ts +0 -298
  494. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  495. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  496. package/src/plugins/builtin/substack/content.ts +0 -478
  497. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  498. package/src/plugins/builtin/substack/index.tsx +0 -49
  499. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  500. package/src/plugins/builtin/substack/model.test.ts +0 -155
  501. package/src/plugins/builtin/substack/normalize.ts +0 -343
  502. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  503. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  504. package/src/plugins/builtin/substack/pane.tsx +0 -547
  505. package/src/plugins/builtin/substack/read-state.ts +0 -30
  506. package/src/plugins/builtin/substack/table.ts +0 -111
  507. package/src/plugins/builtin/substack/types.ts +0 -71
  508. package/src/plugins/builtin/substack/utils.ts +0 -131
  509. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  510. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  511. package/src/plugins/ibkr/account-cache.ts +0 -24
  512. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  513. package/src/plugins/ibkr/config.test.ts +0 -79
  514. package/src/plugins/ibkr/config.ts +0 -250
  515. package/src/plugins/ibkr/dialogs.tsx +0 -40
  516. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  517. package/src/plugins/ibkr/flex/index.ts +0 -367
  518. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  519. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  520. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  521. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  522. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  523. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  524. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  525. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  526. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  527. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  528. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  529. package/src/plugins/ibkr/gateway/history.ts +0 -120
  530. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  531. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  532. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  533. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  534. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  535. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  536. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  537. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  538. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  539. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  540. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  541. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  542. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  543. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  544. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  545. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  546. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  547. package/src/plugins/ibkr/gateway/types.ts +0 -27
  548. package/src/plugins/ibkr/index.test.tsx +0 -162
  549. package/src/plugins/ibkr/index.tsx +0 -159
  550. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  551. package/src/plugins/ibkr/instance-selection.ts +0 -36
  552. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  553. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  554. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  555. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  556. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  557. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  558. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  559. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  560. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  561. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  562. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  563. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  564. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  565. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  566. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  567. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  568. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  569. package/src/plugins/ibkr/trade/utils.ts +0 -144
  570. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  571. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  572. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  573. package/src/plugins/ibkr/trading/state.ts +0 -335
  574. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
@@ -1,155 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { extractArticleContent } from "./content";
3
- import {
4
- articleMatchesPublication,
5
- normalizeFeedItems,
6
- normalizePostDetail,
7
- normalizeSubscriptions,
8
- sortSubscriptionsByLatest,
9
- } from "./normalize";
10
-
11
- describe("Substack article extraction", () => {
12
- test("keeps article structure while removing chrome and image-only links", () => {
13
- const attrs = JSON.stringify({
14
- url: "https://x.com/jukan05/status/2062809398284812532?s=46",
15
- full_text: "* Important: SK hynix has no plans to add any new NAND flash capacity.\n\n=> Pure-play NAND manufacturers add capacity.",
16
- username: "jukan05",
17
- name: "Jukan @COMPUTEX",
18
- date: "2026-06-05T08:11:00.000Z",
19
- photos: [{ img_url: "https://pbs.substack.com/media/HKCSNYaa8AAq-06.jpg" }],
20
- quoted_tweet: {
21
- full_text: "[Exclusive] Jensen Huang asked Hynix to make more.",
22
- username: "jukan05",
23
- },
24
- }).replace(/"/g, """);
25
- const content = extractArticleContent(`
26
- <article>
27
- <script>window.bad = true</script>
28
- <h1>Ignored detail title</h1>
29
- <h2>Victory Giant: -11% as US weighs subsidies</h2>
30
- <p>We bought <a href="/p/company">$NVDA</a> after earnings.</p>
31
- <div class="twitter-embed" data-attrs="${attrs}" data-component-name="Twitter2ToDOM"></div>
32
- <blockquote>Seems very knee jerkish - subsidies seem very small.</blockquote>
33
- <figure>
34
- <a href="https://substackcdn.com/image/fetch/$s_!abc/f_auto,q_auto:good/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fchart.png">image</a>
35
- <img src="/company-card.png" alt="CQME chart" />
36
- </figure>
37
- <p>See <a href="https://example.com/report">the report</a>.</p>
38
- </article>
39
- `, {
40
- baseUrl: "https://collyerbridge.substack.com",
41
- title: "Ignored detail title",
42
- });
43
-
44
- expect(content.text).toContain("$NVDA");
45
- expect(content.text).toContain("Seems very knee jerkish");
46
- expect(content.text).not.toContain("Ignored detail title");
47
- expect(content.text).not.toContain("window.bad");
48
- expect(content.text).not.toContain("substackcdn.com/image/fetch");
49
- expect(content.imageUrls).toEqual(["https://collyerbridge.substack.com/company-card.png"]);
50
- expect(content.linkUrls).toEqual([
51
- "https://collyerbridge.substack.com/p/company",
52
- "https://example.com/report",
53
- ]);
54
- expect(content.blocks.map((block) => block.type)).toEqual([
55
- "heading",
56
- "paragraph",
57
- "embed",
58
- "quote",
59
- "image",
60
- "paragraph",
61
- ]);
62
- expect(content.blocks[2]).toEqual({
63
- type: "embed",
64
- kind: "tweet",
65
- text: "* Important: SK hynix has no plans to add any new NAND flash capacity.\n\n=> Pure-play NAND manufacturers add capacity.\n\nQuoted: [Exclusive] Jensen Huang asked Hynix to make more.",
66
- url: "https://x.com/jukan05/status/2062809398284812532?s=46",
67
- username: "jukan05",
68
- authorName: "Jukan @COMPUTEX",
69
- dateLabel: "Jun 5, 2026",
70
- imageUrls: ["https://pbs.substack.com/media/HKCSNYaa8AAq-06.jpg"],
71
- });
72
- });
73
- });
74
-
75
- describe("Substack feed normalization", () => {
76
- test("normalizes reader rows and orders subscriptions by newest matching post", () => {
77
- const subscriptions = normalizeSubscriptions({
78
- result: {
79
- subscriptions: [
80
- { id: "sa", publication_id: "a" },
81
- { id: "sb", publication_id: "b" },
82
- ],
83
- publicationMap: {
84
- a: { id: "a", name: "Alpha", base_url: "https://alpha.example" },
85
- b: { id: "b", name: "Beta", base_url: "https://beta.example" },
86
- },
87
- },
88
- });
89
- const feed = normalizeFeedItems({
90
- items: [
91
- {
92
- post: {
93
- id: "older",
94
- title: "Older",
95
- post_date: "2026-06-01T10:00:00Z",
96
- publication: { id: "a", name: "Alpha", base_url: "https://alpha.example" },
97
- },
98
- },
99
- {
100
- post: {
101
- id: "newer",
102
- title: "Semis and $NVDA",
103
- subtitle: "A quick note",
104
- post_date: "2026-06-05T10:00:00Z",
105
- canonical_url: "https://beta.example/p/nvda",
106
- slug: "nvda",
107
- wordcount: 560,
108
- publication: { id: "b", name: "Beta", base_url: "https://beta.example" },
109
- },
110
- },
111
- ],
112
- });
113
-
114
- const sorted = sortSubscriptionsByLatest(subscriptions, feed);
115
- expect(feed[1]).toMatchObject({
116
- id: "newer",
117
- title: "Semis and $NVDA",
118
- publicationName: "Beta",
119
- publicationBaseUrl: "https://beta.example",
120
- url: "https://beta.example/p/nvda",
121
- readMinutes: 3,
122
- });
123
- expect(sorted.map((publication) => publication.name)).toEqual(["Beta", "Alpha"]);
124
- expect(articleMatchesPublication(feed[1]!, sorted[0]!)).toBe(true);
125
- });
126
-
127
- test("merges fetched post body into an existing summary", () => {
128
- const summary = normalizeFeedItems({
129
- items: [
130
- {
131
- post: {
132
- id: 99,
133
- title: "Macro Links",
134
- post_date: "2026-06-05T10:00:00Z",
135
- publication: { id: "macro", name: "Macro", base_url: "https://macro.example" },
136
- },
137
- },
138
- ],
139
- })[0]!;
140
-
141
- const detail = normalizePostDetail({
142
- id: 99,
143
- title: "Macro Links",
144
- body_html: "<p>$SPY broke higher. <a href=\"/p/chart\">Chart</a></p>",
145
- publication: { id: "macro", name: "Macro", base_url: "https://macro.example" },
146
- }, summary);
147
-
148
- expect(detail.contentText).toContain("$SPY");
149
- expect(detail.linkUrls).toEqual(["https://macro.example/p/chart"]);
150
- expect(detail.contentBlocks).toEqual([
151
- { type: "paragraph", text: "$SPY broke higher. Chart" },
152
- ]);
153
- expect(detail.id).toBe("99");
154
- });
155
- });
@@ -1,343 +0,0 @@
1
- import {
2
- estimateReadingMinutes,
3
- extractArticleContent,
4
- wordCount,
5
- } from "./content";
6
- import type {
7
- SubstackArticleDetail,
8
- SubstackArticleSummary,
9
- SubstackPublication,
10
- } from "./types";
11
- import {
12
- asRecord,
13
- firstNumber,
14
- firstRawString,
15
- firstRecord,
16
- firstString,
17
- firstValue,
18
- maxIso,
19
- normalizeUrl,
20
- parseDateIso,
21
- timestamp,
22
- uniqueStrings,
23
- type JsonRecord,
24
- } from "./utils";
25
-
26
- function extractPayloadArray(payload: unknown, preferredKeys: string[]): unknown[] {
27
- if (Array.isArray(payload)) return payload;
28
- const record = asRecord(payload);
29
- if (!record) return [];
30
- for (const key of preferredKeys) {
31
- const value = record[key];
32
- if (Array.isArray(value)) return value;
33
- }
34
- const data = asRecord(record.data);
35
- if (data) {
36
- for (const key of preferredKeys) {
37
- const value = data[key];
38
- if (Array.isArray(value)) return value;
39
- }
40
- }
41
- const result = asRecord(record.result);
42
- if (result) {
43
- for (const key of preferredKeys) {
44
- const value = result[key];
45
- if (Array.isArray(value)) return value;
46
- }
47
- }
48
- return [];
49
- }
50
-
51
- export function normalizePublication(raw: unknown): SubstackPublication | null {
52
- const record = asRecord(raw);
53
- if (!record) return null;
54
- const subdomain = firstString(record, ["subdomain", "handle", "slug"]);
55
- const customDomain = firstString(record, ["custom_domain", "customDomain", "domain"]);
56
- const baseUrl = normalizeUrl(firstRawString(record, ["base_url", "baseUrl", "url", "web_url", "canonical_url"]))
57
- ?? (customDomain ? `https://${customDomain}` : null)
58
- ?? (subdomain ? `https://${subdomain}.substack.com` : null);
59
- const idValue = firstValue(record, ["id", "publication_id", "publicationId", "subdomain"]);
60
- const name = firstString(record, ["name", "publication_name", "publicationName", "title"]) ?? subdomain ?? "Substack";
61
- const id = String(idValue ?? baseUrl ?? name).trim();
62
- return {
63
- id,
64
- name,
65
- subdomain,
66
- baseUrl,
67
- description: firstString(record, ["description", "hero_text", "heroText", "author_bio"]),
68
- logoUrl: normalizeUrl(firstRawString(record, ["logo_url", "logoUrl", "logo", "cover_image", "coverImage"]), baseUrl),
69
- latestPublishedAt: parseDateIso(firstValue(record, [
70
- "latestPublishedAt",
71
- "last_published_at",
72
- "last_post_date",
73
- "lastPostDate",
74
- "updated_at",
75
- ])),
76
- };
77
- }
78
-
79
- export function normalizeSubscriptions(payload: unknown): SubstackPublication[] {
80
- const rows = extractPayloadArray(payload, [
81
- "subscriptions",
82
- "publications",
83
- "results",
84
- "items",
85
- ]);
86
- const rootRecord = asRecord(payload);
87
- const publicationLookup = buildPublicationLookup(rootRecord);
88
- const deduped = new Map<string, SubstackPublication>();
89
- for (const row of rows) {
90
- const record = asRecord(row);
91
- const publication = normalizePublication(
92
- record?.publication
93
- ?? record?.pub
94
- ?? (record ? publicationLookup.get(String(record.publication_id ?? record.publicationId ?? "")) : null)
95
- ?? row,
96
- );
97
- if (!publication) continue;
98
- const key = publicationKey(publication);
99
- const existing = deduped.get(key);
100
- if (!existing) {
101
- deduped.set(key, publication);
102
- continue;
103
- }
104
- deduped.set(key, {
105
- ...existing,
106
- latestPublishedAt: maxIso(existing.latestPublishedAt, publication.latestPublishedAt),
107
- });
108
- }
109
- return [...deduped.values()].sort((a, b) => a.name.localeCompare(b.name));
110
- }
111
-
112
- function buildPublicationLookup(record: JsonRecord | null): Map<string, unknown> {
113
- const lookup = new Map<string, unknown>();
114
- if (!record) return lookup;
115
- const maps = [
116
- asRecord(record.publicationMap),
117
- asRecord(record.publication_map),
118
- asRecord(asRecord(record.result)?.publicationMap),
119
- asRecord(asRecord(record.result)?.publication_map),
120
- ];
121
- for (const map of maps) {
122
- if (!map) continue;
123
- for (const [key, value] of Object.entries(map)) {
124
- lookup.set(key, value);
125
- }
126
- }
127
- const arrays = [
128
- record.publications,
129
- asRecord(record.result)?.publications,
130
- ];
131
- for (const array of arrays) {
132
- if (!Array.isArray(array)) continue;
133
- for (const publication of array) {
134
- const normalized = normalizePublication(publication);
135
- if (!normalized) continue;
136
- lookup.set(normalized.id, publication);
137
- }
138
- }
139
- return lookup;
140
- }
141
-
142
- function articlePostRecord(row: unknown): JsonRecord | null {
143
- const record = asRecord(row);
144
- return firstRecord(
145
- record?.post,
146
- record?.item,
147
- record?.entity,
148
- record?.object,
149
- record,
150
- );
151
- }
152
-
153
- function articlePublicationRecord(row: JsonRecord | null, post: JsonRecord | null): JsonRecord | null {
154
- return firstRecord(
155
- post?.publication,
156
- post?.pub,
157
- row?.publication,
158
- row?.pub,
159
- );
160
- }
161
-
162
- function buildArticleUrl(post: JsonRecord | null, publication: SubstackPublication | null): string | null {
163
- const direct = normalizeUrl(firstRawString(post, ["canonical_url", "canonicalUrl", "url", "web_url"]), publication?.baseUrl);
164
- if (direct) return direct;
165
- const slug = firstString(post, ["slug"]);
166
- if (!slug || !publication?.baseUrl) return null;
167
- return normalizeUrl(`/p/${slug}`, publication.baseUrl);
168
- }
169
-
170
- function normalizeArticle(row: unknown, sourcePublication?: SubstackPublication | null): SubstackArticleSummary | null {
171
- const rowRecord = asRecord(row);
172
- const post = articlePostRecord(row);
173
- if (!post) return null;
174
- const publication = normalizePublication(articlePublicationRecord(rowRecord, post)) ?? sourcePublication ?? null;
175
- const title = firstString(post, ["title", "name", "social_title", "email_subject"]) ?? firstString(rowRecord, ["title"]);
176
- const subtitle = firstString(post, ["subtitle", "description", "search_engine_description", "social_description"]);
177
- const bodyHtml = firstRawString(post, ["body_html", "bodyHtml", "body"]);
178
- const coverImage = normalizeUrl(firstRawString(post, [
179
- "cover_image",
180
- "coverImage",
181
- "cover_image_url",
182
- "coverImageUrl",
183
- "thumbnail_url",
184
- "thumbnailUrl",
185
- "image",
186
- ]), publication?.baseUrl);
187
- const extracted = bodyHtml
188
- ? extractArticleContent(bodyHtml, { baseUrl: publication?.baseUrl, imageUrls: coverImage ? [coverImage] : [], title })
189
- : { text: "", blocks: [], imageUrls: uniqueStrings([coverImage]), linkUrls: [], wordCount: 0, readMinutes: 1 };
190
- const previewText = firstString(post, [
191
- "truncated_body_text",
192
- "body_preview",
193
- "preview",
194
- "subtitle",
195
- "description",
196
- "search_engine_description",
197
- ]) ?? (extracted.text ? extracted.text.slice(0, 500) : null);
198
- const rawWordCount = firstNumber(post, ["wordcount", "word_count", "words"]);
199
- const words = Math.max(0, Math.round(rawWordCount ?? extracted.wordCount ?? wordCount(previewText ?? "")));
200
- const rawReadMinutes = firstNumber(post, [
201
- "reading_time_minutes",
202
- "read_time_minutes",
203
- "readMinutes",
204
- "readingTime",
205
- ]);
206
- const publishedAt = parseDateIso(firstValue(post, [
207
- "post_date",
208
- "published_at",
209
- "publish_date",
210
- "date",
211
- "created_at",
212
- "updated_at",
213
- ]) ?? firstValue(rowRecord, ["published_at", "timestamp", "date"]));
214
- const url = buildArticleUrl(post, publication);
215
- const slug = firstString(post, ["slug"]);
216
- const rawId = firstValue(post, ["id", "post_id", "postId", "canonical_slug"]) ?? firstValue(rowRecord, ["id", "post_id"]);
217
- const id = String(rawId ?? url ?? `${publication?.id ?? "substack"}:${slug ?? title ?? publishedAt ?? Math.random()}`).trim();
218
-
219
- if (!title && !url && !bodyHtml) return null;
220
-
221
- return {
222
- id,
223
- title: title ?? "Untitled",
224
- publicationId: publication?.id ?? null,
225
- publicationName: publication?.name ?? null,
226
- publicationSubdomain: publication?.subdomain ?? null,
227
- publicationBaseUrl: publication?.baseUrl ?? null,
228
- url,
229
- slug,
230
- publishedAt,
231
- subtitle,
232
- previewText,
233
- bodyHtml,
234
- imageUrls: extracted.imageUrls,
235
- wordCount: words,
236
- readMinutes: rawReadMinutes != null ? Math.max(1, Math.ceil(rawReadMinutes)) : estimateReadingMinutes(words || extracted.text),
237
- };
238
- }
239
-
240
- export function normalizeFeedItems(payload: unknown, sourcePublication?: SubstackPublication | null): SubstackArticleSummary[] {
241
- const rows = extractPayloadArray(payload, ["items", "posts", "results", "feed"]);
242
- const items = rows
243
- .map((row) => normalizeArticle(row, sourcePublication))
244
- .filter((row): row is SubstackArticleSummary => !!row);
245
- const deduped = new Map<string, SubstackArticleSummary>();
246
- for (const item of items) {
247
- const existing = deduped.get(item.id);
248
- if (!existing || timestamp(item.publishedAt) > timestamp(existing.publishedAt)) {
249
- deduped.set(item.id, item);
250
- }
251
- }
252
- return [...deduped.values()];
253
- }
254
-
255
- export function normalizePostDetail(
256
- payload: unknown,
257
- fallback: SubstackArticleSummary,
258
- ): SubstackArticleDetail {
259
- const post = articlePostRecord(payload) ?? asRecord(payload);
260
- const normalized = normalizeArticle(post ?? payload, {
261
- id: fallback.publicationId ?? fallback.publicationBaseUrl ?? fallback.publicationName ?? "substack",
262
- name: fallback.publicationName ?? "Substack",
263
- subdomain: fallback.publicationSubdomain,
264
- baseUrl: fallback.publicationBaseUrl,
265
- description: null,
266
- logoUrl: null,
267
- latestPublishedAt: null,
268
- }) ?? fallback;
269
- const bodyHtml = normalized.bodyHtml ?? fallback.bodyHtml;
270
- const extracted = extractArticleContent(bodyHtml, {
271
- baseUrl: normalized.publicationBaseUrl ?? fallback.publicationBaseUrl,
272
- imageUrls: uniqueStrings([...fallback.imageUrls, ...normalized.imageUrls]),
273
- title: normalized.title || fallback.title,
274
- });
275
- const contentText = extracted.text || normalized.previewText || fallback.previewText || "";
276
- const words = Math.max(normalized.wordCount, extracted.wordCount, wordCount(contentText));
277
- return {
278
- ...fallback,
279
- ...normalized,
280
- id: fallback.id,
281
- title: normalized.title || fallback.title,
282
- url: normalized.url ?? fallback.url,
283
- bodyHtml,
284
- imageUrls: uniqueStrings([...normalized.imageUrls, ...fallback.imageUrls, ...extracted.imageUrls]),
285
- contentText,
286
- contentBlocks: extracted.blocks,
287
- linkUrls: extracted.linkUrls,
288
- wordCount: words,
289
- readMinutes: estimateReadingMinutes(words || contentText),
290
- };
291
- }
292
-
293
- export function publicationKey(publication: Pick<SubstackPublication, "id" | "name" | "baseUrl" | "subdomain">): string {
294
- return (
295
- publication.baseUrl
296
- ?? publication.subdomain
297
- ?? publication.id
298
- ?? publication.name
299
- ).toLowerCase();
300
- }
301
-
302
- function articlePublicationKeys(article: SubstackArticleSummary): string[] {
303
- return uniqueStrings([
304
- article.publicationBaseUrl?.toLowerCase(),
305
- article.publicationSubdomain?.toLowerCase(),
306
- article.publicationId?.toLowerCase(),
307
- article.publicationName?.toLowerCase(),
308
- ]);
309
- }
310
-
311
- export function articleMatchesPublication(
312
- article: SubstackArticleSummary,
313
- publication: SubstackPublication,
314
- ): boolean {
315
- const keys = new Set(articlePublicationKeys(article));
316
- return [
317
- publication.baseUrl,
318
- publication.subdomain,
319
- publication.id,
320
- publication.name,
321
- ].some((value) => !!value && keys.has(value.toLowerCase()));
322
- }
323
-
324
- export function sortSubscriptionsByLatest(
325
- subscriptions: SubstackPublication[],
326
- feedItems: SubstackArticleSummary[],
327
- ): SubstackPublication[] {
328
- return [...subscriptions]
329
- .map((publication) => {
330
- const latestFeedAt = feedItems
331
- .filter((article) => articleMatchesPublication(article, publication))
332
- .reduce<string | null>((latest, article) => maxIso(latest, article.publishedAt), null);
333
- return {
334
- publication,
335
- latestAt: maxIso(publication.latestPublishedAt, latestFeedAt),
336
- };
337
- })
338
- .sort((a, b) => {
339
- const byDate = timestamp(b.latestAt) - timestamp(a.latestAt);
340
- return byDate !== 0 ? byDate : a.publication.name.localeCompare(b.publication.name);
341
- })
342
- .map((entry) => ({ ...entry.publication, latestPublishedAt: entry.latestAt }));
343
- }
@@ -1,52 +0,0 @@
1
- import { usePaneFooter } from "../../../components";
2
- import type { SubstackAuthState } from "./api/types";
3
- import {
4
- SUBSTACK_PANE_ID,
5
- type SubstackArticleSummary,
6
- } from "./types";
7
- import { cacheStatusLabel, type ActiveFeedState, type DetailState } from "./pane-state";
8
-
9
- export function useSubstackPaneFooter({
10
- auth,
11
- detailOpen,
12
- activeFeedState,
13
- activeDetail,
14
- selectedArticle,
15
- openSelectedArticle,
16
- }: {
17
- auth: SubstackAuthState | null;
18
- detailOpen: boolean;
19
- activeFeedState: ActiveFeedState;
20
- activeDetail: DetailState;
21
- selectedArticle: SubstackArticleSummary | null;
22
- openSelectedArticle: () => void;
23
- }) {
24
- const statusLabel = cacheStatusLabel(activeFeedState.fetchedAt, activeFeedState.stale);
25
-
26
- usePaneFooter(SUBSTACK_PANE_ID, () => ({
27
- info: [
28
- ...(!auth ? [{ id: "auth", parts: [{ text: "login required", tone: "warning" as const }] }] : []),
29
- ...(activeFeedState.loading || activeFeedState.loadingMore ? [{ id: "loading", parts: [{ text: activeFeedState.loadingMore ? "loading more" : "loading", tone: "muted" as const }] }] : []),
30
- ...(activeDetail.loading && detailOpen ? [{ id: "detail-loading", parts: [{ text: "loading article", tone: "muted" as const }] }] : []),
31
- ...(statusLabel && auth ? [{ id: "cache", parts: [{ text: statusLabel, tone: activeFeedState.stale ? "warning" as const : "muted" as const }] }] : []),
32
- ...(activeFeedState.error ? [{ id: "error", parts: [{ text: activeFeedState.error, tone: "warning" as const }] }] : []),
33
- ...(activeDetail.error && detailOpen ? [{ id: "detail-error", parts: [{ text: activeDetail.error, tone: "warning" as const }] }] : []),
34
- ],
35
- hints: auth
36
- ? [{ id: "open", key: "o", label: "pen", onPress: openSelectedArticle, disabled: !selectedArticle?.url }]
37
- : [],
38
- }), [
39
- activeDetail.error,
40
- activeDetail.loading,
41
- activeFeedState.error,
42
- activeFeedState.fetchedAt,
43
- activeFeedState.loading,
44
- activeFeedState.loadingMore,
45
- activeFeedState.stale,
46
- auth,
47
- detailOpen,
48
- openSelectedArticle,
49
- selectedArticle?.url,
50
- statusLabel,
51
- ]);
52
- }