- 01.Starting Point
- 02.Understanding the Problem Space
- 03.Three UX Approaches Considered
- 04.Implementation: Adding the Button
- 05.First Test: Manual Submit
- 06.Iteration: Auto-Submit
- 07.Side Discovery: Missing Space After Glyphs
- 08.Cleaning Up Previous Session's Changes
- 09.Where We Landed
- 10.Future UX Improvements
Keychain Unlock UX for Claude Code
Continuing from a previous session that fixed font rendering (Nerd Fonts, emoji width). The next item was improving UX when Claude Code can't access its API key due to a locked macOS keychain.
Starting Point
Continuing from a previous session that fixed font rendering (Nerd Fonts, emoji width). The next item was improving UX when Claude Code can't access its API key due to a locked macOS keychain.
The problem: Claude Code stores API keys in macOS Keychain. When connecting via SSH (especially after reboot), the keychain is locked. Users see:
Missing API key · Run /login
SSH sessions can't prompt for GUI keychain unlock — the session has no GUI context.
Understanding the Problem Space
First question: what signals do we have from the iOS client to detect keychain state?
From iOS (pre-SSH): Nothing. We're just an SSH client connecting remotely.
During SSH session, we could probe:
security show-keychain-info ~/Library/Keychains/login.keychain-db 2>&1Returns:
- Unlocked:
Keychain "login.keychain-db" lock-on-sleep timeout=300s - Locked:
SecKeychainCopySettings: The user name or passphrase you entered is not correct.
But probing adds latency and feels intrusive. Also, user might have ANTHROPIC_API_KEY exported in shell profile — keychain state wouldn't matter.
Decision: Don't try to detect. Make unlock convenient to trigger. Fresh SSH sessions probably need it; once done, you're good for that session.
Three UX Approaches Considered
A) Shortcut button in toolbar — "🔐 Unlock Keychain" always visible, one tap
B) First-connect banner — dismissable hint on new sessions
C) Detect-on-demand — user taps help when Claude fails, we offer unlock
Started with Option A — simplest, no detection logic, user learns the pattern.
Implementation: Adding the Button
The shortcuts row lives in BottomPanelView.swift. Current shortcuts:
private let shortcuts: [(label: String, key: String?)] = [
("esc", "\u{1b}"),
("tab", "\t"),
("ctrl", nil), // Special handling
("^C", "\u{03}"),
("copy", nil),
("paste", nil),
("del", "\u{7f}"),
// ...
]Added keychain unlock command and button:
// Keychain unlock command for Claude Code API key access
private let keychainUnlockCommand = "security unlock-keychain ~/Library/Keychains/login.keychain-db"
private let shortcuts: [(label: String, key: String?)] = [
("esc", "\u{1b}"),
("tab", "\t"),
("ctrl", nil),
("^C", "\u{03}"),
("copy", nil),
("paste", nil),
("🔐", nil), // Keychain unlock - special handling
("del", "\u{7f}"),
// ...
]Handler in the ForEach:
} else if shortcut.label == "🔐" {
// Type and run the keychain unlock command
onKey(keychainUnlockCommand + "\r")
}The + "\r" auto-submits — tap button, command runs immediately, macOS prompts for password.
Added accessibility label:
case "🔐": return "Unlock keychain for Claude Code"First Test: Manual Submit
Initially tried without \r:
onKey(keychainUnlockCommand)User would see the command, then press Enter manually. Tested it — worked but felt like unnecessary friction.
Iteration: Auto-Submit
Changed to include carriage return:
onKey(keychainUnlockCommand + "\r")Now: tap 🔐 → command runs → macOS password prompt appears → enter password → keychain unlocked → Claude Code works.
One-tap fix.
Side Discovery: Missing Space After Glyphs
While testing, noticed the Nerd Font tree icon was eating the space after it:
Ctx:0.0%|🌲nogit
Should be:
Ctx:0.0%|🌲 nogit
Similar class of bug to the globe emoji we fixed earlier (SwiftTerm character width calculation). Logged to docs/missing-space-glyph-issue.md for later investigation.
Cleaning Up Previous Session's Changes
Git status showed uncommitted changes from theme refactoring work:
M Pocket/Pocket/Core/Models.swift
M Pocket/Pocket/Core/PocketStore.swift
M Pocket/Pocket/UI/Settings/SettingsView.swift
M Pocket/Pocket/UI/TerminalSessionOverlay.swift
M Pocket/Pocket/UI/TerminalView.swift
These were:
- Theme migration — Old names (Pocket Light/Dark, Termius, Matrix) → New (Ember, Slate, Deep, Verdant)
- Removed per-project theme override — Simplified to global theme only
- Simplified MiniTerminalPreview — Removed overengineered contrast adjustment logic
Committed in logical chunks:
277b872- docs: Update build commands for iPhone 17 Pro simulator48979e3- refactor: Migrate to new theme system3c0647f- feat: Add keychain unlock shortcut button
Where We Landed
Keychain unlock is now a one-tap operation:
- User connects to Mac via Pocket
- Runs Claude Code, sees "Missing API key"
- Taps 🔐 in shortcuts row
- Enters macOS password when prompted
- Claude Code works
Commits:
3c0647ffeat: Add keychain unlock shortcut button
Files changed:
Pocket/Pocket/UI/BottomPanelView.swift— Added 🔐 button + handler
Future UX Improvements
Documented in docs/keychain-unlock-ux.md:
- Detection — Watch terminal output for "Missing API key" pattern, surface hint proactively
- Placement — Could move button or make more prominent for new users
- Alternative fix — Suggest
export ANTHROPIC_API_KEY=...in shell profile to bypass keychain entirely
For now, the simple shortcut button solves the immediate friction.