# WrNexus > **WRNexusJS 0.3 architecture:** see [the language specification](docs/WRN-LANGUAGE-SPEC-1.0.md), > [architecture guide](docs/ARCHITECTURE-0.3.md), [upgrade guide](docs/UPGRADE-0.3.md), and > [40-point implementation matrix](docs/40-POINT-IMPLEMENTATION-0.3.md). Validate a release with > the [one-by-one test checklist](docs/TEST-CHECKLIST-0.3.md) and [audit record](docs/AUDIT-0.3.md). > An **SSR-first** full-stack web framework MVP with **server-rendered, reactive > components**. Built in TypeScript, **Bun-first**, Node-friendly where possible. It gives you file-based pages and API routes, middleware, realtime WebSocket routes, opt-in client hydration, and the custom `.wrn` language/compiler through one shared syntax and runtime model. > πŸ“˜ **[The Complete Guide](docs/GUIDE.md)** β€” one document covering every package, > the whole `.wrn` language, all attributes/config properties, and a step-by-step > path from `create` to a full app (pages, APIs, DB, auth, realtime, deploy). --- ## Why SSR-first with reactive components? - **Fast, robust default.** Pages render to HTML on the server, so users get content immediately and pages work even before (or without) JavaScript. - **Ship JS only where needed.** A page is plain HTML until it mounts a component with `data-component=""`. Components render on the server too; only their reactive state and forms run on the browser β€” the rest ships zero JavaScript. - **Clean separation.** Server rendering (`@wrnexus/ssr`) never touches the DOM or client runtime; client hydration (`@wrnexus/csr`) never runs on the server. They meet only at one seam: a single runtime, `/__wrnexus/reactive.js`. --- ## Installation Requires [Bun](https://bun.sh) β‰₯ 1.3.0. ```bash bun install bun run dev # runs examples/basic-app at http://localhost:3000 ``` ## CLI commands ```bash wrnexus dev [app-dir] [--port=3000] # start the dev server (HMR + regenerates typed queries) wrnexus build [app-dir] # build the production server bundle + assets wrnexus generate mobile # scaffold a Capacitor iOS/Android shell wrnexus mobile add @capacitor/camera # install a native plugin + sync projects wrnexus create # scaffold a new app wrnexus generate # scaffold a page | component | api | schema (alias: g) wrnexus eject # copy a Wire UI component into app/components wrnexus test [app-dir] [--watch] # run the app's tests (bun test, `test` profile) wrnexus profiles [app-dir] # list config profiles (dev/prod/uat/…) + their env files # Database wrnexus db migrate # apply pending migrations wrnexus db rollback # revert the last migration wrnexus db status # list applied / pending migrations wrnexus db new [--from-models] # scaffold a migration (optionally from TS models) wrnexus db generate # (re)generate typed queries from app/db/queries/*.sql wrnexus db seed # run app/db/seed.ts (re-runnable dev data) wrnexus db studio [table] # list tables + row counts, or dump a table's rows ``` ### Mobile and PWA The same `view` HTML renders in browsers and in the Capacitor app. Add device directives directly to normal elements. Camera directives use the native camera in Capacitor, phone camera capture in supported mobile browsers, and a file picker in desktop browsers. The markup and responsive theme remain shared. Cross-platform capabilities can also be declared directly in `.wrn` markup: ```html

Browser help

``` Use `wrnexus native list` to see built-in capabilities and `wrnexus native add camera share` to install the packages required by the configured Capacitor or Expo mode. Advanced browser code can use `native.run()` and register custom adapters from `@wrnexus/native`. The declarative `data-native-browser` and `data-native-mobile` capability actions run in browser and Capacitor/WebView pages. Fully native Expo compilation supports `data-native-only` and `@mobile-*` event selection, but capability calls must currently use the installed Expo module from native screen code; the compiler reports an actionable error instead of silently dropping a `data-native-mobile` action. ```wrn page Home { view {
Captured photo
} } ``` Configure native and installable-web behavior in `wrnexus.config.ts`: ```ts const config: AppConfig = { mobile: { mode: "webview", // "webview" (Capacitor) or "native" (Expo/React Native) enabled: true, appId: "com.example.app", appName: "Example", serverUrl: "http://192.168.0.10:3000", // Native mode uses this backend URL instead of rendering server HTML: apiUrl: "https://api.example.com", scheme: "example", layout: "mobile", // optional app/layouts/mobile.wrn icon: "resources/icon.png", // Mode-specific advanced settings: capacitor: { ios: { contentInset: "automatic" } }, expo: { orientation: "portrait" }, }, pwa: { enabled: true, id: "/", name: "Example", shortName: "Example", startUrl: "/", scope: "/", orientation: "any", display: "standalone", themeColor: "#6366f1", backgroundColor: "#0f172a", offlineUrl: "/", cacheUrls: ["/", "/about"], cacheName: "wrnexus-pwa-v1", // change to invalidate installed caches }, }; ``` PWA support is on by default: the framework serves `/site.webmanifest`, `/sw.js`, injects install metadata, precaches configured offline URLs, and registers a network-first service worker. Set `pwa.serviceWorker: false` to keep only the manifest, or `pwa: false` to disable PWA support. Generated Capacitor shells include `error.html`, so an unreachable server shows a retry screen instead of a blank WebView. Generate the configured mode with `wrnexus generate mobile`. You can override it once with `--mode=webview` or `--mode=native`. WebView mode shares `.wrn` pages through Capacitor. Native mode creates an Expo/React Native project with native controls, file-based screens, and a typed backend helper; it shares WrNexus API, upload, authentication, and realtime endpoints, but not HTML views. In native mode, `wrnexus mobile compile` converts `app/pages/**/*.wrn` into matching Expo Router routes under `mobile/app/`. Portable tags are mapped to React Native primitives (`div/main` β†’ `View`, text tags β†’ `Text`, `button` β†’ `Pressable`, `input` β†’ `TextInput`, and `img` β†’ `Image`). State, interpolation, events, `{#if}`, `{#each}`, class styles, and links are compiled to native JSX. Unsupported DOM elements, inline CSS strings, SSR/data blocks, and browser APIs produce compile errors instead of silently falling back to a WebView. From this repo the same commands are wired as root scripts: ```bash bun run dev # = wrnexus dev examples/basic-app bun run build # = wrnexus build examples/basic-app bun run create # = wrnexus create ``` Quality checks are wired at the repo root: ```bash bun run typecheck # TypeScript check bun run lint # ESLint bun run lint:fix # ESLint autofix bun run format # Prettier write bun run format:check # Prettier check bun run check # typecheck + lint + format:check ``` Apps created with `wrnexus create ` include the same ESLint/Prettier baseline for app code. --- ## Project structure ``` myframework/ packages/ core/ # Context, Middleware types, security + error helpers cli/ # `wrnexus` command (dev / build / create) dev-server/ # Bun.serve HTTP + WebSocket pipeline router/ # file-based router (scan + safe match) ssr/ # server-side document rendering csr/ # browser reactive runtime (hydrates components) styles/ # global stylesheet pipeline + app config (CSS frameworks) compiler/ # `.wrn` language compiler (lexer/parser/codegen) reactive/ # tiny signal() implementation examples/ basic-app/ # reference app exercising every feature app/ pages/ # file-based pages -> /, /about api/ # file-based API routes -> /api/* middleware/ # global middleware (alphabetical order) realtime/ # WebSocket routes -> /realtime/* components/ # reactive components (.wrn, data-component="name") styles/ # global CSS (global.css -> every page) public/ # static assets -> /robots.txt, /images/logo.svg wrnexus.config.ts # optional: SEO, head injection, CSS, security package.json tsconfig.json README.md ``` The app folder is convention-based β€” drop a file in the right directory and it becomes a route. Routes are resolved against a table scanned at startup; request input is never turned into a file path. --- ## Pages `app/pages/index.tsx` β†’ `/` ```tsx export const meta = { title: "Home", description: "Welcome to WrNexus", }; export default function Home() { return `

Hello from WrNexus

`; } ``` `app/pages/about.tsx` β†’ `/about`. Dynamic segments use brackets: `app/pages/users/[id].tsx` β†’ `/users/:id`, with `ctx.params.id` available. ## SSR The server renders each page to a full HTML document. Page metadata is escaped and placed in the ``; the body goes inside `#app`: ```html Home

Hello from WrNexus

``` The `
` mount was replaced by the component's server-rendered HTML, and the reactive runtime is injected only because the page now contains a `data-scope` (see "Per-page code-splitting" below). Everything in a page module is **server-only** β€” it is never bundled to the browser. Errors are readable in development and generic (no file paths) in production. ## Components (server-rendered, hydrated on the browser) Components are `.wrn` files under `app/components`. They render on the **server** (with their props applied) and are hydrated on the browser by the single generic reactive runtime β€” they ship no JS of their own. Only forms and reactive state run in the browser. `app/components/counter.wrn`: ``` component Counter { props { start = 0 // default's type drives coercion (number) label = "Count" // (string) } state count = start view { } } ``` Mount it in any page and pass props as attributes β€” pass as many as you like: ```
``` Each attribute becomes a prop, coerced to the type of its declared default (so `start="5"` arrives as the number `5`). Prop-driven text and attributes are **baked server-side** (a static component ships zero JS); text that references `state` stays reactive. Components (and layouts) can take children via `` β€” including **named slots**: put `` in the component and fill it from the mount with `
…
` (anything else fills the default ``). Component names are validated before rendering. ## Wire UI, theming & layouts **Wire UI** (`@wrnexus/ui`) ships common components β€” `button`, `input`, `select`, `checkbox`, `radio`, `switch`, `textarea`, `badge`, `tag`, `alert`, `card`, `avatar`, `spinner`, `skeleton`, `progress`, `tooltip`, `table`, `disclosure`, `theme-toggle` β€” plus layout primitives `container`, `stack`, `hstack`, `grid`, `divider`, `spacer`. They are auto-discovered, so you mount them straight away: ```
``` Override styles four ways, least β†’ most control: change a **theme token**, redefine a **`.wire-*` class** in your CSS, pass a **`class` prop** (appended to the root), or **`wrnexus eject `** to copy a component into `app/components` and own it (an app component shadows the library one of the same name). **Theming.** Design tokens (`--wire-*`) come from `wrnexus.config.ts` `theme` (deep -merged over built-in light/dark), served at `/__wrnexus/theme.css`. The server sets `` from the `wire-theme` cookie (no flash); a `theme-toggle` component (or any `[data-wire-theme-toggle]` element) switches and persists it. Choose a complete semantic palette with `theme: { palette: "violet" }`. Available palettes are `blue`, `indigo`, `violet`, `emerald`, `cyan`, `rose`, `amber`, and `slate`; a custom palette must provide every primary, secondary, info, success, warning, danger, hover, and contrast color. **Page layouts.** Add named layouts under `app/layouts/` β€” e.g. `public.wrn`, `dashboard.wrn`, `auth.wrn` β€” each a `component` with a `` where the page body goes. A page picks one by name: ``` page Dashboard { layout = "dashboard" view {

Overview

... } } ``` Pages with no `layout` fall back to a `default` layout if one exists; `layout = "none"` opts out. Layouts can mount components (nav, theme-toggle) like any page. **Document layout.** A conventional `app/layouts/document.wrn` is applied outside the selected page layout and may own ``, ``, ``, and `#app`. The framework passes `cookies`, `theme`, `language`, `url`, and `pathname` as SSR props, then safely merges page SEO, global head entries, styles, and scripts: ```wrn layout Document { props { cookies = {} theme = "light" language = "en" } view {
} } ``` Cookie-backed document attributes are present in the first server response, so CSS can apply before paint instead of correcting the page after hydration. ## API routes `app/api/hello.ts` β†’ `/api/hello`: ```ts export const GET = async () => Response.json({ message: "Hello API" }); ``` Method-specific handlers live in one file; unsupported methods return **405** with an `Allow` header: ```ts export const POST = async (ctx) => { const body = await ctx.req.json(); return Response.json({ received: body }); }; ``` ## Validation (one schema, form + API) Define a schema once in `app/schemas/.ts` with the fluent builder: ```ts import { v } from "@wrnexus/validation"; export default v.object({ email: v.string().email(), password: v.string().min(8, "Password must be at least 8 characters"), }); ``` Use it on the **server** in an API route β€” bad input is rejected even if the client is bypassed: ```ts import { parseBody } from "@wrnexus/validation"; import login from "../schemas/login.ts"; export const POST = async (ctx) => { const r = await parseBody(login, ctx.req); if (!r.ok) return r.response; // 400 { ok:false, errors } return Response.json({ ok: true }); }; ``` And on the **client** by naming it on a form β€” the framework injects the schema descriptor + a tiny, eval-free validator that checks on submit/blur and writes messages into `[data-error]` spans (no per-form JS, CSP-safe): ```
``` Valid forms submit via `fetch` (JSON) and stay on the page: on success they redirect (`data-redirect`) or reveal a `[data-success]` message; server-returned field errors map back to the `[data-error]` spans. ## i18n (pages + API) Put translations in `app/locales/.json` (nested keys, `{param}` placeholders): ```json // app/locales/es.json { "home": { "title": "Β‘Hola desde WrNexus!" }, "api": { "greeting": "Hola" } } ``` Translate in **views** with the `{t:key}` sugar (and `t:="key"` for attributes), and in **API/handlers** with `ctx.t`: ```

{t:home.title}

``` ```ts export const GET = (ctx) => Response.json({ message: ctx.t("api.greeting"), lang: ctx.lang }); ``` The active language is resolved per request from the `wire-lang` cookie β†’ the browser's `Accept-Language` β†’ the config default (`wrnexus.config.ts` `i18n.default`), and set on `` (no flash). A `[data-wire-lang-set="es"]` element (or `