Skip to content

Session handover — 2026-07-13

WCP now renders ZERO loud-fail placeholders on all 24 pages (276 → 39 → 0). This closes the last of the asset debt the pt5 reconciliation surfaced.

What shipped

The residual 39 img? placeholders (/service-areas 27, /accessibility-statement 12) were not a capture gap — the third time that instinct was wrong this run, and the third time checking the PRODUCER first settled it in one lookup:

  • /service-areas "CLICK AN AREA" is a link grid on live — 9 items of title + href, zero images.
  • /accessibility-statement is an InfoCards block of text cards (title + description).

Our extraction was faithful and the CMS adapter already passed image: undefined. The bug was in the shared canonical block: services-1.astro rendered <TileMedia> unconditionally, so every legitimately image-less card tripped the Image primitive's loud-fail.

Fix: a block-level hasMedia guard — a grid where no item carries an image renders no media slot; a grid where some do keeps it, so an item genuinely missing an image still loud-fails. That signal is load-bearing (a service teaser that lost its photo must be visible), so it was proven still live: blanking one image in a 3-image home grid fired the placeholder; reverted clean.

WCP worker 4f11c3c2. garvanbay unaffected (its grids carry images → hasMedia true → byte-identical markup; verified 0 placeholders, images intact).

Wiki changes

  • ADR-0004 — followup (append-mostly): loud-fail scoping — structural absence is judged at the BLOCK, not the item. ADR-0004 is the loud-fail contract, and both of this run's placeholder bugs were failures of where the guard sits, so the rule belongs there: an item missing a field is loud when its siblings have it; structural when no item in the block has it.
  • known-patterns.md — new entry "A grid with NO imagery is a TEXT grid" (decide media presence per BLOCK, not per item; the scope of "structural absence" is the container, which is the easy thing to get wrong).
  • known-issues.md — the WCP placeholder issue closed; empty-image-slots: 13 explicitly recorded as benign (those are the legitimately image-less items) so nobody chases it to zero — rendered-placeholders is the defect signal.
  • asset-reconcile.mjs — bucket labels corrected to say so.

State

Committed to master: packages/components-v3/…/blocks/services-1.astro (hasMedia guard), apps/cms/scripts/asset-reconcile.mjs (labels), wiki + this handover. Live: WCP worker 4f11c3c2; no D1 writes (the fix is render-only — the data was already correct). No garvanbay change.

Next session (priority order)

  1. fcr.business.imageUrl/assets/logos/… never migrated → dead on the worker, so og:image + JSON-LD imagery are broken. Logos are FCR build assets (no wixstatic source): upload the local file to R2. One-liner, and it clears the last referenced-but-missing entry.
  2. Header CTA is not reproducible from a regen (pt6 graft check) — pt4's detector doesn't fire on the stored capture, so WCP still isn't safely re-seedable end-to-end. Close before any fleet-wide regen.
  3. ADR-0010 slice C — image provenance enrichment (API filenames/dimensions/alt), now that searchFiles gives the true 392-file pool.
  4. at-source-never-pulled: 298 — now worth a second read: with the placeholders at zero and the logo/ribbon questions settled, a residual here is a genuine capture-coverage signal rather than noise.
  5. Section-style density dimension + Slice 6 calibration.

The through-line of this run

Three consecutive "the capture must have missed it" hypotheses; three times the real cause was downstream of a correct capture (a transformer misroute, an emitter shape coercion, an unconditional media slot). The build's section-matchers.json sidecar — which matcher produced this block? — settled each one immediately. Reach for it before theorising about a missing asset.