agentleFS
hyperlink-academyhyperlink-academy/leaflet/CLAUDE.md

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,…

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
  1. Very specific:

    Run npm run lexgen to regenerate both the JSON schemas and the TypeScript API types in lexicons/api/

    line 53

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

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 lexicons

Architecture

Data Flow

  1. Client mutations go through Replicache (src/replicache/mutations.ts) for optimistic local updates
  2. Server actions in actions/ persist changes to Supabase
  3. 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:

  1. Edit the relevant source file in lexicons/src/
  2. Run npm run lexgen to regenerate both the JSON schemas and the TypeScript API types in lexicons/api/

Key Directories

  • app/ - Next.js App Router pages and API routes
  • actions/ - Server actions (mutations, auth, subscriptions)
  • components/ - React components
  • src/ - Shared utilities, hooks, Replicache setup
  • appview/ - AT Protocol firehose consumer for indexing
  • feeds/ - Feed service for publication subscriptions
  • lexicons/ - AT Protocol schema definitions
  • supabase/migrations/ - Database schema

Patterns

  • Server actions: Export async functions with 'use server' directive, return Result<T> from src/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 assertFact that reuses the existing fact id — never retractFact(id) then assertFact({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; one assertFact (cardinality-many + explicit id) captures the old value as a single clean undo entry. Reserve retractFact for genuine deletions (usually paired with deleteEntity).
    • 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: true for derived/follow-up writes that shouldn't be independently undoable (e.g. async link/image enrichment after a paste, the yjs block/text doc persistence).
  • React contexts: DocumentProvider, LeafletContentProvider for 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), imports Props from ./Props, spreads {...props} on the <svg> element, and uses fill="currentColor" instead of hardcoded colors like fill="black".
  • Popovers and menus: Use the existing Popover (components/Popover), Menu, and MenuItem (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 (!inner or 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 via npm run check-query-plans; the fix recipes and plan-checked: suppression rules are documented in scripts/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 the update-default-theme skill (.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 dashboard PubDomainSettings.tsx, editor share menu ShareOptions/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 the domain-settings skill (.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 in buildPublicationRecord.ts, four spots in SettingsContent.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 the publication-settings skill (.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 -->

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.