= ApiContracts[P][M]["output"]',
+ );
+});
+
+test("emits one assertion per sectioned block, naming its route and method", () => {
+ const root = fixture(BLOCK);
+ generateApplicationTypes(root);
+ const generated = readFileSync(join(root, "app/types/wrnexus.generated.d.ts"), "utf8");
+
+ expect(generated).toContain("__wrn_api_check_searchUsers");
+ expect(generated).toContain('ApiInput<"/api/users", "POST">');
+ expect(generated).toContain("name?: string");
+ expect(generated).toContain("age?: number");
+});
+
+test("a legacy bare-body block produces no assertion", () => {
+ const root = fixture(` api legacyUsers GET /api/users {
+ return users.length
+ }`);
+ generateApplicationTypes(root);
+ const generated = readFileSync(join(root, "app/types/wrnexus.generated.d.ts"), "utf8");
+
+ expect(generated).not.toContain("__wrn_api_check_legacyUsers");
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `bun test packages/cli/test/api-block-types.test.ts`
+Expected: FAIL — none of the helpers are emitted.
+
+- [ ] **Step 3: Emit the helpers and assertions**
+
+In `packages/cli/src/types.ts`, add above the function that builds the namespace:
+
+```ts
+/**
+ * Type assertions for sectioned api blocks.
+ *
+ * Enforcement lives here rather than in the compiler because this file is under
+ * `app/` and is therefore compiled by the project's own tsc, while generated
+ * build artifacts are not type-checked at all.
+ */
+function apiBlockAssertions(pages: { path: string; ast: PageAst }[]): string {
+ const lines: string[] = [];
+
+ for (const page of pages) {
+ for (const block of page.ast.dataApis) {
+ if (!block.sections) continue;
+
+ const fields = [...block.sections.parameters, ...block.sections.body];
+ const shape = fields.length
+ ? `{ ${fields.map((f) => `${f.name}${f.optional ? "?" : ""}: ${f.type}`).join("; ")} }`
+ : "Record";
+
+ lines.push(
+ ` type __wrn_api_check_${block.name} = AssertAssignable<${shape}, ApiInput<${JSON.stringify(
+ block.path,
+ )}, ${JSON.stringify(block.method)}>>;`,
+ );
+ }
+ }
+
+ return lines.join("\n");
+}
+```
+
+Add the helper types and the assertions into the generated namespace, next to the existing `generatedContractMap` calls:
+
+```ts
+ type AssertAssignable = [Actual] extends [Expected] ? true : never;
+ type ApiInput
= ApiContracts[P][M]["input"];
+ type ApiOutput
= ApiContracts[P][M]["output"];
+${apiBlockAssertions(pages)}
+```
+
+`generateApplicationTypes` does not retain page ASTs — line ~111 parses component `.wrn` files
+only. Collect them with the `files()` helper this module already uses (see its use at ~line 296):
+
+```ts
+const pageAsts = files(join(root, "app"), (path) => extname(path) === ".wrn").map((file) => ({
+ path: file,
+ ast: parse(readFileSync(file, "utf8")),
+}));
+```
+
+`parse`, `files`, `join`, `extname`, and `readFileSync` are all already imported by this module.
+`PageAst` comes from `@wrnexus/syntax`; add it to the existing type import if absent.
+
+- [ ] **Step 4: Warn for routes with no contract**
+
+Where a block's path has no entry in `apiContracts`, print once per route:
+
+```ts
+console.warn(
+ `[wrnexus] api block "${block.name}" targets ${block.path}, which has no defineEndpoint contract — its declared types are not checked.`,
+);
+```
+
+- [ ] **Step 5: Run tests to verify they pass**
+
+Run: `bun test packages/cli`
+Expected: PASS.
+
+- [ ] **Step 6: Verify the assertion actually bites**
+
+Regenerate types for the example app and confirm a wrong field fails `tsc`, rather than trusting the string match:
+
+```bash
+bun run --cwd examples/basic-app wrnexus generate types
+bun run typecheck
+```
+
+Expected: PASS. Then temporarily add a field the endpoint does not accept to a block in `examples/basic-app`, regenerate, and confirm `bun run typecheck` FAILS. Revert the temporary change.
+
+- [ ] **Step 7: Commit**
+
+```bash
+bun run format
+git add packages/cli/src/types.ts packages/cli/test/api-block-types.test.ts
+git commit -m "feat(cli): generate type assertions for api blocks"
+```
+
+---
+
+### Task 5: SSR-mode `response` and `error` sections
+
+**Files:**
+
+- Modify: `packages/compiler/src/codegen.ts` (`apiBindingMap` at ~line 932, and `dataBody`)
+- Test: `packages/compiler/test/api-block-ssr.test.ts`
+
+**Interfaces:**
+
+- Consumes: `DataApiBlock.sections` from Task 1.
+- Produces: no new exports. An `ssr` block with sections evaluates `response` with the payload bound to `data`; a failure runs `error`.
+
+- [ ] **Step 1: Write the failing test**
+
+Create `packages/compiler/test/api-block-ssr.test.ts`:
+
+```ts
+import { expect, test } from "bun:test";
+import { parse } from "@wrnexus/syntax";
+import { generate } from "../src/codegen.ts";
+
+function serverModule(inner: string): string {
+ return generate(
+ parse(`page Repro {
+ ssr {
+${inner}
+ }
+
+ view {
loading
}
+}
+`),
+ );
+}
+
+test("a sectioned ssr block binds the payload to data", () => {
+ const generated = serverModule(` api ssrUsers GET /api/users {
+ response {
+ return data.users.length
+ }
+ }`);
+
+ expect(generated).toContain("data.users.length");
+});
+
+test("a legacy ssr block is unchanged", () => {
+ const generated = serverModule(` api ssrUsers GET /api/users {
+ return users.length
+ }`);
+
+ expect(generated).toContain("users.length");
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `bun test packages/compiler/test/api-block-ssr.test.ts`
+Expected: FAIL — the sectioned body is not read.
+
+- [ ] **Step 3: Read sections in `apiBindingMap`**
+
+In `packages/compiler/src/codegen.ts`, change the `body` assignment inside `apiBindingMap`:
+
+```ts
+const sectioned = block.sections;
+bindings.set(block.name, {
+ mode: block.mode,
+ method: block.method,
+ path: apiRoutePath(block.path),
+ // A sectioned block binds the payload to `data`; the legacy form keeps
+ // the `with ($data)` injection, which cannot be typed.
+ body: sectioned
+ ? `const data = $data; ${sectioned.response.trim() || "return data;"}`
+ : dataBody(block.body),
+ helpers: modeHelpers(ast, block.mode, sharedHelpers),
+});
+```
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `bun test packages/compiler`
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+bun run format
+git add packages/compiler/src/codegen.ts packages/compiler/test/api-block-ssr.test.ts
+git commit -m "feat(compiler): support sections in ssr api blocks"
+```
+
+---
+
+### Task 6: End-to-end verification in a real browser
+
+**Files:**
+
+- Create: `examples/basic-app/app/api/directory.ts`
+- Create: `examples/basic-app/app/pages/api-block-demo.wrn`
+- Test: exercised through the running dev server; no unit test file
+
+**Interfaces:**
+
+- Consumes: everything from Tasks 1-5.
+- Produces: a demo page that stays in the repo as the worked example.
+
+**Why this task exists:** this repository has repeatedly shipped features whose tests passed while the feature did not work — the island runtime that 404'd, the JSX pragma test asserting generated text, three tests that would have survived deleting the code they guarded. Browser verification is part of done.
+
+- [ ] **Step 1: Add the endpoint**
+
+Create `examples/basic-app/app/api/directory.ts`:
+
+```ts
+import { defineEndpoint } from "@wrnexus/core";
+import type { Context } from "@wrnexus/core";
+
+const ALL = [
+ { name: "Ajay", designation: "UI" },
+ { name: "Asha", designation: "Backend" },
+ { name: "Chen", designation: "UI" },
+];
+
+export const POST = async (ctx: Context) => {
+ const body = (await ctx.req.json().catch(() => ({}))) as { name?: string };
+ const needle = String(body.name ?? "").toLowerCase();
+ return Response.json({ users: ALL.filter((user) => user.name.toLowerCase().includes(needle)) });
+};
+```
+
+- [ ] **Step 2: Add the page**
+
+Create `examples/basic-app/app/pages/api-block-demo.wrn`:
+
+```wrn
+page ApiBlockDemo {
+ state nameFilter = "a"
+ state found = ""
+ state failed = ""
+
+ client {
+ api searchDirectory POST /api/directory {
+ request {
+ body {
+ name?: string
+ }
+ }
+
+ response {
+ return data.users
+ }
+
+ error {
+ return []
+ }
+ }
+ }
+
+ functions {
+ client async function search(): Promise {
+ const users = await api.searchDirectory({ name: nameFilter })
+ found = users.map((user) => user.name).join(", ")
+ }
+ }
+
+ view {
+
+
+
{found}
+
{failed}
+
+ }
+}
+```
+
+- [ ] **Step 3: Start the dev server**
+
+```bash
+bun run --cwd examples/basic-app dev -- --port=3480
+```
+
+Wait for `WrNexus — http://localhost:3480`, then confirm the page responds:
+
+```bash
+curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3480/api-block-demo
+```
+
+Expected: `200`.
+
+- [ ] **Step 4: Drive it in the browser**
+
+Open `http://localhost:3480/api-block-demo`, click Search, and read the page. Expected: `.found` contains `Ajay, Asha` (both match "a"; Chen does not). Confirm in the network panel that exactly one `POST /api/directory` was made and that it carried an `x-csrf-token` header.
+
+- [ ] **Step 5: Verify the failure path**
+
+Temporarily change the block's path to `/api/directory-missing`, reload, and click Search. Expected: no exception in the console and `.found` empty, because the `error` section returned `[]`. Restore the path.
+
+- [ ] **Step 6: Verify the type gate**
+
+```bash
+bun run --cwd examples/basic-app wrnexus generate types
+bun run typecheck
+```
+
+Expected: PASS. Then add `nope?: string` to the block's `body`, regenerate, and confirm `typecheck` FAILS naming `__wrn_api_check_searchDirectory`. Remove it.
+
+- [ ] **Step 7: Check whether errors appear inline in the editor**
+
+The spec assumes the language server surfaces the generated assertion's failure inside the `.wrn`
+file. That is an assumption, not a requirement. With a deliberately wrong field in place, open the
+page in VS Code and note whether the error appears on the block or only in
+`wrnexus.generated.d.ts`. If only the latter, record it as follow-up work — do not expand this
+plan's scope to fix it.
+
+- [ ] **Step 8: Run the full gate**
+
+```bash
+bun run format
+bun test
+bun run typecheck
+bun run --cwd editors/vscode build
+bun run check:production
+```
+
+Expected: all pass. The editor bundles must be rebuilt because `packages/compiler` and `packages/syntax` changed; `check:editor-compiler` fails on a stale bundle.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add examples/basic-app editors/vscode/src
+git commit -m "feat(examples): worked example for typed api blocks"
+```
+
+---
+
+## Notes for the executor
+
+- **`REACTIVE_RUNTIME` is a template literal.** A backtick in code or a comment you add to it terminates the string and produces a confusing parse error elsewhere in the file. Use plain quotes.
+- **Do not use `node -e` or shell heredocs to write regexes** into these files; escaping mangles them silently. Use the editing tools.
+- **The public API surface is gated.** Adding an export to a package makes `check:public-api` fail until you run `bun run generate:public-api` and confirm the diff is additive only.
+- **If a test would still pass with the code it guards deleted, it is not a test.** Delete the implementation, watch it fail, restore it.