@webjsdev/cli 0.10.46 → 0.10.48

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 CHANGED
@@ -497,28 +497,31 @@ async function main() {
497
497
  break;
498
498
  }
499
499
  case 'ui': {
500
- // Delegate to @webjsdev/ui. Bundled as a hard dependency of
501
- // @webjsdev/cli, so `npm install -g webjsdev` pulls it in
502
- // automatically, and `webjs ui add button` works out of the box
503
- // without an extra install in user projects.
504
- const { createRequire } = await import('node:module');
505
- const req = createRequire(import.meta.url);
500
+ // Delegate to @webjsdev/ui's bin. It is a hard dependency of
501
+ // @webjsdev/cli, so `npm install -g webjsdev` pulls it in automatically
502
+ // and `webjs ui add button` works without an extra install.
503
+ //
504
+ // Resolve via resolveBin, NOT req.resolve('@webjsdev/ui/bin/webjsui.js'):
505
+ // the ui package's `exports` map does not list the bin subpath, so a
506
+ // direct subpath resolve throws ERR_PACKAGE_PATH_NOT_EXPORTED even though
507
+ // the file exists, which surfaced as a misleading "could not be resolved"
508
+ // (#1073). resolveBin resolves the `.` export, walks to the package root,
509
+ // and reads the `bin` map, exactly as `db` / `test --browser` do.
506
510
  let entry;
507
511
  try {
508
- entry = req.resolve('@webjsdev/ui/bin/webjsui.js');
512
+ // Hard-dep path: @webjsdev/ui in the CLI's own node_modules.
513
+ entry = resolveBin(join(__dirname, '..'), '@webjsdev/ui', 'webjsui');
509
514
  } catch {
510
- // Fallback: try resolving from the user's cwd in case of weird
511
- // workspace setups.
515
+ // Fallback: the user installed @webjsdev/ui directly in their project.
512
516
  try {
513
- const userReq = createRequire(join(process.cwd(), 'package.json'));
514
- entry = userReq.resolve('@webjsdev/ui/bin/webjsui.js');
517
+ entry = resolveBin(process.cwd(), '@webjsdev/ui', 'webjsui');
515
518
  } catch {
516
519
  console.error('@webjsdev/ui could not be resolved.');
517
520
  console.error('Reinstall the CLI: npm install -g webjsdev');
518
521
  process.exit(1);
519
522
  }
520
523
  }
521
- const child = spawn('node', [entry, ...rest], { stdio: 'inherit', cwd: process.cwd() });
524
+ const child = spawn(process.execPath, [entry, ...rest], { stdio: 'inherit', cwd: process.cwd() });
522
525
  child.on('exit', (code) => process.exit(code ?? 0));
523
526
  break;
524
527
  }
package/lib/create.js CHANGED
@@ -621,6 +621,16 @@ export async function scaffoldApp(name, cwd, opts = {}) {
621
621
  await mkdir(dirname(join(appDir, dest)), { recursive: true });
622
622
  let content = await readFile(src, 'utf8');
623
623
  content = content.replace(/\{\{APP_NAME\}\}/g, name);
624
+ // AGENTS.md carries a template-specific build playbook: the full-stack
625
+ // (UI) playbook or the api (backend) one. The shared meta-rules (required
626
+ // context-gathering, strict typing, data) live in AGENTS.md itself; only
627
+ // the `{{PLAYBOOK}}` section swaps, so the two stay single-sourced. The
628
+ // partials live under templates/partials/ and are NOT copied as app files.
629
+ if (f === 'AGENTS.md') {
630
+ const playbookFile = isApi ? 'agents-playbook-api.md' : 'agents-playbook-fullstack.md';
631
+ const playbook = await readFile(join(TEMPLATES, 'partials', playbookFile), 'utf8');
632
+ content = content.replace('{{PLAYBOOK}}', () => playbook.trimEnd());
633
+ }
624
634
  if (isBun) {
625
635
  if (PROSE_REWRITE.has(f)) content = bunifyProse(content);
626
636
  else if (FILE_REWRITE[f]) content = FILE_REWRITE[f](content);
@@ -639,7 +649,23 @@ export async function scaffoldApp(name, cwd, opts = {}) {
639
649
  const repoRootSkill = resolve(__dirname, '..', '..', '..', '.agents', 'skills', 'webjs');
640
650
  const skillSrc = existsSync(bundledSkill) ? bundledSkill : repoRootSkill;
641
651
  if (existsSync(skillSrc)) {
642
- await cp(skillSrc, join(appDir, '.agents', 'skills', 'webjs'), { recursive: true });
652
+ const skillDest = join(appDir, '.agents', 'skills', 'webjs');
653
+ await cp(skillSrc, skillDest, { recursive: true });
654
+ // Bun runtime (#541): the skill's runnable commands are authored in the
655
+ // canonical npm forms, so derive the bun flavor on copy, the same way the
656
+ // agent-config markdown above goes through bunifyProse. references/runtime.md
657
+ // is EXCLUDED on purpose: it is the deliberate node-vs-bun command matrix,
658
+ // and rewriting its npm column would destroy the comparison it exists to
659
+ // make.
660
+ if (isBun) {
661
+ const { readdir } = await import('node:fs/promises');
662
+ const entries = await readdir(skillDest, { recursive: true, withFileTypes: true });
663
+ for (const e of entries) {
664
+ if (!e.isFile() || !e.name.endsWith('.md') || e.name === 'runtime.md') continue;
665
+ const p = join(e.parentPath, e.name);
666
+ await writeFile(p, bunifyProse(await readFile(p, 'utf8')));
667
+ }
668
+ }
643
669
  }
644
670
 
645
671
  // Make the Claude enforcement hooks + the git pre-commit executable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.46",
3
+ "version": "0.10.48",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -8,20 +8,17 @@ Read `AGENTS.md` first. Full hosted docs are at https://docs.webjs.dev.
8
8
 
9
9
  ## Grow the app in place (non-negotiable)
10
10
 
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 and the database wiring, and resets the home AND the root
20
- layout to a token-free blank slate, no gallery palette or navbar survives; a
21
- layout you already customised is kept, only its theme-toggle wiring stripped);
22
- (3) regenerate the database and grow the app in place under `app/`,
23
- `components/`, and `modules/<feature>/`. Keep the gallery only while exploring,
24
- never ship it.
11
+ - **Study the shipped examples, then clear them and build.** The scaffold is a
12
+ starting point with a browsable showcase to learn the real idioms from, plus a
13
+ database wired up. A full-stack app ships a UI feature gallery (`app/features/`,
14
+ `app/examples/todo`); the api template ships a backend-features showcase
15
+ (`app/api/features/`), with logic in `modules/`. Building a real app: study the
16
+ parts that match your task (the skill teaches the same and SURVIVES the clear),
17
+ run `npm run gallery:clear` to shed the showcase (it keeps the agent skill and
18
+ the database wiring, and resets to a clean base), then regenerate the database
19
+ and grow the app in place under `app/`, `components/`, and `modules/<feature>/`.
20
+ `AGENTS.md` carries the full template-specific build playbook and the order to
21
+ follow.
25
22
  - **Use the wired-up database (Drizzle), never JSON files.** For any data the app
26
23
  stores, define a Drizzle table in `db/schema.server.ts`, then
27
24
  `npm run db:generate` and `npm run db:migrate`. Never use a JSON file, a
@@ -29,12 +26,12 @@ Read `AGENTS.md` first. Full hosted docs are at https://docs.webjs.dev.
29
26
  - **`app/` is routing-only.** Only routing files live in `app/` (page, layout,
30
27
  route, middleware, metadata routes). Browser-safe helpers go in `lib/utils/`,
31
28
  feature logic in `modules/`, server-only code behind `.server.ts`.
32
- - **Give a UI app its own design.** Define design tokens in `app/layout.ts` with
33
- a palette that fits the app (after `gallery:clear` the layout is a token-free
34
- blank slate; `.agents/skills/webjs/references/styling.md` is the guide).
35
- Render the app and LOOK before calling UI work
36
- done: `webjs check` and `webjs typecheck` pass even when a layout collapses, so
37
- open every route you changed in a real browser and play through its states.
29
+ - **For a UI app, render and LOOK before calling it done.** Define design tokens
30
+ in `app/layout.ts` with a palette that fits the app
31
+ (`.agents/skills/webjs/references/styling.md` is the guide), then open every
32
+ route you changed in a real browser and play through its states.
33
+ `npm run check` and `npm run typecheck` pass even when a layout collapses, so
34
+ the browser is the real check.
38
35
 
39
36
  ## Before starting ANY work
40
37
 
@@ -53,7 +50,7 @@ Read `AGENTS.md` first. Full hosted docs are at https://docs.webjs.dev.
53
50
  2. Browser tests in `test/<feature>/browser/*.test.js` for hydration, DOM, slots,
54
51
  and the client router.
55
52
  3. Documentation stays in sync on the SAME PR as the code, never a follow-up.
56
- 4. `webjs check` must pass.
53
+ 4. `npm run check` must pass.
57
54
  5. Pre-merge self-review: before saying a PR is ready, run fresh-context review
58
55
  rounds until one round finds zero issues (minimum two rounds, rotate focus).
59
56
  Skip only for a one-line trivial change.
@@ -212,8 +212,8 @@ Success is a 303 (PRG); failure re-renders the page at 422 with the result on `a
212
212
  ## Testing Defaults
213
213
 
214
214
  - Prefer server/handler tests first: drive the app with `handle()` from `@webjsdev/server/testing` and assert on the `Response`.
215
- - Add a browser test (`webjs test --browser`) for anything touching hydration, the client router, slots, or custom-element upgrade. A unit test is necessary but NOT sufficient for a browser-facing change.
216
- - Render the app and LOOK for any UI change: `webjs check` and `webjs typecheck` pass even when a layout collapses. Static tools give no signal for a visual defect.
215
+ - Add a browser test (`npm run test:browser`) for anything touching hydration, the client router, slots, or custom-element upgrade. A unit test is necessary but NOT sufficient for a browser-facing change.
216
+ - Render the app and LOOK for any UI change: `npm run check` and `npm run typecheck` pass even when a layout collapses. Static tools give no signal for a visual defect.
217
217
  - WebJs runs on Node 24+ AND Bun. Prove a runtime-sensitive change (serializer, listener, streams, `node:crypto`, the TS stripper) on both.
218
218
 
219
219
  ## Common Mistakes To Avoid
@@ -53,9 +53,10 @@ Configure providers once in a `.server.ts` file. `createAuth` returns
53
53
  `handlers` (the OAuth redirect endpoints).
54
54
 
55
55
  ```ts
56
- // lib/auth.server.ts
56
+ // modules/auth/auth.server.ts
57
57
  import { createAuth, Credentials, Google, GitHub } from '@webjsdev/server';
58
58
  import { db } from '#db/connection.server.ts';
59
+ import { compare } from './password.server.ts';
59
60
 
60
61
  export const { auth, signIn, signOut, handlers } = createAuth({
61
62
  providers: [
@@ -154,7 +155,7 @@ is produced, so a logged-out visitor never receives protected markup.
154
155
  ```ts
155
156
  // app/dashboard/page.ts
156
157
  import { html, redirect } from '@webjsdev/core';
157
- import { auth } from '#lib/auth.server.ts';
158
+ import { auth } from '#modules/auth/auth.server.ts';
158
159
 
159
160
  export default async function Dashboard() {
160
161
  const session = await auth();
@@ -197,7 +198,7 @@ default page.
197
198
  ```ts
198
199
  // app/admin/page.ts
199
200
  import { html, forbidden, unauthorized } from '@webjsdev/core';
200
- import { auth } from '#lib/auth.server.ts';
201
+ import { auth } from '#modules/auth/auth.server.ts';
201
202
 
202
203
  export default async function Admin() {
203
204
  const session = await auth();
@@ -227,8 +228,10 @@ an `ActionResult` failure envelope with a status the client can act on.
227
228
  ```ts
228
229
  // modules/posts/actions/delete-post.server.ts
229
230
  'use server';
230
- import { auth } from '#lib/auth.server.ts';
231
+ import { eq } from 'drizzle-orm';
232
+ import { auth } from '#modules/auth/auth.server.ts';
231
233
  import { db } from '#db/connection.server.ts';
234
+ import { posts } from '#db/schema.server.ts';
232
235
 
233
236
  export async function deletePost(input: { id: string }) {
234
237
  const session = await auth();
@@ -108,7 +108,7 @@ export function WS(ws, req) {
108
108
  }
109
109
  ```
110
110
 
111
- `broadcast(path, data, opts?)` fans out to all clients on `path`; `opts.except` skips one socket (typically the sender). `clientCount(path)` returns the live count. Single-instance by default; wire Redis pub/sub yourself for multi-instance.
111
+ `broadcast(path, data, opts?)` fans out to all clients on `path`; `opts.except` skips one socket (typically the sender). Single-instance by default; wire Redis pub/sub yourself for multi-instance.
112
112
 
113
113
  ## File storage
114
114
 
@@ -142,17 +142,18 @@ Actions: `append` / `prepend` (child of the target id), `before` / `after` (sibl
142
142
  ```ts
143
143
  // app/post/[id]/route.ts
144
144
  import { stream, streamResponse, acceptsStream, broadcast } from '@webjsdev/server';
145
+ import { escapeText } from '@webjsdev/core';
145
146
 
146
147
  export async function POST(req: Request, { params }) {
147
148
  const comment = await addComment(params.id, await req.formData());
148
- const parts = stream.append('comments', `<li>${escapeHtml(comment.text)}</li>`);
149
+ const parts = stream.append('comments', `<li>${escapeText(comment.text)}</li>`);
149
150
  broadcast(`post:${params.id}`, parts); // fan out to every viewer
150
151
  if (acceptsStream(req)) return streamResponse(parts);
151
- return Response.redirect(`/post/${params.id}`, 303); // no-JS fallback
152
+ return Response.redirect(new URL(`/post/${params.id}`, req.url), 303); // no-JS fallback
152
153
  }
153
154
  ```
154
155
 
155
- `stream.*` escapes the target id but NOT the content, so escape any user substring yourself, exactly like an `html` hole.
156
+ `stream.*` escapes the target id but NOT the content, so escape any user substring yourself with `escapeText` (from `@webjsdev/core`), exactly like an `html` hole.
156
157
 
157
158
  ## Streaming (Suspense and RPC)
158
159
 
@@ -24,7 +24,7 @@ In Next, `redirect()` works in Server Components, Actions, and Route Handlers al
24
24
  // route.ts WRONG: redirect() is uncaught here.
25
25
  export async function GET() { redirect('/login'); }
26
26
  // route.ts RIGHT: return a real redirect Response.
27
- export async function GET() { return Response.redirect(new URL('/login', req.url), 303); }
27
+ export async function GET(req: Request) { return Response.redirect(new URL('/login', req.url), 303); }
28
28
  ```
29
29
 
30
30
  Do NOT throw `redirect()` from a page `action` to bounce a form POST either. The method-preserving 307 default re-POSTs the body and re-runs the mutation. Return an `ActionResult` with a `redirect` field instead (a 303 PRG), or throw only for a real external redirect.
@@ -75,4 +75,4 @@ Or rely on the worker's own update lifecycle to phase it out.
75
75
 
76
76
  After adding the registration snippet, load the app, then open the browser devtools Application panel and confirm a worker is registered and activated for the origin. To exercise the offline path, visit a page (so it caches), then toggle offline in devtools and reload: a previously visited page should serve from cache, and an unvisited URL should render `public/offline.html`. Confirm the JS-off baseline is unchanged by disabling JavaScript and checking that no worker registers and navigation still works as a plain server-rendered app.
77
77
 
78
- Do not register the worker until the offline experience is something you actually want, because a registered worker keeps serving cached shells to returning visitors until its cache is evicted by a new deploy build id.
78
+ Do not register the worker until the offline experience is something you actually want. Once registered, a worker changes caching for returning visitors: navigations stay network-first (an online visitor gets fresh server HTML), assets are served stale-while-revalidate, and the cached page shell is served only when the visitor is offline, until a new deploy build id evicts the cache.
@@ -73,7 +73,7 @@ Avoid `@apply`: it hides which utilities a class uses and creates a second sourc
73
73
  An `html`-fragment helper is right for a repeated CHUNK of markup (the rubric above). For a repeated UI PRIMITIVE (button, input, card, badge) that needs variants and sizes, use a class helper instead: a function that returns a Tailwind class STRING you spread onto a native element. That is exactly what `@webjsdev/ui` ships (`buttonClass({ variant, size })`, `cardClass()`, `inputClass()`, `badgeClass({ variant })`), and it is what the scaffold gallery uses in `components/ui/`. To style a ONE-OFF that a variant does not cover (a circular icon button, a pill), compose the helper and override the bespoke bits with `cn()`: `cn(buttonClass({ variant: 'secondary', size: 'none' }), 'w-9 h-9 rounded-full')`. `cn` resolves Tailwind conflicts so a later class wins, including a shorthand over the axis it subsumes (`p-0` beats an earlier `px-4 py-2`), so an override just works. For an icon button prefer `size: 'none'` (it states "I supply my own box" by dropping the helper's padding + radius) over layering a `p-0` on top of the default size.
74
74
 
75
75
  ```ts
76
- // components/ui/button.ts (webjs ui add button, themed to your app)
76
+ // components/ui/button.ts (npx webjsdev ui add button, themed to your app)
77
77
  import { cn } from '#lib/utils/cn.ts';
78
78
  const BASE = 'inline-flex cursor-pointer items-center justify-center ...';
79
79
  const VARIANTS = { default: 'bg-primary text-primary-foreground ...', secondary: '...' } as const;
@@ -91,11 +91,11 @@ html`<button class=${buttonClass({ variant: 'secondary', size: 'sm' })} @click=$
91
91
 
92
92
  Why a class helper (not a `<ui-button>` wrapper): it adds NO indirection, so the element stays native (`@click`, `?disabled`, form submission, focus, a11y all just work) and the markup stays readable, while every button shares one source of truth (so no button can forget `cursor-pointer` or drift). Put the affordance every variant needs (like `cursor-pointer`) on the shared BASE.
93
93
 
94
- **Own and theme your copy.** `webjs ui add <name>` copies the primitive INTO your `components/ui/`, so you own it. Theme it to YOUR app: change the class values so the helper produces YOUR look, rather than bending your app to the kit's defaults. Keep only the parts you use (the gallery's `cardClass` is surface-only, since its panels vary their own padding and layout). Reserve `lib/utils/ui.ts` `html`-fragment helpers for repeated markup chunks; reserve `components/ui/*` class helpers for themed primitives with variants.
94
+ **Default: `npx webjsdev ui add`, then modify. Do not hand-write a primitive from scratch.** For a repeated primitive with variants, run `npx webjsdev ui add <name>` then adapt the copied source (add, remove, restructure, or theme it as your app needs). The scaffold already ships the `cn` prerequisite at `lib/utils/cn.ts`, so `add` works out of the box (a non-scaffold app runs `npx webjsdev ui init` once first to write `components.json`, the `cn` util, and the design tokens). The kit is shadcn-style, so `add` COPIES the helper's source INTO your `components/ui/` and you own and edit it exactly as freely as code you typed yourself. That is the key point: `add`-then-modify and hand-writing end at the SAME place (owned, editable class-helper source), so the difference is only the STARTING POINT. `add` starts you from vetted, variant-complete source you then adapt (and the copied header spells out the primitive's accessibility obligations), where hand-writing starts from a blank file and re-derives all of it for no benefit. You own the copied source and can add, remove, restructure, or theme it however your app needs: change the class values so the helper produces YOUR look (rather than bending your app to the kit's defaults), keep only the parts you use (the gallery's `cardClass` is surface-only, since its panels vary their own padding and layout), and add variants the kit does not ship. Hand-author a primitive yourself ONLY for a one-off the kit does not cover, or a deliberate opt-out of the kit. Reserve `lib/utils/ui.ts` `html`-fragment helpers for repeated markup chunks; reserve `components/ui/*` class helpers for themed primitives with variants.
95
95
 
96
96
  ## Accessible native controls
97
97
 
98
- A cleared, growing app hand-authors its own controls, so accessibility is your job (the `@webjsdev/ui` primitives carry their own, but a raw `<button>` / `<input>` does not). Three habits keep hand-authored interactive markup accessible on BOTH the JS and no-JS paths:
98
+ Even with the kit, an app hand-authors SOME markup (a one-off primitive the kit does not cover, or the native element you wrap a class helper around), and there accessibility is your job (the `@webjsdev/ui` primitives carry their own, but a raw `<button>` / `<input>` does not). Three habits keep hand-authored interactive markup accessible on BOTH the JS and no-JS paths:
99
99
 
100
100
  - **Associate a label with its control.** `<label for="email">` paired with `<input id="email">` (or wrap the control in the `<label>`), so a click on the label focuses the field and a screen reader announces it.
101
101
  - **State a toggle's pressed state.** A button that toggles carries `aria-pressed=${on}` so assistive tech announces on/off, not just "button".
@@ -119,9 +119,14 @@ The default stack is a static compiled Tailwind stylesheet (`css:build` compiles
119
119
  --background: light-dark(#ffffff, #1e2226);
120
120
  --foreground: light-dark(#191c20, #dee2e6);
121
121
  --card: light-dark(#f7f8fa, #313539);
122
+ --primary: light-dark(#1e2226, #dee2e6);
123
+ --secondary: light-dark(#eef0f3, #3a3f45);
124
+ --muted: light-dark(#f1f3f5, #2a2e33);
122
125
  --muted-foreground: light-dark(#565c64, #94989c);
126
+ --accent: light-dark(#e9ecef, #383d43);
123
127
  --border: light-dark(#e2e5e9, #3d434b);
124
- --primary: light-dark(#1e2226, #dee2e6);
128
+ --ring: light-dark(#9aa1a9, #6c737b);
129
+ --destructive: light-dark(#b3261e, #f2b8b5);
125
130
  /* a derived token tracks BOTH themes for free via var(--primary) */
126
131
  --primary-tint: color-mix(in srgb, var(--primary) 22%, transparent);
127
132
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  - The four test layers (unit, browser, e2e, smoke) and where each file lives.
6
6
  - The `handle()` harness from `@webjsdev/server/testing` for driving the real request pipeline against a native `Response`.
7
- - `webjs test` and `webjs test --browser`, plus when a browser or e2e test is REQUIRED (hydration, client router, slots, custom-element upgrade).
7
+ - `npm run test` and `npm run test:browser`, plus when a browser or e2e test is REQUIRED (hydration, client router, slots, custom-element upgrade).
8
8
  - Bun cross-runtime parity for runtime-sensitive code.
9
9
  - Rendering the app and LOOKING for visual defects a static check cannot catch (a collapsed or reflowing layout).
10
10
  - Convention validation with `webjs check`.
@@ -27,12 +27,12 @@ Assert only on what the layer needs. A block that inspects only the HTTP respons
27
27
  ## App runners (`webjs test`)
28
28
 
29
29
  ```sh
30
- webjs test # runtime test runner over everything not under browser/ or e2e/
31
- webjs test --browser # web-test-runner against test/**/browser/**
32
- WEBJS_E2E=1 webjs test # adds the e2e layer
30
+ npm run test # unit + browser tests (both layers; e2e only with WEBJS_E2E=1)
31
+ npm run test:browser # web-test-runner against test/**/browser/**
32
+ WEBJS_E2E=1 npm run test # adds the e2e layer
33
33
  ```
34
34
 
35
- `webjs test` dispatches on the runtime (`node --test` on Node, `bun test` on Bun). The scaffold's `web-test-runner.config.js` globs `test/**/browser/**/*.test.js` and is already wired, so you do not set it up.
35
+ `npm run test` dispatches on the runtime (`node --test` on Node, `bun test` on Bun). The scaffold's `web-test-runner.config.js` globs `test/**/browser/**/*.test.js` and is already wired, so you do not set it up.
36
36
 
37
37
  A scaffolded app has one root `test/` directory shaped the same way (feature first, kind second):
38
38
 
@@ -110,11 +110,11 @@ A layout bug (a board that collapses, cells of unequal size, a grid that resizes
110
110
 
111
111
  WebJs runs on Node 24+ or Bun. The Node suite is the source of truth; an additive Bun matrix re-runs the runtime-sensitive suite under Bun to catch the long tail of cross-runtime incompatibilities (a `node:*` API Bun implements differently, a crypto or stream edge case, an error-message-format quirk).
112
112
 
113
- Bun parity is part of the definition of done. A change to a runtime-sensitive surface (the serializer, the `node:http` vs `Bun.serve` listener and request path, SSR / action / CSRF dispatch, streams, `node:crypto`, the TS stripper, auth / session / cors) is NOT done until you run the Bun matrix green AND add or update a `test/bun/<feature>.mjs` cross-runtime assertion. Run it with `node scripts/run-bun-tests.js` (needs `bun` on PATH).
113
+ If your app targets Bun, Bun parity is part of the definition of done. A change to a runtime-sensitive surface (the serializer, the `node:http` vs `Bun.serve` listener and request path, SSR / action / CSRF dispatch, streams, `node:crypto`, the TS stripper, auth / session / cors) is NOT done until you also run your suite under the Bun runtime (needs `bun` installed) and add a cross-runtime assertion for the touched surface.
114
114
 
115
115
  ## Convention validation (`webjs check`)
116
116
 
117
- `webjs check` is the correctness validator. Every rule catches code that is wrong to ship (a crash, a security leak, a type-strip failure), plus the `no-scaffold-placeholder` sentinel for unreplaced scaffold content. Run it and fix every violation before considering the change done (`webjs check --json` for an agent loop, `webjs check --rules` to list the rules). It is separate from `CONVENTIONS.md`, which carries the customizable project conventions you follow by judgment.
117
+ `npm run check` is the correctness validator. Every rule catches code that is wrong to ship (a crash, a security leak, a type-strip failure), plus the `no-scaffold-placeholder` sentinel for unreplaced scaffold content. Run it and fix every violation before considering the change done (`npm run check -- --json` for an agent loop, `npm run check -- --rules` to list the rules). It is separate from `CONVENTIONS.md`, which carries the customizable project conventions you follow by judgment.
118
118
 
119
119
  ## What NOT to do
120
120
 
@@ -128,18 +128,18 @@ With no route literal (or before you generate route types), `params` is `Record<
128
128
 
129
129
  Type page metadata with the exported `Metadata` type (and `MetadataContext` for the `generateMetadata` argument), the same ergonomics as Next.js's `import type { Metadata } from 'next'`.
130
130
 
131
- ### The generated route union (`webjs types`)
131
+ ### The generated route union (`npx webjsdev types`)
132
132
 
133
- Run `webjs types` to write `.webjs/routes.d.ts`, an opt-in overlay augmenting `@webjsdev/core` with one key per route in `app/`. It narrows two things at tsserver time:
133
+ Run `npx webjsdev types` to write `.webjs/routes.d.ts`, an opt-in overlay augmenting `@webjsdev/core` with one key per route in `app/`. It narrows two things at tsserver time:
134
134
 
135
135
  - The `Route` href type: `navigate('/blog/anything')` passes, `navigate('/nonexistent')` is an error. Until you generate the types, `Route` is `string` (unconstrained, non-breaking for JSDoc and un-generated apps).
136
136
  - Per-route `params`: `PageProps<'/blog/[slug]'>['params']` becomes `{ slug: string }`.
137
137
 
138
138
  ```sh
139
- webjs types # writes .webjs/routes.d.ts (route count printed)
139
+ npx webjsdev types # writes .webjs/routes.d.ts (route count printed)
140
140
  ```
141
141
 
142
- `webjs dev` emits it at startup and re-emits after each route rebuild, so the editor always has fresh types. The file is gitignored (regenerated per machine, like Next's `.next/types`); the scaffold `tsconfig.json` already lists it in `include`. To opt in for an existing app, run `webjs types` once and add `.webjs/routes.d.ts` to `include`. This is webjs's no-build equivalent of Next 15's `typedRoutes`, achieved via interface declaration-merging rather than a bundler.
142
+ `npm run dev` emits it at startup and re-emits after each route rebuild, so the editor always has fresh types. The file is gitignored (regenerated per machine, like Next's `.next/types`); the scaffold `tsconfig.json` already lists it in `include`. To opt in for an existing app, run `npx webjsdev types` once and add `.webjs/routes.d.ts` to `include`. This is the WebJs no-build equivalent of Next 15's `typedRoutes`, achieved via interface declaration-merging rather than a bundler.
143
143
 
144
144
  ### The `webjs` config block and auth user
145
145
 
@@ -1,8 +1,13 @@
1
1
  # The `@webjsdev/ui` component kit
2
2
 
3
- Load this when the app has a `components.json` (it uses `@webjsdev/ui`, the
4
- shadcn-style kit for WebJs). The source is copied into your repo (`components/ui/`),
5
- so you own and edit it. Two tiers:
3
+ Load this when the app uses `@webjsdev/ui` (a `components.json` is present), OR
4
+ when you are about to add a UI primitive (button, card, input, badge) to a fresh
5
+ app that has not initialised the kit yet: running `npx webjsdev ui init` then
6
+ `npx webjsdev ui add <name>` is HOW the kit comes to exist, and it is the default for a
7
+ repeated primitive over hand-writing one from scratch. `@webjsdev/ui` is the shadcn-style
8
+ kit for WebJs. The source is copied into your repo (`components/ui/`), so you own
9
+ and edit it exactly as freely as code you wrote yourself, and can add, remove,
10
+ restructure, or theme it however your app needs. Two tiers:
6
11
 
7
12
  - **Tier 1, class helpers (23 components).** Pure functions returning Tailwind
8
13
  class strings (`buttonClass({ variant })`, `cardClass()`), composed with
@@ -27,25 +32,25 @@ paste-ready structure on demand:
27
32
  kit inventory (each component's tier, helper signatures, npm deps); pass
28
33
  `{ name: "accordion" }` for one component's helper signatures, the paste-ready
29
34
  structural example, the accessibility header, and deps.
30
- - **CLI**: `webjs ui list` (inventory), `webjs ui view <name>` (the projected
35
+ - **CLI**: `npx webjsdev ui list` (inventory), `npx webjsdev ui view <name>` (the projected
31
36
  view plus the full source). Same data as the MCP tool (one shared projector).
32
37
 
33
38
  So the loop is: `add` the component, then query `ui <name>` (MCP) or
34
- `webjs ui view <name>` for the accessible structure, paste it, and fill it in.
39
+ `npx webjsdev ui view <name>` for the accessible structure, paste it, and fill it in.
35
40
 
36
41
  ## Setup and resolution
37
42
 
38
- - `webjs ui init` writes `components.json`, `lib/utils.ts`, and the CSS design
43
+ - `npx webjsdev ui init` writes `components.json`, `lib/utils.ts`, and the CSS design
39
44
  tokens the helpers render against (`--background`, `--foreground`,
40
45
  `--destructive`, ...). It HARD-FAILS if the tokens cannot be written, so a
41
46
  clean exit means the kit is styled. `add` self-heals the tokens if they go
42
47
  missing.
43
48
  - Resolution is LOCAL-FIRST: `init` / `add` / `list` / `view` read the registry
44
49
  that ships inside the installed `@webjsdev/ui`, with no network. This pins you
45
- to the installed version; run `webjs ui diff` to see where your local copies
50
+ to the installed version; run `npx webjsdev ui diff` to see where your local copies
46
51
  drift from the upstream (that command alone compares against the live registry).
47
52
 
48
- ## Inventory (run `webjs ui list` or the MCP `ui` tool for the authoritative, current set)
53
+ ## Inventory (run `npx webjsdev ui list` or the MCP `ui` tool for the authoritative, current set)
49
54
 
50
55
  **Tier 1 (class helpers):** accordion, alert, aspect-ratio, avatar, badge,
51
56
  breadcrumb, button, card, checkbox, collapsible, input, kbd, label,
@@ -61,7 +66,7 @@ dropdown-menu, hover-card, sonner, tabs, tooltip, plus toggle and toggle-group
61
66
  - A helper is a function, so compose it: `class=${buttonClass({ variant: 'outline' })}`.
62
67
  The unquoted `${...}` is a normal `html` attribute hole.
63
68
  - Tier-1 helpers assume the design tokens exist; if a component paints unstyled,
64
- the tokens are missing (re-run `webjs ui init` or let `add` self-heal them).
69
+ the tokens are missing (re-run `npx webjsdev ui init` or let `add` self-heal them).
65
70
  - Custom elements are display-only-safe at SSR and hydrate in the browser, the
66
71
  standard WebJs component model (`references/components.md`).
67
72
 
@@ -1,20 +1,20 @@
1
1
  # WebJs app rules (Cursor)
2
2
 
3
- Cursor reads `AGENTS.md` natively; this file points you at it and the agent
3
+ Cursor reads `AGENTS.md` natively. This file points you at it and the agent
4
4
  skill, and carries the commit rule.
5
5
 
6
6
  - **Read `AGENTS.md` first, then `.agents/skills/webjs/SKILL.md`** (the guide to
7
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`.
8
+ `.agents/skills/webjs/references/`). These are required context, not optional
9
+ reading: WebJs is not React, Next, or Lit, so gather this context before you
10
+ write code.
11
+ - **Study the shipped examples, then clear them and build.** The scaffold ships
12
+ a browsable showcase to learn the real idioms from (a full-stack app ships a
13
+ UI feature gallery, the api template ships a backend-features showcase). Read
14
+ the parts that match your task, run `npm run gallery:clear` to shed the
15
+ showcase and reset to a clean base, then grow the app in place: add routes
16
+ under `app/`, features under `modules/<feature>/`, and keep server-only code
17
+ behind `.server.ts`. `AGENTS.md` carries the full template-specific playbook.
18
18
  - **Use the wired-up database (Drizzle)** for persistence. Never a JSON file, an
19
19
  in-memory array, or localStorage.
20
20
  - **Commit per logical unit** as soon as it is complete, and never commit to
@@ -1,68 +1,56 @@
1
1
  # AGENTS.md for {{APP_NAME}}
2
2
 
3
- This is a WebJs app: AI-first, web-components-first, no build step. Read this
4
- before editing any file. It is deliberately short. The framework knowledge
5
- lives in one place that every AI tool can read.
6
-
7
- ## Building features
8
-
9
- Read `.agents/skills/webjs/SKILL.md` first. It is the guide to building a WebJs
10
- app: it helps you choose the right layer, reach for the right export, and avoid
11
- the WebJs-specific mistakes that Next.js or Lit habits cause. It routes to
12
- focused references under `.agents/skills/webjs/references/` that you load only
13
- when a task needs them. The full hosted docs are at https://docs.webjs.dev.
14
-
15
- ## Grow this app in place
16
-
17
- This scaffold is a starting point. It ships a gallery index home
18
- (`app/page.ts`), a root layout with a neutral design-token palette
19
- (`app/layout.ts`), a database wired up (`db/`), and a densely-commented feature
20
- gallery: single-concept demos under `app/features/` plus the `app/examples/todo`
21
- app, with logic in `modules/`. The gallery is reference to learn the idioms
22
- from, not part of your product.
23
-
24
- **Building a real app? Learn from the gallery FIRST, then clear it, then build.**
25
- The order matters:
26
-
27
- 1. **Gather context.** Skim the demos relevant to your task under
28
- `app/features/<x>` (and `app/examples/todo`) for the runnable idiom. You do
29
- not have to read all of it, and you never lose it: the skill at
30
- `.agents/skills/webjs/` teaches the same patterns and SURVIVES the clear, so
31
- clearing is not a knowledge-loss event, the gallery is just a runnable bonus.
32
- 2. **Clear it.** Run `npm run gallery:clear` to shed the whole gallery in one
33
- step: it removes `app/features/`, `app/examples/`, the demo `modules/`, the
34
- gallery's example design system (`components/ui/`, the theme-toggle), the
35
- example tests, and the demo `todos` table, and resets `app/page.ts` to a
36
- minimal home AND `app/layout.ts` to a token-free blank slate (OS system
37
- colours, no navbar, no palette). A layout you already customised (the gallery
38
- brand removed) is KEPT, with only the theme-toggle wiring stripped. It KEEPS
39
- the agent skill, the database wiring, and `lib/utils/cn.ts` (the
40
- `webjs ui add` prerequisite).
41
- 3. **Build.** Regenerate the database (`npm run db:generate` then `npm run
42
- db:migrate`), then grow the app in place: routes under `app/`, components
43
- under `components/`, features under `modules/<feature>/`, server-only code
44
- behind `.server.ts`. Build the app's OWN design system from the blank slate:
45
- define design tokens in `app/layout.ts` and pull primitives with
46
- `npx webjsdev ui add <name>`, following
47
- `.agents/skills/webjs/references/styling.md`.
48
-
49
- If you are only exploring, keep the gallery and browse it.
50
-
51
- ## Commands
52
-
53
- ```sh
54
- npm install
55
- npm run gallery:clear # shed the demo gallery before building a real app
56
- npm run dev # dev server at http://localhost:8080
57
- npm run start # production server
58
- npm test # unit + browser tests
59
- npm run typecheck
60
- npx webjsdev check # correctness checks
61
- npx webjsdev ui add <name> # add a ui-* component on demand
62
- ```
63
-
64
- ## Data
65
-
66
- Use the wired-up database (Drizzle). Define real models in
67
- `db/schema.server.ts`, then `npm run db:generate` and `npm run db:migrate`.
68
- Never store app data in JSON files, in-memory arrays, or localStorage.
3
+ This is a WebJs app: AI-first, web-components-first, buildless, and
4
+ progressively enhanced. Read this whole file before you edit anything, then
5
+ follow it. The steps here are required, not optional.
6
+
7
+ ## Gather context BEFORE you build (required)
8
+
9
+ WebJs is its own framework. It is not React, Next, or Lit, so writing code from
10
+ that muscle memory produces broken WebJs code. Before you write or change
11
+ anything, gather context from these sources. Do not skip a step to save time.
12
+ This is what separates a working app from a broken one.
13
+
14
+ 1. **Read the skill.** Start with `.agents/skills/webjs/SKILL.md`, then load the
15
+ `references/*.md` files it routes to for the surface you are touching. The
16
+ skill is the guide to building a WebJs app: it helps you choose the right
17
+ layer, reach for the right export, and avoid the mistakes Next.js or Lit
18
+ habits cause. Reading it is never wasted work: it survives the
19
+ gallery-clearing step in the playbook below.
20
+ 2. **Study the shipped examples, then build on a clean slate.** The template
21
+ playbook below says what ships and the exact order to follow. The workflow
22
+ rules (git, tests, review) are in `.agents/rules/workflow.md`; follow them
23
+ too.
24
+ 3. **Read the framework source for exact contracts.** WebJs is 100% buildless
25
+ native ES modules, so the source you run IS the source you read. When you
26
+ need a precise API signature or behavior, open the package source under
27
+ `node_modules/@webjsdev/*` directly (each package ships its own `AGENTS.md`).
28
+ The full hosted docs are at https://docs.webjs.dev.
29
+
30
+ {{PLAYBOOK}}
31
+
32
+ ## Type everything (all templates)
33
+
34
+ Define explicit TypeScript interfaces and discriminated unions for your data
35
+ payloads and action inputs and outputs (and, in a UI app, component props and
36
+ optimistic updates). Narrow an `ActionResult` with
37
+ `if (result.success && result.data)`. Never reach for `any` or a loose
38
+ `as any` cast.
39
+
40
+ Keep server-only code (database drivers, secrets, `node:*` builtins) in
41
+ `.server.ts` modules. There are exactly two kinds:
42
+
43
+ - A `.server.ts` file WITH `'use server';` as its first line is a server
44
+ action: WebJs exposes its exported async functions to browser code as RPC
45
+ calls, so browser modules may import it directly.
46
+ - A `.server.ts` file WITHOUT `'use server'` is a server-only utility:
47
+ importing it from a page, layout, or component CRASHES in the browser at
48
+ module load. Reach it only from `'use server'` actions, `route.ts` handlers,
49
+ or middleware. Never add `'use server'` to a file only other server code
50
+ imports (the DB connection, the schema).
51
+
52
+ ## Data (all templates)
53
+
54
+ Use the wired-up database (Drizzle) for every piece of data the app stores;
55
+ the playbook above has the modeling step. Never store app data in a JSON file,
56
+ an in-memory array, or localStorage.
@@ -17,12 +17,13 @@ is the short version.
17
17
  - **Use the wired-up database (Drizzle).** Define real models in
18
18
  `db/schema.server.ts`, then `npm run db:generate` and `npm run db:migrate`.
19
19
  Never persist to a JSON file, an in-memory array or Map, or localStorage.
20
- - **The scaffold ships a feature gallery to learn from.** Single-concept demos
21
- under `app/features/` plus the `app/examples/todo` app, with logic in
22
- `modules/`. When you build a real app: learn from the gallery FIRST (skim the
23
- demos relevant to your task; the skill teaches the same and survives the
24
- clear), then run `npm run gallery:clear` to shed the demos and reset the home,
25
- then grow the app in place.
20
+ - **The scaffold ships a showcase to learn from.** A full-stack app ships a UI
21
+ feature gallery (`app/features/`, `app/examples/todo`); the api template ships
22
+ a backend-features showcase (`app/api/features/`), with logic in `modules/`.
23
+ When you build a real app, study the parts that match your task (the skill
24
+ teaches the same and survives the clear), run `npm run gallery:clear` to shed
25
+ the showcase, then grow the app in place. `AGENTS.md` has the full
26
+ template-specific playbook.
26
27
  - **Progressive enhancement is the default.** Pages render as HTML, `<a>`
27
28
  navigates, `<form>` + a page action submits, all with JavaScript off; opt into
28
29
  interactivity per behaviour inside a component.
@@ -0,0 +1,67 @@
1
+ ## Build a backend API (api template)
2
+
3
+ This template has NO UI: no layout, no pages, no components, no CSS. It ships a
4
+ backend-features showcase under `app/api/features/`, a set of JSON and HTTP
5
+ endpoints that demonstrate the `route()` adapter, input validation, and rate
6
+ limiting, with logic in `modules/`. Build in this order.
7
+
8
+ ### 1. Study the showcase, then clear it
9
+
10
+ Read the endpoints under `app/api/features/` so you copy the real idiom: a
11
+ `route.ts` handler, the `route()` adapter over a `'use server'` action,
12
+ `validate`, and rate limiting. Then run `npm run gallery:clear` to shed the
13
+ showcase and reset to a clean base. The skill teaches the same patterns and
14
+ survives the clear, so the showcase is a runnable copy you study first, not
15
+ something you lose.
16
+
17
+ ### 2. Model the data
18
+
19
+ Define real models in `db/schema.server.ts`, then run `npm run db:generate` and
20
+ `npm run db:migrate`. Write a seed script at `db/seed.server.ts` and run
21
+ `npm run db:seed` so endpoints return real rows while you build, instead of
22
+ empty arrays. Put reads in `modules/<feature>/queries/*.server.ts` and writes
23
+ in `modules/<feature>/actions/*.server.ts`, one function per file.
24
+
25
+ ### 3. Build endpoints
26
+
27
+ Expose HTTP with a `route.ts` handler (named `GET` / `POST` / `PUT` / `PATCH` /
28
+ `DELETE` exports), each `(request, { params }) => Response | value` (a returned
29
+ plain value auto-JSONs, so return the data directly unless you need headers or
30
+ a status). To publish a `'use server'` action as REST, use the `route()`
31
+ adapter from `@webjsdev/server`, which merges the query, the route params, and
32
+ the JSON body into one input object and JSON-responds. Full reference:
33
+ `.agents/skills/webjs/references/data-and-actions.md` and
34
+ `.agents/skills/webjs/references/routing-and-pages.md`.
35
+
36
+ ### 4. Secure every endpoint
37
+
38
+ A `route.ts` handler is NOT covered by the action-RPC CSRF and error-sanitizing
39
+ layer, so on every mutating endpoint you must: authenticate the request
40
+ (sessions and auth are in `.agents/skills/webjs/references/auth-and-sessions.md`),
41
+ pass a `validate` function, rate-limit it, and log without leaking secrets. For
42
+ cross-origin access use the `cors()` middleware from `@webjsdev/server`; with
43
+ `credentials: true` set an explicit origin allowlist, never `'*'`.
44
+
45
+ ### 5. Verify before you call it done
46
+
47
+ Run each of these and fix what it reports, in order:
48
+
49
+ - `npm run check` (correctness: no browser-import or boundary violation).
50
+ - `npm run typecheck` (zero type errors).
51
+ - `npm test` (unit tests for the endpoints and modules you built).
52
+
53
+ Then boot `npm run dev` and probe each endpoint for the expected status and JSON
54
+ shape.
55
+
56
+ ### Commands
57
+
58
+ ```sh
59
+ npm install
60
+ npm run gallery:clear # shed the backend-features showcase before building
61
+ npm run dev # dev server at http://localhost:8080
62
+ npm run start # production server
63
+ npm test # unit + browser tests
64
+ npm run typecheck
65
+ npm run check # correctness checks
66
+ npm run db:generate && npm run db:migrate
67
+ ```
@@ -0,0 +1,124 @@
1
+ ## Build a full-stack app (default template)
2
+
3
+ This scaffold ships a browsable feature gallery to learn from: single-concept
4
+ demos under `app/features/`, the `app/examples/todo` app, and an example design
5
+ system under `components/ui/`, with logic in `modules/`. Build in this order.
6
+
7
+ ### 1. Study the gallery, then clear it
8
+
9
+ Read the demos under `app/features/` (and `app/examples/todo`) that match what
10
+ you are building, so you copy the real idiom: server actions, queries,
11
+ optimistic UI, component hydration, design tokens. Then run
12
+ `npm run gallery:clear` to shed the whole gallery and reset `app/page.ts` and
13
+ `app/layout.ts` to a blank slate. The clear also removes the example
14
+ `components/ui/` primitives, the demo `todos` table, and the demo migrations;
15
+ it keeps the agent skill, the database wiring, and `lib/utils/cn.ts` (needed by
16
+ `npx webjsdev ui add`). The skill teaches the same patterns, so the gallery is
17
+ a runnable copy you study first, not something you lose.
18
+
19
+ ### 2. Model the data
20
+
21
+ Define real models in `db/schema.server.ts`, then run `npm run db:generate` and
22
+ `npm run db:migrate` (required after the clear, which removed the demo table and
23
+ migrations). Write a seed script at `db/seed.server.ts` and run
24
+ `npm run db:seed` so list and detail pages render real rows while you build,
25
+ instead of empty states. Put reads in `modules/<feature>/queries/*.server.ts`
26
+ and writes in `modules/<feature>/actions/*.server.ts`, one function per file.
27
+
28
+ ### 3. Build a token-based design system
29
+
30
+ Full reference: `.agents/skills/webjs/references/styling.md`.
31
+
32
+ - Define your color tokens as CSS custom properties in `app/layout.ts`, each
33
+ written ONCE with the native CSS `light-dark(LIGHT, DARK)` function, so light
34
+ and dark modes come from one declaration.
35
+ - Define at least: `--background`, `--foreground`, `--card`, `--primary`,
36
+ `--secondary`, `--muted`, `--muted-foreground`, `--accent`, `--border`,
37
+ `--ring`, `--destructive`. Add the matching `*-foreground` pair for each
38
+ surface token you use, following the styling guide's reference palette.
39
+ - Consume colors ONLY as token utilities: `bg-background`, `text-foreground`,
40
+ `bg-card`, `border-border`, `text-primary`, `text-muted-foreground`,
41
+ `bg-destructive`.
42
+ - NEVER put a raw un-themed Tailwind color (`red-500`, `blue-600`, `gray-100`)
43
+ on an element or a `@webjsdev/ui` helper.
44
+ - Add an inline theme-detection script in the layout `<head>` so the first
45
+ paint matches the saved theme with no flash.
46
+
47
+ ### 4. Use the UI kit, do not hand-roll primitives
48
+
49
+ Pull primitives with `npx webjsdev ui add <name>`; the source is copied into
50
+ `components/ui/`, so you own it fully and can add, remove, restructure, or theme
51
+ it however your app needs. Do NOT guess a helper or tag signature. Inspect the
52
+ copied file `components/ui/<name>.ts`, or run
53
+ `npx webjsdev ui view <name>`, for the exact exported names, variants, and
54
+ sizes. The kit has two tiers:
55
+
56
+ - **Tier 1, class helpers** for static primitives (button, card, input, badge,
57
+ native-select, textarea). Spread the helper onto a native element, for example
58
+ `class=${buttonClass({ variant: 'outline', size: 'sm' })}`.
59
+ - **Tier 2, custom elements** for stateful controls and overlays (`<ui-tabs>`,
60
+ `<ui-dialog>`, `<ui-dropdown-menu>`, `<ui-tooltip>`, sonner toasts). Use the
61
+ registered tag; it owns its ARIA, focus trap, and keyboard navigation out of
62
+ the box. Never hand-author a tab strip or a modal when a Tier-2 element
63
+ covers it.
64
+
65
+ Full reference: `.agents/skills/webjs/references/ui-kit.md`.
66
+
67
+ ### 5. Build a multi-page app (MPA), not a single page
68
+
69
+ Structure the product as real routes, not one page that swaps client state:
70
+
71
+ - `/` a home or overview page.
72
+ - `/<resource>` a list page with search, filters, sorting, and a create form or
73
+ modal.
74
+ - `/<resource>/[id]` a detail page for one item.
75
+ - a couple of additional feature pages as the product needs.
76
+
77
+ Give `app/layout.ts` a navbar that links the main pages, pinned with
78
+ `position: fixed` (never `position: sticky`, which flickers on iOS during a
79
+ client-router navigation), and reserve its height on the content with a
80
+ `--header-height` offset. In a list or table, clicking a row or card navigates
81
+ to that item's detail page. Wrap each row action button (edit, delete, status)
82
+ so its handler calls `event.stopPropagation()`, letting the button run its own
83
+ action without also triggering the row navigation.
84
+
85
+ ### 6. Build components for interactivity
86
+
87
+ Pages and layouts (`app/**/page.ts`, `app/**/layout.ts`) are server-only HTML
88
+ generators, so put every interactive behavior inside a `WebComponent` custom
89
+ element. Declare a component's reactive properties in the base-class factory,
90
+ never as a class-field initializer (`items = []` clobbers the reactive
91
+ accessor). Use the shorthand for primitives
92
+ (`extends WebComponent({ name: String, count: Number, open: Boolean })`) and the
93
+ `prop<T>()` helper for typed objects and arrays
94
+ (`extends WebComponent({ items: prop<Item[]>(Array), user: prop<User>(Object) })`).
95
+
96
+ ### 7. Verify before you call it done
97
+
98
+ Run each of these and fix what it reports, in order:
99
+
100
+ - `npm run check` (correctness: no browser-import or boundary violation).
101
+ - `npm run typecheck` (zero type errors).
102
+ - `npm test` (unit and browser tests for the features you built).
103
+ - `npm run css:build` (compile Tailwind).
104
+
105
+ Then boot `npm run dev`, confirm every page route returns HTTP 200, and open
106
+ every route you changed in a real browser and play through its states: `check`
107
+ and `typecheck` pass even when a layout collapses, so the browser is the real
108
+ check for UI work.
109
+
110
+ ### Commands
111
+
112
+ ```sh
113
+ npm install
114
+ npm run gallery:clear # shed the demo gallery before building a real app
115
+ npm run dev # dev server at http://localhost:8080
116
+ npm run start # production server
117
+ npm test # unit + browser tests
118
+ npm run typecheck
119
+ npm run css:build # compile Tailwind
120
+ npm run check # correctness checks
121
+ npx webjsdev ui add <name> # copy a ui primitive into components/ui/
122
+ npx webjsdev ui view <name> # inspect a primitive's exact signature
123
+ npm run db:generate && npm run db:migrate
124
+ ```