Skip to content

[TV] Auth parity: silent QR expiry rotation, account-creation-failed, gated create_account_shown - #5804

Merged
sztomek merged 4 commits into
mainfrom
feat/tv-auth-parity-analytics
Aug 31, 2026
Merged

sztomek merged 4 commits into
mainfrom
feat/tv-auth-parity-analytics

Conversation

@sztomek

@sztomek sztomek commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

Android TV parity — Section B / PR 4: auth parity fixes. Brings the device-auth QR flow in line with tvOS across code expiry, and makes account-creation failures + the gated-modal open visible.

Commits:

  1. Silent code rotation on expiry. tvOS treats EXPIRED_TOKEN as retryable — it transparently requests a fresh device code and re-renders the QR. Android's deviceAuthFlow treated anything but authorization_pending as terminal → the screen dropped to the error state and the shared sign-in path emitted a spurious user_signin_failed{error_code=expired_token} per expiry. Now, on expired_token, deviceAuthFlow requests a new code and keeps polling (no Error, no failure event), and SyncManagerImpl skips trackSignIn for expired_token (same as authorization_pending). Fixes both the Sign In and Create Account consumers. (Device auth is TV-only on Android, so this shared change has no phone impact.)
  2. user_account_creation_failed. Shared trackSignIn emitted user_signin_failed regardless of isNewAccount. It now branches: a failed device-auth create-account emits UserAccountCreationFailedEvent(error_code); sign-in failures keep UserSigninFailedEvent. Also rethrows CancellationException in loginWithDeviceAuth's catch so a cancel mid-poll can't fire a stray failure event.
  3. create_account_shown for the gated Follow modal. The account-gated Follow modal (TvCreateAccountModal, opened from podcast details) tracked nothing; it now fires create_account_shown{flow=account_encouragement} on open via CallOnce + a tiny TvCreateAccountModalViewModel. account_encouragement distinguishes the modal from the full-screen create-account (initial_onboarding).

Deliberate non-goal: the modal completion still emits user_signed_in (via isNewAccount=false) where iOS emits user_account_created — a reviewed decision on #5784 (device pairing can't truly distinguish create-vs-login). Left as-is.

Testing Instructions

  1. debugProd TV build + adb logcat on LoggingAnalyticsListener.
  2. Open Sign In (QR). Leave the code past its expiry (minutes) → the QR visibly rotates to a new code and keeps polling; no error screen, no user_signin_failed{expired_token} in logcat.
  3. Create Account → force a pairing failure → user_account_creation_failed (not user_signin_failed).
  4. From a podcast, tap Follow while signed out → the account modal opens → create_account_shown{flow=account_encouragement}.

Unit tests: expiry → fresh code requested (deviceAuthorize called twice), no Error, no failure event; create-account failure → creation-failed event; sign-in failure → signin-failed event; modal open → shown event.

Fixes POC-857 https://linear.app/a8c/issue/POC-875/qr-code-exipration-account-creation-failed-event-reporting

Screenshots or Screencast

QR rotation is time-based (minutes) — best verified on device per the steps above; no static UI change to screenshot.

Checklist

  • If this is a user-facing change, I have added an entry in CHANGELOG.md — n/a
  • Ensure the linter passes (./gradlew spotlessApply)
  • I have considered whether it makes sense to add tests for my changes — ViewModel + SyncManager unit tests added
  • All strings that need to be localized are in localization — n/a
  • Any jetpack compose components I added or changed are covered by compose previews — n/a
  • I have updated the Event Horizon schema to reflect any new or changed analytics — n/a (all events already exist)

Depends on #5801 (TV analytics foundation) landing first, so these events carry platform=tv.

@claude

claude Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @sztomek's task in 4m 39s —— View job


Review

The three changes are each well-scoped and the shared trackSignIn refactor correctly leaves the email/Google paths untouched (isNewAccount defaults to false, and handleLogin is the only other caller). Rethrowing CancellationException in loginWithDeviceAuth is a real fix — the old catch (Exception) both swallowed cancellation and fired a failure event. One concern with the rotation loop: making expired_token non-terminal in both deviceAuthFlow and trackSignIn removes every exit and every signal from that path, so a server that reports a fresh code as expired now produces a silent unbounded loop instead of an error screen.

Blocking

  • tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/signin/TvDeviceAuth.kt:18 — if the server returns expired_token for a code it just issued, the TV user sees the QR rotate every ~5s and can never pair, with no error screen and (because the same commit suppresses trackSignIn for expired_token) no analytics event; bound the rotations and emit TvSignInUiState.Error when exhausted. (thread)

Non-blocking

  • tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/createaccount/TvCreateAccountModalViewModel.kt:16 — the modal fires create_account_shown{account_encouragement} but TvPodcastDetailsViewModel.kt:103 passes isNewAccount = false, so its completion/failure are user_signed_in/user_signin_failed, leaving the new funnel with no joinable terminal event on either side. (thread)
  • modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImplTest.kt — the two new tests cover the isNewAccount branch but nothing covers the expired_token suppression in loginWithDeviceAuth, which is the shared half of commit 1; a regression there silently restores one user_signin_failed{expired_token} per expiry for every TV sign-in attempt. The TvSignInViewModel test only proves the flow doesn't error.

Nits

  • modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImpl.kt:260 — "authorization_pending"/"expired_token" are duplicated as literals here while TvDeviceAuth.kt:12-13 holds them as constants; hoist to one shared place. (thread)
    · feat/tv-auth-parity-analytics

@sztomek sztomek added this to the 8.20 milestone Aug 28, 2026
@sztomek sztomek added [Type] Enhancement Improve an existing feature. [Area] TV labels Aug 28, 2026
@sztomek
sztomek marked this pull request as ready for review August 28, 2026 16:29
@sztomek
sztomek requested a review from a team as a code owner August 28, 2026 16:29
@sztomek
sztomek requested review from geekygecko and removed request for a team August 28, 2026 16:29
@claude

claude Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @sztomek's task in 34s —— View job


Review

No new commits since my previous review (HEAD is still 2ff9cb7), so there's nothing new to review. All three findings are still open in the current code.

Blocking

  • tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/signin/TvDeviceAuth.kt:18 — unbounded silent rotation when the server reports a just-issued code as expired; still unbounded, and SyncManagerImpl.kt:260 still suppresses the analytics signal. (thread) · Fix this →

Non-blocking

  • tv/src/main/java/au/com/shiftyjelly/pocketcasts/onboarding/createaccount/TvCreateAccountModalViewModel.kt:16 — the account_encouragement funnel has no joinable terminal event on either side. (thread)
  • modules/services/repositories/src/test/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImplTest.kt — no test covers the expired_token suppression in loginWithDeviceAuth.

Nits

  • modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/sync/SyncManagerImpl.kt:260 — error-code literals still duplicated against TvDeviceAuth.kt:12-13. (thread)

--- · feat/tv-auth-parity-analytics

@wpmobilebot wpmobilebot modified the milestones: 8.20, 8.21 Aug 31, 2026
@wpmobilebot

Copy link
Copy Markdown
Collaborator

Version 8.20 has now entered code-freeze, so the milestone of this PR has been updated to 8.21.

@sztomek
sztomek merged commit e8c0e44 into main Aug 31, 2026
36 of 37 checks passed
@sztomek
sztomek deleted the feat/tv-auth-parity-analytics branch August 31, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants