Home
All projects

RouteWarp

Trust the model with implementation. Never with verification.

The RouteWarp local workspace, headed "Improve your website. Prove every change.", with entry points for an existing folder, a site built from scratch and a rebuild from URL, and a current project panel showing a page preview alongside page count, applied verified updates and SEO health.
Status
ActiveIn active development.
Started
Updated
Role
Product Design, Systems Architecture, QA, Development Orchestration
Built with
  • TypeScript
  • Node.js
  • Astro
  • Electron
  • Vitest
  • Playwright Core
  • Chrome
  • OpenAI API
  • Cloudflare Workers
  • Supabase
  • PostgreSQL
  • HTML
  • CSS
  • Git

Overview

RouteWarp is a desktop application for changing a website by describing what you want. You point it at a project folder, or at a live URL that it rebuilds into an editable project, and type a sentence like make the home page feel more premium. An AI agent implements the change inside a disposable copy of your project, never in the project itself. RouteWarp then builds both versions, measures the difference itself rather than asking the model what it did, and shows you a preview of the actual built site. If you like it, you press Keep and the reviewed bytes are promoted byte-for-byte, with a rollback that is proven rather than promised. If you do not, you close it and nothing ever happened. It lives at routewarp.com.

It is also the project I have put the most into, and the one whose story I would rather show than summarize, because the interesting part is not the system that exists now but the one that came before it, why that system was correct and still wrong, and what it cost to prove that.

In sixteen days it went through five parallel architectures and 1,074 commits. The thesis it started with is the opposite of the thesis it ships with today.

The current law, written at the bottom of the kernel handoff document:

There is always a seventh kind of edit. The correct response to a hard request is never a lane, a change class, or choreography — it is context, tools, containment, independent measurement, and a human gate. Trust the LLM with implementation. Never trust it with verification.

Everything below is how I got to that sentence.

Version one: the model proposes, RouteWarp decides

The original premise was that an AI model should never be trusted to touch source code directly. It could interpret meaning (“hero”, “background”, “green”) and nothing else. RouteWarp owned authority: which routes exist, what source owns the styling, how far that source reaches, whether the request stays inside the selected scope, what must remain unchanged, and whether there was enough evidence to approve and apply.

In practice that meant a large deterministic system. For Astro I built CSS rule indexing, a style-mutation policy, shared-style isolation, and explicit evidence for routes that must remain unchanged. The important distinction was between the pages a user asked to change and every page the real source owner actually affects. If those sets matched, a shared owner could be edited directly. If they did not, the change had to be isolated to a narrower specialization, or refused.

It also proved negative claims, which most visual tooling does not attempt. Proving the target page changed is easy. RouteWarp also had to prove that specific other pages did not change: any rendered pixel difference on a must-remain-unchanged route invalidated the approval. One acceptance run changed the /about/ hero from #eef to green while / and /services/, two other consumers of the same shared component, stayed byte-identical after real Chrome rendering.

The mainline alone finished at 105,592 lines across 291 modules, held up by 2,844 tests across 148 files. Measured at its true high-water mark, across the branch where it grew furthest before the demolition, version one reached:

Version one at its largest
Source 133,061 lines across 347 modules
Test code 80,015 lines across 173 files
Test cases 3,120

All of that worked as designed. What I had not checked was whether the premise underneath it survived contact with the requests real people actually make.

When 2,664 passing tests were not enough

On August 11 the browser-heavy suite was at 2,664 passing tests across 121 files. I opened the actual product and typed a simple request: make the hero section green.

The interpreter asked five implementation questions a normal user should not have to answer, then represented "sitewide" as though it were a real route. RouteWarp refused the malformed proposal and changed nothing.

That was simultaneously a safety success and a product failure. The guardrail did exactly what it was built to do; the natural-language contract was not ready for anyone but me. It produced the development rule the project has been run on ever since: subsystem acceptance is not product-path acceptance.

At the time I read that as a user-experience problem sitting on top of a sound architecture. It took three parallel attempts at rescuing the design before I accepted that the architecture was the thing at fault.

Three labs, run in parallel

Rather than guess, I branched the mainline three ways and ran the premise to exhaustion in each, on ux-product-lab, wild-lab and free-lab.

Tree Commits Dates Tests Outcome
RouteWarp 167 Aug 6–⁠12 2,844 The original mainline. Ends at class token operations. Abandoned.
UX-Lab 170 Aug 6–⁠12 2,861 Product shape. Ends by collapsing the workflow to request, preview, apply.
WildLab 242 Aug 6–⁠14 3,092 Safety model. Ends at “one admission judgment, and the writer stops deciding for itself.”
FreeLab 260 Aug 6–⁠16 3,120 → 2,482 The teardown. Ends by pointing the project instructions at the demolition state.
RouteWarpV2 235 Aug 17–⁠21 1,042 The current product.

Those test counts overlap, because every lab branched from the same August 6 ancestor, so they share a common core rather than summing to one number. They sit across 148, 148, 171, 133 and 149 test files respectively. What they show is that each branch was carried far enough to be judged on its own evidence. WildLab grew to 120,880 lines and 3,092 tests before I stopped it. FreeLab is the only row that goes down, because FreeLab is where the demolition happened.

I do not think I would have trusted the decision to abandon version one if I had reached it in a single tree. Running three branches at once meant that by the time I stopped, I had watched the same wall get hit from the product side, the safety side and the architecture side, and I could point to where each one ran out of road.

The request that falsified the premise

FreeLab is where it broke, and the receipts are specific.

“Add a fifth bullet point that says ‘Locally Owned’ to the hero section on the home page”

came back as a refusal with a paragraph about not indexing values inside data modules. It was structurally correct and useless. So instead of assuming why, I checked. The visible bullet text appeared nowhere in the source index: not as a text node, not as an attribute, not as a data symbol. The four bullets a person can see were an array literal in the page’s own frontmatter rendered through a .map(), and the index had recorded template words rather than array elements. There was also no change class for inserting an item at all.

A second one: change the green Start a Project button background to blue. Every source index returned nothing, and none of the reasons were accidental: the words come from a data module, the element is emitted by a component, and the color is the winner of a cascade. A person sees all three facts at once. RouteWarp could see none of them.

The conclusion, written down at the time:

The wrong fix is a list lane. The space of legitimate website edits is unbounded, and a tool that must name a change class before it can make one will always be one short of what was asked.

That is the sentence the rest of the project rests on. The previous version of this page listed the closed vocabulary as a headline feature, which is a fair measure of how convinced I was of it right up until the day it stopped working.

The teardown

The demolition report opens with a line I want to keep quotable: this is a report, not a plan of record. It ends in a recommendation to stop short of the largest cut, and says why.

Reachability was traced from three entry points, following static imports, dynamic await import(), and bare side-effect imports. Static in-degree alone was explicitly not trusted, because it had already produced one wrong answer that same session, calling a module dead when another file reached it through a dynamic import.

Teardown, by the trace
Modules examined 311
Reachable from a real entry point 300
Unreachable 11
Queued for deletion, then withdrawn on evidence 7
Recorded decisions covering the cut 6
Files touched 135
Files deleted outright 79
Lines removed 40,090 (against 1,503 added)
Source 133,061 → 117,498 lines · 347 → 311 modules
Test code 80,015 → 56,991 lines · 173 → 133 files
Acceptance suites deleted on purpose 14 suites, 33 test files

D-152 alone deleted the semantic interpretation layer by name: the interpret modules, the evidence layer, the interpret CLI command, the request endpoints, the wire contract and the “Describe a change” screen. The reasoning was blunt: a test suite is evidence about a product, not a reason for one; a front door nobody can reach does not earn its keep by being well tested.

And the cut I refused to make: the apply and rollback package looked mechanical, but one integrity test drove the old rollback path specifically to prove that a rollback may not destroy work that arrived after an apply. Whether the replacement held the same property was unknown and untested. Removing a proven guarantee in favor of an unproven one is the single thing a teardown must not do, so it stayed until eleven suites proved the guarantee survived independently.

Deleting things also found live bugs that testing had not:

  • The measurement could not see an attribute change at all. It compared computed styles and nothing else, so a silently rewritten link destination reported nothing changed.
  • Any change to a duplicated element was invisible: ambiguous identity groups were skipped, and a nav link renders twice on a page. A driven run moved fourteen anchors to new destinations and the measured effect came back empty.
  • One editor was relying on a marker that only existed because it was handed a meaningless value. Removing the caller would have quietly broken a live change class.

The one I still think about is the bug that undermined the whole suite. My Windows user is Ian's PC, so the temp directory on this machine contains an apostrophe, and Astro’s bundler silently emits no stylesheets from a path like that. Every browser-validated test had been building unstyled sites for weeks. They all passed the entire time.

The August 11 lesson also recurred, in a different architecture. The producer-agnostic verification path, the thing built specifically as proof that the engine did not depend on who made a change, was reachable only from a script. No CLI command, no UI route. It worked, it was driven against a real site on every change, and a customer could not run it. Hitting the same failure in two different architectures is what moved it from an observation to something I now check for deliberately.

Version two: the inversion

Version one constrained the model’s input. Version two constrains its output.

Version one Version two
Core bet The model may only propose inside a named vocabulary The model may implement freely inside a disposable copy
Where safety lives In what the model is permitted to say In what is permitted to leave the working copy
Refusals A refusal is a correct outcome Zero vocabulary refusals, by construction
New kinds of edit Need new deterministic vocabulary Need nothing
Proof of correctness Test count and change-class coverage Independent measurement plus a live evidence ledger
Human role Approves a typed proposal Reviews a real preview before any byte moves

The thesis in one line:

A person describes an outcome in ordinary language, the model freely implements it inside a disposable working copy, RouteWarp independently builds and measures both channels, a human reviews a real preview, Keep promotes reviewed bytes byte-verified, and rollback is proven — with zero vocabulary refusals, by construction.

The agent now has broad write capability inside working copy B. No allowlist, no plan gate, no scope grammar. The only write refusals left are protected paths, a stale content hash, an ambiguous anchor, and secrets appearing in a tool result. Safety comes from the fact that B is disposable and nothing reaches the real project except through a separate human command.

Four verbs:

Verb What it can do What it can never do
drive Edit working copy B freely Promote anything
keep Promote reviewed bytes, byte-verified Run without a digest-locked confirmation phrase
rollback Re-hash the tree back to its pre-promotion digest Destroy work that arrived after the apply
close Discard B entirely Touch the real project

None of those boundaries rely on discipline. Each one is enforced by something that fails:

  • The engine cannot import the promotion or apply-guard modules. An architecture test asserts it over the real import graph in both directions, so Keep is unreachable from the agent. An adversarial script proves the same thing from the outside: the correct phrase without a verification marker is refused, and the target is untouched.
  • The verification engine imports nothing from any producer of changes. It subtracts a before from an after and is never handed the model’s account of itself. As FreeLab put it: a producer that could describe its own change to the thing checking it could describe it favorably.
  • Every filesystem write goes through one funnel, enforced by a lint rule with zero exemptions.
  • Lifecycle facts leave the kernel on a dedicated file descriptor rather than in stdout prose, because stdout deliberately carries the agent’s own words and the producer of a line is not a property of its text. A marker, an argv token or an environment token could all be read by a build script the agent wrote and echoed back. Build children are spawned with that descriptor scrubbed from their environment, and a test holds it.

The whole suite runs with zero external model calls and zero API cost: an offline flag makes the engine refuse to construct a live client, unoverridably, and the API key is deleted from the test process outright. The reason is written in the README: a test that needs a live model is not a test; it is a drive.

Current suite Tests Files
Kernel 525 63
Interface and server 337 50
Enterprise platform 80 12
Desktop (Electron) 64 18
SEO audit 24 4
Relay (Cloudflare Worker) 12 2
Total 1,042 149

The ledger

What I point at now is a schema-versioned record of live runs against real sites with a pinned model, kept in the repository alongside the code. Entries marked protected may never regress.

Run Request Result Evidence
G-001 “Change the hero to a modern gradient blue for the home page.” Pass, first attempt · 13 turns · 0 refusals The agent located the component itself and checked other usages to prevent site-wide leakage. The effect was measured across 7 pages and confined to /; Keep promoted 3 files byte-verified.
G-002 “Make the home page feel more premium.” Pass, second attempt · 39 + 14 turns A broad redesign with no implementation instructions, then one defect corrected in ordinary language. The repair stayed local to a single card; the rest of the redesign was preserved.
G-003 A new page, integrated into the whole site. Pass, first attempt · 21 turns The agent inferred the route, file placement, architecture, component reuse, metadata practice, and navigation, footer and internal-link integration, and recovered on its own from a truthful read refusal.

G-001 is essentially make the hero green with better taste: the exact request class version one refused on principle, now passing first try with zero refusals.

What it does now

Removing the vocabulary turned out to remove the ceiling with it. Version two does considerably more than version one ever managed, while being held safe from the opposite direction.

Rebuild from URL

Roughly 3,500 lines across twelve modules turn a live public website into a verified, editable project through a fourteen-stage pipeline. The intake contract is one public host, at most 50 pages, public content only. Every physical request re-resolves the hostname and refuses the entire answer set if any address is local, private, link-local, loopback, multicast, reserved, documentation-only, IPv4-mapped, NAT64 or 6to4, then connects to a validated IP while preserving the original host header and SNI. The reason for re-resolving on every request is that a hostname which passed validation a moment ago can answer differently the next time it is asked. Redirects are bounded, re-checked, same-host, and may not downgrade HTTPS, and robots rules are enforced including wildcards and end anchors. Fetched content is contractually untrusted data, never model instruction: raw HTML lives only long enough to normalize one bounded response, and scripts, styles, templates, embedded documents and hidden elements are discarded.

The quality half is the part I am proudest of.

Stage Model involved? Bound by
Deterministic plan No Archetype classification, per-archetype section caps, excluded overflow recorded as evidence
Design architecture One pass Hash-bound to the plan; may write exactly one JSON file
Representative prototype One pass per page family Shared shell, foundation stylesheet, ≤12 components in a fixed directory
Independent visual review A separate pass Receives desktop and mobile screenshots; may write exactly one evidence file
Bounded refinement Exactly one A second failing verdict stops fail-closed, with no recursive loop
Full-page expansion No Every adapter byte-identical to its deterministic foundation
Full-site verification No Every route, both viewports

The review is scored across six categories: a pass needs every category at 4 or more out of 5 and 27 out of 30 overall, where a 4 is explicitly defined as visibly imperfect, not production-ready. Expansion writes a block-by-block ledger where every source block must appear exactly once on exactly its route. Verification opens every route at 1440×900 and 390×844, recording status, heading structure, landmarks, horizontal overflow, broken images, sub-10px text, link destinations, console errors, blocked requests, and a typography signature that every route in a family must match.

The last full run:

Last end-to-end rebuild
Routes approved 26
Visual review verdict Pass
Routes and asset placements proven in expansion 26 routes, 28 placements
Browser verification checks 52 (26 routes × 2 viewports)
Recorded issues 0
Files in the destination folder Only its control directory

The last row is the one I was watching. All of that ran without a single byte being written into the folder the project was aimed at. Getting the pipeline to that point took more than 70 individual rebuild runs against real websites, each one a full pass end to end. Most of them broke somewhere, and the fixes that came out of them are most of the reason the quality stages exist at all.

Deterministic click-to-edit

Sites RouteWarp creates carry a managed text contract. Every heading, paragraph and action gets a random opaque identifier independent of its words, route, file or line number, bound to its kind, scope, label, current text and content hash. Click a word in the preview, edit it, and the change re-resolves the identifier, rejects a stale hash, and touches only the registry, with no model call at all, while still passing through the same independent verification and the same human gate. Navigation works the same way, where destination, hierarchy, order and component bytes are structural invariants that a model run cannot rewrite without failing closed.

Where an edit can be made deterministic, I would rather not involve a model in it at all.

The site system

Sites under RouteWarp management carry a design-token layer with a three-state, fail-closed capability verdict (managed, detected, or guidance-only) that cannot be forged by copying a marker into a file. RouteWarp verifies the manifest, verifies that the token stylesheet contains all nine required tokens, and verifies that the site’s own stylesheet actually imports it. An orphaned or incomplete contract downgrades with a stated reason rather than being taken at its word.

Global color and typography edits then begin from the verified token bytes, not from a saved profile that may have gone stale, and Apply stays locked unless a bounded independent comparison proves that no other page, script, stylesheet or source file changed. An imported site can be adopted into the system through an ordinary verified candidate, and is recorded as adopted, never inaccurately as RouteWarp-created.

Creating a site from scratch

A six-step guided brief (basics, goals, pages, style, images, review) produces a verified site with individually planned page cards: editable display name, exact route, per-page direction, nested routes like /services/injectables/botox/, and depth-correct imports. Home is mandatory and locked to /. Duplicate or malformed routes fail before anything is generated.

Choosing how hard the model works

Edit runs in one of five explicit modes, from a fast pass up to a frontier-model pass with a five-minute ceiling. The kernel validates the requested mode and selects exactly that model and effort level; prompt wording gets no vote, so writing “please think really hard about this” changes nothing.

The binding for site creation was set by a controlled four-run benchmark with identical brief, images and ordering, timed at 82.6s, 109.7s, 155.8s and 220.4s. The option I judged visually equal or better was not the slowest or the most expensive one, so that is what shipped.

SEO audit

A separate package crawls the last byte-proven applied snapshot, scores eight categories, and groups evidence-backed findings by severity. It never runs project build scripts, never invokes the kernel, and never calls a model. Findings expose one action, prepare a fix in the editor, which opens Edit with the exact page selected and a structured brief prefilled, and sends nothing. Audit findings never silently become edits.

Every page, including the ones that only exist after a build

Edit scope is hierarchical (whole site, page, or section) and is backed exclusively by routes the project adapter actually discovered. The server re-validates every submitted URL against that inventory and expands sections itself rather than trusting the client.

The part that matters is that it merges source-discovered routes with routes proven by built output. Source discovery sees [city]/[service].astro; only built output proves /houston/web-development/. Against one live site, that difference surfaced 23 concrete pages source discovery alone could not instantiate.

It is not a local alpha any more

A Windows desktop installer ships at 137.9 MiB, built on Electron with context isolation, a sandboxed renderer, no Node integration and an in-place upgrade contract. Three versions shipped in three days. A Cloudflare Worker relay accepts exactly one authenticated endpoint plus a credential-free health check, verifies the caller’s token, requires a confirmed non-anonymous account and an active entitlement, and admits usage through a server-only database function that takes a per-user transaction lock and creates the usage row in the same transaction, which closes the check-then-insert race rather than treating best-effort counting as a security boundary. It never persists prompts, request bodies, source code, image bytes, project names or filesystem paths, and the desktop bundle contains no provider secret of any kind.

A ~4,900-line enterprise publishing foundation covers domain purchase through managed hosting, TLS, deployment, renewal and rollback, with provider-neutral contracts, a transactional deployment outbox, exclusive job leasing, an organization-first database migration with row-level security, and a written threat model. It migrated successfully against the real project and passed a seven-check, two-organization acceptance, with all production provider side effects deliberately disabled. Its first rule is the one I would want a customer to read: the customer is the legal registrant of every domain; RouteWarp is the reseller and management layer, never the registrant or beneficial owner. Its eighth: customer lock-in is not a safety mechanism.

First-run consent is a versioned two-step sequence: an AI-processing disclosure, then a separate acknowledgement that build scripts still execute locally with the user’s permissions. The server refuses before acknowledgement, not just a modal.

The bugs that never reached anyone

A representative sample from one audit pass, each reproduced from the current commit before anything was touched:

  • Generated build output was being promoted as if it were the user’s change. RouteWarp runs the project’s real build inside the working copy, so on some frameworks every emitted file was in the diff and indistinguishable from the requested change. The fix was not a directory denylist, because “is a directory called public build output?” has no answer. The question with an answer is “what does this project build, and where does that tool put it?” The design asymmetry is stated explicitly: withholding too little is a mess a person can see and undo; withholding too much silently drops work they approved.
  • A working tree that git could not read was being reported as clean, and that claim was the only gate refusing external work. Unreadable state is now null rather than empty, so the compiler forces every consumer to confront it.
  • A promotion lock whose owner had died refused every future Keep forever. Recovery now runs on evidence: same host, a process id that is neither this process nor alive, and a write-intent fact on disk. Never a timeout.
  • Build-time code that ran and was then reverted left no trace a person could see. It is now two lists that are never merged: what still stands, and what ran and no longer does.
  • The build-config disclosure watched three filenames for a dozen frameworks. Measured on the live commit, 15 of 17 representative build-executed configs produced silence. The two lists are now kept in step by a test rather than by memory, because that drift is precisely what caused the bug.
  • Keep’s readiness digest was settled before the preview build, so RouteWarp’s own build could invalidate a candidate nothing had touched.
  • The recovery verdict was a second door to Apply and carried none of the withheld-output disclosure, so reopening a project could put Apply back on screen without stating that the approved change set was smaller than the measured difference.

What is honestly still open

None of the claims above are worth much without this list.

Host execution is detection, not a jail: build scripts and dependencies run with the user’s permissions. That is accepted for controlled dogfooding and is explicitly not the posture for public use. OS-level sandboxing is the flagged hardening path, not a shipped claim. The build-config disclosure watches names, so a build that runs a script has the invocation disclosed but not a change inside that script; the fix is measurement, not a longer list. File content read by the agent is injection-scanned and audited but not secret-refused before reaching the model, because redaction would corrupt the anchors edits depend on. Projects that build into an unrecognized output directory cannot be onboarded at all, which is the highest-value next fix. The installer is unsigned, and signing plus a clean-machine upgrade acceptance is required before any public beta. And the rebuild pipeline’s model-produced visual quality has not been dogfooded end to end since the family-scoped generation change landed. The pipeline completes, but that aesthetic verdict is still owed.

By the numbers

The whole lineage
Parallel architectures 5
Total commits 1,074, across 16 days
Version one mainline 105,592 lines across 291 modules, 2,844 tests
Version one at its largest 133,061 lines across 347 modules, 3,120 tests
Deleted in the teardown 40,090 lines, 79 files, six recorded decisions
Version two kernel 22,798 lines
Current product total ~76,000 lines across 6 packages
Current tests 1,042 across 149 files, all offline, zero API cost
Live proof entries 3 protected gauntlet runs, zero refusals

The entire product today, counting the kernel, interface, desktop app, relay, enterprise platform and audit together, is smaller than version one’s source alone.

Why this one matters to me

I built a deterministic permission system, proved it could not scale, and rebuilt the safety model inside out in five days. On disk, with dated commits and numbered decision records, there is the premise, the exact request that falsified it, three parallel attempts to save it, the traced demolition, the deliberate refusal to over-cut, and a rebuilt system that does the thing the original could not, in a fifth of the code.

The tests version one accumulated still earned their keep. They are what made the demolition safe to perform: every guarantee worth keeping had to be shown to survive independently before the thing that proved it could be deleted, and that argument was only possible because the coverage existed to make it with.

Version one was a good system built on a premise that did not survive contact with real requests. The part I would want someone to actually look at is the record of how that got settled: dated commits, numbered decisions, three branches run to exhaustion, and a demolition that was traced before any of it was carried out.

Milestones

  1. Visual proof and rollback validated

    Proved real-browser capture, approval invalidation, apply eligibility, and restoration of original site state.

  2. Shared-style isolation added

    Extended edits beyond global tokens to shared style owners with must-remain-unchanged route evidence.

  3. RouteWarp architecture underway

    Established the local-first model with repository mapping, planning, approvals, safety boundaries, and deterministic execution.

  4. Manual semantic QA exposed integration gap

    A simple hero-color request was safely refused after malformed semantic scope. A correct refusal and a failed product path, with 2,664 tests passing at the time.

  5. Three parallel labs branched off the mainline

    UX-Lab, WildLab and FreeLab each ran the closed-vocabulary premise to exhaustion against the same problem, so the decision to pivot would be measured rather than guessed.

  6. The premise was falsified in writing

    Adding a fifth bullet to a hero was refused correctly and uselessly. The bullets were an array literal in page frontmatter that the deterministic index had never seen, and no change class existed for inserting an item.

  7. The teardown

    Verification was made independent of whatever produced a change, and 40,090 lines of closed-vocabulary machinery were traced, condemned and removed across 135 files, with one cut deliberately left undone because the guarantee replacing it was untested.

  8. The replacement thesis proven live

    Three protected gauntlet runs against real sites. A bounded aesthetic edit, a subjective redesign repaired in ordinary language, and a new page integrated across an entire site, all with zero refusals.

  9. Keep and rollback proven byte-for-byte

    Files promoted with byte verification, then rollback verified by re-hashing the project tree back to its exact pre-promotion digest.

  10. Shipped as a real product

    A Windows desktop application, an authenticated model relay with entitlement checks, and an accepted enterprise publishing foundation.

  11. Deterministic click-to-edit and rebuild from URL

    Text edits that involve no model at all, and a pipeline that turns a live public website into a verified, editable project through bounded untrusted-content handling.

  12. Rebuild quality pipeline

    Evidence-backed page families, an independent screenshot review with a single bounded refinement, and model-free full-site browser verification across every route at two viewports.