Skip to content

fix(site): keep a refusal a browser would not store - #157

Merged
vishr merged 3 commits into
mainfrom
fix/consent-memory-fallback
Sep 6, 2026
Merged

fix(site): keep a refusal a browser would not store#157
vishr merged 3 commits into
mainfrom
fix/consent-memory-fallback

Conversation

@vishr

@vishr vishr commented Sep 6, 2026

Copy link
Copy Markdown
Member

What this changes

An explicit Do not allow could be discarded, and analytics could start anyway.

readPreference() fell back to the in-memory answer only when localStorage threw. A browser can let the read succeed and refuse the write: Safari's private mode and an exhausted quota both do. There writePreference kept the choice in memory, setItem threw and was swallowed, and the next readPreference() read null back from storage and returned it.

apply() then treated the reader as unanswered. Neither the denied nor the granted branch ran, so the banner stayed on screen, and in a country the code classifies as not_required the fallback called mayRun("not_required", null), which is true, and loaded the tag.

The stored value still wins when there is one. The change is that the answer given on this page governs it whenever storage has none of its own, however storage failed to keep it.

Why this is correct

  • The failure needs getItem to succeed while setItem throws, which is why the original catch-only fallback missed it: nothing throws on the read path.
  • memoryPreference is assigned only in writePreference, so falling back to it cannot manufacture a grant the reader never made, and a valid stored value still wins.
  • memoryPreference starts null, so a first visit with empty storage still reads as unanswered and the banner still asks.
  • Nothing in the site removes the key (no removeItem, no localStorage.clear), so a persisted answer is never revoked out from under an open page.
  • The privacy page's control reads through window.oneboxAnalytics.preference, which is readPreference itself, so the banner and the control cannot disagree.
  • The cross-tab path already works here: 8146452 applies the preference on the storage event, so a withdrawal in one tab still stops the tag in the others.
  • just check passes, including the site build.

Effect on the safety envelope

Makes a refusal effective where it was being dropped. Nothing new is stored, no new request is made, and what is sent when measurement is allowed is unchanged.

fanout carries an identical copy of this component and has the same bug: labstack/fanout#227. It also still needs the cross-tab fix this repository already has.

Checklist

  • just check passes locally.
  • Tests cover the new behaviour, including the failure paths.
  • Generated documentation is current (just check verifies this).
  • I have accepted the CLA, or will when the bot asks on my first pull request.

https://claude.ai/code/session_013MqwrF5NA179khDtEQdib5

readPreference fell back to the in-memory answer only when localStorage threw.
A browser can let the read succeed and refuse the write — Safari's private
mode and a full quota both do — and there the stored value came back null, so
an explicit "Do not allow" was read as no answer at all: the banner stayed up
and, outside a prior-consent country, the tag loaded anyway.

The answer given on this page now governs it whenever storage has none of its
own, however storage failed to keep it.

Claude-Session: https://claude.ai/code/session_013MqwrF5NA179khDtEQdib5
This file has claimed since it was written that every other open tab is told
about the change and acts on it. Nothing did. The storage listener lived
inside subscribe, whose only caller is the privacy page's control, and that
subscriber redraws its own status text rather than re-applying the preference.

So a reader with a documentation page open who went to /privacy and denied
kept being measured in the tab they left behind, until it happened to reload.
Applying the preference on the storage event is what makes the claim true.

Claude-Session: https://claude.ai/code/session_013MqwrF5NA179khDtEQdib5
@vishr vishr changed the title fix(site): keep a refusal a browser would not store fix(site): make a refusal take effect Sep 6, 2026
@vishr vishr changed the title fix(site): make a refusal take effect fix(site): keep a refusal a browser would not store Sep 6, 2026
@vishr
vishr merged commit f9e9817 into main Sep 6, 2026
5 checks passed
@vishr
vishr deleted the fix/consent-memory-fallback branch September 6, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant