A few weeks ago I built TuringAI -- a streaming AI chat app. It worked. It was clean. It did what I needed. So naturally, I tore the whole thing apart and rebuilt it.

Not because it was broken. Because rebuilding what's already there is the best way to make your brain think about the small stuff.

When you build something from scratch, you're focused on making it work. When you rebuild it, you're focused on making it right. That's a completely different mode of thinking. You start noticing things you glossed over the first time -- the spacing that feels slightly off, the color that doesn't quite belong, the interaction that could be smoother. Your brain shifts from "does it function?" to "does it feel good?"

The Orange Revolution

The original TuringAI was built with a standard violet/cyan theme. It looked fine, honestly. But "fine" is the enemy of interesting. I wanted something warmer, something with more personality. So I went full orange -- warm orange and white for light mode, neon orange on black for dark mode.

It's a small change on paper. In practice, it meant touching every single component. Every border color, every gradient, every hover state, every shadow. I replaced all the arbitrary hex colors with standard Tailwind classes too, because I discovered the hard way that Tailwind v4's custom dark variant doesn't always play nice with arbitrary values like dark:bg-[#1F2023]. Switching to dark:bg-zinc-900 fixed the theme toggle instantly. That's the kind of thing you only learn by rebuilding.

A Prompt Box That Does Everything

The original input was a plain textarea. Functional, boring. I replaced it with a rich prompt box that has animated toggle buttons for Search, Think, and Canvas modes. Each button rotates and glows when active using Framer Motion. There's a file upload with drag-and-drop and image preview, a voice recorder with a real-time visualizer, and tooltips powered by Radix UI.

Did users need all of that? Probably not. But building it taught me how to wire up Radix primitives, how to handle drag events properly, how to build a context-based component architecture where the textarea, actions, and submit button all share state through React context. Those are patterns I'll use again.

The Sign-In Page Nobody Will Use

I built a full WebGL-powered sign-in page with Three.js. It has an animated dot matrix canvas effect -- tiny dots that reveal from the center outward when you load the page, then dissolve from the edges inward when you complete the sign-in flow. There's a floating navbar with animated hover effects, a code verification step, and a success animation.

Will most users ever see it? No, they'll click "Continue as Guest." But here's the secret: the sign-in page is also where my admin login hides. No visible admin button anywhere on the site. Just a normal-looking email and password form. The password is actually an admin secret that gives me unlimited access, bypassing the rate limiter. It hides in plain sight -- any hacker looking for an admin panel won't find one.

Making the AI Name Conversations

This was one of those "small stuff" moments that made my brain light up. The original app titled conversations by truncating the first message: "Hi whats the time in Ind...". Boring. I wanted the AI to generate a smart title based on the actual conversation context.

The challenge was timing. My first attempt called the title API as soon as an assistant message appeared -- but during streaming, that meant it fired after the first token, when the response was basically empty. The fix was waiting for status === "ready", which signals that streaming is complete. Only then do I send the full conversation to a lightweight API endpoint that asks Kimi K2.5 to summarize it in 2-5 words. So "Hi whats the time in India" becomes "Indian Standard Time Zone".

Two phases: instant title from the first message so the sidebar isn't blank, then an AI-refined title once there's actual context. It's a tiny detail that makes the whole app feel smarter.

Rate Limiting Without a Database

I needed to limit free users to 20 messages per day without adding a database. The solution: an in-memory Map keyed by IP address, with a 24-hour window. It resets on cold starts (Vercel serverless), but that's actually fine -- it means the limit is generous rather than strict. If someone really wants to abuse it, they'd need to make 20+ requests before the function recycles.

I also built a usage circle that sits next to the prompt box -- a tiny SVG ring that fills up as you use messages. Hover over it and you see "12 / 20 messages used" with a progress bar. The color shifts from orange to amber to red as you approach the limit. If you're admin, it shows a green infinity symbol. It polls every 30 seconds and refreshes after each message.

Font Picker Because Why Not

I added a font picker in the settings panel with four fonts: Inter, DM Sans, Plus Jakarta Sans, and Space Grotesk. Each one loaded via Google Fonts with CSS custom properties. The selection persists in localStorage and applies instantly through a data-font attribute on the HTML element.

Is this necessary? No. Did it teach me how Next.js handles multiple font loading with the new next/font/google API and how to wire CSS custom properties to font-family declarations? Yes. That's the point.

The Reusable Logo Component

The original app had a letter "T" as a placeholder logo everywhere. I created a proper TuringLogo component -- an SVG circuit-brain icon inside an orange-to-amber gradient square. It takes a size prop (sm, md, lg) and an optional showText flag. Now it appears in the sidebar header, the empty chat state (with a pulsing glow effect), every AI message avatar, and the thinking indicator. One component, five placements, zero repetition.

What I Actually Learned

Here's what rebuilding taught me that building never could:

Tailwind v4's dark mode is picky. Custom variants with arbitrary hex values can break silently. Stick to named colors for anything inside a dark: prefix.

Streaming changes your timing assumptions. React effects fire on every message chunk during streaming. If you want to do something "after the AI responds," you need to check the chat status, not just the message array.

Security through obscurity works for small projects. Hiding admin auth behind a normal sign-in form is more secure than having an "Admin Login" button that invites brute-forcing.

Small details compound. A usage circle, an animated theme toggle, a font picker, AI-generated titles -- none of these are features anyone asked for. But together, they make the difference between an app that works and an app that feels like someone cared.

The Takeaway

Building something new is exciting. Rebuilding something that already works is where the real creative thinking happens. You're not solving the "does it work" problem anymore. You're solving the "could this be better" problem. And that's a fundamentally different kind of challenge -- one that forces you to care about the details that most people skip.

TuringAI is live at turing-ai-one.vercel.app. The code is on GitHub. Go ahead, break it. I'll probably rebuild it again next month.