technical build report · july 2026
Manifest-Driven AI Website Generation: Building Elook's Multi-Page Site Engine.
This week I worked on the part of Elook that turns AI website generation
from a demo into a system: a structured manifest layer for multi-page sites. Instead of
asking the model to emit a loose HTML blob, Elook produces a SiteManifestV2
that can be parsed, validated, rendered, edited, tested, and served through workers.
Make the site model the source of truth, then render HTML from that contract.
Local web app, API worker, site worker, package tests, and health checks wired together.
Routes for home, about, services, reviews, and contact, with SEO metadata and editor support.
Why raw AI-generated HTML breaks
The fastest way to build an AI website demo is to prompt a model for HTML and show the result in an iframe. That works until the user asks for real product behavior: add a second page, change only the pricing cards, preserve SEO metadata, regenerate one section, or deploy the site without losing the editor state.
Once HTML becomes the source of truth, every later operation becomes string surgery. You have to infer structure from markup, guess which section the user meant, avoid breaking routes, and hope a regeneration does not delete something the user already edited. HTML is a good output format. It is a poor internal contract.
The architecture
Elook is split into focused packages instead of one application that does everything. The important boundary is that generation and editing operate on a structured site model, while rendering and serving consume that model.
packages/core schema, parser, edit operations, generation contracts
packages/renderer manifest-to-site rendering
packages/web editor, preview, page switching
packages/api-worker generation and edit API
packages/site-worker generated site serving The flow looks like this:
User prompt / chat edit
|
v
Intent parser + planner
|
v
SiteManifestV2
|
v
Renderer
|
v
Preview / API worker / site worker This gives each layer a stable job. The planner can think in pages and sections. The editor can switch pages and target specific sections. The renderer can stay deterministic. The workers can serve generated output without knowing how the AI arrived at the manifest.
SiteManifestV2 as the contract
The main technical move was treating SiteManifestV2 as the shared contract
between the AI layer, editor, renderer, and deployment path. In the current shape, the
manifest supports multi-page sites with separate entries for routes like /,
/about, /services, /reviews, and
/contact.
type SiteManifestV2 = {
site: {
name: string;
description?: string;
};
pages: Array<{
path: "/" | "/about" | "/services" | "/reviews" | "/contact" | string;
title: string;
seo?: {
title?: string;
description?: string;
};
sections: Section[];
}>;
};
type Section = {
id: string;
type: string;
content: unknown;
style?: {
background?: string;
colorIntent?: string;
};
}; That structure sounds simple, but it changes the whole system. A page is no longer a side effect of generated markup. It is a first-class object with a path, title, SEO metadata, and ordered sections. A section can also carry optional style information, so visual edits do not have to be hidden inside copy or class names.
Making chat edits deterministic
AI site editing is where vague language meets concrete state. A user may say "add three service cards", "make this section darker", or "change the selected card to sound more premium". Those should not trigger a full-page rewrite. They should become targeted operations against the manifest.
type EditIntent =
| { type: "add-items"; targetSectionId: string; count: number; itemKind: string }
| { type: "update-section-style"; targetSectionId: string; style: Section["style"] }
| { type: "select-target"; pagePath: string; elementHint: string }
| { type: "replace-copy"; targetSectionId: string; field: string; value: string };
Recent Elook work went into exactly that layer: adding multiple items through chat,
applying per-section styles, parsing color intents, supporting target-card edits,
using a pick-on-page flow, and auto-exiting after a target is selected. The parser is
intentionally tolerant too. If a user or model misspells something like
background, the edit pipeline should normalize it instead of collapsing.
After that boundary, the rest of the system should receive structured operations, not vague text that every package has to reinterpret.
Rendering and serving stay separate
I want the renderer to be boring. Given the same manifest, it should produce the same site output. That means it should not know about prompt text, chat history, model retries, or UI selection state. It only needs the contract.
The same separation applies to the workers. The api-worker handles
generation and edit requests. The site-worker serves generated sites. The
web app handles the editor and preview UI. Keeping these concerns apart makes it
possible to reason about failures: a parser issue is not a renderer issue, and a
serving issue is not an editor issue.
The local workflow
The development stack is intentionally practical: pnpm for packages,
Vite for the web editor, Wrangler for Workers builds, and
Jest/Vitest for tests. Locally, the web app is running on
localhost:5174 because 5173 was already occupied, and the
API worker health check responds at localhost:8787/api/health.
GET /api/health
{
"ok": true,
"service": "elook-api"
} That health check is small, but it matters. In a multi-package worker setup, I want a quick signal that the API runtime is up before debugging higher-level generation behavior.
Testing the unpredictable part
AI output is variable, so the code around it has to be less variable. The tests focus on the pieces that should stay deterministic: parser behavior, manifest shape, render output, editor operations, and worker health. The project currently has full coverage across the packages touched in this pass.
- Parser tests: normalize user language and tolerate small mistakes.
- Manifest tests: confirm routes, page metadata, and section structure.
- Renderer tests: prove the same manifest renders predictably.
- Worker tests: keep API and serving boundaries honest.
- Editor tests: verify multi-page switching and targeted edits.
What I learned
The biggest lesson is that AI generation needs a real intermediate representation. Not to make the system rigid, but to make it durable. The manifest gives the model a useful shape to fill, gives the editor something stable to modify, and gives the renderer a deterministic input.
- Constrain model output with schemas instead of cleaning arbitrary HTML forever.
- Represent pages, routes, SEO, sections, content, and style as data.
- Make chat edits compile into operations, not whole-site rewrites.
- Keep the renderer free of AI-specific concerns.
- Test the deterministic boundaries around the non-deterministic model.
That is the direction Elook is moving: prompt in, manifest through the system, rendered multi-page website out. Raw HTML is an output artifact. The manifest is the engine.