Clean Shopper — a process case study

Learning AI‑assisted product design with Claude Code

Clean Shopper is a research assistant for ingredient‑aware shoppers. It's also the vehicle I used to learn how to design and build well alongside an AI collaborator — across five phases, from a written workflow through a shipped feature.

React + Vite Supabase Claude API Tailwind Storybook Vercel
01

Phase one

The challenge

I didn't set out to build a shopping app. I set out to learn how to design and build well with an AI collaborator — and I wanted a real product behind that exercise, not a toy example.

Clean Shopper became the vehicle: a single-user research tool for people who read ingredient labels before they buy. Search a home or personal-care product, get a plain-language clean/not-clean read grounded in ingredient safety data, save what passes to a personal library, and build a shopping list from it.

The constraint mattered as much as the feature list. No accounts, no multi-user complexity — small enough scope that the actual subject of the exercise stayed visible: how do you structure a working relationship with Claude Code so the output holds up, phase over phase, instead of drifting?

02

Phase two

Setting up the ecosystem

Before writing a single component, I wrote down how I wanted to work. CLAUDE.md became the standing contract: tech stack, file-naming conventions, state rules, and a short list of things not to touch without asking.

The instructions that mattered most weren't about code style — they were about sequencing. Check the component spec before building anything new. Ask before creating a new file. Treat the build plan as a queue, not a suggestion.

## Do Not
- Do not add user authentication — V1 is single-user only
- Do not use CSS other than Tailwind
- Do not create new components when an existing one covers the use case
- Do not use any AI model other than claude-sonnet-5
design-audit feature-testing prompt-optimizer project-context

Claude Skills turned recurring judgment calls into repeatable procedure — one that checks any component change against the design system before it's committed, another that writes and runs the browser tests for a feature's main flow and edge cases against a fixed checklist. The skills didn't replace judgment; they made sure the same judgment got applied every time, not just when I remembered to ask for it.

03

Phase three

Building design context

Design decisions came before feature decisions. A design-system spec fixed the palette, type scale, spacing, radius and shadow values as the single source of truth — sage green for "clean," a warm terracotta reserved as a decorative accent only, never a text color, Poppins across a six-step type scale.

FIG. 01 Design tokens — Storybook
Background#F3EFE6
Surface#FAF7F2
Text#252320
Text muted#736860
Border#DDD5C8
Green#3A9D7C
Teal#3A8989
Pop#F2633F
Warning#8C6A1A
Error#9B3535
Success#3D7A4E
Info#3A7B9B
Display · 700Aa
H1 · 700Aa
H2 · 600Aa
H3 · 600Aa
Body · 400Plant- and mineral-based.
Small · 400HOME CLEANING
xs·4
sm·8
md·16
lg·24
xl·40
2xl·64
Design tokens enforced consistency before a single feature was built — colour, type, and spacing were locked in code, not left to interpretation.

Those values then had to exist somewhere Claude Code could actually reach for at build time — declared as Tailwind theme tokens, so a component could use bg-primary and text-h3 instead of a hex value or an arbitrary pixel size. A hardcoded color became something an audit could catch and name, not an intuition to argue about.

A component specification followed the same logic: before building any UI element, check whether an existing component already covers the use case.

FIG. 02 ProductGrid — Storybook
Storybook ProductGrid default story, showing four product cards each with a safety badge, score, category tag, and one-line reason.
Every product card follows the same visual grammar — a safety badge, a score, and a plain-language reason — so trust is built through repetition, not decoration.
04

Phase four

Feature development

Every feature got scoped before it got built. The product-comparison feature has a written spec that splits it in two: a read-only, side-by-side facts view that's buildable now, and a preference-weighted recommendation layer that stays blocked until the assessment engine and saved preferences exist. Writing that boundary down kept "compare products" from quietly turning into "recommend products" halfway through a session.

FIG. 03 CompareBar — Storybook
Storybook CompareBar with three products selected, showing removable chips, a Cancel link, and a Compare (3) button.
Comparison was designed as a lightweight, non-committal action — users can queue up to three products without leaving their browsing flow.

Supabase went in underneath as the persistence layer for saved products, categories, and the eventual preference data the second half of the comparison feature will need.

The AI chat feature got the same scoping discipline. It answers one question well — "is this safe?" — instead of becoming an open-ended assistant, and it's pinned to a smaller, faster model than the one used for product analysis, chosen deliberately for the lower cost and latency a short Q&A reply needs.

FIG. 04 ChatDrawer — Storybook
Storybook ChatDrawer story, showing a user question 'Is this fragrance-free?' and an assistant reply explaining that fragrance is a catch-all ingredient term.
The AI chat was scoped to answer one question well — "is this safe?" — rather than becoming an open-ended assistant.

The product grid itself was designed for where the app actually gets used: standing in an aisle, not at a desk.

FIG. 05 Browse products — mobile
Clean Shopper's Browse products screen on mobile, showing the search bar, filters, and stacked product cards for Branch Basics All-Purpose Concentrate and Johnson's Baby Lotion.
The product grid was built mobile-first, since most label-checking happens in-store, on a phone, in the moment.
05

Phase five

Shipping it

Before anything reached main, it had to survive its own checklist. Four baseline rules apply to every feature regardless of what it does: name and score visible, saves persist on reload, every list has a real empty state, all styling resolves to design tokens. A separate testing pass writes and runs the actual browser tests for a feature's main flow and its edge cases against that same baseline.

A design audit runs on anything touching a component or the app shell before it's committed — checking hardcoded colors, off-scale type, and drift from the rules about how the app talks about certainty and risk. It caught small things: a badge and score sitting a few pixels off the title's baseline on the product detail view, invisible until you looked for it, then obvious.

FIG. 06 Product view — alignment fix
✕ Before

Daily Moisturizing Lotion

Clean 88/100
2px baseline drift, title vs. badge
✓ After

Daily Moisturizing Lotion

Clean 88/100
aligned to shared text baseline
Small alignment fixes like this were caught during my own design-standards audit before deployment — treating QA as a design step, not an afterthought.

Catching it there — before deploy, against a written standard — made it a design step, not a bug report.

FIG. 07 Browse products — desktop compare mode
Clean Shopper's desktop Browse products screen in compare mode, showing four selectable product cards with real imagery and scores, plus a floating tray with three selected products and a Compare (3) button.
On desktop, compare mode surfaces real product imagery and scores side-by-side — the payoff of the CompareBar component built earlier in Storybook.

Storybook deploys automatically on every push to main, so the component library stays a live reference instead of a set of screenshots that go stale the week after they're taken. The app itself follows the same path: build, audit, test, ship.

Working this way for five phases changed what I think Claude Code is for. Used as a tool, it's a very fast typist — you still hold every decision.

Used as a collaborator, it holds the standards you wrote down and applies them even when you're tired or moving fast — not doing the thinking for you, but making sure your own decisions actually stick.