Data model (D1)

erDiagram
  users ||--o{ accounts : "signs in with"
  users ||--o{ sessions : has
  users ||--o{ api_tokens : has
  users ||--o{ items : saves
  users ||--o{ collections : owns
  users ||--o{ destinations : owns
  users ||--o{ highlights : makes
  users ||--o{ activity : earns
  users ||--o{ ideas : keeps
  users ||--o{ ai_usage : spends
  collections }o--o| destinations : "sends to"
  items }o--o| collections : "filed in"
  items ||--o{ highlights : contains
  items ||--o{ item_tags : tagged
  tags ||--o{ item_tags : labels
  highlights ||--o{ deliveries : "delivered as"
  destinations ||--o{ deliveries : receives
Table Purpose Notable columns and constraints
users one reader is_demo, plan, daily_goal, timezone, ai_provider; unique email when present
accounts SSO identities PK (provider, provider_account_id)
sessions browser sessions id = sha256(token), expires_at
api_tokens extension and script access token_hash unique, prefix for display, last_used_at
collections shelves per project destination_id (nullable)
items saved pages unique (user_id, url), status, progress, content_html, content_text, reading_minutes
tags, item_tags free tagging unique (user_id, name)
highlights text quote selectors quote, prefix, suffix, position, color (palette name or #rrggbb, see packages/core/src/colors.ts), note
deliveries the ledger PK (highlight_id, destination_id), status, attempts, error
activity glow ledger day in the reader's time zone, kind, glow
ideas Spark output outline and source_item_ids as JSON
ai_usage daily quota counter PK (user_id, day), upserted with RETURNING count
topics what the radar watches source auto or manual, muted; auto topics come from tags
trends stories found per user unique (user_id, url), score, comments, dismissed
radar_runs last refresh per user refreshed every 6 hours by Cron or on view
demo_creations demo abuse guard hashed address, purged daily

Added in migration 0002: items.remind_after, items.last_opened_at, users.share_signals, users.reminders, api_tokens.kind (extension or agent) and api_tokens.client.

All foreign keys cascade from users, so deleting an account (or purging a demo) removes everything in one statement. Migrations live in apps/web/migrations.