/** * Framework conventions shipped into scaffolded apps as CLAUDE.md and public/llms.txt so * AI coding tools (Claude Code, Cursor, Copilot) generate correct WrNexus code. * Generated from the repo-root llms.txt - do not edit by hand. */ /** The canonical WrNexus conventions reference (served as /llms.txt). */ export const AI_GUIDE = `# WrNexus > WrNexus is an SSR-first, **Bun-native** full-stack web framework. UI is written in > \`.wrn\` files (its own component language — NOT React/JSX/Vue). Routing is file-based. > This document teaches an AI how to write correct WrNexus code. It is private and > post-dates model training data, so rely on THIS document, not prior web-framework > assumptions. ## Golden rules - **Pages, components, and layouts are \`.wrn\` files.** Do NOT write \`.tsx\`/\`.jsx\`/React for UI. Do NOT use \`useState\`, hooks, JSX, or a client bundler. - **Routing is file-based** under \`app/\`. The filename is the route. No router config. - **Interactivity** lives in \`state\` + \`{expr}\` + \`@event\` inside \`.wrn\`. Components render on the server and hydrate automatically — you never write client-side JS islands. - **Runtime is Bun only** (uses \`Bun.serve\`, \`bun:sqlite\`, \`Bun.password\`, …). Node is not supported. - To add files, prefer the CLI: \`wrnexus generate page \` / \`component \` / \`api \` / \`schema \`. ## Project layout \`\`\` app/ pages/ *.wrn → routes: index.wrn = "/", about.wrn = "/about", blog/[slug].wrn = "/blog/:slug" components/ *.wrn → reusable UI, mounted in a page/component via
layouts/ *.wrn → named layouts; a page opts in with layout = "name" api/ *.ts → HTTP handlers: export const GET/POST/PUT/PATCH/DELETE = async (ctx) => Response middleware/ *.ts → export default async (ctx, next) => next() realtime/ *.ts → export default defineRoom({ ... }) from "@wrnexus/core" (ws://host/realtime/) schemas/ *.ts → validation schemas (the \`v\` builder), used by forms + parseBody locales/ *.json → i18n messages per language db/ schema.ts, queries/*.sql, migrations/*.sql styles/ global.css → Tailwind (default) or plain CSS wrnexus.config.ts → app config (AppConfig from "@wrnexus/styles") public/ → static assets served at / \`\`\` ## \`.wrn\` page \`\`\`wrn page Home { layout = "public" // optional: a component in app/layouts/.wrn ("none" to skip) state count = 0 // optional: seeds client-reactive state (omit for pure SSR) seo { title = "Home" description = "..." canonical = "/" } view {

Hello

Count is {count}, doubled is {count * 2}.

} style { h1 { color: var(--wrn-color-text); } } } \`\`\` ## \`.wrn\` component \`\`\`wrn component Counter { props { // props come from mount attributes; each is coerced to the start = 0 // TYPE of its default (so start="5" arrives as the number 5) label = "Count" } state count = start // state may reference props view { } } \`\`\` Mount it from any page/component: \`
\`. Components render on the server with their props, then hydrate — no per-component JS. ## The \`view { }\` block (plain HTML + a few directives) - \`{expr}\` — interpolate a JS expression. Reactive if it references \`state\`: \`{count}\`, \`{count * 2}\`, \`{user.name}\`. - \`@event="expr"\` — bind a DOM event; the expression runs in the reactive scope: \`@click="count++"\`, \`@input="name = event.target.value"\`. - \`
\` — mount a component (attrs become string props, coerced). - \`\` / \`\` — component/layout slots; fill with \`
\`. - **Server loop (DB/list/table):** \`{#each as [, ]} …rows… {:empty} …fallback… {/each}\` — iterates SSR data on the server and renders markup per item. \`{item.field}\` interpolates (HTML-escaped, XSS-safe). \`\` is a JS expression, usually an \`ssr\` data binding (see "Data-driven tables" below). This is how you render a database table in \`.wrn\`. - **Server conditional:** \`{#if } … {:else if } … {:else} … {/if}\` — renders the first truthy branch on the server. \`\` can reference \`ssr\` data, or the \`item\`/\`index\` of an enclosing \`{#each}\`. Works at page level and inside loops (e.g. \`{#if r.active}{:else}{/if}\` per row). For client-side show/hide based on reactive \`state\`, use \`data-show="expr"\` instead. - i18n: \`{t:home.title}\` in text, \`t:placeholder="form.name"\` on attributes — resolved per request from \`app/locales/\`. - Theme: any element with \`data-wrn-theme-toggle\` toggles light/dark; \`data-wrn-theme-set="dark"\` sets it. - Void/self-closing tags are fine: \`
\`, \`\`. - Only \`{\` and \`}\` are special (interpolation). Don't use a bare \`}\` in view text. ## Data-driven tables / lists (server-rendered \`.wrn\`) Use an \`ssr\` data binding to fetch rows on the server, then \`{#each}\` to render them. This renders on the **server** (SSR-first) and is HTML-escaped by default. \`\`\`wrn page Admin { layout = "dashboard" // Fetch on the server. The api handler at /api/contacts returns { contacts: [...] }; // this block's \`return contacts\` exposes that array (via \`$data\`) as the binding \`rows\`. ssr { api rows GET /api/contacts { return contacts } } view { {#each rows as r, i} {:empty} {/each}
#{i} {r.name} {r.email}
No submissions yet.
} } \`\`\` The matching API returns the array under a key the \`ssr\` block reads: \`\`\`ts // app/api/contacts.ts → GET /api/contacts import { getDb } from "@wrnexus/db"; export const GET = async () => { const contacts = await getDb().all("SELECT id, name, email FROM contacts ORDER BY id DESC"); return Response.json({ contacts }); // ssr block does \`return contacts\` }; \`\`\` **Prefer this \`.wrn\` + \`{#each}\` approach for DB-backed tables and lists.** (\`.ts\`/\`.tsx\` pages returning an HTML string are also supported for fully-custom programmatic rendering, but a \`.wrn\` page with \`ssr\` data + \`{#each}\` is the idiomatic, SSR-first way.) ## API routes (\`app/api/*.ts\`) \`\`\`ts // app/api/users/list.ts → GET /api/users/list import { getDb } from "@wrnexus/db"; export const GET = async (ctx) => { return Response.json({ users: await ListUsers(getDb()) }); }; export const POST = async (ctx) => { const body = await ctx.req.json(); return Response.json({ ok: true, body }, { status: 201 }); }; \`\`\` \`ctx\` (the \`Context\` from \`@wrnexus/core\`) has: \`req: Request\`, \`url: URL\`, \`params: Record\` (dynamic route params, e.g. \`/users/[id]\` → \`ctx.params.id\`), \`lang: string\`, \`t(key, params?)\` (i18n), \`cookies\` (get/set), \`session\` (get/set). Auth: \`getUser(ctx)\` after \`sessionAuth\`/\`logIn\`. When an SSO forward-auth verifier needs the URL that originally reached the gateway, use \`@wrnexus/helpers\` instead of constructing it from untrusted headers: \`\`\`ts import { redirectToLogin } from "@wrnexus/helpers"; return redirectToLogin(ctx, "/login", { allowedHosts: ["admin.localhost:3000", "reports.localhost:3000"], }); \`\`\` The package also exports \`getOriginalRequestUrl\`, \`getOriginalRequestOrigin\`, \`getOriginalRequestPath\`, and \`getOriginalRequestMethod\`. Always pass \`allowedHosts\` when using forwarded gateway URLs; the helper rejects untrusted redirect destinations. ## Middleware & realtime \`\`\`ts // app/middleware/logger.ts export default async function logger(ctx, next) { console.log(ctx.req.method, ctx.url.pathname); return next(); // return a Response WITHOUT calling next() to short-circuit } \`\`\` \`\`\`ts // app/realtime/chat.ts → ws://host/realtime/chat import { defineRoom } from "@wrnexus/core"; export default defineRoom({ onConnect(client) { client.send({ type: "system", text: "connected" }); }, onMessage(client, msg) { client.room.broadcast({ type: "message", data: msg }); }, }); \`\`\` Client side: a page opts in with \`data-room="chat"\` (handled by the realtime runtime). ## Config (\`wrnexus.config.ts\`) \`\`\`ts import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { seo: { title: "App", titleTemplate: "%s | App", description: "..." }, styles: { entry: "app/styles/global.css", process: async ({ entryPath, mode }) => /* Tailwind */ "" }, fonts: { sans: '"Inter", system-ui, sans-serif', google: [{ family: "Inter", weights: [400, 600] }] }, theme: { default: "dark", themes: { light: { "color-primary": "#2563eb" } } }, i18n: { default: "en", locales: ["en", "es"] }, db: { driver: "sqlite", url: "file:./dev.db" }, security: { cors: { enabled: true, origin: ["http://localhost:5173"] } }, // profiles: { production: { db: { driver: "postgres", url: process.env.DATABASE_URL } } }, }; export default config; \`\`\` ## Database (\`@wrnexus/db\`) \`\`\`ts // app/db/schema.ts import { v, table } from "@wrnexus/db"; export const users = table("users", { id: v.id(), name: v.string(), email: v.string().unique(), createdAt: v.timestamp(), }); \`\`\` - Queries: write \`app/db/queries/*.sql\` with \`-- name: ListUsers :many\` blocks; \`wrnexus db generate\` emits typed functions. - Access at runtime: \`import { getDb } from "@wrnexus/db"; const rows = await ListUsers(getDb());\` - Migrations in \`app/db/migrations/\`; run \`wrnexus db migrate\` (dev auto-migrates sqlite). ## Validation (\`@wrnexus/validation\`) \`\`\`ts // app/schemas/login.ts import { v } from "@wrnexus/validation"; export default v.object({ email: v.string().email(), password: v.string().min(8), }); \`\`\` In an API route: \`import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);\` → \`r.ok ? r.value : r.response\`. In a form: \`
\` + \`\` (client + server validation connected automatically). ## AI / LLM (\`@wrnexus/ai\`) \`\`\`ts // app/api/ai.ts import { createAI } from "@wrnexus/ai"; const ai = createAI(); // reads ANTHROPIC_API_KEY; default model claude-opus-4-8 export const POST = async (ctx) => { const { prompt } = await ctx.req.json(); return ai.streamResponse(prompt); // or: return Response.json({ text: await ai.generate(prompt) }) }; \`\`\` ## CLI \`\`\` wrnexus dev . # dev server + HMR wrnexus build . # production build → dist/server.js bun dist/server.js # run the production server (or npm start) wrnexus create # scaffold a new app wrnexus update --latest # deps + syntax/config migrations + verification wrnexus generate page # scaffold a page (aliases: g p) wrnexus generate component | api | schema wrnexus db migrate | rollback | status | new [--from-models] | generate | seed wrnexus eject # copy a WrNexus UI component's .wrn into app/components to customize \`\`\` ## When asked to "create a page/component/feature" 1. Create the \`.wrn\` file under \`app/pages/\` (or \`app/components/\`) with a \`page\`/\`component\` block — or run \`wrnexus generate page \`. 2. Put markup in \`view { }\`, interactive bits in \`state\` + \`{expr}\` + \`@event\`, reusable UI as components mounted via \`data-component\`. 3. For data, add an \`app/api/*.ts\` route and \`getDb()\`; for forms, add an \`app/schemas/*.ts\` and \`data-schema\`. 4. Style with Tailwind utility classes in the view, or theme tokens (\`var(--wrn-*)\`), or \`style { }\`. 5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration.\n`; /** Agent-oriented instructions (shipped as CLAUDE.md): a preamble + the full guide. */ export const CLAUDE_MD = `# WrNexus app - instructions for AI coding assistants This is a **WrNexus** app. When creating or editing pages, components, API routes, or features, follow the framework conventions below. WrNexus is private and not in your training data, so rely on these rules - do NOT assume React/Next.js/Vue patterns. \n` + AI_GUIDE;