@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
@@ -0,0 +1,81 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Type } from '@dxos/echo';
6
+ import { type Resource } from '@dxos/react-ui';
7
+ import { translations as formTranslations } from '@dxos/react-ui-form/translations';
8
+
9
+ import { meta } from '#meta';
10
+ import { Magazine, Subscription } from '#types';
11
+
12
+ export const translations = [
13
+ ...formTranslations,
14
+ {
15
+ 'en-US': {
16
+ [Type.getTypename(Subscription.Subscription)]: {
17
+ 'typename.label': 'Feed',
18
+ 'typename.label_zero': 'Feeds',
19
+ 'typename.label_one': 'Feed',
20
+ 'typename.label_other': 'Feeds',
21
+ 'object-name.placeholder': 'New feed',
22
+ 'add-object.label': 'Add feed',
23
+ 'rename-object.label': 'Rename feed',
24
+ 'delete-object.label': 'Delete feed',
25
+ 'object-deleted.label': 'Feed deleted',
26
+ },
27
+ [Type.getTypename(Subscription.Post)]: {
28
+ 'typename.label': 'Post',
29
+ 'typename.label_zero': 'Posts',
30
+ 'typename.label_one': 'Post',
31
+ 'typename.label_other': 'Posts',
32
+ 'post-title.placeholder': 'Untitled',
33
+ },
34
+ [Type.getTypename(Magazine.Magazine)]: {
35
+ 'typename.label': 'Magazine',
36
+ 'typename.label_zero': 'Magazines',
37
+ 'typename.label_one': 'Magazine',
38
+ 'typename.label_other': 'Magazines',
39
+ 'object-name.placeholder': 'New magazine',
40
+ 'add-object.label': 'Add magazine',
41
+ 'rename-object.label': 'Rename magazine',
42
+ 'delete-object.label': 'Delete magazine',
43
+ 'object-deleted.label': 'Magazine deleted',
44
+ },
45
+ [meta.profile.key]: {
46
+ 'plugin.name': 'Feed',
47
+ 'empty-feed.message': 'No posts yet',
48
+ 'feed-companion.label': 'Feed',
49
+ 'sync-feed.label': 'Sync feed',
50
+ 'sync-feed-error.title': 'Failed to sync feed',
51
+ 'feed-sync.label': 'Feed Sync',
52
+ 'view-trigger.label': 'View trigger',
53
+ 'add-feed.label': 'Add feed',
54
+ 'curate.label': 'Curate',
55
+ 'clear-magazine.label': 'Clear (preserves starred)',
56
+ 'refreshing-magazine.label': 'Refreshing magazine...',
57
+ 'no-feeds.label': 'Add at least one feed first',
58
+ 'empty-magazine.message': 'No articles yet.',
59
+ 'curate-error.message': 'Failed to curate magazine.',
60
+ 'clear-error.message': 'Failed to clear magazine.',
61
+ 'refresh-content-error.message': 'Failed to refresh content.',
62
+ 'post-companion.label': 'Post',
63
+ 'post-title.placeholder': 'Untitled',
64
+ 'open-original.label': 'Open original',
65
+ 'mark-unread.label': 'Mark as unread',
66
+ 'refresh-content.label': 'Refresh content',
67
+ 'refresh-content-pending.label': 'Refreshing…',
68
+ 'archive-post.label': 'Archive',
69
+ 'unarchive-post.label': 'Unarchive',
70
+ 'star-post.label': 'Star',
71
+ 'unstar-post.label': 'Unstar',
72
+ 'sort-by-date.label': 'Sort by date',
73
+ 'sort-by-rank.label': 'Sort by rank',
74
+ 'view-filter.label': 'Filter posts',
75
+ 'show-archived.label': 'Show archived',
76
+ 'only-starred.label': 'Show starred only',
77
+ 'close.label': 'Close',
78
+ },
79
+ },
80
+ },
81
+ ] as const satisfies Resource[];
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { Format } from '@dxos/echo';
10
+
11
+ import * as Subscription from './Subscription';
12
+
13
+ // Structural gates (regex / URL format), used as form-field validation and to short-circuit the network
14
+ // effects on obviously-malformed input. Handle existence is verified implicitly by the publication lookup
15
+ // (an unresolvable handle yields no publications, and a publication is required to submit).
16
+ export const HandleSchema = Schema.String.pipe(
17
+ Schema.pattern(/^@?([\da-z-]+\.)+[a-z]{2,}$|^did:[a-z]+:[a-zA-Z0-9._%:-]+$/i),
18
+ );
19
+
20
+ export const isHandle = Schema.is(HandleSchema);
21
+ export const isUrl = Schema.is(Format.URL);
22
+
23
+ //
24
+ // Base structs (no dynamic annotations). The capability layer (create-object.ts) applies annotation
25
+ // closures on top — those closures reference operations/sources and so cannot live in this module.
26
+ //
27
+
28
+ export const StandardSiteCreateBase = Schema.Struct({
29
+ type: Schema.Literal('standard-site'),
30
+ handle: HandleSchema.annotations({ title: 'Handle', description: 'atproto handle, e.g. dxos.org.' }),
31
+ // No `name`: the feed name is taken from the selected publication (resolved by `fetchStandardSite`).
32
+ publication: Schema.String.annotations({ title: 'Publication', description: 'Choose a publication.' }),
33
+ });
34
+
35
+ export type StandardSiteValues = Schema.Schema.Type<typeof StandardSiteCreateBase>;
36
+
37
+ export const RssCreateBase = Schema.Struct({
38
+ type: Schema.Literal('rss'),
39
+ url: Format.URL.annotations({ title: 'URL', description: 'RSS feed URL.' }),
40
+ name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
41
+ });
42
+
43
+ export type RssValues = Schema.Schema.Type<typeof RssCreateBase>;
44
+
45
+ // Input type covers both annotated union members; the union itself is built in create-object.ts after
46
+ // annotations are applied. The switch narrows on `type` which is common to both shapes.
47
+ export type CreateSubscriptionInput =
48
+ | { type: 'standard-site'; handle: string; publication: string }
49
+ | { type: 'rss'; url: string; name?: string };
50
+
51
+ /** Normalizes a create-form union member into the stored {@link Subscription.Subscription} fields. */
52
+ export const makeSubscriptionFromCreate = (input: CreateSubscriptionInput): Subscription.Subscription => {
53
+ switch (input.type) {
54
+ case 'standard-site':
55
+ // `url` stores the publication site reference (at:// or https://); sync derives the author DID
56
+ // from it directly — no handle stored, name resolved from the publication at sync time.
57
+ return Subscription.makeSubscription({ type: 'standard-site', url: input.publication });
58
+ case 'rss':
59
+ return Subscription.makeSubscription({ type: 'rss', url: input.url, name: input.name });
60
+ }
61
+ };
@@ -0,0 +1,134 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { Operation } from '@dxos/compute';
10
+ import { Database, DXN, Ref, Registry } from '@dxos/echo';
11
+
12
+ import { meta } from '#meta';
13
+
14
+ import * as Magazine from './Magazine';
15
+ import * as Subscription from './Subscription';
16
+
17
+ const makeKey = (name: string) => DXN.make(`${meta.profile.key}.operation.${name}`);
18
+
19
+ /** Fetches an RSS/Atom feed and appends new posts to the backing ECHO feed. */
20
+ export const SyncFeed = Operation.make({
21
+ meta: {
22
+ key: makeKey('syncFeed'),
23
+ name: 'Sync Feed',
24
+ description: 'Fetches RSS/Atom feed and writes posts to the ECHO feed.',
25
+ icon: 'ph--arrows-clockwise--regular',
26
+ },
27
+ services: [Database.Service],
28
+ input: Schema.Struct({
29
+ feed: Ref.Ref(Subscription.Subscription),
30
+ }),
31
+ output: Schema.Void,
32
+ }).pipe(Operation.visible);
33
+
34
+ /**
35
+ * Fetches a Post's article page over HTTP and returns extracted plain text plus
36
+ * any image URLs found. Read-only; does not mutate the Post. Agent-facing tool
37
+ * (the surviving curation tool: the agent may call it to read a candidate's full
38
+ * text when title + description are insufficient to judge relevance).
39
+ */
40
+ export const FetchArticleContent = Operation.make({
41
+ meta: {
42
+ key: makeKey('fetchArticleContent'),
43
+ name: 'Fetch Article Content',
44
+ description: "Fetches and extracts text + image URLs from a Post's article page.",
45
+ icon: 'ph--article--regular',
46
+ },
47
+ input: Schema.Struct({
48
+ post: Ref.Ref(Subscription.Post).annotations({
49
+ description: 'The Post to fetch content for. The Post must have a link.',
50
+ }),
51
+ }),
52
+ output: Schema.Struct({
53
+ text: Schema.String.annotations({ description: 'Extracted plain text from the article page.' }),
54
+ imageUrls: Schema.Array(Schema.String).annotations({
55
+ description: 'Image URLs found on the page (og:image first, then <img> src values).',
56
+ }),
57
+ }),
58
+ services: [Database.Service],
59
+ });
60
+
61
+ /**
62
+ * Fetches a Post's article page over HTTP and writes the extracted plain text
63
+ * and hero image URL to the source Subscription's content feed. Per-Post read/star/archive
64
+ * state on the Subscription is keyed by Post entity id.
65
+ * (the Post itself is an immutable queue item). Idempotent — skips if
66
+ * `content` is already set or the Post has no link. Used by the reader view
67
+ * to populate the full article on first open.
68
+ */
69
+ export const LoadPostContent = Operation.make({
70
+ meta: {
71
+ key: makeKey('loadPostContent'),
72
+ name: 'Load Post Content',
73
+ description: 'Fetches and stores the full article content on a Post.',
74
+ icon: 'ph--download--regular',
75
+ },
76
+ input: Schema.Struct({
77
+ post: Ref.Ref(Subscription.Post).annotations({
78
+ description: 'The Post to load content for.',
79
+ }),
80
+ force: Schema.optional(
81
+ Schema.Boolean.annotations({
82
+ description: 'Re-fetch and append fresh content even if an entry already exists (refresh).',
83
+ }),
84
+ ),
85
+ }),
86
+ output: Schema.Void,
87
+ });
88
+
89
+ /**
90
+ * End-to-end magazine curation: syncs all referenced feeds (in parallel), then runs an in-memory
91
+ * routine (the magazine's topic instructions + the registry-resolved Magazine skill) through the
92
+ * agent to select matching posts, adds the selected posts mechanically, and finally enforces each
93
+ * feed's `keep` bound. Drives the MagazineArticle Curate button; schedulable via triggers.
94
+ */
95
+ export const CurateMagazine = Operation.make({
96
+ meta: {
97
+ key: makeKey('curateMagazine'),
98
+ name: 'Curate Magazine',
99
+ description: 'Syncs feeds, selects matching posts via the magazine skill, and applies per-feed keep limits.',
100
+ icon: 'ph--sparkle--regular',
101
+ },
102
+ input: Schema.Struct({
103
+ magazine: Ref.Ref(Magazine.Magazine).annotations({
104
+ description: 'The Magazine to curate.',
105
+ }),
106
+ }),
107
+ output: Schema.Struct({
108
+ synced: Schema.Number.annotations({ description: 'Number of feeds successfully synced.' }),
109
+ curated: Schema.Number.annotations({ description: 'Number of posts added to the magazine.' }),
110
+ }),
111
+ // Database.Service for candidate collection, Registry.Service to resolve the methodology skill;
112
+ // sub-operations (SyncFeed, RunInstructions) resolve through the ambient Operation.Service invoker.
113
+ services: [Database.Service, Registry.Service],
114
+ }).pipe(Operation.visible);
115
+
116
+ /**
117
+ * Clears a Magazine's curated posts, preserving any that are starred. Drives the MagazineArticle
118
+ * Clear button.
119
+ */
120
+ export const ClearMagazine = Operation.make({
121
+ meta: {
122
+ key: makeKey('clearMagazine'),
123
+ name: 'Clear Magazine',
124
+ description: "Removes a Magazine's posts, keeping any that are starred.",
125
+ icon: 'ph--trash--regular',
126
+ },
127
+ input: Schema.Struct({
128
+ magazine: Ref.Ref(Magazine.Magazine).annotations({
129
+ description: 'The Magazine to clear.',
130
+ }),
131
+ }),
132
+ output: Schema.Void,
133
+ services: [Database.Service],
134
+ }).pipe(Operation.visible);
@@ -0,0 +1,69 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, it } from '@effect/vitest';
6
+ import * as Effect from 'effect/Effect';
7
+ import { test } from 'vitest';
8
+
9
+ import { Database, Feed, Tag } from '@dxos/echo';
10
+ import { TestHelpers } from '@dxos/effect/testing';
11
+ import { AssistantTestLayer } from '@dxos/functions-runtime/testing';
12
+ import { EntityId } from '@dxos/keys';
13
+ import { StateMap, TagIndex, Text } from '@dxos/schema';
14
+
15
+ import * as Magazine from './Magazine';
16
+ import * as Subscription from './Subscription';
17
+
18
+ EntityId.dangerouslyDisableRandomness();
19
+
20
+ const TestLayer = AssistantTestLayer({
21
+ types: [
22
+ Feed.Feed,
23
+ Subscription.Subscription,
24
+ Subscription.Post,
25
+ Magazine.Magazine,
26
+ Tag.Tag,
27
+ Text.Text,
28
+ StateMap.StateMap,
29
+ TagIndex.TagIndex,
30
+ ],
31
+ disableLlmMemoization: true,
32
+ });
33
+
34
+ describe('Magazine', () => {
35
+ describe('composeInstructions', () => {
36
+ test('returns the default methodology when no topic is set', ({ expect }) => {
37
+ expect(Magazine.composeInstructions()).toBe(Magazine.DEFAULT_INSTRUCTIONS);
38
+ expect(Magazine.composeInstructions(' ')).toBe(Magazine.DEFAULT_INSTRUCTIONS);
39
+ });
40
+
41
+ test('weaves the topic in under a Topic heading', ({ expect }) => {
42
+ const result = Magazine.composeInstructions('Space exploration');
43
+ expect(result.startsWith(Magazine.DEFAULT_INSTRUCTIONS)).toBe(true);
44
+ expect(result).toContain('## Topic');
45
+ expect(result).toContain('Space exploration');
46
+ });
47
+ });
48
+
49
+ it.effect(
50
+ 'make creates the curation routine with the magazine, seeding its instructions',
51
+ Effect.fnUntraced(
52
+ function* ({ expect }) {
53
+ const magazine = yield* Database.add(Magazine.make({ name: 'The Cosmos', instructions: 'Astronomy news' }));
54
+ yield* Database.flush();
55
+
56
+ // The instructions are created with the magazine (not lazily).
57
+ expect(magazine.instructions).toBeDefined();
58
+ const instructions = yield* Database.load(magazine.instructions!);
59
+ expect(instructions.skills.length).toBeGreaterThan(0);
60
+
61
+ const text = yield* Database.load(instructions.text);
62
+ expect(text.content).toContain('## Topic');
63
+ expect(text.content).toContain('Astronomy news');
64
+ },
65
+ Effect.provide(TestLayer),
66
+ TestHelpers.provideTestContext,
67
+ ),
68
+ );
69
+ });
@@ -0,0 +1,214 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { AppAnnotation } from '@dxos/app-toolkit';
10
+ import { Instructions, Skill } from '@dxos/compute';
11
+ import { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';
12
+ import { FormInlineAnnotation, FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
13
+ import { type EntityId } from '@dxos/keys';
14
+ import { StateMap } from '@dxos/schema';
15
+ import { trim } from '@dxos/util';
16
+
17
+ import * as Subscription from './Subscription';
18
+
19
+ export const SKILL_KEY = 'org.dxos.skill.magazine';
20
+
21
+ /**
22
+ * Default editorial methodology seeded into a Magazine's curation Routine. Describes WHAT to curate
23
+ * (selection, dedup, snippet, hero image). The HOW (candidate input shape, tool usage, output
24
+ * contract) lives in the Magazine skill, not here.
25
+ */
26
+ export const DEFAULT_INSTRUCTIONS = trim`
27
+ You curate articles for a Magazine around the Topic described below.
28
+
29
+ Select only candidates that clearly match the Topic — quality over quantity.
30
+
31
+ Never select duplicate articles. Two candidates are duplicates if they share a link or guid, or if
32
+ their titles and content describe the same story (e.g. the same article syndicated by different
33
+ feeds). Keep only one of each — prefer the most complete or authoritative source — and skip the rest.
34
+
35
+ For each candidate you select, also produce:
36
+ - A concise 1-2 sentence snippet (plain text, no markdown) capturing why the article is relevant to
37
+ the Topic. If you read the full article, use it to write a richer snippet.
38
+ - The best hero image URL for the article, when one is available.
39
+ `;
40
+
41
+ /** Per-Post magazine-scoped curation state, keyed by Post id. */
42
+ export const PostState = Schema.Struct({
43
+ /** Agent-assigned relevance within this magazine (lower = more relevant). */
44
+ rank: Schema.optional(Schema.Number),
45
+ /** Agent-written concise snippet summarising the article in the context of this magazine's topic. */
46
+ snippet: Schema.optional(Schema.String),
47
+ /** Agent-selected hero image URL for this post in this magazine. */
48
+ imageUrl: Schema.optional(Schema.String),
49
+ });
50
+ export type PostState = Schema.Schema.Type<typeof PostState>;
51
+
52
+ /**
53
+ * Structured output contract of the curation Routine: the Posts the agent selected, each with
54
+ * agent-generated display values. Declared as the Routine's `output` ({@link make}) so the agent's
55
+ * `completeJob` carries this payload (a Void/Unknown default would silently drop it); {@link
56
+ * CurateMagazine} decodes the Routine result against the same schema.
57
+ */
58
+ export const CurationOutput = Schema.Struct({
59
+ posts: Schema.Array(
60
+ Schema.Struct({
61
+ id: Obj.ID,
62
+ /** Concise 1-2 sentence snippet summarising why this article is relevant to the magazine topic. */
63
+ snippet: Schema.optional(Schema.String),
64
+ /** Best image URL found for this article (from the post or fetched content). */
65
+ imageUrl: Schema.optional(Schema.String),
66
+ }),
67
+ ),
68
+ });
69
+
70
+ /**
71
+ * An agent-curated collection of articles drawn from one or more Feeds.
72
+ * Curation is driven by a {@link Routine.Routine} created with the magazine ({@link make}): its
73
+ * instructions hold the editorial brief and it references the Magazine skill (the tool/output
74
+ * contract). {@link CurateMagazine} runs the Routine to select matching Posts.
75
+ */
76
+ export class Magazine extends Type.makeObject<Magazine>(DXN.make('org.dxos.type.magazine', '0.2.0'))(
77
+ Schema.Struct({
78
+ /** User-facing title of the magazine. */
79
+ name: Schema.String.pipe(Schema.optional),
80
+ /** Feeds to pull content from. */
81
+ feeds: Schema.Array(Ref.Ref(Subscription.Subscription)),
82
+ /** Curated Post refs (insertion order; UI displays newest-last reversed). */
83
+ posts: Schema.Array(Ref.Ref(Subscription.Post)).pipe(FormInputAnnotation.set(false)),
84
+ /**
85
+ * Curation Instructions, created with the magazine ({@link make}). Holds the editorial brief and
86
+ * references the Magazine skill. Rendered inline by the properties form (the Instructions'
87
+ * own fields), so the brief is edited there without a custom surface.
88
+ * Optional for backward compatibility; {@link CurateMagazine} and the toolbar require it.
89
+ */
90
+ instructions: Ref.Ref(Instructions.Instructions).pipe(FormInlineAnnotation.set(true), Schema.optional),
91
+ /**
92
+ * Per-Post magazine-scoped curation state, keyed by Post id. Shared per-Post state (readAt,
93
+ * star/archive tags) lives on `Subscription`; snippet/imageUrl here are agent-written at
94
+ * curation time and take precedence over the RSS-derived defaults in display.
95
+ */
96
+ postState: Ref.Ref(StateMap.StateMap).pipe(FormInputAnnotation.set(false)),
97
+ /**
98
+ * Maximum number of (non-starred) curated Posts retained on the magazine after curation.
99
+ * Older posts beyond this bound are dropped; starred posts are preserved regardless.
100
+ * Defaults to {@link Subscription.DEFAULT_KEEP} when unset.
101
+ */
102
+ keep: Schema.Number.pipe(
103
+ Schema.annotations({
104
+ title: 'Keep',
105
+ description: 'Number of items to keep.',
106
+ }),
107
+ Schema.optional,
108
+ ),
109
+ }).pipe(
110
+ LabelAnnotation.set(['name']),
111
+ Annotation.IconAnnotation.set({ icon: 'ph--book-open-text--regular', hue: 'indigo' }),
112
+ AppAnnotation.SkillsAnnotation.set([SKILL_KEY]),
113
+ ),
114
+ ) {}
115
+
116
+ /** Checks if a value is a Magazine object. */
117
+ export const instanceOf = (value: unknown): value is Magazine => Obj.instanceOf(Magazine, value);
118
+
119
+ export type MakeProps = Omit<Obj.MakeProps<typeof Magazine>, 'feeds' | 'posts' | 'instructions' | 'postState'> & {
120
+ feeds?: Ref.Ref<Subscription.Subscription>[];
121
+ posts?: Ref.Ref<Subscription.Post>[];
122
+ /** Editorial brief seeded into the curation Routine's instructions (composed with the default methodology). */
123
+ instructions?: string;
124
+ };
125
+
126
+ /**
127
+ * Creates a Magazine plus its curation Routine and per-Post state map as hidden children (all
128
+ * cascade-deleted with the magazine). The Routine holds the editorial brief (its instructions,
129
+ * seeded from {@link composeInstructions}) and references the Magazine skill by its registry DXN;
130
+ * {@link CurateMagazine} runs it and the agent resolves the skill at run time.
131
+ */
132
+ export const make = (props: MakeProps = {}): Magazine => {
133
+ const postState = StateMap.make();
134
+ const magazine = Obj.make(Magazine, {
135
+ name: props.name,
136
+ feeds: props.feeds ?? [],
137
+ posts: props.posts ?? [],
138
+ keep: props.keep,
139
+ postState: Ref.make(postState),
140
+ });
141
+
142
+ const instructions = Instructions.make({
143
+ name: props.name ? `${props.name} curation` : 'Magazine curation',
144
+ text: composeInstructions(props.instructions),
145
+ skills: [Ref.fromURI(Skill.registryURI(SKILL_KEY))],
146
+ // Bind the magazine as session context so the agent sees it, not only the candidate JSON input.
147
+ objects: [Ref.make(magazine)],
148
+ // Declare the selection contract so the agent's completeJob carries the chosen Posts.
149
+ output: CurationOutput,
150
+ });
151
+ Obj.update(magazine, (magazine) => {
152
+ magazine.instructions = Ref.make(instructions);
153
+ });
154
+
155
+ // Cascade-delete the Instructions object (and its Text) and the per-Post state with the magazine.
156
+ Obj.setParent(instructions, magazine);
157
+ if (instructions.text.target) {
158
+ Obj.setParent(instructions.text.target, instructions);
159
+ }
160
+ Obj.setParent(postState, magazine);
161
+ return magazine;
162
+ };
163
+
164
+ /** Composes Routine instructions from the default editorial methodology and an optional topic focus. */
165
+ export const composeInstructions = (topic?: string): string => {
166
+ const trimmed = topic?.trim();
167
+ return trimmed ? `${DEFAULT_INSTRUCTIONS}\n\n## Topic\n\n${trimmed}` : DEFAULT_INSTRUCTIONS;
168
+ };
169
+
170
+ /** Schema for the create-magazine dialog form. */
171
+ export const CreateMagazineSchema = Schema.Struct({
172
+ name: Schema.optional(
173
+ Schema.String.annotations({
174
+ title: 'Name',
175
+ }),
176
+ ),
177
+ feeds: Schema.Array(Ref.Ref(Subscription.Subscription)).annotations({
178
+ title: 'Feeds',
179
+ }),
180
+ instructions: Schema.optional(
181
+ Schema.String.annotations({
182
+ title: 'Topic',
183
+ description: 'Describe what content to curate.',
184
+ }),
185
+ ),
186
+ });
187
+
188
+ //
189
+ // Per-Post magazine-scoped curation state, keyed by Post id.
190
+ //
191
+
192
+ /** Agent-assigned relevance of a Post within a Magazine, or undefined. */
193
+ export const getRank = (magazine: Magazine | undefined, postId: EntityId): number | undefined => {
194
+ const stateMap = magazine?.postState.target;
195
+ return stateMap ? StateMap.bind<PostState>(stateMap).get(postId).rank : undefined;
196
+ };
197
+
198
+ /** Returns the full magazine-scoped {@link PostState} for a Post, or an empty record. */
199
+ export const getPostState = (magazine: Magazine | undefined, postId: EntityId): Partial<PostState> => {
200
+ const stateMap = magazine?.postState.target;
201
+ return stateMap ? StateMap.bind<PostState>(stateMap).get(postId) : {};
202
+ };
203
+
204
+ /** Merges partial state into a Post's magazine-scoped {@link PostState}. */
205
+ export const patchPostState = (magazine: Magazine, postId: EntityId, state: Partial<PostState>): void => {
206
+ const stateMap = magazine.postState.target;
207
+ if (stateMap) {
208
+ StateMap.bind<PostState>(stateMap).patch(postId, state);
209
+ }
210
+ };
211
+
212
+ /** Sets the magazine-scoped rank for a Post. */
213
+ export const setRank = (magazine: Magazine, postId: EntityId, rank: number): void =>
214
+ patchPostState(magazine, postId, { rank });