Back to blog
Applications Jan 6, 2026 7 min read

The JSON Descriptor Pattern: How We Made Enterprise UI Generation Deterministic

Last updated Apr 9, 2026

TL;DR

A 40-line JSON descriptor replaces 400 lines of hand-coded Angular because the framework handles rendering deterministically. This 10x reduction in surface area is what makes AI generation reproducible, auditable, and reviewable by non-engineers.

In 2017, the consensus answer for “how do we make AI generate enterprise UI” was to fine-tune a model on a code corpus and hope the output compiled. Nobody asked whether code was the right output target. Seven years of mixed production results later, we built the alternative: have the model emit a 40-line JSON descriptor, and let a deterministic framework render it. A contractor list screen in one of our customer deployments is 38 lines of JSON. The hand-written Angular equivalent it replaced was 412 lines of TypeScript, HTML, and SCSS. One-tenth the surface area, identical behavior.

That ratio is the reason our AI generation pipeline works in production. It’s also the reason most AI-generated enterprise UI doesn’t.

The problem with generating code directly

When a model generates a React component, it makes hundreds of micro-decisions. Variable names, state management, CSS approach, error handling, data fetching. Run the same prompt twice and you get two working implementations that share almost no structure. For a demo, that’s fine. For a production enterprise system, it’s a non-starter.

Enterprises need reproducibility. They need auditability. They need to point at a screen and prove exactly what it does, who can see what, and how a number was computed. Free-form code generation can’t deliver any of that at the architectural level.

In my Oracle days around 2014, I built a “metadata-driven” CRM screen generator for a regional insurer — rows in a config table that described every field, label, and validation. It worked beautifully for about six months. Then a developer needed a conditional highlight that the metadata schema didn’t cover, so he added a VARCHAR2(4000) column called custom_js_hook. Within a year that column held 380 screens’ worth of undocumented JavaScript fragments, and the “declarative system” was just a slower way to write code. That failure is why our descriptor schema is strict by default and extensions are named, typed modules, not free-form strings.

The insight that changed our approach

We spent roughly six months trying different generation strategies before landing on the rule that reorganized everything. Don’t generate the application. Generate the description of the application.

A JSON descriptor specifies what a screen does. Its fields, layout, validation rules, data sources, permissions, and actions. It says nothing about how that screen renders. The framework owns rendering, deterministically, the same way every time.

What a descriptor looks like in practice

A typical list screen descriptor declares the data source endpoint and query parameters, the columns with types and sort behavior, filter and search configuration, the available actions and their permission requirements, and any master-detail relationships to other entities.

That’s it. No JSX, no event handlers, no styling. The framework handles the rendering, the API client handles the network, and the permission layer handles access control. The descriptor is easier to write, easier to review, and dramatically easier to modify.

Why this matters for AI generation

Pointing the model at descriptors instead of code changes four things at once.

Token efficiency. A descriptor is roughly 10x smaller than the equivalent component. Generation is faster and cheaper, and the context window holds more of the surrounding system.

Consistency. The framework renders every descriptor identically. There’s no architectural drift between screens written on Monday and screens written on Tuesday.

Reviewability. A product manager can read a JSON descriptor and understand the screen. They can’t read 400 lines of TypeScript, and they shouldn’t have to.

Diff-ability. When a descriptor changes, the diff shows the actual semantic change. “Added column Priority, type select, options High/Medium/Low.” That’s a meaningful artifact for a human reviewer or a SOX auditor.

The escape hatch

Descriptors aren’t universal. Some screens need custom components, unusual layouts, or behaviors the framework doesn’t anticipate. We handle that with a single rule: any descriptor can reference a custom component written in standard TypeScript.

In our deployments, the framework covers roughly 90% of enterprise screens. The remaining 10% use custom code where it earns its complexity. Even those custom components inherit the framework’s patterns for data access, permissions, and error handling.

The compounding advantage

Every new descriptor makes the system smarter. The AI learns from existing descriptors to draft better ones. The component library absorbs edge cases as they appear. Patterns solved once become available to every future screen.

Over time the system converges toward a state where generating a new enterprise screen takes seconds and the output is production-ready immediately. Not because the model got smarter. Because we made the problem smaller.