← Apps · Wiring map

Improvement Blueprint

The living proposal. Built page by page from an honest read of the whole suite — what exists, what's dead weight, what to build next. Grows every pass.

v7 · 2026-08-06 · full-suite census: 49 pages walked · per-page detail: blueprint-annex.html · companion: wiring-map.html (flows, holes, APIs, perf)

0 · What this overnight run produced — 2026-08-06

Morning ruling — §7 is HELD as questions (spine row 259)

Alan's steer, morning 2026-08-06: the graveyard and orphan items are QUESTIONS, not decisions. Rule adopted: a "zero inbound links" finding from the automated walk is a HYPOTHESIS — before ANY page is archived, retired, promoted, or rewired, it gets a human click-through: walk it like a user and confirm nothing still presents itself as wired (a redirect, a switcher entry, a hardcoded ref, a path the graph missed can all still point at it). Everything in §7 stays drafted-only until Alan eyeballs and click-confirms. And one canonical question is now formally OPEN: which VERSION of the plant library is final — the walk observed at least three candidates (the plant_master table behind /api/plant-library; a separate plant_library table that home.html's count strip reads; the old external Netlify plant DB) and that choice determines what gets wired into everything else. It goes to whoever built the library and the report pages — not decided here. SITREP on the spine; the front is the active lane, the graveyard is parked.

Shipped — live on production now

The four 404 front-page tiles fixed (today / new-lead / properties / procurement were branch-only) · sun-sweep's dead nav link off the retired hub · rentals rates date-stamped with a confirm-by-phone note · the status board bannered as a dated hand-authored snapshot. Plus the walk itself: this blueprint, the 51-page annex, and the wiring map — every page of the suite read cold, receipted on the spine.

Staged — exact plans and decisions waiting on your word

P5-1 public-site hero fix (§3 — a two-minute commit) · review.html retire-vs-fill (§4 — recommend retire-from-nav now) · library pagination (§5) · map ?mid= parameterization (§6) · files.html seed merge + lost-adds recovery sweep (§6) · the §7 items — plan.html / workspace.html / proposal.html / the 10-page graveyard — are HELD as questions per the morning ruling (row 259), pending Alan's click-through. Each non-held item is one small reversible commit with the exact path written out.

Only you can supply

The Google review link for rev.html's GOOGLE_REVIEW_URL — the single highest-value review fix in the suite (completes an already-working flow) · the three review-platform profile URLs if you choose fill over retire (§4) · the launcher call — index.html (the hub every device opens) still doesn't link home.html, so the new front door stays invisible until the one-line swap or a tile · the standing gates: URT-DATA-01 (unlocks persistence + the staged P1 migration), the E2E sweep roll, and the doc-wall share · NEW: the plant-library version check-in — which library is canonical, answered with its builders (§0 ruling card).

1 · Page census — the honest inventory

PageStateThe one thing
urth-reportSOLIDTHE live client report — the standard the rest measure against
brain (registry/est/jobs)SOLIDEngine half real; estimate persists nowhere; 3 disagreeing schedules
site-marksSOLIDGreat field tool whose data nothing reads; saves photos on every tap
library · designer · sun-sweep · sun3d · 3d · massing · client-map · client-fileSOLIDLive tools; library unpaginated (plan in §5); sun-sweep's hub link fixed 2026-08-06; massing reachable only via report (held question, §7)
suppliers · rentals · files · reviews-inbox · rev · doc + 4 reference guidesSOLIDrev is the real review capture; rentals rates date-stamped 2026-08-06; files seed-loss = the key-bump ritual (corrected read + plan in §6)
home · today · new-lead · properties · procurement · wiring-mapSOLIDNew front layer — 404s fixed this pass (were branch-only)
statusHALFHand-drawn "live/planned" claims that drift silently — banner + date added 2026-08-06; still not generated
action-planHALFDevice-local (localStorage), June-era seed, no sync, no consumers
proposalHALFClient deliverable running on the RETIRED P01–P13 demo data — held question, §7 (row 259)
workspaceORPHANFully built all-in-one — held question, §7 (row 259)
planORPHANThe richest plan surface (flags + standing requests) — held question, §7 (row 259)
ingestSTUBOne hardcoded address, base64 photos in localStorage, reaches nothing
card · tree-sourcesSTUBcard bypassed by .vcf redirects; tree-sources = useful diagnostic, invisible
report · property-reportLEGACYReport v1/v2 lineage — walker's read: superseded by urth-report (held, §7)
reports (supplier sourcing)LEGACY37KB, "updated automatically" is false — walker's read: superseded by suppliers (held, §7)
hub · brochure_v2 · reviewLEGACYhub redirected; brochure on demo data; review still in switcher with 4 PLACEHOLDER links live — decision in §4
exec-summary · orlando ×3 · stewardLEGACYHand-authored June snapshots presenting stale claims as current (held, §7)

Full per-page blocks — role, links, data, state, top issue for all 51 surfaces — live in the annex.

Graph health: no missing-file links after this run's 404 fix · functional broken buttons: review.html's four placeholder URLs (LawnStarter / Angi / Yelp / the Zelle tip link — all four confirmed live on main, see §4) + rev.html's empty GOOGLE_REVIEW_URL · sun-sweep's hub link fixed 2026-08-06 · index.html (the PWA home) does not link home.html yet.

2 · The proposal — ranked

R1 · Wire the field walk (the #1 dead-end)

site_marks → planting engine (existing-plants + verified per-pin sun) → estimate. The walk is the business's unfair advantage and today it reaches nothing.

R2 · One schedule, one truth on brain.html

Retire buildBudget()'s hardcoded mix; the engine's schedule (with its why/relaxations finally rendered) becomes the only plant list; scope table consumes it. plan.html already renders exactly this — see §7.

R3 · Persistence (gated on URT-DATA-01)

Estimates, leads, designer looks, action plan — everything now dying in DOM/localStorage lands in the staged P1 tables the moment the boundary call is made.

R4 · Silent-fallback flags

canopy_missing, light-ladder degradation, geocode-failed, overlay-failed — every silent fallback becomes a visible badge. The Q-ladder's geometry_source/missing_info fields are the destination.

R5 · Price truth

Suppliers table → price book (sheet import, source+date per rate) → engine + estimate. Kills the $10-default and the 1.9 hardcoded markup.

R6 · Perf: route through the proxy that already exists

designer/site-marks/brain via /api/c; sunat into precompute; stop shipping every photo on every tap; paginate library (exact plan in §5). One pattern, five wins.

R7 · Graveyard sweep (10 pages)

HELD as questions per the morning ruling (row 259) — the full card with per-page walker's-read lines stays drafted in §7; nothing moves until Alan's click-through review.

R8 · Small honest fixes

review.html placeholders — decision drafted in §4 · GOOGLE_REVIEW_URL · sun-sweep's hub link fixed 2026-08-06 · My Maps hardcoded mid — plan in §6 · date-stamp rentals rates done 2026-08-06 + status board done 2026-08-06 · files.html seed data loss — corrected read + plan in §6.

3 · P5-1 — public-site hero, the exact fix

The measured defect

On urthlandscaping.com at 390px wide, the page's only <h1> sits >1,100px down — a visitor sees a photo and no words until they scroll past a full screen and a half.

Root cause (read from source: urth-website/index.html)

Two rules stack: .hero{height:100vh;height:100dvh} makes screen one the photo alone — zero text — and .intro{min-height:100vh; justify-content:center} then vertically centers the H1 in screen two. Net: the H1's top lands ≈1.4 viewports down (~1,150–1,250px at 390×844). The measurement matches the geometry exactly.

The fix — one additive @media block, phones only, rollback = delete it

Append at the end of the existing <style>:

@media (max-width:760px){
  .hero{ height:42vh; height:42dvh; min-height:280px; }
  .intro{ min-height:0; justify-content:flex-start; padding:40px 6vw 64px; }
}

Result at 390×844: the photo ends ≈354px, the eyebrow lands ≈395px, and the H1's top sits ≈430px — brand, tagline, and the Request-a-Quote button all on screen one, even with Safari's collapsed 664px viewport. Desktop is untouched (a full-bleed hero is a defensible desktop choice — shortening it there too is Alan's call). Spec only this pass — the walk is read-only; landing it is a two-minute copy-level commit on the urth-website repo when Alan nods.

4 · R8 — review.html: retire vs fill (Alan's call, drafted here)

What the page actually is

A genuinely good copy-once, post-everywhere flow: the client writes their review once, taps a platform button, the page copies their words to the clipboard and opens that platform — paste & post. Fully built, nice UX. But every destination is a placeholder on main: YOUR_LAWNSTARTER_PROFILE, YOUR_ANGI_ID, YOUR_YELP_ID, and the gold Tip-the-team button's YOUR_ZELLE_OR_CHASE_LINK (the earlier tip-link removal landed on the branch, not main — all four are live broken buttons today). A fifth button, Google, is honestly disabled as "coming soon". And the app switcher on every page carries this as "Reviews" — one tap from anywhere to four buttons that go nowhere.

Option A — retire from nav (recommended now)

One line in apps.js: swap the switcher entry {n:'Reviews', u:'review.html'}{n:'Reviews inbox', u:'reviews-inbox.html'}. Nav then reaches the review surface that actually works — the internal inbox reading urth_reviews, with capture staying rev.html via the NFC tap redirect. review.html is NOT deleted: still reachable by URL, ready to return.

What's lost while retired: the only surface that pushes happy clients to public platforms (LawnStarter/Angi/Yelp). Honestly: that flow has never worked — the buttons are placeholders — so nothing working is lost, and four broken buttons leave the daily nav. Rollback = revert the one line.

Option B — fill (needs three values only Alan can supply)

The page works the moment these are real: (1) LawnStarter profile URL → the go('https://www.lawnstarter.com/…') button · (2) Angi company ID → angi.com/companyprofile/… · (3) Yelp business ID → yelp.com/writeareview/biz/… · plus either a real payment link for the tip button or deleting the tip block.

The precondition no edit can route around: those profiles have to exist. If URTH has no LawnStarter/Angi/Yelp business profile yet, there is nothing to fill — creating them is the actual task, and it's a business task, not a code one.

Recommendation — A now, B when the profiles exist

Retire the tile today (nav shows only working things; the inbox becomes the visible surface), keep review.html in the wings, and restore it filled when the platform profiles are real. Separately and regardless of A/B: filling rev.html's empty GOOGLE_REVIEW_URL is the single highest-value review fix in the suite — it completes an already-working capture flow (tap → stars → 5★ → straight to Google) and needs only the Google review link. Nothing was changed this pass — no placeholder filled, no file deleted, no switcher edit; both options are one small reversible commit on your word.

5 · P4-5 — library pagination, the exact plan (staged)

What makes it slow today (read from source)

render() rebuilds the ENTIRE grid on every change: grid.innerHTML = rows.map(card).join('') — all ~454 cards at once on load, and again on every keystroke in the search box (q.oninput = render). Images are lazy but the DOM build/layout of 454 cards is the cost. Everything else — spot logic, ten filters, modal — is sound.

The plan — same 20-per-load pattern procurement.html already ships

(1) Keep the filtered result in a module var; render only FILTERED.slice(0, shown) — first page 24 cards (even rows at 2/3/4 columns; identical mechanism to procurement's 20). (2) A "Load more" button after the grid appends the next slice via insertAdjacentHTML — no full rebuild. (3) ANY filter/segment/search change resets to page one (procurement's applyFilter rule). (4) The count line becomes "showing 24 of 213 matches · 454 in library"; the survive/thrive read-line keeps computing over the FULL dataset — its truth doesn't change. (5) 150ms debounce on the search input — kills the per-keystroke rebuild. Untouched: card markup, modal (_i indexing unaffected — it indexes DATA, not the rendered slice), spot logic, filters, API call, offline fallback. First-paint DOM drops ~95% (454 → 24 cards). Rollback = revert one commit.

Status: STAGED — no code shipped. It's a surgical edit inside the live library page (not an additive new file like the shell pages), so it ships on Alan's nod, not on an overnight pass.

6 · R8 staged plans — map.html ?mid= and files.html seed merge

map.html — parameterize the My Maps document (staged)

Defect (from source): the Google My Maps document id mid=1bQu0vtv0AXkjdjZ3hKDlTyOyk7FJJTY is hardcoded in THREE places — the map-list link, the embed-header "Open in Google" link, and the iframe src. Only that one My Map can ever show; any other document Alan builds is unreachable from the suite.

The plan: give the three elements ids (mymapsLink, mymapsOpen, mymapsFrame) and add a six-line script before </body>: var MID = new URLSearchParams(location.search).get('mid') || '1bQu0vtv0AXkjdjZ3hKDlTyOyk7FJJTY'; then set both hrefs to /maps/d/viewer?mid=+MID and the iframe to /maps/d/embed?mid=+MID. Zero behavior change with no param; any other My Map becomes reachable as map.html?mid=<id>. End-state when there's more than one document: a small named list of maps rendered as rows (and DB-driven under R3). Rollback = revert. STAGED — no code shipped (surgical edit in a live page).

files.html — seed merge without data loss (staged, with a corrected read)

Correction to the census: the current code is better than the walk's one-liner claimed. A migrate() already runs on every load and appends missing seed items non-destructively — user adds survive seed edits as long as the storage key never changes. The REAL loss mechanism is the key-bump ritual itself: the key is urth_files_v29 — twenty-nine bumps of history — and every bump orphans everything under the old key, user adds included. Second defect: migrate() matches seed items by URL, so a seed item whose URL changes needs a hand-written remap (the nursery-capture pdf→xlsx→Sheet special-case in the code is the scar tissue).

The plan: (1) Freeze the key — urth_files_v29 is permanent, commented "never bump; migrate() owns seed changes". (2) Stable id (slug) on every SEED item; migrate() upserts by id — new ids appended, changed seed fields (url/kind/name) updated in place, user-added items (no id) never touched; the URL-matching hack dies. (3) Optional RETIRED=[ids] list so removed seed items can actually leave the list. (4) One-time recovery sweep: scan localStorage for older urth_files_v* keys and merge in any user-added items not already present — the adds lost to past bumps are likely STILL SITTING in localStorage under old keys on Alan's devices, recoverable for free. (5) End-state under R3: the list belongs in a table once URT-DATA-01 opens persistence — localStorage stays per-device forever otherwise. No deletes anywhere; rollback = revert. STAGED — no code shipped.

7 · R7 — orphans, graveyard, and duplicates (HELD as questions — row 259)

Morning ruling (spine 259): everything in this section is a HYPOTHESIS from the automated link-graph walk, held pending a human click-through — nothing moves, nothing archives, nothing rewires until Alan walks each page like a user and confirms nothing still points at it (redirects, switcher entries, hardcoded refs, paths the graph missed). The recommendations below stand as the walker's read, not verdicts.

plan.html — walker's read: PROMOTE (held)

What it uniquely does (read cold): the only surface in the suite that renders the planting engine's REASONING — the full schedule with a per-plant "why" and evidence badges (native / wildlife / UF-IFAS / researched), the engine's logged relaxations ("design budget relaxed to avoid a bare spot — which rung, where"), the hardiness-data caveat, the standing requests from the Designer session, a how-it's-built spec, and a raw-JSON link. brain.html and urth-report embed the plan image; none of this reasoning is visible anywhere else. It is the live prototype of proposal R2 ("the engine's schedule with its why/relaxations finally rendered").

Promote path (one small commit): a switcher entry ("Planting Plan") or a "Full plan + reasoning →" link from brain's planting section and urth-report's plan block, passing ?id= (which also retires its hardcoded default UUID in practice). Retire path: lose the only window into why the engine chose what it chose — not recommended. HELD — nothing shipped; awaits click-through.

workspace.html — walker's read: RETIRE after harvesting two organs (held)

What it is (read cold): a fully built four-section all-in-one — Site Analysis (sun/height/slope/scorch images + live numbers), Design (designer/library links + inline plan), Deliverables (report, 3D, CAD), Client (a client_record editor with proposal log). Three of the four are weaker duplicates: Site Analysis ⊂ urth-report; Design duplicates designer + plan.html (without the reasoning); Client duplicates client-file.html — and dangerously, it is a second write path to urth_properties.client_record (both upsert with merge-duplicates), so edits made here and there can silently overwrite each other.

The two organs to harvest before any archive: the Deliverables section is the ONLY UI door to the CAD exports — the combined blueprint SVG (/api/blueprint?sun=1) and the layered /api/dxf download appear nowhere else in the suite. HELD — nothing shipped; awaits click-through.

massing.html — reachability note (held)

Live Three.js massing tool; the link-graph walk found only urth-report’s link inbound — a hypothesis pending click-through. Candidate one-liner if confirmed: add it to the apps.js switcher or link it from sun3d (the sibling 3D view).

proposal.html — walker's read: retire the brain link (held)

The defect: a client-facing proposal page still linked from brain.html, but running on the RETIRED data.js P01–P13 demo system with a hardcoded app.urthlandscaping.com API base — registry UUIDs won't resolve, so opening it from a real property serves demo-or-broken content to exactly the audience that must never see it.

Retire path (one line): remove the brain.html link; urth-report.html IS the client deliverable today. Rebuild path: port to registry UUIDs + live pricing — duplicates the Proposal object already staged in the P1 record model (the Q1/Q2/Q3 ladder). HELD — nothing shipped; awaits click-through.

The graveyard — ten candidates, one walker's-read line each (HELD)

report.html — graph: zero inbound; superseded twice over (→ property-report → urth-report) · property-report.html — graph: zero inbound; superseded by urth-report · reports.html — graph: zero inbound; "updated automatically" claim is false; suppliers.html absorbed its notes · brochure_v2.html — graph: zero inbound; retired P01–P13 demo data · exec-summary.html — graph: zero inbound; stale snapshot · orlando ×3 — graph: zero inbound; stale June snapshots · steward.html — graph: zero inbound; overlaps action-plan · hub.html — redirected by vercel.json; last inbound fixed this run.

Per the ruling: every "zero inbound" line above is a HYPOTHESIS. Before any page moves, it gets a click-through — walked like a user, checked for anything (redirect, switcher entry, hardcoded ref, missed path) that still presents it as wired. The mechanic if confirmed remains: one reversible commit moving confirmed pages into /archive/; git history preserves everything either way. HELD — nothing moved.

Decide, don't archive (also held): ingest.html — fold its pins/zones/photos idea into site-marks, then delete the prototype · card.html — wire the .vcf subdomains here or delete · tree-sources.html — keep; promote as a dev diagnostic via the wiring map.

doc / planting-report duplication (note only)

The same planting-engine explainer lives in TWO places: planting-report.html (a page, linked from home + index + hub) and docs-data.js's "planting-how-it-works" entry (served via doc.html?f=, linked from files.html). Two copies drift. Walker's read: the page is canonical — it has three hub inbound links; the docs-data.js entry becomes a one-line pointer to the page. One small commit when nodded; note only.

8 · Carried findings

The deep plumbing read lives at wiring-map.html: per-surface wiring + 8 dead-ends (#wiring) · 11 client-trust decision holes, worst three = treetop-sun fallback, all-full-sun ladder collapse, whole-parcel canopy-off (#holes) · external-API audit incl. Perenual-isn't-real (#apis) · slow-load top 5 (#perf). This blueprint supersedes nothing there; it adds the page census + the ranked build order.