@dxos/plugin-magazine 0.10.0 → 0.11.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.
- package/dist/lib/MagazinePlugin.mjs +36 -0
- package/dist/lib/MagazinePlugin.mjs.map +1 -0
- package/dist/lib/MagazinePlugin.workerd.mjs +26 -0
- package/dist/lib/MagazinePlugin.workerd.mjs.map +1 -0
- package/dist/lib/atoms.mjs +175 -0
- package/dist/lib/atoms.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +12 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-FeedArticle.mjs +70 -0
- package/dist/lib/chunk-FeedArticle.mjs.map +1 -0
- package/dist/lib/chunk-FeedProperties.mjs +78 -0
- package/dist/lib/chunk-FeedProperties.mjs.map +1 -0
- package/dist/lib/chunk-MagazineArticle.mjs +267 -0
- package/dist/lib/chunk-MagazineArticle.mjs.map +1 -0
- package/dist/lib/chunk-PostArticle.mjs +174 -0
- package/dist/lib/chunk-PostArticle.mjs.map +1 -0
- package/dist/lib/chunk-PostCard.mjs +62 -0
- package/dist/lib/chunk-PostCard.mjs.map +1 -0
- package/dist/lib/chunk-app-graph-builder.mjs +91 -0
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
- package/dist/lib/chunk-clear-magazine.mjs +37 -0
- package/dist/lib/chunk-clear-magazine.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +59 -0
- package/dist/lib/chunk-create-object.mjs.map +1 -0
- package/dist/lib/chunk-curate-magazine.mjs +164 -0
- package/dist/lib/chunk-curate-magazine.mjs.map +1 -0
- package/dist/lib/chunk-date.mjs +25 -0
- package/dist/lib/chunk-date.mjs.map +1 -0
- package/dist/lib/chunk-fetch-article-content.mjs +26 -0
- package/dist/lib/chunk-fetch-article-content.mjs.map +1 -0
- package/dist/lib/chunk-load-post-content.mjs +57 -0
- package/dist/lib/chunk-load-post-content.mjs.map +1 -0
- package/dist/lib/{neutral/chunk-JFPR6OV4.mjs → chunk-meta.mjs} +19 -26
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +13 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-paths.mjs +8 -0
- package/dist/lib/chunk-paths.mjs.map +1 -0
- package/dist/lib/chunk-post-content.mjs +14 -0
- package/dist/lib/chunk-post-content.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +60 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-routine-templates.mjs +52 -0
- package/dist/lib/chunk-routine-templates.mjs.map +1 -0
- package/dist/lib/chunk-skill-definition.mjs +12 -0
- package/dist/lib/chunk-skill-definition.mjs.map +1 -0
- package/dist/lib/chunk-sources.mjs +576 -0
- package/dist/lib/chunk-sources.mjs.map +1 -0
- package/dist/lib/chunk-sync-feed.mjs +86 -0
- package/dist/lib/chunk-sync-feed.mjs.map +1 -0
- package/dist/lib/chunk-text.mjs +52 -0
- package/dist/lib/chunk-text.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +607 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/components.mjs +323 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +11 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/index.mjs +3 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/plugin.workerd.mjs +3 -0
- package/dist/lib/{neutral/skills/index.mjs → skills.mjs} +15 -28
- package/dist/lib/skills.mjs.map +1 -0
- package/dist/lib/testing.mjs +71 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +74 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/MagazinePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
- package/dist/types/src/capabilities/create-object.d.ts +1 -1
- package/dist/types/src/capabilities/index.d.ts +0 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/routine-templates.d.ts +1 -1
- package/dist/types/src/components/PostContent/PostContent.d.ts +1 -13
- package/dist/types/src/components/PostContent/PostContent.d.ts.map +1 -1
- package/dist/types/src/components/PostContent/dedupe-images.d.ts +13 -0
- package/dist/types/src/components/PostContent/dedupe-images.d.ts.map +1 -0
- package/dist/types/src/components/PostContent/index.d.ts +1 -0
- package/dist/types/src/components/PostContent/index.d.ts.map +1 -1
- package/dist/types/src/components/PostStack/PostStack.d.ts +1 -1
- package/dist/types/src/components/PostStack/PostStack.d.ts.map +1 -1
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts +1 -1
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts.map +1 -1
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts +4 -1
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts.map +1 -1
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts +4 -1
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts +4 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts +4 -1
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts +4 -8
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts +4 -1
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts +1 -1
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts.map +1 -1
- package/dist/types/src/containers/PostCard/PostCard.d.ts +4 -6
- package/dist/types/src/containers/PostCard/PostCard.d.ts.map +1 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts +4 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts.map +1 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts.map +1 -1
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts +1 -0
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts.map +1 -1
- package/dist/types/src/stories/MagazineCurate.stories.d.ts.map +1 -1
- package/dist/types/src/templates/magazine-curation.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +1 -1
- package/package.json +57 -60
- package/src/MagazinePlugin.tsx +0 -3
- package/src/atoms/atoms.test.ts +5 -4
- package/src/capabilities/app-graph-builder.ts +8 -6
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/{react-surface.tsx → react-surface.ts} +10 -12
- package/src/components/PostContent/PostContent.test.ts +1 -1
- package/src/components/PostContent/PostContent.tsx +1 -41
- package/src/components/PostContent/dedupe-images.ts +48 -0
- package/src/components/PostContent/index.ts +1 -0
- package/src/containers/FeedArticle/FeedArticle.stories.tsx +2 -2
- package/src/containers/FeedArticle/FeedArticle.tsx +4 -2
- package/src/containers/FeedProperties/FeedProperties.tsx +6 -4
- package/src/containers/MagazineArticle/MagazineArticle.stories.tsx +2 -1
- package/src/containers/MagazineArticle/MagazineArticle.tsx +8 -6
- package/src/containers/MagazineArticle/MagazineTile.tsx +2 -0
- package/src/containers/MagazineArticle/useToolbar.tsx +2 -0
- package/src/containers/PostArticle/PostArticle.stories.tsx +2 -1
- package/src/containers/PostArticle/PostArticle.tsx +6 -3
- package/src/containers/PostArticle/PostToolbar.tsx +1 -1
- package/src/containers/PostCard/PostCard.tsx +3 -1
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.tsx +2 -1
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.tsx +5 -3
- package/src/operations/curate-magazine.test.ts +108 -4
- package/src/operations/sync-feed.test.ts +1 -1
- package/src/operations/sync-feed.ts +1 -1
- package/src/paths.ts +3 -3
- package/src/stories/ArticleExtractor.stories.tsx +1 -1
- package/src/stories/MagazineCurate.stories.tsx +3 -2
- package/src/templates/magazine-curation.ts +3 -2
- package/src/types/Magazine.test.ts +1 -1
- package/src/types/Subscription.ts +1 -1
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs +0 -90
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs.map +0 -7
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs +0 -97
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs.map +0 -7
- package/dist/lib/neutral/MagazineArticle-5T7RTIPS.mjs +0 -325
- package/dist/lib/neutral/MagazineArticle-5T7RTIPS.mjs.map +0 -7
- package/dist/lib/neutral/MagazinePlugin.mjs +0 -62
- package/dist/lib/neutral/MagazinePlugin.mjs.map +0 -7
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs +0 -12
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/PostArticle-BGIPU7XA.mjs +0 -230
- package/dist/lib/neutral/PostArticle-BGIPU7XA.mjs.map +0 -7
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs +0 -60
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs +0 -121
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -21
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs +0 -12
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs +0 -591
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-APQRTYT3.mjs +0 -57
- package/dist/lib/neutral/chunk-APQRTYT3.mjs.map +0 -7
- package/dist/lib/neutral/chunk-GU725ZF4.mjs +0 -11
- package/dist/lib/neutral/chunk-GU725ZF4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JFPR6OV4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs +0 -36
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs.map +0 -7
- package/dist/lib/neutral/chunk-LFSQMLPK.mjs +0 -8
- package/dist/lib/neutral/chunk-LFSQMLPK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs +0 -14
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs +0 -624
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs +0 -28
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XCNCK47N.mjs +0 -15
- package/dist/lib/neutral/chunk-XCNCK47N.mjs.map +0 -7
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs +0 -59
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs.map +0 -7
- package/dist/lib/neutral/components/index.mjs +0 -338
- package/dist/lib/neutral/components/index.mjs.map +0 -7
- package/dist/lib/neutral/containers/index.mjs +0 -17
- package/dist/lib/neutral/containers/index.mjs.map +0 -7
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs +0 -98
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs.map +0 -7
- package/dist/lib/neutral/curate-magazine-7RMKSJC5.mjs +0 -184
- package/dist/lib/neutral/curate-magazine-7RMKSJC5.mjs.map +0 -7
- package/dist/lib/neutral/fetch-article-content-CXPKXIMR.mjs +0 -31
- package/dist/lib/neutral/fetch-article-content-CXPKXIMR.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -18
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/load-post-content-H3S4TKVM.mjs +0 -65
- package/dist/lib/neutral/load-post-content-H3S4TKVM.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -8
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs +0 -18
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs.map +0 -7
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs +0 -8
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs.map +0 -7
- package/dist/lib/neutral/operations/index.mjs +0 -8
- package/dist/lib/neutral/operations/index.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -16
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/plugin.workerd.mjs +0 -16
- package/dist/lib/neutral/plugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs +0 -60
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs.map +0 -7
- package/dist/lib/neutral/routine-templates-D5A2L4KL.mjs +0 -52
- package/dist/lib/neutral/routine-templates-D5A2L4KL.mjs.map +0 -7
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs +0 -8
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs.map +0 -7
- package/dist/lib/neutral/skills/index.mjs.map +0 -7
- package/dist/lib/neutral/sync-feed-SDXYF7HU.mjs +0 -99
- package/dist/lib/neutral/sync-feed-SDXYF7HU.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -81
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/dist/lib/neutral/types/index.mjs +0 -14
- package/dist/lib/neutral/types/index.mjs.map +0 -7
- package/dist/types/src/capabilities/navigation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/navigation-resolver.d.ts.map +0 -1
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts +0 -2
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts.map +0 -1
- package/src/capabilities/navigation-resolver.ts +0 -21
- package/src/operations/curate-magazine.skill.conversations.json +0 -1
- package/src/operations/curate-magazine.skill.test.ts +0 -167
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-types.mjs","names":[],"sources":["../../src/types/Subscription.ts","../../src/types/CreateSubscription.ts","../../src/types/Magazine.ts","../../src/types/FeedOperation.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { type Space } from '@dxos/client/echo';\nimport { Annotation, Database, DXN, Feed, Filter, Obj, Query, Ref, Scope, Tag, Type } from '@dxos/echo';\nimport { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';\nimport { EffectEx } from '@dxos/effect';\nimport { type EntityId } from '@dxos/keys';\nimport { FactoryAnnotation, type FactoryFn, FeedAnnotation, StateMap, TagIndex } from '@dxos/schema';\n\n// TODO: FeedType is currently a closed literal — plugins like plugin-bluesky must add their type here.\n// Consider making it extensible (e.g. a string schema with a well-known-values registry) so plugins can\n// register feed types without modifying core.\n/** Subscription protocol type. */\nexport const FeedType = Schema.Literal('standard-site', 'rss', 'bluesky');\nexport type FeedType = Schema.Schema.Type<typeof FeedType>;\n\n/**\n * System per-Post flags (starred/archived) modelled as space-wide {@link Tag} objects (resolved via\n * `Tag.findOrCreate` on first use). Each entry is a single source of truth carrying its foreign\n * `key` plus display `label`/`hue`.\n */\nexport const SYSTEM_TAGS = {\n starred: { key: { source: 'org.dxos.plugin.magazine', id: 'starred' }, label: 'Starred', hue: 'amber' },\n archived: { key: { source: 'org.dxos.plugin.magazine', id: 'archived' }, label: 'Archived', hue: 'neutral' },\n} as const;\n\n/** A system per-Post flag modelled as a tag. */\nexport type SystemTag = keyof typeof SYSTEM_TAGS;\n\n/** Per-Post mutable state stored on the Subscription (shared across magazines), keyed by Post id. */\nexport const PostState = Schema.Struct({\n /** ISO 8601 timestamp when first opened. */\n readAt: Schema.optional(Schema.String),\n});\nexport type PostState = Schema.Schema.Type<typeof PostState>;\n\n/**\n * Default upper bound on the number of (non-starred) Posts retained when\n * syncing a {@link Subscription} or curating a Magazine. Applied when the object\n * has no explicit `keep` value.\n */\nexport const DEFAULT_KEEP = 10;\n\n/**\n * Subscription feed schema: an RSS/Atom/AT Protocol subscription.\n */\nexport class Subscription extends Type.makeObject<Subscription>(DXN.make('org.dxos.type.subscription.feed', '0.1.0'))(\n Schema.Struct({\n /** User-facing title of the feed. */\n name: Schema.String.pipe(Schema.optional),\n /** The URL of the RSS/Atom feed or AT Protocol handle/DID. */\n url: Schema.String.pipe(Schema.optional),\n /** Protocol type — determines which fetcher is used for sync. */\n type: FeedType.pipe(Schema.optional),\n /** Description of the feed. */\n description: Schema.String.pipe(Schema.optional),\n /** URL of the feed's associated website. */\n link: Schema.String.pipe(Schema.optional),\n /** URL of the feed's icon/image. */\n iconUrl: Schema.String.pipe(Schema.optional),\n /**\n * Maximum number of (non-starred) Posts retained in the feed's queue when syncing.\n * Older posts beyond this bound are dropped; starred posts are preserved regardless.\n * Defaults to {@link DEFAULT_KEEP} when unset.\n */\n keep: Schema.Number.pipe(\n Schema.annotations({\n title: 'Keep',\n description: 'Number of synced items.',\n }),\n Schema.optional,\n ),\n /** Opaque sync cursor — protocol-specific. */\n cursor: Schema.String.pipe(FormInputAnnotation.set(false), Schema.optional),\n /** Backing ECHO feed (queue) for Posts: immutable feed entries appended by sync. */\n feed: Ref.Ref(Feed.Feed).pipe(FormInputAnnotation.set(false)),\n /**\n * Backing ECHO feed (queue) for fetched article bodies — one\n * {@link PostContent} entry per Post whose content has been loaded.\n *\n * Feeds (not the {@link postState} side map) are the right home for big\n * immutable payloads: they're append-only, lazily replicated, and don't\n * inflate the Subscription document. The side map keeps only the small\n * per-Post user state (read/archived/star/imageUrl).\n *\n * Optional for backwards compatibility with older Subscription documents\n * that pre-date this feed; new subscriptions always have one via\n * {@link makeSubscription}.\n */\n contentFeed: Ref.Ref(Feed.Feed).pipe(FormInputAnnotation.set(false), Schema.optional),\n /**\n * Per-Post mutable state keyed by Post id, shared across every Magazine that references the Post.\n * Posts live immutably in the `feed` queue; their `readAt` marker lives here. (`snippet`/`imageUrl`\n * are derived from the Post, or refined onto `contentFeed` entries — not stored here; star/archive\n * are tags — see `tags`.)\n */\n postState: Ref.Ref(StateMap.StateMap).pipe(FormInputAnnotation.set(false)),\n /**\n * Per-Post tags keyed by tag uri → Post ids. Boolean flags (starred, archived — see\n * {@link SYSTEM_TAGS}) are modelled as {@link Tag} objects so they\n * participate in the space-wide tag system. Stored as a child {@link TagIndex} object.\n */\n tags: Ref.Ref(TagIndex.TagIndex).pipe(FormInputAnnotation.set(false)),\n }).pipe(\n LabelAnnotation.set(['name', 'url']),\n Annotation.IconAnnotation.set({ icon: 'ph--rss--regular', hue: 'indigo' }),\n FeedAnnotation.set(true),\n FactoryAnnotation.set(((values) => makeSubscription(values)) as FactoryFn),\n ),\n) {}\n\n/** Checks if a value is a Subscription.Subscription object. */\nexport const instanceOf = (value: unknown): value is Subscription => Obj.instanceOf(Subscription, value);\n\n/** Creates a Subscription.Subscription with backing ECHO feeds for posts and fetched content. */\nexport const makeSubscription = (\n props: Omit<Obj.MakeProps<typeof Subscription>, 'feed' | 'contentFeed' | 'postState' | 'tags'> = {},\n): Subscription => {\n const postFeed = Feed.make();\n const contentFeed = Feed.make();\n const postState = StateMap.make();\n const tags = TagIndex.make();\n const subscription = Obj.make(Subscription, {\n feed: Ref.make(postFeed),\n contentFeed: Ref.make(contentFeed),\n postState: Ref.make(postState),\n tags: Ref.make(tags),\n ...props,\n });\n Obj.setParent(postFeed, subscription);\n Obj.setParent(contentFeed, subscription);\n // Per-Post state and tag index are children: cascade-deleted with the subscription.\n Obj.setParent(postState, subscription);\n Obj.setParent(tags, subscription);\n return subscription;\n};\n\n/**\n * A single post/entry within a subscription feed.\n *\n * Immutable feed-entry: every field below is set at sync time by the protocol\n * fetcher and never written to again. Mutable state (read, archived, starred,\n * fetched content, hero image) lives on\n * {@link Subscription.Subscription.postState} keyed by Post id; curation\n * outputs (snippet, rank) live on `Magazine.postState`.\n */\nexport class Post extends Type.makeObject<Post>(DXN.make('org.dxos.type.subscription.post', '0.1.0'))(\n Schema.Struct({\n /** Source subscription feed; populated by `SyncSubscription` so curated posts can show provenance. */\n source: Ref.Ref(Subscription).pipe(FormInputAnnotation.set(false), Schema.optional),\n /** Post title. */\n title: Schema.String.pipe(Schema.optional),\n /** URL link to the original article. */\n link: Schema.String.pipe(Schema.optional),\n /** Plain-text or summary. */\n description: Schema.String.pipe(Schema.optional),\n /** Plain-text or HTML content. */\n content: Schema.String.pipe(Schema.optional),\n /** Author name. */\n author: Schema.String.pipe(Schema.optional),\n /** ISO 8601 publication date. */\n published: Schema.String.pipe(Schema.optional),\n /** Unique identifier (guid) from the feed. */\n guid: Schema.String.pipe(Schema.optional),\n }).pipe(\n LabelAnnotation.set(['title']),\n Annotation.IconAnnotation.set({ icon: 'ph--article--regular', hue: 'indigo' }),\n ),\n) {}\n\n/** Creates a Subscription.Post object. */\nexport const makePost = (props: Obj.MakeProps<typeof Post> = {}): Post => Obj.make(Post, props);\n\n/**\n * An entry in a Subscription's {@link Subscription.contentFeed} queue: the\n * fetched body of a Post, linked back to the queue Post in the parallel `feed`\n * queue via {@link post}.\n *\n * Lives in a queue (not on `Subscription.postState`) because fetched bodies\n * can be many KB; storing them inline would inflate the Subscription document\n * unboundedly and force every reader to download every body. The queue is\n * lazily replicated and append-only — a natural fit for immutable content.\n */\nexport class PostContent extends Type.makeObject<PostContent>(\n DXN.make('org.dxos.type.subscription.postContent', '0.1.0'),\n)(\n Schema.Struct({\n /** Source Post (in the Subscription's `feed` queue). */\n post: Ref.Ref(Post).pipe(FormInputAnnotation.set(false)),\n /** Extracted article body, in Markdown. */\n text: Schema.String,\n /** Refined snippet derived from the full article (preferred over the description-derived one). */\n snippet: Schema.optional(Schema.String),\n /** Refined hero image derived from the full article (preferred over the description-derived one). */\n imageUrl: Schema.optional(Schema.String),\n /** ISO 8601 timestamp when the content was fetched. */\n fetchedAt: Schema.String,\n }).pipe(Annotation.HiddenAnnotation.set(true)),\n) {}\n\n//\n// Per-Post state, keyed by Post entity id (Posts are immutable queue items). Read marker lives in\n// `postState`; star/archive are system Tags indexed in `tags`. Shared across magazines.\n//\n\n/** ISO timestamp the Post was first opened, or undefined. */\nexport const getReadAt = (\n subscription: Subscription | Obj.Snapshot<Subscription> | undefined,\n postId: EntityId,\n): string | undefined => {\n const stateMap = subscription?.postState.target;\n return stateMap ? StateMap.bind<PostState>(stateMap).get(postId).readAt : undefined;\n};\n\n/** Sets/clears the Post's read marker. */\nexport const setReadAt = (subscription: Subscription, postId: EntityId, value: string | undefined): void => {\n const stateMap = subscription.postState.target;\n if (stateMap) {\n StateMap.bind<PostState>(stateMap).patch(postId, { readAt: value });\n }\n};\n\n/** Resolves the uri of an existing system Tag without creating one; `undefined` when absent. Async. */\nexport const findSystemTagUri = async (\n db: Pick<Database.Database, 'query'>,\n which: SystemTag,\n): Promise<string | undefined> => {\n // Prefer the current key; fall back to the legacy 'org.dxos.plugin.feed' source so\n // starred/archived tags created before the plugin-feed → plugin-magazine rename still resolve.\n // Two separate queries ensure deterministic priority when both tags co-exist.\n const [currentTag] = await db.query(Filter.foreignKeys(Tag.Tag, [SYSTEM_TAGS[which].key])).run();\n if (currentTag) {\n return Obj.getURI(currentTag).toString();\n }\n const legacyKey = { source: 'org.dxos.plugin.feed', id: SYSTEM_TAGS[which].key.id };\n const [legacyTag] = await db.query(Filter.foreignKeys(Tag.Tag, [legacyKey])).run();\n return legacyTag ? Obj.getURI(legacyTag).toString() : undefined;\n};\n\n/** Whether a Post carries the given (resolved) tag uri on its Subscription. Pure. */\nexport const hasTag = (\n subscription: Subscription | Obj.Snapshot<Subscription> | undefined,\n postId: EntityId,\n tagUri: string | undefined,\n): boolean => {\n const tagIndex = subscription?.tags.target;\n return !!(tagIndex && tagUri) && TagIndex.bind(tagIndex).objects(tagUri).includes(postId);\n};\n\n/** Sets/clears a system tag (`starred`/`archived`) on a Post (find-or-creates the Tag object). Async. */\nexport const setTag = async (\n subscription: Subscription,\n postId: EntityId,\n db: Pick<Database.Database, 'query' | 'add'>,\n which: SystemTag,\n value: boolean,\n): Promise<void> => {\n const tag = await Tag.findOrCreate(db, SYSTEM_TAGS[which]);\n const uri = Obj.getURI(tag).toString();\n const tagIndex = subscription.tags.target;\n if (!tagIndex) {\n return;\n }\n const tags = TagIndex.bind(tagIndex);\n if (value) {\n tags.setTag(uri, postId);\n } else {\n tags.unsetTag(uri, postId);\n }\n};\n\n//\n// Content feed (fetched article bodies; carries refined snippet/imageUrl).\n//\n\n/** Resolves a Post's source Subscription synchronously if its `source` ref is loaded, else undefined. */\nexport const getPostSubscription = (post: Post): Subscription | undefined => post.source?.target;\n\n/**\n * Query for {@link PostContent} entries referencing a Post via the reverse-ref index.\n * Scoped to the Subscription's post and content feeds.\n */\nexport const queryPostContentForPost = (\n subscription: Subscription | Obj.Snapshot<Subscription>,\n post: Post | Obj.Snapshot<Post>,\n): Query.Any | undefined => {\n const postFeed = subscription.feed?.target;\n const contentFeed = subscription.contentFeed?.target;\n if (!postFeed || !contentFeed) {\n return undefined;\n }\n const postFeedUri = Obj.getURI(postFeed);\n const contentFeedUri = Obj.getURI(contentFeed);\n return Query.select(Filter.id(post.id))\n .referencedBy(PostContent, 'post')\n .from(Scope.feed(postFeedUri), Scope.feed(contentFeedUri));\n};\n\n/** Pick the newest PostContent when multiple entries reference the same Post. */\nexport const pickLatestPostContent = (entries: readonly PostContent[]): PostContent | undefined => {\n if (entries.length === 0) {\n return undefined;\n }\n return [...entries].sort((entryA, entryB) => entryB.fetchedAt.localeCompare(entryA.fetchedAt))[0];\n};\n\n/** Looks up PostContent for a Post (newest wins); undefined if absent. */\nexport const findPostContent = async (\n subscription: Subscription | Obj.Snapshot<Subscription>,\n post: Post | Obj.Snapshot<Post>,\n): Promise<PostContent | undefined> => {\n const db = Obj.getDatabase(subscription);\n const query = queryPostContentForPost(subscription, post);\n if (!db || !query) {\n return undefined;\n }\n return pickLatestPostContent(await db.query(query).run());\n};\n\n/** Returns the Subscription's `contentFeed`, lazily creating and attaching one when missing. */\nconst ensureContentFeed = (subscription: Subscription): Feed.Feed => {\n const existing = subscription.contentFeed?.target;\n if (existing) {\n return existing;\n }\n const created = Feed.make();\n Obj.update(subscription, (subscription) => {\n const mutable = subscription as Obj.Mutable<typeof subscription>;\n mutable.contentFeed = Ref.make(created);\n });\n Obj.setParent(created, subscription);\n return created;\n};\n\n/**\n * Appends a {@link PostContent} entry (body + refined snippet/imageUrl) to a Subscription's\n * `contentFeed`, lazily creating the feed on first use.\n */\nexport const appendPostContent = async (\n space: Pick<Space, 'db'>,\n subscription: Subscription,\n entry: {\n post: Post | Obj.Snapshot<Post>;\n text: string;\n snippet?: string;\n imageUrl?: string;\n fetchedAt?: string;\n },\n): Promise<void> => {\n const echoFeed = ensureContentFeed(subscription);\n const content = Obj.make(PostContent, {\n post: Ref.fromURI(Obj.getURI(entry.post)),\n text: entry.text,\n ...(entry.snippet ? { snippet: entry.snippet } : {}),\n ...(entry.imageUrl ? { imageUrl: entry.imageUrl } : {}),\n fetchedAt: entry.fetchedAt ?? new Date().toISOString(),\n });\n await Feed.append(echoFeed, [content]).pipe(Effect.provide(Database.layer(space.db)), EffectEx.runAndForwardErrors);\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Schema from 'effect/Schema';\n\nimport { Format } from '@dxos/echo';\n\nimport * as Subscription from './Subscription';\n\n// Structural gates (regex / URL format), used as form-field validation and to short-circuit the network\n// effects on obviously-malformed input. Handle existence is verified implicitly by the publication lookup\n// (an unresolvable handle yields no publications, and a publication is required to submit).\nexport const HandleSchema = Schema.String.pipe(\n Schema.pattern(/^@?([\\da-z-]+\\.)+[a-z]{2,}$|^did:[a-z]+:[a-zA-Z0-9._%:-]+$/i),\n);\n\nexport const isHandle = Schema.is(HandleSchema);\nexport const isUrl = Schema.is(Format.URL);\n\n//\n// Base structs (no dynamic annotations). The capability layer (create-object.ts) applies annotation\n// closures on top — those closures reference operations/sources and so cannot live in this module.\n//\n\nexport const StandardSiteCreateBase = Schema.Struct({\n type: Schema.Literal('standard-site'),\n handle: HandleSchema.annotations({ title: 'Handle', description: 'atproto handle, e.g. dxos.org.' }),\n // No `name`: the feed name is taken from the selected publication (resolved by `fetchStandardSite`).\n publication: Schema.String.annotations({ title: 'Publication', description: 'Choose a publication.' }),\n});\n\nexport type StandardSiteValues = Schema.Schema.Type<typeof StandardSiteCreateBase>;\n\nexport const RssCreateBase = Schema.Struct({\n type: Schema.Literal('rss'),\n url: Format.URL.annotations({ title: 'URL', description: 'RSS feed URL.' }),\n name: Schema.optional(Schema.String.annotations({ title: 'Name' })),\n});\n\nexport type RssValues = Schema.Schema.Type<typeof RssCreateBase>;\n\n// Input type covers both annotated union members; the union itself is built in create-object.ts after\n// annotations are applied. The switch narrows on `type` which is common to both shapes.\nexport type CreateSubscriptionInput =\n | { type: 'standard-site'; handle: string; publication: string }\n | { type: 'rss'; url: string; name?: string };\n\n/** Normalizes a create-form union member into the stored {@link Subscription.Subscription} fields. */\nexport const makeSubscriptionFromCreate = (input: CreateSubscriptionInput): Subscription.Subscription => {\n switch (input.type) {\n case 'standard-site':\n // `url` stores the publication site reference (at:// or https://); sync derives the author DID\n // from it directly — no handle stored, name resolved from the publication at sync time.\n return Subscription.makeSubscription({ type: 'standard-site', url: input.publication });\n case 'rss':\n return Subscription.makeSubscription({ type: 'rss', url: input.url, name: input.name });\n }\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Schema from 'effect/Schema';\n\nimport { AppAnnotation } from '@dxos/app-toolkit';\nimport { Instructions, Skill } from '@dxos/compute';\nimport { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';\nimport { FormInlineAnnotation, FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';\nimport { type EntityId } from '@dxos/keys';\nimport { StateMap } from '@dxos/schema';\nimport { trim } from '@dxos/util';\n\nimport * as Subscription from './Subscription';\n\nexport const SKILL_KEY = 'org.dxos.skill.magazine';\n\n/**\n * Default editorial methodology seeded into a Magazine's curation Routine. Describes WHAT to curate\n * (selection, dedup, snippet, hero image). The HOW (candidate input shape, tool usage, output\n * contract) lives in the Magazine skill, not here.\n */\nexport const DEFAULT_INSTRUCTIONS = trim`\n You curate articles for a Magazine around the Topic described below.\n\n Select only candidates that clearly match the Topic — quality over quantity.\n\n Never select duplicate articles. Two candidates are duplicates if they share a link or guid, or if\n their titles and content describe the same story (e.g. the same article syndicated by different\n feeds). Keep only one of each — prefer the most complete or authoritative source — and skip the rest.\n\n For each candidate you select, also produce:\n - A concise 1-2 sentence snippet (plain text, no markdown) capturing why the article is relevant to\n the Topic. If you read the full article, use it to write a richer snippet.\n - The best hero image URL for the article, when one is available.\n`;\n\n/** Per-Post magazine-scoped curation state, keyed by Post id. */\nexport const PostState = Schema.Struct({\n /** Agent-assigned relevance within this magazine (lower = more relevant). */\n rank: Schema.optional(Schema.Number),\n /** Agent-written concise snippet summarising the article in the context of this magazine's topic. */\n snippet: Schema.optional(Schema.String),\n /** Agent-selected hero image URL for this post in this magazine. */\n imageUrl: Schema.optional(Schema.String),\n});\nexport type PostState = Schema.Schema.Type<typeof PostState>;\n\n/**\n * Structured output contract of the curation Routine: the Posts the agent selected, each with\n * agent-generated display values. Declared as the Routine's `output` ({@link make}) so the agent's\n * `completeJob` carries this payload (a Void/Unknown default would silently drop it); {@link\n * CurateMagazine} decodes the Routine result against the same schema.\n */\nexport const CurationOutput = Schema.Struct({\n posts: Schema.Array(\n Schema.Struct({\n id: Obj.ID,\n /** Concise 1-2 sentence snippet summarising why this article is relevant to the magazine topic. */\n snippet: Schema.optional(Schema.String),\n /** Best image URL found for this article (from the post or fetched content). */\n imageUrl: Schema.optional(Schema.String),\n }),\n ),\n});\n\n/**\n * An agent-curated collection of articles drawn from one or more Feeds.\n * Curation is driven by a {@link Routine.Routine} created with the magazine ({@link make}): its\n * instructions hold the editorial brief and it references the Magazine skill (the tool/output\n * contract). {@link CurateMagazine} runs the Routine to select matching Posts.\n */\nexport class Magazine extends Type.makeObject<Magazine>(DXN.make('org.dxos.type.magazine', '0.2.0'))(\n Schema.Struct({\n /** User-facing title of the magazine. */\n name: Schema.String.pipe(Schema.optional),\n /** Feeds to pull content from. */\n feeds: Schema.Array(Ref.Ref(Subscription.Subscription)),\n /** Curated Post refs (insertion order; UI displays newest-last reversed). */\n posts: Schema.Array(Ref.Ref(Subscription.Post)).pipe(FormInputAnnotation.set(false)),\n /**\n * Curation Instructions, created with the magazine ({@link make}). Holds the editorial brief and\n * references the Magazine skill. Rendered inline by the properties form (the Instructions'\n * own fields), so the brief is edited there without a custom surface.\n * Optional for backward compatibility; {@link CurateMagazine} and the toolbar require it.\n */\n instructions: Ref.Ref(Instructions.Instructions).pipe(FormInlineAnnotation.set(true), Schema.optional),\n /**\n * Per-Post magazine-scoped curation state, keyed by Post id. Shared per-Post state (readAt,\n * star/archive tags) lives on `Subscription`; snippet/imageUrl here are agent-written at\n * curation time and take precedence over the RSS-derived defaults in display.\n */\n postState: Ref.Ref(StateMap.StateMap).pipe(FormInputAnnotation.set(false)),\n /**\n * Maximum number of (non-starred) curated Posts retained on the magazine after curation.\n * Older posts beyond this bound are dropped; starred posts are preserved regardless.\n * Defaults to {@link Subscription.DEFAULT_KEEP} when unset.\n */\n keep: Schema.Number.pipe(\n Schema.annotations({\n title: 'Keep',\n description: 'Number of items to keep.',\n }),\n Schema.optional,\n ),\n }).pipe(\n LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--book-open-text--regular', hue: 'indigo' }),\n AppAnnotation.SkillsAnnotation.set([SKILL_KEY]),\n ),\n) {}\n\n/** Checks if a value is a Magazine object. */\nexport const instanceOf = (value: unknown): value is Magazine => Obj.instanceOf(Magazine, value);\n\nexport type MakeProps = Omit<Obj.MakeProps<typeof Magazine>, 'feeds' | 'posts' | 'instructions' | 'postState'> & {\n feeds?: Ref.Ref<Subscription.Subscription>[];\n posts?: Ref.Ref<Subscription.Post>[];\n /** Editorial brief seeded into the curation Routine's instructions (composed with the default methodology). */\n instructions?: string;\n};\n\n/**\n * Creates a Magazine plus its curation Routine and per-Post state map as hidden children (all\n * cascade-deleted with the magazine). The Routine holds the editorial brief (its instructions,\n * seeded from {@link composeInstructions}) and references the Magazine skill by its registry DXN;\n * {@link CurateMagazine} runs it and the agent resolves the skill at run time.\n */\nexport const make = (props: MakeProps = {}): Magazine => {\n const postState = StateMap.make();\n const magazine = Obj.make(Magazine, {\n name: props.name,\n feeds: props.feeds ?? [],\n posts: props.posts ?? [],\n keep: props.keep,\n postState: Ref.make(postState),\n });\n\n const instructions = Instructions.make({\n name: props.name ? `${props.name} curation` : 'Magazine curation',\n text: composeInstructions(props.instructions),\n skills: [Ref.fromURI(Skill.registryURI(SKILL_KEY))],\n // Bind the magazine as session context so the agent sees it, not only the candidate JSON input.\n objects: [Ref.make(magazine)],\n // Declare the selection contract so the agent's completeJob carries the chosen Posts.\n output: CurationOutput,\n });\n Obj.update(magazine, (magazine) => {\n magazine.instructions = Ref.make(instructions);\n });\n\n // Cascade-delete the Instructions object (and its Text) and the per-Post state with the magazine.\n Obj.setParent(instructions, magazine);\n if (instructions.text.target) {\n Obj.setParent(instructions.text.target, instructions);\n }\n Obj.setParent(postState, magazine);\n return magazine;\n};\n\n/** Composes Routine instructions from the default editorial methodology and an optional topic focus. */\nexport const composeInstructions = (topic?: string): string => {\n const trimmed = topic?.trim();\n return trimmed ? `${DEFAULT_INSTRUCTIONS}\\n\\n## Topic\\n\\n${trimmed}` : DEFAULT_INSTRUCTIONS;\n};\n\n/** Schema for the create-magazine dialog form. */\nexport const CreateMagazineSchema = Schema.Struct({\n name: Schema.optional(\n Schema.String.annotations({\n title: 'Name',\n }),\n ),\n feeds: Schema.Array(Ref.Ref(Subscription.Subscription)).annotations({\n title: 'Feeds',\n }),\n instructions: Schema.optional(\n Schema.String.annotations({\n title: 'Topic',\n description: 'Describe what content to curate.',\n }),\n ),\n});\n\n//\n// Per-Post magazine-scoped curation state, keyed by Post id.\n//\n\n/** Agent-assigned relevance of a Post within a Magazine, or undefined. */\nexport const getRank = (magazine: Magazine | undefined, postId: EntityId): number | undefined => {\n const stateMap = magazine?.postState.target;\n return stateMap ? StateMap.bind<PostState>(stateMap).get(postId).rank : undefined;\n};\n\n/** Returns the full magazine-scoped {@link PostState} for a Post, or an empty record. */\nexport const getPostState = (magazine: Magazine | undefined, postId: EntityId): Partial<PostState> => {\n const stateMap = magazine?.postState.target;\n return stateMap ? StateMap.bind<PostState>(stateMap).get(postId) : {};\n};\n\n/** Merges partial state into a Post's magazine-scoped {@link PostState}. */\nexport const patchPostState = (magazine: Magazine, postId: EntityId, state: Partial<PostState>): void => {\n const stateMap = magazine.postState.target;\n if (stateMap) {\n StateMap.bind<PostState>(stateMap).patch(postId, state);\n }\n};\n\n/** Sets the magazine-scoped rank for a Post. */\nexport const setRank = (magazine: Magazine, postId: EntityId, rank: number): void =>\n patchPostState(magazine, postId, { rank });\n","//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Schema from 'effect/Schema';\n\nimport { Operation } from '@dxos/compute';\nimport { Database, DXN, Ref, Registry } from '@dxos/echo';\n\nimport { meta } from '#meta';\n\nimport * as Magazine from './Magazine';\nimport * as Subscription from './Subscription';\n\nconst makeKey = (name: string) => DXN.make(`${meta.profile.key}.operation.${name}`);\n\n/** Fetches an RSS/Atom feed and appends new posts to the backing ECHO feed. */\nexport const SyncFeed = Operation.make({\n meta: {\n key: makeKey('syncFeed'),\n name: 'Sync Feed',\n description: 'Fetches RSS/Atom feed and writes posts to the ECHO feed.',\n icon: 'ph--arrows-clockwise--regular',\n },\n services: [Database.Service],\n input: Schema.Struct({\n feed: Ref.Ref(Subscription.Subscription),\n }),\n output: Schema.Void,\n}).pipe(Operation.visible);\n\n/**\n * Fetches a Post's article page over HTTP and returns extracted plain text plus\n * any image URLs found. Read-only; does not mutate the Post. Agent-facing tool\n * (the surviving curation tool: the agent may call it to read a candidate's full\n * text when title + description are insufficient to judge relevance).\n */\nexport const FetchArticleContent = Operation.make({\n meta: {\n key: makeKey('fetchArticleContent'),\n name: 'Fetch Article Content',\n description: \"Fetches and extracts text + image URLs from a Post's article page.\",\n icon: 'ph--article--regular',\n },\n input: Schema.Struct({\n post: Ref.Ref(Subscription.Post).annotations({\n description: 'The Post to fetch content for. The Post must have a link.',\n }),\n }),\n output: Schema.Struct({\n text: Schema.String.annotations({ description: 'Extracted plain text from the article page.' }),\n imageUrls: Schema.Array(Schema.String).annotations({\n description: 'Image URLs found on the page (og:image first, then <img> src values).',\n }),\n }),\n services: [Database.Service],\n});\n\n/**\n * Fetches a Post's article page over HTTP and writes the extracted plain text\n * and hero image URL to the source Subscription's content feed. Per-Post read/star/archive\n * state on the Subscription is keyed by Post entity id.\n * (the Post itself is an immutable queue item). Idempotent — skips if\n * `content` is already set or the Post has no link. Used by the reader view\n * to populate the full article on first open.\n */\nexport const LoadPostContent = Operation.make({\n meta: {\n key: makeKey('loadPostContent'),\n name: 'Load Post Content',\n description: 'Fetches and stores the full article content on a Post.',\n icon: 'ph--download--regular',\n },\n input: Schema.Struct({\n post: Ref.Ref(Subscription.Post).annotations({\n description: 'The Post to load content for.',\n }),\n force: Schema.optional(\n Schema.Boolean.annotations({\n description: 'Re-fetch and append fresh content even if an entry already exists (refresh).',\n }),\n ),\n }),\n output: Schema.Void,\n});\n\n/**\n * End-to-end magazine curation: syncs all referenced feeds (in parallel), then runs an in-memory\n * routine (the magazine's topic instructions + the registry-resolved Magazine skill) through the\n * agent to select matching posts, adds the selected posts mechanically, and finally enforces each\n * feed's `keep` bound. Drives the MagazineArticle Curate button; schedulable via triggers.\n */\nexport const CurateMagazine = Operation.make({\n meta: {\n key: makeKey('curateMagazine'),\n name: 'Curate Magazine',\n description: 'Syncs feeds, selects matching posts via the magazine skill, and applies per-feed keep limits.',\n icon: 'ph--sparkle--regular',\n },\n input: Schema.Struct({\n magazine: Ref.Ref(Magazine.Magazine).annotations({\n description: 'The Magazine to curate.',\n }),\n }),\n output: Schema.Struct({\n synced: Schema.Number.annotations({ description: 'Number of feeds successfully synced.' }),\n curated: Schema.Number.annotations({ description: 'Number of posts added to the magazine.' }),\n }),\n // Database.Service for candidate collection, Registry.Service to resolve the methodology skill;\n // sub-operations (SyncFeed, RunInstructions) resolve through the ambient Operation.Service invoker.\n services: [Database.Service, Registry.Service],\n}).pipe(Operation.visible);\n\n/**\n * Clears a Magazine's curated posts, preserving any that are starred. Drives the MagazineArticle\n * Clear button.\n */\nexport const ClearMagazine = Operation.make({\n meta: {\n key: makeKey('clearMagazine'),\n name: 'Clear Magazine',\n description: \"Removes a Magazine's posts, keeping any that are starred.\",\n icon: 'ph--trash--regular',\n },\n input: Schema.Struct({\n magazine: Ref.Ref(Magazine.Magazine).annotations({\n description: 'The Magazine to clear.',\n }),\n }),\n output: Schema.Void,\n services: [Database.Service],\n}).pipe(Operation.visible);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { t as formatDate } from "./chunk-date.mjs";
|
|
2
|
+
import { t as getImageUrl } from "./chunk-post-content.mjs";
|
|
3
|
+
import { Type } from "@dxos/echo";
|
|
4
|
+
import { Subscription } from "#types";
|
|
5
|
+
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
6
|
+
import { Card, Icon, ScrollArea, composable, composableProps, useTranslation } from "@dxos/react-ui";
|
|
7
|
+
import { MarkdownView } from "@dxos/react-ui-markdown";
|
|
8
|
+
import { usePostContentAtom } from "#atoms";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { Focus as Focus$1, Mosaic, useMosaicContainer } from "@dxos/react-ui-mosaic";
|
|
11
|
+
//#region src/components/PostContent/dedupe-images.ts
|
|
12
|
+
/**
|
|
13
|
+
* Strips images from a markdown string whose URL is already on the
|
|
14
|
+
* `excluded` list, AND drops any image that appears more than once. The
|
|
15
|
+
* hero `post.imageUrl` is added to `excluded` by the caller so the
|
|
16
|
+
* extracted article body doesn't re-render the same image immediately
|
|
17
|
+
* below the hero. Captures both Markdown image syntax (``) and inline HTML `<img>` tags that defuddle sometimes
|
|
19
|
+
* leaves behind.
|
|
20
|
+
*/
|
|
21
|
+
var dedupeImagesInMarkdown = (markdown, excluded) => {
|
|
22
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const url of excluded) if (url) seen.add(url);
|
|
24
|
+
const markdownImg = /!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+"[^"]*")?\s*\)/g;
|
|
25
|
+
const htmlImg = /<img\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi;
|
|
26
|
+
const dedupe = (input, regex) => input.replace(regex, (match, url) => {
|
|
27
|
+
if (typeof url !== "string") return match;
|
|
28
|
+
if (seen.has(url)) return "";
|
|
29
|
+
seen.add(url);
|
|
30
|
+
return match;
|
|
31
|
+
});
|
|
32
|
+
return dedupe(dedupe(markdown, markdownImg), htmlImg);
|
|
33
|
+
};
|
|
34
|
+
/** Returns true if the markdown contains at least one Markdown or inline HTML image. */
|
|
35
|
+
var contentHasImage = (markdown) => /!\[[^\]]*\]\(\s*[^)\s]+/.test(markdown) || /<img\b[^>]*\bsrc=["'][^"']+["']/i.test(markdown);
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/components/PostContent/PostContent.tsx
|
|
38
|
+
/**
|
|
39
|
+
* Shared presentational layout for an article-style post.
|
|
40
|
+
* Render order: title → hero image → Markdown body (subscription contentFeed
|
|
41
|
+
* entry for this Post id, falling back to `post.description`) → meta line
|
|
42
|
+
* (author · …extra · published).
|
|
43
|
+
*/
|
|
44
|
+
var PostContent = composable(({ post, metadata = [], ...props }, forwardedRef) => {
|
|
45
|
+
const meta = [
|
|
46
|
+
post.author,
|
|
47
|
+
...metadata,
|
|
48
|
+
formatDate(post.published)
|
|
49
|
+
].filter(Boolean).join(" · ");
|
|
50
|
+
const title = post.title;
|
|
51
|
+
const postContent = usePostContentAtom(post);
|
|
52
|
+
const imageUrl = getImageUrl(post, postContent);
|
|
53
|
+
const fetchedText = postContent?.text;
|
|
54
|
+
const content = useMemo(() => {
|
|
55
|
+
const source = fetchedText || post.description || "";
|
|
56
|
+
if (!source) return "";
|
|
57
|
+
return dedupeImagesInMarkdown(source, [imageUrl]);
|
|
58
|
+
}, [
|
|
59
|
+
fetchedText,
|
|
60
|
+
post.description,
|
|
61
|
+
imageUrl
|
|
62
|
+
]);
|
|
63
|
+
const showHero = useMemo(() => Boolean(imageUrl?.startsWith("http")) && !contentHasImage(content), [imageUrl, content]);
|
|
64
|
+
return /* @__PURE__ */ jsx(ScrollArea.Root, {
|
|
65
|
+
...props,
|
|
66
|
+
orientation: "vertical",
|
|
67
|
+
thin: true,
|
|
68
|
+
ref: forwardedRef,
|
|
69
|
+
children: /* @__PURE__ */ jsxs(ScrollArea.Viewport, {
|
|
70
|
+
classNames: "flex flex-col gap-3 p-4",
|
|
71
|
+
children: [
|
|
72
|
+
title && /* @__PURE__ */ jsx("h1", {
|
|
73
|
+
className: "text-xl font-semibold",
|
|
74
|
+
children: title
|
|
75
|
+
}),
|
|
76
|
+
showHero && /* @__PURE__ */ jsx("img", {
|
|
77
|
+
src: imageUrl,
|
|
78
|
+
alt: "",
|
|
79
|
+
className: "rounded w-full object-cover max-h-72",
|
|
80
|
+
loading: "lazy"
|
|
81
|
+
}),
|
|
82
|
+
content && /* @__PURE__ */ jsx(MarkdownView, { content }),
|
|
83
|
+
meta && /* @__PURE__ */ jsx("div", {
|
|
84
|
+
className: "text-xs text-subdued",
|
|
85
|
+
children: meta
|
|
86
|
+
})
|
|
87
|
+
]
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
PostContent.displayName = "PostContent";
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/components/PostStack/PostStack.tsx
|
|
94
|
+
var PostStack = composable(({ posts = [], currentId, onAction, ...props }, forwardedRef) => {
|
|
95
|
+
const [viewport, setViewport] = useState(null);
|
|
96
|
+
const items = useMemo(() => posts.map((post) => ({
|
|
97
|
+
post,
|
|
98
|
+
onAction
|
|
99
|
+
})), [posts, onAction]);
|
|
100
|
+
const handleCurrentChange = useCallback((id) => {
|
|
101
|
+
if (id) onAction?.({
|
|
102
|
+
type: "current",
|
|
103
|
+
postId: id
|
|
104
|
+
});
|
|
105
|
+
}, [onAction]);
|
|
106
|
+
const handleKeyDown = useCallback((event) => {
|
|
107
|
+
if (event.key === "Enter") {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
document.activeElement?.click();
|
|
110
|
+
}
|
|
111
|
+
}, []);
|
|
112
|
+
return /* @__PURE__ */ jsx(Focus$1.Group, {
|
|
113
|
+
asChild: true,
|
|
114
|
+
...composableProps(props),
|
|
115
|
+
onKeyDown: handleKeyDown,
|
|
116
|
+
ref: forwardedRef,
|
|
117
|
+
children: /* @__PURE__ */ jsx(Mosaic.Container, {
|
|
118
|
+
asChild: true,
|
|
119
|
+
withFocus: true,
|
|
120
|
+
autoScroll: viewport,
|
|
121
|
+
currentId,
|
|
122
|
+
onCurrentChange: handleCurrentChange,
|
|
123
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Root, {
|
|
124
|
+
orientation: "vertical",
|
|
125
|
+
padding: true,
|
|
126
|
+
centered: true,
|
|
127
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Viewport, {
|
|
128
|
+
ref: setViewport,
|
|
129
|
+
children: /* @__PURE__ */ jsx(Mosaic.VirtualStack, {
|
|
130
|
+
Tile: PostTile,
|
|
131
|
+
classNames: "my-2",
|
|
132
|
+
gap: 8,
|
|
133
|
+
items,
|
|
134
|
+
draggable: false,
|
|
135
|
+
getId: (item) => item.post.id,
|
|
136
|
+
getScrollElement: () => viewport,
|
|
137
|
+
estimateSize: () => 120
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
PostStack.displayName = "PostStack";
|
|
145
|
+
var PostTile = forwardRef(({ data, location, current }, forwardedRef) => {
|
|
146
|
+
const post = data?.post;
|
|
147
|
+
const { setCurrentId } = useMosaicContainer("PostTile");
|
|
148
|
+
const { t } = useTranslation(Type.getTypename(Subscription.Post));
|
|
149
|
+
const handleCurrentChange = useCallback(() => {
|
|
150
|
+
if (post) setCurrentId(post.id);
|
|
151
|
+
}, [post, setCurrentId]);
|
|
152
|
+
if (!post) return null;
|
|
153
|
+
const published = post.published ? new Date(post.published).toLocaleDateString() : void 0;
|
|
154
|
+
return /* @__PURE__ */ jsx(Mosaic.Tile, {
|
|
155
|
+
asChild: true,
|
|
156
|
+
classNames: "dx-hover dx-current",
|
|
157
|
+
id: post.id,
|
|
158
|
+
data,
|
|
159
|
+
location,
|
|
160
|
+
children: /* @__PURE__ */ jsx(Focus$1.Item, {
|
|
161
|
+
asChild: true,
|
|
162
|
+
current,
|
|
163
|
+
onCurrentChange: handleCurrentChange,
|
|
164
|
+
children: /* @__PURE__ */ jsxs(Card.Root, {
|
|
165
|
+
ref: forwardedRef,
|
|
166
|
+
fullWidth: true,
|
|
167
|
+
children: [/* @__PURE__ */ jsxs(Card.Header, { children: [
|
|
168
|
+
/* @__PURE__ */ jsx(Card.Block, { children: /* @__PURE__ */ jsx(Icon, { icon: "ph--dot-outline--regular" }) }),
|
|
169
|
+
/* @__PURE__ */ jsx(Card.Text, {
|
|
170
|
+
classNames: "truncate",
|
|
171
|
+
children: post.title ?? t("post-title.placeholder")
|
|
172
|
+
}),
|
|
173
|
+
post.link && /* @__PURE__ */ jsx(Card.Block, {
|
|
174
|
+
end: true,
|
|
175
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
176
|
+
href: post.link,
|
|
177
|
+
target: "_blank",
|
|
178
|
+
rel: "noreferrer",
|
|
179
|
+
className: "shrink-0",
|
|
180
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
181
|
+
icon: "ph--arrow-square-out--regular",
|
|
182
|
+
size: 4
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
] }), /* @__PURE__ */ jsxs(Card.Body, { children: [
|
|
187
|
+
post.author && /* @__PURE__ */ jsxs(Card.Row, { children: [/* @__PURE__ */ jsx(Card.Block, { children: /* @__PURE__ */ jsx(Icon, { icon: "ph--user--regular" }) }), /* @__PURE__ */ jsx(Card.Text, {
|
|
188
|
+
variant: "description",
|
|
189
|
+
children: post.author
|
|
190
|
+
})] }),
|
|
191
|
+
(post.description || post.content) && /* @__PURE__ */ jsx(Card.Row, { children: /* @__PURE__ */ jsx(MarkdownView, {
|
|
192
|
+
content: post.description ?? post.content,
|
|
193
|
+
classNames: "line-clamp-5 text-sm text-description"
|
|
194
|
+
}) }),
|
|
195
|
+
published && /* @__PURE__ */ jsxs(Card.Row, { children: [/* @__PURE__ */ jsx(Card.Block, { children: /* @__PURE__ */ jsx(Icon, { icon: "ph--calendar--regular" }) }), /* @__PURE__ */ jsx(Card.Text, {
|
|
196
|
+
variant: "description",
|
|
197
|
+
children: published
|
|
198
|
+
})] })
|
|
199
|
+
] })]
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
PostTile.displayName = "PostTile";
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/components/SubscriptionStack/SubscriptionStack.tsx
|
|
207
|
+
var SubscriptionStack = composable(({ feeds = [], currentId, onAction, ...props }, forwardedRef) => {
|
|
208
|
+
const [viewport, setViewport] = useState(null);
|
|
209
|
+
const items = useMemo(() => feeds.map((feed) => ({
|
|
210
|
+
feed,
|
|
211
|
+
onAction
|
|
212
|
+
})), [feeds, onAction]);
|
|
213
|
+
const handleCurrentChange = useCallback((id) => {
|
|
214
|
+
if (id) onAction?.({
|
|
215
|
+
type: "current",
|
|
216
|
+
feedId: id
|
|
217
|
+
});
|
|
218
|
+
}, [onAction]);
|
|
219
|
+
const handleKeyDown = useCallback((event) => {
|
|
220
|
+
if (event.key === "Enter") {
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
document.activeElement?.click();
|
|
223
|
+
}
|
|
224
|
+
}, []);
|
|
225
|
+
return /* @__PURE__ */ jsx(Focus$1.Group, {
|
|
226
|
+
asChild: true,
|
|
227
|
+
...composableProps(props),
|
|
228
|
+
onKeyDown: handleKeyDown,
|
|
229
|
+
ref: forwardedRef,
|
|
230
|
+
children: /* @__PURE__ */ jsx(Mosaic.Container, {
|
|
231
|
+
asChild: true,
|
|
232
|
+
withFocus: true,
|
|
233
|
+
autoScroll: viewport,
|
|
234
|
+
currentId,
|
|
235
|
+
onCurrentChange: handleCurrentChange,
|
|
236
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Root, {
|
|
237
|
+
orientation: "vertical",
|
|
238
|
+
padding: true,
|
|
239
|
+
centered: true,
|
|
240
|
+
children: /* @__PURE__ */ jsx(ScrollArea.Viewport, {
|
|
241
|
+
ref: setViewport,
|
|
242
|
+
children: /* @__PURE__ */ jsx(Mosaic.VirtualStack, {
|
|
243
|
+
Tile: SubscriptionTile,
|
|
244
|
+
classNames: "my-2",
|
|
245
|
+
gap: 8,
|
|
246
|
+
items,
|
|
247
|
+
draggable: false,
|
|
248
|
+
getId: (item) => item.feed.id,
|
|
249
|
+
getScrollElement: () => viewport,
|
|
250
|
+
estimateSize: () => 100
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
SubscriptionStack.displayName = "SubscriptionStack";
|
|
258
|
+
var icons = {
|
|
259
|
+
"standard-site": {
|
|
260
|
+
icon: "ph--article--regular",
|
|
261
|
+
className: "text-sky-500"
|
|
262
|
+
},
|
|
263
|
+
"rss": {
|
|
264
|
+
icon: "ph--rss--regular",
|
|
265
|
+
className: "text-green-500"
|
|
266
|
+
},
|
|
267
|
+
"bluesky": {
|
|
268
|
+
icon: "ph--butterfly--regular",
|
|
269
|
+
className: "text-blue-500"
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var SubscriptionTile = forwardRef(({ data, location, current }, forwardedRef) => {
|
|
273
|
+
const { feed, onAction } = data;
|
|
274
|
+
const { setCurrentId } = useMosaicContainer("SubscriptionTile");
|
|
275
|
+
const { icon, className: iconClassName } = icons[feed.type ?? "rss"] || icons.rss;
|
|
276
|
+
const handleCurrentChange = useCallback(() => {
|
|
277
|
+
setCurrentId(feed.id);
|
|
278
|
+
}, [feed.id, setCurrentId]);
|
|
279
|
+
const menuItems = useMemo(() => [{
|
|
280
|
+
label: "Sync",
|
|
281
|
+
onClick: () => onAction?.({
|
|
282
|
+
type: "sync",
|
|
283
|
+
feedId: feed.id
|
|
284
|
+
})
|
|
285
|
+
}, {
|
|
286
|
+
label: "Delete",
|
|
287
|
+
onClick: () => onAction?.({
|
|
288
|
+
type: "delete",
|
|
289
|
+
feedId: feed.id
|
|
290
|
+
})
|
|
291
|
+
}], [feed.id, onAction]);
|
|
292
|
+
return /* @__PURE__ */ jsx(Mosaic.Tile, {
|
|
293
|
+
asChild: true,
|
|
294
|
+
classNames: "dx-hover dx-current",
|
|
295
|
+
id: feed.id,
|
|
296
|
+
data,
|
|
297
|
+
location,
|
|
298
|
+
children: /* @__PURE__ */ jsx(Focus$1.Item, {
|
|
299
|
+
asChild: true,
|
|
300
|
+
current,
|
|
301
|
+
onCurrentChange: handleCurrentChange,
|
|
302
|
+
children: /* @__PURE__ */ jsxs(Card.Root, {
|
|
303
|
+
ref: forwardedRef,
|
|
304
|
+
children: [/* @__PURE__ */ jsxs(Card.Header, { children: [
|
|
305
|
+
/* @__PURE__ */ jsx(Card.Block, { children: /* @__PURE__ */ jsx(Icon, {
|
|
306
|
+
icon,
|
|
307
|
+
classNames: iconClassName
|
|
308
|
+
}) }),
|
|
309
|
+
/* @__PURE__ */ jsx(Card.Title, { children: feed.name ?? "Untitled feed" }),
|
|
310
|
+
/* @__PURE__ */ jsx(Card.Menu, { items: menuItems })
|
|
311
|
+
] }), /* @__PURE__ */ jsx(Card.Body, { children: feed.description && /* @__PURE__ */ jsx(Card.Row, { children: /* @__PURE__ */ jsx(Card.Text, {
|
|
312
|
+
variant: "description",
|
|
313
|
+
children: feed.description
|
|
314
|
+
}) }) })]
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
SubscriptionTile.displayName = "SubscriptionTile";
|
|
320
|
+
//#endregion
|
|
321
|
+
export { PostContent, PostStack, SubscriptionStack, contentHasImage, dedupeImagesInMarkdown };
|
|
322
|
+
|
|
323
|
+
//# sourceMappingURL=components.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.mjs","names":[],"sources":["../../src/components/PostContent/dedupe-images.ts","../../src/components/PostContent/PostContent.tsx","../../src/components/PostStack/PostStack.tsx","../../src/components/SubscriptionStack/SubscriptionStack.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// Kept out of `PostContent.tsx`: react-refresh only fast-refreshes a module whose\n// exports are all components, so values exported beside them force a full page reload on\n// every edit.\n\n/**\n * Strips images from a markdown string whose URL is already on the\n * `excluded` list, AND drops any image that appears more than once. The\n * hero `post.imageUrl` is added to `excluded` by the caller so the\n * extracted article body doesn't re-render the same image immediately\n * below the hero. Captures both Markdown image syntax (``) and inline HTML `<img>` tags that defuddle sometimes\n * leaves behind.\n */\nexport const dedupeImagesInMarkdown = (markdown: string, excluded: ReadonlyArray<string | undefined>): string => {\n const seen = new Set<string>();\n for (const url of excluded) {\n if (url) {\n seen.add(url);\n }\n }\n\n // Markdown image: `` or ``.\n const markdownImg = /!\\[[^\\]]*\\]\\(\\s*([^)\\s]+)(?:\\s+\"[^\"]*\")?\\s*\\)/g;\n // HTML image: `<img ... src=\"url\" ...>`.\n const htmlImg = /<img\\b[^>]*\\bsrc=[\"']([^\"']+)[\"'][^>]*>/gi;\n\n const dedupe = (input: string, regex: RegExp): string =>\n input.replace(regex, (match, url) => {\n if (typeof url !== 'string') {\n return match;\n }\n if (seen.has(url)) {\n return '';\n }\n seen.add(url);\n return match;\n });\n\n return dedupe(dedupe(markdown, markdownImg), htmlImg);\n};\n\n/** Returns true if the markdown contains at least one Markdown or inline HTML image. */\nexport const contentHasImage = (markdown: string): boolean =>\n /!\\[[^\\]]*\\]\\(\\s*[^)\\s]+/.test(markdown) || /<img\\b[^>]*\\bsrc=[\"'][^\"']+[\"']/i.test(markdown);\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { useMemo } from 'react';\n\nimport { ScrollArea } from '@dxos/react-ui';\nimport { composable } from '@dxos/react-ui';\nimport { MarkdownView } from '@dxos/react-ui-markdown';\n\nimport { usePostContentAtom } from '#atoms';\nimport { Subscription } from '#types';\n\nimport { formatDate, getImageUrl } from '../../util';\nimport { contentHasImage, dedupeImagesInMarkdown } from './dedupe-images';\n\nexport type PostContentProps = {\n /** Post to render. */\n post: Subscription.Post;\n /** Additional metadata, such as feed or source domain, rendered between author and published date. */\n metadata?: string[];\n};\n\n/**\n * Shared presentational layout for an article-style post.\n * Render order: title → hero image → Markdown body (subscription contentFeed\n * entry for this Post id, falling back to `post.description`) → meta line\n * (author · …extra · published).\n */\nexport const PostContent = composable<HTMLDivElement, PostContentProps>(\n ({ post, metadata = [], ...props }, forwardedRef) => {\n const meta = [post.author, ...metadata, formatDate(post.published)].filter(Boolean).join(' · ');\n const title = post.title;\n const postContent = usePostContentAtom(post);\n const imageUrl = getImageUrl(post, postContent);\n const fetchedText = postContent?.text;\n\n // Drop duplicate images from the article body — and remove any image\n // that matches the hero `imageUrl` so it doesn't appear stacked immediately\n // below the hero. Falls through to `post.description` when content\n // hasn't been fetched yet.\n const content = useMemo(() => {\n const source = fetchedText || post.description || '';\n if (!source) {\n return '';\n }\n return dedupeImagesInMarkdown(source, [imageUrl]);\n }, [fetchedText, post.description, imageUrl]);\n\n // Suppress the hero when the article body already carries imagery — RSS feeds often\n // duplicate the lead image inside `<content>` under a different URL than `imageUrl`,\n // and rendering both stacks the same picture twice (see e.g. Guardian galleries).\n const showHero = useMemo(\n () => Boolean(imageUrl?.startsWith('http')) && !contentHasImage(content),\n [imageUrl, content],\n );\n\n return (\n <ScrollArea.Root {...props} orientation='vertical' thin ref={forwardedRef}>\n <ScrollArea.Viewport classNames='flex flex-col gap-3 p-4'>\n {title && <h1 className='text-xl font-semibold'>{title}</h1>}\n {showHero && <img src={imageUrl} alt='' className='rounded w-full object-cover max-h-72' loading='lazy' />}\n {content && <MarkdownView content={content} />}\n {meta && <div className='text-xs text-subdued'>{meta}</div>}\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n );\n },\n);\n\nPostContent.displayName = 'PostContent';\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type KeyboardEvent, forwardRef, useCallback, useMemo, useState } from 'react';\n\nimport { Type } from '@dxos/echo';\nimport { Card, Icon, ScrollArea, useTranslation } from '@dxos/react-ui';\nimport { composable, composableProps } from '@dxos/react-ui';\nimport { MarkdownView } from '@dxos/react-ui-markdown';\nimport { Focus, Mosaic, type MosaicTileProps, useMosaicContainer } from '@dxos/react-ui-mosaic';\n\nimport { Subscription } from '#types';\n\nexport type PostStackAction = { type: 'current'; postId: string };\n\nexport type PostStackActionHandler = (action: PostStackAction) => void;\n\nexport type PostStackProps = {\n id: string;\n posts?: Subscription.Post[];\n currentId?: string;\n onAction?: PostStackActionHandler;\n};\n\nexport const PostStack = composable<HTMLDivElement, PostStackProps>(\n ({ posts = [], currentId, onAction, ...props }, forwardedRef) => {\n const [viewport, setViewport] = useState<HTMLElement | null>(null);\n const items = useMemo(() => posts.map((post) => ({ post, onAction })), [posts, onAction]);\n\n const handleCurrentChange = useCallback(\n (id: string | undefined) => {\n if (id) {\n onAction?.({ type: 'current', postId: id });\n }\n },\n [onAction],\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n (document.activeElement as HTMLElement | null)?.click();\n }\n }, []);\n\n return (\n <Focus.Group asChild {...composableProps(props)} onKeyDown={handleKeyDown} ref={forwardedRef}>\n <Mosaic.Container\n asChild\n withFocus\n autoScroll={viewport}\n currentId={currentId}\n onCurrentChange={handleCurrentChange}\n >\n <ScrollArea.Root orientation='vertical' padding centered>\n <ScrollArea.Viewport ref={setViewport}>\n <Mosaic.VirtualStack\n Tile={PostTile}\n classNames='my-2'\n gap={8}\n items={items}\n draggable={false}\n getId={(item) => item.post.id}\n getScrollElement={() => viewport}\n estimateSize={() => 120}\n />\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n </Mosaic.Container>\n </Focus.Group>\n );\n },\n);\n\nPostStack.displayName = 'PostStack';\n\ntype PostTileData = {\n post: Subscription.Post;\n onAction?: PostStackActionHandler;\n};\n\ntype PostTileProps = Pick<MosaicTileProps<PostTileData>, 'data' | 'location' | 'current'>;\n\nconst PostTile = forwardRef<HTMLDivElement, PostTileProps>(({ data, location, current }, forwardedRef) => {\n const post = data?.post;\n const { setCurrentId } = useMosaicContainer('PostTile');\n const { t } = useTranslation(Type.getTypename(Subscription.Post));\n\n const handleCurrentChange = useCallback(() => {\n if (post) {\n setCurrentId(post.id);\n }\n }, [post, setCurrentId]);\n\n if (!post) {\n return null;\n }\n\n const published = post.published ? new Date(post.published).toLocaleDateString() : undefined;\n\n return (\n <Mosaic.Tile asChild classNames='dx-hover dx-current' id={post.id} data={data} location={location}>\n <Focus.Item asChild current={current} onCurrentChange={handleCurrentChange}>\n <Card.Root ref={forwardedRef} fullWidth>\n <Card.Header>\n <Card.Block>\n <Icon icon='ph--dot-outline--regular' />\n </Card.Block>\n <Card.Text classNames='truncate'>{post.title ?? t('post-title.placeholder')}</Card.Text>\n {post.link && (\n <Card.Block end>\n <a href={post.link} target='_blank' rel='noreferrer' className='shrink-0'>\n <Icon icon='ph--arrow-square-out--regular' size={4} />\n </a>\n </Card.Block>\n )}\n </Card.Header>\n <Card.Body>\n {post.author && (\n <Card.Row>\n <Card.Block>\n <Icon icon='ph--user--regular' />\n </Card.Block>\n <Card.Text variant='description'>{post.author}</Card.Text>\n </Card.Row>\n )}\n {(post.description || post.content) && (\n <Card.Row>\n <MarkdownView\n content={post.description ?? post.content}\n classNames='line-clamp-5 text-sm text-description'\n />\n </Card.Row>\n )}\n {published && (\n <Card.Row>\n <Card.Block>\n <Icon icon='ph--calendar--regular' />\n </Card.Block>\n <Card.Text variant='description'>{published}</Card.Text>\n </Card.Row>\n )}\n </Card.Body>\n </Card.Root>\n </Focus.Item>\n </Mosaic.Tile>\n );\n});\n\nPostTile.displayName = 'PostTile';\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type KeyboardEvent, forwardRef, useCallback, useMemo, useState } from 'react';\n\nimport { Card, Icon, ScrollArea } from '@dxos/react-ui';\nimport { composable, composableProps } from '@dxos/react-ui';\nimport { Focus, Mosaic, type MosaicTileProps, useMosaicContainer } from '@dxos/react-ui-mosaic';\n\nimport { type Subscription } from '#types';\n\nexport type SubscriptionStackAction =\n | { type: 'current'; feedId: string }\n | { type: 'sync'; feedId: string }\n | { type: 'delete'; feedId: string };\n\nexport type SubscriptionStackActionHandler = (action: SubscriptionStackAction) => void;\n\n//\n// SubscriptionStack\n//\n\nexport type SubscriptionStackProps = {\n id?: string;\n feeds?: Subscription.Subscription[];\n currentId?: string;\n onAction?: SubscriptionStackActionHandler;\n};\n\nexport const SubscriptionStack = composable<HTMLDivElement, SubscriptionStackProps>(\n ({ feeds = [], currentId, onAction, ...props }, forwardedRef) => {\n const [viewport, setViewport] = useState<HTMLElement | null>(null);\n\n // TODO(burdon): Either sort or make draggable.\n const items = useMemo(() => feeds.map((feed) => ({ feed, onAction })), [feeds, onAction]);\n\n const handleCurrentChange = useCallback(\n (id: string | undefined) => {\n if (id) {\n onAction?.({ type: 'current', feedId: id });\n }\n },\n [onAction],\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n (document.activeElement as HTMLElement | null)?.click();\n }\n }, []);\n\n return (\n <Focus.Group asChild {...composableProps(props)} onKeyDown={handleKeyDown} ref={forwardedRef}>\n <Mosaic.Container\n asChild\n withFocus\n autoScroll={viewport}\n currentId={currentId}\n onCurrentChange={handleCurrentChange}\n >\n <ScrollArea.Root orientation='vertical' padding centered>\n <ScrollArea.Viewport ref={setViewport}>\n <Mosaic.VirtualStack\n Tile={SubscriptionTile}\n classNames='my-2'\n gap={8}\n items={items}\n draggable={false}\n getId={(item) => item.feed.id}\n getScrollElement={() => viewport}\n estimateSize={() => 100}\n />\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n </Mosaic.Container>\n </Focus.Group>\n );\n },\n);\n\nSubscriptionStack.displayName = 'SubscriptionStack';\n\n//\n// SubscriptionTile\n//\n\nconst icons: Record<Subscription.FeedType, { icon: string; className?: string }> = {\n 'standard-site': {\n icon: 'ph--article--regular',\n className: 'text-sky-500',\n },\n 'rss': {\n icon: 'ph--rss--regular',\n className: 'text-green-500',\n },\n 'bluesky': {\n icon: 'ph--butterfly--regular',\n className: 'text-blue-500',\n },\n};\n\ntype SubscriptionTileData = {\n feed: Subscription.Subscription;\n onAction?: SubscriptionStackActionHandler;\n};\n\ntype SubscriptionTileProps = Pick<MosaicTileProps<SubscriptionTileData>, 'data' | 'location' | 'current'>;\n\nconst SubscriptionTile = forwardRef<HTMLDivElement, SubscriptionTileProps>(\n ({ data, location, current }, forwardedRef) => {\n const { feed, onAction } = data;\n const { setCurrentId } = useMosaicContainer('SubscriptionTile');\n const { icon, className: iconClassName } = icons[feed.type ?? 'rss'] || icons.rss;\n\n const handleCurrentChange = useCallback(() => {\n setCurrentId(feed.id);\n }, [feed.id, setCurrentId]);\n\n const menuItems = useMemo(\n () => [\n { label: 'Sync', onClick: () => onAction?.({ type: 'sync', feedId: feed.id }) },\n { label: 'Delete', onClick: () => onAction?.({ type: 'delete', feedId: feed.id }) },\n ],\n [feed.id, onAction],\n );\n\n return (\n <Mosaic.Tile asChild classNames='dx-hover dx-current' id={feed.id} data={data} location={location}>\n <Focus.Item asChild current={current} onCurrentChange={handleCurrentChange}>\n <Card.Root ref={forwardedRef}>\n <Card.Header>\n <Card.Block>\n <Icon icon={icon} classNames={iconClassName} />\n </Card.Block>\n <Card.Title>{feed.name ?? 'Untitled feed'}</Card.Title>\n <Card.Menu items={menuItems} />\n </Card.Header>\n <Card.Body>\n {/* {feed.url && (\n <Card.Row>\n <Card.Text classNames='truncate' variant='description'>\n {feed.url}\n </Card.Text>\n </Card.Row>\n )} */}\n {feed.description && (\n <Card.Row>\n <Card.Text variant='description'>{feed.description}</Card.Text>\n </Card.Row>\n )}\n </Card.Body>\n </Card.Root>\n </Focus.Item>\n </Mosaic.Tile>\n );\n },\n);\n\nSubscriptionTile.displayName = 'SubscriptionTile';\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { lazy } from "react";
|
|
2
|
+
//#region src/containers/index.ts
|
|
3
|
+
var FeedArticle = lazy(() => import("./chunk-FeedArticle.mjs"));
|
|
4
|
+
var FeedProperties = lazy(() => import("./chunk-FeedProperties.mjs"));
|
|
5
|
+
var MagazineArticle = lazy(() => import("./chunk-MagazineArticle.mjs"));
|
|
6
|
+
var PostArticle = lazy(() => import("./chunk-PostArticle.mjs"));
|
|
7
|
+
var PostCard = lazy(() => import("./chunk-PostCard.mjs"));
|
|
8
|
+
//#endregion
|
|
9
|
+
export { FeedArticle, FeedProperties, MagazineArticle, PostArticle, PostCard };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=containers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containers.mjs","names":[],"sources":["../../src/containers/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type ComponentType, lazy } from 'react';\n\nexport const FeedArticle: ComponentType<any> = lazy(() => import('./FeedArticle'));\nexport const FeedProperties: ComponentType<any> = lazy(() => import('./FeedProperties'));\nexport const MagazineArticle: ComponentType<any> = lazy(() => import('./MagazineArticle'));\nexport const PostArticle: ComponentType<any> = lazy(() => import('./PostArticle'));\nexport const PostCard: ComponentType<any> = lazy(() => import('./PostCard'));\n"],"mappings":";;AAMA,IAAa,cAAkC,WAAW,OAAO,0BAAgB;AACjF,IAAa,iBAAqC,WAAW,OAAO,6BAAmB;AACvF,IAAa,kBAAsC,WAAW,OAAO,8BAAoB;AACzF,IAAa,cAAkC,WAAW,OAAO,0BAAgB;AACjF,IAAa,WAA+B,WAAW,OAAO,uBAAa"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { c as Magazine_exports, p as Subscription_exports, r as FeedOperation_exports, u as CreateSubscription_exports } from "./chunk-types.mjs";
|
|
2
|
+
import { t as meta } from "./chunk-meta.mjs";
|
|
3
|
+
export { CreateSubscription_exports as CreateSubscription, FeedOperation_exports as FeedOperation, Magazine_exports as Magazine, Subscription_exports as Subscription, meta };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { OperationHandlerSet } from "@dxos/compute";
|
|
2
|
+
//#region src/operations/index.ts
|
|
3
|
+
var MagazineOperationHandlerSet = OperationHandlerSet.lazy(() => import("./chunk-clear-magazine.mjs"), () => import("./chunk-curate-magazine.mjs"), () => import("./chunk-fetch-article-content.mjs"), () => import("./chunk-load-post-content.mjs"), () => import("./chunk-sync-feed.mjs"));
|
|
4
|
+
//#endregion
|
|
5
|
+
export { MagazineOperationHandlerSet };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.mjs","names":[],"sources":["../../src/operations/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { OperationHandlerSet } from '@dxos/compute';\n\nexport const MagazineOperationHandlerSet = OperationHandlerSet.lazy(\n () => import('./clear-magazine'),\n () => import('./curate-magazine'),\n () => import('./fetch-article-content'),\n () => import('./load-post-content'),\n () => import('./sync-feed'),\n);\n"],"mappings":";;AAMA,IAAa,8BAA8B,oBAAoB,WACvD,OAAO,qCACP,OAAO,sCACP,OAAO,4CACP,OAAO,wCACP,OAAO,wBACf"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { t as meta } from "./chunk-meta.mjs";
|
|
2
|
+
import { MagazineOperationHandlerSet } from "./operations.mjs";
|
|
3
|
+
import { Plugin } from "@dxos/app-framework";
|
|
4
|
+
//#region src/plugin.ts
|
|
5
|
+
var MagazinePlugin = Plugin.lazy(meta, () => import("#plugin"));
|
|
6
|
+
//#endregion
|
|
7
|
+
export { MagazineOperationHandlerSet, MagazinePlugin };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=plugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/plugin.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport { meta } from './meta';\n\nexport const MagazinePlugin = Plugin.lazy(meta, () => import('#plugin'));\n\nexport { MagazineOperationHandlerSet } from './operations';\n"],"mappings":";;;;AAQA,IAAa,iBAAiB,OAAO,KAAK,YAAY,OAAO,UAAU"}
|
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
import "../chunk-J5LGTIGS.mjs";
|
|
2
|
-
|
|
3
|
-
// src/skills/magazine-skill.ts
|
|
4
|
-
import { Skill, Template } from "@dxos/compute";
|
|
5
1
|
import { trim } from "@dxos/util";
|
|
6
|
-
import {
|
|
7
|
-
import { Magazine } from "#types";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
];
|
|
2
|
+
import { Skill, Template } from "@dxos/compute";
|
|
3
|
+
import { FeedOperation, Magazine } from "#types";
|
|
4
|
+
//#region src/skills/magazine-skill.ts
|
|
5
|
+
var operations = [FeedOperation.FetchArticleContent];
|
|
11
6
|
var make = () => Skill.make({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}),
|
|
17
|
-
instructions: Template.make({
|
|
18
|
-
// HOW only: input shape, tool usage, and the completeJob output contract. The editorial
|
|
19
|
-
// methodology (what to select, dedup, snippet/image guidance) and the magazine's topic live on
|
|
20
|
-
// the Routine's instructions (see Magazine.DEFAULT_INSTRUCTIONS), not here.
|
|
21
|
-
source: trim`
|
|
7
|
+
key: Magazine.SKILL_KEY,
|
|
8
|
+
name: "Magazine Curator",
|
|
9
|
+
tools: Skill.toolDefinitions({ operations }),
|
|
10
|
+
instructions: Template.make({ source: trim`
|
|
22
11
|
The candidate Posts are provided in the <input> as a JSON array under "candidates"; each
|
|
23
12
|
candidate has an "id" plus title, description, author, feedName, published, and link. The
|
|
24
13
|
editorial brief (the Topic and selection guidance) is provided in your instructions.
|
|
@@ -32,15 +21,13 @@ var make = () => Skill.make({
|
|
|
32
21
|
listing the selected candidates in the order you want them shown. Only use ids that appear
|
|
33
22
|
in the input — never invent ids. Omit "imageUrl" when no image is available. Do not attempt to
|
|
34
23
|
add Posts yourself; the Magazine is updated mechanically from your output.
|
|
35
|
-
`
|
|
36
|
-
})
|
|
24
|
+
` })
|
|
37
25
|
});
|
|
38
26
|
var skill = {
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
key: Magazine.SKILL_KEY,
|
|
28
|
+
make
|
|
41
29
|
};
|
|
42
|
-
|
|
43
|
-
export {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//# sourceMappingURL=index.mjs.map
|
|
30
|
+
//#endregion
|
|
31
|
+
export { skill as MagazineSkill };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=skills.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.mjs","names":[],"sources":["../../src/skills/magazine-skill.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Skill, Template } from '@dxos/compute';\nimport { trim } from '@dxos/util';\n\nimport { FeedOperation } from '#types';\nimport { Magazine } from '#types';\n\nconst operations = [FeedOperation.FetchArticleContent];\n\nconst make = () =>\n Skill.make({\n key: Magazine.SKILL_KEY,\n name: 'Magazine Curator',\n tools: Skill.toolDefinitions({ operations }),\n instructions: Template.make({\n // HOW only: input shape, tool usage, and the completeJob output contract. The editorial\n // methodology (what to select, dedup, snippet/image guidance) and the magazine's topic live on\n // the Routine's instructions (see Magazine.DEFAULT_INSTRUCTIONS), not here.\n source: trim`\n The candidate Posts are provided in the <input> as a JSON array under \"candidates\"; each\n candidate has an \"id\" plus title, description, author, feedName, published, and link. The\n editorial brief (the Topic and selection guidance) is provided in your instructions.\n\n When a candidate's title and description are not enough to judge relevance, you MAY call\n fetchArticleContent with the candidate's id to read the full article text (and find an image)\n before deciding.\n\n When you are done, call completeJob with the structured output:\n { \"posts\": [{ \"id\": \"<candidate id>\", \"snippet\": \"...\", \"imageUrl\": \"...\" }, ...] }\n listing the selected candidates in the order you want them shown. Only use ids that appear\n in the input — never invent ids. Omit \"imageUrl\" when no image is available. Do not attempt to\n add Posts yourself; the Magazine is updated mechanically from your output.\n `,\n }),\n });\n\nconst skill: Skill.Definition = {\n key: Magazine.SKILL_KEY,\n make,\n};\n\nexport default skill;\n"],"mappings":";;;;AAUA,IAAM,aAAa,CAAC,cAAc,mBAAmB;AAErD,IAAM,aACJ,MAAM,KAAK;CACT,KAAK,SAAS;CACd,MAAM;CACN,OAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC;CAC3C,cAAc,SAAS,KAAK,EAI1B,QAAQ,IAAI;;;;;;;;;;;;;;QAed,CAAC;AACH,CAAC;AAEH,IAAM,QAA0B;CAC9B,KAAK,SAAS;CACd;AACF"}
|