fix: session-create + settings edge cases (double-tap, biometric stuck, recent-dir dupes) (#133)

Four bugs from a review of session creation, the sessions list, and settings
(lower-severity than the core-path hunts — the core is now well-hardened):

1. Double-tap on the new-session FAB / 'Use this folder' created duplicate
   sessions (isCreating state lags a render). Added a synchronous re-entrancy
   ref guard.
2. 'Require biometric for messages' got stuck ON and enforced with no UI escape
   after turning off the parent 'Require biometric to open' toggle (the child
   switch is then disabled). authenticateForMessage now also gates on the parent.
3. Session-create failure on the default path silently closed the modal with no
   feedback (only the dir path alerted). Both paths now alert; message made generic.
4. Recent-directories got duplicate entries ('/x' vs '/x/') and a mismatched
   'current directory' highlight. switchDirectory/addRecentDirectory now
   stripTrailingSlash.

typecheck clean, 199 tests, i18n parity.


Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6

Co-authored-by: engineer <engineer@macbookpro.lan>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Den
2026-07-21 23:03:45 -07:00
committed by GitHub
parent 1a069b53f5
commit 998c6c60cb
5 changed files with 71 additions and 46 deletions

View File

@@ -166,6 +166,10 @@ export default function SessionsScreen() {
const [renaming, setRenaming] = useState<Session | null>(null)
const [renameText, setRenameText] = useState("")
const renamingInFlight = useRef(false)
// Synchronous re-entrancy guard: `isCreating` state lags by a render, so a
// fast double-tap on the FAB / "Use this folder" would fire two session
// creates before the disabled state lands. This blocks the second call.
const creatingInFlight = useRef(false)
const [serverProjects, setServerProjects] = useState<Project[]>([])
const { sessions, isLoading, error, loadSessions, createSession, deleteSession } = useSessions()
@@ -316,57 +320,67 @@ export default function SessionsScreen() {
)
const onCreateSession = async () => {
const session = await createSession()
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
if (creatingInFlight.current) return
creatingInFlight.current = true
try {
const session = await createSession()
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
} else {
Alert.alert(t("common.error"), t("sessionsList.alerts.createFailedMessage"))
}
} finally {
creatingInFlight.current = false
}
}
const onCreateInDirectory = async (dir?: string) => {
if (!activeConnection) return
if (creatingInFlight.current) return
creatingInFlight.current = true
setIsCreating(true)
// If a custom directory is specified, use a one-off client for that directory
// so we don't mutate the connection's default project
if (dir && dir.trim()) {
const dirClient = clientForDirectory(dir.trim())
if (!dirClient) {
setIsCreating(false)
try {
// If a custom directory is specified, use a one-off client for that directory
// so we don't mutate the connection's default project
if (dir && dir.trim()) {
const dirClient = clientForDirectory(dir.trim())
if (!dirClient) return
try {
const session = await dirClient.session.create({})
addRecentDirectory(dir.trim())
setShowNewSession(false)
setCustomDir("")
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
}
} catch (error) {
console.error("Failed to create session in directory:", error)
Alert.alert(t("common.error"), t("sessionsList.alerts.createFailedMessage"))
}
return
}
try {
const session = await dirClient.session.create({})
addRecentDirectory(dir.trim())
setIsCreating(false)
setShowNewSession(false)
setCustomDir("")
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
}
} catch (error) {
console.error("Failed to create session in directory:", error)
Alert.alert(t("common.error"), t("sessionsList.alerts.createFailedMessage"))
setIsCreating(false)
}
return
}
const session = await createSession()
setIsCreating(false)
setShowNewSession(false)
setCustomDir("")
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
const session = await createSession()
setShowNewSession(false)
setCustomDir("")
if (session) {
router.push({
pathname: `/session/[id]`,
params: { id: session.id, ...(session.directory ? { directory: session.directory } : {}) },
})
} else {
Alert.alert(t("common.error"), t("sessionsList.alerts.createFailedMessage"))
}
} finally {
creatingInFlight.current = false
setIsCreating(false)
}
}