Skip to content

Improve settings: copy tokens and edit without signing out#127

Open
mcintyre94 wants to merge 3 commits into
mainfrom
improve-settings-accessibility-and-functionality-3a1f67a1
Open

Improve settings: copy tokens and edit without signing out#127
mcintyre94 wants to merge 3 commits into
mainfrom
improve-settings-accessibility-and-functionality-3a1f67a1

Conversation

@mcintyre94

Copy link
Copy Markdown
Owner

Summary

  • Long-press (context menu) on Sprites API and Claude Code rows to copy the token to clipboard, with a "Copied!" flash
  • Update Token option in the same context menu opens a focused sheet to replace just that token — no sign-out required
  • Sprites token is validated against the API before saving; Claude token saves directly
  • Shared tokenRow @ViewBuilder helper keeps the two rows DRY

Test plan

  • Long-press Sprites API row → "Copy Token" copies to clipboard and shows "Copied!" flash
  • Long-press Claude Code row → same
  • "Update Token" on Sprites API opens sheet; entering an invalid token shows error and does not save
  • "Update Token" on Sprites API with valid token validates, saves, dismisses sheet, row shows "Connected"
  • "Update Token" on Claude Code saves immediately without validation
  • Both tokens update without requiring sign-out; existing sessions unaffected
  • Cancel button dismisses sheet with no changes

🤖 Generated with Claude Code

mcintyre94 and others added 3 commits April 4, 2026 07:19
Adds a ClaudeEffortLevel enum (low/medium/high/max) and surfaces it in
the existing model picker as a second Menu section. The pill label shows
the effort level only when non-default (e.g. "Sonnet · High") to avoid
visual noise. Max effort is hidden unless Opus is selected, and switching
away from Opus while on max resets to high. Effort is passed to every
claude invocation via --effort.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Long-press context menu on Sprites API and Claude Code rows to copy
  the token to clipboard (with "Copied!" flash feedback) or open an
  edit sheet to update the token without signing out
- EditTokenSheet: form sheet for updating a single token; Sprites token
  validates against the API before saving (rolls back on failure),
  Claude token saves directly
- Shared tokenRow @ViewBuilder helper eliminates duplication between
  the two rows

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Apr 9, 2026

Copy link
Copy Markdown

test

@claude

claude Bot commented Apr 9, 2026

Copy link
Copy Markdown

Code review

3 issues found.


1. Bug: Original valid token destroyed on validation failure

File: Wisp/Views/Settings/EditTokenSheet.swiftsave() function

The save flow for Sprites tokens overwrites the Keychain immediately (line 98), then if validateToken() throws, it deletes the Keychain entry entirely (line 103) — leaving the user fully signed out instead of showing an error with their original token intact.

isSaving = true
defer { isSaving = false }
do {
try KeychainService.shared.save(trimmed, for: tokenType.keychainKey)
apiClient.refreshAuthState()
try await apiClient.validateToken()
isPresented = false
} catch {
KeychainService.shared.delete(key: tokenType.keychainKey)
apiClient.refreshAuthState()
errorMessage = "Invalid token. Please check and try again."
}
} else {

Fix: read the old token before overwriting and restore it in the catch block:

let oldToken = KeychainService.shared.read(key: tokenType.keychainKey)
isSaving = true
defer { isSaving = false }
do {
    try KeychainService.shared.save(trimmed, for: tokenType.keychainKey)
    apiClient.refreshAuthState()
    try await apiClient.validateToken()
    isPresented = false
} catch {
    if let oldToken {
        try? KeychainService.shared.save(oldToken, for: tokenType.keychainKey)
    } else {
        KeychainService.shared.delete(key: tokenType.keychainKey)
    }
    apiClient.refreshAuthState()
    errorMessage = "Invalid token. Please check and try again."
}

2. CLAUDE.md violation: context menu without matching swipe actions

File: Wisp/Views/Settings/SettingsView.swifttokenRow() helper

tokenRow() adds "Copy Token" and "Update Token" via .contextMenu only. CLAUDE.md requires:

"Swipe actions and context menus: Always implement both. Swipe actions are the primary interaction on iPhone/iPad; context menus must cover the same set of actions so nothing is inaccessible on Mac. The two should be kept in sync whenever actions are added or removed."

}
.contextMenu {
if isConnected, let token {
Button {
UIPasteboard.general.string = token
withAnimation { copiedFlash.wrappedValue = true }
Task {
try? await Task.sleep(for: .seconds(1.5))
withAnimation { copiedFlash.wrappedValue = false }
}
} label: {
Label("Copy Token", systemImage: "doc.on.doc")
}
}
Button {
showEdit.wrappedValue = true
} label: {
Label("Update Token", systemImage: "pencil")
}
}
}
// MARK: - Sections
private var accountSection: some View {
Section("Account") {
tokenRow(

On iPhone/iPad these actions are only reachable via long-press. Add .swipeActions with the same "Copy Token" and "Update Token" buttons.


3. Token copy not #if DEBUG-guarded (inconsistent with existing pattern)

File: Wisp/Views/Settings/SettingsView.swifttokenRow() helper, line 92

The Sprites and Claude token copy actions write to UIPasteboard.general in all builds. The existing GitHub token copy in this same file is wrapped in #if DEBUG:

Text(apiClient.hasGitHubToken ? "Connected" : "Not Connected")
.foregroundStyle(apiClient.hasGitHubToken ? .green : .secondary)
#endif
}
#if DEBUG
.onTapGesture {
if let token = apiClient.githubToken {
UIPasteboard.general.string = token
withAnimation {
copiedTokenFlash = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation {
copiedTokenFlash = false
}
}
}
}
#endif

Copying API tokens and OAuth tokens to the system pasteboard (which syncs via Universal Clipboard) in production builds may not be intentional. Consider whether these should be #if DEBUG-guarded to match the existing pattern.

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