Repository navigation
fix: mobile anchor ad CLS, plus the Singapore calculator cluster - #29
Merged
Merged
Conversation
Production mobile Lighthouse measured CLS 0.233 on calculator pages when
AdSense served and 0.045 when it did not. The largest shift source was
main#main-content > div.lg:hidden at 0.191, the mobile sticky anchor ad.
AdUnit gives every slot .ad-container { min-height: 100px }. The anchor does
not use the reserveHeight path, so it paints 100px tall, then BaseLayout's
3000ms fill check collapses it when the slot comes back unfilled. A bar that
paints and then collapses shifts the viewport; one that starts at zero and
appears does not.
Zeroing min-height for the anchor only. `body ` prefix is needed because
AdUnit's own scoped rule emits as .ad-container[data-astro-cid-*] at equal
specificity and is inlined after the global sheet.
Applies to all 171 calculator pages. Local builds cannot reproduce the defect
because AdSense does not serve to localhost, so the gate is a source check and
verification is a production Lighthouse run after deploy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5
65 clicks / 78,775 impressions over Aug 24 to Sep 20, against 111 / 112,225 on the matched prior window. Clicks down 41 percent. Step change on 2026-09-02, roughly 4,300 to 2,350 impressions a day. Of the ~24k lost impressions, ~11k was non-anglosphere long tail at position ~65 with zero clicks, and ~6k was discount-calculator instant-answer noise whose position improved 18 to 12 at the same time. Average position held or improved on every page that lost clicks, sitewide 41 to 27. Demand-side seasonality in the summer party and event cluster plus Google pruning worthless impressions, not a rank regression. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Deploying boring-maths with
|
| Latest commit: |
5476560
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://56c754c7.boring-maths.pages.dev |
| Branch Preview URL: | https://fix-mobile-anchor-cls.boring-maths.pages.dev |
Records three cuts of the 28d export that all say on-page work on existing pages is spent, the country-cluster control arm (44 calculators, 2 clicks), and four lanes of Singapore calculator research. Verdict: do not build the Singapore cluster. The niches are held by SG-native specialists, one of whom runs 219 calculators. Our Singapore position advantage is on generic pages, so an SG cluster would leave the market we rank in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5
Ten Singapore calculators plus a guide page that links them all.
Registry goes 171 to 181; the guard test is bumped with it.
Calculators:
take-home pay, employer cost, income tax, buyer stamp duty,
seller stamp duty, CPF retirement sums, notice period,
AWS bonus, GST, utility bill
Rates are dated and primary-sourced: CPF from 1 Jan 2026, BSD from
15 Feb 2023, ABSD from 27 Apr 2023, SSD split at 4 Jul 2025, GST 9%
from 1 Jan 2024, SP Group Jul to Sep 2026, PUB from 1 Apr 2025.
Three places where the current tools are stale or wrong:
* ERS is 4x BRS, not 3x, since 1 Jan 2025
* SSD needs the purchase date because the table split on 4 Jul 2025
* the CPF page refuses to output a CPF LIFE payout, because CPF
Board publishes no formula for it
Known gap: the YA2026 income tax brackets are confirmed against two
independent secondary sources, not IRAS directly. Their rate page is
a JavaScript app and would not render. Recorded in ROADMAP 4.7.
/guides/best-singapore-calculators keeps all ten off the orphan list.
llms.txt gains a Singapore section and the count moves to 181.
ROADMAP 4.5 records this as Keith's call against my own read of the
data, keeps that analysis intact in 4.5a, and fixes a kill gate in
section 5: measure at 8 weeks, pass is weighted position under 25
with any clicks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5
Nothing on the site told a search engine which nation a calculator page was for, so the Singapore pages and our own generic pages competed for the same queries with no signal to separate them. The cluster is derived in BaseLayout from the canonical path it already holds, so there are zero per-page edits and no new prop across its 20+ consumers. Three families have a genuine national counterpart: net pay, employer cost, and consumption tax. That is 17 reciprocal tags across 7 pages; the other 233 built pages emit nothing. UK and EU are reserved codes that Google ignores, and the United Kingdom's region code is GB, so the registry country is mapped explicitly rather than lowercased. A test pins that at the map level, so a country added later cannot reintroduce a silently dead tag. No x-default. It marks a page that targets no particular locale, and no page here qualifies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5
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.
What
Production mobile Lighthouse on calculator pages measured CLS 0.233. Google's "good" bar is under 0.1. The result is bimodal and was confirmed on two different pages: 0.233 when AdSense serves, 0.045 when it does not.
Shift sources:
main#main-content > div.lg:hidden(mobile sticky anchor ad)header > p.text-xl(font-swap FOUT)Root cause
AdUnit.astrogives every slot.ad-container { min-height: 100px }. The two in-content slots opt into thereserveHeightpath, which holds that box whatever the fill state, so they cost nothing. The mobile anchor inCalculatorLayout.astrodoes not use it.So the anchor paints 100px tall, then
BaseLayout's 3000ms fill check resolves it: unfilled setsdisplay: none, filled adds.has-anchor-adto<body>. Either branch changes a painted element's footprint after first paint.An element that appears has no prior position and contributes no CLS. One that paints and then collapses does. The defaults were inverted.
Fix
The
bodyprefix is load-bearing: AdUnit's own scoped rule emits as.ad-container[data-astro-cid-rsu4czfl], which ties at specificity (0,2,0) and is inlined after the linked global sheet, so an unprefixed selector loses on source order. Verified indist/:Expected: 0.233 to ~0.042 across all 171 calculator pages. Left the 0.045 font shift alone; the anchor fix alone clears the threshold.
Verification
tests/seo/mobile-anchor-cls.test.ts, mutation-tested: 2 of 3 assertions fail against the pre-fix CSS.astro previewmeasures 0.034 regardless. Production is the only valid measurement surface; a mobile Lighthouse run after deploy is the real check.Also in this branch
docs/gsc-analysis-2026-09-20.md. Clicks are down 41 percent (65 vs 111) on a hard step change dated 2026-09-02, but average position held or improved on every page that lost clicks and sitewide went 41 to 27. The lost impressions were non-anglosphere long tail at position ~65 with zero clicks plus discount-calculator instant-answer noise. Seasonality and Google pruning junk impressions, not a rank regression.Also in this branch: the Singapore calculator cluster
Ten Singapore calculators plus a guide page, commit 03a6d4a. Registry 171 to 181,
guard test bumped with it.
take-home pay, employer cost, income tax, buyer stamp duty, seller stamp duty,
CPF retirement sums, notice period, AWS bonus, GST, utility bill.
Rates are dated and primary-sourced: CPF from 1 Jan 2026, BSD from 15 Feb 2023, ABSD from
27 Apr 2023, SSD split at 4 Jul 2025, GST 9 percent from 1 Jan 2024, SP Group Jul to Sep
2026, PUB from 1 Apr 2025.
Three places the incumbent tools are stale or wrong, which is the only edge a new entrant
has: ERS is 4x BRS not 3x since 1 Jan 2025; SSD needs the purchase date because the table
split on 4 Jul 2025; the CPF page refuses to output a CPF LIFE payout because CPF Board
publishes no formula for one.
Known gap: the YA2026 income tax brackets rest on two independent secondary sources that
agree, not on IRAS directly. Their rate page is a JavaScript app and would not render
across two fetches. Recorded in ROADMAP 4.7.
/guides/best-singapore-calculators is what keeps all ten off the orphan list, since the
gate counts only calculator pages and guides as referrers.
Verification: full suite 198 files / 1268 tests green; build 239 pages with all four
postbuild gates green, including 181 calculators checked, 0 known orphans.
This was built on Keith's direction against my own read of the GSC data. ROADMAP 4.5a keeps
that analysis intact and section 5 fixes a kill gate in advance: measure 8 weeks after
deploy, pass is impression-weighted position under 25 with any clicks, fail is position 50+
with zero clicks.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XMvYP5cw9yaSpCH2bR2EU5