eleven / messenger Public

ios+mac: fold the sign-in doors away once signed in (#1146)

A landed sign-in kept all three doors on screen under the status line —
'Signed in' with a full menu of ways to sign in again. Now signed in means
no sign-in options: sealed keeps exactly one control (the passkey — it's
the unlock, not an alternative), and a quiet 'Sign in a different way'
escape drops only the home session for the wrong-account landing
(signOut is the full local wipe — oversized for a first-run screen).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 6da7e5c50bc96d4d4c1e41017cf35dbc3d8ab864 parent 2ed3375
2 files changed, +62 −1
  • ios/LChat/Views/SignInWithEleven.swift +28 −0
  • ios/LChatMac/MacHomeSignIn.swift +34 −1
diff --git a/ios/LChat/Views/SignInWithEleven.swift b/ios/LChat/Views/SignInWithEleven.swift
index 26b19fb..0d8a49e 100644
--- a/ios/LChat/Views/SignInWithEleven.swift
+++ b/ios/LChat/Views/SignInWithEleven.swift
@@ -106,6 +106,15 @@ final class HomeSignInModel: ObservableObject {
phase = .idle
}
+ /// The wrong-door escape on the first-run screen: drop ONLY the home
+ /// session — nothing has been restored to this device — and bring the
+ /// doors back for a sign-in that landed on the wrong account.
+ func switchAccount() {
+ HomeSessionStore.clear()
+ sessionExists = false
+ phase = .idle
+ }
+
/// Session adoption — the ONE seam every door funnels a finished sign-in
/// through (passkey ceremony finish, Apple verify, email verify): the
/// same Keychain write, the published flag every surface watches, and
@@ -434,6 +443,8 @@ struct SignInFirstRun: View {
.padding(.bottom, 48)
} else if let step = emailStep {
emailDoor(step)
+ } else if model.phase.signedIn {
+ landed
} else {
doors
}
@@ -466,6 +477,23 @@ struct SignInFirstRun: View {
return order
}
+ // A landed sign-in folds the doors away entirely — signed in means no
+ // sign-in options. Sealed keeps exactly one control, the passkey,
+ // because it is the unlock rather than an alternative; the quiet escape
+ // below it covers the wrong-account landing either way.
+ private var landed: some View {
+ VStack(spacing: 12) {
+ if model.phase == .sealed {
+ door(.passkey, leading: true)
+ }
+ Button("Sign in a different way") { model.switchAccount() }
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(.secondary)
+ }
+ .padding(.horizontal, 32)
+ .padding(.bottom, 8)
+ }
+
private var doors: some View {
VStack(spacing: 12) {
ForEach(doorOrder, id: \.self) { method in
diff --git a/ios/LChatMac/MacHomeSignIn.swift b/ios/LChatMac/MacHomeSignIn.swift
index 1e25183..3158da8 100644
--- a/ios/LChatMac/MacHomeSignIn.swift
+++ b/ios/LChatMac/MacHomeSignIn.swift
@@ -134,6 +134,17 @@ final class MacHomeSignIn: ObservableObject {
}
}
+ /// The wrong-door escape on the first-run screen: drop ONLY the home
+ /// session — nothing has been restored to this Mac yet — and bring the
+ /// doors back. signOut(from:) is the full local wipe + relaunch, wildly
+ /// oversized for a sign-in that just landed on the wrong account.
+ func switchAccount() {
+ HomeSessionStore.clear()
+ sessionExists = false
+ sessionSealed = false
+ phase = .idle
+ }
+
/// Re-read the Keychain session: another surface's model (the first-run
/// hero, the browser handoff) may have signed in or out since this one was
/// created — Settings' copy went stale and hid its Sign Out button.
@@ -551,13 +562,16 @@ struct MacFirstRun: View {
.padding(.bottom, 56)
} else if let step = emailStep {
emailDoor(step)
+ } else if model.phase.signedIn {
+ landed
} else {
doors
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
// A landed sign-in (any door) folds the email door away; the doors
- // return beneath the status — sealed promotes the passkey to unlock.
+ // fold too (signed in = no sign-in options) — sealed keeps only the
+ // passkey, promoted as the unlock.
.onChange(of: model.phase) { _, p in
if p.signedIn {
emailStep = nil
@@ -583,6 +597,25 @@ struct MacFirstRun: View {
return order
}
+ // A landed sign-in folds the doors away entirely — signed in means no
+ // sign-in options. Sealed keeps exactly one control, the passkey,
+ // because it is the unlock rather than an alternative; the quiet escape
+ // below it covers the wrong-account landing either way.
+ private var landed: some View {
+ VStack(spacing: 10) {
+ if model.phase == .sealed {
+ door(.passkey, leading: true)
+ }
+ Button("Sign in a different way") { model.switchAccount() }
+ .buttonStyle(.plain)
+ .font(.subheadline.weight(.medium))
+ .foregroundStyle(.secondary)
+ .padding(.vertical, 2)
+ }
+ .frame(width: 280)
+ .padding(.bottom, 48)
+ }
+
private var doors: some View {
VStack(spacing: 10) {
ForEach(doorOrder, id: \.self) { method in