@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.
@@ -95,16 +95,135 @@ even if the user doesn't explicitly ask.**
95
95
  Run `webjs test` after every change. Never mark work as done with
96
96
  failing tests.
97
97
 
98
- 3. **Documentation updates.** When adding or modifying features:
99
- - Update `AGENTS.md` if the change affects the framework API surface.
100
- - Update `CONVENTIONS.md` only if the change introduces a new convention.
101
- - If a `docs/` directory exists, add or update the relevant doc page.
102
- - If a `website/` directory exists, update the landing page for
103
- user-facing features.
104
-
105
- 3. **Convention check.** Run `webjs check` after changes and fix
98
+ 3. **Documentation updates.** See the **Definition of done** section
99
+ below for the per-surface checklist. The short version: docs land on
100
+ the same PR as the code, never as a follow-up. Drift is how a
101
+ codebase rots; the user should never have to ask "did you update the
102
+ docs?"
103
+
104
+ 4. **Convention check.** Run `webjs check` after changes and fix
106
105
  any violations before reporting the task as done.
107
106
 
107
+ ### Definition of done (MUST be addressed BEFORE opening the PR)
108
+
109
+ This is the per-PR contract. Before running `gh pr create`, walk through
110
+ every surface below and either update it OR write `N/A because <reason>`
111
+ in the PR body so the omission is visible. The
112
+ [`.github/pull_request_template.md`](./.github/pull_request_template.md)
113
+ checklist mirrors this list.
114
+
115
+ **Surfaces to consider on EVERY PR:**
116
+
117
+ 1. **Tests.** Unit coverage for new logic. Real-browser coverage for
118
+ user-facing behaviour. `webjs test` must pass; `webjs test --browser`
119
+ for any DOM-touching change. See the "Testing" section below for the
120
+ per-change matrix.
121
+ 2. **Every markdown file in the project.** Walk the whole tree, not a
122
+ closed list. Run `git ls-files '*.md'` (or `git ls-files '*.md'
123
+ '*.mdx'` if the project ships MDX) and for each path ask: does this
124
+ file describe behaviour, surface, or invariants that this PR changed?
125
+ If yes, update it on this PR. Common surfaces (non-exhaustive):
126
+ - `AGENTS.md` (root and every nested one) for API surface, invariants,
127
+ file-routing rules, project-wide agent workflow.
128
+ - `CONVENTIONS.md` (this file) for architectural conventions. Do NOT
129
+ enumerate lint rules in prose; those live in `package.json` under
130
+ `"webjs": { "conventions": { … } }`.
131
+ - `README.md` (root and any nested ones) for install / use / public
132
+ surface descriptions.
133
+ - `CHANGELOG.md` for any user-visible change, including the SHA / PR
134
+ reference. Keep it in chronological order; don't backdate.
135
+ - `docs/` (if the project has one). Every user-visible change. Add a
136
+ new page if the surface is new and there's no obvious home.
137
+ - Any `*.md` under `agent-docs/`, `docs-internal/`, `decisions/`, or
138
+ similar reference trees.
139
+ - `.github/*.md` (issue templates, PR templates, contributing) when
140
+ a workflow rule shifts.
141
+ 3. **`website/`** (if the project has one). Marketing copy on the
142
+ landing page or pricing page when the change touches a claim made
143
+ there.
144
+ 4. **Scaffold or codegen scripts** (if the project has any). Update
145
+ when the change affects what new instances generate.
146
+ 5. **PR body.** Summary, test plan checklist, and a per-row answer to
147
+ the Definition-of-done checklist (`Updated <path>` or `N/A because
148
+ <reason>`).
149
+
150
+ **How to use the checklist.** For each surface above, explicitly answer
151
+ one of:
152
+
153
+ - **Updated**, with the file path in the commit and PR body.
154
+ - **N/A because**, with a one-sentence reason.
155
+
156
+ The "every markdown file" rule is generative, not enumerative. New
157
+ markdown files appear over a project's lifetime, and this checklist
158
+ must not silently exclude them. The git query above is the source of
159
+ truth; the named files are just common cases.
160
+
161
+ If you find yourself writing `N/A` for every surface except tests, that
162
+ is a smell. Most user-visible code changes touch at least one markdown
163
+ file and either `AGENTS.md` or `CONVENTIONS.md`.
164
+
165
+ **Worked examples:**
166
+
167
+ - Add a new server action `modules/posts/actions/create-post.server.ts`.
168
+ Updated: test (`test/posts/posts.test.ts`), `AGENTS.md` (action listed
169
+ in the module map if the project keeps one), `CHANGELOG.md` (one-line
170
+ entry), `docs/` (the page listing shipped actions if one exists). N/A
171
+ on website / scaffold scripts.
172
+ - Rename a directory convention (e.g. `modules/` to `features/`).
173
+ Updated: existing tests still pass after renames, every markdown file
174
+ that mentions the old name (run `git grep -l 'modules/' '*.md'`),
175
+ scaffold scripts, `CHANGELOG.md`. N/A on website unless the layout
176
+ appears in a landing-page screenshot.
177
+ - Fix a bug in `rateLimit()` that doesn't change the surface. Updated:
178
+ test (regression), `CHANGELOG.md` (one-line entry under fixes). N/A
179
+ on every other markdown file because the public contract did not
180
+ change.
181
+
182
+ ### Pre-merge self-review loop (MUST run before signaling the PR is ready)
183
+
184
+ Saying "ready for merge" before a self-review loop converges is a recurring source of low-quality PRs. The pattern to avoid: agent claims ready-for-merge, user requests a code review, agent finds issues, fixes them, claims ready-for-merge again, repeat 4-5 cycles before a review comes back clean. The cure is to run that loop internally before the first "ready" signal, so the user only hears "ready to merge" after the loop has converged on a clean round.
185
+
186
+ **How the loop works:**
187
+
188
+ 1. After committing the work and (if remote pushes are in use) pushing the branch, do NOT report "ready for merge" yet. Trigger a **fresh-context review pass**: an AI review with NO prior knowledge of the decisions you made during the implementation. Each AI tool exposes its own primitive for this:
189
+
190
+ - **Cursor**: open a new composer tab.
191
+ - **Claude Code**: spawn a `general-purpose` subagent via the Agent tool.
192
+ - **GitHub Copilot**: open a new chat (reset the side panel).
193
+ - **Antigravity** (Google, formerly Windsurf): open a new Cascade thread or a fresh side-panel session.
194
+ - **Aider**: invoke a separately-started `aider` session (do NOT use `/ask` inside the same session; `/ask` only flips the mode for the next message and still sees the existing context).
195
+ - **Gemini CLI**: invoke a separately-started `gemini` session.
196
+ - **OpenCode**: open a new agent session (the `tool.execute.after` hook is a different surface and not a fresh-context primitive).
197
+
198
+ The shared property is that the reviewer does not see your decision log. That independence is what makes the review catch blind spots. If your tool does not expose a true fresh-context primitive, the canonical fallback is a separately-invoked CLI process; what matters is the reviewer starts with an empty context, not the specific UI affordance.
199
+
200
+ 2. Prompt the review for problems only. A working prompt template:
201
+
202
+ > Review the changes on this branch against the project's `AGENTS.md` and `CONVENTIONS.md`. Look for bugs, regressions, security issues, missed edge cases, broken invariants, doc drift, test gaps, and style violations. Read every file the diff touches in its current state, not just the diff hunks. Specifically check: \<focus rotates per round\>. Report findings as a numbered list with file:line references. Problems only, no suggestions. If you find nothing genuinely wrong, reply exactly `CLEAN` on its own line and stop.
203
+
204
+ 3. For each finding the review reports, either:
205
+
206
+ - Fix it on the branch (commit + push), OR
207
+ - Reject it explicitly with a one-sentence reason. False positives are real, but rejection has to be defensible (e.g. "the reviewer flagged X as a security issue but X runs server-side only and never reaches user input"). Hand-waving doesn't count.
208
+
209
+ 4. If the round found any findings, run another round. The new round picks a slightly different focus: if round 1 was broad, round 2 zooms in on the file you most edited; if round 2 zoomed in, round 3 zooms out to cross-file consistency. Rotate focus to avoid the reviewer rediscovering the same surface twice.
210
+
211
+ 5. If the round reports `CLEAN`, the loop is done.
212
+
213
+ The minimum is TWO rounds. A clean first round is rare and usually means the review was too shallow; if round 1 is clean, run a second one with a sharper focus before believing the result.
214
+
215
+ **When to skip the loop:**
216
+
217
+ Skip only for changes that touch a single line of trivially-correct content (a doc typo, a renamed local variable, a one-token config bump). Anything that touches logic, public surface, build, security, or multiple files goes through the loop without exception. A bias toward running the loop is correct; a bias toward skipping it is the exact failure mode this rule exists to prevent.
218
+
219
+ **Reporting after the loop:**
220
+
221
+ When the user is notified the PR is ready, the message should carry:
222
+
223
+ > Ready for merge. Self-review loop ran \<K\> rounds; last round clean. Issues found and fixed during the loop: \<one-line list, or "none" if rounds 2+ kept finding nothing\>.
224
+
225
+ If you cannot honestly say "last round clean", you cannot say "ready for merge". If a finding was rejected as a false positive, mention it so the user can second-guess the rejection.
226
+
108
227
  ### Autonomous mode (sandbox / bypass permissions)
109
228
 
110
229
  When running without interactive approval, agents must NOT ask questions.
@@ -332,6 +451,15 @@ test/
332
451
  - `webjs test --browser` (or `npx wtr`) runs the browser tests.
333
452
  - `WEBJS_E2E=1 webjs test` adds the e2e tests.
334
453
 
454
+ **Every change ships with a test, enforced at commit time.** A commit
455
+ that stages app code (`app/`, `modules/`, `components/`, `lib/`) without
456
+ staging a test is blocked by `.claude/hooks/require-tests-with-src.sh`
457
+ (Claude Code) and the universal `.hooks/pre-commit` (any agent or human).
458
+ A unit test alone is not enough for interactive or component code: add
459
+ the browser test that asserts the rendered/hydrated behaviour. Bypass a
460
+ genuine non-code commit with `WEBJS_NO_TEST_GATE=1`, or `--no-verify` in
461
+ an emergency.
462
+
335
463
  ### Choosing a feature folder
336
464
 
337
465
  Use the same name as the matching module folder when one exists:
@@ -653,7 +781,15 @@ Use `rateLimit()` as per-segment middleware to protect routes:
653
781
  ```ts
654
782
  // app/api/auth/middleware.ts: protect auth endpoints
655
783
  import { rateLimit } from '@webjsdev/server';
784
+
785
+ // Direct deploy (default). Keys on the socket-stamped IP, ignoring
786
+ // forwarded-IP headers.
656
787
  export default rateLimit({ window: '10s', max: 5 });
788
+
789
+ // Behind a reverse proxy or CDN (Cloudflare, Railway, Fly, Vercel,
790
+ // nginx, Caddy). Set trustProxy to honour X-Forwarded-For. The proxy
791
+ // MUST strip inbound X-Forwarded-For before adding its own.
792
+ export default rateLimit({ window: '10s', max: 5, trustProxy: true });
657
793
  ```
658
794
 
659
795
  Place `middleware.ts` at any route level. It applies to that subtree only.
@@ -681,9 +817,11 @@ SSR content is visible immediately. Only the JS download is deferred.
681
817
  ## expose(): REST endpoints from server actions
682
818
 
683
819
  <!-- OVERRIDE -->
684
- Tag a server action to also be reachable over HTTP:
820
+ Tag a server action to also be reachable over HTTP. The file MUST be a `.server.{js,ts}` file: `expose()` is server-only and the bare `@webjsdev/core` specifier resolves to the browser entry which excludes it, so importing from a client-bound file silently reads `undefined`.
685
821
 
686
822
  ```ts
823
+ // modules/posts/actions/create-post.server.ts
824
+ 'use server';
687
825
  import { expose } from '@webjsdev/core';
688
826
  export const createPost = expose('POST /api/posts', async ({ title, body }) => {
689
827
  return prisma.post.create({ data: { title, body } });
@@ -823,7 +961,7 @@ export async function createPost(input: {
823
961
  constructor(x: number) { this.x = x; }
824
962
  }
825
963
  ```
826
- If you turn `erasableSyntaxOnly` off and use non-erasable syntax, the dev server falls back to esbuild and ships inline sourcemaps for those files (~3x wire bytes per request and stack traces lose strict accuracy). The `erasable-typescript-only` convention check warns when the flag is off.
964
+ If you turn `erasableSyntaxOnly` off and use non-erasable syntax, 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. The `erasable-typescript-only` convention check warns when the flag is off.
827
965
  - No semicolons (or with semicolons, pick one and stay consistent)
828
966
  - `const` by default, `let` when needed, never `var`
829
967
  - Prefer `async/await` over `.then()` chains
@@ -837,7 +975,7 @@ export async function createPost(input: {
837
975
  <!-- OVERRIDE -->
838
976
 
839
977
  This project enforces a git workflow via agent-specific config files
840
- (`.claude/settings.json`, `.cursorrules`, `.windsurfrules`,
978
+ (`.claude/settings.json`, `.cursorrules`, `.agents/rules/workflow.md`,
841
979
  `.github/copilot-instructions.md`). These rules apply to ALL AI agents:
842
980
 
843
981
  **Commit rules:**
@@ -862,7 +1000,7 @@ This project enforces a git workflow via agent-specific config files
862
1000
  Delete or keep `<branch>` after?" Wait for approval AND the preference.
863
1001
  - **Claude Code hook** (`.claude/hooks/guard-main-merge.sh`) enforces
864
1002
  merge/push-to-main approval programmatically for Claude agents.
865
- Other agents enforce this via `.cursorrules`, `.windsurfrules`,
1003
+ Other agents enforce this via `.cursorrules`, `.agents/rules/workflow.md`,
866
1004
  `.github/copilot-instructions.md`.
867
1005
 
868
1006
  **Pre-commit checks:**
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import { test } from 'node:test';
8
8
  import assert from 'node:assert/strict';
9
- import { html, renderToString } from '@webjsdev/core';
9
+ import { html } from '@webjsdev/core';
10
+ import { renderToString } from '@webjsdev/core/server';
10
11
 
11
12
  test('html template renders correctly', async () => {
12
13
  const result = await renderToString(html`<p>Hello, ${'world'}!</p>`);
@@ -1,91 +0,0 @@
1
- # Windsurf Rules - webjs app
2
-
3
- You are working on a webjs app - an AI-first, no-build, web-components-first
4
- framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
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**.
7
-
8
- ## Persistence + scaffold rules (non-negotiable)
9
-
10
- - **Use Prisma + SQLite for data, never JSON files.** It's already wired up
11
- (`prisma/schema.prisma`, `lib/prisma.server.ts`, `npm run db:migrate`). For ANY
12
- data the app stores (todos, posts, messages, products, comments…),
13
- define a Prisma model. NEVER create `data/*.json`, `db.json`, or any
14
- JSON file as a fake database. NEVER use module-scope arrays / Maps as
15
- a substitute. NEVER use localStorage for app data. `webjs check`'s
16
- `no-json-data-files` rule will fail the build if you do.
17
- - **The scaffold is reference, not the final product.** Replace
18
- `app/page.ts`, the example `User` model, the example users module, etc.
19
- with the app the user actually asked for. Don't ship "Hello from
20
- <app-name>" as the deliverable.
21
- - **Only three templates exist:** `webjs create <name>` (default
22
- full-stack), `--template api`, `--template saas`. The CLI rejects any
23
- other `--template` value. Pick:
24
- - Any product UI (todo, blog, dashboard, marketplace, social…) → default
25
- - HTTP/JSON API only, no UI → `--template api`
26
- - Auth / login / signup / SaaS → `--template saas`
27
-
28
- ## Before starting ANY work
29
-
30
- FIRST, before writing any code:
31
- 1. Check `git branch --show-current`.
32
- - If on main/master: create a feature branch before editing.
33
- - If on a feature branch: verify it matches the current task.
34
- 2. Sync: `git fetch origin && git rebase origin/main` if behind.
35
-
36
- ## Autonomous mode (sandbox / no-prompt)
37
-
38
- If running without interactive approval, auto-decide:
39
- - On main? Auto-create feature/<task-slug> branch
40
- - Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
41
- - Auto-generate commit messages. Fix failing tests and violations.
42
- Quality bar stays the same - no blocking on questions.
43
-
44
- ## Mandatory workflow (never skip)
45
-
46
- Every code change must include:
47
- 1. Server tests in test/<feature>/*.test.ts (node:test)
48
- 2. Browser tests in test/<feature>/browser/*.test.js (WTR + Playwright, real Chromium)
49
- 3. Documentation updates (AGENTS.md, docs/, website/ if they exist)
50
- 4. Convention check: `webjs check` must pass
51
-
52
- The user should never have to ask for tests or documentation.
53
-
54
- ## Git rules
55
-
56
- - COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
57
- one rename, one doc rewrite per commit. Always `git push` after
58
- committing. This is automatic.
59
- - HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
60
- commit before continuing. The Claude Code hook at
61
- `.claude/hooks/nudge-uncommitted.sh` fires at threshold 4. Windsurf
62
- users should self-enforce the same rule. Batching multiple logical units
63
- into one commit is the failure mode this rule exists to prevent.
64
- - Meaningful commit messages: what changed and why
65
- - NEVER add Co-Authored-By or AI attribution trailers to commits
66
- - NEVER use em-dashes (U+2014), a hyphen-as-pause (` - `), or a
67
- semicolon-as-pause (` ; `) in commit messages or anywhere else.
68
- Rewrite the sentence so no pause-punctuation crutch is needed.
69
- Use a period, comma, colon, parentheses, or a restructured phrasing.
70
- Plain hyphens stay fine in compound words, CLI flags, filenames,
71
- and ranges. Semicolons stay fine inside code
72
- - Work on feature branches, never push directly to main
73
- - Create pull requests for review
74
- - NEVER merge any branch without explicit user permission. Always ask:
75
- "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
76
- Wait for approval AND the delete/keep preference. Applies to ALL merges.
77
- - Run `webjs test` before every commit
78
-
79
- ## Framework specifics
80
-
81
- - No build step: ES modules served directly
82
- - **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).
83
- - Web components render into light DOM by default (so Tailwind / global CSS apply directly). Opt in to shadow DOM per component with `static shadow = true` when you need scoped styles (via `static styles = css\`...\``) or third-party-embed isolation. `<slot>` projection works identically in both modes (named slots, fallback content, `assignedNodes` / `slotchange`, first-wins resolution).
84
- - Custom-element tag names are passed to `.register('tag-name')` - they are NOT a static field on the class.
85
- - One function per server action file (*.server.ts)
86
- - 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.
87
- - Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat` ship. Use plain template-literal expressions (`class=${active ? 'btn active' : 'btn'}`, `style=${'color:' + color}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead of Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard`.
88
- - Use Context for cross-component data, Task for async data in components
89
- - **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.
90
- - **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.
91
- - Full API reference in AGENTS.md