When AI Moves the Button: Build a Support Loop for Adaptive UIs A developer detailed a control loop for AI-generated adaptive user interfaces, using React, TypeScript, Zod, and PostgreSQL to treat generated UI as versioned proposals. The approach validates UI manifests against a schema, stores immutable revisions, and enables support to replay exact versions, ensuring operational safety while limiting the model's control over persistence and actions. An adaptive interface can look impressive right up to the moment a user asks, “Where did the export button go?” That question creates an uncomfortable tension for developers. The model may be capable of producing a plausible layout, but plausibility is not the same as operational safety. Support cannot investigate a screen that no longer exists, and developers cannot improve a system when the only evidence is a screenshot of an ephemeral UI. The answer is not to preserve every pixel or require human approval for every spacing change. It is to treat generated UI as a versioned proposal with three properties: This tutorial builds that control loop with React, TypeScript, Zod, and PostgreSQL. Imagine an AI-generated account screen replaces a prominent Cancel subscription button with an ambiguous Manage plan menu. A production-ready sequence should be: Model proposes manifest ↓ Schema and policy validate it ↓ Server stores immutable revision ↓ Client renders revision ID ui 01J... ↓ User reports “I cannot find cancel” ↓ Report references ui 01J... ↓ Support replays that exact manifest ↓ Human freezes the scope and restores a known-good revision Notice what the model does not control: persistence, action permissions, deployment status, or rollback. That boundary separates the demonstrated capability—generating structured interface proposals—from the hype that a model can safely “own” the interface. The difficult part remains human work: deciding which changes are harmless, which reports indicate real harm, and when novelty is no longer worth the uncertainty. Do not evaluate model-generated JSX, JavaScript, URLs, package names, or import statements. Give the model a small UI language whose actions already exist in your application. js // ui-contract.ts import { z } from "zod"; const actionIds = "open profile", "open billing", "cancel subscription", "contact support", as const; const ActionIdSchema = z.enum actionIds ; export type UINode = | { type: "heading"; text: string; level: 1 | 2 } | { type: "text"; text: string } | { type: "notice"; tone: "info" | "warning"; text: string } | { type: "button"; label: string; actionId: z.infer