I Built a Chat App That Rewrites Its Own UI in Real Time A developer built FlowChat, an AI chat app that rewrites its own UI in real time by injecting raw HTML, CSS, and JavaScript from LLM responses directly into the DOM. Running on Cloudflare Workers and Durable Objects, the app enables dynamic features like playable games, theme changes, and animated backgrounds without a traditional server. So I had this idea that kept nagging at me. Every AI chat app works the same way. You type something, the model returns text or markdown, the UI renders it as a nice formatted paragraph. That is fine if you want an answer. It is genuinely boring if you want to actually build something. What if the AI could respond with a working game board you could click? What if saying "make it Barbie themed" actually transformed the whole interface while you watched? What if "add a starfield in the background" dropped an animated canvas behind your chat in real time? I spent a few weeks building exactly that. I call it FlowChat . Here is the live version: https://flowchat-public.varshithvh.workers.dev https://flowchat-public.varshithvh.workers.dev And yes, someone immediately asked it to play Tic Tac Toe and then asked it to switch to an Oppenheimer theme mid-game. I could not be prouder. Normal AI chat: model returns markdown, client renders it as text. Simple, predictable, boring. FlowChat: model returns raw HTML with CSS and JavaScript, client injects it directly into the DOM using a streaming protocol built on the browser's native template system. That one change makes the entire experience different. You are not reading about a game. You are playing one. You are not reading about a Barbie color palette. You are sitting inside one. The AI does not just answer questions. It rebuilds the UI from its responses . I want to give you a feel for what this means in practice before getting into the technical bits, because the demos are more interesting than any architecture diagram. Games : Ask it to build Tic Tac Toe. You get a playable board, click-to-move, an AI opponent, win detection. Ask for Connect 4. Ask for Snake. The game renders in the chat as an agent bubble with a form inside it. Each move submits to the LLM which processes it and updates only the cells that changed. Themes : Say "change to a Barbie theme". The model injects CSS overrides and the whole interface turns pink. Messages, borders, buttons, the prompt box. Say "Oppenheimer themed". You get dark sepia tones and heavy typography. The sidebar and topbar stay locked so the shell never breaks, but everything inside the chat transforms. Backgrounds : Say "add a starfield". An animated canvas renders behind your messages. Say "DVD bounce animation". The logo bounces around the chat viewport. Say "use a space image". An image fills the background. All of this lives in a contained layer so it never covers the actual UI. Full interface takeover : At one point I asked it to make the page look like Wikipedia. It replaced the prompt box with links. Clicking any link submitted a form back to the LLM which generated a new article replacing the chat content. I was reading about the Roman Empire in a chat app I built on a Sunday afternoon. Everything runs on Cloudflare's edge infrastructure. No traditional server. No Node.js process to keep alive. No managed database to worry about. Cloudflare Workers runs the TypeScript on every request. Cold starts are under 50ms globally. The whole worker is one file that handles routing, auth, rate limiting, WebSocket upgrades, and LLM streaming. Cloudflare Durable Objects is the part that makes this work. Each chat room is a single Durable Object: a stateful actor with its own SQLite database, its own in-memory queue, and its own WebSocket connections. When you and a friend open the same chat URL, you both connect to the same DO. Sync is not something you have to build. It is just how the architecture works. Each DO stores: Hibernatable WebSockets keep connections alive without keeping the DO alive. Cloudflare auto-handles ping/pong. The DO wakes up when a message arrives and goes back to sleep between them. better-auth handles optional authentication. If you do not configure it, the app is open to everyone. If you do, you get Google, GitHub, and email/password with role-based access admin, dev, chat, view, blocked . Inception Labs Mercury-2 is the model powering responses. It is a diffusion-based language model rather than autoregressive, which means it generates differently to GPT or Claude. In practice it feels fast and it seems to genuinely understand the HTML output format I need from it. This was the most enjoyable part to design and the part I am most proud of. The AI cannot just dump raw HTML into a response stream. A single response might need to update three different parts of the page independently. A Tic Tac Toe move should update one cell, not redraw the entire board. A background animation should not affect the sidebar. A private message to one player should not appear in the other player's chat. So I built a delimiter-based streaming protocol. The model wraps every DOM update in a structured envelope: PpqUtcLGQdYN4oqc:BODY START