web

js/ui/web.ts

fino:ui/web — server-driven HTML and semantic UI for fino:net/http/app.

This module renders fino:ui VNodes into real HTML pages and handles form actions by rehydrating durable view snapshots. It is intentionally hypermedia-first: forms keep real action and method attributes, enhanced requests receive short-lived JSON UI streams, and plain browser submits use POST-redirect-GET.

Every SSE response uses the same JSON ui events and host-neutral VNode structure. A client can open a page route directly as an EventSource and receive the current snapshot before subsequent updates. Component type names are routed by the client through its own host adapter. The server owns view state and action execution, but it does not register platform component implementations or receive client-local interaction state.

import { App } from 'fino:net/http/app';
import { h, Signal } from 'fino:ui';
import { page, view, webUI } from 'fino:ui/web';
import { memoryStore } from 'fino:store';

const app = new App();
const ui = app.layer(webUI({ store: memoryStore(), secret: 'dev-secret' }));
ui.get('/').handle(page(() => h('main', null, 'Hello')));

Interfaces

interface ViewActionContext {

Context supplied to a server-driven view action.

Properties

state: StateRecord

Mutable signals restored from the durable snapshot.

snapshot: ViewSnapshot

Snapshot from which this action started.

http: HttpContext

HTTP request context for the action.

Methods

checkpoint(): Promise<void>

Persist the current signals and publish a live render before the action finishes. Await checkpoints to preserve render order.

interface PortableActionRef {

Serializable action descriptor embedded in component props.

Send these fields back in a PortableActionRequest; do not construct action URLs or revision tokens in the client.

Properties

action: string

Action name within the mounted view.

url: string

Relative HTTP endpoint for the action request.

view: string

Mounted view instance id.

revision: number

Snapshot revision from which the action was rendered.

request: string

Single-use request nonce for replay protection.

confirm?: string

Message a client should confirm before sending the request.

interface PortableActionRequest {

JSON body posted by a client to a PortableActionRef.url.

Properties

version: 1

UI protocol version. Version 1 is currently supported.

view: string

Mounted view instance id from the action descriptor.

revision: number

Snapshot revision from the action descriptor.

request: string

Request nonce from the action descriptor.

input?: Record<string, PortableValue>

Optional JSON object validated by the action's input schema.

interface PortableRenderEvent {

Complete semantic render of one mounted view.

Clients reconcile tree against the previous tree using component names and keys. The server does not keep or receive a registry of client implementations.

Properties

version: 1

UI protocol version.

kind: 'render'

Render event discriminator.

view: string

Stable server view definition id.

viewId: string

Mounted view instance id.

revision: number

Monotonic snapshot revision, also sent as the SSE event id.

tree: PortableVNode

Host-neutral component tree.

interface WebUIOptions {

Properties

store: AtomicStore

Generic atomic store used for durable view snapshots.

secret: string

Secret used to seal CSRF tokens and sealed embedded state.

ttlMs?: number

Snapshot lifetime in milliseconds. Defaults to one hour.

maxActionBytes?: number

Maximum JSON action body size in bytes. Defaults to 64 KiB.

sweepIntervalMs?: number | false

Minimum time between opportunistic snapshot sweeps.

Sweeps run before requests handled by this middleware. The default is one minute. Set to false to operate cleanup from an external scheduler, or 0 to sweep on every request (primarily useful in tests).

interface ViewDefinition {

Definition passed to view().

Properties

id: string

Stable id used in action URLs and stored snapshots.

state: () => StateRecord

Factory called for each render or action event to create fresh signals.

embed?: EmbedSpec[]

Signal keys carried in HTML instead of the snapshot.

actions?: Record< string, { handler: ActionHandler; input?: JsonSchema; stale?: 'reject' | 'rebase'; confirm?: string } >

Server actions addressable from rendered forms.

derived?: string[]

Signal keys projected from an authoritative external store.

Derived signals are rendered but never written to the view snapshot, so a store that already owns the data stays the only durable copy. Seed them through derive rather than expecting hydration to restore them.

derive?: (args: { state: StateRecord; http: HttpContext; snapshot: ViewSnapshot; }) => void | Promise<void>

Populate derived signals before a render web.ts owns.

This runs on the action and live-stream paths, which are already async. The initial mount() render is synchronous, so a caller that mounts a view with derived signals must seed them before calling mount().

render: ViewRender

Render function for the view's current state.

Types

type PortableUIEvent = | PortableRenderEvent | { version: 1; kind: 'heartbeat' } | { version: 1; kind: 'navigate'; url: string; replace: boolean } | { version: 1; kind: 'error'; code: string; recoverable: boolean } | { version: 1; kind: 'close' }

Event carried under the SSE ui event name.

A live stream starts with render (or navigate when its view expired). render replaces the current semantic tree, heartbeat can confirm a live stream, navigate requests a page transition, error reports a safe protocol failure, and close ends a short-lived action or error stream.

Classes

class ViewActionError extends Error {

Action failure that carries a stable, publicly safe error code.

An action handler throws this when the caller should learn why the request failed. Any other thrown value is reported as action_failed with its details published only to the internal diagnostics topic, so a handler must opt in before anything reaches the client.

import { ViewActionError } from 'fino:ui/web';

throw new ViewActionError('flow_stale_step', { status: 409, recoverable: true });

Readonly Properties

readonly code: string

Stable code sent to clients as the SSE error code.

readonly status: number

HTTP status for the action response.

readonly recoverable: boolean

Whether a client may retry after resynchronizing.

Constructors

constructor( code: string, options: { status?: number; recoverable?: boolean; message?: string } = {}, )

Functions

function view(def: ViewDefinition): ServerView

Create a server-driven view definition.

function clientScriptPath(): string

Return the content-hashed browser runtime path served by webUI().

function webUI(options: WebUIOptions): LayerMiddleware

Middleware that installs server-driven UI handling for an App.

function page(render: (ctx: HttpContext) => VNode): Handler

Create a page route handler that returns HTML or a live JSON UI stream.