Keep the Remix 3 website implementation lean, stable, and behaviorally aligned with legacy production expectations.
- Framework conventions match the consolidated Remix skill in
.agents/skills/remix/SKILL.md. Do not treat this file as a second copy of those rules; it only records repo-specific invariants. - **
references/remix/**** — local API semantics when package usage is unclear.
- Root
server.ts— Node HTTP process entry. UsescreateRequestListenerfromremix/node-fetch-server, imports the live app router, and closes the asset server during shutdown. app/router.ts—createRouter, root middleware stack,router.map(...)wiring, and theGET /assets/*route that delegates toapp/utils/assets.server.ts.- Production /
pnpm run preview— runs the same TypeScript server entry as development (server.ts) throughremix/node-tsx; there is no separate Vite SSR bundle.
- Route declarations live in
app/routes.ts; router mappings inapp/router.ts; keep them aligned. - Map explicit routes before the
router.map("*", ...)catch-all. - In
app/controllers/**, keep exported route handler/controller first and helper/details below. - For route-local, single-use UI, keep it in the route file; extract to
app/ui/**only when shared. - For route-local hydrated UI in
app/assets/**, keep component/rendering code first and put styles below the components. - Let components own their own state and implementation details. If a parent imports a long list of child constants/helpers, that is a boundary smell; extract a child component/module instead.
- Prefer direct code over tiny wrappers/constants that do not add meaning. Inline one-off helpers, values, and derived constants at their use site when that reads clearly.
- Use shared theme tokens such as
theme.fontFamily.*,theme.fontWeight.*, and route theme objects instead of hardcoded repeated values. Never hardcode customfontWeightnumbers or strings. - In actions/mutations, validate request-derived input with
remix/data-schema+parseSafeand return explicit400on invalid input. - Use
clientEntry(import.meta.url, function ExportName(...) { ... })for hydrated asset modules so server rendering can resolve them throughresolveClientEntry(...)using the component function name. - Resolve the root browser entry and preload links through
app/middleware/asset-entry.ts+app/utils/assets.server.ts; do not hardcode build output paths. - Plain stylesheets still come from
public/stylesviaapp/utils/style-hrefs.ts; this app usesremix/assetsfor hydrated browser modules.
- Prefer CSS for visual states and animations. Use
remix/ui/animationhelpers for CSS transitions when they fit; keep JavaScript for state/timing rather than frame-by-frame styling. - Treat
prefers-reduced-motionas its own behavior path. Avoid churny intro animations, large hover expansions, and motion-heavy effects; provide a simple static hover/focus state instead of removing feedback entirely. - Keep accessible labels stable when visual text updates frequently. For animated counters/timers, prefer a stable label that states the event/date and hide the decorative changing digits from assistive tech.
- Add focused tests around public behavior. Prefer component/browser tests for interactive UI when feasible; avoid testing internal helpers just because they are easy to import.
- Add/update route pattern in
app/routes.ts. - Implement route/controller in
app/controllers/**. - Wire mapping in
app/router.tsbefore catch-all fallback. - Add focused tests and run targeted verification (+ Remix typechecks for substantial changes).
- Run
pnpm run buildbefore shipping a PR to catch CSS/runtime regressions. - If behavior changes, update the parity backlog below.
- Before any commit or push, run the formatter on the changed files.
Remaining differences vs the previous production site (small, shippable items):
- Link prefetch parity: Intent/predictive prefetch is not yet mirrored across Remix pages.
- Analytics on in-app transitions: Verify one pageview per navigation when using client-side navigation.
- Client navigation shape: Keep Jam on top-level client navigation unless a future route needs an independently updating region.
- In sandboxed runs, Playwright may not reach
localhost:5173and may fail to download browsers. - For reliable local runs, install and run with project-local browsers:
PLAYWRIGHT_BROWSERS_PATH=0 pnpm exec playwright install chromiumPLAYWRIGHT_BROWSERS_PATH=0 pnpm exec playwright test e2e/jam.spec.ts --grep newsletter
- If a dev server is already running, prefer reusing it; otherwise Playwright may try to start another
pnpm run devand hitEMFILE(too many file watchers).
- To push the current commit to staging, run
pnpm run push:stage. - Do not use
flyctl deployfor staging unless explicitly requested.