# @wrnexus/csr > The browser-side client runtime for WrNexus — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/csr` holds the three client runtimes that WrNexus serves to the browser. Components are authored as `.wrn` files and rendered on the **server**; this package provides the single, generic runtime that **hydrates** that HTML in the browser — there are no per-component browser bundles. Each runtime is exported as a plain-JS string (no build step, no imports) intended to be served verbatim from a well-known URL: - **reactive** at `/__wrnexus/reactive.js` — reactive directives (`data-scope`, `data-text`, `data-for`, …) - **nav** at `/__wrnexus/nav.js` — SPA-style client navigation with graceful fallback - **realtime** at `/__wrnexus/realtime.js` — WebSocket "rooms", declarative or programmatic The package itself runs on the server (it just returns strings); the strings it returns run in the browser. A dev/prod server (see `@wrnexus/core`) is responsible for actually serving them. ## Installation ```bash bun add @wrnexus/csr ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API All exports come from the package root (`@wrnexus/csr`). The runtime source is delivered as strings, so the "API" on the server side is small; the real surface is the browser directives/globals each string installs. ### Runtime strings | Export | Type | Served at | Contents | | ------------------ | -------- | ------------------------ | ------------------------------ | | `REACTIVE_RUNTIME` | `string` | `/__wrnexus/reactive.js` | Reactive directive runtime | | `NAV_RUNTIME` | `string` | `/__wrnexus/nav.js` | Client-side navigation runtime | | `REALTIME_RUNTIME` | `string` | `/__wrnexus/realtime.js` | Realtime rooms runtime | ### Accessor functions Convenience getters that return the same strings. ```ts getReactiveRuntime(): string // → REACTIVE_RUNTIME getNavRuntime(): string // → NAV_RUNTIME getRealtimeRuntime(): string // → REALTIME_RUNTIME ``` ### Browser: reactive directives Applied to any subtree containing `data-scope`. Expressions are parsed by a tiny eval-free evaluator, so a strict CSP with no `unsafe-eval` works. | Directive | Purpose | | -------------------------------------------------------- | ----------------------------------------------------------------------- | | `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree | | `data-on-="count++"` | Run a statement in scope on a DOM event | | `data-text="expr"` | Bind an element's `textContent` to an expression | | `data-show="expr"` | Toggle visibility (`display`) on truthiness | | `data-for="item in list"` (opt. index and `key item.id`) | Per-item rendering; stable keys preserve DOM identity during reorder | | `data-key="item.id"` | Alternative key declaration for `data-for` templates | | `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values | | `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) | Supported expression features: literals, identifiers, member access (`a.b`, `a[b]`), function/method calls, arrays, objects, arithmetic, comparison, equality, logical (`&& ||`), unary (`! - +`), and ternary. Statements support `++`/`--`, assignment operators (`= += -= *= /= %=`), and bare expression/method calls. Rendering is dependency-tracked: a signal change only re-runs the renderers that actually read it. Browser globals installed: `window.__wrnexusHydrateScopes(root)` and `window.__wrnexusHydrateCsrFetches(root)` — both idempotent, so re-running after a DOM swap or HMR morph is safe. Both run automatically on `DOMContentLoaded`. ### Browser: navigation Intercepts same-origin `` clicks, fetches the target page, and swaps the `#app` container in place (via `importNode` — not `innerHTML` — so it works under a Trusted-Types CSP), updating history, title, and scroll, then re-hydrates. Cross-origin links, modified clicks, `download`/`data-no-nav`/`rel="external"`/`target` links, non-HTML responses, or a missing `#app` fall back to a full browser navigation. - Programmatic navigation: `window.__wrnexusNavigate(url)` - Emits a `wrnexus:navigated` `CustomEvent` (`detail.url`) after each swap - Sends `x-wrnexus-nav: 1` on fetches so the server can return the page fragment - Appends any `/__wrnexus/*` runtime scripts the incoming page needs but the current document lacks ### Browser: realtime rooms Connects to `/realtime/` over WebSocket (`ws`/`wss` chosen from `location.protocol`). Two usage modes. Programmatic API via `window.wire`: ```ts wire.room(name): Room // open (or reuse) a room connection wire.bindRooms(root?) // (re)bind declarative [data-room] containers interface Room { name: string; send(obj: object | string): Room; // JSON-stringifies objects; queues until open on(type: string, cb): Room; // filter by msg.type; "*" or a fn = all messages on(cb): Room; close(): Room; } ``` Internal lifecycle messages are emitted to listeners as `{ type }`: `__open`, `__close`, `__error`, and `__raw` (non-JSON frames, with `data`). Reconnect uses exponential backoff capped at 5s; queued sends flush on reconnect. Declarative binding (zero JS) on a `data-room=""` container: | Attribute | On | Purpose | | ------------------------------------ | --------------- | ------------------------------------------------------------------------ | | `data-room=""` | container | Connect to room `` | | `data-room-user=""` | container | Identify the connection (`?user=`) | | `data-room-log` | element | Where incoming messages are appended | | `