Docs / Developer / React UI layer for structural rendering safety
React UI layer for structural rendering safety
Status: accepted · 2026-07-05
Supersedes: ADR-007's "decide per chat surface later." ADR-007's engine rule is carried
forward unchanged and hardened here.
Context
Two constraints crystallized after ADR-007:
- Bunzai renders untrusted card text and extension fields. Direct DOM construction makes
HTML-sink safety depend on every caller following convention. React escapes text by default,
while repository gates can ban the small set of explicit escape hatches. - The roadmap's chat/agent surfaces (M4 Table Read, packages/agent, Studio app) plus the
per-surface agent-context plan require UI state to be serializable data an agent can
read and act through. Closure-held vanilla DOM state is agent-opaque; store-driven
React state is the pattern comparable production apps (Marinara Engine) ship.
Decision
- The UI layer is React 19 + TSX, transpiled natively by Bun (no Vite, no Next), bundled
into the same single exe. - State is Zustand: one shell store plus one store per app surface. Navigation is
state-driven; there is NO URL router. - Styling is CSS Modules over the existing token system. NO Tailwind.
- The UI converts WHOLE - shell and every app folder - one idiom, no permanent mixed
zone. Until the conversion lands, the vanilla surfaces remain the behavioral spec. - The app contract v2 is a React component export plus manifest, including an
agentSurfacedescriptor (state selector + offered actions) so every surface,
first-party or drop-in, is agent-readable by construction. - ENGINE RULE (unchanged, hardened): the converter, canonical model, engines, and every
*-core.tsstay pure framework-less TS. A renderer import in that layer is a guardrail
violation, not a style choice. - Dependency posture: react, react-dom, and zustand are direct dependencies covered by the
repository's license audit. New dependencies follow normal review; no special commit trailer is
enforced. dangerouslySetInnerHTMLand raw HTML-injection sinks are banned by the repo gates
outside an explicit sanctioned allowlist.
Consequences
- Editor slice 1 and the current shell are rebuilt in React during the conversion; their
behavior and their pure cores (tested) are the spec, so the rewrite is bounded. - Ordinary text rendering is escaped by default; repository gates separately enforce the raw-HTML
escape-hatch ban. - React's established tooling and contributor familiarity reduce the cost of maintaining the UI.
- The framework-churn risk (React major versions) is accepted and bounded: the engine
layer is immune, and surfaces are isolated per app folder.