@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.
- package/README.md +1 -1
- package/bin/webjs.js +241 -1
- package/lib/create.js +15 -4
- package/package.json +2 -2
- package/templates/.agents/rules/workflow.md +149 -0
- package/templates/.claude/hooks/require-tests-with-src.sh +83 -0
- package/templates/.claude/settings.json +9 -0
- package/templates/.cursorrules +36 -22
- package/templates/.github/copilot-instructions.md +53 -26
- package/templates/.github/pull_request_template.md +27 -3
- package/templates/.hooks/pre-commit +26 -1
- package/templates/AGENTS.md +121 -8
- package/templates/CONVENTIONS.md +150 -12
- package/templates/test/hello/hello.test.ts +2 -1
- package/templates/.windsurfrules +0 -91
package/templates/CONVENTIONS.md
CHANGED
|
@@ -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.**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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`, `.
|
|
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`, `.
|
|
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
|
|
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>`);
|
package/templates/.windsurfrules
DELETED
|
@@ -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
|