@webjsdev/cli 0.10.30 → 0.10.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bin/webjs.js +1 -1
  2. package/lib/api-gallery.js +229 -0
  3. package/lib/create.js +462 -161
  4. package/lib/saas-template.js +39 -15
  5. package/package.json +1 -1
  6. package/templates/.agents/rules/workflow.md +78 -1
  7. package/templates/.claude/hooks/check-server-imports.mjs +86 -0
  8. package/templates/.claude/hooks/check-server-imports.sh +26 -0
  9. package/templates/.claude/hooks/cleanup-merged-worktree.sh +129 -0
  10. package/templates/.claude/hooks/commit-before-stop.sh +52 -0
  11. package/templates/.claude/settings.json +28 -0
  12. package/templates/.cursorrules +50 -1
  13. package/templates/.github/copilot-instructions.md +50 -1
  14. package/templates/AGENTS.md +180 -13
  15. package/templates/CLAUDE.md +22 -0
  16. package/templates/CONVENTIONS.md +165 -12
  17. package/templates/gallery/app/examples/todo/page.ts +34 -0
  18. package/templates/gallery/app/features/async-render/page.ts +14 -0
  19. package/templates/gallery/app/features/broadcast/feed/route.ts +19 -0
  20. package/templates/gallery/app/features/broadcast/page.ts +24 -0
  21. package/templates/gallery/app/features/caching/page.ts +39 -0
  22. package/templates/gallery/app/features/client-router/page.ts +34 -0
  23. package/templates/gallery/app/features/client-router/second/page.ts +20 -0
  24. package/templates/gallery/app/features/components/page.ts +14 -0
  25. package/templates/gallery/app/features/directives/page.ts +14 -0
  26. package/templates/gallery/app/features/env/page.ts +36 -0
  27. package/templates/gallery/app/features/file-storage/file/[key]/route.ts +19 -0
  28. package/templates/gallery/app/features/file-storage/page.ts +62 -0
  29. package/templates/gallery/app/features/forms/page.ts +78 -0
  30. package/templates/gallery/app/features/metadata/page.ts +55 -0
  31. package/templates/gallery/app/features/optimistic-ui/page.ts +14 -0
  32. package/templates/gallery/app/features/rate-limit/page.ts +29 -0
  33. package/templates/gallery/app/features/rate-limit/ping/middleware.ts +8 -0
  34. package/templates/gallery/app/features/rate-limit/ping/route.ts +7 -0
  35. package/templates/gallery/app/features/route-handler/data/route.ts +8 -0
  36. package/templates/gallery/app/features/route-handler/page.ts +13 -0
  37. package/templates/gallery/app/features/routing/[id]/page.ts +25 -0
  38. package/templates/gallery/app/features/routing/page.ts +47 -0
  39. package/templates/gallery/app/features/server-actions/page.ts +14 -0
  40. package/templates/gallery/app/features/service-worker/page.ts +36 -0
  41. package/templates/gallery/app/features/websockets/echo/route.ts +19 -0
  42. package/templates/gallery/app/features/websockets/page.ts +25 -0
  43. package/templates/gallery/modules/async-render/components/server-clock.ts +26 -0
  44. package/templates/gallery/modules/async-render/queries/server-greeting.server.ts +9 -0
  45. package/templates/gallery/modules/broadcast/components/broadcast-feed.ts +61 -0
  46. package/templates/gallery/modules/components/components/browser/counter-card.test.js +36 -0
  47. package/templates/gallery/modules/components/components/counter-card.ts +35 -0
  48. package/templates/gallery/modules/directives/components/directive-demo.ts +53 -0
  49. package/templates/gallery/modules/file-storage/actions/store-upload.server.ts +18 -0
  50. package/templates/gallery/modules/optimistic-ui/actions/like-post.server.ts +9 -0
  51. package/templates/gallery/modules/optimistic-ui/components/like-button.ts +35 -0
  52. package/templates/gallery/modules/rate-limit/components/rate-probe.ts +49 -0
  53. package/templates/gallery/modules/server-actions/actions/greet.server.ts +12 -0
  54. package/templates/gallery/modules/server-actions/components/greeter.ts +30 -0
  55. package/templates/gallery/modules/server-actions/utils/format.server.ts +8 -0
  56. package/templates/gallery/modules/todo/actions/create-todo.server.ts +16 -0
  57. package/templates/gallery/modules/todo/actions/delete-todo.server.ts +15 -0
  58. package/templates/gallery/modules/todo/actions/toggle-todo.server.ts +21 -0
  59. package/templates/gallery/modules/todo/components/todo-app.ts +140 -0
  60. package/templates/gallery/modules/todo/queries/list-todos.server.ts +25 -0
  61. package/templates/gallery/modules/todo/types.ts +12 -0
  62. package/templates/gallery/modules/websockets/components/ws-echo.ts +62 -0
  63. package/templates/lib/utils/ui.ts +4 -4
  64. package/templates/test/hello/browser/hello.test.js +6 -0
  65. package/templates/web-test-runner.config.js +9 -1
@@ -3,7 +3,7 @@
3
3
  You are working on a webjs app, an AI-first, no-build, web-components-first
4
4
  framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
5
5
  project-specific conventions before writing any code. When AGENTS.md doesn't
6
- cover what you need, the full hosted docs are at **https://docs.webjs.com**.
6
+ cover what you need, the full hosted docs are at **https://docs.webjs.dev**.
7
7
 
8
8
  ## Persistence + scaffold rules (non-negotiable)
9
9
 
@@ -18,6 +18,51 @@ cover what you need, the full hosted docs are at **https://docs.webjs.com**.
18
18
  `app/page.ts`, the example `User` model, the example users module, etc.
19
19
  with the app the user actually asked for. Don't ship "Hello from
20
20
  <app-name>" as the deliverable.
21
+ - **Study the gallery first, prune it second.** The full-stack and saas scaffolds ship
22
+ single-feature demos under `app/features/` plus one whole example app under
23
+ `app/examples/` (logic in `modules/`). It is your PRIMARY webjs reference:
24
+ read every demo end to end (code AND comments) to learn the idioms BEFORE you
25
+ write or delete anything. Only after internalising the patterns should you
26
+ prune, keeping and adapting what you need and deleting the rest (the
27
+ `app/features/<name>` or `app/examples/<name>` route AND its `modules/<name>`;
28
+ for the todo app, also the `todos` table). Never delete blindly up front. Each
29
+ route page has a `webjs-scaffold-placeholder` marker so `webjs check` fails
30
+ until you resolve it. Delete now-empty directories after pruning. The `api`
31
+ template ships a BACKEND-features showcase instead (endpoints under
32
+ `app/api/features/`: the `route()` adapter + validation, rate limiting,
33
+ streaming, file storage, WebSockets + broadcast, plus `env.ts` validation),
34
+ listed in the root `app/route.ts` index; prune it the same way.
35
+ - **Prune what the app does not use.** Keep the infrastructure the app
36
+ USES and delete the rest (files AND folders). No persistence means
37
+ delete `db/`, `drizzle.config.ts`, and the `db:*` scripts. No UI kit
38
+ used means delete `components/ui/`, `components.json`, and
39
+ `lib/utils/cn.ts`. No PWA means delete `public/sw.js` and
40
+ `offline.html`. Always KEEP the durable knowledge (`AGENTS.md`,
41
+ `CONVENTIONS.md`, the rule files, the MCP), never prune it, so removing
42
+ example code never removes your context. Prune AFTER using the features
43
+ and examples as reference, never blindly up front. A no-op for the `api` template
44
+ (no UI kit, no PWA files).
45
+ - **`app/` is routing-only.** Only routing files live in `app/` (page,
46
+ layout, route, middleware, metadata routes). CSS, helpers, and constants
47
+ do NOT: `globals.css` is at `styles/`, browser-safe helpers at
48
+ `lib/utils/`, feature logic in `modules/`.
49
+ - **Use a unique design, and redesign means more than recolor (UI apps).** Give
50
+ the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
51
+ from what the app IS. Recoloring the scaffold and swapping the logo while
52
+ keeping its skeleton (a fixed top header with a Home link and a theme toggle,
53
+ the centered ~760px reading column, the "Built with webjs" footer) is NOT a
54
+ unique design. Decide from scratch whether this app even needs a header or
55
+ footer, what nav (if any), and what layout fits (a centered board, a
56
+ full-bleed dashboard, a split, a single card). The scaffold ships a
57
+ `webjs-scaffold-placeholder` marker on its footer, so `webjs check` fails until
58
+ you remove or replace the "Built with webjs" branding. Self-audit before
59
+ finishing: nothing should read as the scaffold example (no "Built with webjs"
60
+ footer, no leftover example nav, no default reading column unless it truly
61
+ fits). Keep only the design TOKENS and theme wiring in `app/layout.ts`
62
+ (infrastructure the ui kit reads) and restyle on top. Style with Tailwind
63
+ utilities wherever they reach, and use custom CSS only for what utilities
64
+ cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix or
65
+ gradients). The `api` template has no UI, so this does not apply there.
21
66
  - **Only three templates exist:** `webjs create <name>` (default
22
67
  full-stack), `--template api`, `--template saas`. The CLI rejects any
23
68
  other `--template` value. Pick:
@@ -110,13 +155,17 @@ self-review loop.
110
155
  - No build step: source files are served as ES modules
111
156
  - **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback.
112
157
  - **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css`) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag.
158
+ - **One theme, canonical tokens.** The app has a SINGLE theme, defined once in `app/layout.ts` using the standard `@webjsdev/ui` (shadcn-compatible) semantic tokens set to the brand palette. Use the canonical utility names everywhere, in the page chrome AND inside components: `bg-background`, `text-foreground`, `bg-card`, `bg-muted`, `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-accent`, `text-accent-foreground`, `border-border`, `ring-ring`. These are exactly the tokens a component copied in by `webjs ui add <name>` reads, so a scaffolded page and a later-added ui component share one theme with no wiring. NEVER invent a parallel token vocabulary (`--fg`, `--bg`, `text-fg`, `bg-elev`, a separate `--brand`): it collides with the ui tokens (the accent once flipped to neutral on navigation for exactly this reason) and diverges from the shadcn conventions the kit and AI agents expect. Reach for opacity modifiers (`bg-primary/10`, `hover:bg-primary/90`) before adding a token; add one the canonical way (a `--x` var plus a `--color-x: var(--x)` line in `@theme inline`).
113
159
  - Shadow-DOM components opt in with `static shadow = true` and use `static styles = css` for scoped CSS, not inline styles. That is the right home for scoped CSS.
114
160
  - One function per server action file (*.server.ts)
161
+ - **`.server.ts` vs `'use server'`, the decision.** Will the client call it? Add `'use server'` and the file becomes an RPC action (browser import rewritten to a typed stub). Is it server-only infra instead (a DB driver, secrets, node:*)? Use NO directive, and NEVER import it into a page/layout/component. Reach it from a `'use server'` action, `route.ts`, or `middleware.ts`. A `.server.ts` WITHOUT the directive is a server-only utility whose browser import throws at module load.
162
+ - **Label every interactive control.** Give each control an accessible name, and make clickable text a `<label for="control-id">` (or the control itself) so a text click activates the control on BOTH the JS path and the no-JS form-submit path. Use `aria-label` and `aria-pressed` on icon-only controls. `assertNoA11yViolations(el)` in a browser test catches missing labels.
115
163
  - Components must call customElements.define('tag', Class)
116
164
  - **Never extend raw HTMLElement directly for app components.** Always subclass `WebComponent` (or the factory form `WebComponent({...})`) to hook into SSR, lifecycle, elision, and the reactive property system. Extend raw HTMLElement only for rare native-API edge cases (like form-associated `ElementInternals` or customized built-in elements), and add a `webjs-allow-htmlelement: <reason>` comment to acknowledge the exception.
117
165
  - Server-only code (the DB driver `pg`, node:*, anything that needs Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap the access in a .server.{js,ts} file; the framework rewrites that import into an RPC stub for the browser. The DB lives in db/*.server.ts (db/connection.server.ts); lib/ holds other server-only infra and browser-safe utilities (lib/utils/cn.ts with cn); follow the same rule per file: if a lib/ file needs Node APIs, only import it from server-only files. A TYPE-ONLY `import type { Todo } from '#db/schema.server.ts'` is the exception, fine in a page or component because the stripper erases it before it reaches the browser.
118
166
  - Directives: webjs exports the lit directives with no clean native equivalent (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `cache`, `until`, `ref` / `createRef`, `templateContent`, `asyncAppend` / `asyncReplace`, `watch`). Lit's `classMap` / `styleMap` / `ifDefined` / `when` / `choose` are NOT exported. For those, use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
119
167
  - **Progressive enhancement is the default.** Pages AND every web component are SSR'd to real HTML. Write components so the first paint is the right content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback` is never called on the server, so anything there only runs after hydration. Initial data for components comes from the page function (server-side fetch plus pass as attribute/property) OR from an `async render()` in the component itself (`const u = await getUser(this.uid)`, which SSR awaits so the data is in the first paint), NOT from `fetch` calls in `connectedCallback`. Prefer the co-located `async render()` over prop-drilling; `renderFallback()` is the optional re-fetch loading state (never first paint), and a `Task` is for genuinely client-only data. For write-paths, prefer `<form action=...>` plus server action over `fetch` plus click handler. The framework upgrades plain forms to partial-swap submissions automatically.
168
+ - **Default to optimistic UI for feasible mutations.** Use `optimistic()` from `@webjsdev/core` for create/toggle/like/reorder so the UI updates instantly and rolls back on failure (no hand-written try-catch or temp-id bookkeeping). Do NOT use it where it hurts: unpredictable or server-computed results, side-effectful or OAuth/payment mutations, and destructive irreversible actions (confirm-first instead).
120
169
  - **Client navigation is auto-magic.** Real `<a href>` and `<form action>` get partial-swap behavior with no opt-in. Because layouts persist across navigation, put shared chrome (sidenav, header) in `layout.ts` and page-specific content in `page.ts`. For validation errors, return 4xx HTML from a `route.ts` POST handler; the router renders it in place preserving the user's input. For non-layout swap regions, wrap in `<webjs-frame id="...">`. See "Client navigation patterns" in AGENTS.md.
121
170
  - **Keep pages and layouts as pure carriers** so their modules stay out of the network tab. A page/layout never hydrates; the framework drops its module from the browser as long as its only browser job is registering the components it imports. It starts shipping its own module (invisible in tests, an elision verdict) the moment its closure does any OTHER client work. Don't give a page/layout module-scope client work (a top-level call, a window/document/customElements access, a bare side-effect import, or a @webjsdev/core/client-router import: routing is automatic) or import a client-global-touching non-component util into it. Put client behaviour in a component, server-only code in .server.{js,ts}. Self-check: page.ts/layout.ts should not appear in the browser's network tab.
122
171
  - See AGENTS.md for the complete directive decision guide
@@ -3,7 +3,7 @@
3
3
  You are working on a webjs app, an AI-first, no-build, web-components-first
4
4
  framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
5
5
  project-specific conventions. When AGENTS.md doesn't cover what you need,
6
- the full hosted docs are at **https://docs.webjs.com**.
6
+ the full hosted docs are at **https://docs.webjs.dev**.
7
7
 
8
8
  ## Persistence + scaffold rules (non-negotiable)
9
9
 
@@ -18,6 +18,51 @@ the full hosted docs are at **https://docs.webjs.com**.
18
18
  `app/page.ts`, the example `User` model, the example users module, etc.
19
19
  with the app the user actually asked for. Don't ship "Hello from
20
20
  <app-name>" as the deliverable.
21
+ - **Study the gallery first, prune it second.** The full-stack and saas scaffolds ship
22
+ single-feature demos under `app/features/` plus one whole example app under
23
+ `app/examples/` (logic in `modules/`). It is your PRIMARY webjs reference:
24
+ read every demo end to end (code AND comments) to learn the idioms BEFORE you
25
+ write or delete anything. Only after internalising the patterns should you
26
+ prune, keeping and adapting what you need and deleting the rest (the
27
+ `app/features/<name>` or `app/examples/<name>` route AND its `modules/<name>`;
28
+ for the todo app, also the `todos` table). Never delete blindly up front. Each
29
+ route page has a `webjs-scaffold-placeholder` marker so `webjs check` fails
30
+ until you resolve it. Delete now-empty directories after pruning. The `api`
31
+ template ships a BACKEND-features showcase instead (endpoints under
32
+ `app/api/features/`: the `route()` adapter + validation, rate limiting,
33
+ streaming, file storage, WebSockets + broadcast, plus `env.ts` validation),
34
+ listed in the root `app/route.ts` index; prune it the same way.
35
+ - **Prune what the app does not use.** Keep the infrastructure the app
36
+ USES and delete the rest (files AND folders). No persistence means
37
+ delete `db/`, `drizzle.config.ts`, and the `db:*` scripts. No UI kit
38
+ used means delete `components/ui/`, `components.json`, and
39
+ `lib/utils/cn.ts`. No PWA means delete `public/sw.js` and
40
+ `offline.html`. Always KEEP the durable knowledge (`AGENTS.md`,
41
+ `CONVENTIONS.md`, the rule files, the MCP), never prune it, so removing
42
+ example code never removes your context. Prune AFTER using the features
43
+ and examples as reference, never blindly up front. A no-op for the `api` template
44
+ (no UI kit, no PWA files).
45
+ - **`app/` is routing-only.** Only routing files live in `app/` (page,
46
+ layout, route, middleware, metadata routes). CSS, helpers, and constants
47
+ do NOT: `globals.css` is at `styles/`, browser-safe helpers at
48
+ `lib/utils/`, feature logic in `modules/`.
49
+ - **Use a unique design, and redesign means more than recolor (UI apps).** Give
50
+ the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
51
+ from what the app IS. Recoloring the scaffold and swapping the logo while
52
+ keeping its skeleton (a fixed top header with a Home link and a theme toggle,
53
+ the centered ~760px reading column, the "Built with webjs" footer) is NOT a
54
+ unique design. Decide from scratch whether this app even needs a header or
55
+ footer, what nav (if any), and what layout fits (a centered board, a
56
+ full-bleed dashboard, a split, a single card). The scaffold ships a
57
+ `webjs-scaffold-placeholder` marker on its footer, so `webjs check` fails until
58
+ you remove or replace the "Built with webjs" branding. Self-audit before
59
+ finishing: nothing should read as the scaffold example (no "Built with webjs"
60
+ footer, no leftover example nav, no default reading column unless it truly
61
+ fits). Keep only the design TOKENS and theme wiring in `app/layout.ts`
62
+ (infrastructure the ui kit reads) and restyle on top. Style with Tailwind
63
+ utilities wherever they reach, and use custom CSS only for what utilities
64
+ cannot express (@theme tokens, @keyframes, scrollbar, complex color-mix or
65
+ gradients). The `api` template has no UI, so this does not apply there.
21
66
  - **Only three templates exist:** `webjs create <name>` (default
22
67
  full-stack), `--template api`, `--template saas`. The CLI rejects any
23
68
  other `--template` value. Pick:
@@ -107,9 +152,13 @@ each change must include.
107
152
  - **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback.
108
153
  - Tagged template: html`<div>${value}</div>` with css`...` for styles.
109
154
  - **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css\`...\``) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag. Shadow-DOM components (`static shadow = true`) legitimately author `static styles = css\`...\`` for scoped CSS; don't use inline `style="..."` there.
155
+ - **One theme, canonical tokens.** The app has a SINGLE theme, defined once in `app/layout.ts` using the standard `@webjsdev/ui` (shadcn-compatible) semantic tokens set to the brand palette. Use the canonical utility names everywhere, in the page chrome AND inside components: `bg-background`, `text-foreground`, `bg-card`, `bg-muted`, `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-accent`, `text-accent-foreground`, `border-border`, `ring-ring`. These are exactly the tokens a component copied in by `webjs ui add <name>` reads, so a scaffolded page and a later-added ui component share one theme with no wiring. NEVER invent a parallel token vocabulary (`--fg`, `--bg`, `text-fg`, `bg-elev`, a separate `--brand`): it collides with the ui tokens (the accent once flipped to neutral on navigation for exactly this reason) and diverges from the shadcn conventions the kit and AI agents expect. Reach for opacity modifiers (`bg-primary/10`, `hover:bg-primary/90`) before adding a token; add one the canonical way (a `--x` var plus a `--color-x: var(--x)` line in `@theme inline`).
110
156
  - Components: extend the `WebComponent({ ... })` factory to declare reactive properties (e.g. `extends WebComponent({ count: Number })`; per-prop options via `prop(Number, { reflect: true })`), add `static styles` for shadow-DOM components, call `Class.register('tag-name')` at the bottom of the file. The tag name is the argument to `.register()`, not a static field. A hand-written `static properties` throws at construction (`no-static-properties`); set defaults in the constructor, never a class-field initializer (`reactive-props-no-class-field`). Declare an array-typed prop with the `Array` constructor, not `Object` (`items: prop<Tag[]>(Array)`), flagged by `array-prop-uses-array-type`. **Never extend raw HTMLElement directly for app components.** Always subclass `WebComponent` (or the factory form `WebComponent({...})`) to hook into SSR, lifecycle, elision, and the reactive property system. Extend raw HTMLElement only for rare native-API edge cases (like form-associated `ElementInternals` or customized built-in elements), and add a `webjs-allow-htmlelement: <reason>` comment to acknowledge the exception.
111
157
  - Async data in a component: prefer an `async render()` (`const u = await getUser(this.uid)`), which SSR awaits so the data is in the first paint, over prop-drilling from the page or fetching in `connectedCallback`. `renderFallback()` is the optional re-fetch loading state (never first paint); error isolation is automatic (`renderError()` customizes it); a `Task` is for genuinely client-only data.
158
+ - **Default to optimistic UI for feasible mutations.** Use `optimistic()` from `@webjsdev/core` for create/toggle/like/reorder so the UI updates instantly and rolls back on failure (no hand-written try-catch or temp-id bookkeeping). Do NOT use it where it hurts: unpredictable or server-computed results, side-effectful or OAuth/payment mutations, and destructive irreversible actions (confirm-first instead).
112
159
  - Server actions: *.server.ts files with one exported async function each.
160
+ - **`.server.ts` vs `'use server'`, the decision.** Will the client call it? Add `'use server'` and the file becomes an RPC action (browser import rewritten to a typed stub). Is it server-only infra instead (a DB driver, secrets, node:*)? Use NO directive, and NEVER import it into a page/layout/component. Reach it from a `'use server'` action, route.ts, or middleware.ts. A `.server.ts` WITHOUT the directive is a server-only utility whose browser import throws at module load.
161
+ - **Label every interactive control.** Give each control an accessible name, and make clickable text a `<label for="control-id">` (or the control itself) so a text click activates the control on BOTH the JS path and the no-JS form-submit path. Use `aria-label` and `aria-pressed` on icon-only controls. `assertNoA11yViolations(el)` in a browser test catches missing labels.
113
162
  - Server-only code (a DB driver like pg, node:*, anything needing Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap in a .server.{js,ts} file; the framework rewrites that import to an RPC stub for the browser. The DB lives in db/*.server.ts; lib/ holds other server-only infra and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file. A TYPE-ONLY `import type { Todo } from '#db/schema.server.ts'` is the exception, fine in a page or component because the stripper erases it before it reaches the browser.
114
163
  - Directives: webjs exports the lit directives with no clean native equivalent (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `cache`, `until`, `ref` / `createRef`, `templateContent`, `asyncAppend` / `asyncReplace`, `watch`). Lit's `classMap` / `styleMap` / `ifDefined` / `when` / `choose` are NOT exported; use plain template-literal expressions instead.
115
164
  - Context: import { createContext, ContextProvider, ContextConsumer } from '@webjsdev/core/context'
@@ -4,8 +4,8 @@ Read this before editing any file. This is a webjs app: AI-first, web-
4
4
  components-first, no build step. The framework's own full API reference
5
5
  lives at https://github.com/webjsdev/webjs/blob/main/AGENTS.md and the
6
6
  full hosted documentation (every API, recipe, and example) lives at
7
- **https://docs.webjs.com**. Treat this file as the app-scoped
8
- companion and reach for docs.webjs.com whenever you need more detail.
7
+ **https://docs.webjs.dev**. Treat this file as the app-scoped
8
+ companion and reach for docs.webjs.dev whenever you need more detail.
9
9
 
10
10
  ## If you just scaffolded this app (AI agents, read first)
11
11
 
@@ -20,7 +20,22 @@ example `Home` nav, and pick a content-width container that fits. The
20
20
  default `<main class="max-w-[760px]">` is a reading column for prose and
21
21
  forms, so for a full-bleed app, dashboard, or board, widen the cap or
22
22
  remove it (keep the theme tokens). A wide layout left in the 760px
23
- reading column overflows into a horizontal scrollbar. This is ENFORCED:
23
+ reading column overflows into a horizontal scrollbar. **Give the app a
24
+ unique design, and redesign means more than recolor.** When it has a UI,
25
+ choose its palette, typography, LAYOUT, and chrome from what the app IS.
26
+ Recoloring the scaffold and swapping the logo while keeping its skeleton (a
27
+ fixed top header with a Home link and a theme toggle, the centered ~760px
28
+ reading column, the "Built with webjs" footer) is NOT a unique design.
29
+ Decide from scratch whether this app even needs a header or footer, what nav
30
+ (if any), and what layout fits (a centered board, a full-bleed dashboard, a
31
+ split, a single card). Before finishing, self-audit that nothing still reads
32
+ as the scaffold example (no "Built with webjs" footer, no leftover example
33
+ nav, no default reading column unless it truly fits). The `api` template has
34
+ no UI, so this does not apply there. The design tokens and theme wiring are
35
+ infrastructure to keep and restyle on top of. Style with Tailwind utilities
36
+ wherever they reach, and use custom CSS only for what utilities cannot
37
+ express (@theme tokens, @keyframes, scrollbar, complex color-mix or
38
+ gradients). This is ENFORCED:
24
39
  the example `app/page.ts` and `app/layout.ts` carry a
25
40
  `webjs-scaffold-placeholder` marker comment, and `webjs check` fails
26
41
  while any marker remains, so this freshly scaffolded app fails the check
@@ -51,6 +66,17 @@ user asked for, never leftover scaffold code.
51
66
  app's real domain models (delete the example `User` model unless the
52
67
  app actually needs users), run `webjs db generate` then
53
68
  `webjs db migrate`, then build pages / actions / queries against them.
69
+ 4. **Prune what the app does not use.** The scaffold is reference, so keep
70
+ the infrastructure the app USES and delete the rest, both files AND
71
+ folders. No persistence means delete `db/`, `drizzle.config.ts`, and
72
+ the `db:*` scripts. No UI kit used means delete `components/ui/`,
73
+ `components.json`, and `lib/utils/cn.ts`. No PWA means delete
74
+ `public/sw.js` and `offline.html`. Always KEEP the durable knowledge
75
+ (`AGENTS.md`, `CONVENTIONS.md`, the per-agent rule files, the MCP
76
+ wiring), never prune it, so removing example code never removes your
77
+ context. Prune AFTER you have used the features and examples as reference, never
78
+ blindly up front. This is a no-op for the `api` template (no UI kit,
79
+ no PWA files).
54
80
 
55
81
  **Picking the right scaffold from the user's prompt** (you do this BEFORE
56
82
  running `webjs create`; if you're reading this you've already scaffolded.
@@ -171,7 +197,7 @@ entry, its own template parser. Inside `` html`…` `` templates you get:
171
197
  In VS Code / Cursor / Windsurf, the **`webjs` extension** bundles this
172
198
  automatically (no `tsconfig.json` edit, no separate Lit extension).
173
199
 
174
- See [docs.webjs.com → Editor setup](https://docs.webjs.com/docs/editor-setup)
200
+ See [docs.webjs.dev → Editor setup](https://docs.webjs.dev/docs/editor-setup)
175
201
  for the full walkthrough.
176
202
 
177
203
  **Config validation in `package.json`.** The scaffold ships
@@ -296,11 +322,32 @@ wrapper adds zero value and obscures the real element from inspection,
296
322
  form submission, and screen readers. Custom elements are reserved for
297
323
  behavior the browser can't deliver natively.
298
324
 
325
+ ### Accessible control labeling
326
+
327
+ Give every interactive control an accessible name, and make clickable
328
+ text a `<label for="control-id">` (or the control itself) so a text click
329
+ activates the control on BOTH the JS path and the no-JS form-submit path.
330
+ Use `aria-label` and `aria-pressed` on icon-only controls (a toggle
331
+ button, an icon-only close/menu button). A native `<input>` under a
332
+ `<label>` gets this for free, which is another reason to reach for the
333
+ Tier-1 class helpers on real elements. In a browser test,
334
+ `assertNoA11yViolations(el)` from `@webjsdev/core/testing` catches
335
+ missing labels.
336
+
299
337
  ## File conventions
300
338
 
301
339
  ```
302
- app/ thin route adapters (import from modules/)
303
- page.ts → /
340
+ app/ ROUTING ONLY: thin route adapters (import from modules/).
341
+ No CSS, helpers, or constants here; those live in
342
+ styles/, lib/utils/, and modules/. globals.css is at
343
+ styles/, NOT app/.
344
+ page.ts → / (the scaffold home links to the gallery)
345
+ features/<name>/ single-feature demos (routing, components,
346
+ server-actions, optimistic-ui, async-render,
347
+ directives, route-handler, forms, metadata, caching,
348
+ env, client-router, service-worker); prune what you skip
349
+ examples/<name>/ whole example apps that compose features (todo);
350
+ prune what you skip
304
351
  layout.ts root layout, wraps every page
305
352
  error.ts error boundary (render failures → user-friendly)
306
353
  loading.ts Suspense fallback for sibling page
@@ -324,6 +371,8 @@ modules/<feature>/
324
371
  types.ts feature types
325
372
  lib/
326
373
  ... cross-cutting infra (session, auth config, etc.)
374
+ styles/
375
+ globals.css @webjsdev/ui theme tokens (NOT in app/; app/ is routing-only)
327
376
  db/
328
377
  schema.server.ts Drizzle models + relations (your data layer)
329
378
  columns.server.ts column helpers (dialect-specific; the only file to swap for Postgres)
@@ -332,15 +381,54 @@ db/
332
381
  dev.db SQLite file (gitignored); created when migrations apply (\`dev\`/\`start\` run \`webjs db migrate\`)
333
382
  migrations/ generated migration SQL (committed)
334
383
  drizzle.config.ts drizzle-kit config (root; SQLite by default, --db postgres to switch)
335
- public/ static assets, served at /public/*
384
+ public/ static assets at /public/* (favicon, sw.js, offline.html serve at root)
336
385
  test/<feature>/ feature-scoped tests, one folder per concern
337
386
  <name>.test.ts node unit / integration test (node --test)
338
- browser/<name>.test.js real-browser test (web-test-runner)
387
+ browser/<name>.test.js real-browser test (web-test-runner); may ALSO be
388
+ co-located next to a component, e.g.
389
+ modules/<feature>/components/browser/<name>.test.js
390
+ (see the gallery counter-card test for the idioms:
391
+ suite/test, ssrFixture, inline assert, no chai)
339
392
  e2e/<name>.test.ts end-to-end test (full app boot, opt in via WEBJS_E2E=1)
340
393
  smoke/<name>.test.ts fast post-deploy sanity check
341
394
  middleware.ts root middleware (optional, outermost)
342
395
  ```
343
396
 
397
+ ### The gallery (reference content, prune it)
398
+
399
+ The scaffold ships a gallery organized by KIND, so features and whole apps are
400
+ not mixed:
401
+ - `app/features/<name>/` are single-feature demos, one webjs concept each
402
+ (routing, components, server-actions, optimistic-ui, async-render, directives,
403
+ route-handler, forms, metadata, caching, env, client-router, service-worker,
404
+ plus the infra demos websockets, file-storage, rate-limit, broadcast).
405
+ - `app/examples/<name>/` are whole example apps that compose several features
406
+ (todo: optimistic UI + progressive enhancement + a11y + db + modules).
407
+
408
+ Both keep their logic in `modules/<name>/`. Each route is small, idiomatic, and
409
+ heavily commented, and the gallery is your PRIMARY reference for how webjs works.
410
+
411
+ **Study the whole gallery FIRST, prune SECOND.** Before you write or delete
412
+ anything, read every feature demo and the example app end to end (the code AND
413
+ the comments) to absorb the idioms you will reuse: the modules split, signals,
414
+ the `optimistic()` API, `async render()`, the `.server.ts` vs `'use server'`
415
+ boundary, progressive-enhancement forms, `<label for>` a11y, dynamic routes, and
416
+ `route.ts` handlers. Only AFTER you have internalised the patterns should you
417
+ prune. Never delete the examples blindly up front (that throws away your context
418
+ before you have read it), and never prune the durable knowledge surfaces
419
+ (`AGENTS.md`, `CONVENTIONS.md`, the per-agent rule files), which stay as context
420
+ for every future iteration.
421
+
422
+ Then prune: the examples are REFERENCE, not your app, so keep and adapt the ones
423
+ you need and delete the rest. Pruning a route means deleting its
424
+ `app/features/<name>` or `app/examples/<name>` folder AND its `modules/<name>`
425
+ folder (and, for the todo app, the `todos` table in `db/schema.server.ts`), then
426
+ removing its link from `app/page.ts`. Each route page carries a
427
+ `webjs-scaffold-placeholder` marker so `webjs check` fails until you have
428
+ consciously kept-and-adapted or pruned it. After pruning, delete any now-empty
429
+ directories (an empty `lib/utils/` or `modules/<name>/` is leftover scaffolding,
430
+ not structure).
431
+
344
432
  ### Typed page / layout / route-handler props
345
433
 
346
434
  Type page / layout / route-handler arguments with the exported helpers so a
@@ -517,7 +605,7 @@ git commit -m "vendor + download dayjs"
517
605
  Bundle files land in `.webjs/vendor/<pkg>@<version>.js`. importmap
518
606
  points at local `/__webjs/vendor/` paths. Browser fetches from your
519
607
  own origin. Suitable for `script-src 'self'` CSP, air-gapped deploys,
520
- or compliance environments. See [docs.webjs.com Deployment → CSP](https://docs.webjs.com/docs/deployment#csp).
608
+ or compliance environments. See [docs.webjs.dev Deployment → CSP](https://docs.webjs.dev/docs/deployment#csp).
521
609
 
522
610
  **Other CLI commands:**
523
611
 
@@ -593,7 +681,7 @@ const url = process.env.WEBJS_PUBLIC_API_URL; // works
593
681
  const secret = process.env.AUTH_SECRET; // undefined (fail-closed)
594
682
  ```
595
683
 
596
- `process.env.NODE_ENV` is also defined in the browser (`'development'` in `webjs dev`, `'production'` in `webjs start`), so vendor bundles that probe it work without setup. Full docs: [Configuration](https://docs.webjs.com/docs/configuration).
684
+ `process.env.NODE_ENV` is also defined in the browser (`'development'` in `webjs dev`, `'production'` in `webjs start`), so vendor bundles that probe it work without setup. Full docs: [Configuration](https://docs.webjs.dev/docs/configuration).
597
685
 
598
686
  ## Component pattern
599
687
 
@@ -751,6 +839,31 @@ globally. Prefer Tailwind. When a utility bundle repeats, extract it into
751
839
  a `lib/utils/ui.ts` helper returning an `` html`...` `` fragment, not a
752
840
  CSS class.
753
841
 
842
+ #### Design tokens: ONE theme, shadcn-canonical
843
+
844
+ The app has a SINGLE theme, defined once in `app/layout.ts`. It uses the
845
+ standard `@webjsdev/ui` (shadcn-compatible) semantic tokens, set to this app's
846
+ brand palette. Use the canonical utility names everywhere, in the page chrome
847
+ AND inside components: `bg-background`, `text-foreground`, `bg-card`, `bg-muted`,
848
+ `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-accent`,
849
+ `text-accent-foreground`, `border-border`, `ring-ring`. These are exactly the
850
+ tokens the components copied in by `webjs ui add <name>` read, so a scaffolded
851
+ page and a later-added ui component share one coherent theme automatically.
852
+
853
+ - **Never invent a parallel token vocabulary** (`--fg`, `--bg`, `text-fg`,
854
+ `bg-elev`, a separate `--brand`). It collides with the ui tokens (the accent
855
+ once flipped to neutral on navigation for exactly this reason) and diverges
856
+ from the shadcn conventions the ui kit and AI agents both expect.
857
+ - **Reach for opacity modifiers before a new token**: `bg-primary/10` for a
858
+ tint, `hover:bg-primary/90` for a hover, `text-muted-foreground/70` for a
859
+ subtler text level.
860
+ - **Edit the palette in one place** (`app/layout.ts`). To ADD a token, do it
861
+ the canonical way: a `--x` variable in the `:root` / `.dark` blocks plus a
862
+ `--color-x: var(--x)` line in the `@theme inline` block, then use it as
863
+ `bg-x` / `text-x`.
864
+ - Dark mode is a `.dark` class the theme toggle sets. Tokens switch by theme
865
+ automatically, so a component written with these names works in both.
866
+
754
867
  Reserve raw CSS for what utilities cannot express: design-token `:root` /
755
868
  `@theme` definitions, `@property` + `@keyframes` animations,
756
869
  `::-webkit-scrollbar`, `prefers-reduced-motion` blocks, and complex
@@ -761,6 +874,16 @@ legitimately use `static styles = css\`\`` for scoped CSS.
761
874
 
762
875
  ## Server action pattern
763
876
 
877
+ **The `.server.ts` vs `'use server'` decision, in one question.** Will the
878
+ client call it? Add `'use server'` and the file becomes an RPC action
879
+ (the browser import is rewritten to a typed stub). Is it server-only
880
+ infra instead (a DB driver, secrets, `node:*`)? Use NO directive, and
881
+ never import it into a page, layout, or component. Reach it from a
882
+ `'use server'` action, a `route.ts` handler, or `middleware.ts`. A
883
+ `.server.ts` file WITHOUT the directive is a server-only utility whose
884
+ browser import throws at module load (invariant 2 below), so a
885
+ page/component that imports it directly crashes on the client.
886
+
764
887
  ```ts
765
888
  // modules/posts/actions/create-post.server.ts
766
889
  'use server';
@@ -788,7 +911,43 @@ that RETURNS a `ReadableStream` / async generator streams its chunks (consume
788
911
  with `for await`); read the request `AbortSignal` via `actionSignal()` to cancel
789
912
  on disconnect. **SAFETY:** a `cache` with `public: true` shares one response
790
913
  across all users, so use it only for data identical for every visitor. Full
791
- reference: https://docs.webjs.com/docs/server-actions
914
+ reference: https://docs.webjs.dev/docs/server-actions
915
+
916
+ ## Mutations: default to optimistic UI
917
+
918
+ Default to optimistic UI for every feasible mutation. Use `optimistic()`
919
+ from `@webjsdev/core` (create, toggle, like, follow, reorder, rename,
920
+ status change) so the UI updates instantly and rolls back automatically
921
+ on failure. The declarative form queues an update on a component with
922
+ auto-release when the action promise settles, no hand-written try-catch,
923
+ cache-and-restore, or temp-id bookkeeping.
924
+
925
+ ```ts
926
+ import { WebComponent, prop, optimistic, html } from '@webjsdev/core';
927
+ import { createTodo } from '#modules/todos/actions/create-todo.server.ts';
928
+
929
+ class TodoList extends WebComponent({ todos: prop<Todo[]>(Array) }) {
930
+ private optimisticTodos = optimistic(this, {
931
+ source: () => this.todos,
932
+ update: (state, title: string) => [...state, { title, pending: true }],
933
+ });
934
+ async handleSubmit(title: string) {
935
+ const promise = createTodo({ title });
936
+ this.optimisticTodos.add(title, promise); // auto-releases on settle
937
+ await promise;
938
+ }
939
+ render() {
940
+ return html`<ul>${this.optimisticTodos.value.map(t => html`
941
+ <li class=${t.pending ? 'opacity-50' : ''}>${t.title}</li>`)}</ul>`;
942
+ }
943
+ }
944
+ ```
945
+
946
+ Do NOT use optimistic UI where it hurts: unpredictable or server-computed
947
+ results (AI output, server-assigned values the client cannot guess),
948
+ side-effectful mutations the user must wait on (payment, email, OAuth),
949
+ and destructive irreversible actions (a confirm-first UX is better). Full
950
+ reference: https://docs.webjs.com/docs/optimistic-ui
792
951
 
793
952
  ## Client navigation patterns (auto-magic)
794
953
 
@@ -1258,8 +1417,10 @@ composition, so a nested shell ends up dropped by the HTML parser.
1258
1417
  3. Commit and push **per logical unit**, not at the end. A logical unit is one
1259
1418
  feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
1260
1419
  spanning different concerns, commit the current group before continuing.
1261
- The framework ships a `nudge-uncommitted` hook for several agents that
1262
- fires at threshold 4:
1420
+ For Claude Code, its `CLAUDE.md` explicitly OVERRIDES Claude Code's built-in
1421
+ never-commit default, so it commits per unit without waiting to be asked. The
1422
+ framework also ships a `nudge-uncommitted` hook for several agents that fires
1423
+ at threshold 4:
1263
1424
 
1264
1425
  | Agent | Hook path | Doc |
1265
1426
  |---|---|---|
@@ -1270,6 +1431,12 @@ composition, so a nested shell ends up dropped by the HTML parser.
1270
1431
  | Antigravity (Google) | text rule only (post-write hooks not yet exposed) | `.agents/rules/workflow.md` |
1271
1432
  | GitHub Copilot | text rule only (no hooks API) | `.github/copilot-instructions.md` |
1272
1433
 
1434
+ Claude Code adds two more backstops of its own. A `commit-before-stop.sh`
1435
+ Stop hook refuses to end a turn with a pile of uncommitted work on a feature
1436
+ branch (loop-safe, disable with `WEBJS_NO_COMMIT_STOP=1`), and a
1437
+ `cleanup-merged-worktree.sh` PostToolUse hook removes a merged branch's
1438
+ worktree after a `gh pr merge`.
1439
+
1273
1440
  The `.hooks/pre-commit` hook blocks commits to main and nothing else;
1274
1441
  `webjs test` + `webjs check` run in CI (`.github/workflows/ci.yml`) on
1275
1442
  every PR and push to main, regardless of which agent (or human) made
@@ -1,2 +1,24 @@
1
1
  @AGENTS.md
2
2
  @CONVENTIONS.md
3
+
4
+ # Committing per logical unit (this OVERRIDES Claude Code's default)
5
+
6
+ Claude Code's built-in default is to NEVER commit unless the user explicitly
7
+ asks. **For this project that default does NOT apply.** Commit and push per
8
+ logical unit (one feature, one fix, one rename, one doc rewrite) as soon as it
9
+ is complete, WITHOUT being asked. Do not save all the work for one commit at the
10
+ end. A finished implementation with zero commits is a mistake here, because git
11
+ history is the user's revert and cherry-pick safety net.
12
+
13
+ - After each completed unit whose tests pass, `git add` the related files and
14
+ `git commit` with an imperative subject under 72 chars, then push. If 5+ files
15
+ span more than one concern, you already waited too long.
16
+ - Never commit to `main`. Work on a feature branch (the
17
+ `.claude/hooks/guard-branch-context.sh` hook enforces this).
18
+ - No AI-attribution trailers (`Co-Authored-By`, `Generated by`).
19
+
20
+ See AGENTS.md "Git workflow" for the full contract. Two hooks back this up: the
21
+ `.claude/hooks/nudge-uncommitted.sh` PostToolUse hook reminds you while
22
+ uncommitted changes pile up during work, and the
23
+ `.claude/hooks/commit-before-stop.sh` Stop hook stops you from ending a turn
24
+ with a pile of uncommitted work still on a feature branch.