npm.io
0.1.2 • Published yesterdayCLI

renderstate

Licence
MIT
Version
0.1.2
Deps
3
Size
120 kB
Vulns
0
Weekly
0

renderstate

npm version license

A deterministic UI-state renderer for React screens. Point it at your screen components; it discovers them, reads their prop contracts from TypeScript, derives a small-but-strong set of prop combinations, renders each in isolation (no router, no backend, no app boot), and screenshots them. The output is a neutral manifest.json plus PNG frames. A static board is the first viewer of that manifest; a publisher adapter can send the same output anywhere.

Install

npm install renderstate

Published on npm: npmjs.com/package/renderstate.

Pipeline

component discovery (convention)
      → typescript inspection (ts-morph, never guesses)
      → value resolution (generic + fixtures, recursive)
      → case generation (anchors + pairwise)
      → direct rendering (project's own Vite + Playwright)
      → frames + manifest.json
            ├── board viewer  (renderstate serve)
            └── publisher adapter  (renderstate publish)

Integrating a project

Three things, nothing more:

  1. renderstate.config.ts — where the screens are, viewport, labels, publishers.
  2. Fixtures module — factories for domain types the engine can't synthesize, keyed by type name, with named variants. Node-safe (no CSS/JSX).
  3. Wrapper module — a wrap(node) decorator giving screens your global CSS / fonts / frame.
// renderstate.config.ts
import { defineConfig } from 'renderstate';
export default defineConfig({
  screens: ['src/screens/*-screen.tsx'],   // convention: *-screen.tsx + exported *Screen
  harness: 'src/renderstate.harness.tsx',  // default-exports wrap(node)
  fixtures: 'src/renderstate.fixtures.ts', // default-exports Record<TypeName, { variants }>
  viewport: { width: 402, height: 874 },
});
Multiple sizes

Capture every screen at several device sizes and let the viewer toggle between them. The first entry is the default (also written as each frame's image); the manifest carries a per-size images map.

export default defineConfig({
  screens: ['src/screens/*-screen.tsx'],
  harness: 'src/renderstate.harness.tsx',
  fixtures: 'src/renderstate.fixtures.ts',
  viewports: [
    { id: 'pro', label: 'iPhone 16 Pro', width: 402, height: 874 }, // default
    { id: 'se', label: 'iPhone SE', width: 375, height: 667 },
    { id: 'max', label: 'iPhone 16 Pro Max', width: 440, height: 956 },
  ],
});
renderstate generate   # write .renderstate/{manifest.json,index.html,frames/}
renderstate serve      # open the board on localhost
renderstate publish    # run your publisher adapter(s)

What it derives automatically

TypeScript states
boolean false, true
literal union / enum each member
T | null null + a representative T
optional prop? absent + a representative value
T[] empty + populated (element resolved the same way, recursively)
callbacks a no-op recorder
string / number deterministic representative values
named domain type your fixture registry (fails loudly if unregistered)

Core invariant: renderstate never guesses about an unresolved type. A type it cannot resolve confidently, with no fixture, stops the run with the screen and prop named — never a silent skip.

Determinism

Guaranteed for a fixed (renderstate version, browser environment, source, fixtures): frozen clock, disabled animations, fixed viewport + DSF, loaded fonts, seeded generic values. Not "identical bytes forever" — browser/font/platform drift is real.

Not in v1 (on purpose)

Not a testing framework, navigation/backend simulator, replay system, or publishing platform. The manifest is the product; those are separate consumers that can be built on top of it later.

See examples/mini for the whole integration in a second, non-Matra project.

Product direction

The living product thesis, roadmap, and research questions are grounded in the current implementation. GitHub issues track bounded work; speculative ideas stay in research until real usage earns them a build commitment.

Keywords