@dxos/plugin-magazine 0.0.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 (390) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +653 -0
  3. package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs +90 -0
  4. package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs.map +7 -0
  5. package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs +97 -0
  6. package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs.map +7 -0
  7. package/dist/lib/neutral/MagazineArticle-DEUTR23F.mjs +325 -0
  8. package/dist/lib/neutral/MagazineArticle-DEUTR23F.mjs.map +7 -0
  9. package/dist/lib/neutral/MagazinePlugin.mjs +62 -0
  10. package/dist/lib/neutral/MagazinePlugin.mjs.map +7 -0
  11. package/dist/lib/neutral/MagazinePlugin.workerd.mjs +12 -0
  12. package/dist/lib/neutral/MagazinePlugin.workerd.mjs.map +7 -0
  13. package/dist/lib/neutral/PostArticle-RERHXPXC.mjs +230 -0
  14. package/dist/lib/neutral/PostArticle-RERHXPXC.mjs.map +7 -0
  15. package/dist/lib/neutral/PostCard-VDGN7BBA.mjs +60 -0
  16. package/dist/lib/neutral/PostCard-VDGN7BBA.mjs.map +7 -0
  17. package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs +121 -0
  18. package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs.map +7 -0
  19. package/dist/lib/neutral/capabilities/index.mjs +21 -0
  20. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-3K4AQKBZ.mjs +12 -0
  22. package/dist/lib/neutral/chunk-3K4AQKBZ.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-AFPBWBTK.mjs +591 -0
  24. package/dist/lib/neutral/chunk-AFPBWBTK.mjs.map +7 -0
  25. package/dist/lib/neutral/chunk-APQRTYT3.mjs +57 -0
  26. package/dist/lib/neutral/chunk-APQRTYT3.mjs.map +7 -0
  27. package/dist/lib/neutral/chunk-GU725ZF4.mjs +11 -0
  28. package/dist/lib/neutral/chunk-GU725ZF4.mjs.map +7 -0
  29. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  30. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  31. package/dist/lib/neutral/chunk-JFPR6OV4.mjs +60 -0
  32. package/dist/lib/neutral/chunk-JFPR6OV4.mjs.map +7 -0
  33. package/dist/lib/neutral/chunk-KTYLUOUG.mjs +36 -0
  34. package/dist/lib/neutral/chunk-KTYLUOUG.mjs.map +7 -0
  35. package/dist/lib/neutral/chunk-MBPX7RPI.mjs +14 -0
  36. package/dist/lib/neutral/chunk-MBPX7RPI.mjs.map +7 -0
  37. package/dist/lib/neutral/chunk-MJ2AY2CK.mjs +8 -0
  38. package/dist/lib/neutral/chunk-MJ2AY2CK.mjs.map +7 -0
  39. package/dist/lib/neutral/chunk-OYCDRSWB.mjs +624 -0
  40. package/dist/lib/neutral/chunk-OYCDRSWB.mjs.map +7 -0
  41. package/dist/lib/neutral/chunk-SAWKLGJU.mjs +28 -0
  42. package/dist/lib/neutral/chunk-SAWKLGJU.mjs.map +7 -0
  43. package/dist/lib/neutral/chunk-XCNCK47N.mjs +15 -0
  44. package/dist/lib/neutral/chunk-XCNCK47N.mjs.map +7 -0
  45. package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs +59 -0
  46. package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs.map +7 -0
  47. package/dist/lib/neutral/components/index.mjs +338 -0
  48. package/dist/lib/neutral/components/index.mjs.map +7 -0
  49. package/dist/lib/neutral/containers/index.mjs +17 -0
  50. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  51. package/dist/lib/neutral/create-object-SYZMRR4H.mjs +98 -0
  52. package/dist/lib/neutral/create-object-SYZMRR4H.mjs.map +7 -0
  53. package/dist/lib/neutral/curate-magazine-MA6OLK2X.mjs +184 -0
  54. package/dist/lib/neutral/curate-magazine-MA6OLK2X.mjs.map +7 -0
  55. package/dist/lib/neutral/fetch-article-content-CZO6D3QD.mjs +31 -0
  56. package/dist/lib/neutral/fetch-article-content-CZO6D3QD.mjs.map +7 -0
  57. package/dist/lib/neutral/index.mjs +18 -0
  58. package/dist/lib/neutral/index.mjs.map +7 -0
  59. package/dist/lib/neutral/load-post-content-XGL6VFF6.mjs +65 -0
  60. package/dist/lib/neutral/load-post-content-XGL6VFF6.mjs.map +7 -0
  61. package/dist/lib/neutral/meta.json +1 -0
  62. package/dist/lib/neutral/meta.mjs +8 -0
  63. package/dist/lib/neutral/meta.mjs.map +7 -0
  64. package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs +18 -0
  65. package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs.map +7 -0
  66. package/dist/lib/neutral/operation-handler-JE3743BU.mjs +8 -0
  67. package/dist/lib/neutral/operation-handler-JE3743BU.mjs.map +7 -0
  68. package/dist/lib/neutral/operations/index.mjs +8 -0
  69. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  70. package/dist/lib/neutral/plugin.mjs +16 -0
  71. package/dist/lib/neutral/plugin.mjs.map +7 -0
  72. package/dist/lib/neutral/plugin.workerd.mjs +16 -0
  73. package/dist/lib/neutral/plugin.workerd.mjs.map +7 -0
  74. package/dist/lib/neutral/react-surface-TZESAMG2.mjs +60 -0
  75. package/dist/lib/neutral/react-surface-TZESAMG2.mjs.map +7 -0
  76. package/dist/lib/neutral/routine-templates-A2XTJOZO.mjs +52 -0
  77. package/dist/lib/neutral/routine-templates-A2XTJOZO.mjs.map +7 -0
  78. package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs +8 -0
  79. package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs.map +7 -0
  80. package/dist/lib/neutral/skills/index.mjs +46 -0
  81. package/dist/lib/neutral/skills/index.mjs.map +7 -0
  82. package/dist/lib/neutral/sync-feed-UZX2XHSQ.mjs +99 -0
  83. package/dist/lib/neutral/sync-feed-UZX2XHSQ.mjs.map +7 -0
  84. package/dist/lib/neutral/translations.mjs +81 -0
  85. package/dist/lib/neutral/translations.mjs.map +7 -0
  86. package/dist/lib/neutral/types/index.mjs +14 -0
  87. package/dist/lib/neutral/types/index.mjs.map +7 -0
  88. package/dist/types/dx.config.d.ts +28 -0
  89. package/dist/types/dx.config.d.ts.map +1 -0
  90. package/dist/types/src/MagazinePlugin.d.ts +4 -0
  91. package/dist/types/src/MagazinePlugin.d.ts.map +1 -0
  92. package/dist/types/src/MagazinePlugin.test.d.ts +2 -0
  93. package/dist/types/src/MagazinePlugin.test.d.ts.map +1 -0
  94. package/dist/types/src/MagazinePlugin.workerd.d.ts +4 -0
  95. package/dist/types/src/MagazinePlugin.workerd.d.ts.map +1 -0
  96. package/dist/types/src/atoms/atoms.test.d.ts +2 -0
  97. package/dist/types/src/atoms/atoms.test.d.ts.map +1 -0
  98. package/dist/types/src/atoms/index.d.ts +7 -0
  99. package/dist/types/src/atoms/index.d.ts.map +1 -0
  100. package/dist/types/src/atoms/magazine-posts.d.ts +16 -0
  101. package/dist/types/src/atoms/magazine-posts.d.ts.map +1 -0
  102. package/dist/types/src/atoms/post-content.d.ts +7 -0
  103. package/dist/types/src/atoms/post-content.d.ts.map +1 -0
  104. package/dist/types/src/atoms/post-curation.d.ts +16 -0
  105. package/dist/types/src/atoms/post-curation.d.ts.map +1 -0
  106. package/dist/types/src/atoms/post-display.d.ts +22 -0
  107. package/dist/types/src/atoms/post-display.d.ts.map +1 -0
  108. package/dist/types/src/atoms/post-read.d.ts +14 -0
  109. package/dist/types/src/atoms/post-read.d.ts.map +1 -0
  110. package/dist/types/src/atoms/post-tags.d.ts +16 -0
  111. package/dist/types/src/atoms/post-tags.d.ts.map +1 -0
  112. package/dist/types/src/capabilities/app-graph-builder.d.ts +5 -0
  113. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  114. package/dist/types/src/capabilities/create-object.d.ts +5 -0
  115. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  116. package/dist/types/src/capabilities/index.d.ts +10 -0
  117. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  118. package/dist/types/src/capabilities/navigation-resolver.d.ts +5 -0
  119. package/dist/types/src/capabilities/navigation-resolver.d.ts.map +1 -0
  120. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  121. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  122. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  123. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  124. package/dist/types/src/capabilities/routine-templates.d.ts +5 -0
  125. package/dist/types/src/capabilities/routine-templates.d.ts.map +1 -0
  126. package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
  127. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
  128. package/dist/types/src/components/PostContent/PostContent.d.ts +30 -0
  129. package/dist/types/src/components/PostContent/PostContent.d.ts.map +1 -0
  130. package/dist/types/src/components/PostContent/PostContent.test.d.ts +2 -0
  131. package/dist/types/src/components/PostContent/PostContent.test.d.ts.map +1 -0
  132. package/dist/types/src/components/PostContent/index.d.ts +2 -0
  133. package/dist/types/src/components/PostContent/index.d.ts.map +1 -0
  134. package/dist/types/src/components/PostStack/PostStack.d.ts +17 -0
  135. package/dist/types/src/components/PostStack/PostStack.d.ts.map +1 -0
  136. package/dist/types/src/components/PostStack/PostStack.stories.d.ts +10 -0
  137. package/dist/types/src/components/PostStack/PostStack.stories.d.ts.map +1 -0
  138. package/dist/types/src/components/PostStack/index.d.ts +2 -0
  139. package/dist/types/src/components/PostStack/index.d.ts.map +1 -0
  140. package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts +23 -0
  141. package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts.map +1 -0
  142. package/dist/types/src/components/SubscriptionStack/SubscriptionStack.stories.d.ts +8 -0
  143. package/dist/types/src/components/SubscriptionStack/SubscriptionStack.stories.d.ts.map +1 -0
  144. package/dist/types/src/components/SubscriptionStack/index.d.ts +2 -0
  145. package/dist/types/src/components/SubscriptionStack/index.d.ts.map +1 -0
  146. package/dist/types/src/components/index.d.ts +4 -0
  147. package/dist/types/src/components/index.d.ts.map +1 -0
  148. package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts +6 -0
  149. package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts.map +1 -0
  150. package/dist/types/src/containers/FeedArticle/FeedArticle.stories.d.ts +12 -0
  151. package/dist/types/src/containers/FeedArticle/FeedArticle.stories.d.ts.map +1 -0
  152. package/dist/types/src/containers/FeedArticle/FeedToolbar.d.ts +10 -0
  153. package/dist/types/src/containers/FeedArticle/FeedToolbar.d.ts.map +1 -0
  154. package/dist/types/src/containers/FeedArticle/index.d.ts +2 -0
  155. package/dist/types/src/containers/FeedArticle/index.d.ts.map +1 -0
  156. package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts +6 -0
  157. package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts.map +1 -0
  158. package/dist/types/src/containers/FeedProperties/index.d.ts +2 -0
  159. package/dist/types/src/containers/FeedProperties/index.d.ts.map +1 -0
  160. package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts +6 -0
  161. package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts.map +1 -0
  162. package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts +9 -0
  163. package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts.map +1 -0
  164. package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts +11 -0
  165. package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts.map +1 -0
  166. package/dist/types/src/containers/MagazineArticle/index.d.ts +2 -0
  167. package/dist/types/src/containers/MagazineArticle/index.d.ts.map +1 -0
  168. package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts +18 -0
  169. package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts.map +1 -0
  170. package/dist/types/src/containers/PostArticle/PostArticle.d.ts +6 -0
  171. package/dist/types/src/containers/PostArticle/PostArticle.d.ts.map +1 -0
  172. package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts +8 -0
  173. package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts.map +1 -0
  174. package/dist/types/src/containers/PostArticle/PostToolbar.d.ts +19 -0
  175. package/dist/types/src/containers/PostArticle/PostToolbar.d.ts.map +1 -0
  176. package/dist/types/src/containers/PostArticle/index.d.ts +2 -0
  177. package/dist/types/src/containers/PostArticle/index.d.ts.map +1 -0
  178. package/dist/types/src/containers/PostCard/PostCard.d.ts +11 -0
  179. package/dist/types/src/containers/PostCard/PostCard.d.ts.map +1 -0
  180. package/dist/types/src/containers/PostCard/index.d.ts +2 -0
  181. package/dist/types/src/containers/PostCard/index.d.ts.map +1 -0
  182. package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts +5 -0
  183. package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts.map +1 -0
  184. package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts +8 -0
  185. package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts.map +1 -0
  186. package/dist/types/src/containers/SubscriptionsArticle/index.d.ts +2 -0
  187. package/dist/types/src/containers/SubscriptionsArticle/index.d.ts.map +1 -0
  188. package/dist/types/src/containers/index.d.ts +7 -0
  189. package/dist/types/src/containers/index.d.ts.map +1 -0
  190. package/dist/types/src/index.d.ts +3 -0
  191. package/dist/types/src/index.d.ts.map +1 -0
  192. package/dist/types/src/meta.d.ts +33 -0
  193. package/dist/types/src/meta.d.ts.map +1 -0
  194. package/dist/types/src/operations/clear-magazine.d.ts +10 -0
  195. package/dist/types/src/operations/clear-magazine.d.ts.map +1 -0
  196. package/dist/types/src/operations/curate-magazine.d.ts +25 -0
  197. package/dist/types/src/operations/curate-magazine.d.ts.map +1 -0
  198. package/dist/types/src/operations/curate-magazine.skill.test.d.ts +2 -0
  199. package/dist/types/src/operations/curate-magazine.skill.test.d.ts.map +1 -0
  200. package/dist/types/src/operations/curate-magazine.test.d.ts +2 -0
  201. package/dist/types/src/operations/curate-magazine.test.d.ts.map +1 -0
  202. package/dist/types/src/operations/extraction/article.d.ts +37 -0
  203. package/dist/types/src/operations/extraction/article.d.ts.map +1 -0
  204. package/dist/types/src/operations/extraction/article.test.d.ts +2 -0
  205. package/dist/types/src/operations/extraction/article.test.d.ts.map +1 -0
  206. package/dist/types/src/operations/extraction/index.d.ts +2 -0
  207. package/dist/types/src/operations/extraction/index.d.ts.map +1 -0
  208. package/dist/types/src/operations/fetch-article-content.d.ts +5 -0
  209. package/dist/types/src/operations/fetch-article-content.d.ts.map +1 -0
  210. package/dist/types/src/operations/index.d.ts +3 -0
  211. package/dist/types/src/operations/index.d.ts.map +1 -0
  212. package/dist/types/src/operations/load-post-content.d.ts +8 -0
  213. package/dist/types/src/operations/load-post-content.d.ts.map +1 -0
  214. package/dist/types/src/operations/sources/article.d.ts +24 -0
  215. package/dist/types/src/operations/sources/article.d.ts.map +1 -0
  216. package/dist/types/src/operations/sources/cors.d.ts +12 -0
  217. package/dist/types/src/operations/sources/cors.d.ts.map +1 -0
  218. package/dist/types/src/operations/sources/feed-fetcher.d.ts +22 -0
  219. package/dist/types/src/operations/sources/feed-fetcher.d.ts.map +1 -0
  220. package/dist/types/src/operations/sources/feed-fetcher.test.d.ts +2 -0
  221. package/dist/types/src/operations/sources/feed-fetcher.test.d.ts.map +1 -0
  222. package/dist/types/src/operations/sources/http.d.ts +9 -0
  223. package/dist/types/src/operations/sources/http.d.ts.map +1 -0
  224. package/dist/types/src/operations/sources/index.d.ts +6 -0
  225. package/dist/types/src/operations/sources/index.d.ts.map +1 -0
  226. package/dist/types/src/operations/sources/rss.d.ts +4 -0
  227. package/dist/types/src/operations/sources/rss.d.ts.map +1 -0
  228. package/dist/types/src/operations/sources/rss.test.d.ts +2 -0
  229. package/dist/types/src/operations/sources/rss.test.d.ts.map +1 -0
  230. package/dist/types/src/operations/sources/standard-site.d.ts +33 -0
  231. package/dist/types/src/operations/sources/standard-site.d.ts.map +1 -0
  232. package/dist/types/src/operations/sync-feed.d.ts +5 -0
  233. package/dist/types/src/operations/sync-feed.d.ts.map +1 -0
  234. package/dist/types/src/operations/sync-feed.test.d.ts +2 -0
  235. package/dist/types/src/operations/sync-feed.test.d.ts.map +1 -0
  236. package/dist/types/src/operations/util.d.ts +25 -0
  237. package/dist/types/src/operations/util.d.ts.map +1 -0
  238. package/dist/types/src/operations/util.test.d.ts +2 -0
  239. package/dist/types/src/operations/util.test.d.ts.map +1 -0
  240. package/dist/types/src/paths.d.ts +3 -0
  241. package/dist/types/src/paths.d.ts.map +1 -0
  242. package/dist/types/src/plugin.d.ts +4 -0
  243. package/dist/types/src/plugin.d.ts.map +1 -0
  244. package/dist/types/src/plugin.workerd.d.ts +3 -0
  245. package/dist/types/src/plugin.workerd.d.ts.map +1 -0
  246. package/dist/types/src/skills/index.d.ts +2 -0
  247. package/dist/types/src/skills/index.d.ts.map +1 -0
  248. package/dist/types/src/skills/magazine-skill.d.ts +4 -0
  249. package/dist/types/src/skills/magazine-skill.d.ts.map +1 -0
  250. package/dist/types/src/stories/ArticleExtractor.stories.d.ts +267 -0
  251. package/dist/types/src/stories/ArticleExtractor.stories.d.ts.map +1 -0
  252. package/dist/types/src/stories/MagazineCurate.stories.d.ts +32 -0
  253. package/dist/types/src/stories/MagazineCurate.stories.d.ts.map +1 -0
  254. package/dist/types/src/stories/theregister-fixture.test.d.ts +2 -0
  255. package/dist/types/src/stories/theregister-fixture.test.d.ts.map +1 -0
  256. package/dist/types/src/templates/magazine-curation.d.ts +7 -0
  257. package/dist/types/src/templates/magazine-curation.d.ts.map +1 -0
  258. package/dist/types/src/testing/builder.d.ts +28 -0
  259. package/dist/types/src/testing/builder.d.ts.map +1 -0
  260. package/dist/types/src/testing/index.d.ts +2 -0
  261. package/dist/types/src/testing/index.d.ts.map +1 -0
  262. package/dist/types/src/translations.d.ts +247 -0
  263. package/dist/types/src/translations.d.ts.map +1 -0
  264. package/dist/types/src/types/CreateSubscription.d.ts +29 -0
  265. package/dist/types/src/types/CreateSubscription.d.ts.map +1 -0
  266. package/dist/types/src/types/FeedOperation.d.ts +52 -0
  267. package/dist/types/src/types/FeedOperation.d.ts.map +1 -0
  268. package/dist/types/src/types/Magazine.d.ts +87 -0
  269. package/dist/types/src/types/Magazine.d.ts.map +1 -0
  270. package/dist/types/src/types/Magazine.test.d.ts +2 -0
  271. package/dist/types/src/types/Magazine.test.d.ts.map +1 -0
  272. package/dist/types/src/types/Subscription.d.ts +144 -0
  273. package/dist/types/src/types/Subscription.d.ts.map +1 -0
  274. package/dist/types/src/types/Subscription.test.d.ts +2 -0
  275. package/dist/types/src/types/Subscription.test.d.ts.map +1 -0
  276. package/dist/types/src/types/index.d.ts +5 -0
  277. package/dist/types/src/types/index.d.ts.map +1 -0
  278. package/dist/types/src/util/date.d.ts +8 -0
  279. package/dist/types/src/util/date.d.ts.map +1 -0
  280. package/dist/types/src/util/index.d.ts +4 -0
  281. package/dist/types/src/util/index.d.ts.map +1 -0
  282. package/dist/types/src/util/post-content.d.ts +14 -0
  283. package/dist/types/src/util/post-content.d.ts.map +1 -0
  284. package/dist/types/src/util/text.d.ts +18 -0
  285. package/dist/types/src/util/text.d.ts.map +1 -0
  286. package/dist/types/tsconfig.tsbuildinfo +1 -0
  287. package/dx.config.ts +48 -0
  288. package/package.json +164 -0
  289. package/src/MagazinePlugin.test.ts +36 -0
  290. package/src/MagazinePlugin.tsx +61 -0
  291. package/src/MagazinePlugin.workerd.ts +31 -0
  292. package/src/atoms/atoms.test.ts +323 -0
  293. package/src/atoms/index.ts +10 -0
  294. package/src/atoms/magazine-posts.ts +70 -0
  295. package/src/atoms/post-content.ts +34 -0
  296. package/src/atoms/post-curation.ts +36 -0
  297. package/src/atoms/post-display.ts +59 -0
  298. package/src/atoms/post-read.ts +40 -0
  299. package/src/atoms/post-tags.ts +66 -0
  300. package/src/capabilities/app-graph-builder.ts +125 -0
  301. package/src/capabilities/create-object.ts +127 -0
  302. package/src/capabilities/index.ts +17 -0
  303. package/src/capabilities/navigation-resolver.ts +21 -0
  304. package/src/capabilities/operation-handler.ts +16 -0
  305. package/src/capabilities/react-surface.tsx +53 -0
  306. package/src/capabilities/routine-templates.ts +16 -0
  307. package/src/capabilities/skill-definition.ts +14 -0
  308. package/src/components/PostContent/PostContent.test.ts +81 -0
  309. package/src/components/PostContent/PostContent.tsx +111 -0
  310. package/src/components/PostContent/index.ts +5 -0
  311. package/src/components/PostStack/PostStack.stories.tsx +39 -0
  312. package/src/components/PostStack/PostStack.tsx +151 -0
  313. package/src/components/PostStack/index.ts +5 -0
  314. package/src/components/SubscriptionStack/SubscriptionStack.stories.tsx +52 -0
  315. package/src/components/SubscriptionStack/SubscriptionStack.tsx +161 -0
  316. package/src/components/SubscriptionStack/index.ts +5 -0
  317. package/src/components/index.ts +7 -0
  318. package/src/containers/FeedArticle/FeedArticle.stories.tsx +102 -0
  319. package/src/containers/FeedArticle/FeedArticle.tsx +68 -0
  320. package/src/containers/FeedArticle/FeedToolbar.tsx +47 -0
  321. package/src/containers/FeedArticle/index.ts +5 -0
  322. package/src/containers/FeedProperties/FeedProperties.tsx +107 -0
  323. package/src/containers/FeedProperties/index.ts +5 -0
  324. package/src/containers/MagazineArticle/MagazineArticle.stories.tsx +244 -0
  325. package/src/containers/MagazineArticle/MagazineArticle.tsx +146 -0
  326. package/src/containers/MagazineArticle/MagazineTile.tsx +88 -0
  327. package/src/containers/MagazineArticle/index.ts +5 -0
  328. package/src/containers/MagazineArticle/useToolbar.tsx +145 -0
  329. package/src/containers/PostArticle/PostArticle.stories.tsx +117 -0
  330. package/src/containers/PostArticle/PostArticle.tsx +140 -0
  331. package/src/containers/PostArticle/PostToolbar.tsx +120 -0
  332. package/src/containers/PostArticle/index.ts +5 -0
  333. package/src/containers/PostCard/PostCard.tsx +70 -0
  334. package/src/containers/PostCard/index.ts +5 -0
  335. package/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.tsx +80 -0
  336. package/src/containers/SubscriptionsArticle/SubscriptionsArticle.tsx +99 -0
  337. package/src/containers/SubscriptionsArticle/index.ts +5 -0
  338. package/src/containers/index.ts +11 -0
  339. package/src/index.ts +6 -0
  340. package/src/meta.ts +9 -0
  341. package/src/operations/clear-magazine.ts +83 -0
  342. package/src/operations/curate-magazine.skill.conversations.json +1 -0
  343. package/src/operations/curate-magazine.skill.test.ts +167 -0
  344. package/src/operations/curate-magazine.test.ts +91 -0
  345. package/src/operations/curate-magazine.ts +178 -0
  346. package/src/operations/extraction/article.test.ts +154 -0
  347. package/src/operations/extraction/article.ts +270 -0
  348. package/src/operations/extraction/index.ts +5 -0
  349. package/src/operations/fetch-article-content.ts +27 -0
  350. package/src/operations/index.ts +13 -0
  351. package/src/operations/load-post-content.ts +76 -0
  352. package/src/operations/sources/article.ts +122 -0
  353. package/src/operations/sources/cors.ts +19 -0
  354. package/src/operations/sources/feed-fetcher.test.ts +200 -0
  355. package/src/operations/sources/feed-fetcher.ts +26 -0
  356. package/src/operations/sources/http.ts +42 -0
  357. package/src/operations/sources/index.ts +9 -0
  358. package/src/operations/sources/rss.test.ts +201 -0
  359. package/src/operations/sources/rss.ts +138 -0
  360. package/src/operations/sources/standard-site.ts +381 -0
  361. package/src/operations/sources/testing/feed.xml +477 -0
  362. package/src/operations/sync-feed.test.ts +146 -0
  363. package/src/operations/sync-feed.ts +140 -0
  364. package/src/operations/util.test.ts +67 -0
  365. package/src/operations/util.ts +65 -0
  366. package/src/paths.ts +14 -0
  367. package/src/plugin.ts +11 -0
  368. package/src/plugin.workerd.ts +6 -0
  369. package/src/skills/index.ts +5 -0
  370. package/src/skills/magazine-skill.ts +45 -0
  371. package/src/stories/ArticleExtractor.stories.tsx +204 -0
  372. package/src/stories/MagazineCurate.stories.tsx +217 -0
  373. package/src/stories/fixtures/theregister-ai.xml +45 -0
  374. package/src/stories/theregister-fixture.test.ts +35 -0
  375. package/src/templates/magazine-curation.ts +47 -0
  376. package/src/testing/builder.ts +77 -0
  377. package/src/testing/index.ts +5 -0
  378. package/src/translations.ts +81 -0
  379. package/src/types/CreateSubscription.ts +61 -0
  380. package/src/types/FeedOperation.ts +134 -0
  381. package/src/types/Magazine.test.ts +69 -0
  382. package/src/types/Magazine.ts +214 -0
  383. package/src/types/Subscription.test.ts +171 -0
  384. package/src/types/Subscription.ts +366 -0
  385. package/src/types/index.ts +8 -0
  386. package/src/util/date.ts +27 -0
  387. package/src/util/index.ts +7 -0
  388. package/src/util/post-content.ts +19 -0
  389. package/src/util/text.ts +98 -0
  390. package/src/vite-env.d.ts +10 -0
package/dx.config.ts ADDED
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Config2 } from '@dxos/app-framework/config';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export default Config2.make({
9
+ plugin: {
10
+ key: 'org.dxos.plugin.magazine',
11
+ name: 'Magazine',
12
+ author: 'DXOS',
13
+ description: trim`
14
+ MagazinePlugin brings syndicated content into DXOS Composer by syncing RSS feeds
15
+ and ATproto (Bluesky) feeds as ECHO objects stored in the user's space.
16
+ Subscribed feeds are polled incrementally using a cursor so only new posts
17
+ are fetched on each sync, and feed metadata (name, icon, description) is
18
+ persisted alongside the posts for offline access.
19
+
20
+ Magazines are agent-curated collections drawn from one or more feeds. Each
21
+ Magazine carries the user's topic instructions describing what it should
22
+ cover; pressing the Curate button (or a scheduled trigger) syncs all
23
+ referenced feeds in parallel, then runs a single-shot agent pass that selects
24
+ matching posts and returns their ids, which the operation adds mechanically.
25
+ The agent's base methodology — how to select, how to dedup, and the structured
26
+ output contract — lives in the MagazineSkill. Each magazine stores a
27
+ persisted curation Routine (created at magazine creation time) whose instructions
28
+ hold the editorial brief; pressing Curate runs that Routine against the synced
29
+ candidates via RunInstructions. The skill also exposes a FetchArticleContent
30
+ tool the agent may call to read a candidate's full text when title and
31
+ description are insufficient to judge.
32
+ A short snippet and hero image are derived mechanically from each post for
33
+ display. A Masonry grid renders the curated tiles with images, titles, author
34
+ metadata, and read-state dimming.
35
+
36
+ Clicking a tile opens the PostArticle detail surface via the deck's layout
37
+ dispatch, routing to the appropriate location (complementary drawer, sibling
38
+ plank, or deck companion) based on the current layout mode. The schema also
39
+ scaffolds a future tagging feature — posts carry an optional tags array and
40
+ the tile renders a tag row when tags are present — enabling navigation and
41
+ feed-discovery flows in a later revision.
42
+ `,
43
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-magazine',
44
+ icon: { key: 'ph--book-open-text--regular', hue: 'orange' },
45
+ spec: 'PLUGIN.mdl',
46
+ tags: ['labs'],
47
+ },
48
+ });
package/package.json ADDED
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "@dxos/plugin-magazine",
3
+ "version": "0.0.0",
4
+ "description": "Magazine plugin for RSS and open protocol subscriptions.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
12
+ "author": "DXOS.org",
13
+ "sideEffects": true,
14
+ "type": "module",
15
+ "imports": {
16
+ "#atoms": {
17
+ "source": "./src/atoms/index.ts",
18
+ "types": "./dist/types/src/atoms/index.d.ts",
19
+ "default": "./dist/lib/neutral/atoms/index.mjs"
20
+ },
21
+ "#skills": {
22
+ "source": "./src/skills/index.ts",
23
+ "types": "./dist/types/src/skills/index.d.ts",
24
+ "default": "./dist/lib/neutral/skills/index.mjs"
25
+ },
26
+ "#capabilities": {
27
+ "source": "./src/capabilities/index.ts",
28
+ "types": "./dist/types/src/capabilities/index.d.ts",
29
+ "default": "./dist/lib/neutral/capabilities/index.mjs"
30
+ },
31
+ "#components": {
32
+ "source": "./src/components/index.ts",
33
+ "types": "./dist/types/src/components/index.d.ts",
34
+ "default": "./dist/lib/neutral/components/index.mjs"
35
+ },
36
+ "#containers": {
37
+ "source": "./src/containers/index.ts",
38
+ "types": "./dist/types/src/containers/index.d.ts",
39
+ "default": "./dist/lib/neutral/containers/index.mjs"
40
+ },
41
+ "#meta": {
42
+ "source": "./src/meta.ts",
43
+ "types": "./dist/types/src/meta.d.ts",
44
+ "default": "./dist/lib/neutral/meta.mjs"
45
+ },
46
+ "#operations": {
47
+ "source": "./src/operations/index.ts",
48
+ "types": "./dist/types/src/operations/index.d.ts",
49
+ "default": "./dist/lib/neutral/operations/index.mjs"
50
+ },
51
+ "#plugin": {
52
+ "source": {
53
+ "workerd": "./src/MagazinePlugin.workerd.ts",
54
+ "default": "./src/MagazinePlugin.tsx"
55
+ },
56
+ "types": "./dist/types/src/MagazinePlugin.d.ts",
57
+ "workerd": "./dist/lib/neutral/MagazinePlugin.workerd.mjs",
58
+ "default": "./dist/lib/neutral/MagazinePlugin.mjs"
59
+ },
60
+ "#testing": "./src/testing/index.ts",
61
+ "#translations": {
62
+ "source": "./src/translations.ts",
63
+ "types": "./dist/types/src/translations.d.ts",
64
+ "default": "./dist/lib/neutral/translations.mjs"
65
+ },
66
+ "#types": {
67
+ "source": "./src/types/index.ts",
68
+ "types": "./dist/types/src/types/index.d.ts",
69
+ "default": "./dist/lib/neutral/types/index.mjs"
70
+ }
71
+ },
72
+ "exports": {
73
+ ".": {
74
+ "source": "./src/index.ts",
75
+ "types": "./dist/types/src/index.d.ts",
76
+ "default": "./dist/lib/neutral/index.mjs"
77
+ },
78
+ "./assets/PLUGIN.mdl": "./PLUGIN.mdl",
79
+ "./plugin": {
80
+ "source": {
81
+ "workerd": "./src/plugin.workerd.ts",
82
+ "default": "./src/plugin.ts"
83
+ },
84
+ "types": "./dist/types/src/plugin.d.ts",
85
+ "workerd": "./dist/lib/neutral/plugin.workerd.mjs",
86
+ "default": "./dist/lib/neutral/plugin.mjs"
87
+ },
88
+ "./translations": {
89
+ "source": "./src/translations.ts",
90
+ "types": "./dist/types/src/translations.d.ts",
91
+ "default": "./dist/lib/neutral/translations.mjs"
92
+ }
93
+ },
94
+ "types": "dist/types/src/index.d.ts",
95
+ "files": [
96
+ "dist",
97
+ "dx.config.ts",
98
+ "src",
99
+ "PLUGIN.mdl"
100
+ ],
101
+ "dependencies": {
102
+ "@dxos/app-framework": "workspace:*",
103
+ "@dxos/app-toolkit": "workspace:*",
104
+ "@dxos/assistant-toolkit": "workspace:*",
105
+ "@dxos/client": "workspace:*",
106
+ "@dxos/compute": "workspace:*",
107
+ "@dxos/echo": "workspace:*",
108
+ "@dxos/echo-react": "workspace:*",
109
+ "@dxos/effect": "workspace:*",
110
+ "@dxos/invariant": "workspace:*",
111
+ "@dxos/keys": "workspace:*",
112
+ "@dxos/log": "workspace:*",
113
+ "@dxos/markdown": "workspace:*",
114
+ "@dxos/plugin-assistant": "workspace:*",
115
+ "@dxos/plugin-attention": "workspace:*",
116
+ "@dxos/plugin-client": "workspace:*",
117
+ "@dxos/plugin-graph": "workspace:*",
118
+ "@dxos/plugin-routine": "workspace:*",
119
+ "@dxos/plugin-space": "workspace:*",
120
+ "@dxos/react-client": "workspace:*",
121
+ "@dxos/react-ui-attention": "workspace:*",
122
+ "@dxos/react-ui-form": "workspace:*",
123
+ "@dxos/react-ui-markdown": "workspace:*",
124
+ "@dxos/react-ui-masonry": "workspace:*",
125
+ "@dxos/react-ui-menu": "workspace:*",
126
+ "@dxos/schema": "workspace:*",
127
+ "@dxos/util": "workspace:*",
128
+ "@effect-atom/atom-react": "catalog:",
129
+ "@effect/platform": "catalog:",
130
+ "date-fns": "catalog:",
131
+ "defuddle": "catalog:",
132
+ "fast-xml-parser": "catalog:"
133
+ },
134
+ "devDependencies": {
135
+ "@dxos/ai": "workspace:*",
136
+ "@dxos/echo-client": "workspace:*",
137
+ "@dxos/functions-runtime": "workspace:*",
138
+ "@dxos/plugin-testing": "workspace:*",
139
+ "@dxos/random": "workspace:*",
140
+ "@dxos/react-ui": "workspace:*",
141
+ "@dxos/react-ui-mosaic": "workspace:*",
142
+ "@dxos/react-ui-syntax-highlighter": "workspace:*",
143
+ "@dxos/storybook-utils": "workspace:*",
144
+ "@dxos/ui-theme": "workspace:*",
145
+ "@storybook/react-vite": "catalog:",
146
+ "@types/react": "catalog:",
147
+ "@types/react-dom": "catalog:",
148
+ "effect": "catalog:",
149
+ "react": "catalog:",
150
+ "react-dom": "catalog:",
151
+ "vite": "catalog:"
152
+ },
153
+ "peerDependencies": {
154
+ "@dxos/react-ui": "workspace:*",
155
+ "@dxos/react-ui-mosaic": "workspace:*",
156
+ "@dxos/ui-theme": "workspace:*",
157
+ "effect": "catalog:",
158
+ "react": "catalog:",
159
+ "react-dom": "catalog:"
160
+ },
161
+ "publishConfig": {
162
+ "access": "public"
163
+ }
164
+ }
@@ -0,0 +1,36 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { ActivationEvents } from '@dxos/app-framework';
8
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
9
+ import { createComposerTestApp } from '@dxos/plugin-testing/harness';
10
+
11
+ import { MagazinePlugin } from '#plugin';
12
+
13
+ import { meta } from './meta';
14
+
15
+ const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
16
+
17
+ describe('MagazinePlugin', () => {
18
+ test('modules activate on the expected events', async ({ expect }) => {
19
+ await using harness = await createComposerTestApp({
20
+ plugins: [ClientPlugin({}), MagazinePlugin()],
21
+ });
22
+
23
+ expect(harness.manager.getActive()).toEqual(
24
+ expect.arrayContaining([
25
+ moduleId('AppGraphBuilder'),
26
+ moduleId('CreateObject'),
27
+ moduleId('schema'),
28
+ moduleId('ReactSurface'),
29
+ ]),
30
+ );
31
+
32
+ // Operation handlers are not loaded on startup — SetupProcessManager fires lazily when an operation is invoked.
33
+ await harness.fire(ActivationEvents.SetupProcessManager);
34
+ expect(harness.manager.getActive()).toContain(moduleId('OperationHandler'));
35
+ });
36
+ });
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { ActivationEvent, Plugin } from '@dxos/app-framework';
6
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
7
+ import { Instructions } from '@dxos/compute';
8
+ import { AttentionEvents } from '@dxos/plugin-attention';
9
+ import { ClientEvents } from '@dxos/plugin-client';
10
+ import { StateMap, TagIndex } from '@dxos/schema';
11
+
12
+ import {
13
+ AppGraphBuilder,
14
+ CreateObject,
15
+ NavigationResolver,
16
+ OperationHandler,
17
+ ReactSurface,
18
+ RoutineTemplates,
19
+ SkillDefinition,
20
+ } from '#capabilities';
21
+ import { meta } from '#meta';
22
+ import { translations } from '#translations';
23
+ import { Magazine, Subscription } from '#types';
24
+
25
+ // eslint-disable-next-line import/no-relative-packages
26
+ import pluginSpec from '../PLUGIN.mdl?raw';
27
+
28
+ export const MagazinePlugin = Plugin.define(meta).pipe(
29
+ AppPlugin.addAppGraphModule({
30
+ activatesOn: ActivationEvent.allOf(AppActivationEvents.SetupAppGraph, AttentionEvents.AttentionReady),
31
+ activate: AppGraphBuilder,
32
+ }),
33
+ AppPlugin.addCreateObjectModule({ activate: CreateObject }),
34
+ AppPlugin.addNavigationResolverModule({ activatesOn: ClientEvents.ClientReady, activate: NavigationResolver }),
35
+ AppPlugin.addOperationHandlerModule({ activate: OperationHandler }),
36
+ AppPlugin.addPluginAssetModule({
37
+ asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
38
+ }),
39
+ AppPlugin.addSchemaModule({
40
+ schema: [
41
+ Subscription.Subscription,
42
+ Subscription.Post,
43
+ Subscription.PostContent,
44
+ Magazine.Magazine,
45
+ Instructions.Instructions,
46
+ StateMap.StateMap,
47
+ TagIndex.TagIndex,
48
+ ],
49
+ }),
50
+ AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }),
51
+ AppPlugin.addSurfaceModule({ activate: ReactSurface }),
52
+ AppPlugin.addTranslationsModule({ translations }),
53
+ Plugin.addModule({
54
+ id: 'magazine-automation-templates',
55
+ activatesOn: AppActivationEvents.SetupSchema,
56
+ activate: RoutineTemplates,
57
+ }),
58
+ Plugin.make,
59
+ );
60
+
61
+ export default MagazinePlugin;
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { AppPlugin } from '@dxos/app-toolkit';
7
+ import { StateMap, TagIndex } from '@dxos/schema';
8
+
9
+ import { meta } from '#meta';
10
+ import { Magazine, Subscription } from '#types';
11
+
12
+ import OperationHandler from './capabilities/operation-handler';
13
+ import SkillDefinition from './capabilities/skill-definition';
14
+
15
+ export const MagazinePlugin = Plugin.define(meta).pipe(
16
+ AppPlugin.addSkillDefinitionModule({ id: 'skill-definition', activate: SkillDefinition }),
17
+ AppPlugin.addOperationHandlerModule({ id: 'operation-handler', activate: OperationHandler }),
18
+ AppPlugin.addSchemaModule({
19
+ schema: [
20
+ Subscription.Subscription,
21
+ Subscription.Post,
22
+ Subscription.PostContent,
23
+ Magazine.Magazine,
24
+ StateMap.StateMap,
25
+ TagIndex.TagIndex,
26
+ ],
27
+ }),
28
+ Plugin.make,
29
+ );
30
+
31
+ export default MagazinePlugin;
@@ -0,0 +1,323 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Registry } from '@effect-atom/atom-react';
6
+ import * as Data from 'effect/Data';
7
+ import { afterEach, beforeEach, describe, test } from 'vitest';
8
+
9
+ import { Feed, Obj, Ref, Tag } from '@dxos/echo';
10
+ import { EchoTestBuilder } from '@dxos/echo-client/testing';
11
+ import { StateMap, TagIndex } from '@dxos/schema';
12
+
13
+ import { Magazine, Subscription } from '../types';
14
+ import { postCurationAtom } from './post-curation';
15
+ import { postDisplayAtom } from './post-display';
16
+ import { postReadAtom } from './post-read';
17
+ import { postTagsAtom } from './post-tags';
18
+
19
+ describe('postReadAtom', () => {
20
+ let builder: EchoTestBuilder;
21
+
22
+ beforeEach(async () => {
23
+ builder = await new EchoTestBuilder().open();
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await builder.close();
28
+ });
29
+
30
+ test('fires when this post is marked read', async ({ expect }) => {
31
+ const { db } = await builder.createDatabase({
32
+ types: [Tag.Tag, Subscription.Subscription, Subscription.Post, StateMap.StateMap, TagIndex.TagIndex],
33
+ });
34
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
35
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
36
+ await db.flush();
37
+
38
+ const registry = Registry.make();
39
+ const atom = postReadAtom(post);
40
+ let fireCount = 0;
41
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
42
+ expect(fireCount).toBe(1);
43
+
44
+ Subscription.setReadAt(subscription, post.id, new Date().toISOString());
45
+ await db.flush();
46
+ expect(fireCount).toBe(2);
47
+ expect(registry.get(atom).readAt).toBeDefined();
48
+ });
49
+
50
+ test('does not fire when a sibling post is marked read', async ({ expect }) => {
51
+ const { db } = await builder.createDatabase({
52
+ types: [Tag.Tag, Subscription.Subscription, Subscription.Post, StateMap.StateMap, TagIndex.TagIndex],
53
+ });
54
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
55
+ const postA = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
56
+ const postB = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
57
+ await db.flush();
58
+
59
+ const registry = Registry.make();
60
+ const atomA = postReadAtom(postA);
61
+ let fireCount = 0;
62
+ registry.subscribe(atomA, () => fireCount++, { immediate: true });
63
+ expect(fireCount).toBe(1);
64
+
65
+ Subscription.setReadAt(subscription, postB.id, new Date().toISOString());
66
+ await db.flush();
67
+ expect(fireCount).toBe(1); // postA's readAt unchanged — should not re-fire.
68
+ });
69
+
70
+ test('does not fire when an unrelated subscription field changes', async ({ expect }) => {
71
+ const { db } = await builder.createDatabase({
72
+ types: [Tag.Tag, Subscription.Subscription, Subscription.Post, StateMap.StateMap, TagIndex.TagIndex],
73
+ });
74
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
75
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
76
+ await db.flush();
77
+
78
+ const registry = Registry.make();
79
+ const atom = postReadAtom(post);
80
+ let fireCount = 0;
81
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
82
+ expect(fireCount).toBe(1);
83
+
84
+ Obj.update(subscription, (subscription) => {
85
+ subscription.name = 'Updated';
86
+ });
87
+ await db.flush();
88
+ expect(fireCount).toBe(1); // name change — should not re-fire.
89
+ });
90
+ });
91
+
92
+ describe('postTagsAtom', () => {
93
+ let builder: EchoTestBuilder;
94
+
95
+ beforeEach(async () => {
96
+ builder = await new EchoTestBuilder().open();
97
+ });
98
+
99
+ afterEach(async () => {
100
+ await builder.close();
101
+ });
102
+
103
+ test('fires when this post is starred', async ({ expect }) => {
104
+ const { db } = await builder.createDatabase({
105
+ types: [Tag.Tag, Subscription.Subscription, Subscription.Post, StateMap.StateMap, TagIndex.TagIndex],
106
+ });
107
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
108
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
109
+ await db.flush();
110
+
111
+ const registry = Registry.make();
112
+ const atom = postTagsAtom(post);
113
+ let fireCount = 0;
114
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
115
+ expect(fireCount).toBe(1);
116
+ expect(registry.get(atom).starred).toBe(false);
117
+
118
+ await Subscription.setTag(subscription, post.id, db, 'starred', true);
119
+ await db.flush();
120
+ // tagUrisAtom fires once when Tag.Tag is created (intermediate re-run, still starred=false),
121
+ // then Obj.subscribe fires when subscription.tags is mutated (starred=true, setSelf).
122
+ expect(fireCount).toBe(3);
123
+ expect(registry.get(atom).starred).toBe(true);
124
+ });
125
+
126
+ test('does not fire when a sibling post is starred (after tag warm-up)', async ({ expect }) => {
127
+ const { db } = await builder.createDatabase({
128
+ types: [Tag.Tag, Subscription.Subscription, Subscription.Post, StateMap.StateMap, TagIndex.TagIndex],
129
+ });
130
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
131
+ const postA = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
132
+ const postB = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
133
+ // Pre-warm the Tag.Tag so tagUrisAtom is already populated when the atom subscribes.
134
+ // Without this, the first setTag creates Tag.Tag → tagUrisAtom fires → one extra body re-run.
135
+ await Subscription.setTag(subscription, postA.id, db, 'starred', true);
136
+ await Subscription.setTag(subscription, postA.id, db, 'starred', false);
137
+ await db.flush();
138
+
139
+ const registry = Registry.make();
140
+ const atomA = postTagsAtom(postA);
141
+ let fireCount = 0;
142
+ registry.subscribe(atomA, () => fireCount++, { immediate: true });
143
+ expect(fireCount).toBe(1);
144
+
145
+ await Subscription.setTag(subscription, postB.id, db, 'starred', true);
146
+ await db.flush();
147
+ expect(fireCount).toBe(1); // postA's tags unchanged — should not re-fire.
148
+ });
149
+ });
150
+
151
+ describe('postCurationAtom', () => {
152
+ let builder: EchoTestBuilder;
153
+
154
+ beforeEach(async () => {
155
+ builder = await new EchoTestBuilder().open();
156
+ });
157
+
158
+ afterEach(async () => {
159
+ await builder.close();
160
+ });
161
+
162
+ test('fires when this post gets a curated snippet', async ({ expect }) => {
163
+ const { db } = await builder.createDatabase({
164
+ types: [
165
+ Tag.Tag,
166
+ Feed.Feed,
167
+ Subscription.Subscription,
168
+ Subscription.Post,
169
+ Magazine.Magazine,
170
+ StateMap.StateMap,
171
+ TagIndex.TagIndex,
172
+ ],
173
+ });
174
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
175
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
176
+ const magazine = db.add(Magazine.make({ feeds: [Ref.make(subscription)] }));
177
+ await db.flush();
178
+
179
+ const registry = Registry.make();
180
+ const atom = postCurationAtom(Data.tuple(post, magazine));
181
+ let fireCount = 0;
182
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
183
+ expect(fireCount).toBe(1);
184
+ expect(registry.get(atom).snippet).toBeUndefined();
185
+
186
+ Magazine.patchPostState(magazine, post.id, { snippet: 'A great article about AI.' });
187
+ await db.flush();
188
+ expect(fireCount).toBe(2);
189
+ expect(registry.get(atom).snippet).toBe('A great article about AI.');
190
+ });
191
+
192
+ test('does not fire when a sibling post gets a snippet', async ({ expect }) => {
193
+ const { db } = await builder.createDatabase({
194
+ types: [
195
+ Tag.Tag,
196
+ Feed.Feed,
197
+ Subscription.Subscription,
198
+ Subscription.Post,
199
+ Magazine.Magazine,
200
+ StateMap.StateMap,
201
+ TagIndex.TagIndex,
202
+ ],
203
+ });
204
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
205
+ const postA = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
206
+ const postB = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
207
+ const magazine = db.add(Magazine.make({ feeds: [Ref.make(subscription)] }));
208
+ await db.flush();
209
+
210
+ const registry = Registry.make();
211
+ const atomA = postCurationAtom(Data.tuple(postA, magazine));
212
+ let fireCount = 0;
213
+ registry.subscribe(atomA, () => fireCount++, { immediate: true });
214
+ expect(fireCount).toBe(1);
215
+
216
+ Magazine.patchPostState(magazine, postB.id, { snippet: 'Sibling snippet.' });
217
+ await db.flush();
218
+ expect(fireCount).toBe(1); // postA's curation unchanged — should not re-fire.
219
+ });
220
+
221
+ test('does not fire when an unrelated magazine field changes', async ({ expect }) => {
222
+ const { db } = await builder.createDatabase({
223
+ types: [
224
+ Tag.Tag,
225
+ Feed.Feed,
226
+ Subscription.Subscription,
227
+ Subscription.Post,
228
+ Magazine.Magazine,
229
+ StateMap.StateMap,
230
+ TagIndex.TagIndex,
231
+ ],
232
+ });
233
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Test', url: 'https://example.com' }));
234
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
235
+ const magazine = db.add(Magazine.make({ feeds: [Ref.make(subscription)] }));
236
+ await db.flush();
237
+
238
+ const registry = Registry.make();
239
+ const atom = postCurationAtom(Data.tuple(post, magazine));
240
+ let fireCount = 0;
241
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
242
+ expect(fireCount).toBe(1);
243
+
244
+ Obj.update(magazine, (magazine) => {
245
+ magazine.name = 'Updated Magazine';
246
+ });
247
+ await db.flush();
248
+ expect(fireCount).toBe(1); // name change — should not re-fire.
249
+ });
250
+ });
251
+
252
+ describe('postDisplayAtom', () => {
253
+ let builder: EchoTestBuilder;
254
+
255
+ beforeEach(async () => {
256
+ builder = await new EchoTestBuilder().open();
257
+ });
258
+
259
+ afterEach(async () => {
260
+ await builder.close();
261
+ });
262
+
263
+ test('prefers agent snippet over RSS description', async ({ expect }) => {
264
+ const { db } = await builder.createDatabase({
265
+ types: [
266
+ Tag.Tag,
267
+ Feed.Feed,
268
+ Subscription.Subscription,
269
+ Subscription.Post,
270
+ Magazine.Magazine,
271
+ StateMap.StateMap,
272
+ TagIndex.TagIndex,
273
+ ],
274
+ });
275
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Feed', url: 'https://example.com' }));
276
+ const post = db.add(
277
+ Obj.make(Subscription.Post, { source: Ref.make(subscription), description: 'RSS description.' }),
278
+ );
279
+ const magazine = db.add(Magazine.make({ feeds: [Ref.make(subscription)], posts: [Ref.make(post)] }));
280
+ await db.flush();
281
+
282
+ const registry = Registry.make();
283
+ const atom = postDisplayAtom(Data.tuple(post, magazine));
284
+ registry.subscribe(atom, () => {}, { immediate: true });
285
+
286
+ // Before curation: falls back to RSS description.
287
+ expect(registry.get(atom).snippet).toContain('RSS description');
288
+
289
+ Magazine.patchPostState(magazine, post.id, { snippet: 'Agent snippet.' });
290
+ await db.flush();
291
+
292
+ expect(registry.get(atom).snippet).toBe('Agent snippet.');
293
+ });
294
+
295
+ test('fires on read state change', async ({ expect }) => {
296
+ const { db } = await builder.createDatabase({
297
+ types: [
298
+ Tag.Tag,
299
+ Feed.Feed,
300
+ Subscription.Subscription,
301
+ Subscription.Post,
302
+ Magazine.Magazine,
303
+ StateMap.StateMap,
304
+ TagIndex.TagIndex,
305
+ ],
306
+ });
307
+ const subscription = db.add(Subscription.makeSubscription({ name: 'Feed', url: 'https://example.com' }));
308
+ const post = db.add(Obj.make(Subscription.Post, { source: Ref.make(subscription) }));
309
+ const magazine = db.add(Magazine.make({ feeds: [Ref.make(subscription)], posts: [Ref.make(post)] }));
310
+ await db.flush();
311
+
312
+ const registry = Registry.make();
313
+ const atom = postDisplayAtom(Data.tuple(post, magazine));
314
+ let fireCount = 0;
315
+ registry.subscribe(atom, () => fireCount++, { immediate: true });
316
+ expect(registry.get(atom).read).toBe(false);
317
+
318
+ Subscription.setReadAt(subscription, post.id, new Date().toISOString());
319
+ await db.flush();
320
+ expect(registry.get(atom).read).toBe(true);
321
+ expect(fireCount).toBe(2);
322
+ });
323
+ });
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './magazine-posts';
6
+ export * from './post-content';
7
+ export * from './post-curation';
8
+ export * from './post-display';
9
+ export * from './post-read';
10
+ export * from './post-tags';