Rendered at build time from LOCAL_PAGE_REMEDIATION.md in the repo — edit the source
file and rebuild to update this page.
Version 1.33 · 2026-08-30 · Status: Phase 1 closed — Phase 3 (tail) paused; architecture superseded — collapsing to a two-level model (hub + service), decided; Lakeville LIVE in production at /locations/lakeville/ (LocationHubPageV2) as of 2026-08-30 16:37 UTC, its 4 Level 2 pages confirmed 301ing cleanly — hold period before batching the next 5 cities starts now.
| Objective | Bring the 26 city hub pages (/locations/<city>/) to a defined local-content standard. The 104 city×service pages (/locations/<city>/<service>/) are being retired, not remediated — their content merges into the hub. See Level 2 collapse. |
| Nature of work | Defect remediation, now folded into an architecture simplification. Not a lever being tested. |
| Blocking | Was blocking the tracked-query baseline freeze (E1, E3, E4) — resolved v1.24. |
| Progress | City×service pages (being retired): 13 of 13 arm cities at standard under the pre-v1.28 standard, 4 of 9 Phase 3 tail cities done, 68+ of 104 pages passing and deployed — this content is now an input to the hub merge, not a page type to keep shipping. Lakeville's own 4 service pages are retired in production: /locations/lakeville/<service>/ 301s to the hub via literal-path Functions (confirmed live, single-hop, no chains — scripts/check-redirects.mjs against https://www.honey-doers.com clean, 391/391 healthy), internal links repointed, sitemap entries removed. City hub pages: 1 of 26 (Lakeville) live on LocationHubPageV2 in production as of 2026-08-30 16:37 UTC (PR #5, merge commit 36d0e75); the other 25 remain on LocationPageLayout. A pre-cutover GSC snapshot for the 5 Lakeville URLs involved (11 clicks / 7,560 impressions combined, 2026-05-30→2026-08-27) is recorded in SEO_FINDINGS.md (2026-08-30 entry) as the yardstick for step 4 below. |
| Next action | Hold 2-3 weeks from the 2026-08-30 go-live date (per Delivery step 4), then re-pull GSC over a comparable window and compare against the SEO_FINDINGS.md 2026-08-30 snapshot before deciding whether to batch the next 5 cities. Case-study schema upgrade (tier/timeline/code-permit fields on Project) was scoped in v1.29 but not built — the pilot shipped using projects.ts's existing fields instead; revisit only if a future city's case studies actually need it. |
This file is self-contained. You should not need Search_Strategy.md to run a session of this work — read that for how I11 fits the wider program, not for how to execute it. Ledger entry: I11 in Search_Strategy.md.
Versions are cumulative and superseded material is retained in full, not deleted, so that later readings can be compared against earlier ones. See Version history for what changed between versions and Superseded record for the prior figures and the claims withdrawn.
We publish 104 city×service pages. Roughly 95% of every one of them is the same text.
This section is the diagnosis as it stood at the start of remediation and is deliberately left as the reference baseline. For current numbers see Outcomes; as of 2026-08-02, 9 of 104 pages pass and 2 of 14 arm cities are complete.
A page averages ~2,400 words and carries about 145 words that are actually about its city — a title, a meta description, one sentence on local housing stock, and two headings with the city name substituted. Fifty-seven of roughly sixty-four sentences are byte-identical across all 26 cities. Measured 2026-08-02 against the built site, before any city was remediated:
| Service | Median city-unique | Best page |
|---|---|---|
| Basement | 5.5% | Eden Prairie 9.1% |
| Bathroom | 5.0% | Lakeville 31.7% |
| Kitchen | 4.8% | Lakeville 25.9% |
| Whole-home | 5.5% | Eden Prairie 9.0% |
One page out of 104 meets the standard. All four services are equally undifferentiated; there is no service that is "doing fine."
This reframes our position against competitors. We have been describing our city pages as roughly twice the depth of the thin, templated competitor pages that outrank us. That is true by word count and misleading as strategy. Their 1,200-word page is about Burnsville. Ours is one page reprinted 26 times. This does not mean depth is worthless — it means we have not yet supplied it, and it is a credible answer to the open question of what those competitors have that we don't.
Three supporting defects compound it. All eight FAQs are identical across all 26 cities, so the FAQPage schema duplicates rather than localizes. The project gallery cannot supply local proof — 23 projects carry a city tag, 11 of them Lakeville, and only two basement projects exist site-wide, so "Recent Basement Projects in [City]" almost never fires. And the pages duplicate along the second axis too: Apple Valley's basement and kitchen pages share 60% of their sentences.
Found 2026-08-26: the 26 city hub pages (/locations/<city>/) carry the same defect and were never
in scope. GSC flagged /locations/bloomington/ — published 2026-07-04, so ~7 weeks live — as
"Crawled – currently not indexed." A line-by-line diff of the rendered page against Eagan's and
Lakeville's hub pages found 91% and 82% identical text respectively. The unique fraction is the
title/meta, one hero paragraph, one project caption, and the neighborhoods block where it exists —
the same content shape that made the service pages fail before remediation. "Services We Offer,"
"Why Homeowners Choose Honey-Doers," and the FAQ block are copy-pasted verbatim across cities, same as
the pre-remediation service pages. This is scope this document never covered — every prior
measurement, gate run, and work-queue row addresses only the 104 city×service pages one level below
these hubs. See Hub pages for what's known and what isn't.
Three things make this unusually cheap to fix for the size of the problem.
1. City research amortizes across four pages. Housing stock, neighborhoods, the permit office, soil and water-table conditions are properties of the city, not of the service. Research each city once into a packet, then write four short service-framed passages from it. That converts 104 units of work into 26 research packets plus 104 short write-ups — the difference between a quarter of work and three weeks.
2. The mechanisms are built. Every field the standard requires now exists in the data model and renders in the layout. Nothing needs engineering; the fields are simply empty. Filling them is writing, which is the work we can actually schedule.
3. The gain does not depend on winning an experiment. Remediation is very likely to move rankings on its own. We will not be able to attribute that movement cleanly, because it ships to every page — and that is acceptable. Fixing a page that repeats 25 other cities' text is not a hypothesis requiring a control group.
The ceiling is no longer an estimate — it is a result. The word-budget calibration predicted 34–35% against a 30% bar. Two cities written to the standard came in at 37.6–42.7% (Burnsville) and 38.1–44.6% (Richfield), both without requirement 4, whose data is still pending. The packet overshoots, which is the comfortable direction to be wrong in.
And the research yields genuinely different pages, not reworded boilerplate. Richfield permits a 6-foot-4 basement ceiling where Burnsville requires 7 feet; Richfield exempts pre-2009 homes from basement insulation requirements and stopped issuing electrical permits entirely in November 2024. Neither city's page could be mistaken for the other's, which is the whole point.
Remediate to a uniform standard first, measure second, experiment third.
The blocking reason is not content quality. It is that the experiment arms are imbalanced before treatment. Of the five cities that carry any real local content, three sit in treated arms and none sit in control arms:
| Arm | Cities | With neighborhood content | Business priority |
|---|---|---|---|
| E1-treated | Apple Valley, Burnsville, Richfield | 1 | priority · priority · secondary |
| E1-control | Minnetonka, Chaska, Plymouth, Inver Grove Heights | 0 | opportunistic · opportunistic · opportunistic · secondary |
| E2-treated | Lakeville, Eagan, Prior Lake | 2 | all priority |
| E2-control | Savage, Rosemount, Bloomington, Edina | 0 | priority · priority · secondary · secondary |
Second imbalance, found 2026-08-02: E1's arms are not comparable on business geography either. Three of the four E1-control cities — Minnetonka, Chaska, Plymouth — are the three the priority decision of 2026-07-26 classes as opportunistic, while every E1-treated city is priority or secondary. Remediation levels the content; it cannot level this. Priority geography correlates with customer density, existing link equity, and GBP proximity, so E1 as registered compares better-positioned cities against worse-positioned ones and would credit the treatment with the difference — the same false-positive direction as the content gap, from a second source.
This is a Phase 2 decision, alongside E2's. The south-metro pool is easily large enough to build comparable arms: Apple Valley, Burnsville, Lakeville, Eagan, Prior Lake, Savage, Rosemount, Inver Grove Heights, Shakopee, Elko New Market, Northfield, and Cottage Grove, plus secondary Bloomington, Richfield, and Edina. Rebuild 7-treated / 7-control from priority-comparable cities rather than inheriting the original split.
Enrolling on this footing would credit the treatment with a pre-existing content gap — a false positive, the worst kind of error because it survives review and gets acted on.
The GSC baseline window is retrospective, so remediation shipped in the weeks before the freeze is not absorbed into the baseline — its effect lands inside the comparison window regardless of when we freeze. Uniform remediation across every arm is therefore a common shock, which differences out and leaves the experiment readable. Partial remediation is a differential change, which voids it.
A control is a page at baseline, not a neglected page. This is the single most misunderstood point in the plan and the one most likely to be violated by well-intentioned work.
Template fixes → recalibrate the gate → 14 arm cities → freeze and enroll → the 12-city tail.
The first two are complete. The arm cities are the critical path and set the freeze date. The tail runs during the ten-week experiment window, where it carries no contamination risk and is a better use of editorial slots than the blog refresh queue.
Decide at the freeze:
The freeze slips from mid-August to roughly mid-September. E1 then reports in late November rather than late October, and the follow-on rollout would report in February — we probably lose the "roll it out everywhere and see it by January" step. That is the real price, and it is named here rather than discovered in December.
What it buys: experiments whose results mean something, and a baseline lift that needs no experiment to justify it. The trade in one line — a fast ambiguous answer in January, given up for a slower real one in November, on pages that should have been at this standard before they launched.
Established 2026-08-27. Reframes what each page type is for, after a strategy discussion concluded that I11's original standard optimized for the uniqueness gate more than for the buyer. SEO gets a homeowner to the site; what convinces them to call is proof of work, locality, and reviews — and manufactured differentiation (permit trivia written to pass a text-similarity check) satisfies the first without helping the second. This section reallocates content by the job each layer actually does. It supersedes the framing, not the underlying facts already gathered — permits, site conditions, and neighborhoods research already done for 17 cities is reused, just relocated.
Three layers, three jobs:
| Layer | URL | Job | Search intent it serves |
|---|---|---|---|
| 1 — Service | /services/<service>/ |
Core explanation: approach, service definitions/tiers, unique value, education/resource hub | "kitchen remodeling cost," "how does a bathroom remodel work" — generic, pre-locality |
| 2 — Location×service | /locations/<city>/<service>/ |
Nuanced explanation: the municipal process layer on top of layer 1 | "kitchen remodeling Rosemount" — service-specific, locality-specific |
| 0 — Location hub | /locations/<city>/ |
Trust and routing, not explanation | "remodeling contractor Rosemount," "does Honey-Doers work in Rosemount" — locality-specific, service-agnostic |
/project-gallery/<slug>/. This layer surfaces a filtered, best-of view into that gallery by
service — the same "reorder, never truncate" mechanism already built for testimonials
(Reviews). Writing a second narrative
version of the same job here would reproduce the duplication problem one level up.Keeps, from the existing standard:
Adds:
/project-gallery/ filtered by city and service, same
mechanism as layer 1, not fresh prose.Drops, moved to the hub:
LocationPageLayout.astro:212, LocationServicePageLayout.astro:200), so the identical block is
reprinted five times per city today. This is part of the existing cross-service duplication finding
(Apple Valley's basement/kitchen pages sharing 60% of sentences — Open items). Render
once, on the hub.TestimonialsSlider on all five pages per city today,
LocationPageLayout.astro:255, LocationServicePageLayout.astro:379). Reviews are naturally
city-scoped, not city×service-scoped — a five-star review isn't inherently a kitchen review — so
they belong on the hub once, not repeated on all four service pages beneath it.Explicitly rejected:
Not an explanation layer — a trust and routing layer. Its job is answering "do they work here" before a visitor has picked a service, so it should run shorter and thinner than a service page by design, not by neglect.
This resolves the open question from Hub pages: the standard does not transfer as-is from service pages. The hub gets its own, lighter standard — locality + neighborhoods + reviews + routing — and a lower expected uniqueness score is correct, not a defect, as long as those four elements are genuinely present. The acceptance threshold for this layer is still undetermined; measuring it is implementation work, done after the content moves, not before.
Moving neighborhoods and testimonials off the four service pages will lower their
city-page-uniqueness.mjs scores — those blocks currently contribute real width to the 30%+ scores
recorded in the Work queue. That drop is expected and correct: the differentiation
shifts to the hub and to the new municipal-resource/case-study content rather than disappearing. Don't
read a post-migration score drop on service pages as a regression without checking whether the hub's
score rose to match.
Layer 1 (/services/<service>/) is part of this architecture but outside I11's original objective,
which was scoped to /locations/. Implementing the layer-1 curation change (project-gallery filtering
by service) is related work, not yet its own ledger item — flag for a Search_Strategy.md decision on
whether it folds into I11 or opens a new initiative before starting it.
Decision: drop Layer 2 (/locations/<city>/<service>/). The three-layer model above is
superseded on this one point — two levels remain (Layer 1 service pages, Layer 0 location hubs), not
three. Layer 2's content (permits, municipal resources, cost bands, curated case studies) doesn't
disappear; it merges into the hub, one section per service, on the single /locations/<city>/ page.
Reasons, in the order they surfaced:
Case studies, not process trivia, are what a buyer actually evaluates on. The strategy discussion that produced the three-layer model (above) already concluded permits/ceiling-height detail differentiates for a text-similarity gate more than it helps a homeowner decide. Pushed further: if proof-of-work is the real conversion lever, a fragmented city×service page splits that proof thin across four URLs instead of concentrating it where a visitor is actually deciding whether to call.
No competitor validated the three-level split. Live review of Footprints Bath & Tile, Garrett Construction, Baths R Us, Gerl Construction, Country Creek Builders, and JBDB (2026-08-27) found none running a dedicated location×service page tier — Gerl Construction runs the closest structural analogue to the two-level model being adopted here.
Post-remediation pages don't index better than un-remediated ones. A user-supplied GSC export
(Indexed-Location-Pages.csv, read 2026-08-27) found the 3 hub pages not indexed were all
I11-remediated priority cities (Eagan, Burnsville, Bloomington), while all 23 indexed hubs included
every explicitly non-priority city (Blaine, Maple Grove, Brooklyn Park). Content depth is not
reliably buying indexation.
Three months of click/impression data settles it. GSC "Performance on Search" export
(honey-doers.com-Performance-on-Search-2026-08-27.zip, last 90 days, read 2026-08-27):
| Level | Pages | Clicks | Impressions | CTR | Impr-weighted avg. position |
|---|---|---|---|---|---|
| Hub (Level 0) | 30 | 13 | 11,603 | 0.11% | 29.7 |
| Location×service (Level 2) | 96 | 14 | 14,061 | 0.10% | 24.4 |
85 of 96 Level 2 pages got zero clicks despite real impression volume (several 300–700+); both levels sit at an average position of page 3+ regardless of depth. Almost the entire click signal that exists (8 of 13 hub clicks, 3 of 14 service clicks) concentrates on Lakeville — the best-linked, best-cased city, at both levels — not on whichever pages happened to be remediated. Two conclusions follow: collapsing Level 2 sacrifices very little real traffic, and the underlying constraint on these hyperlocal terms is competitiveness (authority, backlinks, SERP crowding), not page architecture — a separate problem this collapse does not claim to fix.
Caveat: 90 days is thin for this little volume on long-tail terms. Directionally consistent with
findings 1–3, but re-check against a longer window (or seo-baseline.json) before treating the
position/CTR figures themselves as durable.
What does NOT change: the underlying content. Permits, municipal-resource links, cost bands, and curated case studies described under Layer 2 above still get written — they land as a section per service on the hub page (see the "Mockup 4 — Revised per Feedback" pattern below), not on a separate URL.
The 96 indexed/near-indexed Level 2 URLs get 301s, not silent removal — they carry ~14k
impressions of accumulated signal (see table above) that a redirect preserves better than a 404 or an
unlinked page would. Per this repo's redirect rules, a rule set this size
(/locations/<city>/<service>/ → /locations/<city>/) belongs in a Cloudflare Pages Function
(a catch-all under functions/locations/), not 96 individual public/_redirects lines — verify live
with scripts/check-redirects.mjs the same as any other Function-based redirect, and add the mapping
to scripts/functions-redirects.json.
Recommended sequence:
LocationPageLayout.astro as the one template every
other city uses. Don't retire any Level 2 page until the pilot's redirect behavior is confirmed
working in production.projects.ts entries
are a photo + a short blurb — no tier, no timeline, no code/permit callout. If case studies are
meant to carry the proof burden the collapse assumes, they need those fields before the pilot ships,
or the pilot will just demonstrate the old sparsity problem in a new layout.SEO_FINDINGS.md's 2026-08-30 entry. Don't
authorize batch 2 until 2-3 weeks from the go-live date have passed AND a comparable post-cutover
GSC pull is checked against that number.LocationServicePageLayout.astro once every city's Level 2 pages redirect cleanly and
no batch shows a regression. This also resolves the earlier "two templates" delivery concern —
collapsing to two levels means there was never a second live template to maintain during rollout.This section is operational. Everything above it is the case for doing the work; everything in it is how to do the work.
A city×service page is at baseline when it has all six:
| # | Requirement | Field | Coverage today |
|---|---|---|---|
| 1 | housingDescription — city's housing era and dominant stock |
housingDescription |
✅ 26/26 |
| 2 | 3–4 named neighborhoods, each with a service-relevant sentence | neighborhoods |
❌ 5/26 |
| 3 | ≥3 city-specific FAQs, written as a complete replacement set | cityFaqOverrides |
❌ 1/26, 2 services |
| 4 | City cost band in local terms, not a national range | costBands |
❌ 0/26 |
| 5 | Municipality permit/inspection specifics — named office, real process | permits |
❌ 0/26 |
| 6 | No placeholder — real project photos or an honest alternative | — | ✅ 104/104 |
Acceptance threshold: ≥30% city-unique. Grounded two ways — the one real page carrying requirements 1–3 scores 31.7%, and a full six-requirement fixture scores 34–35%.
All six requirements are load-bearing. Requirements 1–5 without a strong FAQ set land at ~21% and fail. Budget per page, from the calibration:
| Component | Contribution |
|---|---|
| 3–4 neighborhoods | ~120 w |
siteConditions — soil, water table, frost depth |
~90 w |
| Permit office + process + per-service note | ~155 w |
| Cost-band note | ~40 w |
| ≥3 city-specific FAQs | ~230 w |
Two traps:
cityFaqOverrides replaces the FAQ set, it does not extend it
(LocationServicePageLayout.astro:25). Write the override as a complete list. Anything omitted
disappears from the page and from the FAQPage schema. Lakeville supplies 5 city-specific FAQs in
place of 8 generic ones.Four city-specific FAQs per service is the working number, not the three the requirement sets as a floor. All four completed cities used four and landed comfortably; three would sit closer to the line than is worth risking. Which four to replace follows a rule: replace the questions a local answer improves, keep the ones whose honest answer is the same everywhere. Anything touching permits, code, structure, or local housing stock gets rewritten; cost ranges, disruption, and "can I live at home during the work" stay generic, because they genuinely are.
Kitchen scores lowest of the four services, every time — 37.6, 38.1, 38.0, 38.9 across the completed cities, against 42.7 to 46.8 on basement. Kitchen pages carry the largest project-carousel denominator. If a city is going to come in short, this is where, so check kitchen first rather than last.
siteConditions is not itself a numbered requirement but renders alongside requirement 5 and
is worth ~90 words of the budget. Fill it when the packet has it.Hard rule: no differentiating sentence may appear on two cities' pages. If it is reusable, it is not differentiation — it belongs in the shared template.
Superseded in part by v1.28. The open questions below about whether the service-page standard transfers, and whether permits/site-conditions/cost-bands belong on the hub, are answered by Architecture: no, and no. The hub gets its own lighter standard (neighborhoods + testimonials + locality intro + routing). The threshold number for that standard is still open — see What this changes for measurement. The measurement data below (the 91%/82% duplication finding, the baseline table) remains current and unaffected.
Not yet started. This section records what's known so a session doesn't have to rediscover it.
What's confirmed:
/locations/<city>/, rendered by LocationPageLayout.astro — a
different layout from LocationServicePageLayout.astro, so it is not covered by
scripts/city-page-uniqueness.mjs today. That script reads the 104 city×service pages only.neighborhoods field (already populated for the 13 remediated arm cities plus the 4 completed
Phase 3 tail cities) renders on the hub page too, so those 17 cities already carry some unique text
here as a side effect of service-page remediation. The other 9 do not.What's not yet known:
permits/siteConditions/costBands belong on the hub page at all, or whether that would
just duplicate the service pages one click away — needs a decision, not an assumption.First session here should measure before writing: extend or fork
city-page-uniqueness.mjs to read /locations/<city>/ pages, run it across all 26 to get a real
baseline (parallel to the 2026-08-02 city×service baseline), and only then decide the standard and
threshold — the same order this initiative followed the first time, in Phase 0.5.
Research the city once; write four service-framed passages from it.
Item 5 is where this plan meets the Business Questions register: the job-list export that answers B1 and B6 also supplies 26 cities' worth of real cost bands. Pull it once, use it twice. If the export isn't available yet, write packets without cost bands and backfill deliberately — don't block the start.
Approved municipal sources are listed in CLAUDE.md; for cities not listed there, the city's own building-department page is the citation.
Written down 2026-08-02 after Burnsville, Richfield, and Apple Valley, because the standard describes what to produce and this describes how to find it. Roughly an hour per city.
1. Go to the city's own building department, not a summary. Search
<city> MN building permit basement finish requirements, restricted to the city domain. Every city
so far publishes a basement finish or basement remodel handout, and it has been the single
richest source each time — it carries ceiling heights, egress dimensions, the inspection list, and
the local quirks in one place. Also open the permits/inspections landing page for the office name,
address, phone, and plan-review turnaround.
2. Municipal PDFs will not parse over WebFetch. Every one so far returned unreadable compressed output. The fetch still saves the file locally and reports the path — read that path directly with the Read tool, which extracts the pages properly. Do not conclude the document is unusable.
3. Hunt for these six specifically. They are where cities actually differ, and where the differentiating sentences come from:
| What | Why it differentiates | Seen so far |
|---|---|---|
| Minimum basement ceiling height | Decides whether a lower level is finishable at all | Burnsville 7'0" · Richfield 6'4" · Apple Valley 6'4" · Chaska not published (only source is 2007) |
| Egress opening and window well | Real money, and homeowners have not budgeted it | Apple Valley and Chaska both 9 sq ft well, min 36" · Chaska adds a ladder for wells over 44" deep: 12" wide, 3–6" projection, rungs ≤18" |
| Inspection list and its order | A scheduling constraint the trades will not know | Burnsville 5 stages · Richfield 3 · Apple Valley 6, framing waits on electrical · Chaska: State inspector's electrical rough first, then heating/plumbing/fireplace/framing as one combined inspection |
| Who issues which permit | Changes recently and locally | Richfield moved electrical to the State on 2024-11-01 · Chaska's electrical rough is inspected by the State, not the city |
| Plan format, submission and fees | Sets the project's real start date and a real cost line | Apple Valley ¼" or ⅛" architectural scale · Chaska two copies at ¼" min, emailed as PDFs, review charged at 50% of the permit fee · Richfield up to 10 working days |
| Notice or hours | Small, concrete, and useful | Richfield requires 24 hours · Chaska runs summer hours from a different building |
| Trade specifics worth quoting | Concrete, checkable, and rarely on a competitor's page | Chaska: anti-scald valves in every shower, tile not over water-resistant gypsum, toilet centreline ≥15", bath duct insulated 3 ft into conditioned space |
4. Site conditions come from geography and history, not a code handout. Search the city's geology, development era, and terrain.
Look for the origin story. Every city so far has exactly one — an economic or geological fact that explains why its housing looks the way it does — and it has been the strongest material each time. Burnsville sits on Glacial River Warren outwash above the Minnesota River. Richfield is the state's oldest suburb, built fast and small for returning veterans, under what became the MSP approach path. Apple Valley was built on top of its own progressively filled gravel pits. Chaska made cream brick from its own clay from 1857 until the last yard closed in 1971. Find that, and the site-conditions passage writes itself and cannot be mistaken for another city's.
Frost depth is Zone II — 42 inches — for every metro county we serve, so state it, but it is not a differentiator on its own.
5. Write comparatively. The strongest sentences put the city's rule against a neighbour's:
"6 feet 4 inches, lower than Burnsville's 7 feet, which is why projects that stall one suburb over
are straightforward here." This is only possible because earlier cities are recorded — so read the
completed cities' permits entries in locations.ts before writing a new one. That table above is
the running comparison; extend it as cities are added.
6. Check the revision date, and treat an old handout as partial. Chaska's basement finish handout is marked Revised 1/2007 and states a 7-foot ceiling minimum with 6-foot-6 for projections — the older rule, contradicting the 6-foot-4 that Richfield's 2024 handout and Apple Valley's current one both cite for existing basements. No ceiling height was published for Chaska. Prefer the city's separately maintained handouts where they exist — Chaska's egress handout cites current code (R310) and was usable in full — and fall back to process and preference items, which age far better than dimensional code: submission method, review fees, inspection sequencing, and the city's own stated practices.
7. Name the office, not the person. Handouts often name an individual inspector with a direct line — Chaska's does, and that handout is from 2007. Staff change, and a wrong name on a commercial page is worse than no name. Publish the department, address, main number, and published email.
8. Never invent a municipal fact. These are code claims on a commercial page. If the handout does not say it, do not write it. Where a detail could not be confirmed — Apple Valley's inspection notice period, for instance — it was simply left out rather than assumed from a neighbouring city.
9. Give a published phone number an escape hatch. A city phone number is one of the fastest-decaying
facts on a page — see Re-running this later — and unlike a ceiling height, a
stale number sends a homeowner nowhere rather than just being wrong. Any non-Honey-Doers phone number
published on a page closes with a short pointer to the page it came from, so a stale number still
leaves a working path to the current one. Rosemount's permits.office is the model: after the
numbers, one sentence — "City phone numbers change from time to time — if any of these don't
connect, [URL] has the current ones." One sentence, applied at the point of publication rather than
retrofitted.
Scope, decided 2026-08-02: once per city, in permits.office only — that field already renders on
all four service sub-pages, so it is already "once per service page." Phone numbers repeated inside
cityFaqOverrides answers (Apple Valley, Prior Lake ×4, Eagan ×2) are left as-is; the office section
on the same page already carries the disclaimer, and repeating it inside every FAQ would clutter the
copy for no added coverage. Applied retroactively the same day to all 7 previously-published arm
cities — Burnsville, Richfield, Apple Valley, Chaska, Prior Lake, Lakeville, Eagan — each phone
number re-verified live against the city's own site before the pointer was added, not assumed from
what was already published.
Added 2026-08-02, immediately after the rule above produced a false positive.
A review of Rosemount flagged three claims as unsourced — a 24-hour inspection
notice, a 4–6 week permit turnaround, and a second phone number for code questions. All three were
correct and published verbatim by the city. They sit on
/177/Building-Permits-and-Inspections ("Inspections: 8:30 a.m. – 3:30 p.m. All inspections require
a 24-hour notice", and "State Building/Fire Code: 651-322-2027") and on
/315/Applications-Permits-and-Inspections ("Due to a high volume of construction currently underway
in Rosemount, building permit turnaround times are taking 4-6 weeks"). The review had looked at
/774/Building-Codes-Inspections and /184/Applications-and-Permits — different pages with
confusingly similar names — and concluded from two misses that the claims were invented.
Municipal sites scatter this material across near-identically named pages. Rosemount alone has
/774/Building-Codes-Inspections, /177/Building-Permits-and-Inspections,
/184/Applications-and-Permits, /315/Applications-Permits-and-Inspections, and
/178/Building-Code-Information. Two searches is not a search.
The discipline this actually requires:
/774 named /315 in its own
response and the reviewer went to /184 instead. The answer was one link away.Both errors are costly, in opposite directions. Publishing an invented fact puts a false code claim on a commercial page. Deleting a verified one strips exactly the specific, local, checkable detail this whole initiative exists to add — and it is the more likely error once a reviewer is primed to hunt for fabrications.
Generalised 2026-08-02 from v1.9's narrower rule, after a review of Rosemount found three unsourced claims in otherwise carefully verified work.
The v1.9 rule covered dimensional figures from PDF summaries. The claims that actually shipped were a different shape: "every inspection needs 24 hours' notice", "a 4–6 week plan review turnaround", and a phone extension — process facts, not dimensions, and not from a PDF. Every genuinely dimensional figure on those pages was exact, because the source document had been read. The gap was in the claims nobody thought of as figures.
Before committing a city, every number and named process fact on its pages must be traceable to a specific source you actually opened. If you cannot say which document or page a claim came from, it does not ship. This costs a few minutes and it is the difference between a page that is differentiated and a page that is differentiated and true.
The three claim types that most need this check, because they are consequential, plausible-sounding, and change often:
| Claim type | Why it slips | Example |
|---|---|---|
| Timing — review turnaround, inspection notice | Sounds like general knowledge, sets a homeowner's expectations | "24 hours' notice" · "4–6 weeks" |
| Money — permit fees, review fees | Specific enough to look researched | Eagan's $135.65 and Rosemount's $176 are real; both were read from source |
| Contact details — direct lines, extensions | Easy to half-remember from a directory | a second number "for code questions" |
Note the asymmetry that makes this worth the effort: a wrong ceiling height is embarrassing, but a wrong permit turnaround changes when someone thinks they can start their project.
WebFetch answers a prompt against a fetched page using a small model, and on a PDF it cannot parse cleanly it will sometimes produce plausible, specific, wholly invented numbers. Eagan's Additions handout came back with "side and rear setbacks not less than 5 feet" and "maximum lot coverage 35 percent". The document says neither. What it actually says is "Contact the Planning Division at (651) 675-5685 for setback and size requirements" — Eagan deliberately does not publish them.
Both fabrications were plausible for a Dakota County suburb, which is exactly what makes this dangerous: nothing about them would have looked wrong on the page, and they would have been published as municipal code on a commercial site.
Rule: never publish a number that came only from a fetch summary of a PDF. Read the file locally first — the fetch saves it and reports the path. If a figure cannot be confirmed against the document itself, treat it as absent, per When a source is stale. The same caution does not apply to HTML pages, where the summariser is working from real extracted text.
Lakeville arrived at this queue with bathroom at 31.7% and kitchen at 25.9% — the only pages on the site that had ever passed, written before the standard existed. Instructed to hold it to the same litmus as the rest, the audit found the score was hiding three failures:
Result after rewriting: 43.3–47.3%, and bathroom gained 14 points over the version that already "passed". A page that clears the gate has not necessarily met the standard — the gate measures distinctness from other cities, not whether the writing says anything. Re-audit inherited content rather than trusting its score.
Prior Lake's site sits behind Akamai and returned 403 to WebFetch and to curl, on every path, including its municipal code library. The city could not be read directly at all. It still produced the highest-scoring set in the queue, by changing what the packet was built from:
The lesson generalises: when a city cannot be read, do not weaken the standard, change which requirement does the work — and do not narrate the change. Requirement 5 asks for permit and process specifics; it does not prescribe which specifics, and it does not license explaining their absence to a homeowner.
A city's flagship handout may be years out of date, and its dimensional requirements are the part most likely to have moved. The rule that came out of Chaska: publish the process, withhold the disputed number. A submission method, a plan-review fee, an inspection sequence, or a city's own stated preference stays true far longer than a ceiling height or a clearance. Where two neighbouring cities' current handouts agree and a third city's old one disagrees, the old one is not evidence — leave the figure out rather than reconciling it yourself.
This is not a one-off. Municipal requirements change, handouts get revised, and pages decay against whatever competitors do next. Plan on an annual pass, and sooner for any city where something visibly changed — a permitting authority moving, a fee schedule, a new city hall.
Re-verification is far cheaper than the first pass, because the expensive part does not decay:
| Element | Decay rate | On a re-run |
|---|---|---|
| Origin story — geology, development history | Effectively never | Leave alone |
| Neighborhoods | Very slow; new developments appear | Skim; add a neighborhood only if the city genuinely grew one |
| Site conditions | Effectively never | Leave alone |
| Permit office, phone, submission method | Moderate — portals and addresses change | Verify against the live page |
| Fees, review turnaround, notice periods | Moderate | Verify |
| Dimensional code — ceilings, egress, clearances | Slow but consequential when it moves | Verify against the current handout, per When a source is stale |
| Who issues which permit | Fast and locally unpredictable | Verify first — Richfield moved electrical to the State in Nov 2024 |
The efficient path:
npm run build && node scripts/city-page-uniqueness.mjs --strict. Scores only move when the shared template changes, so a city that still passes needs
only a factual re-check, not a rewrite.What is worth re-checking even when nothing changed: whether the pages still read as being about different places. The exact-match blind spot means drift toward a common voice is invisible to the gate and will not show up in any number.
src/data/locations.ts — the city's entry takes neighborhoods, siteConditions, permits
(office, process, byService keyed by service slug), and costBands.src/data/locationServices.ts — cityFaqOverrides lives inside each of the four service
objects, not in one place. Add the city key to each of the four existing blocks. Slugs
containing a hyphen must be quoted ("apple-valley":, not apple-valley:).npm run build is the syntax gate. It is fast and catches everything. npx astro check has
hung past a three-minute timeout and is not worth running.Phase 0 — Template fixes · done 2026-08-01 (except I5t)
LocationServicePageLayout.astro now renders the service image with city-specific alt text.
Zero /locations/ pages carry a placeholder. The proof slot is the ProjectCarousel below,
which already prefers city-matched work, so this slot stays illustrative rather than repeating
a project card. Pattern copied from LocationPageLayout.astro, which already did it correctly.srcset, hero compression. Deferred to its own session: sitewide image work needs
its own verification pass and does not block Phase 1.Phase 0.5 — Recalibrate the gate · done 2026-08-02 · 1 session
city-page-uniqueness.mjs corrected — city-unique now means "appears on exactly one city."
The prior definition credited project-carousel text as local content.permits, siteConditions, costBands added to LocationData and rendered in
LocationServicePageLayout.astro. Requirements 4 and 5 previously had no field and no render.
Optional and unset everywhere, so no page output changed.Phase 1 — The arm cities · done 2026-08-19 · gated the freeze
Every city in an experiment arm reaches the standard before the baseline is frozen. Closed at 13 cities, not the original 14 — Plymouth was removed from the arm rather than remediated, see Work queue.
Scope: 14 cities × 4 services (56 pages), not 14 basement pages. The research packet is already paid for, marginal cost per extra page is ~20 minutes, and it permanently removes the risk of a later edit to a city's kitchen page confounding a live basement experiment.
Start with Burnsville end-to-end — E1-treated, priority city, no neighborhood content, and the city the Phase 0.5 fixture was calibrated against, so its word budget is already known to reach ~35%.
References while writing: Lakeville bathroom (31.7%) is what clearing the bar looks like. Apple Valley is the reference for how neighborhood copy should read — but at 7.7–8.6% it is not an example of a page that passes.
Phase 2 — Freeze and enroll · 1 data session
Re-baseline after remediation. The frozen numbers must describe remediated pages, or the experiment measures remediation instead of its treatment.
Phase 3 — The tail · background, during the window
The 12 non-arm cities. Not measured, so no contamination risk.
Scores as of 2026-08-02 — worst and best of a city's four service pages. Tick a city when all four clear 30% and the packet is written. Update this table in the same commit as the work.
Phase 1 — arm cities (pre-freeze, blocking)
| City | Arm | Worst–best | Nbhds | FAQs | Packet | Pages | Done |
|---|---|---|---|---|---|---|---|
| Burnsville | E1-T | 37.6–42.7% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Richfield | E1-T | 38.1–44.6% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Apple Valley | E1-T | 38.0–46.0% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Chaska | E1-C | 38.9–46.8% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| E1-C | 4.6–5.3% | ✗ | ✗ | — | — | deferred | |
| Minnetonka | E1-C | 35.2–39.9% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Inver Grove Heights | E1-C | 34.2–37.4% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Prior Lake | E2-T | 40.7–45.1% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Lakeville | E2-T | 43.3–47.3% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Eagan | E2-T | 40.2–44.8% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Rosemount | E2-C | 39.6–40.7% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Bloomington | E2-C | 40.3–44.1% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Edina | E2-C | 38.7–42.3% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
| Savage | E2-C | 36.8–40.7% | ✓ | ✓ all 4 | ☑ | ☑ | ☑ (cost band pending) |
Plymouth removed from the arm 2026-08-19, not remediated — see the resolution note below. The
table above now reflects the 13 cities actually in the frozen arm; Plymouth is tracked separately
as an observe-only query in seo-baseline.mjs.
Burnsville landed at 37.6–42.7% on 2026-08-02, without requirement 4. The cost-band export (B1/B6) was not available, so the page was written on requirements 1–3, 5, and 6 and still cleared the bar by 7+ points on every service. Two things follow for the remaining thirteen cities:
Do not freeze until every row is ticked. A partially remediated arm is worse than none. As of 2026-08-03, every row except the deliberately deferred Plymouth was ticked, and Minnetonka and Chaska — the other two opportunistic-geography control cities flagged by the same v1.6 finding — were both carried through to standard anyway, so the imbalance narrowed to Plymouth alone.
Plymouth resolved 2026-08-19 — removed from the arm, not remediated. It was deferred 2026-08-02
as northwest metro and opportunistic under the 2026-07-26 priority decision. Deferring it was a
sound editorial call and an unsound experimental one: an unremediated city left inside a control arm
is exactly the differential change these rules exist to prevent. Rather than write four pages for a
city with zero editorial priority, its query was moved from the E1-control cohort to observe in
scripts/seo-baseline.mjs — its position is still tracked, just no longer averaged into the control
arm. This closes Phase 1: all 13 remaining arm cities are at standard and the arm composition is
final. The deeper v1.6 finding (priority geography correlating with the treated/control split
beyond just Plymouth) was not re-litigated here — Minnetonka and Chaska cleared the content standard
identically to every other city, so nothing about this remediation initiative required touching
them; whether the E1 arm assignment itself should be rebuilt is tracked as an open question in
Search_Strategy.md, not an I11 task.
Phase 3 — tail (post-freeze, background)
| City | Worst–best | Notes |
|---|---|---|
| 37.1–41.0% | ✓ remediated 2026-08-22 — priority-adjacent; basement 41.0%, kitchen 38.7%, whole-home 38.6%, bathroom 37.1% | |
| 40.4–43.3% | ✓ remediated 2026-08-23 — priority; basement 40.4%, bathroom 41.9%, kitchen 43.3%, whole-home 43.3% | |
| 39.8–44.3% | ✓ remediated 2026-08-23 — secondary; bathroom 39.8%, kitchen 40.3%, basement 42.1%, whole-home 44.3% | |
| Eden Prairie | 8.2–9.1% | has neighborhoods — best unremediated city on the site |
| Cottage Grove | 4.9–5.9% | secondary |
| 33.6–38.5% | ✓ remediated 2026-08-03 — secondary, no contamination risk (not an arm city) | |
| Woodbury | 4.6–5.3% | secondary |
| Roseville | 4.6–5.3% | secondary |
| 35.6–39.8% | ✓ remediated 2026-08-03 — the SEO position-57 problem is a separate, unresolved issue (see Search_Strategy.md); this row tracks I11 content-standard status only | |
| Blaine · Maple Grove · Brooklyn Park | 4.6–5.8% | Non-priority by decision 2026-07-26 — zero editorial investment. Do not work these. |
Per Operating Rule 1, one session = one objective. A remediation session is a content session; don't mix it with a data pull or a strategy edit.
Starting a session:
permits entries first — the best sentences are comparative.Ending a session:
npm run build && node scripts/city-page-uniqueness.mjs --cities <city>[I11] in the subject.master and verify the live URL, not the build:
curl -s https://www.honey-doers.com/locations/<city>/basement-remodeling/ | grep -c "Permits and Inspections"
A city is not done until it is serving. See Committed is not shipped.Definition of done for a city: all four service pages ≥30% city-unique, ≥3 city-specific FAQs per service, 3–4 named neighborhoods, permit specifics, cost band, no placeholder, and no sentence shared with another city.
Added 2026-08-03 so a full Opus review isn't the mechanism that catches defects already on record. Every item here is a real defect a review found in this initiative — re-checking against a known list is cheap; a full independent review is not. Run this before ticking a city, not after a review flags it.
| Check | What it catches | Found in |
|---|---|---|
| Read every new sentence and ask: does this state a fact, or explain the absence of one? | Meta-commentary — narrating a research gap to the customer ("the city's site wasn't reachable to confirm…") instead of silently omitting the fact | Minnetonka, Savage — see Withholding is silent |
Read the new city's permits, siteConditions, and FAQ answers against the two or three most recently completed cities, side by side |
Paraphrase-trap near-duplicates — the same sentence structure with only the city name and a number swapped, invisible to the exact-match gate | Minnetonka/Savage ceiling-height FAQ opening clause |
| For every number and named process fact, name the exact source document or URL it came from, out loud or in a scratch note | Unsourced claims — plausible-sounding process facts nobody actually verified | Rosemount's 24-hour notice, 4–6 week turnaround, second phone number (all correct, but initially unsourced in the moment) |
| If a claim came from a PDF, confirm it was read via the Read tool locally, not taken from a WebFetch summary | Fabricated figures — a summarizer inventing plausible numbers a document doesn't contain | Eagan's invented setback and lot-coverage figures |
| Check every non-Honey-Doers phone number closes with a source-URL pointer | Phone escape hatch omitted | Backfilled retroactively in v1.14 after being missed on the first 7 cities |
| Re-read the city's origin-story sentence: could it be published verbatim on a neighboring city's page with just the name changed? | Generic, decorative "local color" that isn't actually differentiating | Lakeville's original neighborhoods, pre-rewrite |
What this does not replace. The checklist only catches failure modes already discovered. A full review still has a role — periodically, or whenever something unusual happens (a blocked city, a stale source, a new kind of claim) — because its value going forward is finding failure modes not yet on this list, not re-finding these. Running a full audit on every city re-spends effort on defects a five-minute self-check already prevents.
Measured 2026-08-02: the testimonial slider is 1,185 words of a 3,244-word city page (36.5%), byte-identical across all 26 cities. It is a bigger block of shared text than the FAQ set, and 1 of 23 testimonials carries a city.
Mechanism shipped 2026-08-02 — testimonialsForCity() puts city-matched reviews first and tops
up from the general set, mirroring how ProjectCarousel already prefers city-matched work. A
city-matched review scores as genuine differentiation, so this is the cheapest requirement-2-grade
content available: it is attribution of reviews we already have, not writing.
Two rules, both learned the hard way in the same session:
HIDE_TESTIMONIALS_WITHOUT_CITY_MATCH stays off while I11 is live. With one review attributed,
hiding the section would strip it from 13 of 14 arm cities and leave it on Apple Valley — again
differential, again favouring a treated arm. It also removes social proof from pages whose job is
to convert, which is the same argument that replaced "Photos Coming Soon" rather than deleting the
slot. Revisit after reviews are attributed and E1 reports.Attribution is a data task, not a writing task. Set city only from a real record — a Google
review with a location, or the job file. A review attributed to the wrong city is a false local-proof
claim on a commercial page, which is worse than no attribution.
Attribution done 2026-08-02: 9 of 23 now carry a city — Lakeville 5, Apple Valley 2, Inver Grove Heights 1, Minneapolis 1 — matched against ~300 CompanyCam job records on first name plus last initial, accepting only matches unique in the records. Four were left unset as ambiguous and ten had no candidate; the detail is in the closed attribution task.
It moved the gate by roughly seven words. Because the mechanism reorders rather than truncates, the only city-unique text attribution creates is the heading. Medians stayed at ~5%. This is the right outcome — the alternative would have been score inflation — but it means attribution earns its keep on conversion and E-E-A-T, not on the acceptance metric.
Attribution alone cannot differentiate a page, because it only rearranges text every city already shows. A "city proof block" built on project counts per city was proposed on 2026-08-02 and rejected the same day. Recording it so it is not re-proposed:
CompanyCam is a system of record, not an analytics source. Its value here was record lookup for review attribution, which worked. Treating it as a source of strategic findings was the error.
Evidence that is city-specific, honest at any scale, and does not get weaker where we are thin:
| Element | Source | Status |
|---|---|---|
| Named neighborhoods and what those homes need | research packet | requirement 2 |
| Municipal permit process and site conditions | research packet | requirement 5 |
| Local cost band | job history — revenue, not counts | requirement 4, pending the export |
| Five-star reviews from this city | attributed testimonials | 9 of 23 attributed |
Revenue per project type is the truth source, and it is already Business Questions B1/B6. Project mix is a usable proxy only until revenue exists to replace it; once it does, the proxy is retired rather than kept alongside. This is what the queued export is for.
Correction to the same day's note: the export is worth pulling once and using twice — cost bands and B1/B6 — not three times. The third use was the counts idea, which is withdrawn.
Two basement projects exist site-wide. This is procurement, not writing, which is why it starts first and finishes last.
locationSlug and category on all 23 projects in
src/data/projects.ts.This is the part most likely to silently void the experiments, because the contaminating work is all work we otherwise want to do.
The distinction: a change hitting every arm equally is a common shock and is harmless. A change hitting one arm more than another is differential and voids the reading.
| Work | Rule |
|---|---|
| New blog posts | Continue on cadence. When a post names an arm city, link the city hub (/locations/burnsville/), not the service sub-page. |
| Blog refreshes (I6) | Same hub-link rule. Both blog skills already enforce it. |
| Case studies / project pages | Aim them at non-arm cities — 9 are available and they feed Phase 3. |
| Photography capture | Unrestricted and encouraged. Capturing is not publishing. |
| I5t template batch | Site-wide, common shock. Ship freely. |
| GBP push (I4) | Deferred — its Lakeville radius covers three arm cities and no control. Revisit after E1 reports. |
| Editing an arm city page | Only as registered remediation or treatment. Any other edit is an unrecorded dose. |
The pre-freeze window is not a free pass. The GSC baseline window is retrospective, so work shipped in the weeks before the freeze still lands inside the comparison window. These rules apply now, not from the freeze date.
Detection backs the discipline. scripts/seo-baseline.mjs counts editorial links per tracked
page at freeze and compare, flags any control arm that gained links, and downgrades that experiment
to UNREADABLE rather than reporting a verdict.
scripts/city-page-uniqueness.mjs is the definition of done. Check work against it rather than by
reading pages.
npm run build
node scripts/city-page-uniqueness.mjs # all services
node scripts/city-page-uniqueness.mjs --service basement-remodeling
node scripts/city-page-uniqueness.mjs --cities burnsville # report one city
node scripts/city-page-uniqueness.mjs --shared # dump the boilerplate
node scripts/city-page-uniqueness.mjs --strict # exit 1 on any failure
It reports city-unique % per page against the 30% threshold, detects remaining placeholders, reports
cityFaqOverrides coverage per service, and lists sentences appearing on some cities but not all.
A sentence appearing on exactly one city's page for that service. Not "absent from at least one page" — that was the v1.0 definition, and because project-carousel cards repeat across most cities but not all 26, it credited them as local content and overstated kitchen and bathroom by ~26 points.
Text on some-but-not-all cities now scores zero and is named by the repeat detector. Each such sentence is either boilerplate that belongs in the shared template, or a passage that should have been written for its city.
The cityFaqOverrides coverage reporter reads source, not the build. It confirms an override
is declared, not that it renders. Trust the ratio for that.
--cities filters what is reported, never what the boilerplate baseline is computed from.
Narrowing the baseline would count other cities' shared text as unique and report a flattering
number.
It matches exact sentences, and nothing else. The comparison is string equality on a set
(city-page-uniqueness.mjs:122). Paraphrase defeats it completely. Four cities could be
written from one set of sentences with the nouns swapped — "Burnsville's 1970s ramblers" against
"Richfield's 1950s ramblers" — and every page would score in the forties while remaining, in
substance, one page reprinted four times. That is the original defect wearing a disguise, and the
gate would report success.
The comparison unit is a rendered text node, not a sentence, despite the function's name.
sentences() (city-page-uniqueness.mjs:65) splits on HTML tag boundaries only — a multi-sentence
FAQ answer that sits in one <div> becomes one comparison string. Two cities whose second
sentence is byte-identical are invisible to the gate as long as their first sentence differs,
because the two full paragraphs never match as strings. Found 2026-08-03: Savage's and Minnetonka's
"second bathroom" FAQ answers opened with different sentences but closed with the identical clause
"The lower level is the usual candidate, governed by the state's 6-foot-4-inch minimum ceiling
height for an existing basement." — a real violation of the hard rule that no repeat-detector run
ever surfaced, because the gate never compares below the paragraph.
The repeat-detector output is truncated to the top 15 by frequency
(city-page-uniqueness.mjs:185, .slice(0, 15)). A sentence shared by only 2–3 cities sorts near
the bottom of a list often 20+ entries long and is dropped from the printed report even though it
is counted in the "N sentences appear on some cities but not all" total. The Savage/Minnetonka
duplicate above was in that count the entire time; it just never printed.
Neither of the two limits above has been fixed in the script. A true per-sentence, full-list
comparison would need to split each text node on .!? boundaries before hashing, and print (or at
least separately flag) every 2–3-city repeat rather than only the most frequent ones — worth doing
before Phase 3, since the tail cities will reuse this same fixed-slot structure and the risk
compounds with every additional city. Tracked as an Open item rather than fixed
inline, so the fix gets its own verification pass instead of riding along with content work.
This is the failure mode to actively guard against, because the standard's fixed structure invites it. Every city gets neighborhoods, site conditions, permits, and FAQs in the same slots, so the temptation is to write one set of sentences and swap the facts. The defence is not mechanical: before committing a city, read its pages against a previously completed one and ask whether someone who read both would have learned different things. If the only difference is proper nouns and numbers, the city is not done. What has kept the first four honest is that each was allowed to be about something different — Richfield about small postwar houses under a flight path, Apple Valley about ground that used to be gravel pits, Chaska about brick that is no longer made.
Added 2026-08-02 after this cost a day of live defect.
LOCAL_PAGE_REMEDIATION.md recorded Phase 0 as done 2026-08-01, with "Zero /locations/ pages
carry a placeholder." That was true of the repository and false of the website. The commit had never
been pushed; origin/master was nine commits behind before this workstream opened. "Photos Coming
Soon" was serving on all 104 pages for a further day after being recorded as fixed, on the exact
pages the initiative exists to repair.
The uniqueness checker cannot catch this. It reads dist/, which is built from the working tree, so
it reports on what you have written rather than on what anyone can see. Every number in this document
is a measurement of the repository.
Rule: a page is remediated when it is serving, not when it is committed. The session protocol closes with a live-URL check, and status tables in this document distinguish committed from deployed where the two differ.
Derived 2026-08-02 from a six-requirement fixture written at realistic length against Burnsville, measured, and reverted. It calibrates word budget, not content — the ratio depends on length, not on truth.
| What a page carries | City-unique |
|---|---|
| Requirement 1 only — today's state | ~5% |
| + requirements 2–3 as they exist on Apple Valley / Eagan | 8–9% |
| + requirements 4–5 | ~21% |
| + requirement 3 at full strength | 34–35% |
| Lakeville bathroom — real page, requirements 1–3 | 31.7% |
A standard that can't be measured will be met on paper and missed in fact. Same reasoning as the contamination detector: rules get violated, and detection is what makes that recoverable.
An instrument that gates work is validated against a known-good and a known-bad case before the work starts. Adopted 2026-08-02 as a standing rule after the v1.0 gate was found to be measuring the wrong thing — see Version history.
Target state below is pre-collapse and superseded, added 2026-09-02. It describes remediating location×service pages to the 30% uniqueness standard through Phase 3 (~92/104 pages). The Level 2 collapse decision (2026-08-27) retired that model: location×service pages merge into the location hub instead of being remediated in place, so nothing is bringing the remaining pages to the 92/104 target below. The Phase 1 rows in Delivered so far remain an accurate historical record — that work fed real content into the hub pages during the collapse — but the Target state table and the Phase 3 tail figures under Effort describe a plan that is no longer being executed. See Delivery for what's actually happening now.
| Measure | Today (Phase 1 close) | At Phase 3 close |
|---|---|---|
| Pages meeting the 30% standard | 60 / 104 (13 arm cities + 2 early tail cities) | ~92 / 104 |
| Arm cities at standard | 13 / 13 — final arm size, Plymouth removed | 13 / 13 |
Cities with neighborhoods |
15 / 26 (Lakeville's four rewritten) | 23 / 26 |
Cities with permits |
15 / 26 (13 arm + Minneapolis + St. Paul) | 23 / 26 |
| Testimonials attributed to a city | 9 / 23 | 23 / 23 where a record exists |
Cities with costBands |
0 / 26 — blocked on B1/B6 export | 23 / 26 |
| Experiment arms balanced at baseline | yes — Plymouth's removal was the last imbalance from unremediated content | yes |
(23 of 26 rather than 26 — Blaine, Maple Grove, and Brooklyn Park are non-priority by decision 2026-07-26 and receive zero editorial investment.)
| Phase | Date | Outcome |
|---|---|---|
| Final review · manual duplicate audit | 2026-08-03 | Requested after v1.21's self-audit checklist, to check whether the new rules should be run retroactively across all 13 arm cities. The --strict gate re-confirmed all 52 pages passing, but a one-off script comparing true sentences (period-split, not the gate's tag-split) across the 13 cities' source found 4 real cross-city duplicate sentences invisible to the standard tool — see the new Known limits of the instrument entries. Fixed: Savage/Minnetonka's identical "second bathroom" closing clause, Bloomington/Edina's identical frost-depth sentence, Bloomington/Minnetonka's identical frost-depth sentence, and Richfield/Rosemount's identical "24 hours' notice" sentence (the last two are independently true for both cities — only the phrasing collision was the defect, so one side of each pair was reworded, not the fact). All 6 affected cities re-verified ≥30% after the edits. The gate fix itself (true sentence-splitting, un-truncated repeat report) is deferred to before Phase 3 — see the new Open items row. |
| Phase 1 · Inver Grove Heights | 2026-08-03 | Thirteenth and final arm city — all non-deferred arm cities now at standard. Sixth E1-control city. All four services 34.2–37.4%. Site conditions carry the strongest single fact in the queue: the Pine Bend Impact, a ~490-million-year-old meteor crater about 2.5 miles across identified by the Minnesota Geological Survey in 2021, buried under the Mississippi River bluffs on the city's east side. A second genuine differentiator survived scrutiny — the city's older Rich Valley area is still meaningfully unsewered, and its own basement-finish handout requires an MPCA-licensed septic compliance inspection before a bedroom-adding permit is finalized, a fact with no counterpart in any completed city so far. Ceiling height (7 ft habitable / 6'4" under beams) and egress figures (5.7 sq ft, 24"×20" minimum, 44" max sill) were read directly from the city's own PDF handout after a WebFetch summary returned nothing usable — the file was fetched, saved locally, and read with the Read tool per the standing PDF rule. Inspection order (electrical rough-in by the State first, then a combined framing/insulation/mechanical/plumbing rough-in, then electrical final, then building/mechanical/plumbing finals) and plan-review turnaround (3/15/20 business days by project type) came from the same source. |
| Phase 0 | committed 2026-08-01 deployed 2026-08-02 |
Placeholder removed from all 104 pages; carousel behaviour verified as correct; neighborhood block verified across all four services. Committed but unpushed for a day — "Photos Coming Soon" was live on all 104 pages until the 08-02 deploy. See Committed is not shipped. |
| Phase 0.5 | 2026-08-02 | Acceptance gate corrected; requirements 4–5 given data fields and renders; threshold re-derived and held at 30%; corrected baseline recorded |
| Reviews · geo | 2026-08-02 | Testimonial slider made city-aware (reorder, never truncate); 8 further reviews attributed from CompanyCam records, 9 of 23 now carry a city. Moved the gate ~7 words — value is conversion, not score. A per-city project-count proof block was proposed and rejected the same day: it would change no decision, and as page content it is only proof where the number is large. |
| Phase 1 · Minnetonka | 2026-08-03 | Fifth E1-control city, and the 12th arm city — one remains. All four services 35.2–39.9%. Third city whose own site was unreachable (the same Akamai errors.edgesuite.net block as Prior Lake and Savage), confirmed via curl from this session as well as a real browser on the user's end. Office/process detail came from the unblocked LOGIS ePermits portal (epermits.logis.org/home.aspx?city=mi) rather than search results this time. Site conditions carry a genuine differentiator: Gray's Bay Dam, in Minnetonka, is the literal headwaters control structure for the entire Minnehaha Creek Watershed District, and a real upper/lower watershed drainage divide runs through the city. A user review caught a register defect the gate cannot see: the initial draft correctly withheld unconfirmed fees, turnaround, and ceiling-height figures, but narrated the gap in customer-facing copy three times ("Minnetonka's own site was not directly reachable to us...") — off-register for a homeowner-facing page, and the same defect was found retroactively in one already-deployed Savage sentence. Both corrected; see Withholding is silent, added to the method as point 5. The correction also surfaced a near-duplicate the exact-match gate can't catch — Minnetonka's and Savage's ceiling-height sentences shared an identical opening clause after the fix, differentiated in the same pass. |
| Phase 1 · Savage | 2026-08-03 | Fourth E2-control city. All four services 36.8–40.7%. Second city whose own site was unreachable — savagemn.gov returned Akamai 403s (errors.edgesuite.net) to every path, on both WebFetch and curl, confirmed as a bot-block rather than a bad URL when the user's own browser resolved the same pages fine. Followed the Prior Lake pattern: office details from search results, dimensional code left at the state minimum rather than invented, a different requirement — the city's own Comprehensive Wetland Protection and Management Plan, which exceeds the state Wetland Conservation Act minimum — carrying requirement 5. The user then found a working, unblocked source (epermits.logis.org/home.aspx?city=sa), which supplied real process detail (a $25 plan-review deposit, forfeited on withdrawal; fees over $1,000 paid by cash or check, not online) that replaced the initial "site unreachable" hedge in permits.process. Site conditions carry the strongest single fact found across all eleven cities to date: Savage sits over the Prairie du Chien/Jordan aquifer, which surfaces as artesian springs at Boiling Springs and feeds the Savage Fen, a calcareous wetland protected by name under state statute 103G.223 — rare enough that the city has reduced its own aquifer use to protect it, read directly from the city's 2040 Comprehensive Plan (Natural Resources chapter) after the PDF summary failed and the file was read locally. |
| Phase 1 · Edina | 2026-08-03 | Third E2-control city. All four services 38.7–42.3%. First city with a genuine local regulatory layer beyond building code: the Country Club Historic District (NRHP 1980, Heritage Preservation Landmark overlay 2003) requires a Certificate of Appropriateness for any significant structural change to a street-facing facade, scoped carefully in the copy so it doesn't overstate — a rear kitchen bump-out usually doesn't trigger it, a front-facing change or demolition of a contributing 1924–1944 home does. Site conditions built the same way as Bloomington's: Edina spans two watersheds (Nine Mile Creek, Minnehaha Creek) with historically wetland ground near Lake Cornelia, now drained and storm-sewered — an actionable water-table fact, not just history. Neighborhoods span four distinct eras: Morningside (platted 1905, its own village 1920–1966), Country Club District, White Oaks (1920s-30s, built on the old Baird farm), and Parkwood Knolls (the only remaining buildable land in the city). No permit fee or plan-review turnaround was published anywhere findable, so neither was invented — the Certificate of Appropriateness requirement carries requirement 5 instead. |
| Phase 1 · Bloomington | 2026-08-03 | Second E2-control city. All four services 40.3–44.1%. Site conditions built to directly address the Rosemount open item — an actionable paragraph naming which of the city's three land types (sand plains, low hills, river valley) drains well versus carries a high water table, not just history. Origin story: population grew 410% in one decade (1950→1960) on individual wells and septic before I-35W connected the city to Minneapolis, then a second reinvention in 1982 when the Twins/Vikings' departure from Metropolitan Stadium led to the Mall of America. Distinctive sourced facts: a window well shallower than 32 inches may need additional frost insulation (the opposite of the usual well-depth problem), and basement bottom plates must be treated or redwood — Hem-fir, Doug-fir, and S-P-F explicitly excluded. |
| Phase 1 · Rosemount | 2026-08-02 | First E2-control city, treated identically to the treated arm. All four services 39.6–40.7%. A WebSearch summary of Rosemount's soil geology returned invented specifics (a fabricated "Buck Hill" comparison and unsourced clay/drainage claims) and was discarded in favor of the city's own comprehensive-plan PDF, read directly, and its basement-permit HTML page — same discipline as Verify figures against the document, not the summary, this time on a search summary rather than a PDF fetch. Origin story: 11,500 acres of farmland taken in 1942 for the wartime Gopher Ordnance Works, later UMore Park, still an active sand-and-gravel mine on its southern half — the reason the city still carries thousands of banked acres for growth that built-out neighbors don't have. |
| Phase 1 · Eagan | 2026-08-02 | All four services 40.2–44.8%; its four generic neighborhoods rewritten as Lakeville's were. No basement handout exists, so the current Windows & Doors handout carried the work. A remote PDF summary fabricated setback and lot-coverage figures that the document does not contain — see Verify figures against the document, not the summary. |
| Phase 1 · Lakeville | 2026-08-02 | All four services 43.3–47.3%. The city that already "passed" was the one that most needed rewriting — see A passing score is not the standard. Bathroom 31.7→45.9, kitchen 25.9→43.3, and its four neighborhoods replaced wholesale. |
| Phase 1 · Prior Lake | 2026-08-02 | All four services 40.7–45.1%. First city whose own site was unreachable — Akamai returned 403 to every fetch — and the first where the strongest material was zoning rather than building code: shoreland limits of 25% impervious surface, setbacks from the ordinary high water level, and a state framework permitting improvement of nonconforming lakeshore structures but not expansion. See When the city's own site is unreachable. |
| Phase 1 · Chaska | 2026-08-02 | First control city, treated identically to the treated arm. All four services 38.9–46.8%. Carver County sources are as good as Dakota County's. Also the first city whose primary handout was too old to trust in full — see When a source is stale. |
| Phase 1 · Apple Valley | 2026-08-02 | Third arm city. All four services 38.0–46.0%. First city where existing content was extended rather than written from the template — the three generic neighborhoods were replaced with four sourced ones. Distinctive local facts: a 9-square-foot window-well minimum with no dimension under 36 inches, plans required at true architectural scale, a six-stage inspection order where framing waits on electrical approval, and a city built on its own progressively filled gravel pits. |
| Phase 1 · Richfield | 2026-08-02 | Second arm city. All four services 38.1–44.6%. Richfield's own code handouts yielded genuinely distinct facts — a 6'4" basement ceiling minimum against Burnsville's 7', a pre-June-2009 exemption from basement insulation requirements covering effectively the whole city, and electrical permitting transferred to the State in Nov 2024. |
| Deploy | 2026-08-02 | 15 commits pushed to origin/master and live. Production had been nine commits stale before this workstream even opened. Burnsville and Richfield verified live; an unremediated control (Chaska) verified as carrying no permits section and no placeholder, confirming the template additions are inert where unpopulated. |
| Phase 1 · Burnsville | 2026-08-02 | First arm city to standard. All four services 37.6–42.7% against a 30% bar, on requirements 1–3, 5, 6 — cost band deferred pending the B1/B6 export. Municipal facts sourced from Burnsville's own building-department material. |
| Workstream | Unit | Units | Est. |
|---|---|---|---|
| Phase 0 template + I5t | session | 1–2 | ~4 h |
| Phase 0.5 recalibration · done | session | 1 | ~2 h |
| City research packets, arm cities | ~1 h each | 0 remaining — 13 of 13 done | — |
| Page write-ups, arm cities × 4 services | ~25 min each | 0 remaining — 52 of 52 done | — |
| Phase 3 tail | — | 7 cities remaining (2 of 9 done early) | ~18 h |
| Imagery protocol + tagging | one-time | — | ~3 h |
Phase 1 is done. Phase 2 (freeze) is unblocked. All 13 arm cities are at standard, against an original ~40-hour estimate. Plymouth's arm-membership decision is resolved (removed, not remediated — see Work queue); the cost-band backfill is real remaining work but does not block the freeze, since requirement 4 was never load-bearing for the 30% threshold (see Word budget per page).
Remediation ships to every page, so its own effect on rankings will not be cleanly attributable. This is accepted deliberately rather than worked around: the alternative is holding half the site at a known-defective standard for ten weeks to preserve a comparison we do not need in order to justify the work.
| Item | Raised | Action |
|---|---|---|
Rosemount's siteConditions is written as history, not site conditions. Genuinely differentiating, but it tells a homeowner nothing about soil, water table, or terrain — the 42-inch frost line is the only actionable item. |
2026-08-02 review | Second pass when convenient. Not urgent, not an error. |
permits.office and permits.process are identical, word-for-word, across a city's four service pages — by design (LocationServicePageLayout.astro:240-242): same office, same submission process, regardless of service. On Lakeville that's ~225 of ~350 permits-section words repeated verbatim on the basement, kitchen, bathroom, and whole-home pages, before the service-specific paragraph. The acceptance gate doesn't catch this because it measures differentiation across cities for one service, never across a city's own four services — a page can score 45% unique against the other 25 cities while still reading as copy-paste to someone clicking through one city's four pages back to back. |
2026-08-02, user review | Not urgent — no false content, and most visitors land on one service page, not four in a row. Worth a decision at Phase 2: leave as-is (it's factually correct and cheap), or split process into a short shared core plus a per-service sentence. |
| The gate compares whole rendered paragraphs, not sentences, and truncates its repeat report to the top 15. A manual full-text sentence scan of all 13 arm cities (2026-08-03) found 4 real cross-city duplicate sentences the gate never printed — see Known limits of the instrument. All 4 were fixed the same session. | 2026-08-03, manual audit | Fix the script: split on sentence boundaries, not just tags, and print every 2+-city repeat rather than the top 15. Do before Phase 3 — the tail cities reuse the same fixed-slot structure, so the risk compounds. |
| Service sub-pages read as dense even though the content is appropriate for what they're covering. Word counts are up meaningfully post-remediation (roughly 3,300–3,600 words per page across the eight completed cities) and the page is long, text-heavy blocks stacked vertically — permits, site conditions, neighborhoods, FAQs — with little visual breathing room between them. The content itself isn't the problem; the layout doesn't yet signal "skim here, read there." | 2026-08-02, user review | Second pass, not blocking Phase 1. Evaluate whitespace, visual separators, pull-quotes/callouts for the strongest facts (ceiling height, origin story), and whether some sections (neighborhoods, FAQs) work better as accordions/tabs than a single long scroll. Design work, not a remediation-standard change — track separately from the work queue. |
The 26 city hub pages (/locations/<city>/) were never in scope and carry the same duplicate-content defect — Bloomington measured 91%/82% identical to Eagan/Lakeville, and city-page-uniqueness.mjs doesn't read this layout at all. Surfaced when GSC flagged Bloomington's hub page as "Crawled – currently not indexed" ~7 weeks post-publish. |
2026-08-26, user request | Superseded by v1.29 — the hub is now the only city-facing page type; city-page-uniqueness.mjs needs to read the consolidated hub layout (not the old service-page layout) once the Lakeville pilot exists to measure. |
| Whether the 8 un-remediated, non-priority cities (Blaine, Maple Grove, Brooklyn Park, and others) get a full content merge or a deliberately thin hub is still an open, conceptual decision — raised 2026-08-26 ("HD doesn't really work in Blaine so why even have it on the site"), explicitly not acted on. | 2026-08-26, user | Timing decided 2026-08-28: have this conversation at redirect-cutover time (step 4 of Delivery), not before — cutover is the clean natural point to decide prune vs. invest per city, batch by batch, rather than resolving it in the abstract ahead of the pilot. Not resolved yet, just scheduled. |
Case studies (projects.ts) don't yet carry the fields the collapsed-hub design assumes — no investment tier, no timeline, no code/permit callout, just a photo and a short blurb. |
2026-08-27, delivery planning | Schema upgrade needed with or before the Lakeville pilot — see Delivery, item 2. |
Services-tier pages (/services/, /services/<slug>/) don't link to any /locations/ hub. Competitor audit (Three Rivers Contracting, SEO_FINDINGS.md 2026-08-28) found their equivalent services hub and category pages cross-link to their full city-page network from every services-tier page; ours has zero outbound links to locations anywhere in that tier. This is the same unused-surface problem [[project-priority-city-links]] already tracks for blog posts, just on the services pages instead — under-linked priority cities (Rosemount/Savage) are the natural first targets once this ships. |
2026-08-28, competitor audit | Simple addition — add a location-hub link (or a small "Where we work" block linking a handful of priority cities) to src/pages/services/index.astro and the per-service pages. Do with or shortly after the Lakeville pilot ships, since it should point at the new hub URLs, not the retiring LocationServicePageLayout.astro ones. |
Versions are cumulative. Superseded figures and withdrawn claims are retained in Superseded record rather than deleted, so a later reading can be compared against the one it replaced.
| Version | Date | Change |
|---|---|---|
| 1.33 | 2026-08-30 | Lakeville live in production. Merged locations-hub-v2-pilot into master (PR #5, merge commit 36d0e75, 2026-08-30 16:37 UTC) after 0 astro check errors, a clean build, and a passing check-redirects.mjs run against the Cloudflare Pages preview. Post-deploy, confirmed directly against https://www.honey-doers.com: the hub renders correctly, /locations/lakeville/kitchen-remodeling/ single-hop 301s to /locations/lakeville/ (no chain), and check-redirects.mjs reports 391/391 healthy against production (same 12 pre-existing, unrelated warnings as before). The 2026-08-30 16:37 UTC go-live timestamp is the clock the Delivery step 4 hold (2-3 weeks before batching cities 2-6) runs from. |
| 1.32 | 2026-08-30 | Lakeville cut over to LocationHubPageV2 at the real /locations/lakeville/ URL. Promoted from the /locations-preview/lakeville preview: excluded from the [citySlug]/[serviceSlug] dynamic routes (avoids a duplicate route and stops generating the 4 retiring service pages), sitemap no longer lists them. Added 4 literal-path 301 Functions (/locations/lakeville/<service>/ → /locations/lakeville/) — a real page still lives at /locations/lakeville/, so per this repo's redirect rules a catch-all wasn't appropriate; fixed 2 pre-existing legacy-URL Functions that pointed through the now-retired kitchen-remodeling page, avoiding a redirect chain; synced public/_redirects and functions-redirects.json. Repointed 20 blog posts + 1 case study off the retiring URLs. Pulled a pre-cutover GSC snapshot for the 5 Lakeville URLs involved and recorded it in SEO_FINDINGS.md (2026-08-30 entry) rather than re-freezing seo-baseline.json — that file already holds the 2026-08-19 E1 tracked-query baseline and the script correctly refuses to overwrite it silently; the two track different things (a query cohort vs. whole-page performance) and don't conflict. All work committed to locations-hub-v2-pilot, not yet pushed or deployed. |
| 1.31 | 2026-08-28 | Lakeville pilot built, iterated through a full UX review pass, and pushed for outside review. LocationHubPageV2.astro implemented at /locations-preview/lakeville (preview-only, not linked/sitemapped) and taken through several rounds of feedback in one session: Investment tiers set to TBD pending real figures; dense permits/process copy split into paragraphs behind a disclosure toggle, with a working permit-portal link (found via search, not guessed) and a license-lookup link + number (unverified — sourced from a BBB listing, flagged to the user for confirmation before treating as final); a second watercolor "beat" section added between the service tabs and Neighborhoods, reused on the pre-footer CTA as an optional image prop on ContactForm.astro; per-service Did You Know content split into discrete bullets (previously single dense paragraphs) for bathroom/basement/whole-home; active-tab color changed to the brand amber instead of blue; hero and pre-footer chevron texture swapped for the site's wave pattern, later removed from the hero (competed with the H1) and added to the global footer instead; investment-card/photo bottom-alignment, image-left/content-right layout test, gallery and FAQ intro copy, and a Contact-form City field that now pre-fills from location.name (also wired into the two live location layouts, not just the pilot). Fixed a real mobile bug: neighborhood and project-gallery cards used hover-only reveal for their text, which is unreachable on touch devices — now visible by default below the lg breakpoint, hover-reveal preserved above it. Swept Lakeville's own copy for em dashes, converting each to whatever punctuation was actually grammatically correct (colon/semicolon/comma/parentheses) rather than a blanket find-replace; left the resource-card title/subtitle em dash convention alone at the user's call, and left real customer testimonial quotes untouched (flagged, not fixed, since testimonials.ts was transcribed from Google reviews by Claude in an earlier session — not verified verbatim). Pushed to the locations-hub-v2-pilot branch for a Cloudflare Pages preview; next step is human + fresh-agent review, not further build work. |
| 1.30 | 2026-08-28 | Delivery status + sequencing clarified, no architecture change. Status line updated to reflect the Lakeville pilot's actual state (built and iterating at /locations-preview/lakeville, not yet promoted to the live /locations/lakeville/ URL — redirect Function and case-study schema upgrade still outstanding). Decided the timing of the non-priority-city pruning question (Blaine/Maple Grove/Brooklyn Park, Open items): resolve it at redirect-cutover time, batch by batch, not before batching starts — cutover is the natural point to decide prune-vs-invest per city. Delivery steps 3–4 updated to reflect this. Added a services-tier internal-linking open item from the 2026-08-28 Three Rivers Contracting competitor audit (SEO_FINDINGS.md). |
| 1.29 | 2026-08-27 | Collapsed Layer 2 (location×service pages) — decided. Supersedes the three-layer model on this one point: two levels remain (service pages, location hubs), not three. Reasons recorded under Level 2 collapse: the buyer-value case for concentrating proof rather than splitting it four ways per city; a live competitor review (Footprints Bath & Tile, Garrett Construction, Baths R Us, Gerl Construction, Country Creek Builders, JBDB) finding no three-level structure in the market; a user-supplied GSC index-coverage export showing remediated hubs indexing worse than non-priority thin ones; and a 90-day GSC performance export showing both levels near-zero (13 clicks/11.6k impressions on 30 hubs, 14 clicks/14.1k impressions on 96 service pages, avg. position 24–30 at both levels) — collapsing sacrifices little real traffic. Layer 2's content doesn't disappear; it merges into per-service sections on the hub. Added Delivery: pilot Lakeville as a real Astro page from the latest canvas mockup, land the case-study schema upgrade with or before it, batch the other 25 cities by conversion cost (completed research packets first), stage the redirect cutover, retire LocationServicePageLayout.astro last. Phase 3 tail work paused (packets still useful as hub-merge input, just don't publish new standalone service pages). Header objective/progress/next-action updated to match. |
| 1.28 | 2026-08-27 | Adopted the three-layer content model, following a strategy discussion that concluded I11's original standard (permits, ceiling heights, frost depth) differentiates for the uniqueness gate more than it helps a buyer, while today's hub-page finding showed project/case-study content is both the strongest differentiator and the only piece that maps to real buyer criteria. Reallocates content by layer: service pages (/services/) carry approach/education/curated case studies; location×service pages keep permits/site-conditions/cost-bands, narrow the site-conditions claim to mainly basement, and add a municipal-resource block (permit office, licensure lookup) plus curated municipal case studies; neighborhoods and testimonials move off the four service pages (where they're currently duplicated five times per city — hub plus all four services) to render once on the hub, which becomes a trust/routing layer rather than an explanation layer. Explicitly rejected a written "municipal approach" section (the approach doesn't materially vary by city) and a local-events/activities feed (thin-content pattern, no buyer relevance). No page content changed yet — this is the architecture the next implementation session works from. Supersedes the framing in Hub pages, not its measurement data. |
| 1.27 | 2026-08-26 | Scope expanded to the 26 city hub pages (/locations/<city>/), at the user's direction after investigating why /locations/bloomington/ (published 2026-07-04) showed "Crawled – currently not indexed" in GSC. A line-diff found Bloomington's hub page 91% identical to Eagan's and 82% identical to Lakeville's — the same shape of defect this initiative was opened to fix on service pages, on a page type city-page-uniqueness.mjs doesn't measure. Added Hub pages, documenting what's confirmed (the duplication measurement, which fields already carry some uniqueness via existing neighborhoods data) and what isn't (whether the six-requirement standard transfers, whether the 30% threshold applies, whether this is its own phase). Deliberately does not prescribe a standard yet — that's follow-on work, done in the same order as the original initiative: measure, then define, then write. Objective and progress table updated to reflect the new scope; no service-page finding, score, or phase status changed. |
| 1.26 | 2026-08-23 | Northfield complete — fourth Phase 3 tail city (after Minneapolis, St. Paul, Elko New Market), background work carrying no contamination risk. All four services 39.8–44.3%. Distinguishing facts: an in-house Building and Inspections Department rather than a contracted one (the opposite structure from Elko New Market and Shakopee), with only electrical, fire suppression, and fire alarm work routed to the State; a single set of plans required versus two elsewhere; a homeowner express-permit lane for simple scopes; and a real, sourced Certificate of Survey requirement — triggered only when a project changes a building's footprint or exterior dimensions, so basement and bath work is exempt but additions aren't. Fee figures (residential alteration $104+$1 minimum, plumbing $185 major/$104 minor) read directly from the city's own 2026 fee-schedule PDF. Origin story: John W. North's 1855 Cannon River dam and mills (22 at the industry's peak), the city's pivot to dairy as wheat farming moved west ("Cows, Colleges, and Contentment"), Carleton and St. Olaf, and the September 7, 1876 defeat of the James-Younger gang — the only successful one in the country. Caught and corrected during the self-audit's source-tracing step: an initial neighborhood description and two FAQ answers implied the city's 1978/79 historic district covers surrounding homes and their kitchen/addition work; the district is in fact mapped tightly around the commercial downtown blocks (Division/Water/Third/Fifth Streets per the city's own history), so all three passages were rewritten to state that correctly rather than publish an inflated claim. A soil/water-table sentence was also softened from an implied specific-survey citation to the same general river-bottomland inference this document already uses for Burnsville, Chaska, and Shakopee. No Northfield-specific basement ceiling-height or egress figure is published by the city (unlike Shakopee, Elko New Market, or Burnsville), so none was invented; the page cites the adopted 2020 Minnesota Building Code instead of a fabricated number. |
| 1.25 | 2026-08-23 | Elko New Market complete — third Phase 3 tail city (after Minneapolis and St. Paul), background work carrying no contamination risk. All four services 40.4–43.3%. Distinguishing facts: the city contracts its building inspections to a private company (Municipal Inspections, Inc.) rather than running an in-house department, and electrical is inspected separately by the State of Minnesota — three inspecting parties on one project, a structure not seen in any other queued city. Origin story is a genuine two-town merger: German Catholic New Market (1856) and the Scandinavian Lutheran railroad town of Elko (1902) stayed separate cities for a century before an 84% referendum vote merged them January 1, 2007. Caught and corrected during the self-audit's source-tracing step: the basement handout's ceiling-height soffit exception (6'6") is based on the 2007 MN State Building Code per the document's own notice and conflicts with Shakopee's and Burnsville's current-handout figure of 6'4" for the same exception — per When a source is stale, the disputed number was withheld rather than published as a comparative claim; the corroborated 7-foot base minimum (matching both neighbors) was kept. |
| 1.24 | 2026-08-19 | Phase 1 closed. Plymouth resolved — removed from the E1-control cohort in scripts/seo-baseline.mjs (moved to observe) rather than remediated, per the resolution this document already specified. This was the only remaining arm-composition blocker; the arm is now final at 13 cities. Corrected a discrepancy found during this review: Search_Strategy.md/Search_Strategy_Rules.md described the post-I11 E1 arm as "7 treated vs. 7 control" — the actual composition (E1 + retired-E2 cohorts) is 6 treated / 8 control, 6/7 after Plymouth's removal. Docs corrected to match the work queue rather than the other way around. E2's cohorts were deliberately not merged into E1 here — that would require deciding which additional cities receive real link treatment, a scope decision left open in Search_Strategy.md's Backlog. Cost-band backfill (requirement 4) remains open, blocked on the B1/B6 job-list export, and does not block the freeze. |
| 1.23 | 2026-08-03 | Minneapolis and St. Paul complete — first two Phase 3 tail cities, done ahead of the freeze since tail work carries no contamination risk. All eight pages 33.6–39.8%. Both are big-market cities rather than suburbs: Minneapolis's packet centers on Saint Anthony Falls (the water-power origin of the city, plus the limestone-over-sandstone geology that runs under its river neighborhoods) and a Minneapolis-specific pairing of window-well requirements (8" wood/earth separation and a 2-ft zoning setback) not seen together at any other city; St. Paul's centers on the bluff-top steamboat-landing origin story and a DSI basement-plan checklist that asks for seven named measurements up front, more documentation than any other city's process has required. One fee figure (Minneapolis's 65% plan-review/0.0005 state surcharge) had only been sourced from a WebSearch summary rather than a directly-read document during drafting — caught by the self-audit's source-tracing step before commit, then re-verified by reading the city's own fee-schedule PDF directly. Note per Superseded record: the SEO Findings note that Minneapolis basement-remodeling sits at GSC position 57 and "needs a rebuild, not a packet" describes a ranking problem, unrelated to and unresolved by this content-standard remediation — the two are tracked separately. |
| 1.22 | 2026-08-03 | A manual sentence-level audit (prompted by the user asking whether the newer rules should be checked retroactively) found the gate's real comparison unit is a rendered paragraph, not a sentence, and its repeat report truncates to the top 15 by frequency — together, these hid 4 genuine cross-city duplicate sentences across the 13 arm cities from every prior gate run. All 4 fixed. Documents the finding under Known limits of the instrument and adds an Open item to fix the script itself before Phase 3. |
| 1.21 | 2026-08-03 | Added a Self-audit checklist to the session protocol, tabulating every failure mode a human review has caught in this initiative (meta-commentary, paraphrase-trap near-duplicates, unsourced claims, PDF-summary fabrication, missing phone escape hatches, decorative origin stories) so a same-session check catches known defects instead of a full review re-finding them. Motivation: full reviews are expensive and were mostly re-discovering documented failure modes rather than finding new ones. Reviews remain valuable for genuinely novel defects — the recommendation is periodic spot-checks, not zero review. |
| 1.20 | 2026-08-03 | Inver Grove Heights complete — sixth E1-control city, 13 of 13 non-deferred arm cities now at standard, 52 of 104 pages. All four services 34.2–37.4%. Ceiling height, egress, inspection order, and plan-review turnaround all read directly from the city's own basement-finish PDF handout (WebFetch summary returned nothing usable, read locally instead). Two genuinely distinctive facts: the Pine Bend Impact meteor crater under the Mississippi River bluffs, and a still-unsewered older section (Rich Valley) where the city's own permit process requires an MPCA septic compliance inspection before a bedroom addition — no counterpart anywhere else in the queue. Only Plymouth remains outside the standard, by deliberate deferral rather than incompleteness; Phase 2 (freeze and enroll) is next, pending the Plymouth arm-membership decision and B1/B6 cost-band export. |
| 1.19 | 2026-08-03 | Minnetonka complete — fifth E1-control city, 12 of 13 arm cities, 48 of 104 pages. Third city whose own site was unreachable, and the first time a review caught a defect the gate cannot measure: customer-facing copy narrating the research gap itself ("Minnetonka's own site was not directly reachable to us..."). Correctly withholding a fact and explaining the withholding to a homeowner are different things — only the first is the standard. Added Withholding is silent as point 5 of the unreachable-site method. The same defect was found and fixed in one already-deployed Savage sentence. The fix also surfaced a near-duplicate opening clause between Minnetonka's and Savage's ceiling-height answers — invisible to the exact-match gate since the city names differed — differentiated in the same pass. |
| 1.18 | 2026-08-03 | Savage complete — fourth E2-control city, 11 arm cities, 44 of 104 pages. Second city whose own site was unreachable (Akamai 403 on every path), confirmed as a bot-block rather than a bad URL when the same pages resolved fine in the user's browser. The user then found a working, unblocked LOGIS ePermits URL that supplied real process detail, replacing an initial "site unreachable" hedge — the second time in this initiative a user-supplied lead corrected an assumption made from behind a WAF block. |
| 1.17 | 2026-08-03 | Edina complete — third E2-control city, 10 arm cities, 40 of 104 pages. First city with a real local regulatory layer on top of building code: the Country Club Historic District's Certificate of Appropriateness requirement, scoped carefully to street-facing facades and demolition so the copy doesn't overstate what it covers. No permit fee or turnaround was findable, so neither was invented — consistent with the standing rule. |
| 1.16 | 2026-08-03 | Bloomington complete — second E2-control city, 9 arm cities, 36 of 104 pages. First city written after the v1.13/1.14 phone-escape-hatch rule (applied from the start rather than backfilled) and deliberately built to close the Rosemount siteConditions open item — the terrain paragraph names which land type drains well versus holds a high water table, not just history. |
| 1.15 | 2026-08-02 | Two new Open items from a user review reading Lakeville's four service pages back-to-back: permits.office/permits.process are word-for-word identical across a city's four services by design, which the gate can't see because it only measures across-city differentiation; and service sub-pages read as dense — a layout problem, not a content one, now that pages are running 3,300–3,600 words post-remediation. Neither blocks Phase 1; both are Phase 2+ design/data decisions. |
| 1.14 | 2026-08-02 | Backfilled the v1.13 phone-number escape hatch to the other 7 published arm cities — Burnsville, Richfield, Apple Valley, Chaska, Prior Lake, Lakeville, Eagan — after an audit found only Rosemount had it. Scope decided: once per city in permits.office, not duplicated inside FAQ answers. Every number was re-verified against the city's live site before the pointer was added. |
| 1.13 | 2026-08-02 | Added give a published phone number an escape hatch to How to research a city — any non-Honey-Doers phone number now closes with a pointer to its source page, so a stale number still leaves a working path. Applied to Rosemount's permits.office, the model case. |
| 1.12 | 2026-08-02 | The v1.11 rule produced a false positive within the hour: three Rosemount claims flagged as unsourced were all published verbatim by the city, on pages the review had not found. Adds "I could not find it" is not "it is not there" — record source URLs while writing, follow pointers before flagging, and weigh deleting a true fact as heavily as publishing a false one. |
| 1.11 | 2026-08-02 | Broadened v1.9's fabrication rule from PDF figures to every published claim must trace to a source you read, after a review of Rosemount found three unsourced process claims — a notice period, a review turnaround, and a phone extension — in work whose dimensional figures were all exact. Added a claim-trace step to the session close and an Open items section. |
| 1.10 | 2026-08-02 | Rosemount complete — first E2-control city, 8 arm cities, 32 of 104 pages. A WebSearch summary (not a PDF fetch) fabricated soil-geology specifics; discarded in favor of the city's comprehensive-plan PDF read directly and its own basement-permit page. The fabrication rule from v1.9 held on a second, different failure mode — see the Rosemount row in Delivered so far. |
| 1.9 | 2026-08-02 | Eagan complete — 7 arm cities, 28 of 104 pages. Added Verify figures against the document, not the summary after a PDF fetch summary returned invented setback and lot-coverage numbers. |
| 1.8 | 2026-08-02 | Lakeville complete — 6 arm cities, 24 of 104 pages. Added A passing score is not the standard after the one city that already passed proved to be the one most in need of rewriting. |
| 1.7 | 2026-08-02 | Prior Lake complete — 5 arm cities, 21 of 104 pages. Added When the city's own site is unreachable, the third source-failure mode after thin and stale. |
| 1.6 | 2026-08-02 | Added Re-running this later — decay rates per element and the cheap re-verification path, since this is an annual-or-sooner exercise rather than a one-off. Recorded a second E1 arm imbalance: three of four E1-control cities are opportunistic geography while every treated city is priority. Plymouth deferred as non-south-metro. |
| 1.5 | 2026-08-02 | Directions updated from four completed cities. Records the gate's exact-match blind spot — paraphrase passes it — and what to do about it; four FAQs as the working number and which to replace; the origin-story heuristic for site conditions; name the office not the person; kitchen as the service that scores lowest. Comparison table extended to all four cities. |
| 1.4 | 2026-08-02 | Chaska complete — the first control city, treated identically to the treated arm. 4 of 14 arm cities, 17 of 104 pages. Added When a source is stale after Chaska's flagship handout turned out to be 18 years old and to contradict current code. |
| 1.3 | 2026-08-02 | Apple Valley complete — 3 of 14 arm cities, 13 of 104 pages. Added How to research a city and Where the content goes, so the operating method is in the document rather than in whoever ran the last session. |
| 1.2 | 2026-08-02 | Brought to current state. Burnsville and Richfield complete and deployed — 9 of 104 pages passing, 2 of 14 arm cities. Recorded the committed-is-not-shipped failure and added a live-URL check to the session protocol. Threshold calibration upgraded from prediction to result. Reviews made city-aware and 9 of 23 attributed; the per-city project-count idea proposed and withdrawn the same day. |
| 1.1 | 2026-08-02 | Restructured as a forward-facing plan (problem → opportunity → strategy → action plan → measurement → outcomes). Substantive: acceptance gate corrected and baseline restated (see below); requirements 4–5 given template slots; threshold re-derived and held; word budget added; target-state table added. |
| 1.0 | 2026-08-01 | Initiative opened. Findings from the first uniqueness measurement, the six-point standard, phasing, work queue, concurrency rules, session protocol. |
This is a recurring exercise, so the document now says how to repeat it cheaply. Re-running this later gives a decay rate per element — origin story and site conditions never decay, permitting authority decays fast and unpredictably — and makes the comparison table the diff target, so a re-run re-checks cells rather than re-reading handouts. It also names the thing a re-run should look for that no number will show: drift toward a common voice, invisible to an exact-match gate.
A second arm imbalance was found in E1, and it is not fixable by remediation. Three of the four E1-control cities are the three the priority decision classes as opportunistic; every E1-treated city is priority or secondary. Priority geography correlates with customer density, link equity, and GBP proximity, so the arms differ on a dimension remediation cannot level — biased toward a false positive, from a second source. Rebuilding the arms from priority-comparable south-metro cities is now a Phase 2 decision alongside E2's.
Plymouth deferred as northwest metro and non-priority. Sound editorially, unsound experimentally while it remains inside a control arm — which is the argument for removing it from the arm rather than remediating it.
Four cities in, the method works and the pages are genuinely different from one another. The additions are the things that were being carried in a single session's head rather than on the page.
The gate has a blind spot and it is now written down. city-page-uniqueness.mjs compares exact
sentences. Paraphrase passes it. A future session could write one set of sentences, swap the nouns
across four cities, score in the forties on every page, and reproduce the exact defect I11 exists to
fix — with the acceptance check reporting success. Recorded under
Known limits of the instrument, together with the only defence
available, which is reading a new city against a finished one before committing.
Working numbers replace floors. Four city FAQs per service rather than the required three, with a rule for which to replace. Kitchen scores lowest of the four services every time — 37.6 to 38.9 against 42.7 to 46.8 on basement — so it is the page to check first.
The origin-story heuristic. Every city has one economic or geological fact that explains its housing, and it has been the strongest material every time. Finding it is the research step that matters most.
The method is now written down. v1.2 described what to produce and how it is measured, but not how to find the material — a reader could have followed it precisely and still spent an hour rediscovering that municipal PDFs need reading locally, or not known that basement-finish handouts are where the good facts live. Two new subsections close that: How to research a city and Where the content goes. The first carries a running comparison table of what actually differs between cities — ceiling minimums, egress and window wells, inspection order and count, permitting authority, plan format, notice periods — which is both the research checklist and the source of the comparative sentences that make these pages worth reading.
Apple Valley completed, 38.0–46.0%, the third arm city and the first where existing content was extended rather than written from the template.
Two arm cities are complete and live, and both cleared the bar by 7+ points without requirement 4 — Burnsville 37.6–42.7%, Richfield 38.1–44.6%. The calibration that justified the 30% threshold in v1.1 was a prediction from a synthetic fixture; it is now a result from real pages.
The research produces materially different pages. Richfield permits a 6-foot-4 basement ceiling against Burnsville's 7 feet, exempts pre-June-2009 homes from basement insulation requirements, and stopped issuing electrical permits in November 2024. This was the open question behind the whole standard — whether municipal detail differentiates or just reworders — and the answer is that it differentiates.
A process defect was found and closed. Phase 0 was recorded as done on 2026-08-01 while the fix sat unpushed; the placeholder it removed was live for another day. See Committed is not shipped. Nothing about the standard changed — what changed is that "done" now requires a served page.
Reviews were made city-aware, 9 of 23 testimonials attributed from job records, and a per-city project-count proof block was proposed and withdrawn the same day for failing the decision test. Both are recorded in Reviews.
The acceptance gate was measuring the wrong thing. city-page-uniqueness.mjs defined
city-unique as "not present on every one of the 26 cities." The intent was "differentiated"; the
effect was "absent from at least one page." Project-carousel cards repeat across most cities but not
all 26 — a Lakeville kitchen project renders on the Apple Valley and Burnsville pages too — so they
skipped the intersection and scored as local differentiation.
Decomposing the v1.0 score into sentences on exactly one city versus some-but-not-all:
| Service | Avg page | v1.0 score | Truly city-unique | Carousel text miscounted |
|---|---|---|---|---|
| Kitchen | 2,476 w | 847 (34.2%) | 152 (6.1%) | 695 (28.1%) |
| Bathroom | 2,399 w | 771 (32.1%) | 157 (6.6%) | 614 (25.6%) |
| Basement | 2,321 w | 192 (8.3%) | 143 (6.2%) | 49 (2.1%) |
| Whole-home | 2,401 w | 251 (10.5%) | 144 (6.0%) | 107 (4.4%) |
Every service carries the same ~145 words of real city-specific content. Kitchen and bathroom "passed" on project inventory, not on writing.
The standard had two requirements that could not be met. Requirements 4 (city cost band) and 5
(permit/inspection specifics) had no data field and no render — the layout varied only
housingDescription, neighborhoods, and cityFaqOverrides. v1.0's claim that "both mechanisms
already exist… nothing needs building" was true of requirements 2–3 and false of 4–5. Slots shipped
2026-08-02 as a site-wide common shock:
| Field | On | Renders |
|---|---|---|
permits: { office, process, byService } |
LocationData |
"Permits and Inspections for [Service] in [City]" |
siteConditions |
LocationData |
"Building conditions in [City]" |
costBands: { [service]: { range, note } } |
LocationData |
Replaces the national range in the cost callout |
All three are optional and unpopulated, so no page output changed.
What the correction did not touch. The arm-imbalance argument rests on neighborhood-content coverage (3 treated / 0 control), which was never measured by the ratio. It is unaffected, and it is the reason I11 gates the freeze. The I2a qualification also holds and strengthens — the boilerplate share is ~94%, not the ~90% first stated.
Retained for comparison. Do not cite these figures as current.
| Service | Median city-unique (v1.0) | Corrected (v1.1) |
|---|---|---|
| Bathroom | 34.3% | 5.0% |
| Kitchen | 32.4% | 4.8% |
| Whole-home | 10.0% | 5.5% |
| Basement | 7.7% | 5.5% |
The v1.0 work queue additionally ranked Phase 1 cities on a basement-only column running 5.5%–10.8%. That column is withdrawn: on the corrected measure the spread across unremediated cities is under one point, so it was ranking cities on noise.
| v1.0 claim | Status |
|---|---|
| "Bathroom and kitchen clear the content bar; whole-home and basement fail" | Withdrawn. All four sit at ~5%. The split was project inventory, not writing. |
| "The service the entire strategy bets on has the weakest differentiation of the four" | Withdrawn. Basement is average, not worst. |
| "Result: 46 of 104 pages now pass acceptance, up from 0" (Phase 0) | Withdrawn. Corrected to 1 of 104. The placeholder fix itself stands. |
| "Acceptance threshold ≥30%, matching what kitchen and bathroom already achieve — a demonstrated bar" | Reasoning withdrawn, number retained. Nothing achieved it. Re-derived on a word-budget calibration plus Lakeville bathroom at 31.7%. |
| "Both mechanisms already exist and are wired into the layout… nothing needs building" | Withdrawn in part. True of requirements 2–3, false of 4–5, which had no field and no render until 2026-08-02. |
| "Start with Burnsville — worst score on the site (5.5%)" | Rationale withdrawn, decision retained. Every unremediated city is within a point of every other. Burnsville still leads on arm membership, priority geography, and a calibrated word budget. |