# Lane 2 — real-browser interaction and UX test ledger

Fusion PPC system audit, 2026-08-30. Executor: Lane 2 (Opus, high effort).

**Method.** Playwright (`playwright.sync_api`, Python 1.61.0, chromium headless-shell build 1234),
viewport **1280x900, `device_scale_factor=1`, desktop only**. No artifact claims responsive support,
so no other viewport was tested. Local files were served over `python3 -m http.server` from the
correct root so relative and absolute-from-root assets resolve:

| Port | Server root | Serves |
| --- | --- | --- |
| 8801 | `/home/matt/mirror/Fusion/deliverables` | targets A, B, C, D |
| 8802 | `<scratchpad>/orchbranch` | targets E, G, H (orchestrator-branch materialisation) |
| 8803 | `<scratchpad>/fixture` | target E2 success-path fixture (synthetic data, scratchpad only) |

`<scratchpad>` = `/tmp/claude-1000/-home-matt-mirror-Fusion/3429ff84-e206-4a61-8d4c-770f9bcc2c74/scratchpad`.

**Read-only.** No live Ads/Meta/GA4/CRM/WordPress/staging call, no form submission to a real
endpoint, no send, no spend, no git commit, no restart of the stopped build run. The only writes are
inside `outputs/reviews/fusion-ppc-system-audit-20260830/` and the scratchpad. Browser `localStorage`
written during target-E testing was removed at the end of each run.

**Machine receipts.** Every raw Playwright result JSON is at
`evidence/lane2/receipts/*.json`; screenshots at `evidence/lane2/*.png`.

**Evidence labels used below:** *observed behaviour* unless a row says otherwise. A local HTTP 200 is
never treated as proof of a lead, sale or revenue. Missing data is recorded as `UNAVAILABLE`, never
as zero.

---

**Result.** 71 checks across 10 browser-visible artifacts (targets A-H): **38 pass, 33 fail** —
4 Critical, 10 High, 13 Medium, 8 Low. 0 targets unavailable; 1 linked artifact
(`ads-control/reports/stage-3-20260824T111713-2aa95a23.html`) is `UNAVAILABLE` (HTTP 404) and is
recorded as a defect of the page that links it, not as a missing target. Every high-severity defect
was reproduced twice; both results are recorded in the row.

---

## 1. Test ledger

Severity: **Critical** = actively misleads a budget decision · **High** = blocks or badly degrades one ·
**Medium** = adds friction or risk · **Low** = polish.

| # | Target | Viewport | Action performed | Expected | Observed | Console / network | Pass/Fail | Screenshot | Sev | Smallest safe fix | Decisive retest |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| A1.1 | `deliverables/fusion-ppc-2026-07-30/public/index.html` | 1280x900 | Load hub page | Two cards, both linking to real artifacts | Title `Fusion Tables, ügyfélhívási csomag`, 2 card links, 632 chars, no horizontal scroll (sw 1280 = cw 1280) | 0 console, 0 pageerror, 0 failed req, 0 HTTP>=400 | PASS | `A1-public-index.png` | — | — | — |
| A1.2 | same | 1280x900 | Resolve both card hrefs over HTTP | 200 each | `deck.html` 200, `prototype/index.html` 200 | clean | PASS | — | — | — | — |
| A1.3 | same | 1280x900 | Contrast scan of rendered text | >=4.5:1 for small text | Eyebrow `FUSION TABLES · 2026.07.30` #aa7c26 on #f4f0e8 measured on internal-audit sibling at 3.4-3.5:1 | — | FAIL (minor) | `B1-exec-brief.png` | Low | Darken the gold eyebrow token to ~#8a6210 | Re-run contrast probe, expect >=4.5 |
| A2.1 | `public/deck.html` | 1280x900 | Click `#next` 14 times, reading `#counter` and `.slide.active` each step | Slides 1..14 in order, wrap to 1 | `1/14`..`14/14` then wraps to `1/14`; labels `1. dia`..`10. dia`, `A1..A4 függelék`; 0 slides clipped (each `scrollHeight` 700 < 900) | 0 console, 0 errors, 0 failed | PASS | `A2-deck-slide1.png` | — | — | — |
| A2.2 | same | 1280x900 | Keyboard ArrowRight then ArrowLeft | Advance then return | `1/14` → `2/14` → `1/14` | clean | PASS | — | — | — | — |
| A2.3 | same | 1280x900 | Click `#notesToggle`, then Escape | Notes open, then close | `body.notes-open` true, `.notes` `display:block` with real speaker text; Escape sets it false | clean | PASS | `A2-deck-notes-open.png` | — | — | — |
| A2.4 | same | 1280x900 | Inspect `#notesToggle` state semantics while open | Toggle announces pressed state | `aria-label` stays `Jegyzetek mutatása` ("show notes") while notes are open; no `aria-pressed`, no `aria-expanded` | clean | FAIL | — | Low | Add `aria-pressed` and flip it in `toggleNotes()` — the sibling council deck already does exactly this (`public/deck.js` line 60 of the council build) | Open notes, read `aria-pressed`, expect `true` |
| A2.5 | same | 1280x900 | Hard-load `#8`, `#99`, `#abc`, no hash (fresh document each time) | Valid index honoured, invalid clamped, garbage → slide 1 | `#8`→`8/14`, `#99`→`14/14` (clamped), `#abc`→`1/14`, none→`1/14` | clean | PASS | — | — | — | — |
| A2.6 | same | 1280x900 | Advance to slide 5, F5 reload | State preserved via `replaceState` | before `5/14`, after `5/14` | clean | PASS | — | — | — | — |
| A2.7 | same | 1280x900 | Load every slide, check all `<img>` `naturalWidth` | All non-zero (three-load rule) | 5 images, `naturalWidth` 1280/1280/1280/1330/1330, all with Hungarian alt text | 0 failed requests | PASS | — | — | — | — |
| A2.8 | same | 1280x900 | Enumerate outbound links across all 14 slides | Deck lets the reader reach the evidence | Exactly **1** link in the whole deck (`prototype/index.html`). No link to `internal-audit.html`, `executive-brief.html`, screenshots or any source | clean | FAIL | — | Medium | Add a `[Sources]` link block on the appendix slides pointing at `../internal-audit.html#sources` | Re-enumerate `a[href]`, expect >1 |
| A2.9 | same | 1280x900 | Tab from page start | Reach all three controls | BODY → `#prev` → `#next` → `#notesToggle` → wrap. All reachable | clean | PASS | — | — | — | — |
| B1.1 | `deliverables/fusion-ppc-2026-07-30/executive-brief.html` | 1280x900 | Load, enumerate interactivity | Static doc | 4,103 chars, **0** buttons/inputs/links, no JS, no images, no horizontal scroll | 0 console, 0 failed, 0 HTTP>=400 | PASS | `B1-exec-brief.png`, `-full.png` | — | — | — |
| B1.2 | same | 1280x900 | Read the number block against its own stated window | Numbers carry window + source + a limit statement | Renders `Hirdetési ablak: 2026.06.29–07.28 · CRM-ablak: 2026.07.16–07.28`, then `43 240 Ft` Google / `43 198 Ft` Meta / `10 / 6` forms-quotes, then verbatim: *"Eltérő időablakok. A 30 napos 86 438 Ft költésből és a 13 napos CRM-adatból hivatalos CPL vagy megtérülés nem számítható."* | clean | PASS (**best-in-system**) | `B1-exec-brief-full.png` | — | — | — |
| B2.1 | `internal-audit.html` | 1280x900 | Load, enumerate | Static long-form audit | 17,735 chars, 0 interactive controls, 2 images both loaded (`meta-age-exact.jpg`, `meta-gender-exact.jpg`, `naturalWidth` 1330) | clean | PASS | `B2-internal-audit.png` | — | — | — |
| B2.2 | same | 1280x900 | Resolve all 13 in-document anchors + external refs | Every `#id` target exists | 13 `href="#…"` values, 13 matching `id="…"` — exact 1:1 set match (`access, crm, former, google, inventory, management, meta, recommendations, roadmap, sources, summary, tracking, unknowns`) | clean | PASS | — | — | — | — |
| B2.3 | same | 1280x900 | Contrast scan | >=4.5:1 small text | `BELSŐ, NEM PUBLIKÁLANDÓ AUDIT` 3.4:1; table header `Állítás` 4.45:1 | — | FAIL (minor) | `B2-internal-audit-full.png` | Low | Darken `--gold` and the table-header grey one step | Re-run contrast probe |
| C1.1 | `public/prototype/index.html` | 1280x900 | Load, three-load image check | All 8 images render | 8 images, `naturalWidth` 1081/1081/552/1248/1920/2550/1280/1280 — none broken | 0 console, 0 failed, 0 HTTP>=400 | PASS | `C1-proto-index.png` | — | — | — |
| C1.2 | same | 1280x900 | Click `.contact-trigger`, then Escape | Modal opens, focus moves in, Escape closes and returns focus | `#contact-note` `hidden` true → false, focus moves to `.contact-close`; Escape → `hidden` true, focus back on `.contact-trigger`. Copy states no message is sent and no data is stored | clean | PASS | `C1-proto-contact-open.png` | — | — | — |
| C1.3 | same | 1280x900 | Resolve all 7 links | 200 each | 7/7 = 200 (`deck.html#8`, `rs2.html`, `hinomi-x2.html`, 4 in-page anchors) | clean | PASS | — | — | — | — |
| C2.1 | `public/prototype/rs2.html` | 1280x900 | Click swatch 2, then ArrowRight, then End | Roving radiogroup, one `aria-checked` at a time, alt text updates | Group is `div[role=radiogroup][aria-label="RS2 szín kiválasztása"]`; initial `Fekete` `tabIndex 0`, rest `-1`; click → `Fehér`; ArrowRight → `Piros` and focus follows; End → `Sárga`; exactly one `aria-checked=true` at every step; `img.alt` rewritten each time | clean | PASS (**best a11y in system**) | `C2-rs2-swatch2.png` | — | — | — |
| C2.2 | same | 1280x900 | Inspect what a colour change actually renders | Different colour photo per swatch | The `src` never changes (`rs2-source-crop.png` for all 6). Colour is a CSS filter on one photo: `sepia(1) saturate(5) hue-rotate(315deg)` = "Piros", `grayscale(1) brightness(1.55)` = "Fehér" | clean | FAIL | `C2-rs2-swatch2.png` | Medium | Add one visible line under `#colorName`: "színszimuláció, nem gyártói fotó" — the page is already labelled a prototype, the swatch itself is not | Read the text node under `#colorName`, expect the disclaimer |
| C3.1 | `public/prototype/hinomi-x2.html` | 1280x900 | Load, enumerate controls | Colour selection as the hub page promises | 1 image (`naturalWidth` 2550), **0 `.swatch` elements**. The hub card copy promises "RS2 és Hinomi mintatermék, működő billentyűzetes színválasztás" | clean | FAIL | `C3-hinomi.png` | Low | Reword the hub card to attribute colour selection to RS2 only | Re-read the hub card copy |
| C3.2 | same | 1280x900 | Click both `Kapcsolatfelvétel` buttons | Both open the one modal | Two triggers, one shared `#contact-note`, both work, `Bezárás` closes | clean | PASS | — | — | — | — |
| D1.1 | `deliverables/fusion-ppc-council-2026-07-31/public/index.html` | 1280x900 | Load | Reaches the deck | `<meta http-equiv="refresh" content="0;url=deck.html">` lands on `deck.html#1`; a plain `<a>` fallback exists for no-JS/no-refresh | clean | PASS | `D1-council-index.png` | — | — | — |
| D1.2 | same | 1280x900 | Load index, then browser Back — **run twice** | No redirect trap | Run 1: url after load `…/deck.html#1`, after Back `about:blank`. Run 2: identical. The zero-delay refresh replaces the history entry, so Back escapes cleanly | clean | PASS | — | — | — | — |
| D2.1 | `council-2026-07-31/public/deck.html` | 1280x900 | Click `#next` 22 times | 22 slides, wrap to 1 | `1/22`..`22/22` then `1/22`; 18 core + `A1..A4`; **0 clipped slides** | 0 console, 0 pageerror, 0 failed, 0 HTTP>=400 | PASS | `D2-council-deck-slide1.png` | — | — | — |
| D2.2 | same | 1280x900 | Notes toggle + Escape + arrows + deep links + reload | All deck controls behave | Notes open (`display:block`, real talk track); Escape closes; Arrow nav `1→2→1`; `#8`→`8/22`, `#99`→`22/22` clamped, `#abc`→`1/22`; reload holds `5/22`; `aria-pressed` **is** maintained here | clean | PASS | `D2-council-deck-notes-open.png` | — | — | — |
| D2.3 | same | 1280x900 | Open notes, read the nine "Named agent feedback cards" — **run twice** | Nine distinct expert positions | 9 cards render with 9 distinct names (Hormozi strategic chair, Define Digital Google, EasyGrow Google, Imperium Meta, Sam Ovens Meta, AI-for-PPC specialist, neutral orchestrator, official-platform verifier, account-evidence analyst) but **only 1 unique body across all 9** — every card reads `Pozíció: tanácsi ellenőrzésre vár · Evidence IDs: …nincs kiadott specialist evidence ID · Eltérés vagy abstention: ABSTAIN, candidate snapshot, 0 active/queryable`. Repeats on all 22 slides = 198 identical cards. Run 2 identical | clean | FAIL | `D2-council-agent-cards.png` | High | The roster is hardcoded in `deck.js` `renderNotes()` with a literal body. Render **one** boundary block ("no specialist feedback was released, 0 of 8 candidates queryable, candidate query HTTP 400") instead of nine name-bearing cards | Count `[...new Set(agent-card dl innerText)]`, expect >1 or expect the card grid to be absent |
| D2.4 | same | 1280x900 | Read the `council-state` boundary line | State disclosed | Verbatim: `deck_structure_feedback_only … 8 candidate, 0 active/queryable. Candidate query HTTP 400 … Nincs kiadott course-derived specialist query vagy feedback.` — the deck **does** disclose it, in the notes, under the nine cards that imply the opposite | clean | PASS (disclosure) / see D2.3 | `D2-council-agent-cards.png` | — | — | — |
| D2.5 | same | 1280x900 | Enumerate outbound links | Deck reaches its evidence | **0** `a[href]` in a 22-slide deck | clean | FAIL | — | Medium | Same fix as A2.8 | Re-enumerate, expect >0 |
| E1.1 | `orchbranch/ads-control/template/stage-3-ai-advice-review.html` | 1280x900 | Load the template with no data injected (**this is the first-run / empty-state test**) | Honest, actionable empty state | Embedded `#dashboard-data` = `{"stageDataVersion":"empty-template-v1","account":"","campaign":"","landingPages":[],"keywords":[],"clusters":[],"searchTerms":{}}`. Banner reads `Empty client-neutral template. Run Refresh and Synthesis to populate verified account data.` Panels read `No snapshot has been synthesized yet.` / `No account data has been loaded.` / `No client priorities have been loaded.` | 0 console, 0 pageerror, 0 failed, 0 HTTP>=400, zero external requests | PASS (banners) | `E1-stage3-firstrun.png`, `-full.png` | — | — | — |
| E1.2 | same | 1280x900 | Click all seven filter buttons (`all, attention, promotion, exclusion, coverage, previous, verification`) | Either disabled, or a message naming the real cause | All seven are live and set `.active`, and all seven return the **identical** string `No rows match this view. Clear search or choose another filter.` with `rowcount` 0. The message blames the operator's filter for what is actually "no snapshot loaded", and offers two remedies (clear search / choose another filter) that cannot work | clean | FAIL | `E1-stage3-after-filters.png` | High | In `render()`, branch on `data.keywords.length===0` first and emit "No snapshot loaded — run Refresh and Synthesis" instead of the filter message; disable the seven filter buttons in that state | Load template, click `promotion`, expect the no-snapshot message and a `disabled` button |
| E1.3 | same | 1280x900 | Type `biliárd` into `#search` | "0 results for …" or the real cause | Same string again: `No rows match this view. Clear search or choose another filter.` | clean | FAIL | — | Medium | Same branch as E1.2 | Type a query, expect the query echoed or the no-snapshot message |
| E1.4 | same | 1280x900 | Click `#expand-sections`, then `#collapse-sections` | Feedback or disabled | `details.page-section` count = **0**, so both buttons are inert with no visible response at all. Open-count 0 before and after | clean | FAIL | — | Medium | Disable both when the section count is 0 | Click Expand, expect a disabled control |
| E1.5 | same | 1280x900 | Search for any export / review / recommendation control | The controls the KPC QA receipt certifies | `document.body.innerHTML` contains the substring `export` **0** times. Greps of the file: `export-decisions` 0, `unexported-counter` 0, `pa-use` 0, `choice-input` 0, `verdict-instruction` 0, `evidence-toggle` 0, `snapshotId` 0, `evidenceLedger` 0 | clean | FAIL | `E1-stage3-firstrun-full.png` | High | Ship the real renderer template (or a rendered snapshot) into the repo; the current template is a strictly poorer artifact than the one QA'd | Grep the shipped template for `export-decisions`, expect >0 |
| E1.6 | same | 1280x900 | Tab through the control strip | Logical order, everything reachable | `all → attention → promotion → exclusion → coverage → previous → verification → #search → #expand-sections → #collapse-sections → provenance summary`. Every control reachable, order matches visual order | clean | PASS | — | — | — | — |
| E1.7 | same | 1280x900 | Horizontal overflow at 1280 | No body scroll | `scrollWidth` 1280 = `clientWidth` 1280 | clean | PASS | — | — | — | — |
| E2.1 | `orchbranch/deliverables/fusion-meta-ads-review-2026-08-24/index.html` (no data present — this is the real devbox state) | 1280x900 | Load | Page states its data is unavailable, everywhere | Table body: `Meta insights failed to load. Serve this page from the Fusion repository root.` **but the four hero KPI tiles above the fold read `Loading` / `Loading` / `Loading` / `Loading` indefinitely** under the labels `Spend in snapshot`, `Creative concentration`, `Meta lead actions`, `Evidence coverage`. `boot()`'s catch block writes only to the table body. No timeout, no retry, no error state on the tiles | 2 console errors; HTTP 404 on `/.tmp/overnight-2026-08-10/data/meta_ads.json` and `…/meta_ad_insights.json` | FAIL | `E2-meta-fail-load.png` | **Critical** | Move the four tiles into the same `catch` — set them to `UNAVAILABLE` with the error text | Load with the data absent, read the four tiles, expect `UNAVAILABLE` not `Loading` |
| E2.2 | same | 1280x900 | With the load failed, click `Spend ↕` — **run twice** | The failure message survives | **Run 1:** before `Meta ad inventory failed to load. Serve this page from the Fusion repository root.` → after `No creatives match these filters.`, `#row-count` becomes `0 of 0 insight rows shown`, tiles still `Loading`. **Run 2: byte-identical.** One click silently converts a hard load failure into a routine empty filter result. The operator is now told the data loaded and simply contains nothing | 2 console 404s (from the initial load only) | FAIL | `E2-meta-fail-after-sort.png` | **Critical** | Set a `state.loadFailed` flag in `catch` and have `render()` return the failure message while it is set (it currently overwrites `#creative-table-body` unconditionally) | Fail the load, click any sort header, expect the failure message unchanged |
| E2.3 | same | 1280x900 | With the load failed, type in `#search` | Failure survives | Same laundering: `No creatives match these filters.` | — | FAIL | — | **Critical** (same root cause as E2.2) | Same fix | Same |
| E2.4 | same | 1280x900 | With the load failed, click `Export my review` | Refuse, or export something marked as having no data | Downloads `fusion-meta-review.json` containing `"client":"fusiontables"`, `"source_window":"2026-07-11/2026-08-09"`, `"evidence_boundary":"Meta platform signals are not CRM enquiries"`, `"decisions":[]`. The file **asserts a source window the page never loaded** | download event fired | FAIL | receipt `receipts/E2-meta-fail-export.json` | High | Disable export while `state.rows.length===0`; stamp the exported file with the loaded snapshot id, not a hardcoded window | Fail the load, click Export, expect no download |
| E2.5 | same | 1280x900 | Click tabs `Audience & targeting` and `Decision queue` with the data layer dead | Tabs reflect the failure | Both render fully and look completely healthy — six targeting cards and a five-item decision queue, all static prose. Two of three tabs give no hint that the account data failed to load | clean | FAIL (minor) | `E2-meta-fail-targeting.png` | Medium | Show the same failure banner on every tab | Fail the load, open tab 2, expect the banner |
| E2.6 | Same page, served over the **scratchpad fixture** at :8803 with synthetic `meta_ads.json` (39 ads) + `meta_ad_insights.json` (20 rows) — success path | 1280x900 | Load | Tiles compute, 20 rows render | Tiles `80 000 Ft` / `11.3%` / `5` / `20 / 39`; `20 of 20 insight rows shown`; 20 table rows with chips, triage labels and per-row decision selects | 20 image 404s (see E2.7); 0 pageerror | PASS (data path) | `E2-meta-fixture-load.png`, `-full.png` | — | — | — |
| E2.7 | fixture | 1280x900 | Three-load image check on the Creative column | Every creative thumbnail renders — this is the column the whole page exists for | **20 of 20 images broken (`naturalWidth` 0).** Two distinct causes: (a) the 5 ad ids present in the hardcoded `assetMap` request `/.tmp/overnight-2026-08-10/data/meta-creatives/<id>_<n>_carouselN_hash.jpg` → HTTP 404, the assets were never shipped with the page; (b) every ad **not** in `assetMap` gets `image=''`, so the markup emits `<img src="">`, which the browser resolves to the page URL itself — a guaranteed broken image. No `onerror`, no placeholder, no "no preview" cell | 20 HTTP 404 responses on `meta-creatives/*.jpg` | FAIL | `E2-meta-fixture-load.png` | High | Two lines: emit a `.no-preview` cell when `assetMap[row.ad_id]` is missing instead of `<img src="">`, and add `onerror` → same placeholder. Then ship the creative JPEGs beside the page | Load the fixture, count `[...document.images].filter(i=>!i.naturalWidth)`, expect 0 |
| E2.8 | fixture | 1280x900 | Click `Spend ↕` twice, then `CTR ↕` | Sort toggles and the header shows which column and direction | Sorting itself is correct: default spend desc (`9000, 9000, 9000`), click 1 → asc (`800, 800, 800`), click 2 → desc again, `CTR ↕` re-sorts. But **every sortable header renders the same static `↕` glyph in every state and `aria-sort` is `none` on all six** (`SPEND ↕, IMPR. ↕, CLICKS ↕, CTR ↕, LPV ↕, META LEAD ↕`). Nothing on screen says the table is sorted by spend descending | clean | FAIL | `E2-meta-fixture-columns.png` | Medium | Swap the glyph to `↑`/`↓` for the active key and set `aria-sort` on its `th` — the KPC template already does this (`state.sortKey===key?…' ↑':' ↓'`) | Click Spend, read `th[aria-sort]`, expect `descending` |
| E2.9 | fixture | 1280x900 | Filter `Serving status = Active`, then add `Triage = Control candidate` | Both filters compose; the count is legible | Composes correctly (20 → 7 → 1 row). But the label always reads `N of 20 insight rows shown` — the denominator stays the unfiltered total even when a second filter is stacked, so "1 of 20" gives no sense of how much the second filter removed | clean | PASS (function) / FAIL (label) | — | Low | Show the chain, e.g. `1 of 7 matching status (20 total)` | Stack both filters, read `#row-count` |
| E2.10 | fixture | 1280x900 | Search a string that matches nothing | Honest empty state | `No creatives match these filters.` and `0 of 20 insight rows shown` — correct **here**, because data really did load. Identical to the string shown when loading failed (E2.2), which is exactly why the laundering is undetectable | clean | PASS | — | — | — | — |
| E2.11 | fixture | 1280x900 | Set a row's `My decision` select, then re-check focus — **run twice** | Focus stays on the control just used | **Run 1:** focus before `SELECT` for ad `90000000000000010`; after the change event, focus is `BODY`. **Run 2: identical.** The `change` handler calls `render()`, which rebuilds the whole `<tbody>` via `innerHTML`, destroying the `<select>` the operator just used. Recording each decision costs a full re-tab; from `#search` the first decision select is **10 tabs** away | clean | FAIL | `E2-meta-fixture-decision.png` | High | Update only the changed cell (or restore `document.activeElement` by `data-ad-id` after `render()`) | Set a decision by keyboard, read `document.activeElement.dataset.adId`, expect the same ad id |
| E2.12 | fixture | 1280x900 | Record a decision, filter it out of view, read the counter | Counter reflects total recorded decisions, not visible ones | `Export my review 1` stays at 1 while `6 of 20 insight rows shown` hides the decided row. Correct behaviour, but nothing tells the operator that 1 recorded decision is currently off-screen | clean | PASS | — | Low | Append " (1 hidden by filters)" when applicable | — |
| E2.13 | fixture | 1280x900 | Click `Export my review` with one decision recorded | A file identified to the snapshot | Downloads `fusion-meta-review.json` (no snapshot id, no date, no account in the filename) containing the recorded decision plus the hardcoded `source_window` `2026-07-11/2026-08-09`. Repeat exports collide on the same name | download fired | FAIL | receipt `receipts/E2-meta-fixture-export.json` | Medium | Put the loaded snapshot identity in the filename and the payload — the KPC report the QA receipt describes already does this (`download.suggestedFilename().includes(snapshotId)`) | Export twice, compare filenames |
| E2.14 | fixture | 1280x900 | Reload after recording a decision | Decision persists or the operator is warned | `localStorage['fusion-meta-review']` = `{"90000000000000019":"keep_control"}` survives reload; counter still `1`. Persistence works, but it is per-browser and per-origin only: a second reviewer, another machine, or the same file opened from a different path sees an empty review, and nothing on the page says so. `state.exported` is initialised `true`, so the `beforeunload` guard never fires on a session that starts with unexported edits already in storage | clean | FAIL (partial) | `E2-meta-fixture-final.png` | Medium | One line under the export button: "Decisions are stored in this browser only. Export to share." Initialise `state.exported` from whether stored edits exist | Record a decision in browser A, open in browser B, expect the notice |
| E2.15 | fixture | 1280x900 | Measure column visibility at 1280 | The decision controls are visible without horizontal scrolling | `.table-wrap` `scrollWidth` 1538 > `clientWidth` 1238 (`overflow-x:auto`). `Cost / signal` ends at x=1226 (visible); **`Triage` ends at 1379 and `My decision` at 1559 — both are off-screen at 1280 and require horizontal scrolling.** The page's own recommendation chip and its only decision control are the two columns you cannot see | clean | FAIL | `E2-meta-fixture-columns.png` | Medium | Move `Triage` and `My decision` to the left of the six metric columns, or freeze them right | Load at 1280, assert every `th` right edge <= the wrap's right edge |
| E2.16 | fixture | 1280x900 | Read the `Spend in snapshot` tile against the `Evidence coverage` tile | Totals are scoped honestly | `Spend in snapshot 80 000 Ft` with subtitle "Across the 20 ads with insight rows", beside `Evidence coverage 20 / 39`. So 19 of 39 ads have **UNAVAILABLE** spend, and `Creative concentration 11.3%` is a share of an incomplete denominator without saying so | clean | FAIL (minor) | `E2-meta-fixture-load.png` | Medium | Add "of 39 ads, 19 UNAVAILABLE" to the concentration tile subtitle | Read the concentration tile subtitle |
| F1.1 | `https://b62a73b2.cf-review-9e5.pages.dev/assets-p12/` (remote, GET only) | 1280x900 | Load | Reachable targeting-design reference | **Reachable**, HTTP 200 (curl 200 in 0.72 s). Title: `P12 · Gondtalan utazó · Tavaszi Nyaralás Balin és Gilin · asset csomag`. 34,203 chars, `scrollHeight` 15,979 | 0 console, 0 pageerror, 0 failed, 0 HTTP>=400 | PASS (reachability) | `F1-remote-assets-p12.png`, `-full.png` | — | — | — |
| F1.2 | same | 1280x900 | Search the rendered text for the client this audit is about | Fusion Tables targeting design | `fusion` 0, `fusiontables` 0, `tables` 0, `biliárd` 0, `csocsó` 0. The page is a **different client's** internal ad-asset package for a Bali/Gili holiday (`1.200.000 Ft/fő`, 2027-04-05..19), banner `Belső review anyag · nem ügyfélnek`. Generic targeting vocabulary appears (`targeting` 10, `érdeklődés` 9, `audience` 3, `demográf` 1) but never for Fusion | clean | FAIL | `F1-remote-assets-p12.png` | High | Do not cite this URL as the Fusion targeting-design reference. If a pattern reference is wanted, cite it as "another client's asset-pack layout, structure only" | Grep the rendered text for `Fusion`, expect >0 |
| F1.3 | same | 1280x900 | Click the D1..D7 section nav (`#hookok, #uspk, #meta, #google, #copyblokkok, #emailek, #funnel`) | Each scrolls to its section | All 7 anchor targets exist in the DOM. But clicking `#meta` leaves `scrollY` at **0**; `#funnel` moves to `scrollY` 289 in a 15,979 px document (nowhere near the funnel section); `#hookok` does not move at all; `location.hash` is **never** updated by any of them. A `#comment-html-root` overlay is present on the page and is the likely click interceptor | 0 console errors | FAIL | `F1-remote-assets-p12-after-nav.png`, `-meta.png` | Medium | Out of this audit's write scope (remote, another client). Record only | Click `a[href="#funnel"]`, expect `location.hash === '#funnel'` |
| F1.4 | same | 1280x900 | Contrast scan | >=4.5:1 small text | `Belső review anyag · nem ügyfélnek` white on #ef8934 = **2.53:1** (real defect). Other flagged rows (ratio 1.0 white-on-white in the hero) are a **measurement limitation** of my probe, which ignores `background-image` — not verified as defects | — | FAIL (1 real) | `F1-remote-assets-p12.png` | Low | Darken the orange banner | Re-measure the banner |
| G1.1 | `orchbranch/outputs/loop-runs/20260824T085117Z-fusion-kpc-report-recovery/index.html` | 1280x900 | Load | Closeout page renders | Title `Fusion KPC report recovery closeout`, 7,829 chars, no images, no interactive controls, no horizontal scroll, **0 contrast failures** | 0 console, 0 pageerror, 0 failed, 0 HTTP>=400 | PASS | `G1-kpc-recovery.png`, `-full.png` | — | — | — |
| G1.2 | same | 1280x900 | Resolve all 6 outbound links, including the primary CTA | Every artifact the closeout points at opens | 5/6 = 200 (evidence ledger, frozen bundle, validated advice output, both gate PNGs). **`Open the final report` → `../../../ads-control/reports/stage-3-20260824T111713-2aa95a23.html` = HTTP 404.** That is the page's single most important link and the artifact the whole recovery run exists to produce. Consistent with GROUND-TRUTH: the rendered report is in no branch | 404 recorded by the link checker | FAIL | receipt `receipts/g1.json` | **Critical** | Either commit the 893 KB rendered report beside the closeout, or replace the CTA with an explicit `UNAVAILABLE on this host — the rendered report is Mac-local` note. A dead primary CTA on a "Complete and verified" page is worse than an honest gap | `curl -o /dev/null -w '%{http_code}' <that href>`, expect 200 |
| G1.3 | same | 1280x900 | Verify the page's own Live-browser-QA claims against the only surviving KPC artifact (the E1 template) | Claimed controls exist somewhere reviewable | The page asserts: *"The 17-row attention filter, search, all-row view, review checkbox, recommendation control, section expansion, and JSON export all passed. Export reset the unexported counter."* The report those claims describe is 404 (G1.2). The only surviving KPC HTML in the repo, the E1 template, contains **zero** occurrences of `export`, `export-decisions`, `unexported-counter`, `pa-use`, `choice-input`, `verdict-instruction` or `evidence-toggle`. The QA script `browser_qa.js` in the same directory does drive all of them, so the claims were true of an artifact that no longer exists on this host | clean | FAIL | `E1-stage3-firstrun-full.png` | High | State on the page which artifact the QA ran against and that it is not the template shipped in the repo | Grep the shipped template for `export-decisions`, expect >0 |
| G1.4 | same | 1280x900 | Compare my render against the two supplied 1280 gate screenshots | Same artifact, no drift | **Not a valid pixel comparison, and that is the finding.** `gate/interactive-1280.png` (1280x900) and `gate/_stage_3_..._html-1280.png` (1280x2185) show the **rendered report**, not the template. The gate screenshot shows a toolbar with `Search…`, `Expand sections`, `Collapse sections`, **`Export reviewed decisions`** and a **`0 changes not yet exported`** counter, plus a `Measurement health` caveat block and populated landing-page groups (`16 keyword rows`, `17 keyword rows`, …). My render of the template at the same viewport has the search box and the two section buttons but **no export control, no change counter, no measurement-health block and no rows**. Drift = the repo ships a strictly poorer artifact than the one that was QA'd | clean | FAIL | `E1-stage3-firstrun-full.png` vs `gate/interactive-1280.png` | High | Same as G1.3 | Diff the two toolbars |
| G1.5 | same | 1280x900 | Check the page's own numbers for internal consistency | Sums reconcile | Search 0 clicks / 0 impressions / 0 Ft (2 paused campaigns), Display 0/0/0, Performance Max 4,862 / 119,254 / 121,196.492984 Ft, Account total 5 campaigns 4,862 / 119,254 / 121,196.492984 Ft — the campaign-type rows sum exactly to the account total. Evidence-state audit: Retrieved 704, Verified zero 266, Not applicable 488, Derived 63, Unavailable 3, 0 unresolved critical. Unavailable is explicitly kept distinct from zero | clean | PASS (**best measurement hygiene in system**) | `G1-kpc-recovery-full.png` | — | — | — |
| G1.6 | same | 1280x900 | Cross-check G's Google Ads figures against `report_data.json` | Same account, different windows must not be joined | G covers **2026-06-25..2026-08-23** (KPC window): 4,862 clicks / 119,254 impressions / 121,196.49 Ft. `report_data.json` `ppc.totals` covers **2026-07-25..2026-08-23**: 4,410 clicks / 105,555 impressions / 92,095.56 Ft. Different windows, so **no contradiction and no join attempted**. Both label their window on the page. Spend for one metric is 121,196.49 Ft vs 92,095.56 Ft purely from the extra 30 days | — | PASS | — | — | — | — |
| H1.1 | `orchbranch/outputs/loop-runs/20260828T232516Z-fusiontables-report-closeout/index.html` | 1280x900 | Load | Closeout renders | Title `Fusion Tables report loop closeout`, 4,227 chars, no images, no interactive controls, no horizontal scroll | 0 console, 0 pageerror, 0 failed, 0 HTTP>=400 | PASS | `H1-closeout.png`, `-full.png` | — | — | — |
| H1.2 | same | 1280x900 | Resolve every link | All artifacts reachable | All 4 `artifacts/*.json` receipts = 200 (`slides_receipt`, `report.md`, `ledger`, `run_receipt`); 1 external Google Slides URL not fetched (read-only rule, no auth). Unlike G, H's links are live | clean | PASS | — | — | — | — |
| H1.3 | same | 1280x900 | Check every headline number against `report_data.json` | Each asserted figure is backed | `4,410 Google Ads clicks, up from 452` = `ppc.totals.clicks` 4410/452 ✔ · `825 Meta clicks, up from 322` = `meta_ads.totals.clicks` 825/322 ✔ · `13 Recorded non-QA enquiries, up from 7` = `crm.totals.leads` 13/7 ✔ · `6 Prioritized optimization recommendations` = `recommendations` length 6 ✔ · `61 KPC keyword elements` matches G's 61 criteria ✔. **No unbacked figure found on this page** | — | PASS | `H1-closeout-full.png` | — | — | — |
| H1.4 | same | 1280x900 | Read the "Verified reporting signals" tiles as a budget decision-maker | The metrics that move budget are present | The five tiles show **clicks, clicks, enquiries, keyword elements, recommendation count**. There is **no cost, no CPC, no conversions, no CPA and no CPL anywhere on the page**, although `report_data.json` holds all of them (`cost` 92,095.56 Ft, `conversions` 49.0, `cpa` 1,879.50 Ft, Meta `spend` 89,778 Ft). A closeout headed "the reporting package is complete" gives the reader nothing to allocate budget with | — | FAIL | `H1-closeout-full.png` | Medium | Add cost and cost-per-enquiry tiles beside the click tiles | Read the tile row, expect a cost figure |
| H1.5 | same | 1280x900 | Test whether the 13 enquiries can be attributed to a channel | Enquiries split by Google vs Meta, or the page says they cannot be | The page presents `13 … up from 7` beside `4,410 Google clicks` and `825 Meta clicks`, inviting the join. `report_data.json` shows `crm.source_column: null`, and all 13 come from a single worksheet (`Régi Billiárd Ajánlatkérések` 13; the other four forms are 0/0). **No channel attribution exists**, and the page never says so. The report's own `limitations` do carry the boundary; the closeout page does not repeat it next to the numbers | — | FAIL | `H1-closeout-full.png` | High | One line under the tiles: "Enquiries carry no source column — they cannot be attributed to Google or Meta." | Read the text adjacent to the enquiry tile |
| H1.6 | same | 1280x900 | Check the exclusions block | Honest about what is missing | Verbatim: *"No zero values were substituted for unavailable GA4 or Search Console data"*, and the limitations block names the Meta 190/467 token failure, the GA4/GSC absence, and that platform conversion events, enquiries, sales and revenue are separate evidence layers. `report_data.json` `source_health` corroborates: `ga4` status `absent`, `search_console` status `absent`, `meta_ads` status `degraded` with the `VHK_form_start` form-start caveat | — | PASS | `H1-closeout-full.png` | — | — | — |
| H1.7 | same | 1280x900 | Contrast scan | >=4.5:1 small text | Eyebrow `FUSION TABLES HUNGARY · GOAL V1 CLOSEOUT` #3d8bfd on white = 3.33:1 | — | FAIL (minor) | — | Low | Darken the blue one step | Re-measure |

---

## 2. Per-target UX verdict

### A. `deliverables/fusion-ppc-2026-07-30/public/` — hub + 14-slide deck
**Verdict: sound as a presentation, useless as a decision surface.** Every control I could click worked:
14 slides forward and wrapping, arrow keys, notes toggle, Escape, hash deep-links including a clamped
`#99` and a garbage `#abc`, reload state, all five images loading, zero console output. The two real
gaps are informational, not mechanical. The whole 14-slide deck contains **one** outbound link, so a
reader who wants the source behind a slide has nowhere to click — the audit that holds those sources
sits two directories away and is never linked. And the notes toggle never announces its own state.
Nothing here answers Matt's actual ask (funnel steps last-30 vs prior-30, demographic distribution,
creative/placement/targeting comparison); it is a narrative deck.

### B. `executive-brief.html` + `internal-audit.html`
**Verdict: the strongest evidence hygiene in the entire system.** Both are pure static documents — no
JS, no controls, nothing to break, and nothing broke. The executive brief is the model the rest of the
system should copy: it prints its ad window and its CRM window side by side, notes that they differ,
and then states in plain Hungarian that no official CPL or ROI can be computed from a 30-day spend
figure and 13 days of CRM data. That is a system refusing to manufacture a number it cannot support.
The internal audit's 13 in-document anchors resolve 1:1 with no dead jumps. Only real defect: gold and
grey small text between 3.4:1 and 4.45:1.

### C. `public/prototype/{index,rs2,hinomi-x2}.html`
**Verdict: the best-implemented interaction in the system, with one honesty caveat.** The RS2 colour
picker is a correct WAI-ARIA radiogroup: roving `tabindex`, exactly one `aria-checked` at a time,
Arrow/Home/End all wired, focus follows selection, and the image `alt` text is rewritten to name the
selected colour. The contact modal opens, moves focus to its close button, closes on Escape, and
returns focus to the trigger — and its copy states plainly that nothing is sent and nothing is stored.
All eight images load. The caveat: the six "colours" are CSS filters over one black photograph
(`sepia(1) saturate(5) hue-rotate(315deg)` is "Piros"), so a client shown this would be looking at a
simulation, not product photography. The page is labelled a prototype; the swatch strip is not. And
the hub card promises keyboard colour selection for Hinomi, which has no swatches at all.

### D. `deliverables/fusion-ppc-council-2026-07-31/public/`
**Verdict: mechanically the best deck in the system, and its central content is a placeholder.** 22
slides, all navigation paths pass, deep links and clamping work, `aria-pressed` is maintained (the
2026-07-30 deck's is not), the zero-delay meta-refresh does not trap the Back button, and 0 slides
clip. Then you open the speaker notes: nine "Named agent feedback cards" carry nine distinct expert
names — Hormozi strategic chair, Define Digital Google, Imperium Meta, Sam Ovens Meta and five more —
and **all nine bodies are byte-identical**: `Pozíció: tanácsi ellenőrzésre vár`, `ABSTAIN, candidate
snapshot, 0 active/queryable`, `nincs kiadott specialist evidence ID`. That block repeats on all 22
slides, so the deck renders 198 identical cards under 198 authoritative names. The honest boundary
line *is* there, directly underneath — `8 candidate, 0 active/queryable, Candidate query HTTP 400,
Nincs kiadott course-derived specialist query vagy feedback` — but it is buried under the visual
apparatus of a nine-expert review that never happened. Nine named cards saying nothing read as nine
experts who reviewed and agreed. That is false confidence at the exact point where a reader decides
how much weight the deck carries. Like target A, it has zero outbound links.

### E1. `ads-control/template/stage-3-ai-advice-review.html` (KPC stage-3 dashboard template)
**Verdict: the banners are honest, the controls are not.** With no data injected — which is the
first-run state and the only state this artifact has on devbox — the page correctly says
`Empty client-neutral template. Run Refresh and Synthesis to populate verified account data.`,
`No snapshot has been synthesized yet.`, `No account data has been loaded.` That is the right message,
in the right place, three times.

Then every control below it contradicts it. All seven filter buttons — All rows, Needs attention,
Promotion candidates, Exclusion candidates, Coverage problems, Unresolved previous checks, Changes
awaiting verification — are live, take the `active` style, and return the same sentence:
*"No rows match this view. Clear search or choose another filter."* That sentence blames the operator
for a state they did not cause, and both remedies it offers are impossible: there is no search to clear
and no other filter that will help. Someone hunting for promotion candidates will click all seven and
conclude the account has none, when the truth is that nothing was loaded. `Expand sections` and
`Collapse sections` act on zero sections and give no feedback at all. Keyboard order and overflow are
clean; that is not the problem.

The larger problem is what is absent. This template has no export, no review checkbox chain, no
recommendation control, no evidence toggles and no change counter — none of the controls the KPC
closeout page (target G) certifies as passing QA, and none of the ones visible in the gate screenshot
of the real report. Nobody can review the KPC decision surface from this repository.

### E2. `deliverables/fusion-meta-ads-review-2026-08-24/index.html` (Meta creative-control prototype)
**Verdict: the closest thing that exists to what Matt asked for, and in its actual devbox state it
misleads.** Tested hardest, per brief: failure path against the real repo (data absent), success path
against a synthetic scratchpad fixture.

The design intent is right. The evidence-boundary banner is excellent and unambiguous: *every "lead
action" below is a Meta platform signal, not a CRM enquiry, qualified lead, sale, or revenue*. The
targeting tab refuses to name an interest a winner because 20 interests sat in one Advantage-expanded
pool — that is the correct answer to Matt's targeting question, and it is stated as a limitation rather
than papered over. `Cost / signal` renders `N/A`, not `0`, when there are no leads. Triage rules are
published on the page. Sorting, filtering, filter composition, decision recording, localStorage
persistence across reload, and export all function on the success path.

But on the real host the data files are absent, and there the page fails in the two worst possible
ways. First, the four hero KPI tiles read **`Loading` forever** — the catch block only writes to the
table body, so `Spend in snapshot`, `Creative concentration`, `Meta lead actions` and `Evidence
coverage` sit above the fold in a permanent pending state with no timeout, no retry and no error.
Second, **one click on any sort header converts the honest failure message into a false empty state**:
`Meta insights failed to load…` becomes `No creatives match these filters.` with `0 of 0 insight rows
shown`. Reproduced twice, byte-identical both times. Typing in the search box does the same. An
operator who sorts before reading — which is what everyone does with a metric table — is told the data
loaded and there is simply nothing in the account. Export still fires in that state and writes a file
asserting `source_window: 2026-07-11/2026-08-09`, a window the page never loaded.

On the success path, the column that justifies the page's existence is empty: **20 of 20 creative
thumbnails are broken**, half because the JPEGs were never shipped alongside the HTML and half because
ads outside the hardcoded `assetMap` are emitted as `<img src="">`. You cannot compare creatives you
cannot see. And at 1280 the two columns that carry the decision — `Triage` and `My decision` — are the
only two that fall outside the viewport and require horizontal scrolling. Recording a decision throws
keyboard focus to `<body>` every single time, because the change handler re-renders the entire tbody
(reproduced twice); with the first decision select 10 tabs from the search box, reviewing 20 ads by
keyboard is punishing.

### F. `https://b62a73b2.cf-review-9e5.pages.dev/assets-p12/` (remote)
**Verdict: reachable, and not what it is cited as.** HTTP 200, clean console, no failed requests. But
it is a different client's internal ad-asset package for a Bali/Gili holiday package —
`P12 · Gondtalan utazó`, `1.200.000 Ft/fő`, April 2027 — banner-labelled `Belső review anyag · nem
ügyfélnek`. Zero occurrences of Fusion, fusiontables, biliárd or csocsó in 34,203 rendered characters.
It cannot serve as the Fusion targeting-design reference. Its own D1..D7 section nav is also dead:
targets exist, but clicking `#meta` and `#hookok` moves the page not at all and `#funnel` moves it 289 px
into a 15,979 px document, with `location.hash` never updating — a `#comment-html-root` overlay is
present and is the likely interceptor.

### G. `20260824T085117Z-fusion-kpc-report-recovery/index.html`
**Verdict: the most rigorous measurement writing in the system, attached to a 404.** The reconciliation
is genuinely good: Search 0/0/0 Ft with both campaigns paused, Display 0/0/0, Performance Max carrying
all 4,862 clicks and 121,196.49 Ft, campaign-type rows summing exactly to the account total, and an
evidence-state audit that separates Retrieved 704 / Verified zero 266 / Not applicable 488 / Derived 63
/ Unavailable 3 — with the explicit rule that unavailable never renders as zero. Its window
(2026-06-25..2026-08-23) is labelled and does not contradict `report_data.json`'s narrower
2026-07-25..2026-08-23 window; both are stated, neither is joined.

The page is headed `Complete and verified` and its first call to action, `Open the final report`, is
**HTTP 404**. Five of six links resolve; the one that matters does not. The page then certifies
browser QA of an export control, a review checkbox chain, a recommendation control and an unexported
counter — all real in `browser_qa.js`, all visible in the gate screenshot, and **none present in the
only KPC HTML this repository actually ships**. The gate screenshots are the sole surviving evidence
that the reviewed artifact ever existed.

### H. `20260828T232516Z-fusiontables-report-closeout/index.html`
**Verdict: every number checks out, and the page still cannot support a budget decision.** This is the
only page in the audit whose headline figures I could verify one by one against `report_data.json`:
4,410/452 Google clicks, 825/322 Meta clicks, 13/7 enquiries, 6 recommendations — all exact. All four
artifact links resolve. The exclusions block is honest, including *"No zero values were substituted for
unavailable GA4 or Search Console data"*, corroborated by `source_health` marking GA4 and Search Console
`absent` and Meta `degraded`.

Two things stop it short. The tile row headed `Verified reporting signals` shows clicks, clicks,
enquiries, keyword count and recommendation count — **no cost, no conversions, no CPA, no CPL** — even
though `report_data.json` carries 92,095.56 Ft Google spend, 89,778 Ft Meta spend, 49 conversions and a
1,879.50 Ft CPA. Clicks are the one paid metric that never decides where budget goes. And the page puts
`13 enquiries, up from 7` in the same row as the two channel click counts, which invites exactly the
join the data forbids: `crm.source_column` is `null` and all 13 enquiries come from one legacy
worksheet, so **no enquiry can be attributed to Google or to Meta**. The underlying report states that
boundary; the closeout page repeats the numbers without it.

---

## 3. Cross-target patterns

1. **Failure states are honest at load and dishonest after the first interaction.** Both dashboards
   (E1, E2) print a correct "nothing is loaded" message, then let a filter, sort or search overwrite it
   with "nothing matched your filter". The distinction between *no data* and *no match* is the
   distinction between "investigate the pipeline" and "there is nothing here" — and both pages lose it
   on the first click.
2. **Polished numbers survive their sources.** G certifies interactive QA of a report that 404s; D2
   renders nine named expert cards for a council that returned `0 active/queryable`; E2 exports a
   hardcoded `source_window` it never loaded. In each case the confident surface outlived the evidence
   behind it.
3. **The measurement writing is consistently better than the interaction design.** B1's window
   disclaimer, G's Retrieved/Verified-zero/Not-applicable/Unavailable taxonomy and E2's evidence-boundary
   banner are all genuinely good. The defects are almost entirely in what happens after a click.
4. **Nothing in any browser-visible artifact implements Matt's actual dashboard ask.** No funnel-step
   chart with last-30 vs preceding-30 on any page; demographic distribution exists only as two static
   Meta screenshots in the appendix of the 2026-07-30 deck and internal audit; the creative and
   targeting comparison exists only as E2, whose creative column renders 20 broken images. Per
   GROUND-TRUTH the T3 dashboard was never produced — this browser pass **confirms** it: no such
   surface exists to test. Recorded as `UNAVAILABLE — never produced`.

---

## 4. Limitations of this pass

- Every finding is 1280x900, DSF 1, desktop, headless chromium. No artifact claims responsive support,
  so no other viewport was exercised.
- E2's success path used **synthetic fixture data** written to the scratchpad (39 ads, 20 insight rows).
  Row values, tile totals and the `SYNTHETIC ad N` names are mine, not Fusion's. Every structural
  finding (broken images, focus loss, column visibility, sort indicators, export identity, persistence)
  is independent of the values; the tile arithmetic is not, and is not reported as a Fusion figure.
- The rendered KPC report `ads-control/reports/stage-3-20260824T111713-2aa95a23.html` is `UNAVAILABLE`
  on this host (404). Its interactive behaviour could not be tested; the two 1280 gate screenshots were
  read as the only surviving visual evidence.
- The contrast probe walks up for the first non-transparent `background-color` and ignores
  `background-image`. Readings over hero images (notably the ratio-1.0 rows on target F) are
  measurement limitations, not verified defects, and are labelled as such.
- No download outside the browser sandbox, no form submission to any real endpoint, no authenticated
  request, no live platform call. The Google Slides link on target H was not opened (no auth, read-only
  rule).
- `report.md`, `evidence.json` and the receipt JSONs were used only to verify figures asserted **on
  tested pages**. They were not browser-tested and their own contents are outside this lane.
