SKILL.md
SKILL.mdBrowse 12 files
6,524 tokens
27,677 bytes
Token encoding: o200k_base
Snapshot 8beb6cd
1---2name: remix3description:4 Build and review Remix 3 applications using the `remix` npm package and5 subpath imports. Use when working on Remix app structure, routes, controllers,6 middleware, validation, data access, auth, sessions, file uploads, server7 setup, UI components, hydration, navigation, or tests.8---9 10# Build a Remix App11 12Use this skill for end-to-end Remix app work. It should help the agent choose13the right layer first, reach for the right package, and avoid the most common14Remix-specific mistakes.15 16## What Remix Is17 18Remix 3 is a server-first web framework built on Web APIs such as `Request`,19`Response`, `URL`, and `FormData`. All packages ship from a single npm package,20`remix`, and are imported via subpath. There is no top-level `remix` import.21 22A Remix app has four main pieces:23 24- **Routes** in `app/routes.ts` define the typed URL contract and power `href()`25 generation.26- **Controllers and actions** implement that contract and return `Response`27 objects.28- **Middleware** composes request lifecycle behavior and populates typed context29 via `context.set(Key, value)`.30- **Components** render UI with `remix/ui`. This is not React. A component31 receives a `handle`, reads current props from `handle.props`, and returns a32 render function.33 34## When To Use This Skill35 36Use this skill for:37 38- new features or refactors that touch routing, controllers, middleware, data,39 auth, sessions, UI, or tests40- reviewing Remix app code for correctness, architecture, or framework usage41- answering "how should this be structured in Remix?" questions42- finding the right package, reference doc, or default pattern for a task43 44## Load Only The References You Need45 46Classify the task first, then load the smallest useful reference set. Each47reference file starts with a "What This Covers" section that lists the topics48inside it — read that first to confirm the file is relevant before reading the49rest.50 51Use the table below to find candidates. Loading more than two or three files at52once is usually a sign that the task hasn't been narrowed enough yet.53 54| Task involves... | Start with |55| ----------------------------------------------------------------------------- | ------------------------------------------- |56| Defining URLs, writing controllers and actions, returning responses | `references/routing-and-controllers.md` |57| Composing the request lifecycle, ordering middleware, bridging to a server | `references/middleware-and-server.md` |58| Compiling and serving browser modules, asset URL namespaces, preloads | `references/assets-and-browser-modules.md` |59| Parsing input, validating with schemas, defining tables, querying, migrations | `references/data-and-validation.md` |60| Per-browser state, login flows, route protection, identity | `references/auth-and-sessions.md` |61| Component setup, state, lifecycle, updates, `queueTask`, context | `references/component-model.md` |62| Event handlers, styles, refs, click/key behavior, simple animations | `references/mixins-styling-events.md` |63| `clientEntry`, `run`, `<Frame>`, navigation, `<head>` | `references/hydration-frames-navigation.md` |64| Router tests, component tests, test isolation | `references/testing-patterns.md` |65| Spring physics, tweens, layout transitions | `references/animate-elements.md` |66| Authoring custom reusable mixins | `references/create-mixins.md` |67 68Common bundles:69 70- **Form or CRUD feature** -> routing, data and validation, testing; add auth if71 user-specific72- **Protected area** -> auth and sessions, routing, testing73- **Interactive widget** -> component model, mixins and styling; add hydration74 only if it runs in the browser75- **Browser asset pipeline** -> assets and browser modules, hydration,76 middleware and server77- **File upload** -> middleware and server, data and validation, testing78- **Navigation or frames** -> hydration, frames, navigation79 80## Default Workflow81 821. **Classify the change.** Decide whether it changes the route contract,83 request lifecycle, data model, auth or session behavior, or only UI.842. **Start from the server contract.** Add or update `app/routes.ts` before85 wiring handlers or UI.863. **Put code in the narrowest owner.** Favor route-local code first, then87 promote only when reuse is real.884. **Make the server path correct before adding browser behavior.** A route89 should return the right `Response` via `router.fetch(...)` before you add90 `clientEntry(...)`, animations, or DOM effects.915. **Add middleware deliberately.** Keep fast-exit middleware early and92 request-enriching middleware later. Export a typed `AppContext` from the root93 middleware stack and use it in controllers.946. **Validate input at the boundary.** Parse and validate `Request`, `FormData`,95 params, cookies, and external payloads before they reach rendering or96 persistence logic.977. **Hydrate only when necessary.** Prefer server-rendered UI. Use98 `clientEntry(...)` and `run(...)` only for real browser interactivity or99 browser-only APIs.1008. **Test the narrowest meaningful layer.** Prefer router tests for route101 behavior. Use component tests when the behavior is truly interactive or102 DOM-specific.1039. **Finish with verification.** Re-read the route flow, confirm auth and104 authorization boundaries, and run the smallest relevant test and typecheck105 loop.106 107## Project Layout108 109Use these root directories consistently:110 111- `app/` for runtime application code112- `db/` for migrations and local database files113- `public/` for static assets served as-is114- `test/` for shared helpers, fixtures, and integration coverage115- `tmp/` for uploads, caches, local session files, and other scratch data116 117Inside `app/`, organize by responsibility:118 119- `assets/` for client entrypoints and client-owned browser behavior120- `controllers/` for route-owned handlers and route-local UI121- `data/` for schema, queries, persistence setup, migrations, and runtime data122 initialization123- `middleware/` for request lifecycle concerns such as auth, sessions, uploads,124 and database injection125- `ui/` for shared cross-route UI primitives126- `utils/` only for genuinely cross-layer helpers that do not clearly belong127 elsewhere128- `routes.ts` for the route contract129- `router.ts` for router setup and wiring130 131### Placement Precedence132 133When code could live in multiple places:134 1351. Put it in the narrowest owner first.1362. If it belongs to one route, keep it with that route.1373. If it is shared UI across route areas, move it to `app/ui/`.1384. If it is request lifecycle setup, keep it in `app/middleware/`.1395. If it is schema, query, persistence, or startup data logic, keep it in140 `app/data/`.1416. Use `app/utils/` only as a last resort for truly cross-layer helpers.142 143### Route Ownership144 145- Use a flat file in `app/controllers/` for a simple leaf action, such as146 `app/controllers/home.tsx`147- Use a folder with `controller.tsx` when a route owns nested routes or multiple148 actions, such as `app/controllers/account/controller.tsx`149- Mirror nested route structure on disk, such as150 `app/controllers/auth/login/controller.tsx`151- Keep route-local UI next to its owner, such as152 `app/controllers/contact/page.tsx`153- Move shared UI to `app/ui/`154- If a flat leaf grows child routes or multiple actions, promote it to a155 controller folder156 157### Layout Anti-Patterns158 159- Do not create `app/lib/` as a generic dumping ground160- Do not create `app/components/` as a second shared UI bucket when `app/ui/`161 already owns that role162- Do not put shared cross-route UI in `app/controllers/`163- Do not put middleware or persistence helpers in `app/utils/` when they have a164 clearer home165- Do not create folders for simple leaf actions unless they are real controllers166 167## Core Remix Rules168 169- Import from `remix/<subpath>`, never `import { ... } from 'remix'`170- Treat `app/routes.ts` as the source of truth for URLs. Use171 `routes.<name>.href(...)` for redirects, links, tests, and internal URL172 construction173- Controllers and actions should return explicit `Response` objects, including174 redirects, 404s, and validation failures. At the route boundary, prefer175 returning a `Response` for expected outcomes (validation errors, conflicts,176 not found) over throwing for control flow177- Model HTTP behavior explicitly. Status codes, headers, redirects, cache rules,178 and content types are part of the route contract179- Make the server route correct first. A POST should already return the right180 HTML, redirect, or error response on its own before `clientEntry(...)` layers181 interactivity on top182- Validate input at the boundary using `remix/data-schema` (and183 `remix/data-schema/form-data` for forms). `parseSafe` makes the failure path a184 return value instead of an exception185- Derive `AppContext` from the root middleware stack so `get(Database)`,186 `get(Session)`, `get(Auth)`, and similar keys stay typed. If the controller187 never reads from context, it doesn't need the harness188- Outside actions and controllers, only use `getContext()` when `asyncContext()`189 is in the middleware stack190- Remix Component is not React: read props from `handle.props`, keep state in191 setup-scope variables, call `handle.update()` explicitly, and do DOM-sensitive192 work in event handlers or `queueTask(...)`, not in render193- Prefer host-element mixins via `mix={mixin(...)}` for behavior and styling194 instead of inventing custom host prop conventions. Use `mix={[...]}` only when195 composing multiple mixins196- Hydrated `clientEntry(...)` props must be serializable. Do not pass functions,197 class instances, or opaque runtime objects198 199## Security And Session Defaults200 201- Never ship demo secrets. In non-test environments, require session and202 provider secrets from the environment and fail fast if they are missing203- Use hardened cookies: `httpOnly` always, `sameSite` by default, and `secure`204 when serving over HTTPS205- Regenerate session IDs on login, logout, and privilege changes206- Use `requireAuth()` to protect authenticated route areas, but still authorize207 resource ownership inside handlers and data writes208- Add CSRF protection when browser forms mutate state using cookie-backed209 sessions210- Add CORS only for endpoints that must be called cross-origin. Prefer211 same-origin by default212- Prefer JSX or `remix/html-template` for HTML generation so escaping stays213 correct214- Validate uploads for size, type, and destination. Treat filenames and content215 as untrusted input216 217## Testing Defaults218 219- Prefer server and router tests first. Drive the app with220 `router.fetch(new Request(...))` and assert on the returned `Response`221- Build a fresh router per test or per suite so sessions, in-memory storage, and222 database state stay isolated223- Use `routes.<name>.href(...)` in tests so URLs stay coupled to the route224 contract225- For auth or session scenarios, use a test cookie and226 `createMemorySessionStorage()` instead of production storage227- Use component tests only for interactive or DOM-specific behavior. Render with228 `createRoot(...)`, interact with the real DOM, and call `root.flush()` between229 steps230- Prefer one representative behavior test over many repetitive assertion231 variants232 233## Common Mistakes To Avoid234 235- Treating Remix Component like React and reaching for hooks or implicit236 rerendering237- Importing from a top-level `remix` entry instead of a subpath238- Adding `clientEntry(...)` before the server-rendered route behavior is correct239- Passing non-serializable props into `clientEntry(...)`240- Calling `getContext()` without `asyncContext()` in the middleware stack241- Getting middleware order wrong; fast exits like static files belong early,242 request enrichment later243- Skipping boundary validation and trusting raw `FormData`, params, cookies, or244 external payloads245- Letting route-local domain errors leak out of the controller. Translate246 expected outcomes (validation, conflicts, not-found) into the HTTP `Response`247 the route means to return rather than throwing a custom `Error` subclass and248 catching it elsewhere249- Reaching for `createCookie` when a tamper-sensitive or server-managed250 per-browser fact really wants `remix/session`. If editing the value would be a251 bug, use a session252- Building a JSON-only RPC layer when a normal form POST, redirect, or resource253 route would be simpler. Fetch-from-the-client is a layer on top of sound route254 behavior, not a replacement for it255- Treating JSON state endpoints and `<Frame>` reloads as mutually exclusive256 patterns. Pick the lightest sync mechanism that fits the UX; small widgets may257 reasonably poll a JSON endpoint258- Assuming authentication is enough without per-resource authorization checks259- Dropping shared code into vague buckets like `utils.ts`, `helpers.ts`, or260 `common.ts` when ownership is known261- Writing only component tests for a feature whose main behavior is really an262 HTTP route concern263 264## Package Map265 266Use this map to find the right package quickly. Each entry says what the package267is for, not just what it exports. Open the linked reference file when you need268full examples.269 270### Routing, Server, and Responses271 272- `remix/router` — the router itself. Use for `createRouter`, controller and273 middleware types, and registering routes. A URL that matches a route pattern274 but not the request method gets `405 Method Not Allowed` with an `Allow`275 header (it does not reach `defaultHandler`); register an `ANY` route for a276 per-URL catch-all. `GET` routes also serve `HEAD` with the same status and277 headers and an empty body — do not add explicit `HEAD` routes just for that278- `remix/routes` — declarative route builders. Use for `route`, `get`, `post`,279 `put`, `del`, `form`, `resources` when defining `app/routes.ts`280- `remix/node-fetch-server` — adapter from Node's `http` module to a Fetch-style281 router. Use for `createRequestListener` in `server.ts`282- `remix/assets` — browser asset server. Use for `createAssetServer` when283 serving compiled scripts and styles. Scripts resolve imports through import284 maps: read `getScriptEntry()` and render `<ImportMap value={importMap} />`285 (from `remix/ui/server`) before the preloads and module script; use286 `fingerprint: true` (content hashes) plus `files.cacheKey` for persistent287 caches, and directory `mounts` (default `{ app, npm }`). Shared compiler288 options such as `target`, `sourceMaps`, `sourceMapSourcePaths`, and `minify`289 live at the top level. Kody's origin website does not use this: hydration URLs290 come from Pitlane `?assets=`291- `remix/multiple-import-maps-polyfill` — `importModule()`,292 `detectMultipleImportMapSupport()`, `preloadShim()` for browsers without293 native multiple-import-map support. Only relevant with `remix/assets`294- `remix/middleware/render` — pairs `createAssetServer` with frame rendering for295 Node apps. Do not use it on the origin Worker; Kody owns `renderToStream` plus296 Workers Assets297- `remix/spa` — client-only `run(router)` for apps with no server document. Kody298 keeps a custom client router on top of server-rendered HTML: it runs the299 destination's loader before committing the URL, and route components read the300 payload through `createRouteData` (`#client/route-data.tsx`) so the previous301 page stays until the next one is ready — see302 `docs/contributing/no-flash-navigation.md` before adding a client route303- `remix/headers` — typed header parsers and builders. Use when reading304 `Accept`, `Cookie`, or setting `CacheControl`, `Vary`, etc., instead of305 hand-formatting strings306- `remix/response/redirect` — `redirect(href, status?)`. Use for the canonical307 "POST then redirect" pattern and other location changes308- `remix/response/html` — `createHtmlResponse`. Use when you need an HTML309 `Response` from a string or stream without rendering through `remix/ui`310- `remix/response/compress` — `compressResponse`. Use when compressing one-off311 responses outside the global `compression()` middleware312- `remix/response/file` — file-download responses. Use for313 `Content-Disposition: attachment` responses314- `remix/route-pattern` — low-level URL matching and generation. Use when315 working with raw patterns outside the router (custom matchers, scripts)316- `remix/fetch-proxy` — Fetch-based HTTP proxying. Use to forward a request to317 another origin; pass `xForwardedHeaders` when the upstream needs forwarded318 proto, host, and port319 320### Data, Validation, and Persistence321 322- `remix/data-schema` — schema builders for runtime validation. Use for `parse`323 and `parseSafe` to validate any input that crosses a trust boundary, and324 `.transform(...)` when validated output should map to a different value or325 type326- `remix/data-schema/checks` — common check helpers (`email`, `minLength`,327 `maxLength`, etc.). Use to compose into a schema328- `remix/data-schema/coerce` — coercion helpers for strings, numbers, booleans,329 dates, and ids. Use when input arrives as a string but should be a typed value330- `remix/data-schema/form-data` — `f.object` and `f.field` for parsing331 `FormData` directly. Use in actions that read browser forms332- `remix/data-table` — typed tables and a `Database` interface. Use for `table`,333 `column`, and `new Database(driver)` when modeling persisted data334- `remix/data-table/sqlite`, `remix/data-table/postgres`,335 `remix/data-table/mysql` — official drivers. Use `createSqliteDatabase(...)`336 for Node, Bun, and compatible synchronous SQLite clients. Remix does not ship337 a D1 factory; Kody owns `D1DatabaseDriver` and passes it to `new Database`338- `remix/data-table/migrations` — migration authoring and runners. Use for339 `createMigration`, `createMigrationRunner`340- `remix/data-table/migrations/node` — `loadMigrations` from disk. Use in341 startup scripts that apply migrations342- `remix/data-table/operators` — query operators such as `inList(...)`. Use when343 `where` clauses need set or comparison logic344 345### Auth, Sessions, and Cookies346 347- `remix/session` — the `Session` object: `get`, `set`, `flash`, `unset`,348 `regenerateId`. Use for any per-browser state where tampering would be a bug349 (login, "I submitted this form already", cart, flash messages)350- `remix/middleware/session` — `session(cookie, storage)`. Use to wire a session351 cookie and storage backend into the root middleware stack352- `remix/session-storage/fs`, `remix/session-storage/memory`,353 `remix/session-storage/cookie` — storage backends. Use `fs-storage` for354 single-process apps, `memory-storage` for tests, `cookie-storage` for355 stateless deployments where data fits in a cookie356- `remix/session-storage/redis` — Redis-backed storage. Use for multi-process or357 multi-host deployments358- `remix/session-storage/memcache` — Memcache-backed storage. Same multi-host359 use case as Redis360- `remix/cookie` — `createCookie` for plain signed/unsigned cookies. Use for361 non-sensitive preferences where the client is allowed to control the value362 (theme, locale, dismissed banner). For state where tampering matters, prefer363 `remix/session`364- `remix/auth` — credentials, OAuth, and OIDC providers. Use to define how365 identity is verified, start/finish external login, and refresh stored366 OAuth/OIDC token bundles with `refreshExternalAuth(...)`. Use `OAuthTokens`.367 There is no built-in Atmosphere/atproto provider.368- `remix/middleware/auth` — `auth({ schemes })`, `requireAuth`, the `Auth`369 context key. Use to resolve identity into the request context and to gate370 routes371 372### UI, Hydration, and Browser Behavior373 374- `remix/ui` — the component runtime: components, core mixins, `clientEntry`,375 `run`, `<Frame>`, navigation helpers, and `createRoot`. Use for app UI376 behavior. Framework attributes are `data-rmx-*` (`data-rmx-target`,377 `data-rmx-document`, `data-rmx-history`, …). Listen with native378 `target.addEventListener(type, listener, { signal })`; there is no379 `addEventListeners()` helper. Server-rendered `<script>` elements must have a380 single string child (or stay empty with `src`); non-string children render381 empty and error. `handle.update()` during setup warns and is skipped — move it382 to an event handler or `handle.queueTask()`. Optional `run({ resolveFrame })`383 defaults to fetching the frame source as HTML and rendering `3xx`/`4xx` HTML384 responses in the frame; Kody keeps a custom resolver for the frame registry,385 prefetch cache, and retries but mirrors that status acceptance. `run()` falls386 back to document navigation when the Navigation API is missing, so no387 `window.navigation` stub is needed.388- `remix/ui/server` — server rendering: `renderToStream`, `renderToString`. Use389 in the `render(...)` helper that returns HTML responses390- `remix/ui/animation` — animation APIs: `animateEntrance`, `animateExit`,391 `animateLayout`, `spring`, `tween`, and `easings`392- `remix/ui/<primitive>` — UI primitives, mixins, glyphs, and theme helpers.393 Import from `remix/ui/accordion`, `remix/ui/button`, `remix/ui/select`, etc.394- `remix/ui/test` — component test rendering helpers such as `render`395- `remix/ui/jsx-runtime` — JSX transform target. Configured in `tsconfig.json`,396 rarely imported directly397- `remix/html-template` — escaped HTML template literals. Use when generating398 HTML outside the component system (RSS feeds, email bodies, error pages)399- `remix/file-storage` — backend-agnostic `File` storage interface. Use as the400 type bound for upload destinations401- `remix/file-storage/fs`, `remix/file-storage/memory`, `remix/file-storage-s3`402 — storage backends. Use to implement an upload destination403 404### Middleware405 406- `remix/middleware/static` — `staticFiles(dir)`. Use to serve files from407 `public/` exactly as they exist on disk408- `remix/middleware/form-data` — `formData()`. Use to parse `FormData` once and409 expose it via `get(FormData)` instead of calling `await request.formData()` in410 each action411- `remix/form-data-parser` — lower-level `parseFormData`, `FileUpload`. Use when412 implementing custom upload handlers. Upload handler errors propagate directly413- `remix/multipart-parser` and `remix/multipart-parser/node` — low-level414 multipart stream parsing. `MultipartPart.headers` is a plain object keyed by415 lower-case header name; read values with bracket notation such as416 `part.headers['content-type']`417- `remix/middleware/compression` — `compression()`. Use globally for text-like418 responses419- `remix/middleware/logger` — `logger()`. Use in development for request logs;420 pass `colors` to force terminal color output on or off421- `remix/middleware/method-override` — `methodOverride()`. Use when HTML forms422 need `PUT`, `PATCH`, or `DELETE`423- `remix/middleware/async-context` — `asyncContext()`, `getContext()`. Use when424 helpers outside actions need request context without threading it through425 every call426- `remix/middleware/cors` — `cors(opts?)`. Use for endpoints called cross-origin427- `remix/middleware/csrf` — `csrf(opts?)`. Use when session-backed forms mutate428 state and need synchronizer-token CSRF protection429- `remix/middleware/cop` — cross-origin protection. Use to reject unsafe430 cross-origin browser requests431 432### Test433 434- `remix/test` — `describe`, `it`, and lifecycle hooks. Use as the test435 framework436- `remix/test/cli` — programmatic test runner APIs such as `runRemixTest`437- `remix/cli` — programmatic Remix CLI API. Use the `remix` executable for438 project commands such as `remix test`, `remix routes`, and `remix doctor`439- `remix/assert` — assertion helpers. Use in place of `node:assert` so messages440 render cleanly in the runner441- `remix/terminal` — ANSI styles, color detection, style factories, and testable442 terminal streams. Use for CLIs and terminal output instead of hand-rolled443 escape sequences444 445## Canonical Patterns446 447### Define routes first448 449```typescript450import { form, get, post, resources, route } from 'remix/routes'451 452export const routes = route({453 home: '/',454 contact: form('contact'),455 books: {456 index: '/books',457 show: '/books/:slug',458 },459 auth: route('auth', {460 login: form('login'),461 logout: post('logout'),462 }),463 admin: route('admin', {464 index: get('/'),465 books: resources('books', { param: 'bookId' }),466 }),467})468```469 470### Type controllers against the route contract471 472```typescript473import type { Controller } from 'remix/router'474 475import type { AppContext } from '../router.ts'476import { routes } from '../routes.ts'477 478export default {479 actions: {480 async index({ get }) {481 let db = get(Database)482 let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] })483 return render(<BooksIndexPage allBooks={allBooks} />)484 },485 async show({ get, params }) {486 let db = get(Database)487 let book = await db.findOne(books, { where: { slug: params.slug } })488 if (!book) return new Response('Not Found', { status: 404 })489 return render(<BookShowPage book={book} />)490 },491 },492} satisfies Controller<typeof routes.books, AppContext>493```494 495### Compose middleware deliberately496 497```typescript498import {499 createRouter,500 type AnyParams,501 type MiddlewareContext,502 type WithParams,503} from 'remix/router'504 505export type RootMiddleware = [506 ReturnType<typeof formData>,507 ReturnType<typeof session>,508 ReturnType<typeof loadDatabase>,509 ReturnType<typeof loadAuth>,510]511 512export type AppContext<params extends AnyParams = AnyParams> = WithParams<513 MiddlewareContext<RootMiddleware>,514 params515>516 517let middleware = []518 519if (process.env.NODE_ENV === 'development') {520 middleware.push(logger())521}522 523middleware.push(compression())524middleware.push(staticFiles('./public'))525middleware.push(formData())526middleware.push(methodOverride())527middleware.push(session(cookie, storage))528middleware.push(asyncContext())529middleware.push(loadDatabase())530middleware.push(loadAuth())531 532let router = createRouter({ middleware })533```534 535### Mutate, validate, and respond536 537```typescript538import { redirect } from 'remix/response/redirect'539import * as s from 'remix/data-schema'540import * as f from 'remix/data-schema/form-data'541import { Session } from 'remix/session'542import { Database } from 'remix/data-table'543 544let bookSchema = f.object({545 slug: f.field(s.string()),546 title: f.field(s.string()),547})548 549export default {550 actions: {551 async create({ get }) {552 let parsed = s.parseSafe(bookSchema, get(FormData))553 if (!parsed.success) {554 return render(<NewBookPage errors={parsed.issues} />, { status: 400 })555 }556 557 let db = get(Database)558 let book = await db.create(books, parsed.value)559 560 let session = get(Session)561 session.flash('message', `Added ${book.title}.`)562 563 return redirect(routes.books.show.href({ slug: book.slug }))564 },565 },566} satisfies Controller<typeof routes.books, AppContext>567```568 569This shape works without JavaScript, returns a `Response` for every outcome, and570is ready for `clientEntry(...)` interactivity when the UI needs it.571 572### Build UI from handle props plus render573 574```tsx575import { on, type Handle } from 'remix/ui'576 577function Counter(handle: Handle<{ initialCount?: number; label: string }>) {578 let count = handle.props.initialCount ?? 0579 580 return () => (581 <button582 mix={on('click', () => {583 count++584 handle.update()585 })}586 >587 {handle.props.label}: {count}588 </button>589 )590}591```592 593Only add `clientEntry(...)` and `run(...)` when the component needs browser594interactivity or browser-only APIs.595 Referenced from AGENTS.md
These references come from AGENTS.md at the skill snapshot.
AGENTS.md · same revision ↗
Source excerpt starting at line 43.43 - [docs/contributing/remix.md](./docs/contributing/remix.md) and the44 repo-local [Remix skill](./.agents/skills/remix/SKILL.md)45 - [docs/contributing/no-flash-navigation.md](./docs/contributing/no-flash-navigation.md)