Commit Graph
2 Commits
Author SHA1 Message Date
jaap-janandClaude Opus 5 1db8bed872 Let a cancelled sign-in end the sign-in rather than the timeout
Backing out of the login page on Android left the shell showing "Opening your browser to
sign in…" with the button disabled for five minutes. Nothing was wrong except that nobody
told it: the redirect callback only ever completed when an intent arrived, so a user who
pressed back was waiting on OidcClient's browser timeout to expire before the flow failed
and the button came back.

There is no cancel event to subscribe to on this platform. Pressing back, dismissing the
browser and closing a provider's error page are indistinguishable from here — the browser
goes away and this application is foreground again with nothing delivered — so being
resumed while a sign-in is still waiting is the signal, and the only one there is. The
launcher records that a browser took the intent, OnResume fails the wait, and the guard
means the resumes that have nothing to do with signing in (a launch, recents, the
keystore's fingerprint prompt) go through untouched.

An exception rather than a cancellation, because OidcClient reads a cancelled wait as its
own timeout expiring and would report five minutes passing to somebody who waited two
seconds. It cannot steal a successful sign-in either: Android delivers the redirect to
OnNewIntent before resuming the activity, so the completion is already settled and the
attempt does nothing.

The enrollment key-binding trip through the browser is covered by the same change, since it
waits on the same callback.

The OnNewIntent remark had been sitting above OnResume, describing a method two below it.
Moved back, since the new remark wanted the space and the old one was wrong where it was.

Verified by building the head in Debug and Release. The behaviour itself is unverified for
the reason docs/android-port.md gives about this whole head: nothing has been run on a
device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 21:10:37 +02:00
jaap-jan 7a3a521c59 Give the phone the rest of its screens, and a way in
ci / build and test (push) Failing after 2s
ci / android head (push) Failing after 1s
All seven screens of the design, plus the two it does not draw because it starts at an
enrolled phone: naming a server, and choosing a passphrase.

The five states docs/android-port.md worried about losing at 360dp are all here and none
of them softened. The changed-key refusal is a full-screen panel rather than a bottom
sheet, because a sheet is swipe-to-dismiss by convention and that screen must have no way
forward. The recovery code raises FLAG_SECURE for its own state and lowers it afterwards,
so the sentence about screenshots is true rather than decorative. The delete
confirmations keep their counts and replace the row in place.

Signing in works, and the seam it needed is worth more than the implementation:
IAuthorizationCallback now sits between OidcClient and the loopback listener, so the two
heads differ in where the response arrives and in nothing else. PKCE, the state check,
discovery, the token exchange and the key binding stay one implementation — a second OIDC
client would be a second place for a security bug to live. The phone registers a
private-use scheme with the system rather than binding a loopback port, which on a shared
device any other app can do first.

The accessory key row needed TerminalWorkspace.SendInputAsync: ordinary typing goes from
the renderer straight down the socket, and there was no way in for the keys a software
keyboard does not have. Ctrl latches, because one thumb cannot chord, and the latch is
drawn — a modifier that is on and does not look on is how somebody sends ^L to a database
prompt believing they typed an l.

597 client tests green, including two new ones for the input path and one for the
terminal surface command. Nothing has run on a device.
2026-07-31 21:43:11 +02:00