Repository navigation
Phase 4: Ads Live — AdSense placement, consent-gated loader, privacy disclosure, RPGLogs deploy decision - #17
Open
alexandermayes wants to merge 154 commits into
Open
alexandermayes wants to merge 154 commits into
alexandermayes wants to merge 154 commits into
Conversation
…sent-gate hotfix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, no CMP dependency
- lib/geo.ts: CONSENT_REGIONS (32-code EEA+UK+CH set) + isConsentRegionCode, fail-closed on unknown/missing input
- app/api/geo/route.ts: sole reader of x-vercel-ip-country; returns { isConsentRegion } with Cache-Control: private, no-store, no cookie
- app/components/PostHogProvider.tsx: posthog.init moved to module scope (fixes child-effect-before-parent-effect pageview loss); consent-readiness gate fetches /api/geo once and applies the geo or TCF decision; PostHogPageView now waits for the gate before capturing $pageview
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… gate - Item 7: post-deploy live-traffic check with both HogQL queries, inclusive thresholds (>=20 pageviews, >=2 countries, >=50% of Vercel Analytics), the zero-events-is-a-FAIL rule, and the bounded netlog command (D-08) - Item 4 reworded: an event definition persisting is not evidence of current ingestion; item 4 alone can no longer close the PostHog half of the gate - Fourth recording rule: an absent event count is a FAIL, never no-data - Items 1-6 and the original three recording rules are unchanged Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- 01-VERIFICATION.md, 02-VERIFICATION.md: dated append-only addendum stating the OPS-01 PostHog criterion was not actually met at sign-off (cookieless_mode on_reject dropped PENDING-consent capture since the 2026-09-06 deploy), and was met for real by Phase 2.1. No existing text rewritten or removed. - REQUIREMENTS.md: OPS-01 checklist line and traceability-table row both extended (not replaced) to cite the correction and point at Phase 2.1; MONY-01 untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…executor - Add ConsentGatePath, ConsentGateOutcome and deriveConsentGateOutcome to lib/consent.ts (D-06/D-07): pure merge of the geo boolean and the TCF action into register/opt_in_capturing/startSessionRecording/capture calls - Non-consent-region outcomes ignore tcfAction entirely (D-04); consent-region outcomes reproduce today's SDK calls exactly (D-05); opt-in-non-eea reached with isConsentRegion true is surfaced as a countable list/CMP mismatch (gdpr_applies false) rather than silently absorbed - 10 new tests in lib/consent.test.ts covering every outcome row, the ConsentGatePath literal set, and the optIn/startReplay invariant - PostHogProvider.tsx: replace the inline four-branch TCF switch with a single applyOutcome executor; both the geo path and the TCF path route through it, so there is exactly one call site each for posthog.register/opt_in_capturing/startSessionRecording Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Record Task 2 checkpoint resolution (user-selected option a: GI/IM/JE/GG stay outside CONSENT_REGIONS, no code change needed) - Document Task 3's deriveConsentGateOutcome + single-executor rewiring - Full suite green at 161 tests, typecheck clean, scoped lint clean Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…te hotfix - Part 4 added to docs/OPS-01-SHIP-GATE.md: local gate output, preview deployment dpl_7JGkziQrpZrhKfvxHJNAmtuXfuFy, deployed /api/geo response, and the D-10 netlog proof (3 distinct /ingest/i/v0/e/ capture requests against a production baseline of zero) - Answers RESEARCH Open Question 1: preview deployments receive real x-vercel-ip-country geo headers, same as production - Documents a previously-unknown false-negative trap in the bare D-10 command: posthog-js's built-in bot filter silently drops captures for any User-Agent containing "HeadlessChrome" (opt_out_useragent_filter defaults to false) - fixed for this run via a --user-agent override that does not change what is being tested - Item 7 (live-traffic) and the phase sign-off are left explicitly open, pending the production deploy in 02.1-04 - Purely additive to Parts 1-3; no production deploy was run Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…osed on malformed body A malformed-but-valid-JSON /api/geo response (e.g. a Vercel platform-level failure payload lacking isConsentRegion) previously made body.isConsentRegion undefined, and !undefined === true opted the visitor in immediately with no CMP involvement — the opposite of the fail-closed guarantee. Now the parsed body is runtime-checked for a boolean isConsentRegion field; anything else (missing field, wrong type, null) resolves to true (consent region), matching the existing .catch fail-closed path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t block consent capture indefinitely The TCF path already has CMP_TIMEOUT_MS as a fail-safe against a blocked/absent CMP, but the geo fetch that gates the entire consent decision had no analogous protection. A hung request (dropped connection, stalled edge function) never hit .then or .catch, so applyDecision was never called and consentReady never became true — reproducing the class of bug this hotfix fixes via a different trigger. Bound the fetch with AbortSignal.timeout(5000); the abort rejects the promise, which the existing fail-closed .catch handles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a fully missing one isConsentRegionCode already treats a blank/whitespace-only country header identically to a missing one (both fail closed to consent region), but the logging condition only checked !country, which is false for a non-empty whitespace string. That silently under-counted the blank-header case in the geo_header_missing observability signal this hotfix relies on. Compare against the trimmed value instead, and add a test covering a whitespace-only header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…T handler .claude/CLAUDE.md's Function Design convention requires explicit return types on all functions, and async functions to declare Promise<T>. This route was the one exception among the files reviewed in this phase. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n a repeat TCF resolution startConsentListener's hasResolved guard only covers its timeout branch; the __tcfapi addEventListener callback calls resolve() unconditionally on every non-pending event. Some CMPs (e.g. Google Funding Choices' manage-consent re-confirmation flow) can emit a second resolved event for the same visitor, which previously re-fired posthog.capture(consent_resolved, ...) and inflated the OPS-01 gate's consent_gate_path counts. Track already-fired gate paths in a ref and skip the outbound event (but still update consent state via posthog.register/opt_in_capturing/ startSessionRecording, which are idempotent) on a repeat of the same gate path; a genuine decision change still gets its own event. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Record production deployment dpl_HY5319wSDVw3M4ibBU42JrSTgw4e (2026-09-14T22:16:59Z), the 60-minute measurement window, /api/geo check, and the D-10 netlog proof (16 lines / 3 capture requests vs a baseline of zero) - Record both HogQL queries from Part 1 item 7 run against project 337485: pageview count 25 across 3 non-consent-region countries (PASS x2), consent_gate_path breakdown (geo-non-consent-region only, TCF path unobserved), and per-event ingestion status for the five previously-never-ingested events - Record the Vercel Web Analytics ratio threshold as PENDING (not evaluated), with the reason the figure could not be read from this session - Record the unintended preview-deployment deviation (production unaffected) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cs figure - Record the post-deploy code-review follow-up (02.1-REVIEW.md / 02.1-REVIEW-FIX.md) as not yet in the measured production deployment, requiring a separate developer-approved deploy - Record the Part 1 item 5 Search Console row as no-data (no GSC MCP tool available this session; no route/metadata surface changed this phase) - Record Phase 2.1 as NOT SIGNED: two of item 7's three thresholds pass, the Vercel Web Analytics ratio is pending, so the phase-level sign-off is deliberately withheld per this document's own recording rule; list the remaining steps to close the gate - Append an OPS-01 addendum to REQUIREMENTS.md noting the PostHog criterion remains not-yet-met for Phase 2.1 pending that same figure, without rewriting the existing traceability row (D-09) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… plan Two of item 7's three thresholds pass with counted PostHog evidence (25 pageviews across 3 non-consent-region countries); the Vercel Web Analytics ratio is recorded PENDING because it could not be read from this session. Phase 2.1 is left deliberately unsigned rather than fabricated as complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Four additive gap_closure plans from 02.1-VERIFICATION.md (gaps_found): score the pending Vercel ratio and sign or FAIL (05), drive theme/Timeline events on prod and re-query (06), approval-gated redeploy of the five review fixes with a fresh item-7 window (07), EEA/UK TCF human observation (08). Plan-checker: VERIFICATION PASSED. ROADMAP lists the new plans; STATE.md records 8 plans, ready to execute. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Vercel Web Analytics figure (23 page views, upper bound) recorded in docs/OPS-01-SHIP-GATE.md Part 4 with URL, range, timezone, and reader - All three item 7 thresholds scored on separate lines: PASS, PASS, PASS (25 pageviews >= 20; 3 countries >= 2; 25/23 ~= 108.7% >= 50%) - Phase 2.1 Sign-off SIGNED (2026-09-15, gap-closure 02.1-05), appended below the untouched 2026-09-14 NOT SIGNED section - .planning/REQUIREMENTS.md gains OPS-01 Addendum 2 recording the real met-date, superseding the 2026-09-14 addendum by addition Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eline events - theme_changed, timeline_viewed, timeline_filter_used observed with count>=1 and consent_gate_path=geo-non-consent-region in window 2026-09-15T18:26:27Z-18:28:57Z - timeline_error recorded not triggered by design (optional error step skipped) - consent_resolved / consent_unavailable recorded structurally unobservable from non-consent-region egress, deferred to 02.1-08 - route: automation-performed (developer authorized 'you drive it'; orchestrator-driven headless Chrome scoped to parseforge.gg only) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, and AdSense account config plan Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… AdSense account config plan
…the ad build Append Part 6 subsections recording every Part 1 item 1 command (tsc, lint, 284/284 tests, theme-parity, token-audit, protected-elements) green against the ad build (HEAD 3a5be52), plus the pre-deploy grep proof that all four ad_slot_* events have exactly one true capture call site in AdSlot.tsx with only three low-cardinality properties (route/slot_id/consent_gate_path) and zero report-code/player-name/URL leakage. Records, rather than silently works around, the plan's own literal grep gate false-negative on the ad_slot_filled/ad_slot_empty ternary — same pattern as Part 2's consent_resolved precedent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… box-size defect found Deploy dpl_689q6eqCETYSzy1Yfe9THKNpAJdP (preview only, no --prod) with NEXT_PUBLIC_ADS_ENABLED/NEXT_PUBLIC_ADSENSE_PUB_ID baked in for the first time, and record into Part 6: route contracts for /tbc-audit, the demo analyze page and /ads.txt; a new CDP-based measurement technique (Node's built-in fetch + --experimental-websocket, no package installed) proving every reserved box actually renders at 300x250 on both desktop and phone viewports; the two-direction netlog proof (adHostRequestCount=3 for an admitted visitor, 0 for one whose consent decision fails closed via a network-layer-blocked /api/geo); and the report-only CSP violation harvest (ep1/ep2.adtrafficquality.google, www.google.com as new Google ad hosts not yet in the CSP allowlist, left for the checkpoint decision; vercel.live and wow.zamimg.com as non-ad, non-blocking findings). The box measurement surfaces a real defect: AdSlot.tsx's reserved-box <div> carries an inline style={{width: base.width, height: base.height}} that outranks the responsive md: Tailwind classes at every viewport, so no slot ever grows to its declared desktop size. Recorded as a failure per this plan's own "a mismatch is a failure, not a rounding note" instruction, not fixed here (app/ is out of this dispatch's scope) — surfaced at the Task 3 checkpoint for the developer's decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…new findings Record the both-theme structural check (no border/background on the reserved box in either theme, verified via computed style), a six-row phone reachability table at 384px with distance-to-nearest-ad-box for every protected element (smallest clearance 117px, no overlap), and an adjacency read (no ad box inside a table or between a share control and its target). Every judgment-only line is marked "awaiting the developer" rather than folded into a pass, per this document's own recording rule. Also logs two findings to WINDOWS.md: the AdSlot.tsx inline-style box-size defect (already recorded in Part 6's measured-box section), and a pre-existing, non-ad-related horizontal-overflow on / and /tbc-audit caused by an unwrapped example-URL <code> string in ReportUrlForm.tsx, found while measuring ad-box adjacency but outside Phase 4's scope to fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…illed AdSense frames
Orchestrator-authorized deviation (Rule 1 auto-fix) to the docs-only scope
of 04-06-PLAN.md: fixes the two real defects this plan's own preview
verification found, per the developer's checkpoint answer.
- AdSlot.tsx's reserved <div> no longer sets an inline
style={{width, height}} pinned to the `base` size — that inline style
outranked the md:w-[...]/md:h-[...] Tailwind classes at every viewport,
so a box never reached its declared md size (Part 6 "Measured box
dimensions" MISMATCH). Size is now controlled by boxClass alone.
- AD_SLOTS' boxClass gains a max-w-full safety net on every slot: the
728px-wide -mid banner slots sit in a container narrower than 728px
between the md breakpoint (768px) and ~776px (layout's sm:px-6 padding),
so without a cap the box would overflow that gap instead of shrinking to
fit it.
- AdSlot.tsx now hides the mounted <ins> (visibility: hidden) until a
MutationObserver confirms data-ad-status="filled"; an "unfilled" status,
or the 10s observe ceiling passing with no resolution, both leave it
hidden. Fixes the white-rectangle defect the developer reported in dark
mode: Google's adsbygoogle.js mounts a blank iframe inside the <ins> the
instant it starts processing a pushed unit, well before fill status
resolves, and our transparent wrapper could not restyle that iframe's
interior. The reserved wrapper div keeps its declared box size
regardless (D-04, zero layout shift) — only the <ins>'s own paint is
gated.
- lib/ads.test.ts: asserts every slot's boxClass carries max-w-full.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y CSP Orchestrator-authorized deviation to the docs-only scope of 04-06-PLAN.md, per the plan's own Task 2 CSP violation harvest against the first Phase 4 preview (docs/OPS-01-SHIP-GATE.md Part 6): adds ep1.adtrafficquality.google (observed under connect-src) and ep2.adtrafficquality.google (observed under script-src and frame-src) to those exact directives. Both are Google's "sodar" ad-viewability/anti-fraud beacon network for AdSense, seen only on the admitted (ads-loading) netlog run. www.google.com was also observed on that run but the harvest's own top-5 sample did not capture which directive it violated, so per the harvest's stated disposition it is deliberately NOT added here. Policy stays report-only; promotion to enforcing is Phase 7, unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records checkpoint round 2: the box-size and dark-mode-white-block fixes (249058e) plus the CSP allowlist fix (349c2e9), a fresh preview deployment (dpl_9SxD4PEAPUHv8aghVucyjqJ3LQoV) built from both, and an honest blocker this session hit trying to re-run the plan's own Task 2 netlog/measured-box/ CSP-harvest procedure against that new preview. The one documented method for sourcing the Vercel SSO automation-bypass secret (`vercel project protection ... --format json`) was denied this session by the coding tool's own Bash-permission auto-classifier (Credential Materialization) — an environment-level guard, not a plan decision — and this dispatch's own instructions separately forbid touching project-protection settings or rotating the secret, so no alternative was attempted. `vercel env pull` was retried and, as Parts 4/5 and this plan's own Task 2 already recorded, still does not surface that variable. Recorded as not-observed with the exact closing test, per this document's own discipline, rather than skipped or folded into a pass. As supplementary, explicitly-non-preview evidence, a local next dev server against /tbc-audit (public NEXT_PUBLIC_ADSENSE_PUB_ID, no secret) confirms the box-size fix live: every slot now measures its declared md size at 1280px and its declared base size at 390px, the opposite of the round-1 MISMATCH. The dark-mode fill/hide behavior could not be exercised live locally (no <ins> ever mounts without a real geo-consent admission) and remains verified by code review only until the preview-level check can run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cross-phase defect register entry for the unrun-verify recorded in docs/OPS-01-SHIP-GATE.md this session: 04-06 Task 2's netlog/measured-box/ CSP-harvest procedure could not be re-run against the second preview because this session's Bash-permission auto-classifier denied the one working method to source the Vercel SSO bypass secret. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…never let a blank AdSense frame paint Round-2's `visibility: hidden` on the <ins> was defeated the instant adsbygoogle.js processed the pushed unit: it inserts a child div (`#aswift_N_host`) inside the <ins> with its own explicit `visibility: visible` inline style, and CSS lets any descendant re-assert visibility regardless of how many ancestor levels up it was set hidden — confirmed live via CDP against a local reproduction (data-ad-status still unresolved, the injected host div already visibility:visible). Replaces the visibility toggle with an occluding cover: a plain later sibling of the <ins>, absolute/inset-0/z-10/bg-background, which Google's script can never reach and which paints on top of the <ins>'s entire subtree by document order regardless of what Google injects inside it. The wrapper also gains `relative overflow-hidden`. The MutationObserver additionally requires a rendered, non-zero-size iframe before trusting a "filled" status, not the attribute alone. Verified via pixel-sampled local screenshots (dark and light, desktop and phone): the ad box's average color is byte-identical to the surrounding page background in every reading, 0% white-pixel deviation in dark mode, and the light-mode reading matches the page's own near-white --background token exactly rather than a distinguishable white rectangle. Zero layout shift confirmed. Box sizes unchanged (Defect A untouched). Full standing gate re-run clean (tsc, lint, 285/285 tests, theme-parity, token-audit, protected-elements). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…review) Round-2's ins-level visibility:hidden fix (249058e) did not hold on the second preview: adsbygoogle.js injects a child div inside the <ins> with its own explicit visibility:visible, which CSS lets override an ancestor's hidden value regardless of tree depth. Diagnosed live via a local next-dev + CDP reproduction (no bypass secret needed) that admits a non-EEA visitor by spoofing x-vercel-ip-country over CDP — the previous session's attempt at this hit an unexplained 404 that did not reproduce this session. Records the round-3 root-cause finding, the occluding-cover fix (cfb6d05), pixel-sampled local verification in both themes and both viewports (0% white-pixel deviation, exact page-background match, zero layout shift, box sizes unchanged), the re-run standing gate output, and the third preview deployment (dpl_2CU68Zw3yFuxq7qqMjxNeTjT1VC1, never --prod). The preview-level SSO-gated netlog/measured-box/CSP re-verification remains blocked by the same bypass-secret gap WINDOWS #14 already records — that entry stays open. WINDOWS #12 (Defect A, box sizing) is marked fixed; a new #15 records this round's Defect B finding and fix, pointing back to #14 for the still-open preview-level closing test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… background Round 3's flat bg-background cover occludes the page's fixed .bg-noise::before grain layer, painting a textureless patch in an otherwise-grainy page — the subtle box a developer caught on the third preview. Pixel-sampled CDP screenshots (production build, unclipped full-viewport capture — a `clip`- scoped capture was found this session to silently miss the fixed noise layer under headless Chrome + software rendering) confirmed it numerically: cover stddev 0 vs a control region's real ~0.5-0.8 stddev and ~1-3 RGB-level warmer mean, in both themes at both viewports. Adds a local (position: absolute, not fixed — avoids escaping overflow-hidden) equivalent of the same noise tile at the same 0.03 opacity, scoped to the cover itself. Re-verified: cover and control now match within ~0.1-0.2 RGB levels and near-identical stddev in all four configurations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records round 4's diagnosis (production build achieved locally this time), the two CDP/headless-Chrome quirks found and worked around (clip-scoped screenshots miss the fixed noise layer; setDeviceMetricsOverride desyncs at 390px with mobile:true), the before/after pixel-sampled numeric tables across both themes and both viewports, the clean gate re-run, and the fourth preview deployment (dpl_9hkgsvPz57brpYzdHdFf3x7GM56Z). Adds WINDOWS #16 for the grain-mismatch defect and its fix, closing test = developer review of the fourth preview. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oop) Closes 04-06 with the developer's final verdicts: the fourth preview's ad boxes are confirmed fixed in both themes, and /privacy reads true. Records the full 4-round evidence trail (box-sizing fix, two-attempt occluding-cover fix) and the permanent WINDOWS #14 evidence-chain gap honestly rather than papering over it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…operator-override deploy decision - Thread table gains a 2026-09-22 no-reply row: no request has ever been sent (draft remains unsent since the developer's 2026-09-21 deferral), so no mailbox was checked and no reply exists - OPS-01 Part 6 gains "### Deploy decision (Phase 4, 2026-09-22)": records that this state doesn't cleanly match any of Task 1's five decision options (closest in shape to silent-before-14, since the D-11/D-14 clock never started), and that the developer explicitly overrode the plan's default wait behavior with two verbatim, dated statements after being told the real consequence (RPGLogs could revoke the WCL API client entirely) - Never uses "approved" to describe the correspondence outcome — only, where used, to describe the developer's own deploy go-ahead, a distinct fact - Names Task 2's literal precondition mismatch explicitly as a second, distinct deviation Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…evidence - Production deploy dpl_HRmX5EMk5aKgXtDp4z9FYW6jXNih (commit cc931c0, 2026-09-22T09:23:24Z), aliased to parseforge.gg, ran under the developer's verbatim "deploy" (2026-09-22) go-ahead — the same statement recorded as the operator-override authority above - npm run protected-elements: 25/25 PASS against production, including both live-fetch rows (tbc-audit-mid + tbc-audit-end present on /tbc-audit, analyze-end present on the demo analyze page, no unknown slot markers) - The plan's own curl+node route-contract verify block literally GATE-FAILs on tbc-boxes; root caused and independently corrected: grep -c counts matching lines, not occurrences, and Next.js production HTML is a single unbroken line, so it can never report more than 1 regardless of how many data-ad-slot markers that line contains. The true occurrence count is 2, cross-confirmed by protected-elements's own live-fetch check using its own (correct) method. No rollback runbook trigger applies; recorded as a script counting artifact, same category as 04-01/04-05's own documented gate-script discrepancies, not edited to force a pass - analyze-boxes=1 (analyze-end only, analyze-mid absent from pre-hydration SSR by design since it mounts only after AnalyzeClient's client-side report fetch resolves) satisfies the plan's own >=1 threshold and is expected behavior, not a regression - npm run seo-invariants --base https://parseforge.gg: every route reads "same", no unexplained diff on the ad routes or /privacy - Two harness permission-classifier denials on read-only follow-up commands (vercel inspect, an initial npm run protected-elements attempt) recorded transparently; both succeeded on direct retry with no workaround; the deploy command itself was never denied Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ase 4 addendum Task 3: OPS-01 item 7 PostHog thresholds recorded NOT EVALUABLE (no PostHog query channel in this dispatch), with Vercel Web Analytics hourly traffic and a real production CDP ad-request capture recorded as supporting context, not a substitute. Search Console recorded no-data (no GSC access). Production CSP report-only violation harvest compared against 04-06's preview. Day-2/day-7 CWV re-read obligations dated against the real deploy time, plus today's informal CLS readability check (no automatic-pause trigger fires). Phase 4 sign-off row: NOT SIGNED, with every open row named and its closing test. REQUIREMENTS.md gains a dated Phase 4 addendum closing MONY-02's ad-serving-and-placement half on counted evidence while leaving MONY-03's monitoring half and R0-1 explicitly open — nothing is marked complete on the strength of the deploy alone. WINDOWS.md gains three open entries (#17-19) for the PostHog/GSC access gaps and the scheduled CWV re-reads. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ent-side report navigation
app/components/AdSlot.tsx's push/observe effect listed `pathname`/`spec.route`
in its dependency array. AnalyzeClient is rendered with no `key`, and
ReportUrlForm navigates report-to-report via router.push() (no remount), so
navigating to a different report re-ran the effect while `status` was still
"loaded" from the previous report — pushing the same already-loaded `<ins>`
node to adsbygoogle a second time (Google's "already have ads in them"
duplicate-request failure mode), against an account still in initial review.
- Removed pathname/spec.route from the effect's deps ([status, id] only),
with a documented eslint-disable-next-line matching this repo's existing
suppression style. Route is still captured correctly via closure for the
PostHog ad_slot_* events.
- Added key={reportCode} to <AnalyzeClient> in the analyze page as
defense-in-depth: traced AnalyzeClient's own selectedFight/selectedSource
state (a one-time lazy useState initializer) and usePlayerAnalysis/
useRaidOverview/useCLA's result-clearing effects (keyed on selectedFight
changing value, not reportCode) and confirmed a second, related bug — a
bare report-to-report navigation with no fight/source URL params leaves
stale analysis results from the OLD report displayed under the NEW
report's heading. The key forces a full remount, fixing both bugs.
Verified: npx tsc --noEmit clean, npm test 285/285, npm run lint 0 errors
(1 pre-existing unrelated warning), npm run protected-elements 25/25.
No new automated test added: vitest runs with environment: "node" (no DOM),
no @testing-library/react or *.test.tsx exists in this repo, so there is no
infrastructure to simulate a mount + non-remounted navigation and assert
adsbygoogle.push call count. A source-regex test was considered and
rejected as not actually proving the fix.
Committed but NOT deployed — production still runs the pre-fix code.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the post-launch AdSlot duplicate-push hotfix (commit 1836bed) as a dated Part 6 subsection: the bug, the second related state-leak bug found during investigation, the fix, local verification results, and an explicit NOT YET DEPLOYED status pending developer deploy confirmation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t ad refusal
app/components/AdSlot.tsx's consent-subscription effect set refusedTerminally
= true on ANY gate path failing shouldLoadAds — but that's true for both
"tcf-reject" (a genuine, deliberate decline) and "tcf-timeout" (a PROVISIONAL
fail-closed state per lib/consent.ts's own doc comment on ConsentGatePath/
deriveConsentGateOutcome, which a later real TCF resolution can legitimately
supersede — startConsentListener's __tcfapi listener deliberately has no
re-entry guard so that late resolution still republishes). Because the
subscriber's first line was `if (cancelled || refusedTerminally) return;`, a
"tcf-timeout" publish permanently dropped a later legitimate "tcf-accept"
republish for that mount: a legitimately-consenting EEA/UK visitor whose CMP
resolved slowly silently lost ad eligibility for the rest of that page view,
even though PostHogProvider correctly treated them as opted in everywhere
else. Real consent/revenue-eligibility mismatch, not a security issue — and
unlike this dispatch's other AdSlot.tsx fix, this one is currently live and
reachable on any ordinary page load, no special navigation required.
- Added lib/ads.ts#isTerminalRefusal(gatePath) — true only for "tcf-reject".
- AdSlot.tsx's first effect now latches refusedTerminally (and collapses a
collapsible slot) only when isTerminalRefusal(path) is true. A
"tcf-timeout" publish now does nothing terminal — the subscription stays
live for a possible later real resolution.
- Tradeoff documented inline: a collapsible slot whose CMP never resolves
further after a timeout now waits indefinitely (no ceiling covers this
pre-admission case) rather than eventually collapsing — accepted as
consistent with how a non-collapsible slot already behaves on genuine
refusal, not a follow-up bug.
- lib/ads.test.ts: isTerminalRefusal("tcf-reject") true, ("tcf-timeout")
false, plus a consistency check against shouldLoadAds.
Verified: npx tsc --noEmit clean, npm test 288/288 (3 new), npm run lint 0
errors (1 pre-existing unrelated warning), npm run protected-elements 25/25.
Committed but NOT deployed — production still runs the pre-fix code.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-01 Part 6 - Hotfix #1 (AdSlot duplicate-push): added an explicit severity correction — a second review pass confirmed there is currently no in-app link that navigates directly analyze-page-to-analyze-page without unmounting via / or /tbc-audit first, so this bug is latent/defensive (a future "related reports" link would make it live), not currently reachable in production today. The fix still ships; only the framing is corrected. - Hotfix #2 (commit b9d0fb3, TCF-timeout latched as permanent refusal): new dated subsection — the bug, why it's currently live and reachable (unlike hotfix #1), the fix, the documented indefinite-wait tradeoff for a permanently-stuck collapsible slot, test coverage, verification results, and an explicit NOT YET DEPLOYED status. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the production deploy of hotfixes #1 and #2 (AdSlot duplicate-push guard + key={reportCode} remount fix, and the TCF-timeout terminal-refusal fix) plus their two docs recording commits, as dpl_2gh6B8Eqi7MN8x3aydhpFQTN8bsR at commit 9911eae. Documents the developer's fresh "deploy" confirmation, that the deploy command was run by the orchestrator after being denied to this dispatch's own executor, and full post-deploy verification (protected-elements 25/25, route curls, seo-invariants, commit-sha confirmation). Updates the two "NOT YET DEPLOYED" notes in the hotfix subsections to point at this record. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch carries Phase 4 (Ads Live) plus the still-unmerged Phase 2.1 and Phase 3 work that
has accumulated on
growth/phase-2-review-fixessince PR #16. Phase 4's own commits (04-01through 04-07 Task 1) are the focus of this PR:
pre-ad Core Web Vitals baseline, D-10 rollback trigger, D-08 rollback runbook, and the D-11/D-14
observation and escalation clocks into
docs/OPS-01-SHIP-GATE.mdPart 6.report-only CSP entries for Google ad hosts,
/ads.txt, and the ad-slot whitelist gate inscripts/protected-elements.mjs.parse-lensengine, and a Redis-backed WCL rate-budgetgate — tested, unwired, imported by nothing yet (R0-3, by design).
/tbc-auditand/analyze/[reportCode], rewrote/privacy's advertising and Warcraft Logs data-sourcedisclosure, and wired the AdSense account (unit ids, env vars, Auto ads confirmed off).
bug and an occluding unfilled-ad-frame visual mismatch, both confirmed by the developer's
first-hand review of the fourth preview.
approval request drafted in 04-01 was never sent, so there is no reply) and the developer's
explicit, twice-given, dated operator-override decision to proceed with the production ad
deploy without that approval on record, after being told the real consequence (RPGLogs could
revoke the WCL API client entirely). See
docs/OPS-01-SHIP-GATE.mdPart 6### Deploy decision (Phase 4, 2026-09-22)for the full record.Earlier commits on this branch (Phase 2.1's consent-gate hotfix, Phase 3's share loop) are
already deployed to production in prior sessions but have not yet been merged to
mainvia aPR — this PR brings
mainup to date with everything currently live plus Phase 4's work so far.Test plan
npx tsc --noEmitcleannpm test— full vitest suite passing (285/285 as of 04-06)npm run lint— no new findings (pre-existing debt incomponents/ui/meteors.tsxandlib/analysis-engine.tsleft as-is)npm run protected-elements— all rows passingof live traffic to measure)
🤖 Generated with Claude Code