@webjsdev/cli 0.10.47 → 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/lib/create.js +27 -1
- package/package.json +1 -1
- package/templates/.agents/rules/workflow.md +18 -21
- package/templates/.agents/skills/webjs/SKILL.md +2 -2
- package/templates/.agents/skills/webjs/references/auth-and-sessions.md +7 -4
- package/templates/.agents/skills/webjs/references/built-ins.md +1 -1
- package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +4 -3
- package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +1 -1
- package/templates/.agents/skills/webjs/references/service-worker.md +1 -1
- package/templates/.agents/skills/webjs/references/styling.md +8 -3
- package/templates/.agents/skills/webjs/references/testing.md +7 -7
- package/templates/.agents/skills/webjs/references/typescript.md +4 -4
- package/templates/.agents/skills/webjs/references/ui-kit.md +10 -10
- package/templates/.cursorrules +11 -11
- package/templates/AGENTS.md +54 -67
- package/templates/CONVENTIONS.md +7 -6
- package/templates/partials/agents-playbook-api.md +67 -0
- package/templates/partials/agents-playbook-fullstack.md +124 -0
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
|
-
|
|
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
|
@@ -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
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
- **
|
|
33
|
-
a palette that fits the app
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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. `
|
|
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 (`
|
|
216
|
-
- Render the app and LOOK for any UI change: `
|
|
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
|
-
//
|
|
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 '#
|
|
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 '#
|
|
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 {
|
|
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).
|
|
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>${
|
|
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,
|
|
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 (
|
|
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,7 +91,7 @@ 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
|
-
**Default: `
|
|
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
|
|
|
@@ -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
|
-
--
|
|
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
|
-
- `
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
WEBJS_E2E=1
|
|
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
|
-
`
|
|
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
|
|
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
|
-
`
|
|
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 (`
|
|
131
|
+
### The generated route union (`npx webjsdev types`)
|
|
132
132
|
|
|
133
|
-
Run `
|
|
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
|
-
|
|
139
|
+
npx webjsdev types # writes .webjs/routes.d.ts (route count printed)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
`
|
|
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
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Load this when the app uses `@webjsdev/ui` (a `components.json` is present), OR
|
|
4
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 `
|
|
6
|
-
`
|
|
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
7
|
repeated primitive over hand-writing one from scratch. `@webjsdev/ui` is the shadcn-style
|
|
8
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
|
|
10
|
-
|
|
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:
|
|
11
11
|
|
|
12
12
|
- **Tier 1, class helpers (23 components).** Pure functions returning Tailwind
|
|
13
13
|
class strings (`buttonClass({ variant })`, `cardClass()`), composed with
|
|
@@ -32,25 +32,25 @@ paste-ready structure on demand:
|
|
|
32
32
|
kit inventory (each component's tier, helper signatures, npm deps); pass
|
|
33
33
|
`{ name: "accordion" }` for one component's helper signatures, the paste-ready
|
|
34
34
|
structural example, the accessibility header, and deps.
|
|
35
|
-
- **CLI**: `
|
|
35
|
+
- **CLI**: `npx webjsdev ui list` (inventory), `npx webjsdev ui view <name>` (the projected
|
|
36
36
|
view plus the full source). Same data as the MCP tool (one shared projector).
|
|
37
37
|
|
|
38
38
|
So the loop is: `add` the component, then query `ui <name>` (MCP) or
|
|
39
|
-
`
|
|
39
|
+
`npx webjsdev ui view <name>` for the accessible structure, paste it, and fill it in.
|
|
40
40
|
|
|
41
41
|
## Setup and resolution
|
|
42
42
|
|
|
43
|
-
- `
|
|
43
|
+
- `npx webjsdev ui init` writes `components.json`, `lib/utils.ts`, and the CSS design
|
|
44
44
|
tokens the helpers render against (`--background`, `--foreground`,
|
|
45
45
|
`--destructive`, ...). It HARD-FAILS if the tokens cannot be written, so a
|
|
46
46
|
clean exit means the kit is styled. `add` self-heals the tokens if they go
|
|
47
47
|
missing.
|
|
48
48
|
- Resolution is LOCAL-FIRST: `init` / `add` / `list` / `view` read the registry
|
|
49
49
|
that ships inside the installed `@webjsdev/ui`, with no network. This pins you
|
|
50
|
-
to the installed version; run `
|
|
50
|
+
to the installed version; run `npx webjsdev ui diff` to see where your local copies
|
|
51
51
|
drift from the upstream (that command alone compares against the live registry).
|
|
52
52
|
|
|
53
|
-
## Inventory (run `
|
|
53
|
+
## Inventory (run `npx webjsdev ui list` or the MCP `ui` tool for the authoritative, current set)
|
|
54
54
|
|
|
55
55
|
**Tier 1 (class helpers):** accordion, alert, aspect-ratio, avatar, badge,
|
|
56
56
|
breadcrumb, button, card, checkbox, collapsible, input, kbd, label,
|
|
@@ -66,7 +66,7 @@ dropdown-menu, hover-card, sonner, tabs, tooltip, plus toggle and toggle-group
|
|
|
66
66
|
- A helper is a function, so compose it: `class=${buttonClass({ variant: 'outline' })}`.
|
|
67
67
|
The unquoted `${...}` is a normal `html` attribute hole.
|
|
68
68
|
- Tier-1 helpers assume the design tokens exist; if a component paints unstyled,
|
|
69
|
-
the tokens are missing (re-run `
|
|
69
|
+
the tokens are missing (re-run `npx webjsdev ui init` or let `add` self-heal them).
|
|
70
70
|
- Custom elements are display-only-safe at SSR and hydrate in the browser, the
|
|
71
71
|
standard WebJs component model (`references/components.md`).
|
|
72
72
|
|
package/templates/.cursorrules
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# WebJs app rules (Cursor)
|
|
2
2
|
|
|
3
|
-
Cursor reads `AGENTS.md` natively
|
|
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/`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
package/templates/AGENTS.md
CHANGED
|
@@ -1,69 +1,56 @@
|
|
|
1
1
|
# AGENTS.md for {{APP_NAME}}
|
|
2
2
|
|
|
3
|
-
This is a WebJs app: AI-first, web-components-first,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
npm run dev # dev server at http://localhost:8080
|
|
58
|
-
npm run start # production server
|
|
59
|
-
npm test # unit + browser tests
|
|
60
|
-
npm run typecheck
|
|
61
|
-
npx webjsdev check # correctness checks
|
|
62
|
-
npx webjsdev ui add <name> # add a ui-* component on demand
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Data
|
|
66
|
-
|
|
67
|
-
Use the wired-up database (Drizzle). Define real models in
|
|
68
|
-
`db/schema.server.ts`, then `npm run db:generate` and `npm run db:migrate`.
|
|
69
|
-
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.
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
clear),
|
|
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
|
+
```
|