0
2
Stories Design
Overview
Ephemeral photo posts that disappear after 24 hours. Lower friction than galleries — just pick a photo, optionally tag a location, and post. Stories encourage casual, frequent sharing alongside the more intentional gallery format.
Data Model
Record: social.grain.story
{
"type": "record",
"key": "tid",
"record": {
"required": ["media", "aspectRatio", "createdAt"],
"properties": {
"media": { "type": "blob", "accept": ["image/*", "video/*"], "maxSize": 5000000 },
"aspectRatio": { "ref": "social.grain.defs#aspectRatio" },
"location": { "ref": "social.grain.defs#location" },
"createdAt": { "type": "string", "format": "datetime" }
}
}
}
- One record = one photo (or video in the future).
- Active stories = records where
createdAt > now - 24h. No expiry field — computed at query time. - Records stay in the repo permanently. The app view only serves active ones by default.
- V1 is images only. Video support requires no schema changes.
View: social.grain.story.defs#storyView
uri, cid, creator (profileView), thumb, fullsize, aspectRatio, location?, createdAt
Server
XRPC Queries
social.grain.unspecced.getStoryAuthors
- Returns authors with active stories, ordered by most recent post.
- Each entry:
profileView+ story count. - Used by the story strip.
social.grain.unspecced.getStories
- Params:
did(required). - Returns that author's active stories in chronological order as
storyView[]. - Used by the full-screen viewer.
New Lexicons
lexicons/social/grain/story.jsonlexicons/social/grain/story/defs.jsonlexicons/social/grain/unspecced/getStoryAuthors.jsonlexicons/social/grain/unspecced/getStories.json
Cleanup
Old stories remain in the AT Protocol repo (no auto-deletion). The app view simply stops serving them after 24h. A background cleanup job is not needed for v1.
Client UI
Story Strip
- Horizontal scrollable row at the top of the home feed.
- First circle: "+" with the user's own avatar for creating a story.
- Remaining circles: authors with active stories, avatar with a colored ring.
- Tap opens the full-screen story viewer for that author.
Story Viewer (Full-Screen Overlay)
- Photo fills the screen with a dark background.
- Progress bars at top: thin segmented bar, one segment per story from that author. Current segment fills left-to-right over 5 seconds, then auto-advances.
- Author name/avatar overlaid at top below progress bars.
- Location tag overlaid if present.
- Navigation: tap right side = next, tap left side = previous. Last tap advances to next author. Swipe to skip author.
- Close button (X) top-right.
- No interactions (hearts, replies) in v1. View-only.
Story Creation
- Triggered from the "+" circle in the story strip.
- Opens a file picker (single photo for v1).
- Optional location tag (reuses
LocationInputcomponent). - Post button. No title, no description.
- Could be a modal or a minimal page.
New Components
StoryStrip.svelte— horizontal avatar row for the feed.StoryViewer.svelte— full-screen overlay with progress bars and navigation.StoryCreate.svelte— simplified create flow (modal or page).
Queries (in queries.ts)
storyAuthorsQuery()— feeds the story strip.storiesQuery(did)— feeds the viewer.
Decisions
- 24-hour expiry, computed not stored.
- No grouping/containers — a "story" in the viewing sense is just all of a user's story records from the last 24h.
social.grain.storynamespace (not beta/unspecced) — ephemeral nature makes namespace caution less important.- Images only for v1 — schema accepts video MIME types for future support.
- No interactions for v1 — view count could be added later, reactions/replies after that.
- No dedicated route — viewer is an overlay, not a page.