@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:- Re-key on
runIdchange. If your UI lets the user switch between runs without a full page reload, abort the live stream, clearstate, and re-issuerefreshRun()/refreshApprovals()before painting. - Generation-tag in-flight reads. Increment a counter on every input change; drop responses whose generation no longer matches.
- 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.
useGatewayRpc source in packages/gateway-react/src/useGatewayRpc.ts is a 60-line reference if you want a minimal recipe.
Registering the UI
/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 inapps/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").
What this example demonstrates
- Vanilla SDK boot —
new SmithersGatewayClient()reads the boot config; no framework required. ?runId=parsing + same-origin RPC against the Gateway.streamRunEventsResilientas an async generator for pushed updates and metrics, managed with anAbortControllerlifecycle.- 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.