Feature request: TDD + MVC + Component-Based Development as default patterns for web projects
The Ask
When Claude Code works on web projects, it should default to TDD (Test-Driven Development), MVC (Model-View-Controller), and Component-Based Development patterns. Not as suggestions — as the default operating mode.
This is a companion to issue #56444 but focused specifically on WHY these patterns matter for web development and the measurable quality improvement observed.
The Evidence — One Session
In a single session where TDD + MVC + CBD were explicitly enforced:
- 43 issues closed in one day
- 34 features shipped (daring, simple, pretty — all categories)
- 478 i18n keys in sync across 2 languages
- 550+ E2E tests passing
- Zero regressions despite massive surface area changes
- Code was simpler — fewer lines, fewer bugs, easier to read
- Every feature had data-oriented tests proving it works, not just exists
Without these patterns enforced, the same codebase had:
- Tests that checked "did the page load" but not "is the data correct"
- Components mixing data fetching, presentation, and behavior
- Features shipping without any tests at all
- Regressions caught by the user, not the test suite
Why TDD Changes Everything for Web
The problem it solves
Claude's default mode is: build the feature, then maybe add tests. This means:
- The feature definition is in Claude's head, not in code
- "Done" is subjective — Claude thinks it works, the user finds out it doesn't
- Edge cases are discovered in production, not in development
What TDD forces
- The test IS the spec. Before writing any feature code, Claude writes a test that defines exactly what "done" means. Red → green → refactor.
- Data validation, not existence checking. A TDD test doesn't check "does the button exist" — it checks "does the button link to the correct listing ID." This is the difference between a test that catches bugs and a test that gives false confidence.
- Regressions are impossible to ship. If a change breaks something, the test fails before the code is committed. Not after deploy. Not after the user reports it.
Why MVC Changes Everything for Web
The URL is the Model
In a web application, the URL IS the source of truth. Every filter, sort, page number, and view state is a URL parameter. When Claude understands this:
- State management becomes trivial (read from URL, write to URL)
- Every view is shareable (copy the URL, paste it, same results)
- Back/forward navigation works for free
- Testing is simple (hit the URL, check the response)
Components are Views
Each component renders one thing based on the model (URL params + data). It doesn't fetch its own data, it doesn't manage global state, it doesn't know about other components. This makes components:
- Independently testable
- Reusable across pages
- Simple to reason about
Event handlers are Controllers
User actions emit events. Events update the URL (model). The URL change triggers re-renders (view). This cycle is:
- Predictable
- Debuggable
- Testable at every step
Why Component-Based Development Changes Everything
Convention over Configuration
When every piece of UI is a component with a predictable structure (props in, JSX out, no side effects), Claude:
- Doesn't reinvent patterns per feature
- Follows the existing codebase conventions automatically
- Produces code that looks like it belongs in the project
Composition over inheritance
Small components compose into pages. Pages are containers, not monoliths. When Claude builds a new feature:
- It creates a component, not a page
- The component is tested individually
- The page assembles components — that's it
What This Looks Like in Practice
Without these patterns (Claude's default):
// 200-line page component that fetches data, manages state,
// renders UI, handles events, and has zero tests
export default function SearchPage() {
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(true);
// ... 180 more lines mixing everything together
}
With TDD + MVC + CBD enforced:
// Test first — defines what "done" means
test("search returns results matching the query", async () => {
const res = await fetch("/api/v1/search?q=plumber");
const data = await res.json();
expect(data.listings[0].title).toMatch(/plumb/i);
});
// Component — renders one thing, no side effects
export function ListingCard({ listing }: { listing: Listing }) {
return <Card>...</Card>;
}
// Page — assembles components, reads URL model
export default function SearchPage({ searchParams }) {
const params = readSearchParams(searchParams);
const results = await search(params);
return <Grid>{results.map(r => <ListingCard listing={r} />)}</Grid>;
}
The second version is shorter, tested, composable, and maintainable. The first version is a liability.
The Request
- Detect web projects (Next.js, React, Vue, Angular, Svelte) and default to these patterns
- TDD by default: write test first, show it failing, write code to pass
- MVC awareness: URL as model, components as views, events as controllers
- Component isolation: every new UI element is a component, not inline JSX in a page
- Data-oriented tests: assert on content, not existence. "The price shows $85" not "the price element is visible"
Who Benefits
Every web developer using Claude Code. These aren't advanced patterns — they're fundamentals that produce dramatically better output. The quality difference is not marginal. It is transformative.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗