A fitness SaaS with no Figma, where Claude co-authored 76% of the commits. It started on Tailwind, moved to a custom SCSS token system, and I audited the result the way I'd audit a client's. Here's what held up, what drifted, and what I'd do differently.
tokenized
97%
of commits AI co-authored
76%
commits
579
As of October 6, 2026
What this shows
01A product built with AI from the first commit came out about 97% tokenized, and its raw values cluster in the oldest components, ported from the Tailwind MVP.
02Several findings sit around the code, not in it: an audit tool that reports live components as unused, a reviewer enforcing an outdated rule, and docs promising reduced motion the code doesn't deliver.
03Read-only review agents made the output more precise, but a reviewer is only as current as its rules.
summary
What this is
Veltofit is a fitness SaaS I designed and built myself, with AI from the first commit: Cursor for the MVP, then Claude. Git only shows Claude's co-author tag from March 2026, because that's when I switched. Since July, almost every commit carries it.
Since the rebuild there's no Figma file. The design system is the code. So I audited it the way I'd audit a client's: read-only, every number from a command you can rerun. Being your own client has one advantage: there's nobody else to blame. All figures are a snapshot as of October 6, 2026.
Veltofit in one paragraph
Veltofit is a trainer-first platform: plans, clients and progress in one place, instead of PDFs, spreadsheets and WhatsApp. The constraint behind most design decisions: a trainer uses it with one hand, between sets. That's all the product you need. The rest is about the system underneath.
From MVP to a system
The first version ran on Tailwind, designed from Figma mocks. It was an MVP to test the idea, and it did that job. In January 2026 I started v2: my own design system plus a back-end refactor, on SCSS modules from the first commit. The reasons I wrote down at the time:
"SCSS Modules over Tailwind: Better for 1:1 design match, smaller bundle" (v2/REFACTOR_PROGRESS.md)
"Convert to SCSS modules for exact control" (v2/docs/audits/DESIGN_SYSTEM_AUDIT.md)
"Smaller bundle" was a reason, not a result: I never measured it. And the tool wasn't the point. Tailwind lets you write p-[13px]; SCSS lets you write padding: 13px. What matters is whether the system allows raw values, and whether something checks.
Date
Milestone
Tokens
2026-01-19
v2 starts, on SCSS
97
2026-04-07
Tailwind MVP deleted, v2 becomes the app
157
2026-05-07
Semantic names, dark-first
168
2026-10-06
Audit snapshot
195
The turning point: agents
On April 4, 2026 I gave the project agents and skills, so the output stopped depending on how well I phrased each request. There are 7 agents now. Every UI change takes the same route: frontend-dev → design-reviewer → code-reviewer. The design reviewer is read-only (tools: Read, Grep, Glob): it can fail a change, never fix it.
◇ CLAUDE.md · 441–443
6. **No Tailwind.** SCSS modules only. Use `@use '@/app/_mixins' as *;` at top of every SCSS file.
7. **No hardcoded values in SCSS.** Every color → `var(--*)`. Every spacing → `var(--spacing-*)`. Every radius → `var(--radius-*)`. Every font → mixin.
8. **Use existing components.** Check `src/components/ui/` before building any UI element.
◇ .claude/agents/design-reviewer.md · 23–29
**FAIL if you find:**
- Any hardcoded hex/rgb/hsl color (e.g., `#437AB1`, `rgb(...)`, `hsl(...)`) — must use `var(--primary)` etc.
- Any hardcoded `px` spacing value (e.g., `padding: 16px`, `gap: 8px`) — must use `var(--spacing-*)` tokens
- Any hardcoded font-size, font-weight, line-height — must use typography mixins like `@include heading-2`
- Any hardcoded border-radius (e.g., `border-radius: 8px`) — must use `var(--radius-md)` etc.
- Any hardcoded z-index number — must use `var(--z-*)` tokens
- Any hardcoded shadow/transition values — must use tokens
After that, the output got noticeably more precise: with the review agents in the loop, it matched what I wanted almost every time. That's my experience, not a metric. The audit shows where it held.
frontend-dev
Writes the change.
case-study / frontend-dev
↓→
design-reviewerread-only
Checks the FAIL list with Read, Grep and Glob. It can't edit code.
✓ pass → code-reviewer
✕ fail → frontend-dev
case-study / design-reviewer
↓→
code-reviewer
Reviews the rest.
case-study / code-reviewer
Every UI change in Veltofit takes this route.
Scorecard: 5 AI-ready, 5 not
AI-ready:
One source of truth. All 195 tokens in globals.scss, one rule against raw values.
A read-only reviewer with explicit FAIL conditions.
Comments record the reason, a measurement and a date, so an agent can check a value instead of "fixing" a deliberate choice.
Predictable file shape. Every component is index.tsx plus styles.module.scss.
Reference material: 37 design-system pages, a token skill, a self-audit script, a "Last verified" date on CLAUDE.md.
◇ globals.scss · 466–476
/* Text-on-tint variants (dark on light) — brighter siblings live in :root.
Lightness is tuned so each clears WCAG AA (4.5:1) against its own faint
tint composited over BOTH --bg-surface (#FFFFFF) and --bg-base (#F8FAFC):
--warning-text on --color-streak-bg → 5.00:1 / 4.81:1
--error-text on --error-10 → 7.14:1
--success-text on --color-progress-bg → 4.96:1 / 4.78:1
--warning-text was 35% (3.87:1, failed AA) — dropped to 30%.
Hue tracks the tint each one sits on: 38 = --color-streak, 142 = --success. */
--warning-text: hsl(38, 92%, 30%);
--error-text: hsl(0, 84%, 35%);
--success-text: hsl(142, 71%, 28%);
Not AI-ready:
Names that mislead.--neutral-white is dark navy in dark mode; --line-height-relaxed is tighter than --line-height-normal.
Instructions that disagree with the code. See below.
A measuring tool that's wrong. 9 "unused" components are in use.
Missing primitives. No variant helper, no shared motion presets. Skeleton has no size props, so it has 102 inline styles.
Dead code that looks alive. PlanCard and WorkoutCard are documented as complete and rendered nowhere.
Veltofit's design-system docs, Colors page. Note the names: --background and --foreground are the legacy generation the audit flags. Captured locally on October 7, 2026: these pages return 404 in production.
What the audit found
97% tokenized, and the 3% isn't random. There are 6,692 token references in SCSS against about 205 raw values. The raw values cluster in the oldest components, ported from the Tailwind MVP; components added since July mostly use tokens. Six findings matter most for AI:
Finding
Two generations of names, no primitives
Evidence
The May refresh added semantic names (--text-*, --bg-*) but kept the old ones (--foreground-NN, --neutral-*) as "temporary" aliases. 73% of color-role references use the new names, 27% (612) the old. Both hold raw values directly.
Why it matters for AI
A model sees two plausible names for one color, and only a comment says which is legacy.
Fix
Add a primitive layer for semantic tokens to point at, and remove the 612 legacy references, as the "remove in Phase 4" comment promises.
finding / card-01
Finding
The audit tool lies
Evidence
npm run ds:audit reports 9 components as unused. All 9 are in use: BackLink renders in 9 pages. The script only matches deep imports, so the 78 files that import from the barrel are invisible to it.
Why it matters for AI
It's the kind of report an agent acts on, and it's wrong about 9 of the 38 components.
Fix
Match barrel imports too, and give the script a test before anyone, human or agent, acts on its output.
finding / card-02◇ scripts/ds-audit.ts · 73–80
for (const folder of componentFolders) {
const importPattern = `@/components/ui/${folder}`;
const usedInFiles: string[] = [];
for (const file of tsxFiles) {
const content = readText(file);
if (content.includes(importPattern)) {
usedInFiles.push(relative(ROOT, file));
An agent cleaning up from that report would delete live code.
Finding
The reviewer enforces an outdated rule
Evidence
The project moved from Sass @import to @use on April 14, 2026, ten days after the reviewer was written. 162 files use @use, none use @import, and design-reviewer.md still fails a file for a missing @import.
Why it matters for AI
The agent that guards the system checks against a rule the system no longer follows, so it would fail correct code.
Fix
Update the rule to @use, and check agent instructions against the code as often as the code itself.
finding / card-03
Finding
The docs promise reduced motion
Evidence
The motion page says "Framer Motion automatically respects prefers-reduced-motion". No component uses MotionConfig or useReducedMotion, and the global CSS rule doesn't reach animations driven from JS, which is how Button, Modal, Drawer and Tabs animate.
Why it matters for AI
An agent that reads the docs trusts a guarantee the code doesn't make, and has no reason to add the handling itself.
Fix
Handle reduced motion in JS for those four components, or take the claim out of the docs.
finding / card-04
Finding
CLAUDE.md became an incident log
Evidence
It started at 501 lines as an architecture description. At 607, the additions read like post-mortems: a "Last verified" header, because "this file drifted badly once because nothing here signalled its own age".
Why it matters for AI
Every lesson became prose for a model to read, and prose only works if the model reads it and follows it every time.
Fix
Turn the lessons that can be checked into checks, and keep prose for the ones that can't.
finding / card-05
Finding
AI copied what was there
Evidence
The same accentColor line appears in 28 files. It reads a per-user accent that nothing in the app can set any more, and its accentBg sibling has drifted into three different fallbacks.
Why it matters for AI
It looks like copying the nearest working example, at scale. Dead code spreads because it looks alive.
Fix
Delete the accent path: nothing can set the setting it reads.
Contrast fixes are measured and dated, with a light-theme sibling for each.
A four-way consistency audit ran on July 23, 2026.
The read-only reviewer gates every UI change.
The drift is real, but visible, because the system writes down why.
What broke, and what I'd do differently
Agents earlier. They arrived two and a half months into v2, and the raw values cluster in the components ported before that. I can't prove one caused the other, but next time the reviewer exists before the first component.
Treat instructions as code. A reviewer is only as current as its rules. Instructions, skills and audit scripts need the same tests and freshness checks as the code they guard.
What I wouldn't change: a quick Tailwind MVP to test the idea.
The same lesson showed up in the 18 runs behind this site's demo. The model wasn't careless: given a library that never marked its brand teal, every pricing card and settings form came out with a blue button, and the empty-state runs switched between two primary buttons because nothing said which one was current. It couldn't know intent the system never wrote down. See the demo→
What a client gets
The same audit, on your system:
AI-Readiness Review: the scorecard, run on your Figma library, tokens and documentation, with a prioritized fix list.
Audit + Roadmap: the code side too: component library, token pipeline, design–code parity, and an AI Generation Test with prompts from your own backlog.