Skip to main content
This is the same workflow UI as the React example, built directly on @smithers-orchestrator/gateway-client with no framework. Pick this shape when you want a tiny bundle, when you already own your render layer (Solid, Lit, Mithril, plain DOM), or when you need the UI to render before any framework has booted. For the full conceptual walkthrough, see Custom Workflow UIs.

The bundle

Stale-data-free in vanilla

The React layer’s stale-data-free model is built into the hooks. In vanilla code you wear that hat yourself. Three rules:
  1. Re-key on runId change. If your UI lets the user switch between runs without a full page reload, abort the live stream, clear state, and re-issue refreshRun() / refreshApprovals() before painting.
  2. Generation-tag in-flight reads. Increment a counter on every input change; drop responses whose generation no longer matches.
  3. Render after every fetch resolves, never before. A render that prepaints the previous state and then updates is the most confusing failure mode at the iframe boundary; render only after the new fetch lands or after an explicit “loading” tick.
The React useGatewayRpc source in packages/gateway-react/src/useGatewayRpc.ts is a 60-line reference if you want a minimal recipe.

Registering the UI

The Gateway bundles the entry with esbuild (no React injected, no framework assumed) and serves it at /workflows/demo-vanilla-ui.

Test pattern (no UI library coupling)

A vanilla bundle pairs nicely with a Playwright test that asserts the HTML the bundle painted, since there is no virtual DOM to traverse. The fixture in apps/smithers/tests/fixtures/demoWorkflowUi.ts is exactly this pattern: render a <main data-testid="demo-vanilla-ui"> with a data-testid="demo-run-id" child, then assert against the iframe with customUiFrame(page).getByTestId("demo-run-id").
This is the integration test — bundle + serve + embed + deep-link — that catches the regressions vanilla bundles are most likely to hit.

What this example demonstrates

  • Vanilla SDK bootnew SmithersGatewayClient() reads the boot config; no framework required.
  • ?runId= parsing + same-origin RPC against the Gateway.
  • streamRunEventsResilient as an async generator for pushed updates and metrics, managed with an AbortController lifecycle.
  • Node output reads, approvals, and submitApproval with explicit error handling.
  • A render model you author yourself that mirrors the stale-data-free contract the React hooks provide.