From be268f1b19e3e629ccce4db10d683a61bb4a80ac Mon Sep 17 00:00:00 2001 From: t Date: Thu, 8 Oct 2026 10:04:53 +0530 Subject: [PATCH 1/2] docs: state each agent rule once and point to it The scaffold's agent docs restated the same rules (typing, the .server.ts boundary, the data rule, the ci gate list, the form-binding rules, the git contract) across AGENTS.md, CONVENTIONS.md, workflow.md, CLAUDE.md, both playbooks and SKILL.md. Every build agent reads all of them, so each copy was paid for on every run. Each rule now lives in one canonical place and the others point to it; the two facts that existed only in the scaffold AGENTS.md (no 'use server' on server-only infra, nothing enforces the typing rule) moved into the skill. Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo --- .agents/skills/webjs/SKILL.md | 20 +++------ .../cli/templates/.agents/rules/workflow.md | 30 +++---------- packages/cli/templates/AGENTS.md | 45 +++---------------- packages/cli/templates/CLAUDE.md | 11 ++--- packages/cli/templates/CONVENTIONS.md | 42 +++++------------ .../templates/partials/agents-playbook-api.md | 19 +++----- .../partials/agents-playbook-fullstack.md | 27 ++++------- 7 files changed, 46 insertions(+), 148 deletions(-) diff --git a/.agents/skills/webjs/SKILL.md b/.agents/skills/webjs/SKILL.md index cd92824bd..297e0e15d 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 `