Overview
The first prototype generator had a familiar failure mode: it was good at making a dashboard and bad at making our dashboard.
Given a ticket, an LLM could produce a polished HTML page. But it would invent navigation, spacing, filters, table behavior, and data labels. The result communicated the rough idea while quietly creating a second design system.
The fix was to stop asking the model to design the whole screen.
Instead, the system starts from a living snapshot of the real application and asks the model to change the smallest possible surface.
The Snapshot Pipeline
real frontend repository
-> production build with mock API enabled
-> browser visits every discovered route
-> rendered DOM + styles + assets captured
-> faithful snapshot
-> compact semantic snapshot
-> published template catalog
The browser matters. Source components do not contain the final DOM, computed class names, portal content, or data-dependent states. Capturing the running application preserves what users actually see.
The build uses the frontend’s own mock transport, so routes render without a backend. This makes the snapshot job deterministic enough for scheduled automation and safe to run in CI.
Two Outputs for Two Audiences
Each screen produces two artifacts.
Faithful snapshot
The full snapshot keeps the rendered layout and inlined assets. Designers and engineers can open it directly and compare it with the product.
Semantic snapshot
The lean variant is designed for an LLM:
- hashed CSS suffixes are removed
- repeated boilerplate and scripts are dropped
- large embedded images are replaced with placeholders
- stable utility and semantic classes are preserved
- interaction hints are expressed as
data-*attributes - common styling and behavior move into a shared runtime
This is not minification. It is representation design.
The goal is to preserve the information needed to edit the screen while removing tokens that carry no useful design intent.
Restoring Interaction Semantics
A captured DOM is visually accurate but inert. React event handlers do not survive serialization.
The snapshotter therefore adds a small semantic layer:
<th data-ui-sort="numeric">Cost</th>
<button data-ui-modal-open="item-details">View details</button>
<select data-ui-filter="status">...</select>
A shared runtime turns those attributes back into predictable interactions. Sorting, filtering, modal opening, and reset behavior become reusable primitives rather than fresh JavaScript generated for every prototype.
This also gives the LLM a smaller vocabulary. It does not need to invent event wiring; it selects from known interaction contracts.
Route Discovery and Realistic Data
Maintaining a hand-written list of screens failed as the application grew. The pipeline now discovers routes from the application and keeps a small configuration layer only for cases that cannot be inferred reliably.
Data follows the same principle. Generic placeholder rows made every prototype look artificial, so route-specific mock-data files travel with the snapshots. A prototype for a planning screen starts with planning-shaped values; a review screen starts with review-shaped values.
The data is realistic enough to reveal layout and workflow problems without containing production information.
Prototype Generation
The ticket assistant uses the snapshot catalog in four steps:
- Confirm that the request is a UI change.
- Match the request to the closest existing route and state.
- Load the semantic snapshot, shared runtime, and route-specific mock data.
- Ask the LLM to apply only the requested change.
The prompt explicitly protects the surrounding product shell. Navigation, typography, layout primitives, and unrelated interactions are outside the edit scope.
After generation, an interaction smoke test opens the prototype in a real browser and exercises the expected controls. A visually convincing page with a dead modal is still a failed prototype.
Why Scheduled Regeneration Matters
The design system is not a PDF. It is the application itself.
A scheduled job rebuilds the catalog from the latest accepted frontend state and publishes the generated artifacts to a separate branch. Generated history is treated as disposable output; source logic remains on the main branch.
This separation avoids two common problems:
- generated files do not pollute normal code review
- consumers can update their visual context without rebuilding the snapshot tool
If the product changes, tomorrow’s prototype starts from the new reality.
What Worked
- Using the running application as the design-system source of truth.
- Giving the LLM compact semantic HTML instead of component source or screenshots alone.
- Encoding interactions as stable attributes backed by a shared runtime.
- Keeping realistic mock data beside each route.
- Validating prototypes in a browser, not by inspecting generated text.
What Was Hard
- Capturing components rendered through portals and overlays.
- Removing noisy generated classes without losing styling hooks.
- Keeping snapshots small enough for an LLM while retaining design fidelity.
- Distinguishing a meaningful UI change from harmless build-generated DOM churn.
- Preventing scheduled output branches from becoming a second source of truth.
Tradeoffs
- Snapshot templates inherit existing design flaws as well as strengths.
- Browser capture costs more than parsing source files.
- Semantic interaction attributes cover common behavior, not every custom component.
- Scheduled regeneration is eventually consistent with the frontend, not instantaneous.
Those are acceptable constraints. The system optimizes for believable design review, not for recreating the production application inside a prototype.
Takeaway
LLMs are better at changing a product than remembering one.
Give them the real screen, a small interaction vocabulary, and a narrow edit boundary—and prototypes stop looking generated.