CLAUDE.md #
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview #
Leaflet is a full-stack TypeScript web app for shared writing and social publishing, built on Bluesky (AT Protocol). Users create collaborative documents ("Leaflets") and publish them as blog posts/newsletters ("Publications") that others can follow.
Tech Stack #
- Frontend: React 19 + Next.js 16 (App Router, Turbopack)
- Database: PostgreSQL via Supabase + Drizzle ORM
- Real-time sync: Replicache for optimistic updates and offline support
- Editor: ProseMirror with Yjs CRDT
- Social: AT Protocol (@atproto packages) for Bluesky integration
- Jobs: Inngest for async serverless functions
Commands #
npm run dev # Start Next.js dev server (port 3000)
npm run start-appview-dev # Start AT Protocol appview service
npm run start-feed-service-dev # Start feed subscription service
npm run lint # ESLint
npx tsc # TypeScript type checking (used in CI)
npm run generate-db-types # Regenerate Supabase types after schema changes
npm run lexgen # Regenerate AT Protocol types from lexicons
Architecture #
Data Flow #
- Client mutations go through Replicache (
src/replicache/mutations.ts) for optimistic local updates - Server actions in
actions/persist changes to Supabase - CVR sync reconciles client and server state via Replicache
Document Model #
Documents are composed of blocks (text, image, embed, code, poll, etc.). Block components live in components/Blocks/. Blocks are rendered in linear lists by components/Blocks/index.tsx and in an xy canvas by components/Canvas.tsx.
AT Protocol / Bluesky #
- Lexicons (
lexicons/) define schemas for reading and writing records to the AT Protocol network and users' PDSs - Appview (
appview/) consumes the firehose to index published content - Feeds (
feeds/) provides subscription feeds for publications
Lexicon Workflow #
The source of truth for lexicon schemas is the TypeScript files in lexicons/src/ (e.g. facet.ts, publication.ts, blocks.ts). The JSON files in lexicons/pub/ are generated output — do not edit them directly, as npm run lexgen will overwrite them.
To add or modify a lexicon:
- Edit the relevant source file in
lexicons/src/ - Run
npm run lexgento regenerate both the JSON schemas and the TypeScript API types inlexicons/api/
Key Directories #
app/- Next.js App Router pages and API routesactions/- Server actions (mutations, auth, subscriptions)components/- React componentssrc/- Shared utilities, hooks, Replicache setupappview/- AT Protocol firehose consumer for indexingfeeds/- Feed service for publication subscriptionslexicons/- AT Protocol schema definitionssupabase/migrations/- Database schema
Patterns #
- Server actions: Export async functions with
'use server'directive, returnResult<T>fromsrc/result.ts - Replicache mutations: Named handlers in
src/replicache/mutations.ts, keep server mutations idempotent - React contexts:
DocumentProvider,LeafletContentProviderfor page-level data - Inngest functions: Async jobs in
app/api/inngest/functions/ - Icons: Icon components live in
components/Icons/. Each icon is a named export in its own file (e.g.RefreshSmall.tsx), importsPropsfrom./Props, spreads{...props}on the<svg>element, and usesfill="currentColor"instead of hardcoded colors likefill="black". - Popovers and menus: Use the existing
Popover(components/Popover),Menu, andMenuItem(components/Menu) components — do not create new popover/menu primitives. Avoid using buttons inside Popover or Modal triggers unless using a specific button component. If button is being used as trigger, always add an "asChild" prop to the Menu or Popover