
- Status
- ActiveIn active development.
- Started
- Updated
- Role
- Product Design, Systems Architecture, QA, Development Orchestration
- Built with
Overview
RouteWarp is a desktop application for building and changing a website without writing code. Describe a site and it creates one; paste a public URL and it imports one; click anything on the page and change it; or type a sentence like make the home page feel more premium and let an AI agent implement it inside a disposable copy, never in the project itself. Every change is previewed, undoable and yours: the output is ordinary source you can build and host anywhere, and nothing leaves the machine until you publish. 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 ones that came before it, why each of those was correct and still wrong, and what it cost to prove that.
Three repositories, six architectures and 1,641 commits. Two of the four core theses were stated in writing and then falsified in writing, and the thing shipping today is not what any of them predicted.
This page is in the order it happened. Versions one and two are the engine: how far a deterministic permission system can be taken, why it could not be taken far enough, and the inverted safety model that replaced it. Version three is the product built on top of that, and its own reversal three days before the end.
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
publicbuild 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 was still open when version two shipped
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.
Version three: the editor
On 27 August I started a third repository.
Not a branch this time. RouteWarpV3 is its own tree, and the instruction at the top of its agent entry point is that C:\RouteWarpV2 is completely off-limits — not to read, search, inspect, compare against, execute from, or modify. The isolation is enforced in the product as well as in the instructions: every runtime identity value a V3 build uses — namespace, application-data directory, ports, bundle identifiers — is owned by a single manifest, generated into each package by one script, and re-checked before every build, typecheck and test run. A V3 installation cannot reach a V2 project’s data by accident, because it does not know any of V2’s names.
The reason for a clean tree is the same reason the labs existed in version one. Version two’s answer was correct and incomplete: it made a model safe to use on a codebase. It did not make a website editable by someone who does not write code. Every edit still went through a prompt, a working copy, a build of both channels and a human review of a preview. That is the right ceremony for rebuild my home page. It is absurd ceremony for make this heading bigger.
Eleven days, 567 commits across 48 branches, and 57 architecture and review documents totalling 26,426 lines. What came out is a visual website editor with a cloud, a desktop installer and a live importer — and a second thesis inversion inside it, three days before the end.
Phase one: every click is a source transaction
The first thesis was that a visual editor could edit ordinary Astro source directly, deterministically, and with no model involved at all. Click a heading, change its colour, and what happens is a parse, an index, a proof of stylesheet ownership, a journalled write, an atomic rename and a re-verification. The kernel’s guarantees from version two carried over intact — the four verbs, the independent verification engine, the single filesystem funnel, the offline test posture.
That phase closed on 28 August against a formal acceptance run, and the shape of the evidence matters more than the fact of it. Three receipts, each a JSON file with its own SHA-256, all three binding the same production-source digest; two of them additionally binding the same dist digest. Not “the tests passed” — the same bytes, provably, across three separate proofs.
| Phase one, measured in headed Chrome | Samples | p95 | Budget |
|---|---|---|---|
| Selection | 416 | 46.9 ms | 100 ms |
| Transaction | 36 | 227.1 ms | 250 ms |
| Preview | 36 | 585.9 ms | 750 ms |
Selection is measured from pointer input to a nonce-bound iframe acknowledgement after the selected attribute has survived two animation frames. Preview is measured from the durable committed-revision response to a revision-stamped acknowledgement after window load, document.fonts.ready, and two more frames. Those definitions are the interesting part: each one ends at a fact the browser can be made to state, not at a moment the code assumed had arrived.
The same run proved 155 of 155 element identifiers present in rendered output and in literal View Source — 89 on / and 66 on /about/. The live preview never lets Astro see the real project: it gets an independently preflighted transactional mirror under application data, outside the project tree, with Vite watching, config discovery, environment loading and PostCSS discovery all disabled, and with Windows short-path and long-path aliases explicitly proven unable to weaken the containment. Project admission is exact rather than best-effort — schema version, pinned Astro 7.1.6 by package, lock and installed identity, exact route equality, a bounded dependency closure, one-render-per-identifier component reachability — and executable frontmatter, endpoints, middleware, actions, content configuration and .env files are refused before any executor is entered at all.
Root suite at that point: 122 files, 1,006 tests. Interface: 114 files, 645 tests.
Phase two, and the Ctrl+C
Phases 2A through 2C turned those primitives into a visual editor: a bounded palette of 13 semantic tiles and 15 runtime variants, layout-aware manipulation, responsive controls, and a history model where one user gesture is one source transaction, one revision, one receipt and one Undo entry.
Gate 2C.5 ran a frozen browser harness exactly once, as a formal campaign, from an immutable tag and base commit. All 26 operations passed, along with restart, offline build, refusal, traffic and cleanup proofs.
Then the owner opened it and stopped the dogfood session with Ctrl+C.
The finding document is unusually blunt about what its own evidence is worth:
The accepted A6 campaign and receipt continue to prove the declared deterministic operation, source-authority, history, restart, offline-build, refusal, traffic, and cleanup matrix. They are not customer-usability evidence and must not be represented as proof that the editor is seamless, polished, or launch-quality.
Four findings, and none of them was a bug:
- The canvas was not a workspace. The website was a preview with an engineering dock under it. Opening a panel rescaled the site and changed its active breakpoint.
- Editing was fragmented and spoke the implementation’s language. Raw tags, source hashes, revision numbers, internal identities and capability vocabulary were on screen for a normal customer.
- Drag feedback did not match committed placement. The reproduction is exact: “See how it works” began beside the “Download WarpRoute” button. It was dragged below the button, but landed far to the left near another structural boundary instead of at the intended target.
- Default decoration obscured the website. Every editable boundary was outlined, all the time.
This is August 11 again, in a different architecture and at a different layer. A subsystem passed its acceptance; the product path failed a person. The rule version one produced — subsystem acceptance is not product-path acceptance — turns out to be the rule that keeps applying.
The correction was scoped as five gates, with one condition I want to keep quoting, because it is the opposite of how a plan usually gets written:
Before any UXC.4 production edit is dispatched, a documentation-only measurement record must define numeric budgets from a reproducible owner-machine baseline. No numeric claim is invented in this planning update because no runtime measurement was authorized.
A plan that refuses to state a latency target it has not measured is a plan that can be checked later.
The correction, in about a hundred rounds
The backlog for that correction is the longest document in the repository, and it is a record of rounds rather than a list of features. Some of what is in it:
The shell. The website fills the entire canvas with no letterbox. Panels overlay it, drag anywhere by pointer capture, collapse to a tab bar and re-dock on a double-click; the navigation became a drawer. One motion vocabulary — a single easing and duration set, press feedback on every pressable control, entrances via @starting-style, reduced-motion and reduced-transparency fallbacks — with keyboard actions and tab switches left deliberately unanimated.
Direct manipulation, five rounds. Press anything and travel six pixels and it becomes a drag: a ghost that freezes the element’s rendered appearance inline before it leaves its ancestors, a dashed placeholder that stands in for it while siblings and midpoints are measured, edge auto-scroll, Escape to cancel, and a click that ends a drag never changing the selection. Then eight spacing handles, amber outside and green inside, painting live with a label and committing one change even when Alt moved both sides. Then arrow-key nudging, painted immediately and committed as one change once the presses pause for 450 ms. Then orientation: crumbs to the two nearest editable ancestors, and Tab, Enter and Shift+Enter walking the page from the selection. Then guides — a resize snaps within six screen pixels to a sibling’s size, a sibling’s far edge or the parent’s content edge, drawing a named line (“Same width as link”, “Parent edge”), and the snapped size is what commits.
Honesty about viewports. A size written at the Desktop preset used to land in the base rule and beat the site’s own phone media queries. Desktop-only properties are now emitted inside @media (min-width:1025px) while colours and fonts still apply everywhere, and files written before the change are rewritten into the scoped form on the next edit — which is also how an existing site’s broken phone heading gets repaired. Fit to phone measures every element at 390px, finds headings over 40px, non-wrapping rows that overflow and images wider than the viewport, and commits one mobile-viewport batch that a single Undo reverses. It is deterministic and never contacts the assistant.
Speed, measured each time. Batch planning stopped re-running full project validation between steps, so a step went from 80 ms to about 15 ms and a 50-step pricing block from 5.7 s to 2.8 s. The preview stopped reloading: a mutation announces a revision, a hidden second iframe loads it and is swapped in the moment it reports painting, so the page never blanks and keeps its scroll position. Then even that became the fallback — the page now refreshes in place, morphing itself keyed by element identity, with stylesheets re-fetched and swapped once loaded and scripts never re-run, so a text edit or a whole block lands in about a second in the same document. A stale fetch, a busy gesture, a failed morph or a 2.5-second stall all answer with a refusal, and the frame swap takes over.
Ask RouteWarp. A prompt box scoped to the selection or the whole site. The model answers with a plan drawn only from the editor’s own operations, the plan is validated in the kernel, previewed live on the page, and applied through the queue as ordinary undoable changes. The invariant is written into the repository’s contributor instructions: the editor never edits the project through a model. A follow-up refines the plan on the table rather than starting over — the request carries up to six prior turns, and a refinement of an unapplied plan comes back as a complete replacement while a follow-up to an applied one comes back as only the further changes. Verified against the live model: “make this heading dark red” (4.4 s), “a bit lighter” (3.5 s), Apply, “now make it bold” (2.9 s), and two Undos reverting the two applied plans in order.
The second inversion: a closed document
On 3 September the premise underneath phase one was written down and rejected, in a document signed off the same day.
Today every click in the editor is a verified source transaction: the kernel parses the page, indexes every element, proves stylesheet ownership, writes a journal, renames it into place, and re-verifies. That is where the one-second lag, the Windows rename locks, and the “cannot create a hover-only selector” refusals come from. All of it exists because the page is arbitrary source.
So the editor stopped editing arbitrary source. It owns a closed site document — one JSON file at site/site.json, the single authority for editing — and an edit is a state change applied in memory and rendered at once. The kernel does not leave; it becomes the compiler, the verifier and the publisher, and steps out of the everyday edit loop.
| Phase one | The document | |
|---|---|---|
| What an edit is | A verified source transaction | A pure function on a JSON document |
| Where undo lives | The transaction journal | The operation log, one entry per gesture |
| Cost of a click | Parse, index, prove ownership, journal, rename, re-verify | Apply and paint |
| Can an edit fail on the filesystem | Yes | No |
| Preview equals output because | Verification says so | One renderer produces both |
| The kernel’s job | Every click | Compile, verify, publish |
Three choices make that more than a rewrite. The nineteen operation kinds the editor already spoke became the document’s mutation language, so nothing new had to be invented. Every operation is a pure function returning its inverse, which makes the operation log the undo stack and a batch one entry. And a single renderer produces both the live preview and the compiled Astro pages — so what you saw is what you get by construction rather than by verification, which the design document calls out explicitly as a constraint on the design rather than a test to pass later.
The estimate in that plan was four to six weeks. The commit log says the schema, the operations with inverses, the flagged editor and Ask RouteWarp inside it all landed on 3 September — 69 commits that day — and the flag came off the same day, on the owner’s call. Any project with a site/site.json opens in the document editor; sites created before it still open in the source-native editor, which is exactly the “code sites” path the design reserved.
The output is still ordinary Astro source, compiled beside the document, readable and buildable and hostable anywhere. That was a stated rule rather than an accident: the escape hatch stays open.
What the document made possible in three days
Removing the per-click transaction did to version three what removing the closed vocabulary did to version two — it took the ceiling off.
- A creation agent that emits a whole document. “Describe your site” plans pages of blocks with the copy that fills them, validated against the catalogue: known blocks and placeholders, well-formed unique routes, at most 8 pages and 8 blocks per page, with anything dropped named in the limits. A four-page bakery site planned in 36 seconds and verified in 34.
- Templates that are real, not sketches. Five structures and five looks plus a custom palette, previewed as the actual compiled pages in a sandboxed frame, one tab per page. A four-page local-business site opens finished in about forty seconds with no network at all, because template creation takes no prompt and involves no model.
- Navigation as a first-class structure. An explicit ordered menu with parents, independent of which pages are published, rendered as native
detailsdisclosures on desktop and phone, with published children of a drafted parent promoted rather than made unreachable. Every gesture in the menu tree is one operation and one Undo. - Automatic breadcrumbs derived from that hierarchy, with no copied labels to maintain, switchable per page, and placeable on the page as an ordinary draggable element.
- Rich text — bold, italic, underline, strikethrough, links and breaks inside a paragraph — stored as a small HTML subset that is parsed to nodes and re-serialised through the escaper, so only that vocabulary can ever reach the compiled site.
- Forms with an inbox. A real
<form method="POST">with a visually hidden honeypot that works with JavaScript switched off, and an Inbox view reading submissions back through the control plane. - A blog collection whose index and post layouts are ordinary editable element trees with bound fields, generating archives, pagination, RSS and SEO.
Importing a real website
Start from an existing site takes a public URL and produces an ordinary editable document — not a screenshot, not pasted HTML. Text, headings, images, links, buttons, sections, containers, rows and grids all arrive as normal editor elements with normal layouts, and repeated colours and fonts become site tokens.
The network side inherits version two’s posture. One public host; every hop re-resolved and pinned to public addresses; no private-network requests, credentials, POSTs, WebSockets, service workers or imported page scripts in the output. Budgets grow with the crawl — 160 requests and 40 MB to start, plus 80 requests and 10 MB per additional page, capped at 3,280 requests and 430 MB, with a three-minute overall deadline.
Small sites are quick: Bootstrap’s Album example imports in 3.52 seconds, Paul Graham’s homepage in 5.81. Simple.css came in at seven pages and 490 editable elements, 8.57 seconds to capture and 12.59 from the form to a visible editor.
Then a real one. The first import of a live 37-page medical-aesthetics site produced a 25.0 MB document and failed on size, and each of the three fixes that followed is worth more than the feature:
| What the real site exposed | The fix | Result |
|---|---|---|
| A 25.0 MB document and 5.2 million characters of generated CSS | Exact repeated style bundles stored once and referenced by id, deduplicated across pages and shared chrome | 9.35 MB (−62.7%), CSS 862,134 characters (−83.5%), verified by comparing every captured property and element rectangle across 74 page/viewport pairs |
| 137 images totalling 99.4 MB, over the production dependency allowance | Raster images above 512 KiB re-encoded as WebP at 2000px, keeping the original bytes if that came out larger | 21.0 MB |
| Its full menu inlined on every page blew the journal’s identity-effect allowance | Full chrome staged on Home only, empty semantic shells elsewhere, extraction otherwise unchanged | No kernel bound was raised |
Then a responsiveness round sampled three viewports instead of one, kept percentages and automatic sizing instead of freezing used pixel widths, turned script-driven hamburger menus into native editable disclosures, and pulled video posters and decorative gradient overlays through as ordinary elements. The 37-page import passed 111 page/viewport checks on its own production build with no horizontal overflow and no failed image.
The last round is my favourite, because the owner said it looked wrong and the answer was not one cause. Four symptoms, four unrelated confirmed reasons, each with a measurement:
- Black dots on the menu and footer: a retained
display:list-itemwith the source’s suppressed marker lost, so browsers drew their default discs. - An overlapping service strip:
inline-flexhad become block flex and lost its intrinsic width. Fixed, the strip measures 3814.375px again and one item is 215.578 × 17.797px instead of 73.391 × 53.391px. - Some text far too large: differently styled spans inside a link had been flattened into one parent style.
- A distorted creation preview: the miniature rendered at 1280px while capture and the editor used 1440.
And the fifth suspicion — that everything had been globally inflated — was checked and not confirmed. At 1440 × 1000 the source and the imported copy both use 80px type, 83.2px line height and 474.312px text width. The only real difference is 4.17% at an intermediate 1280px, because the source is fluid and the import is a snapshot. Writing down the symptom you could not reproduce is the part most reports skip.
The importer is described in its own documentation as a bounded visual reconstruction and not a claim of pixel identity: scripts and animations are not copied, video arrives as its poster, custom web fonts are not retained, and complex applications, authentication and bot challenges can reduce fidelity or prevent an import entirely.
Publish, hosting and a desktop application
Publishing never edits source. The project’s own npm run build runs on the pinned runtime, verification proves every declared route has its page and checksums every file within explicit bounds (20,000 files, 64 MB per file, 1 GB total, no links), and a versioned routewarp-deploy.json describes the result. Export writes a zip after re-checking every file against that manifest. Hosting and a custom host consume the same artifact, which is why the manifest carries render mode and service needs — a static site today and a site with server endpoints tomorrow travel the same road.
Version history restores by composing the writes from the journals of every operation committed after the target, newest first — and then commits that as an ordinary edit on the undo stack. History never rewinds; one Undo brings the newer edits back.
RouteWarp hosting is live, with the control plane in a separate repository on Cloudflare Workers, D1, R2 and KV. The client-side rule is enforced by the rehearsal campaign rather than by intention: the app makes no non-loopback request on its own. Nothing is fetched, and no authentication provider is even mounted, until a person clicks Sign in. Publishing then uploads only the blobs the platform does not already have, and deployments list with rollback.
The desktop application is real and unfinished in a specific, stated way: Electron and NSIS at version 0.3.3, a 174,742,067-byte Windows installer, signature NotSigned, sent to exactly one family tester behind an individually approved account. Three of the things that round found are the kind only a real machine produces:
- Native embedded sign-up failed on a CAPTCHA load error. Authentication now opens the system browser and returns a short-lived, single-use ticket to the initiating desktop through a hashed challenge, with CAPTCHA and email verification left enabled rather than weakened.
- The first live request came back
A verified RouteWarp email account is required. Diagnostics showed the cloud had authenticated the session and returned an identity whose email was null; the relay now resolves the verified primary email directly and rejects mismatched, banned, locked and unverified profiles. - A normal Explorer launch — not a scripted one — exposed that the launcher defaulted to port 1000, which was already the owner’s development server. It now takes an OS-selected loopback port, persists it so restart origins stay stable, falls back when one is occupied and retries competing binds, with four checks holding all of that.
That last one is the August 11 lesson for the third time: every previous native test had supplied a port override, so the only untested path was the one an ordinary person takes.
The suite now
| Package | Files | Tests |
|---|---|---|
| Kernel and engine | 146 | 1,218 |
| Interface and server | 166 | 997 |
| Desktop (Electron) | 8 | 31 |
| Relay (Cloudflare Worker) | 3 | 24 |
| Measured 8 September | 323 | 2,270 |
Plus the platform and SEO-audit packages, and four external-project acceptance campaigns that are kept out of the default collection on purpose — so that a lingering environment variable cannot turn an ordinary npm test into a destructive clean install or a real-browser run.
The offline posture is unchanged from version two and still enforced rather than requested: an offline flag the engine cannot override, the API key deleted from the test process, and the agent loop exercised through a scripted model. The worker-fork cap is still four, with the comment explaining that this was measured rather than guessed, back in version one, and asking that it be raised only with the same evidence.
What is honestly still open
Version three’s own documents are better at this than I would be, because every review in the repository ends with what was not done. A representative set, unedited in substance:
- The owner has not accepted the editing experience. Dogfooding reports major improvement over the blocked checkpoint, but final usability acceptance and public-release acceptance are separate, explicit owner calls and neither has been made. The catalogue phase behind it is planned and deliberately not authorised.
- The installer is unsigned, and the private test build says so in its delivery instructions. Signing and a clean-machine upgrade acceptance come before any public beta.
- The workspace and visual-direction rounds are not merged. They exist on a branch for review, and the full root suite has not been re-run against them.
- The forms control plane is not deployed. The editor can add a form and the Inbox can read one, but submissions only round-trip once a separate repository ships a migration and a deploy — which is the owner’s call.
- One authorisation gap is named rather than quietly carried: the logo-palette request calls a path that is absent from the relay’s allowlist, so that creation journey is not cleared.
- Host execution is still 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.
- The importer is a bounded reconstruction. Scripts, animations and web fonts are not copied, video is a poster image, and fluid typography differs from a snapshot at intermediate widths by a measured few per cent.
- Seven roadmap phases remain, from the local-first product shell through plugins, publishing, a 1.0 launch, a third-party marketplace, application capabilities, and — only if demand ever justifies it — a browser editor.
By the numbers
| Versions one and two | |
|---|---|
| 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 |
| Version two product total | ~76,000 lines across 6 packages |
| Version two tests | 1,042 across 149 files, all offline, zero API cost |
| Live proof entries | 3 protected gauntlet runs, zero refusals |
| Version three | |
|---|---|
| Commits | 567 across 48 branches, in 11 days |
| Thesis inversions inside it | 1 more — source transactions to a closed document |
| Product source | 131,090 lines across 414 modules, in 6 packages |
| Test code | 68,190 lines across 336 files |
| Tests | 2,270 in the four measured suites, all offline, zero API cost |
| Architecture and review documents | 57 files, 26,426 lines |
| Formal acceptance receipts | Digest-bound JSON, one campaign run exactly once from an immutable tag |
| Largest site imported and edited | 37 pages, 9,805 editable elements |
| The whole lineage | |
|---|---|
| Architectures, across three repositories | 6 |
| Total commits | 1,641, over two build periods |
| Core theses stated, then falsified in writing | 2 of 4 |
The version two product, counting the kernel, interface, desktop app, relay, enterprise platform and audit together, was smaller than version one’s source alone.
Version three is about the same size as version one at its largest — 131,090 lines against 133,061 — and that comparison is the one I find most useful. Version one spent all of it on an engine deciding what a model was allowed to say. In version three the kernel is about 50,000 of those lines and everything else is the part a person actually touches.
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.
Version three did it again, and that is the part I did not expect. A phase closed with digest-bound receipts and a browser campaign that passed every declared operation, and then one person opened the product and pressed Ctrl+C. The document that recorded it refused to let its own evidence be quoted as proof of something it had not measured. Three days later the premise underneath that phase — that a visual editor should edit arbitrary source, one verified transaction per click — was written down, argued against and replaced, and the thing that replaced it removed a whole class of failure instead of handling it.
What I would take from the three of them together is a working method rather than an architecture. State the premise in writing so it can be falsified. Measure the thing rather than asserting it, and if measuring was not authorised, leave the number blank and say so. Let a subsystem’s acceptance prove exactly what it proves and nothing adjacent. Put the price, the limits and the unfinished parts in front of the person before they commit, not after. And when the premise fails, delete the code it justified rather than adding a lane to it — after proving that every guarantee worth keeping survives on its own.
Three times now, the expensive part was not the building. It was being willing to write down that the thing I had just built was answering the wrong question.
Milestones
Visual proof and rollback validated
Proved real-browser capture, approval invalidation, apply eligibility, and restoration of original site state.
Shared-style isolation added
Extended edits beyond global tokens to shared style owners with must-remain-unchanged route evidence.
RouteWarp architecture underway
Established the local-first model with repository mapping, planning, approvals, safety boundaries, and deterministic execution.
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.
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.
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.
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.
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.
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.
Shipped as a real product
A Windows desktop application, an authenticated model relay with entitlement checks, and an accepted enterprise publishing foundation.
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.
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.
A third repository, deliberately isolated
Version three started in its own tree with version two off-limits, and every runtime identity value owned by one manifest and re-checked before each build, typecheck and test run.
Source-native editing accepted on digest-bound receipts
Three JSON receipts binding the same production-source digest, 155 of 155 element identifiers proven in rendered output and literal View Source, and headed-Chrome latency budgets met on 416 and 36 samples.
The dogfood session stopped with Ctrl+C
A passing 26-operation browser campaign was not usability evidence. Four findings, an authorised correction, and a plan that refused to state a latency budget it had not measured.
The second inversion, signed off and shipped the same day
Every click stopped being a verified source transaction. A closed site document, operations as pure functions with inverses, and one renderer for both the preview and the compiled site.
A live 37-page site imported and edited
Shared style rules cut the document 62.7 per cent, oversized imagery 79 per cent, and the production build passed 111 page and viewport checks.
Hosting, publishing and an inbox
RouteWarp hosting live on a Cloudflare control plane, version history that restores without rewinding, and forms whose submissions come back into the app.
A desktop build in someone else's hands
An unsigned 0.3.3 Windows installer, browser sign-in through a single-use ticket, and three failures that only a real machine and a real person could produce.


