diff --git a/.agents/skills/webjs/SKILL.md b/.agents/skills/webjs/SKILL.md index 29108b07f..c0c865aca 100644 --- a/.agents/skills/webjs/SKILL.md +++ b/.agents/skills/webjs/SKILL.md @@ -104,12 +104,12 @@ Common bundles: 1. **Classify the change.** Route contract, data model, server mutation, auth, or only UI? 2. **Start from the server.** Add the page/route and its server action or query before wiring interactive UI. A page render or a `
` POST should already return correct HTML before any component hydrates. 3. **Put code in the narrowest owner.** Route-local first (`modules//`), promote to `lib/` or `components/` only when reuse is real. -4. **Keep server-only code behind `.server.ts`.** The DB driver, secrets, and `node:*` never belong in a page, layout, or component. +4. **Keep server-only code behind `.server.ts`** (Core WebJs Rules 1 and 2). 5. **Add interactivity per behaviour.** Reach for a component (and a signal or `@event`) only where the UI is genuinely interactive. A display-only component is elided from the browser. Then wrap the interactive part and STOP: the static markup around it stays in the page, where it costs nothing. 6. **Validate input at the boundary.** Declare `export const validate` on an action; the RPC and `route()` boundaries run it. 7. **Default mutations to optimistic UI** where the client can predict the result (`optimistic()` from `@webjsdev/core`). -8. **Type every boundary from its source, never `unknown` or `any`.** The row type comes from the schema (`typeof todos.$inferSelect`), the action's input from a named `interface` and its result from `ActionResult`, the routing files from `PageProps` / `LayoutProps` / `RouteHandlerContext`. `unknown` belongs on a payload nothing has vouched for yet that the next line narrows, and on a parameter of your own helper that forwards into an `html` template hole. Everywhere else, including a layout's `children`, it is a missing type. See `references/typescript.md`. -9. **Test the narrowest meaningful layer**, and render the app in a real browser for any UI change (static checks do not catch a collapsed layout). +8. **Type every boundary from its source, never `unknown` or `any`.** The row type comes from the schema (`typeof todos.$inferSelect`), the action's input from a named `interface` and its result from `ActionResult`, the routing files from `PageProps` / `LayoutProps` / `RouteHandlerContext`. `unknown` belongs on a payload nothing has vouched for yet that the next line narrows, and on a parameter of your own helper that forwards into an `html` template hole. Everywhere else, including a layout's `children`, it is a missing type. Carry a row type into a shipping component with `import type` (erased before the browser sees it). Nothing enforces this (both spellings are valid TypeScript, so `webjs check` and `tsc` pass either way), which is why it is written down. See `references/typescript.md`. +9. **Test the narrowest meaningful layer**, and look at any UI change in a real browser (see Testing Defaults). ## Project Layout @@ -137,7 +137,7 @@ App-internal imports use the `#` root alias (`import { db } from '#db/connection ## Core WebJs Rules (invariants) 1. Server-only code lives in `.server.ts`, `route.ts`, or `middleware.ts`. Never in a page, layout, or component (it crashes the browser at module load). -2. `'use server'` exports are `async` functions returning serializer-safe values. Files without `'use server'` are server-only utilities. +2. `'use server'` exports are `async` functions returning serializer-safe values, callable from browser code as RPC. Files without `'use server'` are server-only utilities: reach them only from `'use server'` actions, `route.ts`, or middleware. Never add `'use server'` to a file only other server code imports (the DB connection, the schema). 3. Custom element tag names contain a hyphen. Pass the tag to `Class.register('tag-name')`. 4. Event (`@`), property (`.`), and boolean (`?`) holes in `html` are UNQUOTED: `@click=${fn}`, never `@click="${fn}"`. 5. Signals are the default state primitive. Import `signal` / `computed` from `@webjsdev/core`, read via `signal.get()` inside `render()`. The base-class factory `WebComponent({ ... })` is only for values riding an HTML attribute or arriving via SSR hydration. @@ -147,7 +147,7 @@ App-internal imports use the `#` root alias (`import { db } from '#db/connection 9. No backtick characters inside an `html\`...\`` body, even in comments (it closes the literal and 500s). 10. TypeScript must be erasable (`erasableSyntaxOnly: true`): no `enum`, no value `namespace`, no constructor parameter properties, no legacy decorators. 11. Reactive properties are declared ONLY through the base-class factory `extends WebComponent({ count: Number })`. Never a `static properties` block, never a class-field initializer (it clobbers the reactive accessor). -12. A form that writes binds its action: ``, or a per-button `