~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
110 lines · 3.9 KB Raw History

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.json
  • lexicons/social/grain/story/defs.json
  • lexicons/social/grain/unspecced/getStoryAuthors.json
  • lexicons/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 LocationInput component).
  • 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.story namespace (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.