fix(site): make a refusal take effect - #227
Merged
Merged
Conversation
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 the preference is mirrored across tabs. Nothing did that. 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
4 tasks
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 changed
Two ways an explicit Do not allow failed to take effect. Both are in
site/src/components/Analytics.astro; neither is new. This component is shared with onebox, which carries an identical copy; the same pair is labstack/onebox#157.A refusal a browser would not store.
readPreference()fell back to the in-memory answer only whenlocalStoragethrew. A browser can let the read succeed and refuse the write: Safari's private mode and an exhausted quota both do. TherewritePreferencekept the choice in memory,setItemthrew and was swallowed, and the nextreadPreference()readnullback from storage and returned it.apply()then treated the reader as unanswered: the banner stayed on screen, and in a country the code classifies asnot_requiredthe fallback calledmayRun("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.A withdrawal that never reached the open tabs. The file has claimed since it was written that the preference is mirrored across tabs. Nothing did that. The
storagelistener lived insidesubscribe, whose only caller is the privacy page's control, and that subscriber redraws its own status text rather than re-applying the preference. A reader with a documentation page open who went to/privacyand denied kept being measured in the tab they left behind, until it happened to reload. The preference is now applied on the storage event.Verification
just site-buildpasses.The first failure needs
getItemto succeed whilesetItemthrows, which is why the originalcatch-only fallback missed it: nothing throws on the read path.memoryPreferenceis assigned only inwritePreference, so falling back to it cannot manufacture a grant the reader never made, and a valid stored value still wins.memoryPreferencestartsnull, 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, nolocalStorage.clear), so a persisted answer is never revoked out from under an open page.The privacy page's control reads through the exposed
preferenceAPI, which isreadPreferenceitself, so the banner and the control cannot disagree.apply()on the storage event handles both directions: adeniedwritten elsewhere callsstop()and hides the banner, agrantedcallsstart().just check— site build verified viajust site-build; the change is a single site component with no Go, API, or UI surface.just test-racewhen auth, API, ingest, query, MCP, or agent paths changed — not applicable, none touchedUser-facing behavior and configuration docs are current
No credentials, private telemetry, host details, or enterprise-only source are included
API, migration, ingest, MCP/AG-UI, or release-contract changes are called out — none
https://claude.ai/code/session_013MqwrF5NA179khDtEQdib5