Skip to content

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.

Where the conventions live

The project conventions ship as prose in the cross-agent skill, .agents/skills/webjs/SKILL.md (a routing skill), plus the git, test, and review workflow rules in .agents/rules/workflow.md. There is no separate CONVENTIONS.md file. All AI agents read the skill and AGENTS.md before writing code. The conventions define:

  • Module architecture: where actions, queries, and components go.
  • Testing rules: when unit vs E2E tests are required.
  • Component patterns: light DOM by default with Tailwind, shadow DOM opt-in, Class.register('tag'), and the class-prefix rule for light-DOM custom CSS.
  • Styling convention: a static compiled Tailwind stylesheet (css:build) + @theme tokens, JS helpers in lib/utils/ui.ts to dedupe repeated class bundles, no @apply.
  • Server action patterns: one function per file, ActionResult envelope.
  • Code style: TypeScript extensions, const/let preferences, async/await patterns.

How to Override Architectural Conventions

The conventions are prose, so you customize them by editing the skill (.agents/skills/webjs/SKILL.md) or the workflow rules (.agents/rules/workflow.md) to match your team's preferences. For example, if you prefer shadow DOM components by default (the scaffold defaults to light DOM + Tailwind), state that in the component-patterns section:

# Component patterns

- Opt in to shadow DOM (static shadow = true) for every component
- Author styles via static styles = css`...`
- Always call register()

AI agents read the skill and AGENTS.md before every task and follow them. Together they are the source of truth for project conventions: how code is organized, named, and tested. These are preferences you can change, so they are guidance, not a hard gate.

webjs check: correctness, not conventions

The webjs check command is a separate tool from the conventions. It runs only correctness checks: rules that catch objectively broken code, such as a browser global in render() that crashes SSR, a non-public process.env read that leaks a secret, a reactive prop that silently breaks reactivity, a server-only .server.ts import reaching a page that ships to the browser (a runtime crash the elision verdict lets the check catch statically), a 'use server' file that exports no callable action (so a client import resolves to nothing and the call 404s), or non-erasable TypeScript that fails the type-strip. Every rule always runs.

The dividing line

One test decides where something belongs: could a sensible app legitimately want this to pass? If yes, it is a convention (it lives in the skill and AGENTS.md as guidance). If no, it is a check (it lives in webjs check and always runs). That is why checks are not overridable, they catch real breakage, and conventions are not enforced by a tool, they are judgment.

# Validate the project (correctness only)
webjs check

# List the correctness checks and their descriptions
webjs check --rules

Workflow for AI agents

  1. Read the skill (.agents/skills/webjs/SKILL.md) and AGENTS.md for the project conventions and follow them by judgment.
  2. Run webjs check and fix every violation: they are correctness bugs, not style.
  3. To change a convention, edit the prose in the skill or .agents/rules/workflow.md. There is no package.json switch and nothing to toggle.
  4. Run webjs check before every commit. AI agents run it automatically as part of their workflow.

webjs test

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

Unit Tests

# Run all unit tests
webjs test

# Test files live in test/unit/*.test.{ts,js}

Unit tests use node:test and node:assert/strict. Test server actions (via direct import), component rendering (via renderToString), and utility functions:

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { html } from '@webjsdev/core';
import { renderToString } from '@webjsdev/core/server';

test('renders heading', async () => {
  const result = await renderToString(html`<h1>Hello</h1>`);
  assert.ok(result.includes('Hello'));
});

test('action validates input', async () => {
  const result = await createPost({ title: '', body: '' });
  assert.equal(result.success, false);
  assert.equal(result.status, 400);
});

E2E Tests

# Run unit + E2E tests
webjs test --e2e

# Test files live in test/browser/*.test.{ts,js}

E2E tests use WTR + Playwright to launch a real browser and test complete user flows: navigation, form submission, auth, and live interactions.

Convention: Always Write Tests

When implementing any feature, tests are mandatory:

  • Unit tests for server actions, queries, and component rendering.
  • E2E tests for user-facing features (navigation, forms, auth flows).

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.

Branch Checking

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

  • If on main: stop immediately and create a feature branch.
  • If on a feature branch: verify it matches the current task.
  • Before starting work: sync with the parent branch (git fetch origin && git rebase origin/main).

The Claude Code hook (.claude/hooks/guard-branch-context.sh) enforces this programmatically by intercepting Edit/Write calls when on main.

Merge Approval

Agents never merge without explicit user permission. Before any merge, the agent asks:

Ready to merge <branch> into <target>?
After merging, should <branch> be deleted or kept?

The agent waits for approval before proceeding.

Automatic Tests & Docs

Every code change includes the following, automatically, without the user asking:

  1. Tests: unit tests for logic, E2E tests for user-facing behavior.
  2. Documentation: updates to AGENTS.md for API changes, the skill (.agents/skills/webjs/SKILL.md) for convention changes, and docs/ for user-facing features.
  3. Convention validation: webjs check runs and violations are fixed.

The user should never have to say "also write tests" or "also update the docs." That is the default behavior in a WebJs project.

Agent Config Files

When you scaffold a project with webjs create, it generates config files for every major AI coding agent:

FileAgentPurpose
AGENTS.mdAll agents (Cursor, opencode, Antigravity, Copilot coding agent read it natively)Framework API, conventions, recipes (the source of truth)
.agents/skills/webjs/SKILL.mdAll agentsThe shipped routing skill carrying the framework context and project conventions (guidance, customizable in the prose)
.agents/rules/workflow.mdAll agentsGit, test, and review workflow rules
CLAUDE.mdClaude CodeThin bridge pointing at AGENTS.md
GEMINI.mdGemini CLIThin bridge pointing at AGENTS.md
.claude/settings.jsonClaude CodePreToolUse hook guarding git merge/push to main
.github/copilot-instructions.mdCopilot in VS CodeThin bridge pointing at AGENTS.md
.github/pull_request_template.mdAll (via GitHub)PR checklist: tests, docs, convention check
.editorconfigAll editorsConsistent indent/encoding/line endings

All config files encode the same rules: the framework conventions, git workflow, and quality expectations. Each is formatted for its target agent's native config format.

Autonomous Mode

When an agent runs in sandbox or bypass-permissions mode, it follows these defaults instead of asking questions:

DecisionAutonomous Default
On main, need a branchAuto-create feature/<task-slug>
Parent branch has new commitsAuto-rebase before starting
Ready to mergeAuto-merge (no prompt)
Delete branch after merge?Delete feature/fix branches, keep long-lived branches
Commit messageAuto-generate meaningful message
Tests failingFix them, don't ask
Convention violationsFix them, don't ask

The principle: in autonomous mode, the agent is more disciplined, not less. It follows every rule but makes decisions instead of blocking on questions.

webjs create

The scaffolding command generates a complete project with all conventions, config files, and example tests pre-configured:

npm i -g webjsdev
webjs create my-app
cd my-app && npm run dev

This generates:

  • app/ with root layout + page
  • modules/ skeleton for feature-scoped code
  • components/ with a theme toggle component
  • db/schema.server.ts: the Drizzle schema, SQLite by default, example User model. db/connection.server.ts exports the db connection.
  • test/unit/ and test/browser/ with example tests
  • .agents/skills/webjs/SKILL.md: the shipped routing skill with the framework context and editable project conventions
  • .agents/rules/workflow.md: git, test, and review workflow rules
  • AGENTS.md: full framework API reference
  • Thin bridges pointing at AGENTS.md (CLAUDE.md, GEMINI.md, .github/copilot-instructions.md)
  • .editorconfig for consistent formatting
  • package.json with scripts (dev, build, start, test, check, db:migrate, db:generate, db:studio)

Every file is ready to use immediately. The project works out of the box with webjs dev, and every AI agent that opens the project will automatically read the config files and follow the conventions.

The Complete Workflow

When a user tells an AI agent "add a contact page" in a WebJs project, the agent automatically delivers:

app/contact/page.ts                           # the page
modules/contact/actions/send-message.server.ts # the server action
modules/contact/types.ts                       # type definitions
test/unit/contact.test.ts                      # unit test for the action
test/browser/contact.test.ts                       # E2E test for the form flow
AGENTS.md                                      # updated if new API/conventions
docs/app/docs/contact/page.ts                  # doc page (if docs/ exists)

Plus: a git commit with a meaningful message, passing tests, and valid conventions. The user never has to ask for tests, docs, or a commit. That is the default behavior.

Next Steps