From Hit-and-Miss to Ideas We Took Forward
Exploring a design properly is expensive, so a feature gets one direction drawn at its happy path, and the states that actually decide it never get drawn at all. AI looked like the fix, and at first it made things worse. So I built the Rayo Design Lab, where AI builds inside our own design system, with the rules for using it written down where it can read them. What comes out is worth arguing over, and some of it we’ve taken forward. It’s also built so the rest of the team doesn’t need me to use it.
My role
Design and development. I built the lab, wrote the guide that gets the team into it, and set up how work gets reviewed. I own the foundations every prototype sits on; any designer can review and merge an exploration.
Impact
💡 New ideas land inside the product, not beside it
The lab keeps the app’s own screens as they actually are, so a new idea gets designed into the real screen rather than generated as a fresh page of invented parts. That’s the difference between reviewing a change to the product and reviewing something that merely resembles it.
🗺️ A feature arrives as competing directions, not one frame
Every exploration puts several working options side by side rather than one frame. Each option stating what it’s betting and what it costs, measured, in the frame, next to the thing it describes.
⚠️ States we’d have got to last
Building this way surfaces the states nobody has got to yet. The ones that are neither an error nor progress, and so get drawn last or not at all. More than once they’ve changed the direction we took rather than just how quickly we got there.
⛹️♂️ A playground the whole team can get into
It’s meant to be somewhere you try things, and the designers do the trying. The lab carries its own guide, written for designers who have never opened Terminal, from a blank Mac to a first prototype. Any designer can approve someone else’s exploration, so an idea never waits on the person who built the tool.
Which design A B C D What condition Default Offline Error The bet Needs a system decision Still unverified the screen under test
The anatomy of one exploration
Problem
Two things were true at once, and each made the other worse.
Exploration was expensive, so it stayed narrow. Producing screens in Figma was never the problem; prototyping them is. A Figma prototype is a graph of screens, and every state is another screen (loading, empty, error, offline, locked etc.) each one drawn, linked and kept in step by hand. Three directions across five states is fifteen screens to maintain, and a change to one of them is a change to all of them. That cost lands hardest on user testing, where the version you test stays the version you drew first, whichever way the first session went.
And AI, on its own, made it worse rather than better. Designing with it felt like gambling: write a prompt, spin, mostly miss. The output was always nearly right, which is the worst kind of wrong: a colour close to ours, a component we don’t have, a locked state invented from scratch. You can’t decide anything from a screen that is approximately the product.
It wasn’t the tool, it happened across every AI design tool I tried. Give a capable model nothing of ours to work from and you get something plausible and generic, because that is all the information it has.
It started as the Storybook iOS can’t have
Rayo ships on iOS and Android, and the two platforms aren’t equally stuck. Android has workable routes to a browsable component gallery, iOS doesn’t. SwiftUI previews live inside Xcode, which is not a place a designer goes. I started from the iOS side, which is the half with no answer, and mirrored it in React: I extracted it from the iOS source with Claude Code: colours from the asset catalogue, spacing and radius constants, type sizes and weights, and each component’s variants, states and animation timings from its Swift file.
It is a smaller library than a designer expects, and that is worth explaining. Engineering makes something a component when it gets reused; designers make components of almost everything, including modules assembled out of other modules. So a library mirrored from the app is structurally short - a dozen or so real primitives, and the composing happens in the prototype instead. Which turns out to be the right split: what the app guarantees lives in the library, and everything still being decided stays where it can be argued with.
The icons are in there too: the full set, browsable and grouped by what each one is for, including the handful the app only ships as images and so have no component at all.
That alone would have been useful. It’s also the least interesting thing here: a library is a catalogue, so it tells you which components exist. It doesn’t tell you which of them is the wrong choice for the screen in front of you.
First build Today
The part that made the AI useful
Each component ships a contract beside it - a Markdown file next to the code, and where they disagree the Markdown wins. Not an API reference. Use when, don’t use when, content rules, anti-patterns.
None of that is in the code, and none of it was written down anywhere. It lived in designers’ heads and in review comments. It’s also exactly what an agent needs, because an agent doesn’t hedge and doesn’t ask: given a list of components and no rules, it picks something reasonable-looking and is confidently wrong.
MD Button.md ## Use when / Don’t use when - **Don’t use** for navigation between tabs or list rows. - **Don’t use** for playback controls — playback has its own components, with different sizing and hit areas. ## Anti-patterns - **Don’t add a `disabled` boolean.** The iOS API models disabled as a *style* ; duplicating it as a prop lets the two disagree. - Pressed state drops to `0.1` opacity over `200ms` . This is unusually aggressive — it is the intended feel, so don’t “fix” it.
One component’s rules, in a file an agent can read
The cold-start test
Documentation nobody can fail is documentation nobody maintains. So the system has a test. Open a fresh agent session in the repo - no history, no hints - and give it one prompt: build a list screen for a piece of locked content. Then audit what comes back. Right components? Tokens throughout? Locked state handled the way the pattern doc describes? Both themes?
The rule that makes it useful is how you read the result: every failure is a documentation bug, not a model failure. Don’t rewrite the prompt and don’t add context in the chat - find the missing rule, put it in the contract, run it again.
Then it became a place to explore
Once real components existed with rules attached, building a screen stopped being the expensive part, and in code a state is a prop, not another screen. That single difference is what makes exploring several directions across all their states affordable at all. So the lab grew a prototype harness, and the harness is built around the argument rather than the screen.
Each exploration has two sets of controls. One switches between the design options; the other switches the state: offline, error, loading, locked. They look different on purpose, because they aren’t the same kind of choice. Any option can be seen in any state, which is the thing a static mockup can’t do.
An example of the Design Lab’s prototype harness
Each option carries a bet: what it’s good for, and what it costs, written next to it. Not a description of the layout - an argument, with the trade-off named and measured where it can be. Rejected options keep their place, with the reason they lost written next to them. It is much easier to agree that something is too loud once you can see it, and easier still not to have the same argument again in three months.
Downloading in the player Treatment The bet
What the screen draws today, finished: the icon’s own circle becomes a progress ring, and nothing else moves. No layout shift, no reserved space. Its cost is legibility - at that size a ring reads as “something is happening”, not as “42%, about a minute left”. If downloading is meant to sit in the background, this is right and the rest are over-design.
Every prototype also carries two lists underneath it. The first is called Needs a system decision, and it collects the gaps that exploration ran into. The second is Still unverified, and it lists everything in the screen we made up: invented content, guessed values, placeholder copy. One prototype says outright that a number in it is invented and has to be replaced before anyone sees it, because that number is the whole thing a test of that screen would measure. Both lists are part of the work rather than an appendix to it. A prototype that hides what it guessed is worse than one that stops and asks.
Which is the opposite of where I started. When I began, designing with AI was a bet I couldn’t see the odds on. Now every option has to state its own.
The states we’d have got to last
This is where it stopped being a faster way to do the same work. Downloading a podcast has a state most download UIs skip: the listener taps download while on mobile data, with mobile-data downloads switched off, and nothing happens. The control ended up with six states rather than the obvious four.
Download control State
The first four are the main line. The outlined two branch off it, and they are the ones most designs skip.
Waiting
Tapped on mobile data with mobile-data downloads switched off. Not an error and not progress - and the one that generates the support ticket, because the listener tapped download, nothing happened, and no screen says why.
The other was storage: how much room downloads are taking and how much is left, at the top of the screen. Obvious once it’s there, and a list of episodes cannot answer the question everyone actually has before a flight. Neither is decoration, and the second one carried weight in where the feature landed.
Example of a great suggestion that we kept
I don’t read that as the model being clever. The house rules in the repo say build the unhappy states, and the design doc says a screen isn’t finished until loading, empty, error and locked all exist. What happened is that a rule I’d written got applied further than I’d applied it myself - because state exploration is tedious, so humans do it last and under time pressure, which is exactly why unhappy states ship broken. Write the intent down properly and it holds you to it too.
Ideas need something true to start from
Once the team was exploring in it, a different problem showed up. The ideas were good, but the screens they sat on drifted. Every prototype that touched an existing screen rebuilt it first, from memory or from another prototype, and every rebuild lost something. The podcast show page was rebuilt three separate times, and five prototypes ended up with a nav bar 20 points taller than the app’s, because there was no correct version anywhere to copy. An idea shown on a screen that isn’t quite the app has the same problem as AI with nothing of ours to work from: you end up arguing about the difference rather than the idea.
So the lab grew a second portal, Pages: the production screens rebuilt as they are, from the reference designs and the iOS source. It now covers close to every screen in the app, tab by tab, plus settings, the premium flow and login.
A Page plays by different rules from a prototype. It answers no question, so it has states but no options. It has to name its source, so anyone can check it against the thing it claims to be. And it isn’t allowed to improve the screen, even where the screen is wrong, because a baseline that quietly fixes things can’t tell you which parts are the product.
The For you tab as a Page: one truth, its states, and the rest of the app in the sidebar
The Pages link up the way the app does. You can tap from the Welcome screen through login and land on For you, or from a Go Premium prompt through sign-up to the congrats screen and back to where you started. The content is Rayo’s real catalogue, so a show looks the same on every screen you find it on.
A prototype now starts from the Pages rather than redrawing them. The first one built this way explores five ways to hand Hits Radio app listeners over to Rayo, and it doesn’t copy the screens it touches: it uses the Pages themselves, so a fix to a Page reaches every prototype built on it. What differs between the directions is the idea, not the screen underneath it.
A new idea built on a real Page: the Welcome screen as it ships, with the sign-in on top
A Page is also the strictest audit the design system gets, because it’s the only thing in the lab that isn’t allowed to design around a gap. Every missing token or component turns up in its notes.
It finds what the system is missing
Building real things in a design system is the fastest way to discover what it doesn’t have, and the gap lists turned out to be a channel rather than a complaint box.
01 An exploration hits a gap “No gradient tokens exist” - on a product whose signature device is a full-bleed gradient hero. → 02 It goes on the notes, not in a backlog Named under “Needs a system decision”, in the frame, next to the thing that needed it. → 03 The system grows Gradient tokens now exist - sampled from the Figma exports, with their uncertainty written down.
A gap list is a feedback channel
The audit file that started as a record of what was inferred from the Swift now runs to twelve sections, and the later ones are all things found by using the system rather than reading it: no motion, elevation or blur primitives; a secondary text colour that doesn’t survive being placed on anything tinted; a button with no nav-bar size; a selected state that is a colour and nothing else, still open. Each one is a question the design system now has to answer, raised by something real rather than in the abstract.
Designing the lab itself
I should admit a bias here. I like the way Apple’s software feels - Liquid Glass, the spatial UI direction, that sense of something modern and slightly ahead of itself - and I wanted this to feel like that rather than like a documentation site. It’s where the name came from: a lab is somewhere you try things, not somewhere you file them.
Prototypes is what people open the lab for, so it’s the front door, with Pages, Components and Icons one click away in the top bar. Moving between prototypes doesn’t send you back to an index either. You go straight from one to the next, which is what makes comparing them quick. And ⌘K searches everything at once. It runs locally, with no model behind it, and the panel says so, because a search box shaped like a prompt that could only match text would be lying about what the tool does.
Prototypes and Pages each open on a feed of what’s changed, because a lab that changes most days is only useful if people can tell what’s new. Half of it writes itself: when the site builds, it reads its own git history and works out which prototype, page or component each change touched, so the feed says what moved rather than repeating a commit message. The other half is a written note, for the changes that deserve a headline, saying what to go and look at. Anything new since your last visit is marked.
The one rule the whole thing is built on: the shell is transparent, the frame is not. The lab’s own chrome is deliberately unlike Rayo - glass over a slow wash of light, permanently dark - and it stops dead at the edge of the phone frame. Inside, the screen under test sits on a real Rayo surface and is judged against it, never through a veil. Light and dark belong to the screen being reviewed, not to the tool, so the appearance switch sits above the frame rather than in the header. That rule is what stops a nicely-designed tool contaminating the thing it’s supposed to help you judge.
⌘K across prototypes, pages and icons, with the panel saying search is local
It shouldn’t depend on me
A prototyping tool one designer uses is a hobby, and one that needs its builder in the room is a bottleneck. That was the risk here. I was the only one who could set it up, get someone unstuck and merge their work, and AI that speeds up one designer while everyone else queues behind them isn’t a gain for the team. So a lot of the work since has been getting myself out of everyone’s way.
Onboarding started as sessions I ran: install Node, clone the repo with GitHub Desktop, run it locally, let Claude Code handle git. That got people started, but a session doesn’t scale. Steps heard once are easy to lose, and the next person to join wasn’t in the room.
So the lab now carries its own Guide, written for someone who has never opened Terminal, used git or written a line of code. It goes from a blank Mac to a first prototype: a one-time setup, a file you double-click to open the lab, and then the whole workflow in plain words. Get the latest, start a branch, brief Claude the way you’d brief another designer, save, and share it for review.
The line I think does the most work is near the top: you can’t break it. Nothing on your Mac reaches the published lab until another designer has looked at it and approved it. For someone who has never used Terminal, knowing that matters more than any of the instructions.
The Guide: from a blank Mac to a first prototype, for people who have never opened Terminal
The details are where people actually get stuck, so that’s where most of the writing went. The usual Node installer needs an administrator password, which not everyone has, so setup has a second route that doesn’t. The launcher catches the macOS privacy block that otherwise surfaces as a baffling EPERM error, and says exactly which setting to change. There’s a chapter for when things go wrong, and another for the words you’ll hear.
The Guide ends with one rule for itself: if a step tripped you, it will trip the next person, and the fix belongs on the page. It’s the cold-start test again, pointed at people instead of an agent. A failure is a documentation bug, not the reader’s fault.
Approval was the other bottleneck, because it used to be mine. Every pull request now needs one approval and a passing build, and any designer’s approval is enough to merge an exploration, because explorations should be cheap to merge. Only the foundations every prototype sits on need mine: the tokens, the components, the shared screen furniture and the rules Claude reads, because a change there quietly moves everyone else’s screens. I hold the parts where a mistake spreads, and nobody waits for me to try an idea.
And it deploys itself - merging to main publishes the lab internally, so an exploration is a link you send in Slack rather than an export, and the link is never out of date because it is the repo.
Any designer 1 Set up from the Guide No Terminal or git experience needed. 2 Start a branch Your own space. Nothing reaches anyone else yet. 3 Build with Claude Brief it like another designer; it builds in the real components. 4 Open a pull request When it’s worth the rest of the team seeing. Another designer 5 Review and approve One approval merges an exploration. Only the foundations need mine. Automatic 6 It publishes itself A build check, then merging to main deploys the lab. 7 Everyone has it One internal link, never out of date.
From a blank Mac to the whole team, with no step that waits for me
Where it stands: designers have set up from the Guide, and the first explorations to go through another designer’s review are what I’m watching for next.
It doesn’t have to stay a design-team tool, and that’s the next thing I want to test. Anyone who can describe a screen can now get a real one built from the real system: an engineer sketching a feature they’ve been thinking about, a product manager putting an idea in front of people instead of describing it. The platform is open to them; whether they take it up is the interesting question.
What’s next
Designing with AI is a solo activity, and it shouldn’t be. Setting up and reviewing no longer depend on one person, but the work in between still does. It’s a question design leaders raised at an AI conference I went to recently, and I agree with them. Sharing the result is solved: a prototype is a link you can send to anyone. Working on it together isn’t. The session where the options get generated happens between one designer and one agent, and the feedback happens somewhere else, in a message thread, detached from the thing it’s about. There’s nowhere to pin a comment to the exact state you disagree with, the way you would in Figma, and no way to pick up another designer’s direction and take it somewhere new. The bets and the gap lists were a first step towards the argument living inside the work. The next is making room for other people’s arguments there too, designers first, then engineering and product. That’s what I’m exploring next.
Handoff annotations. Pages closed the fidelity gap for screens that already exist. What’s still missing is annotations, so for now this is a system for exploring and deciding internally, not for handing over.
Prototypes built for real user testing. Everything so far has been for internal decisions: arguing a direction out among ourselves. The next step is somewhere in the lab built for putting a prototype in front of actual listeners. The form isn’t decided yet, but a prototype made of the real system, with its states already built, is most of the way to being testable already.
Outcome
The team works in it, and what changed isn’t the speed. We can see many ways of doing the same thing side by side, each with its argument and its cost attached, before anyone commits. It’s an ideation tool more than a decision tool, and that’s the honest description: the downloads work ran through three destinations for where downloaded episodes should live, and the one we took forward won on grounds the exploration made visible - separating managing saved content from managing downloaded content, and being the only place that could show storage.
It isn’t finished, and the list above is honest about that. The team also isn’t yet at the point where designers push pull requests for UI changes, which is a question about how design and engineering work together rather than about the repo.
What I’d take from it is narrower than “AI makes design faster”, and more useful. The models were never the constraint. The constraint was that they had nothing of ours to build with - no tokens they could reach, no components that were really ours, and no written rules about when each one is the wrong choice. Give an agent the actual system and the rules that go with it, and the work it produces is something you can decide from.
The second is about who it’s for. AI that makes one designer faster while everyone else waits for them isn’t a gain for the team. Much of the work on the lab since has gone into the parts nobody sees in a demo - the Guide, the review rules, the baseline everyone builds on - so the lab works without me in the room.
It didn’t make us faster. It let us explore more of the options before we chose.
Written by Alex Chiu, Senior Product Designer in London. Contact: alex@mchiu.co.uk.