0
2
# Grain hatk Template — MVP Design
## Goal
Rebuild grain-next (Lit Web Components + GraphQL/Quickslice) as a hatk-powered SvelteKit app, using hatk-template-teal as the scaffold. MVP: shell + auth + timeline with gallery cards, running end-to-end.
## Source Projects
- **hatk-template-teal** (`~/code/hatk-template-teal`): SvelteKit 5 + hatk scaffold for teal.fm music tracker. Provides the application shell, auth flow, component architecture, and build tooling.
- **grain-next** (`~/code/grain-next`): Lit Web Components PWA for Grain photo sharing. Provides lexicons, data models, and feature reference.
## Architecture
### Stack
- SvelteKit 5 + Svelte 5 (from teal template)
- hatk framework (XRPC endpoints, feeds, OAuth, SQLite)
- TanStack Svelte Query (client-side data fetching/caching)
- Vite build tooling
### Key Translation
| grain-next | hatk-template-grain |
| ------------------------- | ---------------------------- |
| Lit Web Components | Svelte 5 components |
| GraphQL via Quickslice | hatk XRPC + feeds |
| Client-side SPA router | SvelteKit file-based routing |
| Custom query/record cache | TanStack Svelte Query |
| Quickslice OAuth | hatk OAuth |
## Project Setup
1. Copy hatk-template-teal structure wholesale into hatk-template-grain.
2. Replace `lexicons/fm/teal/` with `lexicons/social/grain/` (copied from grain-next).
3. Keep `lexicons/app/bsky/`, `lexicons/com/atproto/`, `lexicons/dev/hatk/`.
4. Regenerate types with `npx hatk generate types`.
5. Update `hatk.config.ts`:
- `signalCollections: ["social.grain.actor.profile"]`
- OAuth scopes for all Grain collections (request upfront).
6. Update `package.json` name/description.
## Styling
Keep teal's dark theme and CSS variables unchanged. Tweak later.
## Routes (MVP)
```
app/routes/
├── +layout.svelte # Shell (3-column, from teal)
├── +layout.server.ts # Session parsing
├── +layout.ts # Query client setup
├── +page.svelte # Timeline (gallery cards)
├── +page.ts # Timeline feed query
├── oauth/callback/ # OAuth redirect
└── profile/[did]/ # Profile page (placeholder)
```
## Server (MVP)
```
server/
├── feeds/
│ └── recent.ts # Recent galleries feed
└── xrpc/ # Empty for now
```
## Components
### Keep from teal (as-is)
- `Shell.svelte` — 3-column layout
- `Sidebar.svelte` — left nav (logo, home, auth)
- `SidebarRight.svelte` — right sidebar (strip trending artists/genres, keep search bar placeholder)
- `Avatar.svelte`, `Button.svelte`, `Skeleton.svelte`, `Modal.svelte` — generic atoms
- `AuthBar.svelte`, `LoginModal.svelte` — auth organisms
### Replace
| teal | grain |
| ----------------------- | ------------------------------- |
| `PlayCard.svelte` | `GalleryCard.svelte` |
| `ScrobbleModal.svelte` | Remove |
| `BookmarkButton.svelte` | Remove |
| `FeedTabs.svelte` | Simplify to single "Recent" tab |
### GalleryCard.svelte
Displays:
- Thumbnail grid (first ~4 photos from gallery)
- Gallery title
- Author chip (avatar + handle)
- Photo count
## Feed: Recent Galleries
**Server (`server/feeds/recent.ts`):**
- Query `social.grain.gallery` ordered by `created_at DESC`
- Hydrate with author profile (`social.grain.actor.profile`)
- Join `social.grain.gallery.item` for photo refs, then `social.grain.photo` for blobs/aspect ratios
- Cursor-based pagination
**Client:**
- `+page.ts` calls `dev.hatk.getFeed` with feed `recent`
- TanStack Query caches + paginates
- `+page.svelte` renders `GalleryCard` list
## Auth
Identical to teal's hatk OAuth flow. Update scopes to:
```
atproto
repo:social.grain.gallery
repo:social.grain.gallery.item
repo:social.grain.photo
repo:social.grain.actor.profile
repo:social.grain.graph.follow
repo:social.grain.favorite
repo:social.grain.comment
```
## hatk.config.ts
```typescript
signalCollections: ["social.grain.actor.profile"];
```
## What's Deferred
- Comments, favorites, notifications
- Search / explore
- Gallery create/edit UI
- Photo upload
- EXIF extraction
- Profile editing
- Follow/unfollow
- Rich text / facets
- PWA / service worker
- Custom accent color / branding