From 82223725fcc7a238ec9f077618874ad9ec04e364 Mon Sep 17 00:00:00 2001 From: Vivek Date: Wed, 8 Jul 2026 23:51:59 +0530 Subject: [PATCH 1/5] docs(website): capitalize WebJs at sentence starts in prose --- website/app/page.ts | 4 ++-- website/lib/samples.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/app/page.ts b/website/app/page.ts index e948e57e..c26061e1 100644 --- a/website/app/page.ts +++ b/website/app/page.ts @@ -27,7 +27,7 @@ import { highlight } from '#lib/highlight.ts'; // alone is ~44 KB. The ~99 KB is the full Next baseline, react + react-dom // plus the Next runtime plus the app-router client). const STATS = [ - { big: '~29 KB', label: 'Client runtime, gzipped', sub: 'A minimal Next.js client bundle is ~99 KB gzipped including React. webjs is self-sufficient at ~29 KB, 3.4x lighter on the wire.' }, + { big: '~29 KB', label: 'Client runtime, gzipped', sub: 'A minimal Next.js client bundle is ~99 KB gzipped including React. WebJs is self-sufficient at ~29 KB, 3.4x lighter on the wire.' }, { big: '0 build', label: 'Instant agent loops', sub: 'No compilation, no bundler. Agents edit, run tests, and verify in the browser in milliseconds.' }, { big: '100%', label: 'Web standards', sub: 'Standard-aligned Web Component lifecycles, so models write components reliably.' }, { big: '~16k', label: 'LLM-context friendly', sub: 'Under 6.5k lines of client runtime, ~16k for the whole stack, small enough to fit an LLM context window.' }, @@ -328,7 +328,7 @@ export default function LandingPage() { `)} -

Familiar from day one. webjs uses Next.js-style file-based routing and lit-style web components, conventions both people and agents already know.

+

Familiar from day one. WebJs uses Next.js-style file-based routing and lit-style web components, conventions both people and agents already know.

Gzipped production sizes. A Next.js app ships a client bundle around ~99 KB gzipped (react, react-dom, and the Next runtime); @webjsdev/core is self-sufficient at ~29 KB gzipped with zero runtime dependencies and no build step.

diff --git a/website/lib/samples.ts b/website/lib/samples.ts index 30b905dc..782a1ff0 100644 --- a/website/lib/samples.ts +++ b/website/lib/samples.ts @@ -15,7 +15,7 @@ import { eq } from 'drizzle-orm'; import { db } from '#db/connection.server.ts'; import { posts } from '#db/schema.server.ts'; -// Import this from a page or client component. webjs rewrites +// Import this from a page or client component. WebJs rewrites // the import into a typed RPC stub (the real call at SSR). No // fetch by hand. export async function getPost(id) { From ba9e16acb3348dfb8176459e8ba40650794b21a0 Mon Sep 17 00:00:00 2001 From: Vivek Date: Wed, 8 Jul 2026 23:54:54 +0530 Subject: [PATCH 2/5] docs: capitalize WebJs at sentence starts in docs site prose --- docs/app/docs/ai-first/page.ts | 8 ++--- docs/app/docs/api-routes/page.ts | 8 ++--- docs/app/docs/architecture/page.ts | 8 ++--- docs/app/docs/auth/page.ts | 2 +- docs/app/docs/authentication/page.ts | 2 +- docs/app/docs/backend-only/page.ts | 8 ++--- docs/app/docs/cache/page.ts | 2 +- docs/app/docs/client-router/page.ts | 4 +-- docs/app/docs/components/page.ts | 16 +++++----- docs/app/docs/configuration/page.ts | 8 ++--- docs/app/docs/controllers/page.ts | 2 +- docs/app/docs/conventions/page.ts | 6 ++-- docs/app/docs/data-fetching/page.ts | 2 +- docs/app/docs/database/page.ts | 2 +- docs/app/docs/deployment/page.ts | 30 +++++++++---------- docs/app/docs/directives/page.ts | 4 +-- docs/app/docs/editor-setup/page.ts | 4 +-- docs/app/docs/error-handling/page.ts | 2 +- docs/app/docs/file-storage/page.ts | 4 +-- docs/app/docs/getting-started/page.ts | 4 +-- docs/app/docs/layout.ts | 2 +- docs/app/docs/lifecycle/page.ts | 2 +- docs/app/docs/loading-states/page.ts | 2 +- docs/app/docs/metadata-routes/page.ts | 4 +-- docs/app/docs/middleware/page.ts | 8 ++--- docs/app/docs/migrating-from-nextjs/page.ts | 10 +++---- docs/app/docs/no-build/page.ts | 16 +++++----- docs/app/docs/progressive-enhancement/page.ts | 2 +- docs/app/docs/rate-limiting/page.ts | 2 +- docs/app/docs/runtime/page.ts | 2 +- docs/app/docs/security/page.ts | 2 +- docs/app/docs/server-actions/page.ts | 4 +-- docs/app/docs/sessions/page.ts | 2 +- docs/app/docs/ssr/page.ts | 6 ++-- docs/app/docs/styling/page.ts | 2 +- docs/app/docs/suspense/page.ts | 2 +- docs/app/docs/testing/page.ts | 2 +- docs/app/docs/troubleshooting/page.ts | 4 +-- docs/app/docs/typescript/page.ts | 14 ++++----- docs/app/docs/websockets/page.ts | 4 +-- 40 files changed, 109 insertions(+), 109 deletions(-) diff --git a/docs/app/docs/ai-first/page.ts b/docs/app/docs/ai-first/page.ts index f405c261..c9919226 100644 --- a/docs/app/docs/ai-first/page.ts +++ b/docs/app/docs/ai-first/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'AI-First Development | webjs' }; export default function AIFirst() { return html`

AI-First Development

-

webjs is designed from the ground up to be the framework AI agents can read, write, and ship. Every architectural decision (from the file layout to the naming conventions to the one-function-per-file rule) was made with one question in mind: can an LLM understand this without loading the entire codebase into context?

+

WebJs is designed from the ground up to be the framework AI agents can read, write, and ship. Every architectural decision (from the file layout to the naming conventions to the one-function-per-file rule) was made with one question in mind: can an LLM understand this without loading the entire codebase into context?

Why AI-First Matters

Modern AI coding assistants (Claude Code, GitHub Copilot, Cursor, Antigravity, etc.) are increasingly writing production code. But most frameworks were designed for humans who hold the whole project in their head. They rely on:

@@ -81,7 +81,7 @@ modules/posts/queries/get-post.server.ts → exports getPost()

5. No Build Step = What You See Is What Runs

Frameworks with build pipelines transform source code before it executes. The JSX you write becomes React.createElement calls. Your imports become webpack chunks. Your CSS modules get hashed classnames. An AI agent reading the source sees one thing, while the runtime does another.

-

webjs has no build step you run. The .ts file you see is the file that runs. .ts imports are stripped of types by the runtime's stripper (Node 24+'s built-in module.stripTypeScriptTypes, or amaro on Bun), which is whitespace replacement: every (line, column) in the source maps to the same position in the output, so stack traces stay byte-exact. The same transform runs server-side and on browser-bound requests. No intermediate representation, no generated code, no output directory. An AI agent can reason about what the code does by reading the file, because the file IS what runs. See No-Build Model for the full pipeline.

+

WebJs has no build step you run. The .ts file you see is the file that runs. .ts imports are stripped of types by the runtime's stripper (Node 24+'s built-in module.stripTypeScriptTypes, or amaro on Bun), which is whitespace replacement: every (line, column) in the source maps to the same position in the output, so stack traces stay byte-exact. The same transform runs server-side and on browser-bound requests. No intermediate representation, no generated code, no output directory. An AI agent can reason about what the code does by reading the file, because the file IS what runs. See No-Build Model for the full pipeline.

6. Explicit Server Boundary

The .server.ts extension is a visible, greppable marker that says "this code runs only on the server." An AI agent never accidentally puts a database call in a component, because the naming convention prevents it. And the framework enforces it: .server.ts files are rewritten to RPC stubs for the browser.

@@ -102,7 +102,7 @@ export async function createPost(

Zero indirection. Zero codegen. Zero schema drift.

8. JSDoc or TypeScript: Agent's Choice

-

Some AI agents work better with TypeScript, others prefer JSDoc. webjs supports both equally. The type-checking story is identical either way, since the TS language server reads both. An agent can generate whichever format it's more fluent in.

+

Some AI agents work better with TypeScript, others prefer JSDoc. WebJs supports both equally. The type-checking story is identical either way, since the TS language server reads both. An agent can generate whichever format it's more fluent in.

9. Cross-Agent Config Files

webjs create scaffolds guardrail config files for every major AI coding agent:

@@ -122,7 +122,7 @@ export async function createPost(

In a webjs project, the user never has to say "also write tests" or "also update the docs." Agents do this automatically with every code change. The convention is enforced via CONVENTIONS.md, webjs test, and webjs check. For an agent lint-and-fix loop, run webjs check --json: the structured output lets the agent parse violations, fix them, and re-run until the report is clean (the same correctness data the MCP check tool returns).

12. Scaffold + Persistence Defaults

-

When a layman user says "create a todo app with webjs", the agent should produce a real full-stack app with a real database, not a JSON-file simulation. webjs enforces this with three guardrails:

+

When a layman user says "create a todo app with webjs", the agent should produce a real full-stack app with a real database, not a JSON-file simulation. WebJs enforces this with three guardrails:

`; } diff --git a/docs/app/docs/architecture/page.ts b/docs/app/docs/architecture/page.ts index 268baf3c..b6209ab9 100644 --- a/docs/app/docs/architecture/page.ts +++ b/docs/app/docs/architecture/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'Architecture | webjs' }; export default function Architecture() { return html`

Architecture

-

webjs is a monorepo with three packages that together form the framework. Understanding the split helps when you need to import something specific or embed webjs into another runtime.

+

WebJs is a monorepo with three packages that together form the framework. Understanding the split helps when you need to import something specific or embed webjs into another runtime.

Mental model, especially if you come from React Server Components: webjs has no server/client component split. Pages, layouts, and components are isomorphic modules, the same source runs on the server to produce SSR'd HTML and then loads in the browser to add interactivity. There is no Flight protocol and no "server component" identity. The one server boundary is the .server.{js,ts} file, which is an RPC + source-protection mechanism (server actions and server-only utilities), not a server-rendered component. route.{js,ts} is a server-only HTTP handler. And elision (skipping the JS download of a module that does no client work) is a build-free optimization on those isomorphic modules, not a boundary, it never changes behaviour because progressive enhancement is the no-JS baseline.

@@ -19,7 +19,7 @@ export default function Architecture() { │ └── blog/ # reference app exercising every feature └── docs/ # this documentation site (built on webjs) -

webjs (core)

+

WebJs (core)

Isomorphic: safe to import on both server and client. Contains:

What webjs does not give you:

diff --git a/docs/app/docs/cache/page.ts b/docs/app/docs/cache/page.ts index 393680e3..582b7e9a 100644 --- a/docs/app/docs/cache/page.ts +++ b/docs/app/docs/cache/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'Caching | webjs' }; export default function Cache() { return html`

Caching

-

webjs provides two complementary caching layers: cache() for server-side query result caching, and HTTP Cache-Control headers for page-level browser/CDN caching. Zero config in development (in-memory store). For horizontal scaling in production, call setStore(redisStore({ url: process.env.REDIS_URL })) once at app startup to share the cache across instances.

+

WebJs provides two complementary caching layers: cache() for server-side query result caching, and HTTP Cache-Control headers for page-level browser/CDN caching. Zero config in development (in-memory store). For horizontal scaling in production, call setStore(redisStore({ url: process.env.REDIS_URL })) once at app startup to share the cache across instances.

cache(): Server-Side Query Caching

Wrap any async function with cache() to cache its return value on the server. Same function + same arguments = cached result until TTL expires or you call invalidate().

diff --git a/docs/app/docs/client-router/page.ts b/docs/app/docs/client-router/page.ts index 2ef135b5..f9ab4b6b 100644 --- a/docs/app/docs/client-router/page.ts +++ b/docs/app/docs/client-router/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'Client Router | webjs' }; export default function ClientRouter() { return html`

Client Router

-

webjs ships a nested-layout-aware client router that intercepts same-origin <a> clicks and <form> submissions, fetches the target HTML, and swaps only the deepest layout boundary the two pages don't share. Outer layout DOM is preserved: sidenav scroll, input values, <details> open state, mounted custom elements all survive navigation without authors writing anything.

+

WebJs ships a nested-layout-aware client router that intercepts same-origin <a> clicks and <form> submissions, fetches the target HTML, and swaps only the deepest layout boundary the two pages don't share. Outer layout DOM is preserved: sidenav scroll, input values, <details> open state, mounted custom elements all survive navigation without authors writing anything.

The router is automatic and needs no import: it auto-enables whenever @webjsdev/core loads in the browser, which happens on any page that ships a component. For 99% of apps the contract is "write standard HTML, navigation gets faster." The advanced primitives below (frames, revalidation, programmatic navigation) exist for the cases where you need to take over.

The one edge: a fully-static page with zero components ships no JavaScript at all, so it has no router and its links do a normal full-page navigation (correct progressive enhancement, and cheaper). This is invisible during a session, since a router started on any earlier interactive page stays active across soft navigations. It only shows on a cold direct load of such a page (a bare error or 404 screen). If you want soft navigation there too, render any component in the page or its layout, or add import '@webjsdev/core/client-router' to force the router on.

@@ -26,7 +26,7 @@ export default function ClientRouter() {

When the destination streams (it has a Suspense or <webjs-suspense> boundary), the router applies the response PROGRESSIVELY: it swaps the shell (with the fallbacks) in immediately and advances the URL, then streams each resolved boundary into the live DOM as it arrives, fast-before-slow. So a soft navigation to a streamed page matches the initial-load experience (fallback first, content streams in) instead of buffering the whole response before the swap. A non-streaming page is unaffected (the response is read to completion and applied once). A navigation superseded mid-stream stops applying, and a mid-stream transport failure leaves the applied boundaries in place with the rest showing their fallback (non-destructive).

Form submissions

-

<form action="/x" method="post"> works exactly per the HTML spec. webjs intercepts the submit event in the bubble phase (after a component's own @submit handler) and routes the same fetch the browser would have sent through the partial-swap pipeline. Because it runs after, a component that calls e.preventDefault() in @submit keeps the form to itself and the router leaves it alone; the same applies to @click on links. Submitter attributes (formmethod, formaction, formenctype on a clicked <button>) take precedence over the form's own per HTML5.

+

<form action="/x" method="post"> works exactly per the HTML spec. WebJs intercepts the submit event in the bubble phase (after a component's own @submit handler) and routes the same fetch the browser would have sent through the partial-swap pipeline. Because it runs after, a component that calls e.preventDefault() in @submit keeps the form to itself and the router leaves it alone; the same applies to @click on links. Submitter attributes (formmethod, formaction, formenctype on a clicked <button>) take precedence over the form's own per HTML5.

Slots: Content Projection

-

Slots are how a parent passes content into a component. If you are coming from React, think of the default slot as children. webjs supports the full shadow-DOM <slot> surface in light DOM as well as shadow DOM, so every example below works identically whether the component sets static shadow = true or leaves it at the default (light DOM). The light-DOM runtime mirrors HTMLSlotElement.assignedNodes(), assignedElements(), assignedSlot, and the slotchange event, plus named slots, fallback content, and first-wins resolution. To our knowledge no other web-components framework offers this complete parity in light DOM. Lit's slot APIs only work inside shadow roots, and Stencil's light-DOM slot polyfill has known gaps around fallback content and mixed shadow / non-shadow trees.

+

Slots are how a parent passes content into a component. If you are coming from React, think of the default slot as children. WebJs supports the full shadow-DOM <slot> surface in light DOM as well as shadow DOM, so every example below works identically whether the component sets static shadow = true or leaves it at the default (light DOM). The light-DOM runtime mirrors HTMLSlotElement.assignedNodes(), assignedElements(), assignedSlot, and the slotchange event, plus named slots, fallback content, and first-wins resolution. To our knowledge no other web-components framework offers this complete parity in light DOM. Lit's slot APIs only work inside shadow roots, and Stencil's light-DOM slot polyfill has known gaps around fallback content and mixed shadow / non-shadow trees.

Default Slot

The <slot></slot> element in a component's render() is where the parent's child content appears:

@@ -531,7 +531,7 @@ html\`

Content without a slot attribute goes to the default (unnamed) slot. Content with slot="name" is routed to the matching <slot name="name">. Text inside the <slot> tag itself is fallback content shown when no matching content is provided.

Lifecycle

-

webjs components use the standard custom element lifecycle callbacks. If you override them, always call super.

+

WebJs components use the standard custom element lifecycle callbacks. If you override them, always call super.

connectedCallback()

Called when the element is inserted into the document. This is where webjs attaches the shadow root, adopts styles, and performs the first render. Use it for setup work like fetching data, opening WebSocket connections, or reading from localStorage:

@@ -557,7 +557,7 @@ html\`

You do not need to call super.disconnectedCallback() (the base class is a no-op), but it does not hurt to include it for safety.

attributeChangedCallback(name, oldValue, newValue)

-

Called when one of the observedAttributes changes. webjs handles this for you. It coerces the attribute value based on the type declared in the factory shape, sets the corresponding instance property, and schedules a re-render. You rarely need to override this, but you can if you need side effects when a specific attribute changes:

+

Called when one of the observedAttributes changes. WebJs handles this for you. It coerces the attribute value based on the type declared in the factory shape, sets the corresponding instance property, and schedules a re-render. You rarely need to override this, but you can if you need side effects when a specific attribute changes:

attributeChangedCallback(name, oldVal, newVal) {
   super.attributeChangedCallback(name, oldVal, newVal);
@@ -641,7 +641,7 @@ render() {
 
     
MyCounter.register('my-counter');
-

webjs wraps the native API (and installs a compatible shim on the server) so the same line works in both environments:

+

WebJs wraps the native API (and installs a compatible shim on the server) so the same line works in both environments:

  • Browser: tells the browser to upgrade all <my-counter> elements with the MyCounter class, and mirrors the mapping into webjs's internal registry.
  • Server: stores the class in the internal registry so renderToString can look it up for Declarative Shadow DOM injection.
  • @@ -654,7 +654,7 @@ render() {

    The tag argument accepts any short-string quote style. register('my-counter'), register("my-counter"), and register(`my-counter`) are all equivalent. The framework's tag-name-has-hyphen lint rule reads the tag through any of them.

    Server Rendering

    -

    webjs components are server-rendered using Declarative Shadow DOM. When the server renders a page containing <my-counter count="5"></my-counter>, the output looks like:

    +

    WebJs components are server-rendered using Declarative Shadow DOM. When the server renders a page containing <my-counter count="5"></my-counter>, the output looks like:

    <my-counter count="5">
       <template shadowrootmode="open">
    diff --git a/docs/app/docs/configuration/page.ts b/docs/app/docs/configuration/page.ts
    index e75d0282..40ebce84 100644
    --- a/docs/app/docs/configuration/page.ts
    +++ b/docs/app/docs/configuration/page.ts
    @@ -5,7 +5,7 @@ export const metadata = { title: 'Configuration | webjs' };
     export default function Configuration() {
       return html`
         

    Configuration

    -

    webjs is designed to work with zero configuration. File conventions handle routing, TypeScript works out of the box, and the server is pre-configured with sensible defaults. This page documents what you can configure when you need to.

    +

    WebJs is designed to work with zero configuration. File conventions handle routing, TypeScript works out of the box, and the server is pre-configured with sensible defaults. This page documents what you can configure when you need to.

    CLI Options

    webjs dev

    @@ -65,7 +65,7 @@ webjs db seed # run db/seed.server.ts

    webjs check runs a fixed set of correctness checks (a crash, a security leak, a build or type-strip failure). They always run; there is no project-level config to disable them, and the checks read no package.json config block of their own. Project conventions (layout, naming, testing) are guidance in CONVENTIONS.md, not a tool. See Conventions & AI Workflow for the split and run webjs check --rules to list the checks.

    Security response headers

    -

    webjs sets standard security headers on every response by default (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, plus Strict-Transport-Security in production over HTTPS). Override or extend them per path with a webjs.headers block in package.json, an array of { source, headers: [{ key, value }] } rules where source is a URLPattern path pattern and a null value removes a default. App middleware wins over the path config, which wins over the defaults. A webjs.csp key (off by default) additionally mints a per-request CSP nonce and emits a matching Content-Security-Policy header. See Deployment → Secure response headers for the full reference.

    +

    WebJs sets standard security headers on every response by default (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, plus Strict-Transport-Security in production over HTTPS). Override or extend them per path with a webjs.headers block in package.json, an array of { source, headers: [{ key, value }] } rules where source is a URLPattern path pattern and a null value removes a default. App middleware wins over the path config, which wins over the defaults. A webjs.csp key (off by default) additionally mints a per-request CSP nonce and emits a matching Content-Security-Policy header. See Deployment → Secure response headers for the full reference.

    Redirects

    For a moved URL, declare a redirect under webjs.redirects in package.json, an array of { source, destination, permanent?, statusCode? } rules. source is a URLPattern path pattern (so :param / :rest* works) and destination is the target: a path, a path referencing named groups from the source (/posts/:slug filled from /blog/:slug), or an absolute URL for an external redirect. permanent defaults to true (a 308 Permanent Redirect, what SEO wants so link equity transfers); permanent: false is a 307 Temporary Redirect. 308 / 307 preserve the request method (a redirected POST stays a POST); for a legacy 301 / 302 set statusCode explicitly. The incoming query string is preserved by default. Redirects apply at the very start of request handling, before routing, and a malformed entry is dropped with a warning rather than crashing the app.

    @@ -99,7 +99,7 @@ webjs db seed # run db/seed.server.ts
{ "webjs": { "maxBodyBytes": 262144, "maxMultipartBytes": 5242880, "requestTimeoutMs": 30000 } }

Environment Variables

-

Use process.env in server-side code (pages, actions, route handlers, middleware). webjs auto-loads <appDir>/.env into process.env once at boot using Node 24+'s built-in process.loadEnvFile, so a scaffolded app with a committed .env.example and a developer-copied .env just works without installing dotenv or wiring up the file path. The auto-load fires before any server-only module is imported, which matters for code that reads process.env at module-init time (e.g. createAuth({ secret: process.env.AUTH_SECRET })).

+

Use process.env in server-side code (pages, actions, route handlers, middleware). WebJs auto-loads <appDir>/.env into process.env once at boot using Node 24+'s built-in process.loadEnvFile, so a scaffolded app with a committed .env.example and a developer-copied .env just works without installing dotenv or wiring up the file path. The auto-load fires before any server-only module is imported, which matters for code that reads process.env at module-init time (e.g. createAuth({ secret: process.env.AUTH_SECRET })).

Precedence: shell wins over file. process.loadEnvFile does not override values that are already present in process.env, so values exported by the host shell or a process manager (Docker, systemd, Railway, Fly) take precedence over the same key in .env. This matches the Rails / Next / Astro convention: .env is for developer-local defaults; production secrets come from the platform.

@@ -130,7 +130,7 @@ export default (env) => { schema.parse(env); };

Any environment variable that does not start with WEBJS_PUBLIC_ is server-only. It is never sent to the browser. DATABASE_URL, AUTH_SECRET, OAuth client secrets, third-party API keys: read them in server actions, route handlers, middleware, or page functions, and pass derived values (not the raw secret) to components.

Public env vars (WEBJS_PUBLIC_*)

-

Any env var whose name starts with WEBJS_PUBLIC_ is exposed to the browser as process.env.WEBJS_PUBLIC_X. webjs injects an inline script in the SSR'd HTML head that sets window.process.env before any user code or vendor bundle runs. Components can read these directly:

+

Any env var whose name starts with WEBJS_PUBLIC_ is exposed to the browser as process.env.WEBJS_PUBLIC_X. WebJs injects an inline script in the SSR'd HTML head that sets window.process.env before any user code or vendor bundle runs. Components can read these directly:

// .env at the app root (auto-loaded at boot)
 WEBJS_PUBLIC_API_URL=https://api.example.com
 WEBJS_PUBLIC_STRIPE_KEY=pk_live_abc
diff --git a/docs/app/docs/controllers/page.ts b/docs/app/docs/controllers/page.ts
index d39ae3a6..96dcca30 100644
--- a/docs/app/docs/controllers/page.ts
+++ b/docs/app/docs/controllers/page.ts
@@ -162,7 +162,7 @@ UserList.register('user-list');
DashboardWidget.register('dashboard-widget');

Built-in Controllers

-

webjs ships three controllers out of the box:

+

WebJs ships three controllers out of the box:

Task

Manages async operations with automatic loading/error states, abort support, and reactive args. Imported from webjs/task. See the Task Controller page for full documentation.

diff --git a/docs/app/docs/conventions/page.ts b/docs/app/docs/conventions/page.ts index 9028bf7c..1b8d663b 100644 --- a/docs/app/docs/conventions/page.ts +++ b/docs/app/docs/conventions/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'Conventions & AI Workflow | webjs' }; export default function Conventions() { return html`

Conventions & AI Workflow

-

webjs is an AI-first framework. It ships an opinionated conventions system that both humans and AI agents follow. The conventions are enforced via config files, CLI commands, and guardrails that ensure consistent, high-quality code across the entire project, whether written by a person or an agent.

+

WebJs is an AI-first framework. It ships an opinionated conventions system that both humans and AI agents follow. The conventions are enforced via config files, CLI commands, and guardrails that ensure consistent, high-quality code across the entire project, whether written by a person or an agent.

CONVENTIONS.md

Every webjs app has a CONVENTIONS.md file at its root. This is the project-specific conventions document that all AI agents read before writing code. It defines:

@@ -51,7 +51,7 @@ webjs check --rules

webjs test

-

webjs ships a testing setup based on node:test and WTR + Playwright.

+

WebJs ships a testing setup based on node:test and WTR + Playwright.

Unit Tests

# Run all unit tests
@@ -94,7 +94,7 @@ webjs test --e2e
     

Tests-per-feature is a project convention (guidance), not a webjs check rule.

AI Agent Guardrails

-

webjs enforces disciplined AI workflows through config files and hooks. These guardrails apply to all agents: Claude, Cursor, Copilot, Antigravity, and others.

+

WebJs enforces disciplined AI workflows through config files and hooks. These guardrails apply to all agents: Claude, Cursor, Copilot, Antigravity, and others.

Branch Checking

AI agents must never commit directly to main or master. Before any edit, the agent checks what branch it is on:

diff --git a/docs/app/docs/data-fetching/page.ts b/docs/app/docs/data-fetching/page.ts index 6a0ad85a..d190f091 100644 --- a/docs/app/docs/data-fetching/page.ts +++ b/docs/app/docs/data-fetching/page.ts @@ -8,7 +8,7 @@ export const metadata = { export default function DataFetching() { return html`

Data Fetching

-

webjs gives a component four ways to get data into the page. This is the canonical guide to which one to reach for, and the anti-patterns to avoid. The headline is bare-await async render: a component fetches its own server data into the first paint, co-located, with no page orchestration.

+

WebJs gives a component four ways to get data into the page. This is the canonical guide to which one to reach for, and the anti-patterns to avoid. The headline is bare-await async render: a component fetches its own server data into the first paint, co-located, with no page orchestration.

The default: async render()

Make a component's render() async and call a 'use server' action directly. Writing await makes the function async; webjs awaits a promise-returning render() automatically on both the server and the client. There is no flag.

diff --git a/docs/app/docs/database/page.ts b/docs/app/docs/database/page.ts index 94beb44f..6e735f94 100644 --- a/docs/app/docs/database/page.ts +++ b/docs/app/docs/database/page.ts @@ -5,7 +5,7 @@ export const metadata = { title: 'Database (Drizzle) | webjs' }; export default function Database() { return html`

Database (Drizzle)

-

webjs uses Drizzle as the default ORM. It fits the buildless thesis: there is no codegen and no engine binary (what you write is what runs), it runs on Node and Bun, and the types are inferred straight from your schema. SQLite is the default; Postgres is a flag away. The scaffold wires it all up under a db/ folder.

+

WebJs uses Drizzle as the default ORM. It fits the buildless thesis: there is no codegen and no engine binary (what you write is what runs), it runs on Node and Bun, and the types are inferred straight from your schema. SQLite is the default; Postgres is a flag away. The scaffold wires it all up under a db/ folder.

What the scaffold gives you

db/
diff --git a/docs/app/docs/deployment/page.ts b/docs/app/docs/deployment/page.ts
index 5a0c979d..922ec8b0 100644
--- a/docs/app/docs/deployment/page.ts
+++ b/docs/app/docs/deployment/page.ts
@@ -5,10 +5,10 @@ export const metadata = { title: 'Deployment | webjs' };
 export default function Deployment() {
   return html`
     

Deployment

-

webjs runs as a standard server on Node 24+ or Bun. There is no static export, no serverless adapter, and no edge runtime yet. Deploy it anywhere you can run Node or Bun: a VPS, a container, a PaaS like Fly.io or Railway, or behind a reverse proxy on bare metal. On Node the minimum is set by the built-in TypeScript type-stripping; on Bun the stripping comes from amaro automatically, so the same source runs on either.

+

WebJs runs as a standard server on Node 24+ or Bun. There is no static export, no serverless adapter, and no edge runtime yet. Deploy it anywhere you can run Node or Bun: a VPS, a container, a PaaS like Fly.io or Railway, or behind a reverse proxy on bare metal. On Node the minimum is set by the built-in TypeScript type-stripping; on Bun the stripping comes from amaro automatically, so the same source runs on either.

Dev vs Prod

-

webjs has two modes, controlled by the npm script (which wraps the underlying webjs dev / webjs start CLI):

+

WebJs has two modes, controlled by the npm script (which wraps the underlying webjs dev / webjs start CLI):

# Development: live reload, no compression, no caching, verbose errors
 npm run dev -- --port 8080
 
@@ -52,7 +52,7 @@ npm run start -- --port 8080

Pick the mode that matches your security posture. The choice is per-deploy, not per-package: either everything goes through jspm.io or everything is locally vendored. Mixing modes per-package is not supported.

Secure response headers

-

webjs sets a baseline of standard security headers on every response, so a deployed app is not clickjackable or MIME-sniffable without any reverse-proxy configuration. The defaults are literal HTTP headers:

+

WebJs sets a baseline of standard security headers on every response, so a deployed app is not clickjackable or MIME-sniffable without any reverse-proxy configuration. The defaults are literal HTTP headers:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: SAMEORIGIN
  • @@ -60,7 +60,7 @@ npm run start -- --port 8080
  • Permissions-Policy: camera=(), microphone=(), geolocation=()
  • Strict-Transport-Security: max-age=63072000; includeSubDomains in production over HTTPS only
  • -

    HSTS is gated to production AND HTTPS. webjs detects the original scheme from X-Forwarded-Proto (the header the trusted edge proxy forwards after terminating TLS), honoring the same proxy-trust posture as the rest of the framework, so HSTS is never set on a plain-HTTP hop or in dev. Set WEBJS_NO_TRUST_PROXY=1 to stop trusting forwarded headers when the container is directly exposed.

    +

    HSTS is gated to production AND HTTPS. WebJs detects the original scheme from X-Forwarded-Proto (the header the trusted edge proxy forwards after terminating TLS), honoring the same proxy-trust posture as the rest of the framework, so HSTS is never set on a plain-HTTP hop or in dev. Set WEBJS_NO_TRUST_PROXY=1 to stop trusting forwarded headers when the container is directly exposed.

    A default is set only when the response does not already carry that header, so anything your middleware, a route.{js,ts} handler, or expose sets always wins.

    Per-path overrides

    Declare per-path header rules in package.json under "webjs": { "headers": [...] }, shaped like Next's. The source is a path pattern matched with the native URLPattern API, so :param and :rest* tokens work:

    @@ -75,7 +75,7 @@ npm run start -- --port 8080

    A rule can ADD a header, OVERRIDE a default by giving a new value, or DISABLE a default on a path with a null value (the first example drops X-Frame-Options so a public-embed route can be framed). Precedence, lowest to highest, runs secure defaults, then the webjs.headers path config, then app middleware (which always wins, since its headers are already on the response when webjs merges).

    Content-Security-Policy (nonce, opt-in)

    -

    webjs can mint a fresh per-request CSP nonce and emit a matching Content-Security-Policy response header. It is OFF by default (a strict policy would break an app with third-party inline scripts/styles, so you opt in). Enable it with a webjs.csp key in package.json:

    +

    WebJs can mint a fresh per-request CSP nonce and emit a matching Content-Security-Policy response header. It is OFF by default (a strict policy would break an app with third-party inline scripts/styles, so you opt in). Enable it with a webjs.csp key in package.json:

    {
       "webjs": { "csp": true }
     }
    @@ -106,7 +106,7 @@ npm run start -- --port 8080

    Unhandled promise rejections are logged but do not crash the process. Uncaught exceptions trigger an orderly shutdown (state may be corrupted, so continuing is unsafe).

    Health and readiness probes

    -

    webjs answers two built-in probe endpoints, and the distinction matters under runtime-first boot:

    +

    WebJs answers two built-in probe endpoints, and the distinction matters under runtime-first boot:

    GET /__webjs/health    # liveness:  always 200 once the process is listening
     GET /__webjs/ready     # readiness: 503 until the instance is fully warm, then 200

    /__webjs/health is liveness. It returns 200 { "status": "ok" } as soon as the process is accepting connections, so an orchestrator can tell the process is alive. It never waits on the analysis.

    @@ -136,7 +136,7 @@ export default async function ready() { }

    HTTP/2: at the edge, not in webjs

    -

    webjs delegates TLS termination and HTTP/2 negotiation to whatever sits in front of npm run start. The framework's HTTP server speaks plain HTTP/1.1. ALPN, certificates, and h2 framing are entirely the proxy's concern. Two reasons:

    +

    WebJs delegates TLS termination and HTTP/2 negotiation to whatever sits in front of npm run start. The framework's HTTP server speaks plain HTTP/1.1. ALPN, certificates, and h2 framing are entirely the proxy's concern. Two reasons: