The old WordPress site (floodrecordingstudios.com on WP Engine) snapshotted page-for-page and served from our own Cloudflare Worker flood-recording-studios. This is M1 of the takeover — Mirror → DNS → Stage on the Website Builder → Flip — so the studio never goes dark while the domain moves and the new site is built elsewhere.
Preview: https://flood-recording-studios.appolis.workers.dev · the real domain attaches in M2 · the flip (M4) re-points the domain at the Website Builder and keeps this Worker as rollback.
Single source of truth for this app. Updated every time it changes. Board: Kosmos project flood-recording-studios — read 🧠 STATE (note_2696) first; the takeover brief and decisions are in 🗺 ROADMAP (note_2694); the measured as-is site is 📋 INVENTORY (note_2695); traps in note_2760; the lane split with the Website Builder in 🔗 LOCKSTEP (note_3084).
cd "F:\Claude Code\flood-recording-studios" && npx wrangler deployGET /__version → {"app":"flood-recording-studios","role":"mirror","version":"0.1.0","date":"2026-09-09","pages":4,"indexable":false}scripts/harvest.js snapshots the 4 real pages (home, /booking/, /contact/, /availability-policy/) and everything they reference — 259 assets, 15,787,504 bytes, including the whole 76-file WordPress media library — into site/, rewrites same-origin URLs to root-relative, removes the WordPress discovery and endpoint tags that would point at doors this Worker does not have (api.w.org, EditURI, shortlink, alternate/oEmbed/RSS, generator), the unused Contact Form 7 script/style, WP Engine's per-response Cloudflare bot-detection beacon, and WordPress's own robots meta (the visibility decision moves into lib/routes.js — see §2), keeps the Cal.com booking embed, the GoHighLevel chat bubble, the Google Maps iframe and the Google Fonts loads exactly as they were, and adds the one content addition Tyler asked for (todo_2698 #8): a LocalBusiness JSON-LD block and an address / hours / socials strip above the copyright line. The Worker serves site/ through the static-assets binding, answers /__version, sends the 7 leftover theme-demo pages and every WordPress endpoint to 410 Gone, redirects www → apex, and — while SEARCH_VISIBLE is false — marks every host noindex, so nothing here can be indexed before Tyler says go-live. Tests: npm test → 20 pass, 0 fail (routes 8 · site 12). Not a registered Appolis app (see §7). Blueprint: note_3090 on the Flood board.A static snapshot, served from our own Worker. Nothing here talks to WordPress at run time: the pages are HTML files, the assets are the theme's own CSS/JS/images copied to the same paths, and the Worker's only logic is the routing table in lib/routes.js. That is the whole design — the mirror exists so that the DNS move (M2) has a target that cannot break, and so that the Website Builder (the sister lane) has a byte-exact reference of what the old site looked like.
What it deliberately is not: a redesign, a CMS, a booking system or a store. Booking and chat stay on Cal.com and GoHighLevel until the flip; afterwards Hermes owns them (Hermes board todo_3078). The new site is built on the Website Builder (project website-builder), never here.
scripts/harvest.jsnode scripts/harvest.js (or npm run harvest) rebuilds site/ and harvest/ from the live WordPress site; --offline redoes only the rewrite from the raw pages already on disk:
harvest/raw/ for provenance.//floodrecordingstudios.com/…, which is how the theme loads Slider Revolution's CSS), JSON-escaped (https:\/\/…) and root-relative /wp-content|/wp-includes — from the pages, then from every stylesheet (url(), @import) and script it downloads, then the whole media library via /wp-json/wp/v2/media. Everything is saved under site/ at the same path. WP Engine rate-limits bursts (HTTP 429 after ~35 requests, measured), so requests are spaced 250 ms apart and a 429/5xx is retried with exponential backoff; a genuine 404 is recorded in harvest/manifest.json → missing[] and tolerated by the tests (the live theme has five: three owl.video.play.png paths, a LESS placeholder @{img-path}, a misspelled Barlow-SemiBoold.ttf), a 429 left behind fails them. Known quirk carried over as-is: the theme's placeholder.png files are JPEG data under a .png name; browsers sniff the bytes and render them, so they are served unchanged.</head> and the address strip immediately before the © Copyright paragraph.sitemap.xml (the 4 real URLs on the real domain), robots.txt, 404.html, and harvest/manifest.json (pages, every asset with its byte size, the media-library count, what was stripped, what was missing). Prints one line: HARVEST pages=4 assets=N missing=M media=K bytes=B stripped=S — at v0.1.0: HARVEST pages=4 assets=259 missing=5 media=76 bytes=15787504 stripped=68.Re-run it whenever the WordPress site is edited before the flip (the routine session's page-count check tells you when). The tests then prove the new snapshot is complete and closed.
The two deliberate changes. Everything else on the pages is byte-for-byte what WordPress served.
<script type="application/ld+json" id="frs-localbusiness"> (LocalBusiness · 6003 28th St E Unit B, Bradenton, FL 34203 · +1 941-705-9356 · floodrecordingstudios@gmail.com · Instagram/Facebook · the map pin's coordinates) and <div id="frs-address"> with the same facts plus "Sessions by appointment — booking open 24/7", above the copyright line. The JSON-LD carries no opening hours on purpose: nobody has measured the studio's hours, and "open 24 hours" would have been invented.<meta name='robots' content='noindex, nofollow' /> — WordPress's "Discourage search engines" switch is ON in the WP Engine install. Tyler's call, 2026-09-08: "Just keep it off because we're going to be remaking everything anyways. When we're done with the staging site and ready to push it live on our platform, then we can just not make anything be hidden." So: WordPress is not touched, the harvest strips the baked-in tag so the snapshot's HTML is clean, and the decision lives in lib/routes.js as SEARCH_VISIBLE = false — while it is false the Worker sends x-robots-tag: noindex, nofollow on every host, the apex included, and /__version reports "indexable":false. At go-live one constant flips; the tests and this section are coupled to it on purpose, and a preview host stays noindex either way.lib/routes.js (pure, tested)| Request | Result |
|---|---|
GET /__version (any host) | 200 JSON with app, role, version, date, pages, indexable |
www.floodrecordingstudios.com + any path | 301 → https://floodrecordingstudios.com + same path and query |
the 7 orphan pages /sample-page/ /testimonials/ /blog/ /about-page/ /dance-classes/ /teachers/ /an-instructor/ (with or without the slash) | 410 Gone — the dance-school theme demo left published since 2021–23; 410 tells search engines to drop them |
the WordPress doors /wp-json /wp-admin /wp-login.php /xmlrpc.php /wp-cron.php /feed /comments/feed — each door and everything beneath it | 410 Gone — no back end here |
| everything else | the static snapshot via the ASSETS binding: /booking → /booking/ (auto trailing slash), ?ver= query strings ignored, unknown paths → 404.html |
every host while SEARCH_VISIBLE is false; after go-live, every host except the apex | same response plus x-robots-tag: noindex, nofollow |
wrangler.jsonc: assets.directory = ./site, binding = ASSETS, run_worker_first = true (the Worker sees every request first, then falls through to the assets), html_handling = auto-trailing-slash, not_found_handling = 404-page, workers_dev = true.
worker.js the Worker — turns lib/routes.js decisions into responses
lib/routes.js VERSION · SEARCH_VISIBLE · ORPHANS · DEAD · decide(url, opts)
scripts/harvest.js the snapshot script (§2)
site/ the snapshot: index.html, booking/, contact/, availability-policy/, wp-content/, wp-includes/, sitemap.xml, robots.txt, 404.html — 266 files
harvest/raw/ the 4 pages as WordPress served them (provenance)
harvest/manifest.json what was harvested, byte for byte
test/routes.test.mjs the routing table + the search-visibility switch (8 tests)
test/site.test.mjs the snapshot is complete, closed to WordPress, carries the changes (12 tests)
wrangler.jsonc · package.json · APP_BREAKDOWN.md (this file) · README.md
.gitattributes `* -text` — byte-exact checkouts (this machine's git has core.autocrlf=true; without it every clone/worktree would rewrite 64 text files and break the manifest test)
.gitignore /node_modules/ /.wrangler/ *.log /archive/ — ANCHORED: the theme ships isotope.pkgd.min.js under site/…/vendor/node_modules/, and an unanchored ignore dropped it from every clone (found by cloning); the WP Engine backup zip lives in archive/, never committed
npm test (= node --test test/routes.test.mjs test/site.test.mjs) — no dependencies, node's own runner. 20 tests, 0 failing at v0.1.0.
site/ · stylesheets pull nothing that is not on disk · Cal.com on /booking/, GoHighLevel + fonts everywhere, the map on /contact/ · the address strip and JSON-LD on every page, above the copyright line · sitemap/robots/404 · no image, font or script on disk is secretly an HTML error page · this doc carries measured numbers · the manifest agrees with the disk and no 429 was left behind.https://flood-recording-studios.appolis.workers.dev/__version → version equals lib/routes.js VERSION and "indexable":false until Tyler says go-live./sample-page/ → 410; /wp-json/ → 410; every page answers with x-robots-tag: noindex, nofollow./booking/ in the browser pane shows the Cal.com iframe (app.cal.com/flood-studios-amn0ru/…)./wp-json/wp/v2/pages?per_page=100&_fields=id) is still 11 — if it moved, re-run the harvest and the tests.aspmx.l.google.com · a test mail delivers · the apex still answers x-robots-tag: noindex (the studio stays hidden until the new site goes live).The Appolis connector does not open a flood-recording-studios app (measured 2026-09-08: appolis_compliance_list → "this connector does not open flood-recording-studios"), so nothing can be attested under this name. The suite-wide directives open on 2026-09-09 were read against this build: overlay lock (cmp_8c8134e18f) — the mirror opens no overlays of its own (the chat bubble is GoHighLevel's); schema proof (cmp_c7b85307b7) — no database, no migration; token discipline (cmp_18c8b53201) — no AI call anywhere; email identity (cmp_34f01cf47c) — sends no email (there is no form); progress on every wait (cmp_progress0001) — static files, no wait; Hephaestus first (cmp_b80e65baad) — the mirror makes no AI call to route; answers are a form (cmp_0f71c92a20) — this lane asks through Kosmos decision forms, not text lists. If the mirror ever grows a form, a wait or an AI call, it registers as an app first and attests then.
pay CNAME to GoDaddy pay links is a consumer the move must carry).SEARCH_VISIBLE flips at go-live, not before.