Before any Next.js work, find and read the relevant doc in node_modules/next/dist/docs/. Your training data is outdated -- the docs are the source of truth.
- NEVER use an em-dash ("—").
| Command | Purpose |
|---|---|
yarn dev |
Start dev server with HMR. Use this, not build |
yarn lint |
Run ESLint |
yarn test |
Run test suite |
yarn build |
Production build. Never run during agent sessions -- it replaces .next/ with production assets and breaks HMR |
- App Router (
app/directory). Do not use Pages Router patterns. - Client side by default. Assume all of this app runs on the client side unless we need to add a server side compontent.
- TypeScript required. All new files must be
.ts/.tsx. Noanytypes without a comment explaining why. - Cloud-sync API lives in
app/api/matrix/**/route.ts(Node runtime). Shared route helpers live inlib/cloud/. The relay stores opaque ciphertext only — seeTHREAT-MODEL.md.
This is a single Next.js app with both client (SPA) and server (Route Handlers) code in the same tree. Be deliberate about which side a piece of work belongs on.
Use server (app/api/**/route.ts, lib/cloud/**) only when you need to:
- Touch the MongoDB collection (
getCollectionfromlib/cloud/db.ts). - Enforce a security invariant the client cannot be trusted with — e.g. ciphertext size cap, rate limit, optimistic-concurrency check.
- Hold a secret (DB credentials, future API keys). Anything imported from a Route Handler is server-only and is not sent to the browser.
Use client (components/, app/page.tsx, hooks under components/risk-matrix/) for:
- Anything React, hooks, DOM, drag-and-drop, localStorage.
- Anything that touches
lib/e2ee/*— encryption MUST run client-side; the server never sees plaintext or keys. - Anything reading
window.location(share-link import lives client-side).
Don't import server modules into client code. A "use client" file that imports from lib/cloud/db.ts will leak mongodb and connection logic into the browser bundle. The line is enforced informally — if a client file imports from lib/cloud/, that's a smell to check.
Don't import client modules into Route Handlers. Anything under components/ is client UI; importing it server-side will fail at build time on "use client" boundaries.
Same-origin by design. The client repository (components/risk-matrix/matrixCloudRepository.ts) hits relative URLs like /api/matrix. There's no CORS allow-list; the API only responds to same-origin requests. If you change this, also re-add server-side origin validation.
Rate limiter is in-process. lib/cloud/rateLimit.ts uses rate-limiter-flexible with the memory backend. Sufficient for a single-instance deploy; for horizontal scale, swap RateLimiterMemory for RateLimiterRedis (same API).
- Co-locate related files: component, styles, tests, and types in the same directory.
- Use
next/imagefor images,next/linkfor navigation,next/fontfor fonts. - Fetch data in Server Components. Avoid
useEffectfor data fetching on the client. - Use loading.tsx, error.tsx, and not-found.tsx for route-level UI states.
- Accessibility is required: meet WCAG 2.1 AA color contrast, preserve visible keyboard focus states, and ensure all interactive controls have clear labels.
The app supports light + dark modes via class-based Tailwind v4 (@custom-variant dark in app/globals.css). Whenever you touch styles, the dark mode treatment must land in the same change as the light mode treatment. Do not ship a light-mode-only update.
- Prefer the semantic tokens.
bg-rm-surface,bg-rm-surface-2,bg-rm-surface-hover,border-rm-border,border-rm-border-strong,border-rm-divider,text-rm-muted,text-rm-muted-2,bg-rm-overlay,ring-rm-ring, plus the matrix palette (rm-canvas,rm-ink,rm-line,rm-actions,rm-primary,rm-{green,yellow,orange,red}and their-saturated/-strongsiblings) all auto-swap via CSS variables defined in:rootand.dark. Reach for one of these before adding a literalbg-whiteortext-zinc-600. - If a token doesn't fit, define one. Add the variable in both
:rootand.darkinapp/globals.css, expose it under@theme inline, and use it. Don't sprinkle ad-hocdark:variants when a token would do. dark:variants are reserved for status tints. Domain colors that don't live in the rm-* palette (sky/amber/red/emerald pills, etc.) should pair every light-mode utility with adark:counterpart in the same className:bg-emerald-50 dark:bg-emerald-950/40 text-emerald-900 dark:text-emerald-200.- Test every change in both themes. Toggle via the Theme button in the top bar (light / dark / system). Watch for: tinted hover backgrounds that don't cover the underlying cell color, focus rings that disappear on dark, shadows that vanish on dark canvas, and copy that drops below WCAG AA contrast.
- Avoid raw hardcoded colors.
bg-white,border-black/X,text-zinc-X,bg-black/X,ring-black/Xare smells — they don't switch with the theme. Replace with semantic tokens.
- Write or update tests alongside every major change. If we're debugging something major, wait to add tests until the change is actually fixes the problem.
- Co-locate test files next to the code they test (e.g.,
Button.test.tsxbesideButton.tsx). - If you add a component, add a test. If you fix a bug, add a regression test.
yarn addthe package.- Restart the dev server so Next.js picks up the change.
yarn lintpasses with no errors.yarn testpasses (if tests exist for the changed code).- No TypeScript errors (
npx tsc --noEmit). - Verify the change works in the browser via the dev server.