Shelfie -- Architecture

Internal reference. Not exposed in the app. Updated by hand when the stack changes.

Flow diagram

                                                       ┌──────────────────┐
                                                       │  OpenLibrary     │
                                                       │  search.json     │
                                                       │  (no auth, free) │
                                                       └────────▲─────────┘
                                                                │ enrich:
                                                                │ title/author/subjects/year/ISBN/cover
                                                                │
   ┌──────────────────┐    HTTPS    ┌────────────────────────────────┐
   │  Browser / PWA   │────────────▶│  Vercel Edge / Functions       │
   │  shelfiebook.com │◀────────────│  Next.js 15 App Router         │
   │  iOS+Android+Web │             │  middleware.ts (auth gate)     │
   └────────┬─────────┘             │                                │
            │                       │  Server components:            │
            │ camera capture        │  /library/*  /scan/*  /u/*     │
            │ multipart upload      │                                │
            ▼                       │  Server actions / API routes:  │
   ┌────────────────┐               │  /api/spines  (vision)         │
   │  Spine photo   │──────────────▶│  /api/identify-cover           │
   └────────────────┘               │  /api/enrich   /api/import     │
                                    │  /api/export   /api/recommend  │
                                    │  /api/books    /api/v1/books   │
                                    │  /api/migrate-local            │
                                    │  /api/library-profile          │
                                    │  /api/api-keys                 │
                                    │  /api/stripe/{checkout,webhook,│
                                    │              portal}           │
                                    │  /auth/callback                │
                                    └─┬────────┬──────────┬──────────┘
                                      │        │          │
                                      │ Bearer │ Auth     │ JWT
                                      │ JWT    │ cookies  │
                                      ▼        ▼          ▼
                       ┌───────────────────────────────────────┐
                       │              Supabase                 │
                       │  ┌─────────────┐  ┌────────────────┐  │
                       │  │ Postgres    │  │ Auth (GoTrue)  │  │
                       │  │ (with RLS)  │  │ magic links    │  │
                       │  └──────┬──────┘  └────────┬───────┘  │
                       │         │                  │           │
                       │  profiles, libraries,      │           │
                       │  books, photos,            │           │
                       │  book_photos,              │           │
                       │  shelf_labels,             │           │
                       │  smart_shelves, api_keys,  │           │
                       │  embeddings (pgvector)     │           │
                       │                            │           │
                       └────────────┬───────────────┘           │
                                    │                           │
                       ┌────────────┘                           │
                       │  SMTP relay                            │
                       ▼                                        │
              ┌──────────────────┐                              │
              │      Resend      │  auth@shelfiebook.com        │
              │  smtp.resend.com │  delivers magic-link emails  │
              └──────────────────┘                              │
                                                                │
            ┌───────────────────────────────────────────────────┘
            │ direct (server-side only)
            ▼
   ┌────────────────────────┐    ┌────────────────────────┐
   │  Anthropic API         │    │  Google Gemini API     │
   │  claude-sonnet-4-6     │    │  gemini-2.5-flash-image│
   │  - readSpines          │    │  one-shot UX graphics  │
   │  - readCover           │    │  (npm run gen-graphics)│
   │  - recommendForCluster │    └────────────────────────┘
   │  - library-profile     │
   │  prompt caching ON     │
   └────────────────────────┘

   ┌────────────────────────┐    ┌────────────────────────┐
   │  Voyage AI             │    │  Google Books API      │
   │  voyage-3 embeddings   │    │  fallback enrichment   │
   │  (planned: pgvector)   │    │  if OpenLibrary misses │
   └────────────────────────┘    └────────────────────────┘

   ┌────────────────────────┐    ┌────────────────────────┐
   │  Stripe                │    │  Cloudflare R2         │
   │  Checkout + Customer   │    │  shelfie-photos bucket │
   │  Portal + Webhooks     │    │  (planned for cloud    │
   │  freemium tiers        │    │   photo storage)       │
   └────────────────────────┘    └────────────────────────┘

   ┌────────────────────────┐
   │  GitHub                │  source of truth: ERoske/shelfie (private)
   │  ERoske/shelfie        │  Vercel deploys via CLI; auto-deploy planned
   └────────────────────────┘

Local mode (no cloud)

When SHELFIE_DB_MODE=local (the default in .env.local), the data plane shrinks to just the browser + filesystem:

   Browser ──▶  Next.js (localhost:3003) ──▶  fixtures/library.json
       │                                       fixtures/spines/*.json
       └──── localStorage (overrides, status, rating, notes, etc.)

Cloud-only services (Supabase, Resend, Stripe) are skipped. Anthropic + Gemini still work because they're stateless API calls.

Tech stack table

Layer Tech Purpose Where
Hosting Vercel Next.js production runtime, SSL, edge cache, atomic deploys shelfiebook.com
Domain registrar Name.com DNS for shelfiebook.com A record + CNAME pointing to Vercel
Framework Next.js 15 (App Router) UI, server components, API routes, middleware top-level repo
Language TypeScript 5.6 All app code tsconfig.json
Styling Tailwind CSS 3.4 All component classes tailwind.config.ts
Type system React 19 + @types/react 19 Type checking against React 19 APIs n/a
Auth Supabase Auth (GoTrue) Magic-link sign-in + sessions middleware.ts, lib/supabase/*
Database Supabase Postgres All user data, with Row-Level Security supabase/migrations/*
Email relay Resend Magic-link transactional email smtp.resend.com (configured in Supabase)
Vision OCR Anthropic Claude Sonnet 4.6 Read book spines from photos lib/claude.ts (readSpines, readCover)
Recommendations Anthropic Claude Sonnet 4.6 Gaps + library-profile generation lib/claude.ts (recommendForCluster)
Metadata OpenLibrary REST Enrich book metadata (free, no auth) lib/enrich.ts
Metadata fallback Google Books v1 When OpenLibrary misses lib/enrich.ts
Embeddings (planned) Voyage AI voyage-3 Semantic "Books like this" lib/embeddings.ts
One-shot graphics Google Gemini 2.5 Flash Image Generate static UX assets (hero, empty states, logo) scripts/gen-graphics.ts -> public/graphics/
Payments Stripe Subscriptions for Plus + Pro tiers lib/stripe.ts, /api/stripe/*
Photo storage (planned) Cloudflare R2 Cheap S3-compatible blob store not yet wired
Vector search (planned) pgvector (Supabase extension) "Books like this" cosine search enabled in 0001_init.sql
Graph render Cosmograph WebGL Library-as-graph view components/LibraryGraph.tsx
Spreadsheet export xlsx (npm) Build .xlsx exports app/api/export/route.ts
Postgres driver pg + pgvector Migration runner scripts/migrate.ts
MCP server @modelcontextprotocol/sdk Expose library to Claude Desktop / Cursor mcp/bin.js
CLI (vanilla node fetch) Command-line wrapper over /api/v1 cli/bin.js
Source control Git + GitHub (ERoske/shelfie, private) Code history n/a
CI / deploys Vercel CLI (vercel --prod) Push to production scripts/push-env.ts, scripts/add-domain.ts

Where every secret lives

Secret Used by Stored in
ANTHROPIC_API_KEY server: lib/claude.ts .env.local + Vercel env (3 envs)
GOOGLE_API_KEY (Gemini + Books) server: scripts/gen-graphics.ts, lib/enrich.ts .env.local
SUPABASE_*, NEXT_PUBLIC_SUPABASE_* server + client .env.local + Vercel env
SUPABASE_SERVICE_ROLE server admin only (bypasses RLS) .env.local + Vercel env
SUPABASE_DB_URL scripts/migrate.ts .env.local (not in Vercel)
RESEND_API_KEY server: app uses Resend SDK; Supabase SMTP password .env.local + Vercel env + Supabase Auth SMTP form
STRIPE_SECRET_KEY server: lib/stripe.ts (planned) Vercel env
STRIPE_WEBHOOK_SECRET server: /api/stripe/webhook (planned) Vercel env
STRIPE_PRICE_PLUS, STRIPE_PRICE_PRO server: lib/stripe.ts (planned) Vercel env

Service-role / DB password are never exposed to the browser. Anything NEXT_PUBLIC_* is.

Data flow: a typical shelfie

  1. User opens /scan on iOS, taps the camera button.
  2. Browser uploads JPEG to /api/spines via multipart form.
  3. Vercel function downsamples, sends image to Anthropic Claude Sonnet 4.6 with cached system prompt + emit_spines tool.
  4. Response is [{title, author, confidence, bbox}]. Function stashes it in sessionStorage and redirects to /scan/review/<live_id>.
  5. User accepts / fixes spines. Two-tap fix opens /api/suggest (OpenLibrary search) for candidates.
  6. On Save, each spine becomes a row via /api/v1/books (cloud) or the localStorage additions store (local).
  7. Cloud writes go to books (RLS-gated by library_id); the same bookKey() normalization keeps duplicates from any future re-shelfie collapsing into one row.
  8. Library-page server component re-renders with the new books, photos, and book_photos rows.