leaflet
CLAUDE.md527 starsChanged 4 months agoMITView on GitHub
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. 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. The source of truth for lexicon schemas is the TypeScript files in lexicons/src/ (e.g. facet.ts,…
In this repository12 other files it gives its agents.
- blueprint-generateSkill
- blueprintSkill
- domain-settingsSkill
- leaflet-ai-apiSkill
- lexiconsSkill
- mirror-editor-block-stylesSkill
- notificationsSkill
- publication-settingsSkill
- specSkill
- tests-postsSkill
- tests-subscribeSkill
- update-default-themeSkill
The file
We split this CLAUDE.md into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 53
Run npm run lexgen to regenerate both the JSON schemas and the TypeScript API types in lexicons/api/
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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
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 lexiconsArchitecture
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. When adding one:- Move/replace a fact with a single
assertFactthat reuses the existing fact id — neverretractFact(id)thenassertFact({id}). The retract+assert pair records two undo entries with an intermediate state where the fact is gone, so undo/redo flickers the block out of existence; oneassertFact(cardinality-many+ explicit id) captures the old value as a single clean undo entry. ReserveretractFactfor genuine deletions (usually paired withdeleteEntity). - Group a multi-mutation user action into one undo step with
undoManager.withUndoGroup(async () => { ... }), awaiting every mutation inside — undo entries are recorded asynchronously in mutator bodies, so a fire-and-forget or un-awaited mutation lands as its own separate Cmd-Z step. - Pass
ignoreUndo: truefor derived/follow-up writes that shouldn't be independently undoable (e.g. async link/image enrichment after a paste, the yjsblock/textdoc persistence).
- Move/replace a fact with a single
- 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 - Supabase query plans: Never combine
.order()on a table's own column with.limit()and a row-restricting embed (!inneror an embed-path filter) in one query — PostgREST compiles the embed into a LATERAL join that forces Postgres to walk the order column's index across the whole table probing the embed per row (this has caused repeated production full-table scans). Filter on the from-table's own indexed columns, start the query from the join table, or use a fenced SQL function for newest-N-across-a-join (examples:get_publication_feed_docs,get_tag_page_document_uris). CI enforces this vianpm run check-query-plans; the fix recipes andplan-checked:suppression rules are documented inscripts/check-supabase-query-plans.mjs. - Default theme: The default theme colors (accent/background/primary/highlights) are hardcoded in several disconnected places (
themeDefaults.ts,themeUtils.ts,app/globals.css:root,emails/shared.tsx,PubPresetPicker.tsx) with no shared import, so they drift easily. When changing any default color, use theupdate-default-themeskill (.claude/skills/update-default-theme/) which lists every place to update and keeps them in sync. - Custom domain settings: Custom domains are listed, verified, and assigned in three disconnected surfaces (profile
settings/domains, publication dashboardPubDomainSettings.tsx, editor share menuShareOptions/DomainOptions.tsx) that each hand-roll their own row UI over the same data. When changing domain row UI or behavior in any one of them, use thedomain-settingsskill (.claude/skills/domain-settings/), which lists the shared pieces and requires asking whether the change applies to the other two. - Publication settings: A setting in the General tab passes through several hand-maintained explicit field lists (both lexicons,
normalizePublication, both builders inbuildPublicationRecord.ts, four spots inSettingsContent.tsx,createPublication.ts) — none of them spread, so a field missing from one is dropped with no type error. When adding or changing a setting there, use thepublication-settingsskill (.claude/skills/publication-settings/), which walks the round trip and how to verify it saves and applies.
Comments
Don't describe in a comment what the code already says, and don't capture counterfactuals — comments shouldn't be hysteretic. Don't narrate what changed, what it used to do, what you considered and rejected, or why something isn't done another way. Comment only on the non-obvious why that the code itself can't convey.
<!-- BEGIN:nextjs-agent-rules -->This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

