Component previews

js/ui/preview.md

fino:ui/preview renders one component catalog in two places: a retained terminal application and a live browser page. Preview views return ordinary fino:ui trees, so they exercise the same target lowerings as production code. There is no preview-specific component renderer.

Run the built-in catalog in the terminal:

fino preview

Or serve it locally in a browser:

fino preview --html --port 3080

The browser runner uses the existing fino:ui/web action protocol. Controls and component handlers post server actions, and updated trees return as SSE render events for in-place DOM reconciliation. The same forms remain usable as ordinary requests when client enhancement is unavailable.

Preview application components

A preview module default-exports an array of groups. Keys must be unique across the complete catalog because terminal selection, browser links, and action state all address previews by the same key.

import { h } from 'fino:ui';
import { Panel, Text } from 'fino:ui/components';
import type { PreviewGroup } from 'fino:ui/preview';

export default [
  {
    title: 'Application',
    previews: [
      {
        key: 'welcome-card',
        name: 'Welcome card',
        controls: {
          title: { type: 'text', default: 'Hello' },
          bordered: { type: 'boolean', default: true },
        },
        view: (args) =>
          h(
            Panel,
            { title: String(args.title), border: args.bordered === true },
            h(Text, null, 'Preview content'),
          ),
      },
    ],
  },
] satisfies PreviewGroup[];

Pass the module to either runner:

fino preview ./previews.ts
fino preview ./previews.ts --html

Preview descriptors own demonstrations, not component contracts. Keep behavior tests beside the component family; use the preview suite for catalog-wide key validation, dual-target renderability, runner navigation, and live action transport.

Programmatic runners

Use runPreviewTui() for a fullscreen terminal catalog. runPreviewHtml() returns the same ServeServer contract as the HTTP server APIs, including ready, close(), and async disposal. createPreviewApp() returns the app without opening a listener, which is useful for composition and deterministic request tests. previewPage() produces a static progressively enhanced page.

import { catalogPreviews, runPreviewHtml } from 'fino:ui/preview';

await using server = runPreviewHtml({
  hostname: '127.0.0.1',
  port: 0,
  groups: catalogPreviews(),
});
await server.ready;
console.log(`preview: http://127.0.0.1:${server.port}`);

The built-in catalog is assembled from co-located *.preview.ts modules. A family controls its own examples and state; the catalog only validates and orders them. This keeps preview additions local and avoids a second central registry of component behavior.