Why These Rules Exist
folksbase uses strict rules to keep the codebase consistent and catch bugs early. Biome handles formatting and linting (replacing ESLint + Prettier), and TypeScript is configured with maximum strictness. CI rejects any violations — there’s no “fix it later.”Biome
Biome handles both formatting and linting in a single tool. Configuration lives inbiome.json at the repo root.
dist,.next,node_modules,migrations— build artifactsstorybook-static— Storybook build outputtest-results,playwright-report— Playwright artifacts**/globals.css— uses modern CSS features (@starting-style,@theme inline) that Biome can’t parse yet
TypeScript Strictness
Everytsconfig.json in the project has strict: true and noUncheckedIndexedAccess: true. This means:
- All function parameters and return types must be explicitly typed
- Array indexing returns
T | undefined— you must narrow with?.or?? - No
anytype, ever — useunknownand narrow, or create a proper type - No
// @ts-ignoreor// @ts-expect-error
Import Rules
- Use path aliases for cross-package imports:
@folksbase/db,@folksbase/types,@folksbase/emails - Within an app, use
@/*alias (e.g.,@/lib/logger.js,@/env.js) - Backend imports must include
.jsextension for ESM compatibility - Never use relative paths crossing package boundaries (
../../packages/dbis forbidden)
Export Rules
- Named exports only — no
export defaultexcept Next.js pages and layouts (framework requirement) - No barrel files (
index.tsre-exporting everything) insideapps/ - Import directly from the source file
Key Conventions
Types
- Use
typeoverinterfaceunless you need declaration merging - Check
@folksbase/typesbefore creating new types — it may already exist - Zod schemas are the source of truth for runtime validation; derive TS types with
z.infer<>
Error Handling
- Never
throwraw strings - API errors follow the
{ code, message, details? }shape from@folksbase/types - Let errors propagate to the global
errorHandlermiddleware — don’t add manual try/catch in route handlers
Logging
- No
console.login production code — ever - Use the structured logger:
import { logger } from "@/lib/logger.js" - Methods:
logger.info(),logger.warn(),logger.error()— all accept(message, extra?)where extra isRecord<string, unknown>
Variables
- Prefer
constoverlet— restructure with ternary or early return when possible - Every
letmust have an explicit type annotation (Biome rule:noImplicitAnyLet) - Don’t shadow restricted names (
Error,Object,Number, etc.) - Use
async/awaitover.then()chains
CSS and Design Tokens
- All colors use oklch-based CSS custom properties from
globals.css - Use Tailwind token classes (
text-gray-10,bg-green-a-3) — never hardcoded hex/hsl or Tailwind defaults likeslate-* - Card containers use the
.cardclass - Error states use token colors (
border-red-a-7,text-red-a-9)
Accessibility
All interactive UI components must include proper ARIA attributes:| Component | Required |
|---|---|
| Dialog | aria-modal, aria-labelledby, aria-describedby |
| Dropdown menu | role="menu" on container, role="menuitem" on items |
| Select | aria-haspopup="listbox", aria-expanded |
| Input (with error) | aria-invalid="true", aria-describedby pointing to error element |
| Icon-only buttons | aria-label describing the action |
| Navigation links | aria-current="page" on active link |
| Table headers | scope="col" on <th> elements |
| Error messages | role="alert" |
Quick Reference: Common Pitfalls
| Pitfall | Fix |
|---|---|
Using any | Use unknown and narrow |
console.log | Use logger.info/warn/error |
process.env.X | Import env from @/env or @/env.js |
Non-null assertion (!) | Use ??, ?., or explicit null check |
let without type | Add explicit type or use const |
| Hardcoded colors | Use oklch design tokens |
| Missing ARIA attributes | See accessibility table above |