← Home

The wiring, cold

How the pieces actually talk today — read from the source, not the intention. Built in the same breath, reviewed for the seams. Internal document.

Wiring & dead-endsDecisions a client must trustExternal APIsWhy deep pages drag

1 · Wiring — what flows, what dead-ends

Site Marks (field walk)

reads: blueprint SVG · per-pin sun (/api/sunat) · canopy warm (/api/terrain-mine) · plant library · Plant.id photo IDs

writes: site_marks (one row per property, every pin + photo) + localStorage

DEAD-END: nothing reads site_marks

Zero consumers in the repo. Every field capture — species IDs, health, quantities, drawn bed areas, per-pin sun — never reaches the plan, the estimate, or the report. The planting engine's own spec admits it: "existingPlants (site-marks) — designed, not yet wired."

Plant Library (/api/plant-library)

reads: plant_master → plant_library view + overlay JSON

feeds: library.html · designer.html · site-marks enrich · planting engine — the one hub that IS wired

Planting Plan (/api/planting-plan)

reads: LaunchPad layers · grid squares (per-bed light) · plant library

writes: SVG plan + JSON schedule → brain.html schedule table, report, precompute

DEAD-END: the engine's reasoning is computed, then dropped

The JSON's why, relaxations, palette justifications render nowhere — the client sees rows/qty/$ with none of the defensibility the engine already wrote.

Estimate (brain.html)

reads: static data.js price book + hardcoded PRICEBOOK + LaunchPad areas

writes: DOM only. commit() renders "Committed ✓" into innerHTML. Reload = gone.

DEAD-END: three plant schedules, one page, zero agreement

buildBudget() hardcodes its own mix (Foxtail/Hibiscus/Ixora…), renderPlan() shows the engine's schedule, autoScope() builds a third service-level scope — they can disagree on the same screen and none persists.

Procurement / Suppliers

reads: suppliers (86) + static catalog

writes: nothing — pure display

DEAD-END: supplier data never touches a price

The engine prices from a 28-species hardcoded SUPPLIER map + size-class defaults (XL:180 L:45 M:14 S:10 GC:8); the estimate prices from static data.js. The 86-supplier table — the real market — reaches no dollar figure a client sees.

DEAD-END: designer selections die on the device

Saved looks/palettes go to localStorage (urthFullLooks/urthPalettes) and are read by nothing — not the engine's theme param, not the estimate.

Also orphaned: window.__plantTotal (written once, read never) · site-marks libFlag ("flag for the library") saved into marks, consumed by no process.

2 · Decisions a client must trust — the hole list

Pin sun can silently be treetop sun (api/sunat.js)

Canopy lookup has a 9s timeout; on failure the code keeps the flux-only band — full sun reported under the avocado, the exact bug the file exists to fix. And byte-255 canopy (county-sees-vegetation-no-height, i.e. palms) is treated as OPEN sky.

Fix: hard canopy_missing flag on the pin, never a silent fallback.

Sun bands are relative to the lot, not to the sun (sunat.js)

pct_open = kwh / brightest-pixel, banded at 70/45. One bright roof or pool deck in the radius depresses every reading on the property. Thresholds (70/45, canopy 8ft/15ft) are invented constants.

The engine's light ladder degrades to "everything is full sun" (planting-plan.js)

grid squares → beds → binary solar-map → nothing: two silent catch{}s mean an empty light map plants every bed as full sun, labeled "mixed light." No error reaches the SVG or the schedule.

survivesLight() barely gates (planting-plan.js)

Part-sun beds accept FULL, PART, or SHADE species — the filter passes everything; null scorch fields pass the bake check; one species (firebush) is patched by name inside scoring.

Whole-property light can be crown-top light (grid.js)

If terrain-mine fails, CAN=null → canopy 0 everywhere → every square marked ground_sun_valid — the moat feature quietly off for the whole parcel, feeding plan + report. Palms (255-cells) never block the ray-trace despite the comment saying they should.

Quantities are massaged after the math (planting-plan.js)

Counts under 2 are deleted; even counts get +1 for the odd-number design rule — the priced qty is not the computed qty, and nothing says so.

Budget card is decorative math (brain.html)

buildSchedule(): palms = bed/450 (min 3), fixed species mix, $10 default price; PRICEBOOK hardcodes labor 45/32/24, markup 1.9; "Pursue / Clarify" verdict from a hardcoded 0.8 factor. None of it reads real supplier prices — the P1 price-book gate exists for exactly this.

One failed fetch un-excludes banned plants (api/plant-library.js)

applyOverlay() returns data unchanged if urth-overlay.json fails to load — excluded/do-not-use species silently re-enter plans and the designer for up to 6h of cache.

Genus roulette on unknown species (site-marks)

A two-word species not in the library matches the first same-genus entry in object order and is presented "✓ In our library" with that other species' sun/toxicity/spacing.

Drawn-area plant counts hinge on label↔polygon pairing (site-marks)

SCALE = totalFt / sumTop pairs ALL "N ft" labels with the N largest polygons; any mismatch skews every mass-planting quantity and sq-ft figure.

Same spot, two answers (sunat vs grid)

sunat requests LOW 0.5m imagery; grid tries HIGH 0.1m first — a pin in Site Marks and the same square in the plan can band differently, and no surface says which basis it used.

3 · External APIs — what each needs to be trustworthy

APIWeakness foundThe one thing it needs
LaunchPad layersLayer names matched by exact string ("Beds/Natural", "Lawn"); a renamed/missing layer silently zeroes that surfaceMeasurement date + layer checklist surfaced per property
Google Solar fluxsunat fixed at LOW 0.5m vs grid HIGH 0.1m — divergent bands; hourly nulls fall to lot-relative flux cutOne shared cached flux artifact per property, imagery quality + date shown
terrain-mine (canopy)The input everything trusts; fails silent in both consumers (timeout → treetop light; catch{} → whole parcel open-sky)Hard canopy_missing flag propagated into every derived band
Plant.id v3Best-behaved: confidence, close-call, candidates reach the UI. But genus_only is emitted and ignored on save; N-America filter can exclude planted Old-World ornamentalsPersist the ID + the human's candidate choice somewhere that gets read
Google geocodeFailure silently defaults to Miami center — job-feed distances then wrong with no warningFail loud: "geocode failed" is an error, not a hint
Image hotlinks (Wikimedia/iNat/UF)onerror hides image; licensing already flaggedSelf-host (already on the roadmap)
Supabase (public keys)urth_cache RLS off + public write — the shipped key can poison every cached report componentFLAGGED, not fixed here — rides the URT-DATA-01 gate
PerenualReferenced in old docs; not found anywhere in the repoStrike it from the mental model

4 · Why deep pages drag (top 5, ranked)

1 · Raw grid-squares JSON on the client

designer.html fetches /api/grid?format=json&squares=1 directly — up to ~200k squares of multi-MB JSON parsed on a phone to compute six tallies. On cache miss the server re-runs LaunchPad + 4 GeoTIFFs + terrain-mine. sun3d routes via the cache proxy; designer and the engine don't.

2 · Site-marks per-pin sun fan-out

Every un-versioned pin fires its own /api/sunat; each cold call pulls LaunchPad + flux GeoTIFF + full canopy JSON. sunat is not in precompute and not in the cache proxy's allow-list — it can never be pre-warmed.

3 · Site-marks saves the world on every tap

save() POSTs the entire marks array including every base64 photo (~180KB each) on every field change/drag, and re-downloads it whole on open. A walked property is a multi-MB upsert per tap.

4 · Site-marks open sequence

Two blueprint SVGs (sun SVG can exceed 1MB), canopy warm, full plant-library payload, and duplicate ref_data fetches (species + species_img each fetched twice by page + fix layer).

5 · Full-catalog render, everywhere

All ~454 library rows with every column go to three pages; library.html renders all cards in one innerHTML pass; brain.html loads ~800KB of Leaflet/turf regardless of tab. (P4-5 pagination card covers the render half.)

The fix that pays five times: route designer/site-marks/brain through the existing /api/c cache proxy + add sunat to precompute — the plumbing already exists, three pages just bypass it.

Cold review · read from source on main · no schema, security, or data touched. Receipts: spine rows (scintilla-live).