Aggressively keep this up-to-date if you notice anything outdated!
- @docs/DESIGN.md - Aggressively keep this up-to-date if you notice anything outdated!
- @docs/diagrams/ - Flow diagrams for various systems
ALWAYS read the relevant documentation before working.
Read these if you need context on features or specific references.
- @docs/features - Feature specs (may be outdated)
- @docs/references/ - Reference docs for external tools. Consult before editing related configs.
pnpm typecheck- Run before committing (noany, no@ts-ignore)
- Types: Explicit types everywhere; use Zod for runtime validation
- Queries: Avoid N+1 queries; use joins or batch fetching
- UI: Use optimistic updates for responsive UX
- DRY: Deduplicate logic that must stay in sync; don't merge code that merely looks similar but serves independent purposes
- Always write tests for the intended behavior of functions, not the actual behavior. If the actual behavior is wrong and the issue is pre-existing, write the test correctly, mark it skipped, and file a GitHub issue on brendanlong/clawed-burrow
- Don't create barrel files, prefer direct imports within our code
See @src/components/CLAUDE.md for UI component guidelines, available components, and icons.
- Break work into commit-sized chunks; commit when finished
- Use amend commits when it makes sense (ALWAYS check the current commit before amending)
- Main branch:
master - Commit
migrations/schema.sqlchanges separately if unrelated to current work
When you mention GitHub issues:
- Fetch the issues - Use the GitHub API to list issues:
https://api.github.com/repos/brendanlong/lion-reader/issues - Read relevant issues - Fetch detailed issue content via the API to understand requirements and discussion
- Reference in commits - Include issue numbers in commit messages (e.g., "Fix: prevent over-fetching slow feeds (#175)") when applicable
src/server/
trpc/routers/ # tRPC API endpoints
services/ # Reusable business logic (shared across APIs)
db/ # Database schemas and client
jobs/ # Background job queue
plugins/ # Content source plugins (LessWrong, Google Docs, ArXiv, GitHub)
mcp/ # MCP server
src/lib/ # Shared utilities (client and server)
src/components/ # React components
src/app/ # Next.js routes
tests/unit/ # Pure logic tests (no mocks, no DB)
tests/integration/ # Real DB via docker-compose (no mocks)
See docs/diagrams/ for more detail. These diagrams are very helpful for quickly understanding the codebase.
- IDs: UUIDv7, generated in TypeScript via
generateUuidv7()from@/lib/uuidv7.gen_uuidv7()is not available in our Postgres version. - Timestamps:
timestamptz, store UTC - Soft deletes: Use
deleted_at/unsubscribed_atpatterns - Upserts: Prefer
onConflictDoNothing()/onConflictDoUpdate()over check-then-act - Background jobs: Postgres-based queue
- Caching/SSE: Redis available for caching and coordinating SSE
Use the database views for frontend queries instead of manual joins:
user_feeds: Active subscriptions with feed data merged. Use forsubscriptions.list/get/export. Already filters out unsubscribed entries and resolves title (custom or original).visible_entries: Entries with visibility rules applied. Use forentries.list/get/count. Includes read/starred state and subscription_id. An entry is visible if it's from an active subscription OR is starred.
These views are defined in migrations/0035_subscription_views.sql and have Drizzle schemas in src/server/db/schema.ts.
- Pagination: Always cursor-based (never offset)
- tRPC naming:
noun.verb(e.g.,entries.list,entries.markRead)
Business logic should be extracted into reusable service functions in src/server/services/:
- Purpose: Share logic between tRPC routers, MCP server, background jobs, etc.
- Pattern: Pure functions that accept
dband parameters, return plain data objects - Location:
src/server/services/{domain}.ts(e.g.,entries.ts,subscriptions.ts) - Naming:
verbNoun(e.g.,listEntries,searchSubscriptions,markEntriesRead)
Don't try to invalidate the cache or look things up in the cache. Pass data down as props if needed (and add to the backend API if necessary).
Always use our custom user agent.
import { USER_AGENT, buildUserAgent } from "@/server/http/user-agent";
headers: { "User-Agent": USER_AGENT }Prefer SAX-style parsing unless the algorithm requires a DOM.
- XML/RSS:
fast-xml-parser(streaming) - HTML extraction:
htmlparser2(streaming) - DOM required (Readability):
linkedom - Parse once, pass parsed structure through code