Skip to content

Show the QR handoff on every sign-in, Ring installed or not - #336

Merged
jvsena42 merged 5 commits into
mainfrom
feat/qr-handoff-on-every-device
Sep 23, 2026
Merged

jvsena42 merged 5 commits into
mainfrom
feat/qr-handoff-on-every-device

Conversation

@jvsena42

Copy link
Copy Markdown
Owner

What

Tapping Sign in with Pubky Ring on a phone used to fire the pubkyauth:// deeplink whenever something on the device answered it. It now raises the same QR handoff a tablet gets — as a sheet — and "I have Pubky Ring on this device" is what opens Ring.

Ring answering the scheme says only that some app claims it, not that it holds this user's key. Auto-deeplinking on that guess walked straight past the code, which is the one way in for a key kept in Ring on the user's other phone.

How

  • OnboardingViewModel emits no OpenDeeplink for any handoff. onOpenRingOnThisDevice — already the tablet panel's escape hatch — is the only thing that does, against the same live authorisation rather than a fresh one. ringInstalledHere now decides only whether that button is offered; RingHandoff only how the code is presented.
  • Android's RingScanSheet takes the panel's two branches: open-here when something is installed, "Get the app" when nothing is. Its body copy swaps too — the sheet's own string opens on "Pubky Ring isn't on this device", so with Ring here it borrows the panel's wording. No new string in any of the eleven languages.
  • iOS only loses the deeplinkFired guard that suppressed its sheet; its panel already branched on ringInstalledHere.

Verified

./gradlew :shared:jvmTest :androidApp:assembleDebug detektAll clean, plus two new shared tests (no deeplink on sign-in; open-here reuses the authorisation).

Driven on emulator-5554 with a hand-built stub APK claiming pubkyauth://, since Ring is not installed there and the installed branch is the whole change:

Ring on the device Result
yes (stub) Loopky stayed in the foreground; sheet showed the code, "I have Pubky Ring on this device" and "Copy sign-in link", with the panel's body copy and no "Get the app". Open-here resumed the stub — same authUrl, no second beginSignIn
no unchanged

Tablet and iOS are untouched in behaviour. iOS was not re-driven — no Mac in this session.

journeys/01-onboarding-ring-auth.xml is updated (the happy path is four taps now) with both branches added, and journeys/RESULTS.md records the run.

🤖 Generated with Claude Code

jvsena42 and others added 5 commits September 23, 2026 09:18
Ring answering `pubkyauth://` here says only that some app does, not that
it holds this user's key. Firing the deeplink on that guess sent a phone
straight out to the signer, past the QR code that is the only way in for
a key kept in Ring on the user's other phone.

So `onSignInClick` emits no `OpenDeeplink` for any handoff. Every live
authorisation is presented as the code, and `onOpenRingOnThisDevice` —
already the tablet panel's escape hatch — is what opens Ring, against
the same authorisation rather than a fresh one. `ringInstalledHere` now
decides only whether that button is worth offering, and `RingHandoff`
only how the code is presented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`RingScanSheet` was raised only where nothing claimed `pubkyauth://`;
now it is the phone's half of every sign-in, so it takes the panel's two
`ringInstalledHere` branches: "I have Pubky Ring on this device" when
something is there to open, "Don't have Pubky Ring? Get the app" when
nothing is.

The body copy swaps with it. The sheet's own string opens on "Pubky Ring
isn't on this device", which is a lie to someone who has it, so with Ring
here it borrows the panel's wording — no new string in eleven languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`scanPrompt` returned nil when it believed the VM had deeplinked, which
it no longer does for any handoff. The panel already branched on
`ringInstalledHere` for its body copy and its open-here button, so
removing the guard is the whole change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ches

The happy path is four taps now, not three — sign in raises the code and
open-here fires the deeplink. Adds the two branches the change is about:
with Ring installed Loopky must stay in the foreground, and without it
the sheet is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was `foregroundMuted`, the same grey as the waiting line above it — the
one pointer to the app this whole screen depends on, styled as a caption.
`accentPrimary` reads as a control.

Light mode puts it at 3.1:1 on the card, which clears AA for UI text but
not the 4.5:1 for body copy; dark is 5.0:1. Kept because it is the brand
orange on a link, not a paragraph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jvsena42
jvsena42 merged commit bb7955d into main Sep 23, 2026
8 checks passed
@jvsena42
jvsena42 deleted the feat/qr-handoff-on-every-device branch September 23, 2026 12:36
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