@webjsdev/cli 0.10.40 → 0.10.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/webjs.js +4 -46
- package/lib/create.js +282 -479
- package/lib/doctor.js +1 -38
- package/package.json +5 -1
- package/templates/.agents/rules/workflow.md +61 -271
- package/templates/.agents/skills/webjs/SKILL.md +226 -0
- package/templates/.agents/skills/webjs/references/auth-and-sessions.md +220 -0
- package/templates/.agents/skills/webjs/references/built-ins.md +200 -0
- package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +204 -0
- package/templates/.agents/skills/webjs/references/components.md +167 -0
- package/templates/.agents/skills/webjs/references/data-and-actions.md +187 -0
- package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +170 -0
- package/templates/.agents/skills/webjs/references/optimistic-ui.md +128 -0
- package/templates/.agents/skills/webjs/references/routing-and-pages.md +158 -0
- package/templates/.agents/skills/webjs/references/runtime.md +80 -0
- package/templates/.agents/skills/webjs/references/service-worker.md +78 -0
- package/templates/.agents/skills/webjs/references/styling.md +123 -0
- package/templates/.agents/skills/webjs/references/testing.md +125 -0
- package/templates/.agents/skills/webjs/references/typescript.md +148 -0
- package/templates/.claude/hooks/check-server-imports.mjs +1 -1
- package/templates/.claude/hooks/require-tests-with-src.sh +1 -1
- package/templates/.claude/settings.json +0 -14
- package/templates/.cursorrules +21 -189
- package/templates/.github/copilot-instructions.md +7 -185
- package/templates/.github/pull_request_template.md +1 -1
- package/templates/AGENTS.md +59 -1494
- package/templates/CLAUDE.md +0 -1
- package/templates/CONVENTIONS.md +32 -1383
- package/templates/GEMINI.md +11 -0
- package/templates/gallery/app/apple-icon.ts +0 -1
- package/templates/gallery/app/examples/todo/page.ts +0 -1
- package/templates/gallery/app/features/async-render/page.ts +0 -1
- package/templates/gallery/app/features/boundaries/page.ts +0 -1
- package/templates/gallery/app/features/broadcast/page.ts +0 -1
- package/templates/gallery/app/features/caching/page.ts +0 -1
- package/templates/gallery/app/features/client-router/page.ts +0 -1
- package/templates/gallery/app/features/client-router/second/page.ts +0 -1
- package/templates/gallery/app/features/components/page.ts +0 -1
- package/templates/gallery/app/features/directives/page.ts +0 -1
- package/templates/gallery/app/features/env/page.ts +0 -1
- package/templates/gallery/app/features/file-storage/page.ts +0 -1
- package/templates/gallery/app/features/forms/page.ts +0 -1
- package/templates/gallery/app/features/metadata/page.ts +0 -1
- package/templates/gallery/app/features/optimistic-ui/page.ts +0 -1
- package/templates/gallery/app/features/rate-limit/page.ts +0 -1
- package/templates/gallery/app/features/route-handler/page.ts +0 -1
- package/templates/gallery/app/features/routing/page.ts +0 -1
- package/templates/gallery/app/features/server-actions/page.ts +0 -1
- package/templates/gallery/app/features/service-worker/page.ts +0 -1
- package/templates/gallery/app/features/sessions/page.ts +0 -1
- package/templates/gallery/app/features/websockets/page.ts +0 -1
- package/templates/gallery/app/global-error.ts +0 -1
- package/templates/gallery/app/global-not-found.ts +0 -1
- package/templates/gallery/app/icon.ts +0 -1
- package/templates/gallery/app/manifest.ts +0 -1
- package/templates/gallery/app/opengraph-image.ts +0 -1
- package/templates/gallery/app/robots.ts +0 -1
- package/templates/gallery/app/sitemap.ts +0 -1
- package/templates/gallery/app/twitter-image.ts +0 -1
- package/templates/public/favicon.svg +5 -0
- package/templates/public/sw.js +1 -1
- package/templates/scripts/clear-gallery.mjs +95 -0
- package/lib/clear-placeholders.js +0 -98
- package/lib/design-bar.js +0 -67
- package/templates/.claude/hooks/design-review-before-stop.sh +0 -36
- package/templates/.claude/hooks/route-skills.sh +0 -35
- package/templates/.claude/skills/webjs-design-review/SKILL.md +0 -84
- package/templates/LAYOUT-REFERENCE.md +0 -96
- package/templates/lib/utils/ui.ts +0 -83
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# GEMINI.md
|
|
2
|
+
|
|
3
|
+
Gemini CLI reads `GEMINI.md`, not `AGENTS.md`, by default, so this file is a
|
|
4
|
+
thin bridge to the single source.
|
|
5
|
+
|
|
6
|
+
The instructions for this app live in `AGENTS.md` (the cross-agent source) and
|
|
7
|
+
the skill at `.agents/skills/webjs/SKILL.md`. Read `AGENTS.md` first, then the
|
|
8
|
+
skill (it routes to focused references on demand).
|
|
9
|
+
|
|
10
|
+
To have Gemini read `AGENTS.md` directly instead of this bridge, add it to
|
|
11
|
+
`context.fileName` in `.gemini/settings.json`.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Example gallery route. Keep and adapt it, or prune it (delete this app/examples/todo route, modules/todo, AND the todos table in db/schema.server.ts), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// A THIN route adapter: app/ is routing only. It fetches the initial data
|
|
3
2
|
// (server-side) via the 'use server' query and renders the interactive
|
|
4
3
|
// component, plus a page `action` for the no-JS write path. All the real logic
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/async-render route AND modules/async-render), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html, Suspense } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/async-render/components/server-clock.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/boundaries route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Boundaries: the four control-flow throws and their boundary files. A page (or
|
|
3
2
|
// a page `action`) can THROW to short-circuit rendering, and the framework
|
|
4
3
|
// renders the NEAREST matching boundary file in the route's ancestor chain
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/broadcast route AND modules/broadcast), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Broadcast: fan a message out to EVERY client connected to a WebSocket path,
|
|
3
2
|
// not just the sender. The framework auto-registers each connection to its path,
|
|
4
3
|
// so broadcast(path, data) from '@webjsdev/server' reaches all of them. This is
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/caching route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Caching: `export const revalidate = N` opts the page into the server HTML
|
|
3
2
|
// response cache, keyed by URL for N seconds. The rendered timestamp below only
|
|
4
3
|
// changes once per window: reload inside 10s and it is identical, reload after
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/client-router route AND its second/ subpage), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Client router: automatic. It auto-enables the moment @webjsdev/core loads in
|
|
3
2
|
// the browser (the bundle every component pulls, so any page with a component
|
|
4
3
|
// gets it for free). There is nothing to import. An <a href> to another page
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route (client-router page two). Pruned together with the parent app/features/client-router route. Delete this marker line once you adapt or remove it. webjs check fails while the marker remains.
|
|
2
1
|
// The soft-navigation target for the client-router demo. A plain page: the
|
|
3
2
|
// router needs no per-page code. The browser Back button restores this page and
|
|
4
3
|
// its scroll position from the client-router snapshot cache.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/components route AND modules/components), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/components/components/counter-card.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/directives route AND modules/directives), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/directives/components/directive-demo.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/env route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Environment variables: process.env.X reads are server-only. NODE_ENV is
|
|
3
2
|
// defined on both sides. A name prefixed WEBJS_PUBLIC_ is exposed to the browser
|
|
4
3
|
// through an inline script (no build step); everything else stays server-side
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/file-storage route AND modules/file-storage), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// File storage: a no-JS upload. A multipart <form> posts to this page's `action`
|
|
3
2
|
// (the progressive-enhancement write path); the action calls a 'use server'
|
|
4
3
|
// helper that streams the bytes into the FileStore. On success it redirects
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/forms route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// forms: the no-JS write path. A real <form method="post"> posts to this page's
|
|
3
2
|
// `action` export, and the framework re-renders the SAME page with the result on
|
|
4
3
|
// `actionData`. WHY it matters: the form works with JS OFF (server round-trip),
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/metadata route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Metadata: how a page declares its <title>, description, and Open Graph tags.
|
|
3
2
|
// A static `metadata` export is read as-is. `generateMetadata(ctx)` takes
|
|
4
3
|
// precedence and can read the request (params, searchParams, url) to compute
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/optimistic-ui route AND modules/optimistic-ui), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/optimistic-ui/components/like-button.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/rate-limit route AND modules/rate-limit), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Rate limiting: rateLimit() from '@webjsdev/server' is a middleware. It lives in
|
|
3
2
|
// a middleware.ts scoped to the endpoint it protects (here app/features/
|
|
4
3
|
// rate-limit/ping/middleware.ts, so it limits /features/rate-limit/ping WITHOUT
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/route-handler route, including its data/route.ts handler), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/route-handler/components/rich-data.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/routing route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Routing basics: a static page that links to a dynamic route. app/ is routing
|
|
3
2
|
// only; a folder maps to a URL segment, and [id] is a dynamic segment read from
|
|
4
3
|
// `params`. See app/features/routing/[id]/page.ts.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/server-actions route AND modules/server-actions), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
import { html } from '@webjsdev/core';
|
|
3
2
|
import type { Metadata } from '@webjsdev/core';
|
|
4
3
|
import '#modules/server-actions/components/greeter.ts';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/service-worker route), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Service worker: opt-in progressive enhancement. webjs does NOT register one by
|
|
3
2
|
// default (an active SW caches aggressively and would confuse dev). You opt in
|
|
4
3
|
// by adding public/sw.js and registering it from a component's connectedCallback
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/sessions route AND modules/sessions), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// Sessions: a per-segment middleware.ts applies session() (a signed cookie by
|
|
3
2
|
// default; store-backed for larger sessions), and a route.ts reads/writes it
|
|
4
3
|
// with getSession(req). Session state is per-user, so it lives on the server
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// webjs-scaffold-placeholder. Feature gallery route. Keep and adapt it, or prune it (delete this app/features/websockets route AND modules/websockets), then delete this marker line. webjs check fails while the marker remains.
|
|
2
1
|
// WebSockets: a route.ts exports WS(ws, req) for the server endpoint, and a
|
|
3
2
|
// component uses connectWS() to talk to it. WebSockets are inherently JS-only
|
|
4
3
|
// (there is no no-JS fallback for a live socket), so the component degrades to a
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="WebJs">
|
|
2
|
+
<rect width="32" height="32" rx="7" fill="#e9edf0"/>
|
|
3
|
+
<path d="M8 11.5l4.5 4.5L8 20.5" fill="none" stroke="#1e2226" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<line x1="15.5" y1="21.5" x2="23" y2="21.5" stroke="#1e2226" stroke-width="2.6" stroke-linecap="round"/>
|
|
5
|
+
</svg>
|
package/templates/public/sw.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This adds an offline fallback and an asset cache WITHOUT changing the
|
|
5
5
|
* JavaScript-disabled baseline: with JS off no service worker registers, so
|
|
6
6
|
* pages, links, and forms behave exactly as they do today. It is registered
|
|
7
|
-
* explicitly (see the opt-in snippet in
|
|
7
|
+
* explicitly (see the opt-in snippet in the skill's references/service-worker.md), never
|
|
8
8
|
* automatically.
|
|
9
9
|
*
|
|
10
10
|
* Strategy:
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Clear the scaffold's feature gallery to a clean, buildable base.
|
|
3
|
+
//
|
|
4
|
+
// The scaffold ships a browsable gallery (single-concept demos under
|
|
5
|
+
// app/features/, the app/examples/todo app, their modules/, and example
|
|
6
|
+
// metadata routes) so an agent can learn the idioms from real, running code.
|
|
7
|
+
// The gallery is reference, NOT part of your product. Learn from the demos
|
|
8
|
+
// relevant to your task first (the skill at .agents/skills/webjs/ teaches the
|
|
9
|
+
// same patterns and SURVIVES this reset, so nothing is lost), then run this
|
|
10
|
+
// once to shed the gallery, then grow the app in place.
|
|
11
|
+
//
|
|
12
|
+
// It removes the gallery routes + modules + demo metadata routes, resets
|
|
13
|
+
// app/page.ts to a minimal home, and drops the demo `todos` table from the
|
|
14
|
+
// schema. It KEEPS the agent skill (.agents/skills/webjs/), the layout, the
|
|
15
|
+
// database wiring, the theme toggle, and (for the saas template) the auth
|
|
16
|
+
// modules. It is a one-time reset: if the gallery is already gone (no
|
|
17
|
+
// app/features/) it does nothing, so a rerun never clobbers an app you built.
|
|
18
|
+
import { rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
const root = process.cwd();
|
|
22
|
+
const rm = (p) => { if (existsSync(join(root, p))) { rmSync(join(root, p), { recursive: true, force: true }); return true; } return false; };
|
|
23
|
+
|
|
24
|
+
// Guard: the gallery is identified by app/features/. If it is absent, the gallery
|
|
25
|
+
// was already cleared (or this is not a gallery scaffold), so exit before any
|
|
26
|
+
// destructive write. This keeps a rerun safe and never clobbers a built app (a
|
|
27
|
+
// customized app/page.ts, a same-named module, or real migrations).
|
|
28
|
+
if (!existsSync(join(root, 'app/features'))) {
|
|
29
|
+
console.log('No gallery found (app/features/ is absent); nothing to clear.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 1) Gallery route trees + example metadata routes.
|
|
34
|
+
const galleryPaths = [
|
|
35
|
+
'app/features', 'app/examples', 'app/sitemaps',
|
|
36
|
+
'app/icon.ts', 'app/apple-icon.ts', 'app/manifest.ts', 'app/opengraph-image.ts',
|
|
37
|
+
'app/twitter-image.ts', 'app/robots.ts', 'app/sitemap.ts',
|
|
38
|
+
'app/global-error.ts', 'app/global-not-found.ts',
|
|
39
|
+
];
|
|
40
|
+
// 2) The gallery's feature modules (by name, so saas auth modules survive).
|
|
41
|
+
const galleryModules = [
|
|
42
|
+
'async-render', 'broadcast', 'caching', 'client-router', 'components',
|
|
43
|
+
'directives', 'file-storage', 'optimistic-ui', 'rate-limit', 'route-handler',
|
|
44
|
+
'server-actions', 'sessions', 'todo', 'websockets',
|
|
45
|
+
].map((m) => `modules/${m}`);
|
|
46
|
+
|
|
47
|
+
let removed = 0;
|
|
48
|
+
for (const p of [...galleryPaths, ...galleryModules]) if (rm(p)) removed++;
|
|
49
|
+
|
|
50
|
+
// 3) Reset app/page.ts to a minimal home (no gallery grid, no dead links).
|
|
51
|
+
writeFileSync(join(root, 'app/page.ts'), MINIMAL_PAGE());
|
|
52
|
+
|
|
53
|
+
// 4) Drop the demo `todos` table from the schema (keep everything else).
|
|
54
|
+
const schemaPath = join(root, 'db/schema.server.ts');
|
|
55
|
+
if (existsSync(schemaPath)) {
|
|
56
|
+
let s = readFileSync(schemaPath, 'utf8');
|
|
57
|
+
s = s.replace(/\n(?:\/\/[^\n]*\n)*export const todos = table\('todos',[\s\S]*?\n\}\);\n/, '\n');
|
|
58
|
+
s = s.replace(/defineRelations\(\{ users, todos \}/, 'defineRelations({ users }');
|
|
59
|
+
writeFileSync(schemaPath, s);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 5) Drop generated migrations + the dev database so the next db:generate is
|
|
63
|
+
// clean against the reset schema (safe: the scaffold has no real data yet).
|
|
64
|
+
rm('db/migrations');
|
|
65
|
+
for (const f of ['db/dev.db', 'db/dev.db-shm', 'db/dev.db-wal']) rm(f);
|
|
66
|
+
|
|
67
|
+
console.log(`Gallery cleared (${removed} paths removed). The agent skill and your database wiring are kept.`);
|
|
68
|
+
console.log('Next: regenerate the database (db:generate then db:migrate), then start the dev server and build your app in app/ and modules/.');
|
|
69
|
+
|
|
70
|
+
function MINIMAL_PAGE() {
|
|
71
|
+
return `import { html } from '@webjsdev/core';
|
|
72
|
+
import '#components/theme-toggle.ts';
|
|
73
|
+
|
|
74
|
+
export const metadata = {
|
|
75
|
+
title: 'Home',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default function Home() {
|
|
79
|
+
return html\`
|
|
80
|
+
<div class="fixed top-4 right-4 z-10"><theme-toggle></theme-toggle></div>
|
|
81
|
+
<div class="max-w-2xl mx-auto px-6 py-24 flex flex-col items-center text-center gap-6">
|
|
82
|
+
<h1 class="text-4xl font-bold tracking-tight m-0">Your app</h1>
|
|
83
|
+
<p class="text-base text-muted-foreground leading-relaxed m-0">
|
|
84
|
+
The gallery is cleared. This is <code class="text-[0.9em] text-foreground">app/page.ts</code>. Build your
|
|
85
|
+
app from here. The guide is <code class="text-[0.9em] text-foreground">.agents/skills/webjs/SKILL.md</code>.
|
|
86
|
+
</p>
|
|
87
|
+
<nav class="flex items-center gap-5 text-sm text-muted-foreground">
|
|
88
|
+
<a href="https://docs.webjs.dev" class="hover:text-foreground transition-colors no-underline">Docs</a>
|
|
89
|
+
<a href="https://github.com/webjsdev/webjs" class="hover:text-foreground transition-colors no-underline">GitHub</a>
|
|
90
|
+
</nav>
|
|
91
|
+
</div>
|
|
92
|
+
\`;
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// `webjs check --clear-placeholders`: acknowledge the scaffold gallery in one
|
|
2
|
-
// command instead of hand-editing every demo file. A fresh scaffold trips
|
|
3
|
-
// `no-scaffold-placeholder` on every unadapted file at once, and the rule's
|
|
4
|
-
// sanctioned "deliberately keep it, then delete the marker line" path otherwise
|
|
5
|
-
// means one manual edit per file. This strips the marker comment lines so the
|
|
6
|
-
// gate goes green while the demo CODE is kept verbatim (it does NOT prune the
|
|
7
|
-
// gallery; deleting a demo you do not want stays a deliberate `rm`).
|
|
8
|
-
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
// Assembled so THIS source does not itself carry the contiguous literal (the
|
|
12
|
-
// check scans raw source, and this file ships in the published CLI).
|
|
13
|
-
export const MARKER = 'webjs-scaffold-' + 'placeholder';
|
|
14
|
-
|
|
15
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', '.webjs', 'graphify-out', 'dist']);
|
|
16
|
-
|
|
17
|
-
// Every scaffold marker's text ends with this closing clause (wrapped across
|
|
18
|
-
// lines or not), so it precisely bounds the marker sentence. See create.js /
|
|
19
|
-
// api-gallery.js where the markers are emitted.
|
|
20
|
-
const TERMINATOR = 'the marker remains';
|
|
21
|
-
|
|
22
|
-
// Strip comment punctuation and collapse whitespace so a marker wrapped across
|
|
23
|
-
// several `//` or `<!-- -->` lines can be matched as one normalized sentence.
|
|
24
|
-
const norm = (line) => line.replace(/<!--|-->|\/\//g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
25
|
-
const isLineComment = (line) => /^\s*\/\//.test(line);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Pure: drop exactly the marker COMMENT that carries the token, not just the
|
|
29
|
-
* token line and NOT the adjacent educational comment/code. The marker is not
|
|
30
|
-
* always one line (the layout footer is a multi-line `<!-- ... -->`, and the
|
|
31
|
-
* global-* markers wrap across `//` lines), and it is often immediately followed
|
|
32
|
-
* by a SEPARATE comment paragraph, so a greedy "remove the whole comment run"
|
|
33
|
-
* over-removes. The marker sentence always ends in TERMINATOR, so extend from
|
|
34
|
-
* the token line only to the line that completes that clause (or, if the clause
|
|
35
|
-
* is somehow absent, remove just the token line rather than over-reaching).
|
|
36
|
-
* @param {string} content
|
|
37
|
-
* @param {string} [marker]
|
|
38
|
-
* @returns {{ content: string, removed: number, markers: number }}
|
|
39
|
-
*/
|
|
40
|
-
export function stripPlaceholderMarkers(content, marker = MARKER) {
|
|
41
|
-
const lines = content.split('\n');
|
|
42
|
-
const kept = [];
|
|
43
|
-
let removed = 0;
|
|
44
|
-
let markers = 0;
|
|
45
|
-
for (let i = 0; i < lines.length; i++) {
|
|
46
|
-
const line = lines[i];
|
|
47
|
-
if (!line.includes(marker)) { kept.push(line); continue; }
|
|
48
|
-
markers += 1;
|
|
49
|
-
let acc = norm(line);
|
|
50
|
-
let end = i;
|
|
51
|
-
if (!acc.includes(TERMINATOR)) {
|
|
52
|
-
// Extend over the marker's own comment (contiguous `//`, or up to the HTML
|
|
53
|
-
// `-->`) until the closing clause completes. Stop at the comment boundary
|
|
54
|
-
// if the clause never appears, so an edited marker under-removes, never
|
|
55
|
-
// eats the following paragraph or code.
|
|
56
|
-
const html = line.includes('<!--');
|
|
57
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
58
|
-
if (!html && !isLineComment(lines[j])) break;
|
|
59
|
-
acc += ' ' + norm(lines[j]);
|
|
60
|
-
end = j;
|
|
61
|
-
if (acc.includes(TERMINATOR) || (html && lines[j].includes('-->'))) break;
|
|
62
|
-
}
|
|
63
|
-
if (!acc.includes(TERMINATOR) && !html) end = i; // clause absent: token line only
|
|
64
|
-
}
|
|
65
|
-
removed += end - i + 1;
|
|
66
|
-
i = end;
|
|
67
|
-
}
|
|
68
|
-
return { content: kept.join('\n'), removed, markers };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Walk an app root, strip the marker comments in place, and return a per-file
|
|
73
|
-
* report of how many markers were cleared (and how many lines that removed).
|
|
74
|
-
* Only `.ts`/`.js`/`.mts`/`.mjs` files that actually carry the marker are
|
|
75
|
-
* rewritten.
|
|
76
|
-
* @param {string} root
|
|
77
|
-
* @param {{ marker?: string, write?: (path: string, content: string) => void }} [opts]
|
|
78
|
-
* @returns {Array<{ file: string, markers: number, removed: number }>}
|
|
79
|
-
*/
|
|
80
|
-
export function clearPlaceholders(root, opts = {}) {
|
|
81
|
-
const marker = opts.marker ?? MARKER;
|
|
82
|
-
const write = opts.write ?? ((p, c) => writeFileSync(p, c));
|
|
83
|
-
const report = [];
|
|
84
|
-
(function walk(dir) {
|
|
85
|
-
for (const name of readdirSync(dir)) {
|
|
86
|
-
if (SKIP_DIRS.has(name)) continue;
|
|
87
|
-
const full = join(dir, name);
|
|
88
|
-
if (statSync(full).isDirectory()) { walk(full); continue; }
|
|
89
|
-
if (!/\.m?[jt]s$/.test(name)) continue;
|
|
90
|
-
const src = readFileSync(full, 'utf8');
|
|
91
|
-
if (!src.includes(marker)) continue;
|
|
92
|
-
const { content, removed, markers } = stripPlaceholderMarkers(src, marker);
|
|
93
|
-
write(full, content);
|
|
94
|
-
report.push({ file: full, markers, removed });
|
|
95
|
-
}
|
|
96
|
-
})(root);
|
|
97
|
-
return report;
|
|
98
|
-
}
|
package/lib/design-bar.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// The design bar the scaffold sets (AGENTS.md / CONVENTIONS.md item 6): a
|
|
2
|
-
// delivered UI app must have its OWN design, not the scaffold's. The scaffold is
|
|
3
|
-
// a teaching artifact for how to USE the framework, never a starting design.
|
|
4
|
-
// This lives in one place so the `--clear-placeholders` reminder and the
|
|
5
|
-
// `webjs doctor` advisory speak with one voice (the clear command strips the
|
|
6
|
-
// layout marker that carried this reminder just-in-time, so it is re-surfaced
|
|
7
|
-
// there, and doctor catches an app that kept the shell anyway).
|
|
8
|
-
|
|
9
|
-
import { existsSync } from 'node:fs';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* True when the app has a root layout, i.e. it is a UI app the design bar
|
|
14
|
-
* applies to. The `api` template ships no `app/layout`, so the reminder /
|
|
15
|
-
* advisory stay quiet there.
|
|
16
|
-
* @param {string} appDir
|
|
17
|
-
* @returns {boolean}
|
|
18
|
-
*/
|
|
19
|
-
export function hasUiLayout(appDir) {
|
|
20
|
-
return ['ts', 'js', 'mts', 'mjs'].some((e) => existsSync(join(appDir, 'app', `layout.${e}`)));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const DESIGN_REMINDER =
|
|
24
|
-
'\nDesign: the scaffold is a TEACHING artifact, not a starting design.\n' +
|
|
25
|
-
'A delivered UI app must have its OWN design chosen from what the app IS:\n' +
|
|
26
|
-
'layout, palette, typography, icons, spacing, and chrome.\n' +
|
|
27
|
-
'- Layout: app/layout.ts ships as a MINIMAL shell (no header / nav / footer /\n' +
|
|
28
|
-
' reading column). Design your own from what the app IS. LAYOUT-REFERENCE.md\n' +
|
|
29
|
-
' shows the mechanics; do not reproduce its example header verbatim.\n' +
|
|
30
|
-
'- Palette: the design-token NAMES (--background, --primary, --card, ...) are\n' +
|
|
31
|
-
' infrastructure to keep, but their COLOR VALUES are yours. Set a distinctive\n' +
|
|
32
|
-
' palette that fits the app; keeping the starter orange is not a redesign.\n' +
|
|
33
|
-
'- Verify by USING it: render the app and play through every state, and confirm\n' +
|
|
34
|
-
' nothing resizes or shifts as it fills (even, stable cells). A glance at the\n' +
|
|
35
|
-
' empty first paint is not enough; the layout bugs show up mid-interaction.\n' +
|
|
36
|
-
'See AGENTS.md / CONVENTIONS.md item 6.';
|
|
37
|
-
|
|
38
|
-
// Distinctive strings that indicate an app kept scaffold-specific chrome or the
|
|
39
|
-
// unmodified starter palette, rather than designing its own. Counting them is an
|
|
40
|
-
// objective proxy for "did not own the design" without judging taste. NOTE: the
|
|
41
|
-
// theme apparatus (`--header-h`, the `theme-toggle` import) is KEEP-infrastructure
|
|
42
|
-
// the minimal shell ships in every app, so it is NOT a tell (it fired on every
|
|
43
|
-
// finished app and made the advisory nag forever). The tells that remain are
|
|
44
|
-
// genuine "reproduced the scaffold" signals: the exact 760px reading column, the
|
|
45
|
-
// scaffold's own attribution footer, and the two exact default palette VALUES (a
|
|
46
|
-
// verbatim match means the palette was never changed; a recolor does not match).
|
|
47
|
-
const SHELL_TELLS = [
|
|
48
|
-
{ key: 'reading-column (max-w-[760px])', re: /max-w-\[760px\]/ },
|
|
49
|
-
// Specific to the scaffold's own attribution (its footer links webjs.dev and
|
|
50
|
-
// says "Built with webjs"). A bare "Built with ..." is a common bespoke footer,
|
|
51
|
-
// so it is NOT a tell on its own.
|
|
52
|
-
{ key: 'attribution footer', re: /webjs\.dev|Built with webjs/ },
|
|
53
|
-
{ key: 'default scaffold primary color', re: /--primary:\s*oklch\(0\.7\s+0\.16\s+52\)/ },
|
|
54
|
-
{ key: 'default scaffold card color', re: /--card:\s*oklch\(0\.18\s+0\.01\s+55\)/ },
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The scaffold-shell tells present in a root-layout source string. Two or more
|
|
59
|
-
* is a strong signal the app kept the scaffold chrome instead of designing its
|
|
60
|
-
* own. Returns the human-readable keys that matched.
|
|
61
|
-
* @param {string} layoutSrc
|
|
62
|
-
* @returns {string[]}
|
|
63
|
-
*/
|
|
64
|
-
export function scaffoldShellTells(layoutSrc) {
|
|
65
|
-
if (typeof layoutSrc !== 'string' || !layoutSrc) return [];
|
|
66
|
-
return SHELL_TELLS.filter((t) => t.re.test(layoutSrc)).map((t) => t.key);
|
|
67
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# Claude Code Stop hook: render-and-look before finishing UI work.
|
|
4
|
-
#
|
|
5
|
-
# An AI agent writes CSS blind (it never renders), and layout / design defects
|
|
6
|
-
# have NO failure signal: `webjs check` and `typecheck` pass, the app runs. So a
|
|
7
|
-
# collapsed board, uneven cells, a layout that resizes as it fills, or an app
|
|
8
|
-
# that just kept the scaffold's design all ship silently. The one thing that
|
|
9
|
-
# catches them is looking at the rendered pixels. This backstop fires at the END
|
|
10
|
-
# of a turn that touched UI files and reminds you to render the app and inspect
|
|
11
|
-
# every state (see the webjs-design-review skill + CONVENTIONS item 6) before you
|
|
12
|
-
# stop. Loop-safe (fires at most once per stop) and skipped when no UI changed.
|
|
13
|
-
#
|
|
14
|
-
# Disable with WEBJS_NO_DESIGN_STOP=1.
|
|
15
|
-
|
|
16
|
-
set -uo pipefail
|
|
17
|
-
payload=$(cat 2>/dev/null || true)
|
|
18
|
-
|
|
19
|
-
if [ "${WEBJS_NO_DESIGN_STOP:-}" = "1" ]; then exit 0; fi
|
|
20
|
-
active=$(printf '%s' "$payload" | jq -r '.stop_hook_active // false' 2>/dev/null || echo false)
|
|
21
|
-
if [ "$active" = "true" ]; then exit 0; fi
|
|
22
|
-
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then exit 0; fi
|
|
23
|
-
|
|
24
|
-
# Did this turn touch UI surface? A component, a page/layout, or app styling.
|
|
25
|
-
ui_changed=$(git status --porcelain --untracked-files=all 2>/dev/null \
|
|
26
|
-
| grep -vE '(^|/)(node_modules|\.webjs)(/|$)' \
|
|
27
|
-
| grep -cE '(app/.*(page|layout)\.(t|j)sx?$)|(components/.*\.(t|j)sx?$)|(modules/.*components/.*\.(t|j)sx?$)|(\.css$)' || true)
|
|
28
|
-
|
|
29
|
-
if [ -z "$ui_changed" ] || [ "$ui_changed" -lt 1 ]; then exit 0; fi
|
|
30
|
-
|
|
31
|
-
reason="You changed UI in this turn but a design/layout defect has no failing test: check and typecheck pass even when a component collapses, cells are uneven, the layout shifts as it fills, or the app just resembles the scaffold. Before you stop, RENDER the app and LOOK at it: start it (webjs dev / start), open the routes you changed in a browser, and PLAY THROUGH every state (fill the board, win, draw, reload). Confirm (1) nothing collapses or resizes, cells stay equal; (2) the design is the app's OWN (layout, palette, typography, chrome), not the scaffold shell or its default colors; (3) it looks correct in light AND dark. See the webjs-design-review skill and CONVENTIONS item 6. If you already rendered and verified it this turn, say so in your final message. Disable this backstop with WEBJS_NO_DESIGN_STOP=1."
|
|
32
|
-
|
|
33
|
-
jq -n --arg r "$reason" '{decision: "block", reason: $r}' 2>/dev/null \
|
|
34
|
-
|| printf '{"decision":"block","reason":%s}\n' "$(printf '%s' "$reason" | jq -Rs . 2>/dev/null || echo '""')"
|
|
35
|
-
|
|
36
|
-
exit 0
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# UserPromptSubmit hook: route a UI-building prompt to the design-review
|
|
4
|
-
# skill, so it is never silently skipped.
|
|
5
|
-
#
|
|
6
|
-
# Why this exists: a Skill is model-invoked, so it fires only when the model
|
|
7
|
-
# judges the prompt to match, and that judgement is exactly what fails for
|
|
8
|
-
# design work ("build a tic-tac-toe app" reads as backend/logic work and the
|
|
9
|
-
# render-and-look step gets skipped, shipping a collapsed or scaffold-looking
|
|
10
|
-
# UI). A hook is deterministic: it runs on every prompt, decides from the
|
|
11
|
-
# prompt TEXT, and injects a directive the model reads before acting. It
|
|
12
|
-
# cannot invoke the Skill itself (the harness forbids that); the strongest
|
|
13
|
-
# lever is UserPromptSubmit additionalContext.
|
|
14
|
-
#
|
|
15
|
-
# Output contract: print one JSON object with
|
|
16
|
-
# hookSpecificOutput.additionalContext and exit 0. Never block (exit 2 would
|
|
17
|
-
# erase the prompt); routing informs, it does not gate.
|
|
18
|
-
|
|
19
|
-
set -euo pipefail
|
|
20
|
-
payload=$(cat)
|
|
21
|
-
prompt=$(printf '%s' "$payload" | jq -r '.prompt // empty' 2>/dev/null || true)
|
|
22
|
-
[ -z "$prompt" ] && exit 0
|
|
23
|
-
lc=$(printf '%s' "$prompt" | tr '[:upper:]' '[:lower:]')
|
|
24
|
-
has() { printf '%s' "$lc" | grep -Eq "$1"; }
|
|
25
|
-
|
|
26
|
-
# UI / app-building intent: any request to build/create/change something the
|
|
27
|
-
# user will SEE. Broad on purpose (a false positive just reminds you to look).
|
|
28
|
-
if has '(build|create|make|add|design|redesign|style|implement|scaffold).{0,40}(app|page|layout|component|screen|view|board|form|dashboard|ui|site|game|list|table|card|nav|header|footer|modal|button|theme)' \
|
|
29
|
-
|| has '(make|help me|let'\''s).{0,20}(look|prettier|beautiful|nicer|design)' \
|
|
30
|
-
|| has '(tic.?tac.?toe|todo|blog|dashboard|landing|storefront|kanban|chat)'; then
|
|
31
|
-
ctx="ROUTING: this prompt involves UI work. Invoke the webjs-design-review skill (Skill tool) as part of this task: after building/changing any page, layout, or component and BEFORE reporting the work done, render the app in a real browser and LOOK at every state, confirming the app owns its design (layout + palette + type, not the scaffold), nothing collapses or resizes, cells stay even, and light + dark both read. A design/layout defect has NO failing test, so the render-and-look is the only check that catches it."
|
|
32
|
-
jq -n --arg c "$ctx" '{hookSpecificOutput: {hookEventName: "UserPromptSubmit", additionalContext: $c}}'
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
exit 0
|