# What Framework, full agent guide > What is a signal-based reactive web framework built for AI agents. Components run once, no re-renders, no virtual DOM. The compiler lowers JSX to fine-grained DOM operations. A live MCP server (29 tools) lets agents inspect a running app, and its `what_fix` tool returns a code, fix, and worked example for any What error code offline. This file inlines the essentials an agent needs to write, debug, and ship correct What code. --- ## Mental model - **Signals are functions.** `const count = signal(0, 'count')`. Read with `count()`. Write with `count(5)` or `count(c => c + 1)`. `count.set(...)` is the same write, and `count.peek()` reads without subscribing. - **Components run once.** The component function body executes a single time. It never re-runs on state change. Reactivity comes from signals creating fine-grained DOM effects, not from re-rendering. - **`computed()`** is lazy, cached derived state: `const doubled = computed(() => count() * 2)`. - **`effect()`** auto-tracks the signals it reads. No dependency array. Return a function to clean up. It returns a dispose function. - **`batch()`** groups writes so effects flush once. It returns nothing: the callback's value is discarded. - Import the core from `'what-framework'`. SSR and server actions come from `'what-framework/server'` and routing from `'what-framework/router'`; neither set is re-exported by the umbrella. - **Two render paths, and only one of them can be server-rendered.** The What compiler lowers JSX to a module-level `_$template()` call that runs `document.createElement` at MODULE LOAD TIME. So a compiled module cannot be imported in Node at all: the import throws `ReferenceError: document is not defined` before any render function is reached, and no SSR entry point can change that. Write anything you intend to server-render with `h()` or with the automatic JSX runtime (`jsx`/`jsxs`, which build vnodes at call time rather than templates at load time). Keep the template compiler for client-only bundles. The full-stack scaffold `create-what` generates is buildless and uses `h()` for exactly this reason. The static-site scaffold (`--template=islands`) writes JSX for the automatic runtime for the same reason: every page is rendered on the server. Where behaviour differs between the two paths (keyed list reconciliation, the `` item protocol, whether `{sig()}` stays live) the compiled path is the one with the optimizations and the runtime path is the one that can run on a server. --- ## Quickstart ```bash npm create what@latest my-app cd my-app npm install npm run dev ``` Open `http://localhost:5173`. `create-what` wires up Vite and the compiler, so you rarely touch bundler config. Bun works too: `bun create what@latest my-app && bun run dev`. Minimal component: ```jsx import { signal, computed, mount } from 'what-framework'; function Counter() { const count = signal(0, 'count'); const doubled = computed(() => count() * 2); return (

Count: {count()}

Doubled: {doubled()}

); } mount(, '#app'); // returns a dispose function ``` Signal reads inside JSX children and attributes are auto-wrapped by the compiler, you do not write `{() => count()}` manually. That is a COMPILED-path guarantee. Writing `h()` calls by hand there is no compiler, so `h('span', {}, \`n=${count()}\`)` renders once and never updates: pass a thunk (`() => \`n=${count()}\``) or the bare signal (`h('span', {}, count)`) instead. Manual setup without the scaffolder: ```js // vite.config.js import { defineConfig } from 'vite'; import what from 'what-compiler/vite'; export default defineConfig({ plugins: [what()] }); ``` --- ## Signals API ```js import { signal, computed, effect, batch, onMount, onCleanup, h, mount } from 'what-framework'; // Create state. The second arg is an optional debug name (recommended, it // makes the signal traceable in DevTools and MCP). const count = signal(0, 'count'); count(); // read -> 0 (subscribes the current effect) count(5); // write -> sets to 5 count(c => c + 1); // updater write -> increments count.set(10); // same as count(10) count.peek(); // read WITHOUT subscribing // Derived state, lazy and cached. const doubled = computed(() => count() * 2); // Side effects, auto-track reads, run on change. Return a cleanup function. effect(() => { const id = setInterval(() => count(c => c + 1), 1000); return () => clearInterval(id); }); // Batch multiple writes so effects run once at the end. batch(() => { count(0); /* ...other writes... */ }); // Lifecycle. BOTH must be called inside a component function body: called // anywhere else they throw ("onMount() can only be called inside a component // function"). onMount runs asynchronously after the mount, so it is not // observable in the same tick as mount(); flushSync() does not pull it forward. function Widget() { onMount(() => { /* runs after first mount; good for browser-only APIs */ }); onCleanup(() => { /* runs on unmount / dispose */ }); return h('div', {}); } ``` Lists: use `.map()` with a `key` prop. The compiler lowers it to keyed reconciliation. ```jsx ``` Immutable updates for objects/arrays, never mutate in place: ```js items(prev => [...prev, newItem]); // add items(prev => prev.filter(i => i.id !== targetId)); // remove items(prev => prev.map(i => i.id === id ? { ...i, done: true } : i)); // update one ``` --- ## Common errors and fixes A `WhatError` serializes to JSON with `code`, `message`, `suggestion`, `codeExample`, `file`, `line`, `component`, `signal`, `effect`, and dev-mode collected errors are readable with `getCollectedErrors()`. Know which channel each condition actually arrives on, because they differ: - **Structured `WhatError`:** only ERR_MISSING_SIGNAL_READ, and only once the signal-read guardrail is installed on a signal (what-devtools does this for every signal it registers). Without devtools nothing detects it. - **Dev console warning, no code in the text, nothing collected:** ERR_INFINITE_EFFECT, ERR_UNSAFE_INNERHTML, ERR_HYDRATION_MISMATCH. - **Build time, from the compiler:** ERR_MISSING_KEY. - **Not reported at all by anything:** ERR_SIGNAL_WRITE_IN_RENDER and ERR_ORPHAN_EFFECT. They are still real mistakes and still valid input to `what_fix`, but nothing will tell you when you make them. ### ERR_INFINITE_EFFECT, effect cycle An effect writes to a signal it also reads, creating a loop. The runtime caps the flush at 25 iterations, drops the pending effects, and warns in dev: `[what] Possible infinite effect loop detected (25 iterations)`, naming the looping effects. ```js // Bad, reads and writes count in the same effect: effect(() => { count(count() + 1); }); // Good, read without subscribing: import { untrack } from 'what-framework'; effect(() => { count(untrack(count) + 1); }); ``` ### ERR_MISSING_SIGNAL_READ, signal used without calling it Raised when a signal is coerced to a string or a number instead of being read, so the signal itself, not its value, ends up in the output or the comparison. This is the one code the runtime raises as a structured `WhatError`, and it is raised off the signal's own `toString`/`valueOf`, which only the guardrail installs. In an app without what-devtools the same mistake is SILENT: the template literal prints the function's source text and the comparison is false. ```jsx

{`Total: ${count}`}

// Bad, prints the signal function's source text

{`Total: ${count()}`}

// Good, call the signal if (count > 5) { } // Bad, compares a function, always false if (count() > 5) { } // Good ``` A signal passed on its own as a JSX child or an attribute value is fine: a function there is What's reactive-binding protocol, so `{count}` renders the value and stays live. ### ERR_SIGNAL_WRITE_IN_RENDER, write during render NOT DETECTED by anything today, in dev or otherwise: this one is on you. The component body should read signals, not write them. Move writes into handlers, effects, or `onMount`. (Defining a handler in the body is fine, components run once, so the handler does not execute during the render.) ```jsx // Bad: function Counter() { count(count() + 1); return {count()}; } // Good: function Counter() { return ; } ``` ### ERR_UNSAFE_INNERHTML, raw innerHTML Raw HTML must be opted into with the `__html` marker. A raw string is refused outright (the element is left empty) and warned about in dev. ```jsx
// Bad, XSS risk, refused
// Good, explicit opt-in ``` ### ERR_HYDRATION_MISMATCH, server/client HTML differ Avoid reading browser-only APIs (`window`, `localStorage`) during the initial render; use `onMount` for client-only values. On a mismatch the runtime warns in dev (`[what] Hydration mismatch: expected
, got SPAN. Falling back to client render.`) and rebuilds that node rather than reusing it. ### ERR_MISSING_KEY, list without a key Reported by the COMPILER at build time in dev, not by the runtime: whether a list has keys is settled by the source. A `.map()` that returns JSX with no `key` prop bails out of keyed reconciliation and prints `[what-compiler] ERR_MISSING_KEY: ... at src/List.jsx:5:24`. It stays silent for a keyed list, and for a bare index key (`key={i}`), which is read as deliberate positional reconciliation. ### ERR_ORPHAN_EFFECT NOT DETECTED, and deliberately so: the only available signal is owner presence at effect creation, which is absent in component bodies, `onMount` callbacks and event handlers alike, so the check would fire on the pattern its own advice recommends. Create effects inside components or a `createRoot()` anyway so they are cleaned up. Tip for agents: the MCP tool `what_fix({ error })` returns the diagnosis, fix, and example for any code offline, use it as your first move when you hit a What error. --- ## MCP DevTools usage What ships an MCP server (`what-devtools-mcp`) that bridges an agent to a running app over the Model Context Protocol. It exposes 29 tools. Wire it into your MCP client: ```json { "mcpServers": { "what-framework": { "command": "npx", "args": ["what-devtools-mcp"] } } } ``` Enable live inspection by adding the Vite plugin: ```js import what from 'what-compiler/vite'; import whatDevToolsMCP from 'what-devtools-mcp/vite-plugin'; export default defineConfig({ plugins: [what(), whatDevToolsMCP({ port: 9229 })], }); ``` First five minutes, run in order: 1. `what_connection_status`, am I connected? how big is the app? 2. `what_diagnose`, one-call health check (errors + perf + reactivity). 3. `what_page_map`, visual structure and accessibility. 4. `what_components({ filter })` → `what_explain({ componentId })`, deep-dive a leaf component. 5. `what_signals({ filter: "...", named_only: true })`, check key state (always filter; never dump all). Useful workflows: - **Trace reactivity:** `what_signals({ filter })` → `what_dependency_graph({ signalId, direction: "downstream" })`. - **Before/after a change:** `what_diff_snapshot({ action: "save" })` → make the change → `what_diff_snapshot({ action: "diff" })`. - **Test a fix live:** `what_set_signal({ signalId, value })` then re-check with `what_diff_snapshot`. - **Validate code offline:** `what_lint({ code })`, `what_scaffold({ type, name })`, `what_fix({ error })` work without a browser. Notes for agents: - Component and signal IDs are ephemeral, re-query `what_components` after any `what_set_signal` that may remount the tree. - `what_eval` refuses arbitrary code by default (security). A dotted read off `document`/`window`/`navigator`/`location`/`screen`/`performance`/`console` still works (`document.title`, `window.innerWidth`), minus the sensitive paths (`cookie`, `localStorage`, …). Anything else needs `--unsafe-eval` / `WHAT_UNSAFE_EVAL=1`. - Module-scope signals are not attributed to any component; use `what_signals` directly rather than per-component counts. --- ## React compatibility `what-react` runs many React ecosystem libraries inside What components. It is a secondary feature, prefer native What APIs for new code. The tested library matrix in REACT-COMPAT.md is the source of truth. ```bash npm install what-react ``` --- ## More - Repository: https://github.com/CelsianJs/what-framework - Docs site: https://whatfw.com/docs - Agent patterns: https://github.com/CelsianJs/what-framework/blob/main/docs/AGENT-PATTERNS.md - MCP DevTools reference: https://github.com/CelsianJs/what-framework/blob/main/docs/MCP-DEVTOOLS.md - Compatibility matrix: https://github.com/CelsianJs/what-framework/blob/main/REACT-COMPAT.md