@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/templates/.cursorrules
CHANGED
|
@@ -1,189 +1,21 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
single-feature demos under `app/features/` plus one whole example app under
|
|
23
|
-
`app/examples/` (logic in `modules/`). It is your PRIMARY webjs reference:
|
|
24
|
-
read every demo end to end (code AND comments) to learn the idioms BEFORE you
|
|
25
|
-
write or delete anything. Only after internalising the patterns should you
|
|
26
|
-
prune, keeping and adapting what you need and deleting the rest (the
|
|
27
|
-
`app/features/<name>` or `app/examples/<name>` route AND its `modules/<name>`;
|
|
28
|
-
for the todo app, also the `todos` table). Never delete blindly up front. Each
|
|
29
|
-
route page has a `webjs-scaffold-placeholder` marker so `webjs check` fails
|
|
30
|
-
until you resolve it. Delete now-empty directories after pruning. The `api`
|
|
31
|
-
template ships a BACKEND-features showcase instead (endpoints under
|
|
32
|
-
`app/api/features/`: the `route()` adapter + validation, rate limiting,
|
|
33
|
-
streaming, file storage, WebSockets + broadcast, plus `env.ts` validation),
|
|
34
|
-
listed in the root `app/route.ts` index; prune it the same way.
|
|
35
|
-
- **Prune what the app does not use.** Keep the infrastructure the app
|
|
36
|
-
USES and delete the rest (files AND folders). No persistence means
|
|
37
|
-
delete `db/`, `drizzle.config.ts`, and the `db:*` scripts. No UI kit
|
|
38
|
-
used means delete `components/ui/`, `components.json`, and
|
|
39
|
-
`lib/utils/cn.ts`. No PWA means delete `public/sw.js` and
|
|
40
|
-
`offline.html`. Always KEEP the durable knowledge (`AGENTS.md`,
|
|
41
|
-
`CONVENTIONS.md`, the rule files, the MCP), never prune it, so removing
|
|
42
|
-
example code never removes your context. Prune AFTER using the features
|
|
43
|
-
and examples as reference, never blindly up front. A no-op for the `api` template
|
|
44
|
-
(no UI kit, no PWA files).
|
|
45
|
-
- **`app/` is routing-only.** Only routing files live in `app/` (page,
|
|
46
|
-
layout, route, middleware, metadata routes). CSS, helpers, and constants
|
|
47
|
-
do NOT: `globals.css` is at `styles/`, browser-safe helpers at
|
|
48
|
-
`lib/utils/`, feature logic in `modules/`.
|
|
49
|
-
- **Use a unique design, and redesign means more than recolor (UI apps).** Give
|
|
50
|
-
the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
|
|
51
|
-
from what the app IS. `app/layout.ts` ships as a MINIMAL shell (theme, design
|
|
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
|
|
85
|
-
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
86
|
-
other `--template` value. Pick:
|
|
87
|
-
- Any product UI (todo, blog, dashboard, marketplace, social…) → default
|
|
88
|
-
- HTTP/JSON API only, no UI → `--template api`
|
|
89
|
-
- Auth / login / signup / SaaS → `--template saas`
|
|
90
|
-
|
|
91
|
-
## Before starting ANY work
|
|
92
|
-
|
|
93
|
-
FIRST, before writing any code:
|
|
94
|
-
1. Run `git branch --show-current` to check the branch.
|
|
95
|
-
- If on main/master: STOP. Ask the user which branch to use, or create
|
|
96
|
-
one with `git checkout -b feature/<name>`.
|
|
97
|
-
- If on a feature branch: verify it matches the current task. Ask if unsure.
|
|
98
|
-
2. Sync with parent: `git fetch origin && git log HEAD..origin/main --oneline`
|
|
99
|
-
- If upstream has new commits: `git rebase origin/main` before starting.
|
|
100
|
-
- Resolve any conflicts before proceeding with the task.
|
|
101
|
-
3. If more than one agent may work this repo at once, use a DEDICATED git
|
|
102
|
-
worktree per task, not a shared checkout: `git worktree add -b <branch>
|
|
103
|
-
../<repo>-<slug> origin/main`, `cd` in, work there, `git worktree remove`
|
|
104
|
-
after merge. Two agents in one directory collide (a `git checkout` in one
|
|
105
|
-
moves HEAD under the other, so commits land on the wrong branch). A lone
|
|
106
|
-
agent in a clean checkout may use a plain branch.
|
|
107
|
-
|
|
108
|
-
## Autonomous mode (sandbox / no-prompt)
|
|
109
|
-
|
|
110
|
-
If running without interactive approval, auto-decide:
|
|
111
|
-
- On main? Auto-create feature/<task-slug> branch
|
|
112
|
-
- Parent has new commits? Auto-rebase before starting
|
|
113
|
-
- Merge? Auto-merge in autonomous mode, delete feature branches after
|
|
114
|
-
- Commit message? Auto-generate (meaningful, no AI attribution)
|
|
115
|
-
- Tests failing? Fix them. Convention violations? Fix them.
|
|
116
|
-
Quality bar stays the same, no blocking on questions.
|
|
117
|
-
|
|
118
|
-
## Mandatory workflow (never skip)
|
|
119
|
-
|
|
120
|
-
Every code change must include:
|
|
121
|
-
1. Server tests in `test/<feature>/*.test.ts` (node:test).
|
|
122
|
-
2. Browser tests in `test/<feature>/browser/*.test.js` (WTR + Playwright, real Chromium).
|
|
123
|
-
3. Documentation updates. Walk every surface in the **Definition of done**
|
|
124
|
-
section of CONVENTIONS.md (AGENTS.md, CONVENTIONS.md, README.md, docs/,
|
|
125
|
-
website/, scaffold scripts) and either update it or write
|
|
126
|
-
"N/A because <reason>" in the PR body. Docs land on the same PR as the
|
|
127
|
-
code, never as a follow-up.
|
|
128
|
-
4. Convention check: `webjs check` must pass.
|
|
129
|
-
5. Pre-merge self-review loop. Before saying the PR is ready for merge, run
|
|
130
|
-
fresh-context review rounds until one round finds zero issues. Cursor
|
|
131
|
-
primitive: open a NEW composer tab and prompt the review there so the
|
|
132
|
-
reviewer has no prior context on your decisions. Minimum two rounds;
|
|
133
|
-
rotate focus each round. Skip the loop only for one-line trivial
|
|
134
|
-
changes; skipping on a change that touches logic, public surface, build,
|
|
135
|
-
security, or multiple files is the exact failure mode the loop exists
|
|
136
|
-
to prevent. The full rule, prompt template, and reporting contract live
|
|
137
|
-
in the **Pre-merge self-review loop** section of CONVENTIONS.md.
|
|
138
|
-
|
|
139
|
-
The user should never have to ask for tests, documentation, or the
|
|
140
|
-
self-review loop.
|
|
141
|
-
|
|
142
|
-
## Git rules
|
|
143
|
-
|
|
144
|
-
- COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
|
|
145
|
-
one rename, one doc rewrite per commit. Always `git push` after
|
|
146
|
-
committing. The user should never have to ask for a commit.
|
|
147
|
-
- HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
|
|
148
|
-
commit before continuing. Cursor 1.7+ has its own
|
|
149
|
-
`.cursor/hooks/nudge-uncommitted.sh` (afterFileEdit) firing at threshold
|
|
150
|
-
4; the same enforcement runs for Claude users via
|
|
151
|
-
`.claude/hooks/nudge-uncommitted.sh`. On older Cursor versions without
|
|
152
|
-
the hook, self-enforce the same rule. Batching multiple logical units
|
|
153
|
-
into one commit is the failure mode this rule exists to prevent.
|
|
154
|
-
- Write meaningful commit messages: what changed and why, not "update files"
|
|
155
|
-
- NEVER add "Co-Authored-By", "Generated by", "AI-assisted" or similar
|
|
156
|
-
attribution trailers to commits
|
|
157
|
-
- NEVER use em-dashes (U+2014), a hyphen-as-pause (` - `), or a
|
|
158
|
-
semicolon-as-pause (` ; `) in commit messages or anywhere else.
|
|
159
|
-
Rewrite the sentence so no pause-punctuation crutch is needed.
|
|
160
|
-
Use a period, comma, colon, parentheses, or a restructured phrasing.
|
|
161
|
-
Plain hyphens stay fine in compound words, CLI flags, filenames,
|
|
162
|
-
and ranges. Semicolons stay fine inside code
|
|
163
|
-
- Work on feature branches, not main
|
|
164
|
-
- NEVER push directly to main. Create a pull request instead.
|
|
165
|
-
- NEVER merge any branch without explicit user permission. Always ask:
|
|
166
|
-
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
167
|
-
Wait for approval AND the delete/keep preference before proceeding.
|
|
168
|
-
This applies to ALL merges, not just merges into main.
|
|
169
|
-
- Run tests before every commit
|
|
170
|
-
|
|
171
|
-
## Framework rules
|
|
172
|
-
|
|
173
|
-
- No build step: source files are served as ES modules
|
|
174
|
-
- **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback.
|
|
175
|
-
- **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css`) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag.
|
|
176
|
-
- **One theme, canonical tokens.** The app has a SINGLE theme, defined once in `app/layout.ts` using the standard `@webjsdev/ui` (shadcn-compatible) semantic tokens set to the brand palette. Use the canonical utility names everywhere, in the page chrome AND inside components: `bg-background`, `text-foreground`, `bg-card`, `bg-muted`, `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-accent`, `text-accent-foreground`, `border-border`, `ring-ring`. These are exactly the tokens a component copied in by `webjs ui add <name>` reads, so a scaffolded page and a later-added ui component share one theme with no wiring. NEVER invent a parallel token vocabulary (`--fg`, `--bg`, `text-fg`, `bg-elev`, a separate `--brand`): it collides with the ui tokens (the accent once flipped to neutral on navigation for exactly this reason) and diverges from the shadcn conventions the kit and AI agents expect. Reach for opacity modifiers (`bg-primary/10`, `hover:bg-primary/90`) before adding a token; add one the canonical way (a `--x` var plus a `--color-x: var(--x)` line in `@theme inline`).
|
|
177
|
-
- Shadow-DOM components opt in with `static shadow = true` and use `static styles = css` for scoped CSS, not inline styles. That is the right home for scoped CSS.
|
|
178
|
-
- One function per server action file (*.server.ts)
|
|
179
|
-
- **`.server.ts` vs `'use server'`, the decision.** Will the client call it? Add `'use server'` and the file becomes an RPC action (browser import rewritten to a typed stub). Is it server-only infra instead (a DB driver, secrets, node:*)? Use NO directive, and NEVER import it into a page/layout/component. Reach it from a `'use server'` action, `route.ts`, or `middleware.ts`. A `.server.ts` WITHOUT the directive is a server-only utility whose browser import throws at module load.
|
|
180
|
-
- **Label every interactive control.** Give each control an accessible name, and make clickable text a `<label for="control-id">` (or the control itself) so a text click activates the control on BOTH the JS path and the no-JS form-submit path. Use `aria-label` and `aria-pressed` on icon-only controls. `assertNoA11yViolations(el)` in a browser test catches missing labels.
|
|
181
|
-
- Components must call customElements.define('tag', Class)
|
|
182
|
-
- **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.
|
|
183
|
-
- Server-only code (the DB driver `pg`, node:*, anything that needs Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap the access in a .server.{js,ts} file; the framework rewrites that import into an RPC stub for the browser. The DB lives in db/*.server.ts (db/connection.server.ts); lib/ holds other server-only infra and browser-safe utilities (lib/utils/cn.ts with cn); follow the same rule per file: if a lib/ file needs Node APIs, only import it from server-only files. A TYPE-ONLY `import type { Todo } from '#db/schema.server.ts'` is the exception, fine in a page or component because the stripper erases it before it reaches the browser.
|
|
184
|
-
- Directives: webjs exports the lit directives with no clean native equivalent (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `cache`, `until`, `ref` / `createRef`, `templateContent`, `asyncAppend` / `asyncReplace`, `watch`). Lit's `classMap` / `styleMap` / `ifDefined` / `when` / `choose` are NOT exported. For those, use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
|
|
185
|
-
- **Progressive enhancement is the default.** Pages AND every web component are SSR'd to real HTML. Write components so the first paint is the right content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback` is never called on the server, so anything there only runs after hydration. Initial data for components comes from the page function (server-side fetch plus pass as attribute/property) OR from an `async render()` in the component itself (`const u = await getUser(this.uid)`, which SSR awaits so the data is in the first paint), NOT from `fetch` calls in `connectedCallback`. Prefer the co-located `async render()` over prop-drilling; `renderFallback()` is the optional re-fetch loading state (never first paint), and a `Task` is for genuinely client-only data. For write-paths, prefer `<form action=...>` plus server action over `fetch` plus click handler. The framework upgrades plain forms to partial-swap submissions automatically.
|
|
186
|
-
- **Default to optimistic UI for feasible mutations.** Use `optimistic()` from `@webjsdev/core` for create/toggle/like/reorder so the UI updates instantly and rolls back on failure (no hand-written try-catch or temp-id bookkeeping). Do NOT use it where it hurts: unpredictable or server-computed results, side-effectful or OAuth/payment mutations, and destructive irreversible actions (confirm-first instead).
|
|
187
|
-
- **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.
|
|
188
|
-
- **Keep pages and layouts as pure carriers** so their modules stay out of the network tab. A page/layout never hydrates; the framework drops its module from the browser as long as its only browser job is registering the components it imports. It starts shipping its own module (invisible in tests, an elision verdict) the moment its closure does any OTHER client work. Don't give a page/layout module-scope client work (a top-level call, a window/document/customElements access, a bare side-effect import, or a @webjsdev/core/client-router import: routing is automatic) or import a client-global-touching non-component util into it. Put client behaviour in a component, server-only code in .server.{js,ts}. Self-check: page.ts/layout.ts should not appear in the browser's network tab.
|
|
189
|
-
- See AGENTS.md for the complete directive decision guide
|
|
1
|
+
# WebJs app rules (Cursor)
|
|
2
|
+
|
|
3
|
+
Cursor reads `AGENTS.md` natively; this file points you at it and the agent
|
|
4
|
+
skill, and carries the commit rule.
|
|
5
|
+
|
|
6
|
+
- **Read `AGENTS.md` first, then `.agents/skills/webjs/SKILL.md`** (the guide to
|
|
7
|
+
building a WebJs app; it routes to focused references under
|
|
8
|
+
`.agents/skills/webjs/references/` that you load only when a task needs them).
|
|
9
|
+
- **The scaffold ships a browsable feature gallery** (single-concept demos under
|
|
10
|
+
`app/features/` plus the `app/examples/todo` app, with logic in `modules/`).
|
|
11
|
+
It is reference to learn the idioms from, not part of your product.
|
|
12
|
+
- **Building a real app? Learn from the gallery FIRST, then clear it, then
|
|
13
|
+
build.** Skim the demos relevant to your task for the runnable idiom (the skill
|
|
14
|
+
teaches the same and SURVIVES the clear, so you never lose it), then run
|
|
15
|
+
`npm run gallery:clear` to shed the gallery and reset the home, then grow the
|
|
16
|
+
app in place: add routes under `app/`, components under `components/`, features
|
|
17
|
+
under `modules/<feature>/`, and keep server-only code behind `.server.ts`.
|
|
18
|
+
- **Use the wired-up database (Drizzle)** for persistence. Never a JSON file, an
|
|
19
|
+
in-memory array, or localStorage.
|
|
20
|
+
- **Commit per logical unit** as soon as it is complete, and never commit to
|
|
21
|
+
`main`.
|
|
@@ -1,187 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Copilot instructions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
the full hosted docs are at **https://docs.webjs.dev**.
|
|
3
|
+
This is a thin bridge to the single source. GitHub Copilot always reads this
|
|
4
|
+
file; in VS Code it reads `AGENTS.md` directly only when `chat.useAgentsMdFile`
|
|
5
|
+
is enabled, so this bridge keeps Copilot pointed at the instructions regardless.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
(`db/schema.server.ts`, `db/connection.server.ts`, `npm run db:generate` + `npm run db:migrate`). For ANY
|
|
12
|
-
data the app stores (todos, posts, messages, products, comments…),
|
|
13
|
-
define a Drizzle table. 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. It resets on reload and cannot scale. This is a project convention
|
|
16
|
-
(CONVENTIONS.md).
|
|
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
|
-
- **Study the gallery first, prune it second.** The full-stack and saas scaffolds ship
|
|
22
|
-
single-feature demos under `app/features/` plus one whole example app under
|
|
23
|
-
`app/examples/` (logic in `modules/`). It is your PRIMARY webjs reference:
|
|
24
|
-
read every demo end to end (code AND comments) to learn the idioms BEFORE you
|
|
25
|
-
write or delete anything. Only after internalising the patterns should you
|
|
26
|
-
prune, keeping and adapting what you need and deleting the rest (the
|
|
27
|
-
`app/features/<name>` or `app/examples/<name>` route AND its `modules/<name>`;
|
|
28
|
-
for the todo app, also the `todos` table). Never delete blindly up front. Each
|
|
29
|
-
route page has a `webjs-scaffold-placeholder` marker so `webjs check` fails
|
|
30
|
-
until you resolve it. Delete now-empty directories after pruning. The `api`
|
|
31
|
-
template ships a BACKEND-features showcase instead (endpoints under
|
|
32
|
-
`app/api/features/`: the `route()` adapter + validation, rate limiting,
|
|
33
|
-
streaming, file storage, WebSockets + broadcast, plus `env.ts` validation),
|
|
34
|
-
listed in the root `app/route.ts` index; prune it the same way.
|
|
35
|
-
- **Prune what the app does not use.** Keep the infrastructure the app
|
|
36
|
-
USES and delete the rest (files AND folders). No persistence means
|
|
37
|
-
delete `db/`, `drizzle.config.ts`, and the `db:*` scripts. No UI kit
|
|
38
|
-
used means delete `components/ui/`, `components.json`, and
|
|
39
|
-
`lib/utils/cn.ts`. No PWA means delete `public/sw.js` and
|
|
40
|
-
`offline.html`. Always KEEP the durable knowledge (`AGENTS.md`,
|
|
41
|
-
`CONVENTIONS.md`, the rule files, the MCP), never prune it, so removing
|
|
42
|
-
example code never removes your context. Prune AFTER using the features
|
|
43
|
-
and examples as reference, never blindly up front. A no-op for the `api` template
|
|
44
|
-
(no UI kit, no PWA files).
|
|
45
|
-
- **`app/` is routing-only.** Only routing files live in `app/` (page,
|
|
46
|
-
layout, route, middleware, metadata routes). CSS, helpers, and constants
|
|
47
|
-
do NOT: `globals.css` is at `styles/`, browser-safe helpers at
|
|
48
|
-
`lib/utils/`, feature logic in `modules/`.
|
|
49
|
-
- **Use a unique design, and redesign means more than recolor (UI apps).** Give
|
|
50
|
-
the app a design of its own (palette, typography, LAYOUT, and chrome) chosen
|
|
51
|
-
from what the app IS. `app/layout.ts` ships as a MINIMAL shell (theme, design
|
|
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
|
|
85
|
-
full-stack), `--template api`, `--template saas`. The CLI rejects any
|
|
86
|
-
other `--template` value. Pick:
|
|
87
|
-
- Any product UI (todo, blog, dashboard, marketplace, social…) → default
|
|
88
|
-
- HTTP/JSON API only, no UI → `--template api`
|
|
89
|
-
- Auth / login / signup / SaaS → `--template saas`
|
|
90
|
-
|
|
91
|
-
## Before starting ANY work
|
|
92
|
-
|
|
93
|
-
FIRST, before writing any code:
|
|
94
|
-
1. Check `git branch --show-current`.
|
|
95
|
-
- If on main/master: create a feature branch before editing.
|
|
96
|
-
- If on a feature branch: verify it matches the task at hand.
|
|
97
|
-
2. Sync: `git fetch origin && git rebase origin/main` if behind.
|
|
98
|
-
3. If more than one agent may work this repo at once, use a DEDICATED git
|
|
99
|
-
worktree per task (`git worktree add -b <branch> ../<repo>-<slug> origin/main`,
|
|
100
|
-
`cd` in, work there, `git worktree remove` after merge), never a shared
|
|
101
|
-
checkout. Two agents in one directory collide: a `git checkout` in one moves
|
|
102
|
-
HEAD under the other, so commits land on the wrong branch. A lone agent in a
|
|
103
|
-
clean checkout may use a plain branch.
|
|
104
|
-
|
|
105
|
-
## Autonomous mode (sandbox / no-prompt)
|
|
106
|
-
|
|
107
|
-
If running without interactive approval (sandbox, auto-approve, etc.):
|
|
108
|
-
- On main? Auto-create feature/<task-slug> branch
|
|
109
|
-
- Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
|
|
110
|
-
- Auto-generate meaningful commit messages. Fix tests and violations.
|
|
111
|
-
|
|
112
|
-
Quality bar stays the same, no blocking on questions.
|
|
113
|
-
|
|
114
|
-
## Mandatory workflow (never skip)
|
|
115
|
-
|
|
116
|
-
Every code change must include:
|
|
117
|
-
1. Server tests in `test/<feature>/*.test.ts` (node:test).
|
|
118
|
-
2. Browser tests in `test/<feature>/browser/*.test.js` (WTR + Playwright, real Chromium).
|
|
119
|
-
3. Documentation updates. Walk every surface in the **Definition of done**
|
|
120
|
-
section of CONVENTIONS.md (AGENTS.md, CONVENTIONS.md, README.md, docs/,
|
|
121
|
-
website/, scaffold scripts) and either update or write
|
|
122
|
-
"N/A because <reason>" in the PR body. Docs land on the same PR as the
|
|
123
|
-
code, never as a follow-up.
|
|
124
|
-
4. Convention check: `webjs check` must pass.
|
|
125
|
-
5. Pre-merge self-review loop. Before saying the PR is ready for merge,
|
|
126
|
-
run fresh-context review rounds until one round finds zero issues.
|
|
127
|
-
Copilot primitive: open a NEW chat session (reset the side panel) for
|
|
128
|
-
each round so the reviewer has no prior context on the implementation
|
|
129
|
-
decisions. Minimum two rounds; rotate focus each round. Skip the loop
|
|
130
|
-
only for one-line trivial changes; skipping on a change that touches
|
|
131
|
-
logic, public surface, build, security, or multiple files is the exact
|
|
132
|
-
failure mode the loop exists to prevent. The full rule, prompt
|
|
133
|
-
template, and reporting contract live in the **Pre-merge self-review
|
|
134
|
-
loop** section of CONVENTIONS.md.
|
|
135
|
-
|
|
136
|
-
The user should never have to ask for tests, documentation, or the
|
|
137
|
-
self-review loop. The commit-per-logical-unit rule lives under "Git rules"
|
|
138
|
-
below, not here, since it governs how work is grouped rather than what
|
|
139
|
-
each change must include.
|
|
140
|
-
|
|
141
|
-
## Git rules
|
|
142
|
-
|
|
143
|
-
- COMMIT AND PUSH PER LOGICAL UNIT, NOT AT THE END. One feature, one fix,
|
|
144
|
-
one rename, one doc rewrite per commit. Always `git push` after
|
|
145
|
-
committing. The user should never have to ask for a commit.
|
|
146
|
-
- HARD LIMIT: if you have 5+ unstaged files spanning different concerns,
|
|
147
|
-
commit before continuing. The Claude Code hook at
|
|
148
|
-
`.claude/hooks/nudge-uncommitted.sh` enforces threshold 4 for Claude
|
|
149
|
-
users. Copilot has no equivalent hook surface today; self-enforce the
|
|
150
|
-
same rule. Batching multiple logical units into one commit is the
|
|
151
|
-
failure mode this rule exists to prevent.
|
|
152
|
-
- Meaningful commit messages: what changed and why
|
|
153
|
-
- NEVER add Co-Authored-By or AI attribution trailers to commits
|
|
154
|
-
- NEVER use em-dashes (U+2014), a hyphen-as-pause (` - `), or a
|
|
155
|
-
semicolon-as-pause (` ; `) in commit messages or anywhere else.
|
|
156
|
-
Rewrite the sentence so no pause-punctuation crutch is needed. Use a
|
|
157
|
-
period, comma, colon, parentheses, or a restructured phrasing. Plain
|
|
158
|
-
hyphens stay fine in compound words, CLI flags, filenames, and ranges.
|
|
159
|
-
Semicolons stay fine inside code.
|
|
160
|
-
- Work on feature branches, create PRs, never push directly to main
|
|
161
|
-
- NEVER merge any branch without explicit user permission. Always ask:
|
|
162
|
-
"Ready to merge <branch> into <target>? Delete or keep <branch> after?"
|
|
163
|
-
Wait for approval AND the delete/keep preference. Applies to ALL merges.
|
|
164
|
-
- Run `webjs test` before every commit
|
|
165
|
-
|
|
166
|
-
## Framework rules
|
|
167
|
-
|
|
168
|
-
- No build step: source files are served as ES modules. Don't introduce
|
|
169
|
-
build tools or bundlers in the critical path.
|
|
170
|
-
- **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. WebJs is buildless end-to-end and has no bundler fallback.
|
|
171
|
-
- Tagged template: html`<div>${value}</div>` with css`...` for styles.
|
|
172
|
-
- **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css\`...\``) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag. Shadow-DOM components (`static shadow = true`) legitimately author `static styles = css\`...\`` for scoped CSS; don't use inline `style="..."` there.
|
|
173
|
-
- **One theme, canonical tokens.** The app has a SINGLE theme, defined once in `app/layout.ts` using the standard `@webjsdev/ui` (shadcn-compatible) semantic tokens set to the brand palette. Use the canonical utility names everywhere, in the page chrome AND inside components: `bg-background`, `text-foreground`, `bg-card`, `bg-muted`, `text-muted-foreground`, `bg-primary`, `text-primary-foreground`, `bg-accent`, `text-accent-foreground`, `border-border`, `ring-ring`. These are exactly the tokens a component copied in by `webjs ui add <name>` reads, so a scaffolded page and a later-added ui component share one theme with no wiring. NEVER invent a parallel token vocabulary (`--fg`, `--bg`, `text-fg`, `bg-elev`, a separate `--brand`): it collides with the ui tokens (the accent once flipped to neutral on navigation for exactly this reason) and diverges from the shadcn conventions the kit and AI agents expect. Reach for opacity modifiers (`bg-primary/10`, `hover:bg-primary/90`) before adding a token; add one the canonical way (a `--x` var plus a `--color-x: var(--x)` line in `@theme inline`).
|
|
174
|
-
- Components: extend the `WebComponent({ ... })` factory to declare reactive properties (e.g. `extends WebComponent({ count: Number })`; per-prop options via `prop(Number, { reflect: true })`), add `static styles` for shadow-DOM components, call `Class.register('tag-name')` at the bottom of the file. The tag name is the argument to `.register()`, not a static field. A hand-written `static properties` throws at construction (`no-static-properties`); set defaults in the constructor, never a class-field initializer (`reactive-props-no-class-field`). Declare an array-typed prop with the `Array` constructor, not `Object` (`items: prop<Tag[]>(Array)`), flagged by `array-prop-uses-array-type`. **Never extend raw HTMLElement directly for app components.** Always subclass `WebComponent` (or the factory form `WebComponent({...})`) to hook into SSR, lifecycle, elision, and the reactive property system. Extend raw HTMLElement only for rare native-API edge cases (like form-associated `ElementInternals` or customized built-in elements), and add a `webjs-allow-htmlelement: <reason>` comment to acknowledge the exception.
|
|
175
|
-
- Async data in a component: prefer an `async render()` (`const u = await getUser(this.uid)`), which SSR awaits so the data is in the first paint, over prop-drilling from the page or fetching in `connectedCallback`. `renderFallback()` is the optional re-fetch loading state (never first paint); error isolation is automatic (`renderError()` customizes it); a `Task` is for genuinely client-only data.
|
|
176
|
-
- **Default to optimistic UI for feasible mutations.** Use `optimistic()` from `@webjsdev/core` for create/toggle/like/reorder so the UI updates instantly and rolls back on failure (no hand-written try-catch or temp-id bookkeeping). Do NOT use it where it hurts: unpredictable or server-computed results, side-effectful or OAuth/payment mutations, and destructive irreversible actions (confirm-first instead).
|
|
177
|
-
- Server actions: *.server.ts files with one exported async function each.
|
|
178
|
-
- **`.server.ts` vs `'use server'`, the decision.** Will the client call it? Add `'use server'` and the file becomes an RPC action (browser import rewritten to a typed stub). Is it server-only infra instead (a DB driver, secrets, node:*)? Use NO directive, and NEVER import it into a page/layout/component. Reach it from a `'use server'` action, route.ts, or middleware.ts. A `.server.ts` WITHOUT the directive is a server-only utility whose browser import throws at module load.
|
|
179
|
-
- **Label every interactive control.** Give each control an accessible name, and make clickable text a `<label for="control-id">` (or the control itself) so a text click activates the control on BOTH the JS path and the no-JS form-submit path. Use `aria-label` and `aria-pressed` on icon-only controls. `assertNoA11yViolations(el)` in a browser test catches missing labels.
|
|
180
|
-
- Server-only code (a DB driver like pg, node:*, anything needing Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap in a .server.{js,ts} file; the framework rewrites that import to an RPC stub for the browser. The DB lives in db/*.server.ts; lib/ holds other server-only infra and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file. A TYPE-ONLY `import type { Todo } from '#db/schema.server.ts'` is the exception, fine in a page or component because the stripper erases it before it reaches the browser.
|
|
181
|
-
- Directives: webjs exports the lit directives with no clean native equivalent (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `cache`, `until`, `ref` / `createRef`, `templateContent`, `asyncAppend` / `asyncReplace`, `watch`). Lit's `classMap` / `styleMap` / `ifDefined` / `when` / `choose` are NOT exported; use plain template-literal expressions instead.
|
|
182
|
-
- Context: import { createContext, ContextProvider, ContextConsumer } from '@webjsdev/core/context'
|
|
183
|
-
- Task: import { Task, TaskStatus } from '@webjsdev/core/task'
|
|
184
|
-
- Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts).
|
|
185
|
-
- Keep pages and layouts as pure carriers so their modules stay out of the network tab. A page/layout never hydrates; the framework drops its module from the browser as long as its only browser job is registering the components it imports. It starts shipping its own module (invisible in tests, an elision verdict) the moment its closure does any OTHER client work. Don't give a page/layout module-scope client work (a top-level call, a window/document/customElements access, a bare side-effect import, or a @webjsdev/core/client-router import: routing is automatic) or import a client-global-touching non-component util into it. Put client behaviour in a component, server-only code in .server.{js,ts}. Self-check: page.ts/layout.ts should not appear in the network tab.
|
|
186
|
-
- 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 (declared via the `WebComponent({ ... })` factory) are for HTML attributes and .prop=${...} hydration.
|
|
187
|
-
- Don't skip tests or documentation updates.
|
|
7
|
+
The instructions for this app live in `AGENTS.md` (the cross-agent source) and
|
|
8
|
+
the skill at `.agents/skills/webjs/SKILL.md`. Read `AGENTS.md` first, then the
|
|
9
|
+
skill (it routes to focused references on demand).
|
|
@@ -23,7 +23,7 @@ in [`CONVENTIONS.md`](../CONVENTIONS.md) for the full guidance.
|
|
|
23
23
|
- [ ] **Every markdown file in the project** that describes the
|
|
24
24
|
changed surface. Common cases (non-exhaustive): `AGENTS.md` (root
|
|
25
25
|
+ nested), `CONVENTIONS.md`, `README.md` (root + nested),
|
|
26
|
-
`CHANGELOG.md`, `docs/**/*.md`,
|
|
26
|
+
`CHANGELOG.md`, `docs/**/*.md`, `.agents/skills/webjs/**/*.md`,
|
|
27
27
|
`.github/*.md`. The rule is generative: if a markdown file in
|
|
28
28
|
this project mentions a thing this PR changed, it gets touched
|
|
29
29
|
on this PR.
|