@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.
- package/bin/webjs.js +1 -1
- package/lib/api-gallery.js +229 -0
- package/lib/create.js +462 -161
- package/lib/saas-template.js +39 -15
- package/package.json +1 -1
- package/templates/.agents/rules/workflow.md +78 -1
- package/templates/.claude/hooks/check-server-imports.mjs +86 -0
- package/templates/.claude/hooks/check-server-imports.sh +26 -0
- package/templates/.claude/hooks/cleanup-merged-worktree.sh +129 -0
- package/templates/.claude/hooks/commit-before-stop.sh +52 -0
- package/templates/.claude/settings.json +28 -0
- package/templates/.cursorrules +50 -1
- package/templates/.github/copilot-instructions.md +50 -1
- package/templates/AGENTS.md +180 -13
- package/templates/CLAUDE.md +22 -0
- package/templates/CONVENTIONS.md +165 -12
- package/templates/gallery/app/examples/todo/page.ts +34 -0
- package/templates/gallery/app/features/async-render/page.ts +14 -0
- package/templates/gallery/app/features/broadcast/feed/route.ts +19 -0
- package/templates/gallery/app/features/broadcast/page.ts +24 -0
- package/templates/gallery/app/features/caching/page.ts +39 -0
- package/templates/gallery/app/features/client-router/page.ts +34 -0
- package/templates/gallery/app/features/client-router/second/page.ts +20 -0
- package/templates/gallery/app/features/components/page.ts +14 -0
- package/templates/gallery/app/features/directives/page.ts +14 -0
- package/templates/gallery/app/features/env/page.ts +36 -0
- package/templates/gallery/app/features/file-storage/file/[key]/route.ts +19 -0
- package/templates/gallery/app/features/file-storage/page.ts +62 -0
- package/templates/gallery/app/features/forms/page.ts +78 -0
- package/templates/gallery/app/features/metadata/page.ts +55 -0
- package/templates/gallery/app/features/optimistic-ui/page.ts +14 -0
- package/templates/gallery/app/features/rate-limit/page.ts +29 -0
- package/templates/gallery/app/features/rate-limit/ping/middleware.ts +8 -0
- package/templates/gallery/app/features/rate-limit/ping/route.ts +7 -0
- package/templates/gallery/app/features/route-handler/data/route.ts +8 -0
- package/templates/gallery/app/features/route-handler/page.ts +13 -0
- package/templates/gallery/app/features/routing/[id]/page.ts +25 -0
- package/templates/gallery/app/features/routing/page.ts +47 -0
- package/templates/gallery/app/features/server-actions/page.ts +14 -0
- package/templates/gallery/app/features/service-worker/page.ts +36 -0
- package/templates/gallery/app/features/websockets/echo/route.ts +19 -0
- package/templates/gallery/app/features/websockets/page.ts +25 -0
- package/templates/gallery/modules/async-render/components/server-clock.ts +26 -0
- package/templates/gallery/modules/async-render/queries/server-greeting.server.ts +9 -0
- package/templates/gallery/modules/broadcast/components/broadcast-feed.ts +61 -0
- package/templates/gallery/modules/components/components/browser/counter-card.test.js +36 -0
- package/templates/gallery/modules/components/components/counter-card.ts +35 -0
- package/templates/gallery/modules/directives/components/directive-demo.ts +53 -0
- package/templates/gallery/modules/file-storage/actions/store-upload.server.ts +18 -0
- package/templates/gallery/modules/optimistic-ui/actions/like-post.server.ts +9 -0
- package/templates/gallery/modules/optimistic-ui/components/like-button.ts +35 -0
- package/templates/gallery/modules/rate-limit/components/rate-probe.ts +49 -0
- package/templates/gallery/modules/server-actions/actions/greet.server.ts +12 -0
- package/templates/gallery/modules/server-actions/components/greeter.ts +30 -0
- package/templates/gallery/modules/server-actions/utils/format.server.ts +8 -0
- package/templates/gallery/modules/todo/actions/create-todo.server.ts +16 -0
- package/templates/gallery/modules/todo/actions/delete-todo.server.ts +15 -0
- package/templates/gallery/modules/todo/actions/toggle-todo.server.ts +21 -0
- package/templates/gallery/modules/todo/components/todo-app.ts +140 -0
- package/templates/gallery/modules/todo/queries/list-todos.server.ts +25 -0
- package/templates/gallery/modules/todo/types.ts +12 -0
- package/templates/gallery/modules/websockets/components/ws-echo.ts +62 -0
- package/templates/lib/utils/ui.ts +4 -4
- package/templates/test/hello/browser/hello.test.js +6 -0
- package/templates/web-test-runner.config.js +9 -1
package/templates/.cursorrules
CHANGED
|
@@ -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.
|
|
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.
|
|
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'
|
package/templates/AGENTS.md
CHANGED
|
@@ -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.
|
|
8
|
-
companion and reach for docs.webjs.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1262
|
-
|
|
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
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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.
|