
The New Rules of Technical Debt: How AI Code Generation Changes Everything About Quality, Testing, and Speed

Leonardo Steffen

You’ve probably heard the word modular thrown around in tech circles. Everyone’s building modular this, modular that.
But here’s what’s different now: when AI systems coordinate your interfaces, when responses stream in token-by-token, when agents compose UIs on the fly, suddenly, modularity isn’t just a nice architectural principle.
It’s the foundation that makes dynamic, intelligent interfaces possible.
The most interesting components today aren’t just UI bits you snap together.
They’re building blocks that both humans and AI systems can understand, reason about, and orchestrate safely.
Think about how you currently build interfaces.
You design components, wire them together, and ship them. The arrangement is fixed. The flow is predetermined.
Now imagine your components need to work for both human developers and AI systems.
That changes how you design them.
Traditional components were built for humans to arrange and rearrange.
Modern modular components benefit from clear, machine-readable documentation and metadata that help LLMs understand and use them effectively, going beyond basic type definitions.
Here’s where things get interesting.
In many AI-tooling stacks (such as the Vercel AI SDK and similar frameworks), it’s now common practice, though not a requirement, to define input/output contracts using schema validation libraries.
Zod is the most popular, but alternatives like TypeBox or Valibot serve similar purposes.
This approach provides runtime validation and machine-readable contracts for AI coordination.
import { z } from 'zod';
import { tool } from 'ai';
const tools = {
getWeather: tool({
description: 'Get the weather for a location',
inputSchema: z.object({
location: z.string().describe('The location to get weather for'),
}),
execute: async ({ location }) => {
// fetch from your weather API here
return {
temperature: 72,
weather: 'sunny',
location
};
}
})
};This isn’t just “better TypeScript.”
It’s a fundamental shift: your component or tool definitions become machine-readable documentation.
The schema tells an LLM or agent what your component expects, what it returns, and how to use it correctly, eliminating guesswork.
Traditional components were built for request-response patterns: you fetch data, render UI, done.
But many modern, conversational AI interfaces are streaming-first. Responses arrive token by token.
Components update incrementally instead of all at once.
Fine-grained components, combined with proper React optimization (like memoization or stable references), make this possible.
When new tokens arrive, only the relevant regions update, not the entire interface.
Coarse-grained components, by contrast, tend to re-render everything on change, killing the fluidity that makes AI experiences feel alive.
That doesn’t mean “smaller is always better.”
Too many micro-components create state-sync and network overhead. The sweet spot balances reusability with coherence.
Beyond schema-based contracts, AI-friendly components share a few key traits:
Button, but SubmitButton, CancelButton, DeleteButton.The component’s name signals intent, not just appearance.
A WeatherCard displays weather. A WeatherFetcher retrieves it. They don’t step on each other’s responsibilities.
Server-side orchestration changes the game for AI-driven interfaces.
Your server handles reasoning, prompt composition, and tool execution; your client handles streaming display and user interaction.
This separation keeps your UI fast and responsive while giving AI systems full access to context and backend tools.
It’s the same pattern whether you’re in Next.js, Rails + React, or another stack: let the server think, and let the client react.
“More granular is always better.”
Over-fragmented components create overhead: extra network requests, duplicated state, and higher cognitive load.
The sweet spot is logical boundaries—weather display, user profile, navigation menu—rather than hyper-atomic pieces like individual buttons or spans.
“Over-abstraction is the main modularity trap.”
Over-abstraction gets attention, but under-abstraction breaks systems in production.
Components that fetch, validate, and render all at once are the real time bombs.
The solution isn’t fewer abstractions—it’s better ones: clean, focused, and reusable.
Let’s see how an AI might coordinate modular components in a restaurant-booking flow.
Instead of a single, monolithic form, you design composable parts that the AI can arrange dynamically based on conversation context.
import { z } from 'zod';
import { tool } from 'ai';
export const tools = {
fetchRestaurantData: tool({
description: 'Fetch restaurant information with availability',
inputSchema: z.object({
cuisine: z.string().describe('Type of cuisine to filter by'),
}),
execute: async ({ cuisine }) => {
// Typically call an API or database here.
return [
{
id: '1',
name: 'Trattoria Bella',
cuisine,
rating: 4.7,
availableTimes: ['6:00 PM', '7:00 PM', '8:00 PM']
},
{
id: '2',
name: 'Osteria Roma',
cuisine,
rating: 4.5,
availableTimes: ['6:30 PM', '7:30 PM']
}
];
}
}),
createBooking: tool({
description: 'Submit a booking for a restaurant',
inputSchema: z.object({
restaurantId: z.string(),
time: z.string(),
partySize: z.number()
}),
execute: async ({ restaurantId, time, partySize }) => {
// Perform booking and return confirmation payload.
return {
restaurantId,
time,
partySize,
confirmationNumber: 'ABC123'
};
}
})
};function RestaurantList({ restaurants }) {
return (
<div>
{restaurants.map((r) => (
<RestaurantCard
key={r.id}
name={r.name}
cuisine={r.cuisine}
rating={r.rating}
availableTimes={r.availableTimes}
/>
))}
</div>
);
}
function BookingConfirmation({ confirmation }) {
return (
<div>
<h3>Booking confirmed!</h3>
<p>
#{confirmation.confirmationNumber} — {confirmation.partySize} guests at{' '}
{confirmation.time}
</p>
</div>
);
}How it flows:
fetchRestaurantData({ cuisine: 'Italian' }), which returns structured restaurant data.RestaurantList and RestaurantCard.createBooking(...); the app then shows BookingConfirmation with the returned payload.Each part does one thing well: tools handle data, components handle presentation, and your app orchestrates the flow (potentially guided by the AI’s tool calls).
This is modularity with purpose.
Components that serve both humans and AI systems equally well—designed around clear contracts, focused responsibilities, and flexible composition.
The choice, as always, is yours.
But as AI agents begin coordinating your interfaces, modular components aren’t just good architecture.
They’re the foundation of a new kind of UI, one where humans and machines co-compose experiences in real time.

Sergey Kaplich