@webjsdev/cli 0.9.1 → 0.10.1

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.
@@ -1,6 +1,6 @@
1
- # Cursor Rules - webjs app
1
+ # Cursor Rules: webjs app
2
2
 
3
- You are working on a webjs app - an AI-first, no-build, web-components-first
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
6
  cover what you need, the full hosted docs are at **https://docs.webjs.com**.
@@ -36,7 +36,7 @@ FIRST, before writing any code:
36
36
  - If upstream has new commits: `git rebase origin/main` before starting.
37
37
  - Resolve any conflicts before proceeding with the task.
38
38
 
39
- ## Autonomous mode (sandbox / no-prompt mode)
39
+ ## Autonomous mode (sandbox / no-prompt)
40
40
 
41
41
  If running without interactive approval, auto-decide:
42
42
  - On main? Auto-create feature/<task-slug> branch
@@ -44,29 +44,44 @@ If running without interactive approval, auto-decide:
44
44
  - Merge? Auto-merge in autonomous mode, delete feature branches after
45
45
  - Commit message? Auto-generate (meaningful, no AI attribution)
46
46
  - Tests failing? Fix them. Convention violations? Fix them.
47
- Quality bar stays the same - just no blocking on questions.
47
+ Quality bar stays the same, no blocking on questions.
48
48
 
49
49
  ## Mandatory workflow (never skip)
50
50
 
51
- 1. TESTS: Server tests in test/<feature>/ (node:test), browser tests in
52
- test/<feature>/browser/ (WTR + Playwright, real Chromium). Run `webjs test`
53
- after every change. Never deliver code without passing tests.
51
+ Every code change must include:
52
+ 1. Server tests in `test/<feature>/*.test.ts` (node:test).
53
+ 2. Browser tests in `test/<feature>/browser/*.test.js` (WTR + Playwright, real Chromium).
54
+ 3. Documentation updates. Walk every surface in the **Definition of done**
55
+ section of CONVENTIONS.md (AGENTS.md, CONVENTIONS.md, README.md, docs/,
56
+ website/, scaffold scripts) and either update it or write
57
+ "N/A because <reason>" in the PR body. Docs land on the same PR as the
58
+ code, never as a follow-up.
59
+ 4. Convention check: `webjs check` must pass.
60
+ 5. Pre-merge self-review loop. Before saying the PR is ready for merge, run
61
+ fresh-context review rounds until one round finds zero issues. Cursor
62
+ primitive: open a NEW composer tab and prompt the review there so the
63
+ reviewer has no prior context on your decisions. Minimum two rounds;
64
+ rotate focus each round. Skip the loop only for one-line trivial
65
+ changes; skipping on a change that touches logic, public surface, build,
66
+ security, or multiple files is the exact failure mode the loop exists
67
+ to prevent. The full rule, prompt template, and reporting contract live
68
+ in the **Pre-merge self-review loop** section of CONVENTIONS.md.
54
69
 
55
- 2. DOCS: Update AGENTS.md for API changes. Update docs/ and website/ if
56
- they exist. The user should never have to ask for tests or docs.
57
-
58
- 3. CONVENTIONS: Run `webjs check` and fix violations before committing.
70
+ The user should never have to ask for tests, documentation, or the
71
+ self-review loop.
59
72
 
60
73
  ## Git rules
61
74
 
62
75
  - COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
63
76
  one rename, one doc rewrite per commit. Always `git push` after
64
- committing. This is automatic.
77
+ committing. The user should never have to ask for a commit.
65
78
  - HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
66
- commit before continuing. The Claude Code hook at
67
- `.claude/hooks/nudge-uncommitted.sh` fires at threshold 4. Cursor users
68
- should self-enforce the same rule. Batching multiple logical units into
69
- one commit is the failure mode this rule exists to prevent.
79
+ commit before continuing. Cursor 1.7+ has its own
80
+ `.cursor/hooks/nudge-uncommitted.sh` (afterFileEdit) firing at threshold
81
+ 4; the same enforcement runs for Claude users via
82
+ `.claude/hooks/nudge-uncommitted.sh`. On older Cursor versions without
83
+ the hook, self-enforce the same rule. Batching multiple logical units
84
+ into one commit is the failure mode this rule exists to prevent.
70
85
  - Write meaningful commit messages: what changed and why, not "update files"
71
86
  - NEVER add "Co-Authored-By", "Generated by", "AI-assisted" or similar
72
87
  attribution trailers to commits
@@ -77,23 +92,22 @@ Quality bar stays the same - just no blocking on questions.
77
92
  Plain hyphens stay fine in compound words, CLI flags, filenames,
78
93
  and ranges. Semicolons stay fine inside code
79
94
  - Work on feature branches, not main
80
- - NEVER push directly to main - create a pull request
95
+ - NEVER push directly to main. Create a pull request instead.
81
96
  - NEVER merge any branch without explicit user permission. Always ask:
82
97
  "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
83
98
  Wait for approval AND the delete/keep preference before proceeding.
84
99
  This applies to ALL merges, not just merges into main.
85
100
  - Run tests before every commit
86
- - Keep commits small and focused
87
101
 
88
102
  ## Framework rules
89
103
 
90
104
  - No build step: source files are served as ES modules
91
- - **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (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 falls back to esbuild + inline sourcemap for those files (~3x wire bytes per request).
105
+ - **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (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.
92
106
  - Web components with shadow DOM: use `static styles = css` not inline styles
93
107
  - One function per server action file (*.server.ts)
94
108
  - Components must call customElements.define('tag', Class)
95
109
  - Server-only code (@prisma/client, 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. lib/ holds both server-only infra (lib/prisma.server.ts) 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.
96
- - Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat` ship. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported - use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
97
- - **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()`, not `connectedCallback` - the server doesn't call lifecycle hooks). Initial data for components comes from the page function (server-side fetch + pass as attribute/property), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` + server action over `fetch` + click handler - the framework upgrades plain forms to partial-swap submissions automatically.
98
- - **Client navigation is auto-magic.** Real `<a href>` and `<form action>` get partial-swap behavior with no opt-in. Layouts persist across navigation - put shared chrome (sidenav, header) in `layout.ts`, 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.
110
+ - Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat` ship. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported. Use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
111
+ - **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), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` plus server action over `fetch` plus click handler. The framework upgrades plain forms to partial-swap submissions automatically.
112
+ - **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.
99
113
  - See AGENTS.md for the complete directive decision guide
@@ -33,53 +33,80 @@ FIRST, before writing any code:
33
33
  - If on a feature branch: verify it matches the task at hand.
34
34
  2. Sync: `git fetch origin && git rebase origin/main` if behind.
35
35
 
36
- ## Autonomous mode
36
+ ## Autonomous mode (sandbox / no-prompt)
37
37
 
38
38
  If running without interactive approval (sandbox, auto-approve, etc.):
39
39
  - On main? Auto-create feature/<task-slug> branch
40
40
  - Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
41
41
  - Auto-generate meaningful commit messages. Fix tests and violations.
42
42
 
43
- ## Mandatory workflow
43
+ Quality bar stays the same, no blocking on questions.
44
+
45
+ ## Mandatory workflow (never skip)
44
46
 
45
47
  Every code change must include:
46
- 1. Commit and push PER LOGICAL UNIT, not at the end. One feature, one fix,
47
- one rename, one doc rewrite per commit. Always `git push` after
48
- committing. Don't accumulate changes. If you have 5+ unstaged files
49
- spanning different concerns, commit before continuing. The Claude Code
50
- hook at `.claude/hooks/nudge-uncommitted.sh` enforces threshold 4 for
51
- Claude users; Copilot users should self-enforce the same rule. Automatic.
52
- 2. Server tests in test/<feature>/*.test.ts (node:test for actions, queries, utilities)
53
- 3. Browser tests in test/<feature>/browser/*.test.js (WTR + Playwright, real Chromium)
54
- 4. Documentation updates (AGENTS.md for API, docs/ for user guides)
55
- 5. Convention validation: `webjs check` must pass
48
+ 1. Server tests in `test/<feature>/*.test.ts` (node:test).
49
+ 2. Browser tests in `test/<feature>/browser/*.test.js` (WTR + Playwright, real Chromium).
50
+ 3. Documentation updates. Walk every surface in the **Definition of done**
51
+ section of CONVENTIONS.md (AGENTS.md, CONVENTIONS.md, README.md, docs/,
52
+ website/, scaffold scripts) and either update or write
53
+ "N/A because <reason>" in the PR body. Docs land on the same PR as the
54
+ code, never as a follow-up.
55
+ 4. Convention check: `webjs check` must pass.
56
+ 5. Pre-merge self-review loop. Before saying the PR is ready for merge,
57
+ run fresh-context review rounds until one round finds zero issues.
58
+ Copilot primitive: open a NEW chat session (reset the side panel) for
59
+ each round so the reviewer has no prior context on the implementation
60
+ decisions. Minimum two rounds; rotate focus each round. Skip the loop
61
+ only for one-line trivial changes; skipping on a change that touches
62
+ logic, public surface, build, security, or multiple files is the exact
63
+ failure mode the loop exists to prevent. The full rule, prompt
64
+ template, and reporting contract live in the **Pre-merge self-review
65
+ loop** section of CONVENTIONS.md.
66
+
67
+ The user should never have to ask for tests, documentation, or the
68
+ self-review loop. The commit-per-logical-unit rule lives under "Git rules"
69
+ below, not here, since it governs how work is grouped rather than what
70
+ each change must include.
56
71
 
57
72
  ## Git rules
58
73
 
59
- - Commit after each logical unit of work
74
+ - COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
75
+ one rename, one doc rewrite per commit. Always `git push` after
76
+ committing. The user should never have to ask for a commit.
77
+ - HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
78
+ commit before continuing. The Claude Code hook at
79
+ `.claude/hooks/nudge-uncommitted.sh` enforces threshold 4 for Claude
80
+ users. Copilot has no equivalent hook surface today; self-enforce the
81
+ same rule. Batching multiple logical units into one commit is the
82
+ failure mode this rule exists to prevent.
60
83
  - Meaningful commit messages: what changed and why
61
84
  - NEVER add Co-Authored-By or AI attribution trailers to commits
85
+ - NEVER use em-dashes (U+2014), a hyphen-as-pause (` - `), or a
86
+ semicolon-as-pause (` ; `) in commit messages or anywhere else.
87
+ Rewrite the sentence so no pause-punctuation crutch is needed. Use a
88
+ period, comma, colon, parentheses, or a restructured phrasing. Plain
89
+ hyphens stay fine in compound words, CLI flags, filenames, and ranges.
90
+ Semicolons stay fine inside code.
62
91
  - Work on feature branches, create PRs, never push directly to main
63
92
  - NEVER merge any branch without explicit user permission. Always ask:
64
93
  "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
65
94
  Wait for approval AND the delete/keep preference. Applies to ALL merges.
66
95
  - Run `webjs test` before every commit
67
96
 
68
- ## Code patterns
97
+ ## Framework rules
69
98
 
70
- - **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (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 falls back to esbuild + inline sourcemap for those files (~3x wire bytes per request).
71
- - Tagged template: html`<div>${value}</div>` with css`...` for styles
99
+ - No build step: source files are served as ES modules. Don't introduce
100
+ build tools or bundlers in the critical path.
101
+ - **Erasable TypeScript only.** Node 24+ strips types via `module.stripTypeScriptTypes` (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.
102
+ - Tagged template: html`<div>${value}</div>` with css`...` for styles.
103
+ Don't use inline `style="..."` on components (use `static styles = css\`...\``).
72
104
  - Components: extend WebComponent, declare `static properties` (and `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.
73
- - Server actions: *.server.ts files with one exported async function each
74
- - Directives: webjs ships only `unsafeHTML`, `live`, and `repeat`. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported - use plain template-literal expressions and lifecycle hooks instead.
105
+ - Server actions: *.server.ts files with one exported async function each.
106
+ - Server-only code (@prisma/client, 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. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file.
107
+ - Directives: webjs ships only `unsafeHTML`, `live`, and `repeat`. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported. Use plain template-literal expressions and lifecycle hooks instead.
75
108
  - Context: import { createContext, ContextProvider, ContextConsumer } from '@webjsdev/core/context'
76
109
  - Task: import { Task, TaskStatus } from '@webjsdev/core/task'
77
- - Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts)
78
-
79
- ## What NOT to do
80
-
81
- - Don't introduce build tools or bundlers in the critical path
82
- - Server-only code (@prisma/client, 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. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file.
83
- - Don't use inline style="..." on components (use static styles = css`...`)
110
+ - Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts).
84
111
  - Component state lives in signals from @webjsdev/core. Module-scope signals share state across components; instance signals (created in the constructor) carry component-local state. Reactive properties (static properties + declare) are for HTML attributes and .prop=${...} hydration.
85
- - Don't skip tests or documentation updates
112
+ - Don't skip tests or documentation updates.
@@ -8,7 +8,31 @@
8
8
  - [ ] E2E tests added/updated for user-facing changes (`webjs test --e2e` passes)
9
9
  - [ ] `webjs check` passes (no convention violations)
10
10
 
11
- ## Documentation
11
+ ## Definition of done
12
12
 
13
- - [ ] AGENTS.md updated (if API surface changed)
14
- - [ ] Docs updated (if docs/ exists and feature is documented)
13
+ Documentation MUST land on the same PR as the code change. Drift is how
14
+ a codebase rots. Walk every markdown file in the project (`git ls-files
15
+ '*.md'`) and ask whether this PR changed behaviour, surface, or
16
+ invariants it describes. For each row below, write `Updated <path>` or
17
+ `N/A because <reason>`. Reviewers should reject the PR if this section
18
+ is left as the template default. See the **Definition of done** section
19
+ in [`CONVENTIONS.md`](../CONVENTIONS.md) for the full guidance.
20
+
21
+ - [ ] **Tests.** Unit coverage for logic. Real-browser coverage for
22
+ user-facing behaviour.
23
+ - [ ] **Every markdown file in the project** that describes the
24
+ changed surface. Common cases (non-exhaustive): `AGENTS.md` (root
25
+ + nested), `CONVENTIONS.md`, `README.md` (root + nested),
26
+ `CHANGELOG.md`, `docs/**/*.md`, `agent-docs/**/*.md`,
27
+ `.github/*.md`. The rule is generative: if a markdown file in
28
+ this project mentions a thing this PR changed, it gets touched
29
+ on this PR.
30
+ - [ ] **`website/`** (if the project has one). Marketing copy on the
31
+ landing or pricing page when the change touches a claim made
32
+ there.
33
+ - [ ] **Scaffold scripts / codegen** (if the project has any). Updated
34
+ when the change affects what new instances generate.
35
+ - [ ] **Pre-merge self-review loop.** Ran N rounds; last round clean.
36
+ Skip only for one-line trivial changes. See the **Pre-merge
37
+ self-review loop** section in [`CONVENTIONS.md`](../CONVENTIONS.md)
38
+ for the prompt template and reporting contract.
@@ -21,8 +21,33 @@ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
21
21
  exit 1
22
22
  fi
23
23
 
24
+ # Require a test to accompany app-code changes. Tool-agnostic floor: a
25
+ # commit that stages app code (app/, modules/, components/, lib/) with no
26
+ # test (test/** or *.test.* / *.spec.*) is blocked. The full suite below
27
+ # proves tests PASS; this proves a test was WRITTEN. Bypass for a genuine
28
+ # non-code commit with WEBJS_NO_TEST_GATE=1, or --no-verify in emergencies.
29
+ if [ "${WEBJS_NO_TEST_GATE:-}" != "1" ]; then
30
+ STAGED=$(git diff --cached --name-only 2>/dev/null)
31
+ APP_CODE=$(printf '%s\n' "$STAGED" | grep -E '^(app|modules|components|lib)/.*\.([mc]?[jt]sx?)$' || true)
32
+ if [ -n "$APP_CODE" ]; then
33
+ TESTS=$(printf '%s\n' "$STAGED" | grep -E '(^|/)test/|\.test\.[mc]?[jt]sx?$|\.spec\.[mc]?[jt]sx?$' || true)
34
+ if [ -z "$TESTS" ]; then
35
+ echo ""
36
+ echo "ERROR: app code changed but no test is staged."
37
+ echo "Every change ships with a test. Add or update the test that"
38
+ echo "proves the new behaviour (a browser/e2e test for interactive"
39
+ echo "or component code), then git add it."
40
+ echo ""
41
+ echo "Genuine non-code commit? WEBJS_NO_TEST_GATE=1 git commit ..."
42
+ echo "Emergency bypass: git commit --no-verify"
43
+ echo ""
44
+ exit 1
45
+ fi
46
+ fi
47
+ fi
48
+
24
49
  # webjs test + webjs check on every commit. Tool-agnostic enforcement:
25
- # fires regardless of which agent (Claude, Cursor, Windsurf, Copilot,
50
+ # fires regardless of which agent (Claude, Cursor, Antigravity, Copilot,
26
51
  # human) is making the commit. Skipped if the CLI is not yet installed
27
52
  # (fresh clone before npm install).
28
53
  if command -v webjs >/dev/null 2>&1 || [ -x "node_modules/.bin/webjs" ]; then
@@ -301,6 +301,20 @@ In Docker / Railway, prefer `npm start` (or `node node_modules/.bin/npm
301
301
  start`) as the CMD over `node ... webjs.js start ...`. The npm form
302
302
  fires `prestart`; the direct binary form skips it.
303
303
 
304
+ **Health and readiness probes.** Every webjs server answers two endpoints:
305
+ `/__webjs/health` (liveness, 200 once the process is listening) and
306
+ `/__webjs/ready` (readiness, 503 until the instance is fully warm, then 200).
307
+ Fully warm means the deterministic analysis AND the first vendor attempt have
308
+ both completed, so the importmap and its build id are settled. Point your
309
+ platform's readiness check at `/__webjs/ready` so it holds traffic off a
310
+ not-yet-warmed instance instead of routing the first user request into the cold
311
+ analysis or the brief window where the importmap is still resolving. On
312
+ Railway, set `"healthcheckPath": "/__webjs/ready"` under `deploy` in
313
+ `railway.json`. For dependency-aware
314
+ readiness (gate on a live DB ping), add an optional `readiness.{js,ts}` at the
315
+ app root that default-exports an async check; `/__webjs/ready` runs it once warm
316
+ and reports 503 if it returns `false` or throws.
317
+
304
318
  Scripts:
305
319
 
306
320
  - `npm run db:migrate`: `prisma migrate dev` (dev-time schema changes + migration + generate)
@@ -320,6 +334,87 @@ const users = await prisma.user.findMany();
320
334
  To switch to Postgres or MySQL: change `provider` in `prisma/schema.prisma`
321
335
  and the `DATABASE_URL` in `.env`.
322
336
 
337
+ ## NPM packages (vendor pipeline)
338
+
339
+ Adding a third-party npm package follows the same `npm install` flow
340
+ as any Node project, with one webjs-specific concern: how the BROWSER
341
+ fetches that package.
342
+
343
+ ```sh
344
+ npm install dayjs # standard npm install
345
+ ```
346
+
347
+ Now write `import dayjs from 'dayjs'` in any component or page. The
348
+ import works in dev immediately. webjs's scanner discovers bare
349
+ imports on the first request (memoized for the process) and asks
350
+ `api.jspm.io` to resolve them to CDN URLs (jspm.io serves pre-bundled
351
+ ESM for every npm package). The browser fetches the bundle directly
352
+ from `https://ga.jspm.io`.
353
+
354
+ **For production deploys**, run `webjs vendor pin` once and commit
355
+ the result:
356
+
357
+ ```sh
358
+ webjs vendor pin # writes .webjs/vendor/importmap.json
359
+ git add .webjs/vendor/
360
+ git commit -m "vendor dayjs"
361
+ ```
362
+
363
+ The pin file holds the resolved jspm.io URLs. Server reads it from
364
+ disk on the first request (memoized); no `api.jspm.io` call needed in
365
+ production. Deterministic across deploys.
366
+
367
+ **For offline-capable / strict-CSP production**, use `--download`:
368
+
369
+ ```sh
370
+ webjs vendor pin --download # also vendors bundle bytes locally
371
+ git add .webjs/vendor/
372
+ git commit -m "vendor + download dayjs"
373
+ ```
374
+
375
+ Bundle files land in `.webjs/vendor/<pkg>@<version>.js`. importmap
376
+ points at local `/__webjs/vendor/` paths. Browser fetches from your
377
+ own origin. Suitable for `script-src 'self'` CSP, air-gapped deploys,
378
+ or compliance environments. See [docs.webjs.com Deployment → CSP](https://docs.webjs.com/docs/deployment#csp).
379
+
380
+ **Other CLI commands:**
381
+
382
+ ```sh
383
+ webjs vendor list # show pinned packages with versions
384
+ webjs vendor unpin <pkg> # remove one entry from pin file
385
+ webjs vendor audit # npm security advisories against pinned versions
386
+ webjs vendor outdated # list pinned packages with newer versions on npm
387
+ webjs vendor update # re-pin every outdated package to its latest
388
+
389
+ # Switch CDN at pin time (default: jspm.io). Resolver options:
390
+ # jspm, jsdelivr, unpkg, skypack. Useful for jspm.io incident response.
391
+ webjs vendor pin --from jsdelivr
392
+ webjs vendor update --from jsdelivr
393
+ ```
394
+
395
+ Same posture as Rails 7 + importmap-rails: explicit pin command,
396
+ committed manifest, optional `--download` for full offline capability,
397
+ and a `--from` knob to swap the resolver CDN if jspm.io has an
398
+ incident.
399
+
400
+ **Don't auto-run `webjs vendor pin` in `predev` / `prestart`.** Auto-pin
401
+ would silently churn the committed importmap.json as jspm.io resolves
402
+ URLs or transitive deps drift. Pin is a deliberate developer action,
403
+ like `npm install` itself.
404
+
405
+ **Do NOT modify the `.webjs/` lines in `.gitignore` / `.dockerignore`.**
406
+ The scaffolded pattern is three lines (`.webjs/*` + `!.webjs/vendor/`
407
+ + `!.webjs/vendor/**`) and is structurally load-bearing. Collapsing it
408
+ to a single `.webjs/` excludes the parent directory; once the parent
409
+ is excluded, git cannot re-include `.webjs/vendor/` via a child
410
+ negation (gitignore semantics: parent exclusion blocks child
411
+ negations). The breakage is invisible: `webjs vendor pin` runs, writes
412
+ files, and git silently ignores them. Production then has no
413
+ importmap.json and the server falls back to calling api.jspm.io on
414
+ every cold start. The `gitignore-vendor-not-ignored` lint rule
415
+ (`webjs check`) verifies the pattern with `git check-ignore` and will
416
+ fail CI if it regresses.
417
+
323
418
  ## Imports
324
419
 
325
420
  ```ts
@@ -770,9 +865,9 @@ composition, so a nested shell ends up dropped by the HTML parser.
770
865
  ```
771
866
 
772
867
  If you turn `erasableSyntaxOnly` off and use non-erasable syntax,
773
- the dev server falls back to esbuild and emits inline sourcemaps
774
- for those specific files: roughly 3x wire bytes per request, and
775
- stack-trace positions are no longer byte-exact. The
868
+ the dev server fails at strip time and returns a 500 naming the
869
+ file and pointing at the `no-non-erasable-typescript` lint rule.
870
+ webjs is buildless end-to-end and has no bundler fallback. The
776
871
  `erasable-typescript-only` convention check warns when the flag
777
872
  is missing or set to false.
778
873
  9. **No em-dashes (U+2014) anywhere, and no hyphen or semicolon used
@@ -788,8 +883,15 @@ composition, so a nested shell ends up dropped by the HTML parser.
788
883
  ## Workflow expectations for AI agents
789
884
 
790
885
  1. Branch before editing. Never push to `main` directly.
791
- 2. Every code change comes with: unit test(s), AGENTS.md / docs updates if
792
- the feature surface changed, `webjs check` passing.
886
+ 2. Every code change comes with a test, AGENTS.md / docs updates if the
887
+ feature surface changed, `webjs check` passing. A unit test is not
888
+ always enough: a component, hydration, the client router, or a server
889
+ action called from the client needs a browser test
890
+ (`webjs test --browser`) asserting the behaviour in a real browser. A
891
+ commit that stages app code (`app/`, `modules/`, `components/`, `lib/`)
892
+ with no test is blocked by `.claude/hooks/require-tests-with-src.sh`
893
+ and the `.hooks/pre-commit` floor (bypass a genuine non-code commit
894
+ with `WEBJS_NO_TEST_GATE=1`).
793
895
  3. Commit and push **per logical unit**, not at the end. A logical unit is one
794
896
  feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
795
897
  spanning different concerns, commit the current group before continuing.
@@ -802,14 +904,25 @@ composition, so a nested shell ends up dropped by the HTML parser.
802
904
  | Gemini CLI | `.gemini/hooks/nudge-uncommitted.sh` (`AfterTool`) | `.gemini/settings.json` |
803
905
  | Cursor 1.7+ | `.cursor/hooks/nudge-uncommitted.sh` (`afterFileEdit`) | `.cursor/hooks.json` |
804
906
  | OpenCode | `.opencode/plugins/nudge-uncommitted.ts` (`tool.execute.after`) | `.opencode/plugins/` |
805
- | Windsurf | text rule only (post-write hooks cannot inject context) | `.windsurfrules` |
907
+ | Antigravity (Google) | text rule only (post-write hooks not yet exposed) | `.agents/rules/workflow.md` |
806
908
  | GitHub Copilot | text rule only (no hooks API) | `.github/copilot-instructions.md` |
807
- | Google Antigravity | text rule only (no hooks API) | `AGENTS.md` |
808
909
 
809
910
  Tool-agnostic fallback: `.hooks/pre-commit` runs `webjs test` + `webjs check`
810
911
  on every commit, regardless of which agent (or human) made it. No AI
811
912
  attribution trailers in commit messages.
812
- 4. When unsure how a framework feature works, `grep` or `cat` the
913
+ 4. Run the **pre-merge self-review loop** before signaling the PR is
914
+ ready. After committing the work, trigger a fresh-context review
915
+ pass (a new chat / composer tab / subagent / Cascade thread
916
+ depending on your tool) and iterate fix-then-review rounds until
917
+ one round finds zero issues. Minimum two rounds; rotate focus each
918
+ round so the reviewer does not rediscover the same surface twice.
919
+ Skip the loop only for one-line trivial changes; skipping on a
920
+ change that touches logic, public surface, build, security, or
921
+ multiple files is the exact failure mode the loop exists to
922
+ prevent. The full rule, prompt template, and reporting contract
923
+ live in the **Pre-merge self-review loop** section of
924
+ `CONVENTIONS.md`.
925
+ 5. When unsure how a framework feature works, `grep` or `cat` the
813
926
  relevant `node_modules/@webjsdev/*/src/` file before asking the user.
814
927
 
815
928
  Project-specific conventions and overrides live in