atlabs · case study 02
Designing in production
I ship my own designs into Atlabs' production code: 68 merged pull requests, written with Claude Code and held to the repo's rules, reviews and CI.
- youEmpty submits shouldn't hit a disabled button. Keep it active and explain what's missing, inline.
- ●Reading the repo's CLAUDE.md conventions
- ●Editing the prompt form · inline error · role="alert" · tracking invalid submits
- ●Preview ready. I check it against the design and iterate
- ✓Claude review
- ✓check-quality
- ✓ultra-review
- ✓CI · lint · tests
- ⮑Merged
A replay of how one change ships. Illustration, condensed.
Merged PRs by month, 2026, as of Oct 2026. 16 from March to July, 52 from August to October: once the loop was in place, shipping became part of designing.
workflow
The loop
The slowest part of design here was never Figma. It was the queue between a finished design and a shipped one. So I close it myself.
- 01IntentI decide what should change and why, in product terms.
- 02Claude CodeWrites the code inside the real repo, following its CLAUDE.md rules.
- 03PreviewI check the result live against the design and iterate.
- 04ReviewsClaude review, check-quality and ultra-review, every time.
- 05CILint and tests run on every push.
- 06MergeFrontend and minor backend: I merge. Bigger backend changes: developers review and merge.
# Conventions (excerpt) - Radix Themes components only - Design tokens. No hardcoded colours or sizes - CSS Modules - One icon set - Analytics on every click - Strict lint - CI on every push - Never disable a CTA for incomplete input. Answer with an inline error. ← added after story/01
claude code
Writes most of the code and the PR summaries. 96 of my 97 PRs carry its attribution.
me
Decides what to build, sets constraints, runs the reviews, asks for every backend change in plain words, and owns it when it breaks.
stories
Four changes, end to end
story/01 accessibility
Never disable a button. Explain instead.
- Problem
- A greyed-out submit left people stuck, with no idea why.
- Change
- The button always works. An empty submit answers inline, even before login.
- Why it matters
- It became a written rule for the whole repo.
How it was built
The check runs before the login gate, so logged-out visitors see guidance instead of a sign-in wall. The message uses role="alert" for screen readers, and invalid submits are tracked in analytics.
Describe your idea to get started
story/02 architecture
AI Edit: from floating window to a real panel.
- Problem
- AI Edit floated over the scene you were editing and behaved unlike every other tool.
- Change
- It now opens in the editor's standard side panel.
- Why it matters
- Consistency came from changing the architecture, not the paint.
How it was built
I removed the drag hook and the portal rendering, and dropped its separate open/closed state from the global store, so it rides on the same panel system as everything else.
before
- drag hook
- portal rendering
- its own open/closed state
after
Deleted code, not added code. AI Edit became one more panel.
story/03 async state
Showing what's generating, everywhere.
- Problem
- Jobs run in the background across scenes, and people lost track.
- Change
- Rail tabs get live badges, and the tooltip names what's in flight.
- Why it matters
- Async state is one of the hardest UX problems in AI products.
How it was built
A new hook watches every scene. Badges have aria-labels, and the animation respects prefers-reduced-motion.
Hover or tap a tab to see what's in flight.
story/04 owning failures
Shipping a regression, and fixing it properly.
- Problem
- My one-click narrator change dropped the "this affects N scenes" step.
- Change
- It was reverted, then re-landed with a per-scene regenerate dialog.
- Why it matters
- Owning the code means owning its failures. The second version is better than the plan.
How it was built
The re-land also needed a small backend change, so regeneration accepts a list of scenes, with a test.
- v1 dropped the "this affects N scenes" step
- Rolled back
- v2: per-scene dialog, backend accepts a list of scenes, plus a test
learning
The vocabulary, one real problem at a time
- Whenever Claude changes the backend, I ask it to explain exactly what it did, in simple terms, before anything merges.
- On PRs with migrations, that's how I understood which columns were being added.
- When a migration conflict came up, I had it explain what that meant. That's how the terms below stuck.
range
Everything else, by area
- +Welcome-offer messaging and badges
- +Animated strikethrough on the original price
- +Post-purchase onboarding-call offer
- +"How did you hear about us?" adds ChatGPT, Claude, Grok
- +Cancellation reasons shuffled per user to remove order bias
- +Mobile stepper bar
- +Exit confirmation mid-wizard
- +Scroll affordance for long content
- +Deep links that pre-fill a script
- +Custom and preset styles in one dropdown
- +Insert a scene between scenes
- +Clearer selected scene, scroll-to-current
- +Visible tile loading state
- +Dismissible hover demos on tabs
- +Redesigned audio trimmer
- +Smarter music entry points
- +Live webinar strip on Tutorials
- +Shareable section links
- +Preview videos on picker cards
- +"My Stories" menu with recent stories
- +Staggered rise-in on project grids
- +Hover lift and delete-in-flight states
- +All of it honours reduced motion
- Source: pull request history of Atlabs' private product repository, Oct 2026. 97 opened, 68 merged. Backend and tracking counts are based on PR descriptions.
- Code written with Claude Code (96 of 97 PRs carry its attribution). Every change passed CI and AI review; frontend and minor backend changes I merge, larger backend changes are reviewed and merged by developers.
- The replay, diff and demos are recreations to explain the work, not production code or screenshots. Screenshots are labelled as such.
Get to know me.
The story behind the work, or my résumé on one page.
