The vibe-coding paradox

Same prompt — two trajectories.

Vibe coding is great. It's also untethered by default. Every AI-generated screen looks reasonable in isolation; six months in, the codebase is a museum of styles, accessibility gaps, and ad-hoc data fetches no one wants to touch. This page walks through nine real stages where the trajectory forks — and what CleenUI does at each fork to keep the speed without paying the long-term cost.

9 stages · same prompt at each fork

Where the trajectory forks.

Same starting prompt at every stage. Same engineer typing it. Two very different codebases six months in. Each stage names the CleenUI primitive that closes off the failure mode entirely — the architecture intervenes before the symptoms appear, not after.

Stage 01

Generating the first screen

"Build me a settings page."

UntetheredVibe coding alone

Random vocabulary from the start

The AI generates a settings page using whatever components, spacing system, and accent palette it most recently saw. Looks fine. Doesn't match any other page in your app — because you don't have any other pages yet.

  • No design tokens
  • Ad-hoc spacing
  • No a11y guarantees
TetheredVibe coding + CleenUI

Design system primitives, accessibility baked in

The Builder skill recognizes "settings page" as a known template structure and scaffolds it from CleenUI's component library + the Settings page template. Design tokens. WCAG 2.1 AA. Theme-aware light/dark. Generated in the same time.

  • WCAG 2.1 AA
  • Design tokens
  • Theme-aware
Stage 02

Adding a similar screen a week later

"Now build the notifications preferences page."

UntetheredVibe coding alone

Drift from screen #1 immediately

Different button colors. Different label placement. Different spacing scale. Different empty-state language. The model isn't aware of what it generated last week. Neither, after a few of these, are you.

  • Pattern fragmentation
  • Different spacing
  • Inconsistent language
TetheredVibe coding + CleenUI

Visually identical, by default

The Builder skill picks the same page-template pattern (Settings) and the same components (Switch, Select, Card, Heading). Visual consistency happens automatically — the prompt didn't need to ask for it.

  • Same primitives
  • Same template
  • Same vocabulary
Stage 03

A user reports an accessibility issue

"Screen reader skips the toggle on the settings page."

UntetheredVibe coding alone

Generate a fix → introduce more drift

You prompt for a fix. The model wraps the toggle in fresh markup that works, but the wrapper doesn't match the rest of your form patterns. You ship the fix. Three weeks later a different a11y issue surfaces on a different page. The cycle compounds.

  • Spot fixes
  • Drift compounds
  • No baseline guarantee
TetheredVibe coding + CleenUI

The bug didn't exist

Every CleenUI form primitive is a11y-tested in Storybook before it ships. The Switch component is wired to a label automatically; the screen-reader announces it correctly the first time. The class of bug doesn't appear.

  • Pre-tested in Storybook
  • Built-in label binding
  • Class of bug prevented
CleenUI interventionWCAG 2.1 AA primitives with built-in label bindingSwitch component
Stage 04

Wiring up real data

"Save the settings to the database when the user clicks Save."

UntetheredVibe coding alone

Inline fetch, no auth, no row-level scoping

AI generates inline `fetch('/api/save', { method: 'POST', body: JSON.stringify(state) })`. No error handling. No loading state. No auth header. No row-level scoping on the backend — so if the user posts another tenant's id, they update that tenant's row.

  • No error handling
  • No loading state
  • No row-level scoping
TetheredVibe coding + CleenUI

Data layer + stored procs do the work

The Builder skill uses the existing data-access hook (`useSaveUserSettings`). The hook posts to a controller that calls `account.sp_SaveUserSetting` — which enforces account-scoping in T-SQL. App-layer mistakes can't leak rows across tenants.

  • Auth required
  • Row-level access
  • Optimistic UI handled
Stage 05

Adding internationalization

"We need to launch in Spanish next quarter."

UntetheredVibe coding alone

"I'll do it later" — and you don't

Every string is hardcoded across dozens of screens. Adding Spanish means a manual sweep, a string-extraction tool, a translation pipeline that doesn't exist yet, and per-page rebuilds. You scope it as a quarter of work and quietly de-prioritize. The customer goes elsewhere.

  • Hardcoded strings
  • Manual extraction
  • Re-platforming required
TetheredVibe coding + CleenUI

Add a row, kick off a backfill, done

Every translatable entity already has a sibling `<Entity>Language` table. Adding Spanish is a configuration row + a background-job backfill. 100+ languages supported out of the box. No code changes.

  • Translation registry
  • Sibling *Language tables
  • Backfill job ready
Stage 06

Hiring engineer #2

"Add the second engineer to the team."

UntetheredVibe coding alone

Three weeks to first shipped feature

There's no documented pattern. There's no module structure. The codebase is whatever the model generated, plus your patches. The new engineer spends three weeks reading code, asking you questions, and then generates more AI code in their own style that diverges from yours.

  • No documented patterns
  • Tribal knowledge
  • Onboarding tax
TetheredVibe coding + CleenUI

Productive in days

The Codebase tour shows them the four-layer stack. ModuleSurfaces on every module page shows them the Frontend / API / Database / Services for the slice they're touching. The Glossary defines every domain term. The ADRs explain every foundational choice. They ship in their first sprint.

  • Per-module surfaces
  • Documented decisions
  • Architect-led onboarding
Stage 07

Hitting production scale

"Something feels slow. Customers are complaining."

UntetheredVibe coding alone

Debug by reading server logs

No observability was wired in. You SSH into the production box and tail logs. You add a print statement. You redeploy. You add another print statement. You eventually find the slow query — but you don't have query-plan visibility, so the fix takes another week.

  • No telemetry
  • No query metrics
  • Print-statement debugging
TetheredVibe coding + CleenUI

You knew about it before the customer did

The Infrastructure module's job-run monitor flagged the regression two hours ago. The system-performance dashboard shows the slow stored proc and its average elapsed time. The exception-digest job already pinged you in Slack with a one-line summary.

  • Job-run monitoring
  • Per-proc metrics
  • Exception digest
Stage 08

Customer wants SSO

"Our enterprise customer needs SAML login by quarter-end."

UntetheredVibe coding alone

"Wait — we built our own auth?"

You realize the AI generated a homegrown email-password flow back in week one. There's no abstraction. Bolting SAML onto it touches every controller. You scope it as 8 weeks. The deal slips.

  • Homegrown auth
  • Per-controller refactor
  • Deal slips
TetheredVibe coding + CleenUI

SAML is a configuration toggle

Identity has always been Auth0 + JWT. Adding a SAML connection is admin config in the Auth0 dashboard. Your app code doesn't change — it still receives a JWT, still respects RBAC, still scopes by tenant. You ship in a sprint.

  • Auth0 + JWT
  • OIDC standard
  • RBAC unchanged
Stage 09

Month six

"How's the codebase looking?"

UntetheredVibe coding alone

Considering a rewrite

Velocity has dropped 60% from month one. The codebase has eight different button styles. The accessibility ticket queue has 23 items. Two engineers have quit. The architect you brought in to evaluate it has used the word "strangler" three times.

  • Velocity collapsed
  • Accessibility queue
  • Architect mentioning "strangler"
TetheredVibe coding + CleenUI

Shipping features

Velocity is increasing — every new feature reuses primitives the team is fluent in. The accessibility queue is empty. Engineers are happy. The customer asked for SSO last month and you shipped it last sprint. You're considering whether to license module M14 for the marketplace play.

  • Velocity increasing
  • Empty bug queue
  • Shipping ahead of schedule
Preventative architecture

Preventative architecture, by primitive

The same way preventative medicine intervenes before symptoms become a crisis, CleenUI's primitives intervene before architectural debt compounds. Each one closes off a specific class of failure mode entirely — not by being smarter than vibe coding, but by being there first.

Design system + 60+ components
PreventsVisual drift across screens; per-screen accessibility regressions; theme inconsistencies.
Page templates (15 patterns)
PreventsLayout fragmentation across similar pages; inconsistent navigation; per-screen UX reinvention.
Dapper + ADO.NET data layer (stored-proc-first)
PreventsInline fetches with no auth; query plans no one can read; ORM-driven N+1 surprises in production.
Row-level access control at the proc
PreventsCross-tenant data leaks from a forgotten WHERE clause; app-layer authorization sprawl.
Translation registry + *Language tables
PreventsHardcoded strings that block international expansion; per-language re-platforming.
Auth0 + JWT + RBAC
PreventsHomegrown identity; SSO refactor cliffs; per-controller authorization sprawl.
Two-layer caching with automatic invalidation
PreventsStale data bugs; cache-coherence misadventure; per-feature cache reinvention.
Job-run monitoring + exception digest
PreventsDebugging in production with print statements; outages discovered by customer tweets.
ModuleSurfaces + ADRs + Glossary
PreventsMulti-week onboarding tax; tribal knowledge; new-engineer drift.
Architect-led delivery
PreventsMisuse patterns escaping early; bad-fit engagements going live anyway.
Get started

Get started with CleenUI.

Two paths to your first component. Pick the one that fits how your team builds.

Path A · Recommended

With AI agent skills

One prompt to your AI tool. The Setup skill handles dependencies, design tokens, build config, and component registration — all without leaving your editor.

Path B · Manual

With npm

The classic flow. Install the package, import the styles, drop in your first component. No agents required — same end result.