Guide for coding agents working in this repo. Use repo commands, follow existing patterns, and keep edits focused. Prefer nearby conventions over new abstractions.
- React Router (framework mode), React 18, TypeScript, Vite
- Package manager:
pnpm(pnpm-lock.yaml) - Runtime: Node
>=24(Volta pins Node 24.13.1) - Styling: Tailwind + custom CSS
- Content: MDX in
content/ - Data/cache: Redis +
@epic-web/cachified - Integrations: Inngest, Algolia, Sentry, Fly.io
- App:
app/ - Routes:
app/routes/ - Utilities:
app/utils/ - Inngest:
app/inngest/ - Content:
content/blog,content/til,content/pages - Server entry:
index.mjs,server/index.mjs,server/dev-server.mjs - Config:
tsconfig.json,eslint.config.mjs,prettier.config.js
- Install deps:
pnpm install - Pull env (optional):
npx dotenv-vault pull - Start Redis:
docker-compose up -d - Optional Inngest server:
npx inngest-cli@latest dev - Start app:
pnpm dev(usuallyhttp://localhost:8080)
- Clean:
pnpm clean - Dev:
pnpm dev - Build:
pnpm build - Start prod:
pnpm start - Lint:
pnpm lint - Typecheck:
pnpm typecheck - Format:
pnpm format - Extra checks:
pnpm knip
- Runner: Vitest (
vitest.config.ts) - Full suite:
pnpm test(vitest run) - Current coverage is a small smoke suite in
tests/smoke/*.test.ts
- File:
pnpm test:single tests/smoke/health-route.test.ts - Test name:
pnpm test:single tests/smoke/env.server.test.ts -t "fallback"
- Lint one file:
pnpm lint -- app/routes/_index.tsx - Typecheck is project-wide (
tsc -b .), so usepnpm typecheck
- Pre-commit hook (
.husky/pre-commit) runsnpx lint-staged lint-stagedruns clean, test, lint, typecheck, and prettier- CI deploy workflow runs lint + typecheck + test before deploy
- Prettier is source of truth (
prettier.config.js) - ESLint config is
eslint.config.mjs - Prettier/ESLint both extend
@epic-web/config/* - Prefer fixing warnings instead of disabling rules
- Existing style: single quotes, trailing commas, minimal semicolons
- Order: Node built-ins -> third-party ->
~/...-> relative - Prefer
typeimports for type-only symbols - Use
~/alias for cross-app imports (~/*->app/*) - Use relative imports for same-folder files
strict: trueis enabled; keep code strongly typed- Avoid
any; useunknownand narrow - Use Zod for runtime validation of untrusted input
- Infer schema types with
z.infer<typeof schema> - In routes, prefer
useLoaderData<typeof loader>() - Use
LoaderFunctionArgs/ActionFunctionArgsfromreact-router
- Files/folders: kebab-case
- Components and types: PascalCase
- Variables/functions: camelCase
- Booleans should read as predicates (
isOpen,hasX,showX) - Server-only modules use
.server.ts - Client-only modules use
.client.tsxwhen applicable
- Common route exports:
loader,action,meta,shouldRevalidate, default - Use
data(...),redirect(...), and thrownResponse - Use
invariantResponsefor required params/data checks - Keep explicit status codes for expected failures
- Validate params, form data, JSON body, and env early
- Log useful context in catch blocks
- Rethrow when callers should fail
- Return fallbacks only when intentionally graceful
- Env schema is in
app/utils/env.server.ts - Add new env vars there before use
- Expose only safe values via
getEnv() - Never leak secrets to
window.ENV
- Follow Redis key patterns (
gql:*,home:*) - Reuse existing cachified patterns in
app/utils/* - For bulk refreshes, use bounded concurrency (
p-queue) - Be careful changing cache key shapes
- Tailwind is the default styling approach
- Use
twMerge/twJoinfor conditional classes - Reuse shared UI primitives before adding new ones
- MDX compile path:
app/utils/mdx.server.ts - MDX cache/list path:
app/utils/mdx-utils.server.ts
- Checked
.cursor/rules/,.cursorrules,.github/copilot-instructions.md - No Cursor or Copilot instruction files were found
- Make minimal, targeted changes
- Avoid bundling broad refactors with feature/fix work
- Keep server/client boundaries intact (
.servermust not leak client-side) - After non-trivial edits, run
pnpm lintandpnpm typecheck - If you add tests, add full-suite + single-test commands and update this file
- Primary: Other software developers reading technical content
- Context: Reading on desktop or mobile, often during work breaks or personal learning time
- Job to be done: Find useful technical content quickly, browse TILs, stay updated on posts
- Playful developer - voice that feels like a real person, not a content machine
- Confident monospace choice signals technical identity without apology
- Humor and personality (Giphy errors, random quotes, easter eggs) is intentional, not accidental
- Monospace-only typography: CommitMono as the single font family - bold choice that works
- Pink accent: hsl(322, 83%, 57%) is the only color beyond neutrals - commit to it
- Dark + light modes: Both supported, no "default" - user preference wins
- Anti-patterns to avoid:
- No gradient text
- No glassmorphism
- No hero metric layouts
- No generic shadows or glowing effects
- No centered-container everything
- Personality first - If a choice makes the site feel more like a real person, do it
- Technical identity - The monospace font IS the brand; lean into it, don't soften it
- Pink is enough - One accent color is stronger than five; resist adding more
- Avoid the template - If it's a pattern you'd see on 100 AI-generated sites, reject it
- Motion with purpose - Every animation should feel intentional, not decorative