your personal website on atproto - mirror blento.app
25
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 105 lines 4.9 kB view raw view rendered
1# External data sources for sections 2 3Allow sections to be auto-filled from external data (e.g. Grain gallery photos, standard.site blog posts) instead of manually curated cards. 4 5## Goals 6 7- Sections can pull items from external sources (ATProto collections, web APIs, etc.) without the user manually creating each card. 8- Extension path mirrors the existing card `loadData` pattern so there's one consistent idea in the codebase. 9- A section can always fall back to manual content (same UX as today). 10- Multiple sections on a page can use different sources; a single page can mix sourced and manual sections. 11 12## Proposed API 13 14### SectionDefinition additions 15 16```ts 17type SectionDefinition = { 18 // existing... 19 20 // Optional external data fetch. Result stored in WebsiteData.additionalData[section.id]. 21 loadData?: (section: SectionRecord, ctx: { did; handle; cache? }) => Promise<unknown>; 22 loadDataServer?: ( 23 section: SectionRecord, 24 ctx: { did; handle; cache?; env?; platform? } 25 ) => Promise<unknown>; 26}; 27``` 28 29Mirrors the card-level `loadData`/`loadDataServer`. Reuses the same cache service + KV infrastructure. 30 31### sectionData shape 32 33Each section that supports sourcing stores its source config in `sectionData`: 34 35```ts 36// Manual (default) 37sectionData: { source: { provider: 'manual' }, ...otherStuff } 38 39// Sourced from Grain 40sectionData: { 41 source: { provider: 'grain-gallery', galleryUri: 'at://did:.../xyz.grain.photo.gallery/abc' } 42} 43 44// Sourced from standard.site blog 45sectionData: { 46 source: { provider: 'standard-site-blog', did: 'did:...', limit: 10 } 47} 48``` 49 50### Source registration 51 52Sources are pluggable modules that can be used by one or more section types: 53 54```ts 55// src/lib/sections/sources/types.ts 56type SectionSource = { 57 id: string; // e.g. 'grain-gallery' 58 name: string; // user-facing name 59 supportedSections: string[]; // e.g. ['gallery'] 60 configComponent: Component<{ 61 config: any; 62 onchange: (next: any) => void; 63 }>; 64 loadData?: (config: any, ctx: any) => Promise<unknown>; 65 loadDataServer?: (config: any, ctx: any) => Promise<unknown>; 66}; 67``` 68 69Registered in a central `AllSectionSources` array like cards/sections are. Section types query the list filtered by `supportedSections`. 70 71Example: `src/lib/sections/sources/grain-gallery.ts` registers a source with `supportedSections: ['gallery']` and a `loadDataServer` that fetches photos via the Grain lexicon. 72 73### Rendering flow 74 75The section's `contentComponent` receives: 76 77- `items` (manual cards assigned to the section, as today) 78- `externalData` (from `additionalData[section.id]`) 79 80If `sectionData.source.provider !== 'manual'`, the section renders from `externalData`; otherwise it renders `items`. Optionally, a section could combine both (e.g. pinned manual items + auto-pulled items). 81 82External items are "synthetic": they look like `Item`s at the render layer (enough for `BaseCard` / `Card` to render them) but they have no PDS record and no `sectionId`. They reuse existing card types — a Grain photo renders as a synthetic `image` card, a blog post as a synthetic `link` card. No new card components needed. 83 84### Editing UX 85 86- Section settings popover/drawer gets a **Source** dropdown listing compatible sources (derived from `AllSectionSources` filtered to the current section type) 87- When the source is non-manual, the inline card-editing UI is replaced with the source's `configComponent` (e.g. "Paste Grain gallery URI") 88- Switching source back to "Manual" restores the manual editing UX (and the existing cards resurface) 89- The source's config is persisted in `sectionData.source` 90 91## Implementation order 92 931. Add `loadData` / `loadDataServer` to `SectionDefinition` and wire into `load.ts` (server-side fan-out like cards already do). 942. Add the sources registry (`src/lib/sections/sources/index.ts`). 953. Teach `contentComponent` / `editingContentComponent` to branch on `sectionData.source.provider`. 964. Build one real source end-to-end: **Grain gallery → Gallery section** (infrastructure already exists in `PhotoGalleryCard`). 975. Follow with: **standard.site blog → Row section** (requires a blog-post card first, or a synthetic link card with richer metadata). 98 99## Open questions 100 101- Should sources be able to return card-type-specific `cardData`, or should they always return a normalised shape? Probably card-type-specific for flexibility. 102- Pagination: do sources need to know about "load more" or infinite scroll? For now assume batch-fetch (limit in config). 103- Caching: per-section cache key by `section.id + section.updatedAt`? Or by source config hash so multiple sections with the same source share cache? Probably the latter. 104- Mixing sourced + manual: start with mutually exclusive (source OR manual), add mixing later if needed. 105- Mobile UX for the Source dropdown and config UI: same treatment as the card editing popovers.