Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/webjs-doc-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ when_to_use: |

# Keep every webjs doc surface in sync with shipped behaviour

webjs ships its documentation across SEVERAL independent surfaces. The recurring
WebJs ships its documentation across SEVERAL independent surfaces. The recurring
failure mode is updating ONE (usually `AGENTS.md`) and silently missing the rest,
so the docs site, the marketing website, and the scaffold's per-agent rule files
drift behind the framework. HTTP-verb server actions (#488) shipped with
Expand Down
50 changes: 50 additions & 0 deletions .claude/hooks/block-prose-punctuation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,54 @@ EOF
exit 2
fi

# --- 5. Lowercase "webjs" at a prose sentence start ---------------------
# The brand renders "WebJs" when it BEGINS a sentence and lowercase
# "webjs" everywhere else. This flags the lowercase form only at a
# sentence-initial PROSE position: after . ! ?, or at a prose line start
# (optionally behind markdown / comment markers and emphasis), or right
# after a prose HTML tag. A following space is required, so "webjs.dev",
# "webjs-suspense", "@webjsdev", and "webjsdev" never match. A "webjs"
# immediately followed by a CLI subcommand (webjs dev, webjs check, ...)
# is a command reference and is kept lowercase, so it is filtered out.
# Inline `code`, fenced blocks, and mid-sentence brand uses are not a
# sentence start and do not match. The rule biases toward false negatives
# (a missed case) over false positives (a wrongly blocked write), the
# same tradeoff as the pause rules above.
webjs_cli='create|dev|start|test|check|db|ui|doctor|types|typecheck|mcp|vendor|add|init|generate|migrate|push|studio|seed|pin|unpin|list|audit|outdated|update|view'

webjs_hits=$(printf '%s\n' "$new_content" | grep -nE \
-e '[.!?]["'"'"')]?[[:space:]]+(\*\*|__|\*|_|")?webjs(\*\*|__|\*|_|")?[[:space:]]' \
-e '^[[:space:]]*((//|#{1,6}|>|[-*])[[:space:]]+)*(\*\*|__|\*|_|")?webjs(\*\*|__|\*|_|")?[[:space:]]' \
-e '<(p|li|td|h[1-6]|strong|em|blockquote)[^>]*>[[:space:]]*(\*\*|__|\*|_|")?webjs(\*\*|__|\*|_|")?[[:space:]]' \
2>/dev/null || true)

if [ -n "$webjs_hits" ]; then
# Drop hits whose "webjs" is a CLI subcommand reference (kept lowercase).
offending=$(printf '%s\n' "$webjs_hits" \
| grep -vE "webjs[[:space:]]+(${webjs_cli})([[:space:]]|[.,:;)]|\$)" 2>/dev/null || true)
if [ -n "$offending" ]; then
cat >&2 <<'EOF'
BLOCKED: lowercase "webjs" at a prose sentence start.

The brand is "WebJs" when it BEGINS a sentence and lowercase "webjs"
everywhere else. Capitalize this occurrence.

Bad: webjs ships a cache() helper.
Good: WebJs ships a cache() helper.
Bad: ...round-trips. webjs rewrites the import.
Good: ...round-trips. WebJs rewrites the import.

Still lowercase (NOT a sentence start, do NOT capitalize these):
- a CLI command: `webjs dev`, `webjs check`, `webjs create my-app`
- a domain / package / config / env: webjs.dev, @webjsdev,
"webjs": { ... }, WEBJS_PUBLIC_*
- mid-sentence: "Most webjs apps ship without a build step."

Rule: AGENTS.md, Invariants section, item 11.
Hook: .claude/hooks/block-prose-punctuation.sh.
EOF
exit 2
fi
fi

exit 0
2 changes: 1 addition & 1 deletion .claude/skills/webjs-doc-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ when_to_use: |

# Keep every webjs doc surface in sync with shipped behaviour

webjs ships its documentation across SEVERAL independent surfaces. The recurring
WebJs ships its documentation across SEVERAL independent surfaces. The recurring
failure mode is updating ONE (usually `AGENTS.md`) and silently missing the rest,
so the docs site, the marketing website, and the scaffold's per-agent rule files
drift behind the framework. HTTP-verb server actions (#488) shipped with
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ reference there.

## AI-driven development: guardrails for all agents

**webjs is AI-first. These rules apply to ALL agents (Claude, Cursor, Copilot, Antigravity, Aider), enforced via per-agent config the scaffold ships** (`AGENTS.md` + `CONVENTIONS.md` + `CLAUDE.md`, `.claude/settings.json` hooks, `.cursorrules`, `.agents/rules/workflow.md`, `.github/copilot-instructions.md`, a PR template, `.editorconfig`), all carrying the same rules in each agent's format.
**WebJs is AI-first. These rules apply to ALL agents (Claude, Cursor, Copilot, Antigravity, Aider), enforced via per-agent config the scaffold ships** (`AGENTS.md` + `CONVENTIONS.md` + `CLAUDE.md`, `.claude/settings.json` hooks, `.cursorrules`, `.agents/rules/workflow.md`, `.github/copilot-instructions.md`, a PR template, `.editorconfig`), all carrying the same rules in each agent's format.

### Before starting ANY work: verify and sync the branch

Expand All @@ -44,7 +44,7 @@ Claude Code enforces step 1 via `.claude/hooks/guard-branch-context.sh`. Other a

### One task per git worktree when agents run concurrently

webjs is worked by MULTIPLE agents at once. If more than one agent (or more than one in-flight task) shares ONE working checkout, they collide: a `git checkout` in one moves `HEAD` under the other, so the next commit lands on the WRONG branch. This has happened (a `chore: release` commit landed on an unrelated `feat/` branch, with a contaminated changelog). So give each task its own worktree:
WebJs is worked by MULTIPLE agents at once. If more than one agent (or more than one in-flight task) shares ONE working checkout, they collide: a `git checkout` in one moves `HEAD` under the other, so the next commit lands on the WRONG branch. This has happened (a `chore: release` commit landed on an unrelated `feat/` branch, with a contaminated changelog). So give each task its own worktree:

```sh
git worktree add -b <prefix>/<slug> ../<repo>-<slug> origin/main
Expand Down Expand Up @@ -109,7 +109,7 @@ An **AI-first, web-components-first** framework inspired by NextJs, Lit, and Rai

## Execution model (read this to avoid the RSC mental model)

webjs has **no server/client component split.** There is no RSC render tree, no Flight protocol, no "use client" / "use server" component boundary.
WebJs has **no server/client component split.** There is no RSC render tree, no Flight protocol, no "use client" / "use server" component boundary.

**Pages, layouts, and components are isomorphic modules** (same source both sides), but hydrate differently:

Expand Down Expand Up @@ -440,7 +440,7 @@ const result = await optimistic(liked, true, () => likePost(postId));
9. **No backtick characters inside `html\`...\`` template bodies**, even inside CSS / HTML comments. A nested backtick closes the literal at JS-parse time and 500s in prod.
10. **TypeScript must be erasable.** Set `compilerOptions.erasableSyntaxOnly: true`. No `enum`, no value `namespace`, no constructor parameter properties, no legacy decorators with `emitDecoratorMetadata`, no `import = require`. Types are stripped via Node 24+'s `module.stripTypeScriptTypes` (buildless, no bundler fallback); non-erasable syntax 500s at strip time. Enforced by `erasable-typescript-only` (tsconfig flag) and `no-non-erasable-typescript` (source scan). See `agent-docs/typescript.md`.

11. **No em-dashes (U+2014), no hyphen or semicolon used as pause-punctuation in prose, and no colon attached to a code-shaped LHS.** Banned as a pause: U+2014, a space-surrounded hyphen between words, a space-surrounded semicolon between words. Banned colon attachments: a colon-then-prose after `xyz()`, a `<my-tag>`, an `[expr]` subscript, or a `<code>foo()</code>` definition list (rephrase verb-led). Prefer a period, comma, a colon on a plain-noun LHS, parentheses, or a restructure. Plain hyphens stay fine in compound words, flags, filenames, ranges; semicolons and colons stay fine inside code / TS / JSON / CSS. Enforced via `.claude/hooks/block-prose-punctuation.sh`, which scans only NEW content (you can still edit an existing line to remove a glyph).
11. **No em-dashes (U+2014), no hyphen or semicolon used as pause-punctuation in prose, and no colon attached to a code-shaped LHS.** Banned as a pause: U+2014, a space-surrounded hyphen between words, a space-surrounded semicolon between words. Banned colon attachments: a colon-then-prose after `xyz()`, a `<my-tag>`, an `[expr]` subscript, or a `<code>foo()</code>` definition list (rephrase verb-led). Prefer a period, comma, a colon on a plain-noun LHS, parentheses, or a restructure. Plain hyphens stay fine in compound words, flags, filenames, ranges; semicolons and colons stay fine inside code / TS / JSON / CSS. The same hook also enforces brand casing: write `WebJs` when the brand BEGINS a sentence and lowercase `webjs` everywhere else (a CLI command like `webjs dev`, a `webjs.dev` domain, an `@webjsdev` package, a `webjs.*` config key, a `WEBJS_*` env var, and any mid-sentence mention all stay lowercase). Enforced via `.claude/hooks/block-prose-punctuation.sh`, which scans only NEW content (you can still edit an existing line to remove a glyph or fix casing).

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ Pre-1.0. Current packages: `@webjsdev/core` 0.7.1, `@webjsdev/server` 0.7.2, `@w
- **Core:** Signals (`signal`, `computed`, `effect`, `batch`, TC39 Stage 1 shape) as the default state primitive, with WebComponent's built-in SignalWatcher auto-tracking `.get()` reads inside `render()`. Reactive properties via the declare-free base-class factory `extends WebComponent({ count: Number })` (the `prop()` helper carries options like `reflect` / `state` / `attribute` / `default`), reserved for HTML attribute round-trip (a direct `static properties` block throws at runtime, flagged by the `no-static-properties` rule, and a class-field initializer on a factory prop is caught by `reactive-props-no-class-field`). Full lit-API parity: ReactiveController hooks (`hostConnected`, `hostDisconnected`, `hostUpdate`, `hostUpdated`) and lifecycle (`shouldUpdate`, `willUpdate`, `update`, `updated`, `firstUpdated`, `updateComplete`), 12 directives (`repeat`, `unsafeHTML`, `live`, `keyed`, `guard`, `templateContent`, `ref` + `createRef`, `cache`, `until`, `asyncAppend`, `asyncReplace`, `watch`). SSR with DSD (opt-in) + light-DOM hydration (default), light-DOM `<slot>` projection (framework-driven, same API as shadow DOM), fine-grained client renderer, `Suspense()`, client router with `composedPath()` for shadow DOM, mixed-attribute interpolation, MutationObserver upgrade safety net.
- **Data:** Server actions with webjs's built-in serializer (`Date`, `Map`, `Set`, `BigInt`, `TypedArray`, `Blob`, `File`, `FormData`, reference cycles all survive the wire). Two-marker server-file convention: `.server.{js,ts}` for path-level source-protection (browser imports get a throw-at-load stub), `'use server'` for RPC registration (file is also browser-callable). REST over HTTP via a `route.ts` (or the `route()` adapter) with an optional `validate` config export. `json()` + `richFetch()` for content-negotiated APIs. `cache()` for server-side query caching with TTL + `invalidate()`. `WEBJS_PUBLIC_*` env vars injected into `window.process.env` at SSR (no build step, no transform).
- **Server:** File router with `page.ts`, `layout.ts`, `route.ts`, `error.ts`, `loading.ts`, `not-found.ts`, `middleware.ts`, metadata routes (`sitemap`, `robots`, `manifest`, `icon`, `opengraph-image`), per-segment middleware, `rateLimit()`, WebSockets (`WS` export + `connectWS()` + `broadcast()`), CSRF, gzip / brotli compression, HTTP/2, 103 Early Hints, modulepreload hints, health probes, graceful shutdown on `SIGTERM`, `Session` class with `SessionStorage` (cookie or store-backed), NextAuth-style `createAuth()` (Credentials, Google, GitHub), single pluggable cache store (in-memory by default, swap to Redis with one `setStore()` call shared by auth, sessions, caching, and rate limiting).
- **DX:** Node 24+ or Bun runtime (run a Bun app with `bun --bun run dev` / `start`, and the CLI hot-reloads via `node --watch` on Node and `bun --hot` on Bun), with the dev server stripping TypeScript via Node's built-in `module.stripTypeScriptTypes` (or `amaro` on Bun, byte-identical), zero build, position-preserving, no sourcemap. Non-erasable TS (enums, value-carrying namespaces, constructor parameter properties, legacy decorators) fails with a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback. Vendor (`node_modules`) packages resolve through importmap to jspm.io URLs at runtime; the webjs server doesn't bundle them. `webjs vendor pin` writes resolved URLs to `.webjs/vendor/importmap.json` for deterministic deploys; `webjs vendor pin --download` additionally vendors bundle bytes for offline-capable production. `webjs check` lint covers `use-server-needs-extension`, `no-server-env-in-components`, `no-static-properties`, `reactive-props-no-class-field`, `erasable-typescript-only`, `no-non-erasable-typescript`, `shell-in-non-root-layout`, and more (run `webjs check --rules` to enumerate). `AGENTS.md` contract + `CLAUDE.md` + per-tool agent configs (`.cursorrules`, `.agents/rules/workflow.md` for Antigravity, `.github/copilot-instructions.md`, `.claude/settings.json` PreToolUse hook guarding edits on `main`). Live reload in dev (`fs.watch` + SSE). `@webjsdev/intellisense` is the standalone editor-only piece (no Lit dependency): its own `` html`…` `` template parser drives go-to-definition on tags / attributes / CSS classes, binding-aware completions, value/binding diagnostics, and hover, all gated by the file's import graph. The `webjs` VS Code / Cursor / Windsurf extension bundles it. Not required for the framework to run.
- **DX:** Node 24+ or Bun runtime (run a Bun app with `bun --bun run dev` / `start`, and the CLI hot-reloads via `node --watch` on Node and `bun --hot` on Bun), with the dev server stripping TypeScript via Node's built-in `module.stripTypeScriptTypes` (or `amaro` on Bun, byte-identical), zero build, position-preserving, no sourcemap. Non-erasable TS (enums, value-carrying namespaces, constructor parameter properties, legacy decorators) fails with a 500 pointing at the `no-non-erasable-typescript` lint rule. WebJs is buildless end-to-end and has no bundler fallback. Vendor (`node_modules`) packages resolve through importmap to jspm.io URLs at runtime; the webjs server doesn't bundle them. `webjs vendor pin` writes resolved URLs to `.webjs/vendor/importmap.json` for deterministic deploys; `webjs vendor pin --download` additionally vendors bundle bytes for offline-capable production. `webjs check` lint covers `use-server-needs-extension`, `no-server-env-in-components`, `no-static-properties`, `reactive-props-no-class-field`, `erasable-typescript-only`, `no-non-erasable-typescript`, `shell-in-non-root-layout`, and more (run `webjs check --rules` to enumerate). `AGENTS.md` contract + `CLAUDE.md` + per-tool agent configs (`.cursorrules`, `.agents/rules/workflow.md` for Antigravity, `.github/copilot-instructions.md`, `.claude/settings.json` PreToolUse hook guarding edits on `main`). Live reload in dev (`fs.watch` + SSE). `@webjsdev/intellisense` is the standalone editor-only piece (no Lit dependency): its own `` html`…` `` template parser drives go-to-definition on tags / attributes / CSS classes, binding-aware completions, value/binding diagnostics, and hover, all gated by the file's import graph. The `webjs` VS Code / Cursor / Windsurf extension bundles it. Not required for the framework to run.
- **Release:** Per-package per-version changelog under `changelog/<pkg>/<version>.md`, auto-generated on the same commit that bumps a `package.json` `version` field (universal pre-commit hook). The `.github/workflows/release.yml` workflow watches for new changelog files on `main` and dual-publishes to npm (`npm publish --workspace=@webjsdev/<pkg>`) and GitHub Releases (`gh release create <pkg>@<version>`), both idempotent so re-runs pick up where they left off. Free for public repos via `NPM_TOKEN` + the auto-provisioned `GITHUB_TOKEN`.

## License
Expand Down
4 changes: 2 additions & 2 deletions agent-docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Five stacked zero-build optimizations:

## No-build production model

webjs has no bundler and no `webjs build` step. The same `.js` / `.ts`
WebJs has no bundler and no `webjs build` step. The same `.js` / `.ts`
source files that run in dev are served as-is to the browser in
production. The Rails 7+ / Hotwire pattern:

Expand Down Expand Up @@ -139,7 +139,7 @@ production. The Rails 7+ / Hotwire pattern:
npm **vendor** URLs those shipped modules reach (#754). The browser
parallelizes fetches instead of waterfalling through nested imports. This
is what closes most of the perceived gap vs a bundle. **The honest caveat:**
a bundle still wins on a DEEP vendor tree. webjs flattens the FIRST level
a bundle still wins on a DEEP vendor tree. WebJs flattens the FIRST level
(the vendor entries your code imports are hinted up front, with their SRI
`integrity`, byte-identical to the importmap target so there is no double
fetch), but a vendor's OWN transitive deps are still discovered by parsing
Expand Down
6 changes: 3 additions & 3 deletions agent-docs/built-ins.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Opinionated defaults: **set `REDIS_URL` and everything scales.**

## Caching (HTTP standards, Remix-style)

webjs uses standard HTTP caching via `Cache-Control` on responses. Let
WebJs uses standard HTTP caching via `Cache-Control` on responses. Let
browsers, CDNs, and reverse proxies handle caching. No framework cache
layer to debug.

Expand Down Expand Up @@ -301,7 +301,7 @@ Both are opt-in and types-only. See `agent-docs/typescript.md` and the auth reci

## File storage (`FileStore` + `diskStore`)

webjs round-trips a native `File` / `Blob` / `FormData` over the wire, and the file-storage primitive decides WHERE the bytes land. The model mirrors the cache / session adapters: a documented `FileStore` interface, a default local-disk adapter (`diskStore`), and a module singleton (`setFileStore` / `getFileStore`) so an app swaps the backend in one call without touching any call site.
WebJs round-trips a native `File` / `Blob` / `FormData` over the wire, and the file-storage primitive decides WHERE the bytes land. The model mirrors the cache / session adapters: a documented `FileStore` interface, a default local-disk adapter (`diskStore`), and a module singleton (`setFileStore` / `getFileStore`) so an app swaps the backend in one call without touching any call site.

```js
import { getFileStore, generateKey, signedUrl, verifySignedUrl } from '@webjsdev/server';
Expand Down Expand Up @@ -360,7 +360,7 @@ The content-type a store records is the one the BROWSER sent at upload time, so

### S3-pluggability (call-site stability)

The interface operates on web-standard objects only, so an S3 / R2 / GCS / MinIO adapter is a drop-in: it implements the same `put` (PutObject, streaming the body), `get` (GetObject, returning the SDK's response stream as `body`), `delete` (DeleteObject), and `url` (the object / CDN URL). Because the shape is identical, `setFileStore(s3Store({ ... }))` switches the whole app with no call-site change. webjs ships no S3 SDK (no new dependency); the adapter is a thin wrapper an app provides.
The interface operates on web-standard objects only, so an S3 / R2 / GCS / MinIO adapter is a drop-in: it implements the same `put` (PutObject, streaming the body), `get` (GetObject, returning the SDK's response stream as `body`), `delete` (DeleteObject), and `url` (the object / CDN URL). Because the shape is identical, `setFileStore(s3Store({ ... }))` switches the whole app with no call-site change. WebJs ships no S3 SDK (no new dependency); the adapter is a thin wrapper an app provides.

See the "Receive and persist an uploaded file" recipe in `agent-docs/recipes.md` for the no-JS `<form>` upload + serving route end to end.

Expand Down
Loading
Loading