diff --git a/Frameworks/TermindPresentation/ConnectionDecisionForms.swift b/Frameworks/TermindPresentation/ConnectionDecisionForms.swift index 016f2b67..34d0cf91 100644 --- a/Frameworks/TermindPresentation/ConnectionDecisionForms.swift +++ b/Frameworks/TermindPresentation/ConnectionDecisionForms.swift @@ -72,13 +72,14 @@ public struct SSHAuthenticationForm: View { !username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (!needsPassphrase || !passphrase.isEmpty) && (!prompt.isProxy || !needsPasswordEntry || !password.isEmpty) + && !selectedIdentityPasswordIsUnavailable } - private var canSave: Bool { - guard canContinue, let selectedIdentity else { return canContinue } - switch library.passwordAvailability(for: selectedIdentity) { - case .missing, .failed: return false - case .notConfigured, .available: return true + private var selectedIdentityPasswordIsUnavailable: Bool { + guard let selectedIdentity else { return false } + return switch library.passwordAvailability(for: selectedIdentity) { + case .missing, .failed: true + case .notConfigured, .available: false } } @@ -109,7 +110,7 @@ public struct SSHAuthenticationForm: View { if prompt.keyboardInteractive == nil, let keyboard { keyboard.bridge( canSubmit: canContinue, - submit: { commit(save: !needsPassphrase && canSave) }, cancel: cancel) + submit: { commit(save: !needsPassphrase) }, cancel: cancel) } } .onAppear { @@ -249,7 +250,14 @@ public struct SSHAuthenticationForm: View { Spacer() continueButton } - saveAndContinueButton.frame(maxWidth: .infinity, alignment: .trailing) + #if os(iOS) + HStack { + Spacer() + saveAndContinueButton + } + #else + saveAndContinueButton.frame(maxWidth: .infinity, alignment: .trailing) + #endif } } } @@ -283,7 +291,7 @@ public struct SSHAuthenticationForm: View { } .buttonStyle(.borderedProminent) .keyboardShortcut(.defaultAction) - .disabled(!canSave) + .disabled(!canContinue) } private func updateFocusForRequirement() { diff --git a/Memory/ARCHITECTURE.md b/Memory/ARCHITECTURE.md index d7e23a6a..01a13203 100644 --- a/Memory/ARCHITECTURE.md +++ b/Memory/ARCHITECTURE.md @@ -138,7 +138,8 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r the route. Missing local proxy passwords and explicit HTTP/SOCKS authentication rejection use the same local authentication surfaces across sessions, Metrics, forwarding, Assistant and external MCP requests. Proxy submissions are separate from SSH submissions; snapshots retain only proxy network data, username and Identity ID. An Identity whose - password lookup is missing or failed opens as Custom with the inherited username and cannot be overwritten while selected. + password lookup is missing or failed opens as Custom with the inherited username; if manually reselected, neither + Continue nor Save and Continue is available until the user returns to Custom or selects a usable Identity. - Username-only Identities try `none`, then advertised methods. Device-local `lastUsedAt` tracks successful saved-secret reads, not authentication. Batch route reads; missing/availability/edit reads do not count. First access per Identity/ process writes directly without Vault journal/refresh; sync strips this field and preserves local values on apply.