Sign in on a phone with no console work, in the browser's own session

Mobile OAuth reused the desktop client all along; what stopped it was the
browser. Sending the user out to Safari or Chrome backgrounds the app, iOS
suspends it, and the redirect carrying the code arrives at a socket nobody
is accepting on. The consent page now opens in front of the app instead, in
SFSafariViewController or a Chrome Custom Tab, so the loopback listener
stays live and the existing `installed` client is enough. Verified against
Google's real consent screen on a simulator and an emulator.

A per-platform client is still supported and is now an upgrade rather than a
prerequisite. On iOS it buys ASWebAuthenticationSession, which shares
Safari's session so nobody is asked to sign in to Google twice. Android
needs nothing: Custom Tabs share Chrome's cookies, measured rather than
assumed. iOS session sharing could not be confirmed on the simulator and
wants a real device.

Never an app-owned WebView: Google blocks it, and rightly, since a webview
the app controls can read the password typed into it.

Cancelling is no longer reported as a failure. AuthEvent carries a
`cancelled` flag, set by comparing against the constant every back-out path
returns, and Google's `access_denied` on desktop counts too.

Five frontend bugs found by driving the real UI, not by reading it: the
details card slid under the tab bar leaving its buttons unhittable; the
ghost click after a touch pressed a button in the card that tap had just
opened, opening the editor by itself; the swipe that pages the day was dead
over every read-only block; 84px of macOS traffic-light lane was reserved on
platforms with no traffic lights; and the desktop header ignored the top
safe area on an iPad. A first launch now says what to do next rather than
showing an empty grid, and accounts are named as Google accounts throughout.
This commit is contained in:
pj committed 2026-08-12 18:32:45 +05:30
1 parent 661100dfdc
commit d4c3a304b5
31 files changed
+1319 -114

No files matched your search

+48 -2
View File
@@ -103,15 +103,26 @@ select {
flex: none;
position: relative;
z-index: 45;
height: var(--titlebar-h);
/* Out of the way of a status bar or a notch, the same way the phone bars do it. A desktop reads
both insets as zero; an iPad gets this header rather than those bars and does not. */
height: calc(var(--titlebar-h) + var(--safe-top));
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
padding: 0 14px 0 var(--traffic-pad);
padding: var(--safe-top) 14px 0;
background: var(--shell);
border-bottom: 1px solid var(--line);
}
/* The macOS traffic lights float over this row, so it opens a lane for them and costs no extra
height. macOS is the only platform with any: they come from `titleBarStyle: "Overlay"`, which is
a macOS-only window setting, and on Linux, Windows, an iPad or a browser the lane was 84px of
nothing, pushing the view switcher off centre and starving the date range of the room it needed
to say what day it is. */
:root[data-traffic] .titlebar {
padding-left: var(--traffic-pad);
}
.titlebar .lead {
display: flex;
align-items: center;
@@ -273,9 +284,44 @@ select {
min-height: 0;
display: flex;
flex-direction: column;
/* The first-run note lays itself over whatever is here, so this is the box it covers. */
position: relative;
background: var(--paper);
}
/* Above every part of the grid, which stacks up to 15, and under the overlays, which start at 20:
the panel this opens has to come up in front of it. */
.first-run {
position: absolute;
inset: 0;
z-index: 19;
display: grid;
place-items: center;
padding: 24px;
background: var(--paper);
}
.first-run-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
max-width: 320px;
text-align: center;
}
.first-run-title {
margin: 0;
font-family: var(--font-heading);
font-weight: 500;
font-size: 18px;
}
.first-run-note {
margin: 0;
color: var(--ink-soft);
}
/* Overlays, margin's idiom: summoned by a key, dismissed with Escape, never resident. */
.overlay {
position: fixed;