···11+# External data sources for sections
22+33+Allow sections to be auto-filled from external data (e.g. Grain gallery photos, standard.site blog posts) instead of manually curated cards.
44+55+## Goals
66+77+- Sections can pull items from external sources (ATProto collections, web APIs, etc.) without the user manually creating each card.
88+- Extension path mirrors the existing card `loadData` pattern so there's one consistent idea in the codebase.
99+- A section can always fall back to manual content (same UX as today).
1010+- Multiple sections on a page can use different sources; a single page can mix sourced and manual sections.
1111+1212+## Proposed API
1313+1414+### SectionDefinition additions
1515+1616+```ts
1717+type SectionDefinition = {
1818+ // existing...
1919+2020+ // Optional external data fetch. Result stored in WebsiteData.additionalData[section.id].
2121+ loadData?: (section: SectionRecord, ctx: { did; handle; cache? }) => Promise<unknown>;
2222+ loadDataServer?: (
2323+ section: SectionRecord,
2424+ ctx: { did; handle; cache?; env?; platform? }
2525+ ) => Promise<unknown>;
2626+};
2727+```
2828+2929+Mirrors the card-level `loadData`/`loadDataServer`. Reuses the same cache service + KV infrastructure.
3030+3131+### sectionData shape
3232+3333+Each section that supports sourcing stores its source config in `sectionData`:
3434+3535+```ts
3636+// Manual (default)
3737+sectionData: { source: { provider: 'manual' }, ...otherStuff }
3838+3939+// Sourced from Grain
4040+sectionData: {
4141+ source: { provider: 'grain-gallery', galleryUri: 'at://did:.../xyz.grain.photo.gallery/abc' }
4242+}
4343+4444+// Sourced from standard.site blog
4545+sectionData: {
4646+ source: { provider: 'standard-site-blog', did: 'did:...', limit: 10 }
4747+}
4848+```
4949+5050+### Source registration
5151+5252+Sources are pluggable modules that can be used by one or more section types:
5353+5454+```ts
5555+// src/lib/sections/sources/types.ts
5656+type SectionSource = {
5757+ id: string; // e.g. 'grain-gallery'
5858+ name: string; // user-facing name
5959+ supportedSections: string[]; // e.g. ['gallery']
6060+ configComponent: Component<{
6161+ config: any;
6262+ onchange: (next: any) => void;
6363+ }>;
6464+ loadData?: (config: any, ctx: any) => Promise<unknown>;
6565+ loadDataServer?: (config: any, ctx: any) => Promise<unknown>;
6666+};
6767+```
6868+6969+Registered in a central `AllSectionSources` array like cards/sections are. Section types query the list filtered by `supportedSections`.
7070+7171+Example: `src/lib/sections/sources/grain-gallery.ts` registers a source with `supportedSections: ['gallery']` and a `loadDataServer` that fetches photos via the Grain lexicon.
7272+7373+### Rendering flow
7474+7575+The section's `contentComponent` receives:
7676+7777+- `items` (manual cards assigned to the section, as today)
7878+- `externalData` (from `additionalData[section.id]`)
7979+8080+If `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).
8181+8282+External 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.
8383+8484+### Editing UX
8585+8686+- Section settings popover/drawer gets a **Source** dropdown listing compatible sources (derived from `AllSectionSources` filtered to the current section type)
8787+- When the source is non-manual, the inline card-editing UI is replaced with the source's `configComponent` (e.g. "Paste Grain gallery URI")
8888+- Switching source back to "Manual" restores the manual editing UX (and the existing cards resurface)
8989+- The source's config is persisted in `sectionData.source`
9090+9191+## Implementation order
9292+9393+1. Add `loadData` / `loadDataServer` to `SectionDefinition` and wire into `load.ts` (server-side fan-out like cards already do).
9494+2. Add the sources registry (`src/lib/sections/sources/index.ts`).
9595+3. Teach `contentComponent` / `editingContentComponent` to branch on `sectionData.source.provider`.
9696+4. Build one real source end-to-end: **Grain gallery → Gallery section** (infrastructure already exists in `PhotoGalleryCard`).
9797+5. Follow with: **standard.site blog → Row section** (requires a blog-post card first, or a synthetic link card with richer metadata).
9898+9999+## Open questions
100100+101101+- Should sources be able to return card-type-specific `cardData`, or should they always return a normalised shape? Probably card-type-specific for flexibility.
102102+- Pagination: do sources need to know about "load more" or infinite scroll? For now assume batch-fetch (limit in config).
103103+- 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.
104104+- Mixing sourced + manual: start with mutually exclusive (source OR manual), add mixing later if needed.
105105+- Mobile UX for the Source dropdown and config UI: same treatment as the card editing popovers.
···80808181 canResize?: boolean;
82828383+ // allow rotation for this card type (default true — section can also disable)
8484+ allowRotate?: boolean;
8585+8386 // if true, content can render outside the card's bounds (no overflow:hidden on the inner wrapper)
8487 noOverflow?: boolean;
8588