Feat/503 consent pr2 - #566
Open
nhyiramante1 wants to merge 8 commits into
Open
Conversation
…done snapshot) NOT ready to merge. Half-done work parked to finish Mon 2026-07-27. Includes: - onboarding consent gate (no Skip; visibility derived from hasSetConsent) - shared useConsent save hook (gate + account page, one code path) - standalone account & privacy page wiring + navbar Account entry - cross-tab consent sync (consentSync.ts + provider listener) - device-auth tweaks Open review items — recorded, deliberately NOT addressed yet: - in-flight consent choice bug still unfixed - reconsider whole cross-tab sync vs. dropping target="_blank" on the Account link (navigate in-context instead) — reviewer's simpler interim - broadcastConsentChange: `storage = localStorage` default arg evaluates before the try, so catch doesn't cover a throwing global - LIVE-verify Better Auth returns consentUpdatedAt on get-session (merge prereq) Still to do Mon: finish code review, e2e + browser QA (two-tab sync, get-session network check, cross-user gate lifecycle). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> # Conflicts: # backend/.gitignore # frontend/src/components/navbar/index.tsx # frontend/src/pages/app/index.tsx
…st-effort Two failure paths found reviewing the onboarding gate: - The gate's chooser stayed live while the save was in flight. Continue commits the level as it read at click time, so a pick made mid-request rendered as selected while the server recorded the earlier one -- and on success the gate unmounts, hiding the divergence. Disable it while saving, matching the account page. - broadcastConsentChange resolved localStorage in a default parameter, which is evaluated outside the function's try. Merely touching localStorage throws in a partitioned context (Office task-pane iframe, Safari private mode), and that throw escaped into useConsent.save's catch -- rolling the level back and reporting failure after the POST had already succeeded. Resolve storage inside the try so the broadcast stays best-effort. Two regression tests cover the storage cases: a throwing setItem and a throwing localStorage getter. Co-Authored-By: Claude <noreply@anthropic.com>
…entity chip
The nav strip switches in-app pages; Account is an outbound link to
/account.html, so it was a category error there. It also competed for the
strip's ~300px budget with the Labs overflow button that main's page registry
introduced -- and the hunk anchored on a `{tabs.map(...)}` block that registry
rewrite deleted, so it was a hard rebase conflict besides.
Account affordances belong next to identity, so the footer's existing
"User: {name}" chip becomes the link rather than adding a fifth nav item or a
new icon. Target stays /account.html: same-origin, so the persisted token is
shared and there is no second sign-in. Only the entry point moves.
The chip picks up hover, focus-visible, and cursor affordances it did not need
as plain text.
Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts:
# frontend/src/components/navbar/index.tsx
…nt dip Cross-tab sync fails closed by dropping the receiving tab to `none` until its user refresh lands. That made consent < 'usage' a state every broadcast passes through -- including a consent *raise* -- and the PostHog bridge treated it the same as a withdrawal: opt_out_capturing() plus reset(). reset() is about identity, not capture; it rotates the anonymous distinct_id and drops feature flags and session recording. So every cross-tab consent change silently churned a person profile for a user who withdrew nothing. Opting out is what fails closed, and it stays unconditional. reset() now waits for the provisional window to end. Gating it on "authenticated with an id" instead was rejected: that reads as identity-still-present and so would skip the reset on a genuine withdrawal, which is exactly when dropping the locally stored analytics identity matters most. So the reconcile now reports when it is in flight. invalidateConsent becomes reconcileConsentFromOtherTab -- fail closed and re-fetch as one function, so the provisional window has a defined end that consentPending brackets exactly; two separate calls left no way to tell a transient `none` from a chosen one. The in-flight count is a counter rather than a boolean so overlapping pings don't let the first to finish present the second's provisional level as authoritative. Tests cover the drop-then-adopt path, the failed refresh (stays `none`, stops being pending), overlapping reconciles, and applyConsentSnapshot. jsdom and @testing-library/react arrived with main, so the earlier "no component tests" constraint no longer applies. Co-Authored-By: Claude <noreply@anthropic.com>
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
Implements PR 2 of #503: users can make and manage their logging-consent choice.
consentUpdatedAtthrough the authenticated session so the gate is shown only until a choice is persisted.noneuntil its authoritative session refresh completes.Account-page behavior
The Account & privacy entry opens a standalone page in the user’s browser. On Office and Google Docs add-in surfaces, that browser may not share the host webview’s storage, so the user may need to sign in again.
The UI states this explicitly. We intentionally do not pass bearer tokens in URLs; an in-app Account screen could be the long-term solution for seamless session continuity.
Validation
pages/draft/index.tsxconsentUpdatedAtsession propagationFollow-ups
alwaysAllowflag from the client session response in a small backend-focused PR.Note: