@webjsdev/cli 0.10.40 → 0.10.41
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 +4 -46
- package/lib/create.js +282 -479
- package/lib/doctor.js +1 -38
- package/package.json +5 -1
- package/templates/.agents/rules/workflow.md +61 -271
- package/templates/.agents/skills/webjs/SKILL.md +226 -0
- package/templates/.agents/skills/webjs/references/auth-and-sessions.md +220 -0
- package/templates/.agents/skills/webjs/references/built-ins.md +200 -0
- package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +204 -0
- package/templates/.agents/skills/webjs/references/components.md +167 -0
- package/templates/.agents/skills/webjs/references/data-and-actions.md +187 -0
- package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +170 -0
- package/templates/.agents/skills/webjs/references/optimistic-ui.md +128 -0
- package/templates/.agents/skills/webjs/references/routing-and-pages.md +158 -0
- package/templates/.agents/skills/webjs/references/runtime.md +80 -0
- package/templates/.agents/skills/webjs/references/service-worker.md +78 -0
- package/templates/.agents/skills/webjs/references/styling.md +123 -0
- package/templates/.agents/skills/webjs/references/testing.md +125 -0
- package/templates/.agents/skills/webjs/references/typescript.md +148 -0
- package/templates/.claude/hooks/check-server-imports.mjs +1 -1
- package/templates/.claude/hooks/require-tests-with-src.sh +1 -1
- package/templates/.claude/settings.json +0 -14
- package/templates/.cursorrules +21 -189
- package/templates/.github/copilot-instructions.md +7 -185
- package/templates/.github/pull_request_template.md +1 -1
- package/templates/AGENTS.md +59 -1494
- package/templates/CLAUDE.md +0 -1
- package/templates/CONVENTIONS.md +32 -1383
- package/templates/GEMINI.md +11 -0
- package/templates/gallery/app/apple-icon.ts +0 -1
- package/templates/gallery/app/examples/todo/page.ts +0 -1
- package/templates/gallery/app/features/async-render/page.ts +0 -1
- package/templates/gallery/app/features/boundaries/page.ts +0 -1
- package/templates/gallery/app/features/broadcast/page.ts +0 -1
- package/templates/gallery/app/features/caching/page.ts +0 -1
- package/templates/gallery/app/features/client-router/page.ts +0 -1
- package/templates/gallery/app/features/client-router/second/page.ts +0 -1
- package/templates/gallery/app/features/components/page.ts +0 -1
- package/templates/gallery/app/features/directives/page.ts +0 -1
- package/templates/gallery/app/features/env/page.ts +0 -1
- package/templates/gallery/app/features/file-storage/page.ts +0 -1
- package/templates/gallery/app/features/forms/page.ts +0 -1
- package/templates/gallery/app/features/metadata/page.ts +0 -1
- package/templates/gallery/app/features/optimistic-ui/page.ts +0 -1
- package/templates/gallery/app/features/rate-limit/page.ts +0 -1
- package/templates/gallery/app/features/route-handler/page.ts +0 -1
- package/templates/gallery/app/features/routing/page.ts +0 -1
- package/templates/gallery/app/features/server-actions/page.ts +0 -1
- package/templates/gallery/app/features/service-worker/page.ts +0 -1
- package/templates/gallery/app/features/sessions/page.ts +0 -1
- package/templates/gallery/app/features/websockets/page.ts +0 -1
- package/templates/gallery/app/global-error.ts +0 -1
- package/templates/gallery/app/global-not-found.ts +0 -1
- package/templates/gallery/app/icon.ts +0 -1
- package/templates/gallery/app/manifest.ts +0 -1
- package/templates/gallery/app/opengraph-image.ts +0 -1
- package/templates/gallery/app/robots.ts +0 -1
- package/templates/gallery/app/sitemap.ts +0 -1
- package/templates/gallery/app/twitter-image.ts +0 -1
- package/templates/public/favicon.svg +5 -0
- package/templates/public/sw.js +1 -1
- package/templates/scripts/clear-gallery.mjs +95 -0
- package/lib/clear-placeholders.js +0 -98
- package/lib/design-bar.js +0 -67
- package/templates/.claude/hooks/design-review-before-stop.sh +0 -36
- package/templates/.claude/hooks/route-skills.sh +0 -35
- package/templates/.claude/skills/webjs-design-review/SKILL.md +0 -84
- package/templates/LAYOUT-REFERENCE.md +0 -96
- package/templates/lib/utils/ui.ts +0 -83
package/lib/doctor.js
CHANGED
|
@@ -841,43 +841,7 @@ async function checkElisionCarriers(appDir) {
|
|
|
841
841
|
message:
|
|
842
842
|
`${report.shipped.length} page/layout module(s) ship to the browser instead of being elided:\n` +
|
|
843
843
|
lines.map((l) => ` ${l}`).join('\n'),
|
|
844
|
-
fix: 'Move the client work out of the page/layout closure (into a component, or a .server module reached through an action) so the carrier can be elided, or accept that it ships. See
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* ADVISORY: the delivered app still rides the scaffold shell. AGENTS.md /
|
|
850
|
-
* CONVENTIONS.md item 6 asks a UI app to own its design (layout, palette,
|
|
851
|
-
* typography, chrome); the scaffold is a teaching artifact, not a starting
|
|
852
|
-
* design. WARN-level and never a hard fail: a reading column or a theme toggle
|
|
853
|
-
* CAN be a legitimate choice, so this nudges, it does not gate. The signal is
|
|
854
|
-
* objective (distinctive scaffold-authored chrome strings still present in the
|
|
855
|
-
* root layout), not a judgment of taste. Two or more tells is the threshold.
|
|
856
|
-
* @param {string} appDir
|
|
857
|
-
* @returns {Promise<DoctorResult>}
|
|
858
|
-
*/
|
|
859
|
-
async function checkScaffoldDesign(appDir) {
|
|
860
|
-
const name = 'App design (own design, not the scaffold shell)';
|
|
861
|
-
let layoutSrc = '';
|
|
862
|
-
for (const ext of ['ts', 'js', 'mts', 'mjs']) {
|
|
863
|
-
const p = join(appDir, 'app', `layout.${ext}`);
|
|
864
|
-
if (existsSync(p)) { layoutSrc = await readFile(p, 'utf8').catch(() => ''); break; }
|
|
865
|
-
}
|
|
866
|
-
if (!layoutSrc) {
|
|
867
|
-
return { name, status: 'pass', message: 'no app/layout to analyse' };
|
|
868
|
-
}
|
|
869
|
-
const { scaffoldShellTells } = await import('./design-bar.js');
|
|
870
|
-
const tells = scaffoldShellTells(layoutSrc);
|
|
871
|
-
if (tells.length < 2) {
|
|
872
|
-
return { name, status: 'pass', message: 'app/layout does not look like the unmodified scaffold shell' };
|
|
873
|
-
}
|
|
874
|
-
return {
|
|
875
|
-
name,
|
|
876
|
-
status: 'warn',
|
|
877
|
-
message:
|
|
878
|
-
`app/layout still carries ${tells.length} scaffold design signal(s): ${tells.join(', ')}. ` +
|
|
879
|
-
'A delivered UI app should own its design (layout AND palette), not adapt the scaffold.',
|
|
880
|
-
fix: 'Design the app\'s own layout, palette, typography, and chrome from what the app IS (a centered board, a full-bleed dashboard, ...), not the scaffold\'s exact 760px reading column, its "Built with webjs" attribution footer, or the unmodified starter palette values (the theme-toggle and --header-h are keep-infrastructure). Recoloring the scaffold is not a redesign. Render the app and look at it. See AGENTS.md / CONVENTIONS.md item 6.',
|
|
844
|
+
fix: 'Move the client work out of the page/layout closure (into a component, or a .server module reached through an action) so the carrier can be elided, or accept that it ships. See references/components.md in the skill.',
|
|
881
845
|
};
|
|
882
846
|
}
|
|
883
847
|
|
|
@@ -1054,7 +1018,6 @@ export async function runDoctorChecks(appDir, opts = {}) {
|
|
|
1054
1018
|
checkImportmapCoherence(appDir, opts),
|
|
1055
1019
|
Promise.resolve(checkGitHook(appDir)),
|
|
1056
1020
|
checkElisionCarriers(appDir),
|
|
1057
|
-
checkScaffoldDesign(appDir),
|
|
1058
1021
|
checkStaticAssetFreshness(appDir),
|
|
1059
1022
|
]);
|
|
1060
1023
|
return results;
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "webjs CLI - dev, start, create, db",
|
|
6
6
|
"bin": {
|
|
7
7
|
"webjs": "bin/webjs.js"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepack": "node ../../scripts/sync-scaffold-skill.mjs",
|
|
11
|
+
"postpack": "node ../../scripts/sync-scaffold-skill.mjs --clean"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"bin",
|
|
11
15
|
"lib",
|
|
@@ -1,287 +1,77 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Workspace workflow rules: WebJs app
|
|
2
2
|
|
|
3
|
-
You are working on a
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
You are working on a WebJs app (AI-first, no-build, web-components-first). This
|
|
4
|
+
file is the WORKFLOW contract (git, tests, review). For HOW to build (routing,
|
|
5
|
+
components, actions, styling, the framework API), read
|
|
6
|
+
`.agents/skills/webjs/SKILL.md`, which routes to focused references on demand.
|
|
7
|
+
Read `AGENTS.md` first. Full hosted docs are at https://docs.webjs.dev.
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## Grow the app in place (non-negotiable)
|
|
9
10
|
|
|
10
|
-
- **
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
the
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
`
|
|
25
|
-
|
|
26
|
-
or delete anything. Only after internalising the patterns should you prune,
|
|
27
|
-
keeping and adapting what you need and deleting the rest (the
|
|
28
|
-
`app/features/<name>` or `app/examples/<name>` route AND its `modules/<name>`;
|
|
29
|
-
for the todo app, also the `todos` table). Never delete blindly up front. Each
|
|
30
|
-
route page has a `webjs-scaffold-placeholder` marker so `webjs check` fails
|
|
31
|
-
until you resolve it. Delete now-empty directories after pruning. The `api`
|
|
32
|
-
template ships a BACKEND-features showcase instead (endpoints under
|
|
33
|
-
`app/api/features/`: the `route()` adapter + validation, rate limiting,
|
|
34
|
-
streaming, file storage, WebSockets + broadcast, plus `env.ts` validation),
|
|
35
|
-
listed in the root `app/route.ts` index; prune it the same way.
|
|
36
|
-
- **Prune what the app does not use.** Keep the infrastructure the app USES
|
|
37
|
-
and delete the rest (files AND folders). No persistence means delete `db/`,
|
|
38
|
-
`drizzle.config.ts`, and the `db:*` scripts. No UI kit used means delete
|
|
39
|
-
`components/ui/`, `components.json`, and `lib/utils/cn.ts`. No PWA means
|
|
40
|
-
delete `public/sw.js` and `offline.html`. Always KEEP the durable knowledge
|
|
41
|
-
(`AGENTS.md`, `CONVENTIONS.md`, the rule files, the MCP), never prune it, so
|
|
42
|
-
removing example code never removes your context. Prune AFTER using the
|
|
43
|
-
features and examples as reference, never blindly up front. A no-op for the `api` template
|
|
44
|
-
(no UI kit, no PWA files).
|
|
11
|
+
- **The scaffold is a starting point with a browsable feature gallery.** It ships
|
|
12
|
+
a gallery index home, a root layout, a database wired up, and single-concept
|
|
13
|
+
demos under `app/features/` plus the `app/examples/todo` app (logic in
|
|
14
|
+
`modules/`). The gallery is reference, not part of your product. **Building a
|
|
15
|
+
real app? Learn from the gallery FIRST, then clear it, then build:** (1) skim
|
|
16
|
+
the demos relevant to your task under `app/features/<x>` for the runnable idiom
|
|
17
|
+
(the skill teaches the same and SURVIVES the clear, so you never lose it);
|
|
18
|
+
(2) run `npm run gallery:clear` to shed the whole gallery in one step (it keeps
|
|
19
|
+
the agent skill, the layout, and the database wiring, and resets the home);
|
|
20
|
+
(3) regenerate the database and grow the app in place under `app/`,
|
|
21
|
+
`components/`, and `modules/<feature>/`. Keep the gallery only while exploring,
|
|
22
|
+
never ship it.
|
|
23
|
+
- **Use the wired-up database (Drizzle), never JSON files.** For any data the app
|
|
24
|
+
stores, define a Drizzle table in `db/schema.server.ts`, then
|
|
25
|
+
`npm run db:generate` and `npm run db:migrate`. Never use a JSON file, a
|
|
26
|
+
module-scope array or Map, or localStorage as a database.
|
|
45
27
|
- **`app/` is routing-only.** Only routing files live in `app/` (page, layout,
|
|
46
|
-
route, middleware, metadata routes).
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
tokens, and Tailwind infra, then `${children}` in a bare padded container) with
|
|
53
|
-
NO header, nav, footer, or reading column: design the app's own chrome from
|
|
54
|
-
scratch. Decide whether it needs a header at all, a nav (or none), a footer, a
|
|
55
|
-
sidebar, a centered reading column, or a full-bleed canvas, from what fits the
|
|
56
|
-
app. `LAYOUT-REFERENCE.md` at the project root is a complete worked layout to
|
|
57
|
-
learn the patterns from, then build your own. Two `webjs-scaffold-placeholder`
|
|
58
|
-
markers gate `webjs check`: the minimal shell ("design your layout from
|
|
59
|
-
scratch") and the palette block ("own the colors"), so check fails until each
|
|
60
|
-
is addressed. Keep the design TOKENS and theme wiring in `app/layout.ts`
|
|
61
|
-
(infrastructure the ui kit reads) and set the token VALUES to your own palette;
|
|
62
|
-
run `webjs check --clear-placeholders` to keep the starter palette
|
|
63
|
-
deliberately. Style with Tailwind utilities wherever they reach, and use custom
|
|
64
|
-
CSS only for what utilities cannot express (@theme tokens, @keyframes,
|
|
65
|
-
scrollbar, complex color-mix or gradients). The `api` template has no UI, so
|
|
66
|
-
this does not apply there.
|
|
67
|
-
- **Render the app and LOOK before you call UI work done (every agent, not just one harness).**
|
|
68
|
-
You write CSS blind, so a layout or design defect ships silently: `webjs check`
|
|
69
|
-
and `webjs typecheck` pass even when a component collapses, grid cells are
|
|
70
|
-
uneven, the layout resizes as it fills, or the app just kept the scaffold's
|
|
71
|
-
colors. Static tools give no failure signal for this. The only thing that
|
|
72
|
-
catches it is rendering the app and looking at the pixels. So for ANY page,
|
|
73
|
-
layout, or component work: run it (`webjs dev`), open every route you changed in
|
|
74
|
-
a real browser (drive it with your harness's browser tool or MCP if it has one,
|
|
75
|
-
otherwise open it yourself and screenshot), and PLAY THROUGH every state (empty,
|
|
76
|
-
filled, win, draw, reload, narrow and wide, light and dark). Confirm nothing
|
|
77
|
-
collapses or reflows, that cells stay equal, that the design is the app's OWN,
|
|
78
|
-
and that both themes read. Ship a real-browser test (`webjs test --browser`) for
|
|
79
|
-
the mechanical floor (measure `getBoundingClientRect()` and assert cells stay
|
|
80
|
-
equal across a move). Fix and re-render until it holds, then state in your final
|
|
81
|
-
message what you rendered and confirmed. Claude Code additionally ENFORCES this
|
|
82
|
-
via the `webjs-design-review` skill plus a Stop hook, but the discipline is
|
|
83
|
-
harness-agnostic and this rule is the source of truth for every agent.
|
|
84
|
-
- **Only three templates exist:** `webjs create <name>` (default full-stack),
|
|
85
|
-
`--template api`, `--template saas`. The CLI rejects any other `--template`
|
|
86
|
-
value. Pick:
|
|
87
|
-
- Any product UI (todo, blog, dashboard, marketplace, social) goes through
|
|
88
|
-
the default template.
|
|
89
|
-
- HTTP/JSON API only, no UI, uses `--template api`.
|
|
90
|
-
- Auth / login / signup / SaaS uses `--template saas`.
|
|
28
|
+
route, middleware, metadata routes). Browser-safe helpers go in `lib/utils/`,
|
|
29
|
+
feature logic in `modules/`, server-only code behind `.server.ts`.
|
|
30
|
+
- **Give a UI app its own design.** Set the design-token values in `app/layout.ts`
|
|
31
|
+
to a palette that fits the app. Render the app and LOOK before calling UI work
|
|
32
|
+
done: `webjs check` and `webjs typecheck` pass even when a layout collapses, so
|
|
33
|
+
open every route you changed in a real browser and play through its states.
|
|
91
34
|
|
|
92
35
|
## Before starting ANY work
|
|
93
36
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
checkout. Two agents in one directory collide: a `git checkout` in one moves
|
|
103
|
-
HEAD under the other, so commits land on the wrong branch. Git enforces
|
|
104
|
-
one-branch-per-worktree, so worktrees prevent it. A lone agent in a clean
|
|
105
|
-
checkout may use a plain branch.
|
|
37
|
+
1. Check `git branch --show-current`. If on main or master, create a feature
|
|
38
|
+
branch before editing. If on a feature branch, verify it matches the task.
|
|
39
|
+
2. Sync: `git fetch origin` and `git rebase origin/main` if behind.
|
|
40
|
+
3. If more than one agent may work this repo at once, use a DEDICATED git worktree
|
|
41
|
+
per task (`git worktree add -b <branch> ../<repo>-<slug> origin/main`, work
|
|
42
|
+
there, `git worktree remove` after merge), never a shared checkout. Two agents
|
|
43
|
+
in one directory collide: a `git checkout` in one moves HEAD under the other,
|
|
44
|
+
so commits land on the wrong branch.
|
|
106
45
|
|
|
107
|
-
##
|
|
108
|
-
|
|
109
|
-
If running without interactive approval, auto-decide:
|
|
110
|
-
- On main? Auto-create feature/<task-slug> branch.
|
|
111
|
-
- Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
|
|
112
|
-
- Auto-generate commit messages. Fix failing tests and violations.
|
|
113
|
-
Quality bar stays the same, no blocking on questions.
|
|
114
|
-
|
|
115
|
-
## Mandatory workflow (never skip)
|
|
46
|
+
## Every code change
|
|
116
47
|
|
|
117
|
-
Every code change must include:
|
|
118
48
|
1. Server tests in `test/<feature>/*.test.ts` (node:test).
|
|
119
|
-
2. Browser tests in `test/<feature>/browser/*.test.js`
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
5. Pre-merge self-review loop. Before saying the PR is ready for merge, run
|
|
127
|
-
fresh-context review rounds until one round finds zero issues. Antigravity
|
|
128
|
-
primitive: open a new Cascade thread or a fresh side-panel session for
|
|
129
|
-
each round so the reviewer has no prior context on the implementation
|
|
130
|
-
decisions. Minimum two rounds; rotate focus each round. Skip the loop
|
|
131
|
-
only for one-line trivial changes; skipping on a change that touches
|
|
132
|
-
logic, public surface, build, security, or multiple files is the exact
|
|
133
|
-
failure mode the loop exists to prevent. The full rule, prompt template,
|
|
134
|
-
and reporting contract live in the **Pre-merge self-review loop** section
|
|
135
|
-
of CONVENTIONS.md.
|
|
136
|
-
|
|
137
|
-
The user should never have to ask for tests, documentation, or the
|
|
138
|
-
self-review loop.
|
|
49
|
+
2. Browser tests in `test/<feature>/browser/*.test.js` for hydration, DOM, slots,
|
|
50
|
+
and the client router.
|
|
51
|
+
3. Documentation stays in sync on the SAME PR as the code, never a follow-up.
|
|
52
|
+
4. `webjs check` must pass.
|
|
53
|
+
5. Pre-merge self-review: before saying a PR is ready, run fresh-context review
|
|
54
|
+
rounds until one round finds zero issues (minimum two rounds, rotate focus).
|
|
55
|
+
Skip only for a one-line trivial change.
|
|
139
56
|
|
|
140
57
|
## Git rules
|
|
141
58
|
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
commit before continuing. The Claude Code hook at
|
|
147
|
-
`.claude/hooks/nudge-uncommitted.sh` fires at threshold 4. Antigravity
|
|
148
|
-
users should self-enforce the same rule. Batching multiple logical units
|
|
149
|
-
into one commit is the failure mode this rule exists to prevent.
|
|
59
|
+
- Commit and push per logical unit (one feature, one fix, one rename, one doc
|
|
60
|
+
rewrite), not at the end. Push after every commit.
|
|
61
|
+
- If you have 5 or more unstaged files spanning different concerns, commit before
|
|
62
|
+
continuing.
|
|
150
63
|
- Meaningful commit messages: what changed and why.
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
semicolon
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- Work on feature branches, never push directly to main.
|
|
159
|
-
- Create pull requests for review.
|
|
160
|
-
- NEVER merge any branch without explicit user permission. Always ask:
|
|
161
|
-
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
162
|
-
Wait for approval AND the delete/keep preference. Applies to ALL merges.
|
|
163
|
-
- Run `webjs test` before every commit.
|
|
64
|
+
- Never add Co-Authored-By or AI-attribution trailers.
|
|
65
|
+
- Never use an em-dash (U+2014), a space-surrounded hyphen as a pause, or a
|
|
66
|
+
space-surrounded semicolon as a pause, in commit messages or anywhere. Use a
|
|
67
|
+
period, comma, colon, parentheses, or a restructured phrasing.
|
|
68
|
+
- Work on feature branches, never push directly to main. Open pull requests for
|
|
69
|
+
review. Never merge without explicit user permission (ask which target, and
|
|
70
|
+
whether to delete or keep the branch, then wait for both answers).
|
|
164
71
|
|
|
165
|
-
##
|
|
72
|
+
## Autonomous mode (sandbox / no-prompt)
|
|
166
73
|
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
`erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace`
|
|
172
|
-
with values, constructor parameter properties, legacy decorators with
|
|
173
|
-
`emitDecoratorMetadata`, and `import = require`. Use erasable equivalents:
|
|
174
|
-
`const X = { ... } as const` plus a derived union type instead of `enum`;
|
|
175
|
-
explicit fields plus constructor body assignments instead of parameter
|
|
176
|
-
properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is
|
|
177
|
-
used, the dev server fails at strip time and returns a 500 pointing at the
|
|
178
|
-
`no-non-erasable-typescript` lint rule. WebJs is buildless end-to-end and
|
|
179
|
-
has no bundler fallback.
|
|
180
|
-
- Web components render into light DOM by default (so Tailwind / global CSS
|
|
181
|
-
apply directly). Opt in to shadow DOM per component with
|
|
182
|
-
`static shadow = true` when you need scoped styles (via
|
|
183
|
-
`static styles = css\`...\``) or third-party-embed isolation. `<slot>`
|
|
184
|
-
projection works identically in both modes (named slots, fallback content,
|
|
185
|
-
`assignedNodes` / `slotchange`, first-wins resolution).
|
|
186
|
-
- **Tailwind-first styling.** Tailwind utilities are the strong default for
|
|
187
|
-
pages AND light-DOM components: layout, spacing, color (via `@theme`
|
|
188
|
-
tokens), typography, borders, radius, shadows, interaction states. Light
|
|
189
|
-
DOM does not scope, so utilities apply directly. The lit reflex to scope
|
|
190
|
-
CSS (`static styles = css\`...\``) or write an inline `<style>` with
|
|
191
|
-
semantic class names (`.hero`, `.card`) in a light-DOM component is wrong:
|
|
192
|
-
the scoped block needs `static shadow = true`, and inline class names leak
|
|
193
|
-
globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper
|
|
194
|
-
returning an `` html`...` `` fragment, not a CSS class. Reserve raw CSS for
|
|
195
|
-
the allowlist (design tokens / `@theme`, `@property` + `@keyframes`,
|
|
196
|
-
`::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` /
|
|
197
|
-
gradients); when unavoidable in a light-DOM component, prefix every class
|
|
198
|
-
selector with the component tag. Shadow-DOM components legitimately use
|
|
199
|
-
`static styles = css\`...\`` for scoped CSS.
|
|
200
|
-
- **One theme, canonical tokens.** The app has a SINGLE theme, defined once in
|
|
201
|
-
`app/layout.ts` using the standard `@webjsdev/ui` (shadcn-compatible)
|
|
202
|
-
semantic tokens set to the brand palette. Use the canonical utility names
|
|
203
|
-
everywhere, in the page chrome AND inside components: `bg-background`,
|
|
204
|
-
`text-foreground`, `bg-card`, `bg-muted`, `text-muted-foreground`,
|
|
205
|
-
`bg-primary`, `text-primary-foreground`, `bg-accent`, `text-accent-foreground`,
|
|
206
|
-
`border-border`, `ring-ring`. These are exactly the tokens a component copied
|
|
207
|
-
in by `webjs ui add <name>` reads, so a scaffolded page and a later-added ui
|
|
208
|
-
component share one theme with no wiring. NEVER invent a parallel token
|
|
209
|
-
vocabulary (`--fg`, `--bg`, `text-fg`, `bg-elev`, a separate `--brand`): it
|
|
210
|
-
collides with the ui tokens (the accent once flipped to neutral on navigation
|
|
211
|
-
for exactly this reason) and diverges from the shadcn conventions the kit and
|
|
212
|
-
AI agents expect. Reach for opacity modifiers (`bg-primary/10`,
|
|
213
|
-
`hover:bg-primary/90`) before adding a token; add one the canonical way (a
|
|
214
|
-
`--x` var plus a `--color-x: var(--x)` line in `@theme inline`).
|
|
215
|
-
- Custom-element tag names are passed to `.register('tag-name')`. They are NOT
|
|
216
|
-
a static field on the class.
|
|
217
|
-
- **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.
|
|
218
|
-
- One function per server action file (`*.server.ts`).
|
|
219
|
-
- **`.server.ts` vs `'use server'`, the decision.** Will the client call it?
|
|
220
|
-
Add `'use server'` and the file becomes an RPC action (browser import
|
|
221
|
-
rewritten to a typed stub). Is it server-only infra instead (a DB driver,
|
|
222
|
-
secrets, `node:*`)? Use NO directive, and NEVER import it into a
|
|
223
|
-
page/layout/component. Reach it from a `'use server'` action, `route.ts`, or
|
|
224
|
-
`middleware.ts`. A `.server.ts` WITHOUT the directive is a server-only utility
|
|
225
|
-
whose browser import throws at module load.
|
|
226
|
-
- **Label every interactive control.** Give each control an accessible name, and
|
|
227
|
-
make clickable text a `<label for="control-id">` (or the control itself) so a
|
|
228
|
-
text click activates the control on BOTH the JS path and the no-JS
|
|
229
|
-
form-submit path. Use `aria-label` and `aria-pressed` on icon-only controls.
|
|
230
|
-
`assertNoA11yViolations(el)` in a browser test catches missing labels.
|
|
231
|
-
- Server-only code (a DB driver like `pg`, `node:*`, anything that needs Node APIs)
|
|
232
|
-
goes only in `.server.{js,ts}` files, `route.ts` handlers, or
|
|
233
|
-
`middleware.ts`. Never in pages, layouts, or components. Wrap the access in
|
|
234
|
-
a `.server.{js,ts}` file; the framework rewrites that import into an RPC
|
|
235
|
-
stub for the browser. `lib/` holds both server-only infra
|
|
236
|
-
(the DB in `db/*.server.ts`) and browser-safe utilities (`lib/utils/cn.ts` with
|
|
237
|
-
`cn`); follow the same rule per file. A TYPE-ONLY `import type { Todo } from
|
|
238
|
-
'#db/schema.server.ts'` is the exception, fine in a page or component because
|
|
239
|
-
the stripper erases it before it reaches the browser.
|
|
240
|
-
- Keep pages and layouts as pure carriers so their modules stay out of the
|
|
241
|
-
network tab. A page/layout never hydrates; the framework drops its module
|
|
242
|
-
from the browser as long as its only browser job is registering the
|
|
243
|
-
components it imports. It starts shipping its own module (invisible in tests,
|
|
244
|
-
an elision verdict) the moment its closure does any OTHER client work. So do
|
|
245
|
-
not give a page/layout module-scope client work (a top-level call, a
|
|
246
|
-
`window` / `document` / `customElements` access, a bare side-effect import,
|
|
247
|
-
or a `@webjsdev/core/client-router` import: routing is automatic), and do not
|
|
248
|
-
import a client-global-touching non-component util into it. Put client
|
|
249
|
-
behaviour in a component, server-only code in `.server.{js,ts}`. Self-check:
|
|
250
|
-
`page.ts` / `layout.ts` should not appear in the browser's network tab.
|
|
251
|
-
- Directives: webjs exports the lit directives with no clean native equivalent
|
|
252
|
-
(`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `cache`, `until`, `ref` /
|
|
253
|
-
`createRef`, `templateContent`, `asyncAppend` / `asyncReplace`, `watch`).
|
|
254
|
-
`classMap` / `styleMap` / `ifDefined` / `when` / `choose` are NOT exported.
|
|
255
|
-
For those, use plain template-literal expressions
|
|
256
|
-
(`class=${active ? 'btn active' : 'btn'}`, `style=${'color:' + color}`,
|
|
257
|
-
`${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in
|
|
258
|
-
`firstUpdated`) instead of Lit's `classMap` / `styleMap` / `ref` / `when` /
|
|
259
|
-
`choose` / `guard`.
|
|
260
|
-
- Use Context for cross-component data. For async data in a component, prefer
|
|
261
|
-
an `async render()` (`const u = await getUser(this.uid)`, awaited at SSR so
|
|
262
|
-
the data is in the first paint); keep `Task` for genuinely client-only data.
|
|
263
|
-
- **Progressive enhancement is the default.** Pages AND every web component
|
|
264
|
-
are SSR'd to real HTML. Write components so the first paint is the right
|
|
265
|
-
content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback`
|
|
266
|
-
is never called on the server, so anything there only runs after
|
|
267
|
-
hydration. Initial data for components comes from the page function
|
|
268
|
-
(server-side fetch plus pass as attribute/property) OR from an `async
|
|
269
|
-
render()` in the component itself (preferred over prop-drilling;
|
|
270
|
-
`renderFallback()` is the optional re-fetch loading state, never first
|
|
271
|
-
paint), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` plus
|
|
272
|
-
server action over `fetch` plus click handler. The framework upgrades plain
|
|
273
|
-
forms to partial-swap submissions automatically.
|
|
274
|
-
- **Default to optimistic UI for feasible mutations.** Use `optimistic()` from
|
|
275
|
-
`@webjsdev/core` for create/toggle/like/reorder so the UI updates instantly
|
|
276
|
-
and rolls back on failure (no hand-written try-catch or temp-id bookkeeping).
|
|
277
|
-
Do NOT use it where it hurts: unpredictable or server-computed results,
|
|
278
|
-
side-effectful or OAuth/payment mutations, and destructive irreversible
|
|
279
|
-
actions (confirm-first instead).
|
|
280
|
-
- **Client navigation is auto-magic.** Real `<a href>` and `<form action>`
|
|
281
|
-
get partial-swap behavior with no opt-in. Because layouts persist across
|
|
282
|
-
navigation, put shared chrome (sidenav, header) in `layout.ts` and
|
|
283
|
-
page-specific content in `page.ts`. For validation errors, return 4xx HTML
|
|
284
|
-
from a `route.ts` POST handler; the router renders it in place preserving
|
|
285
|
-
the user's input. For non-layout swap regions, wrap in `<webjs-frame id="...">`. See "Client
|
|
286
|
-
navigation patterns" in AGENTS.md.
|
|
287
|
-
- Full API reference in AGENTS.md.
|
|
74
|
+
If running without interactive approval, auto-decide: on main, auto-create a
|
|
75
|
+
`feature/<task-slug>` branch; auto-rebase if the parent moved; auto-generate
|
|
76
|
+
commit messages; fix failing tests and check violations rather than asking. The
|
|
77
|
+
quality bar stays the same. Only merging into main is gated on user permission.
|