Skip to content

Commit 00bb777

Browse files
committed
fix(path): handle stale focused choice in path browser navigation
When pressing right→left→right in path browser, the third navigation would fail because the renderer's focusedChoiceAtom hadn't updated yet (showing __app__/no-choice sentinel value). Added fallback in onRight/onLeft handlers to use first valid choice from kitPrevChoices when the focused choice is invalid. This fixes the timing race condition between renderer state updates and user input.
1 parent 4322297 commit 00bb777

1 file changed

Lines changed: 46 additions & 9 deletions

File tree

src/target/path/path.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,16 +272,16 @@ const toUiPath = (p: string) => isWin ? p.replaceAll('\\', '/') : p
272272
*/
273273
const filterByFileTypes = (choices: Choice[], fileTypes: string[] | undefined): Choice[] => {
274274
if (!fileTypes) return choices
275-
275+
276276
return choices.filter((choice) => {
277277
// Always include directories (identified by folder.svg icon)
278278
if (choice.img?.includes('folder.svg')) {
279279
return true
280280
}
281-
281+
282282
// Check file extension
283283
const ext = ogPath.extname(choice.value)
284-
284+
285285
// Include files with matching extensions
286286
// Exclude files without extensions (like README, Makefile) when fileTypes is specified
287287
return ext && fileTypes.includes(ext)
@@ -311,7 +311,7 @@ const getWindowsRoots = async (): Promise<string[]> => {
311311

312312
export const createPathChoices = async (
313313
startPath: string,
314-
{ dirFilter = (dirent) => true, dirSort = (a, b) => 0, onlyDirs = false, statFn = statAsync, fileTypes, useSessionCache = true } : {
314+
{ dirFilter = (dirent) => true, dirSort = (a, b) => 0, onlyDirs = false, statFn = statAsync, fileTypes, useSessionCache = true }: {
315315
dirFilter?: (dirent: any) => boolean
316316
dirSort?: (a: any, b: any) => number
317317
onlyDirs?: boolean
@@ -456,7 +456,7 @@ export const createPathChoices = async (
456456

457457
const mapped = await mapDirents(folders.concat(files))
458458
const sorted = mapped.sort(dirSort)
459-
459+
460460
// Apply fileTypes filtering if specified
461461
return filterByFileTypes(sorted, fileTypes)
462462
}
@@ -614,13 +614,15 @@ ${pathError.hint ? `**Hint:** ${pathError.hint}` : ''}
614614
}
615615

616616
let upDir = async (dir) => {
617+
global.log(`[path] upDir called: dir.name=${dir?.name}, dir.value=${dir?.value}, dir.miss=${dir?.miss}`)
617618
if (dir?.miss) {
618619
return
619620
}
620621

621622
// Get the current path from the actual input state, not startPath
622623
// This ensures we're always working with the most current value
623624
const currentPath = global.__kitPromptState?.input || currentInput || startPath
625+
global.log(`[path] upDir: currentPath=${currentPath}, startPath=${startPath}`)
624626

625627
// Save the current selection in navigation history before going up
626628
if (dir?.name && currentPath) {
@@ -673,14 +675,17 @@ ${pathError.hint ? `**Hint:** ${pathError.hint}` : ''}
673675
}
674676

675677
let downDir = async (dir) => {
678+
global.log(`[path] downDir called: dir.name=${dir?.name}, dir.value=${dir?.value}, dir.miss=${dir?.miss}`)
676679
if (dir?.miss) {
677680
return
678681
}
679682

680683
// Get the current path from the actual input state, not startPath
681684
const currentPath = global.__kitPromptState?.input || currentInput || startPath
685+
global.log(`[path] downDir: currentPath=${currentPath}, startPath=${startPath}`)
682686

683687
let targetPath = typeof dir === 'string' ? ogPath.resolve(currentPath, dir) : dir.value
688+
global.log(`[path] downDir: targetPath=${targetPath}`)
684689
let allowed = true
685690
let needsPermission =
686691
targetPath === home('Downloads') || targetPath === home('Documents') || targetPath === home('Desktop')
@@ -830,20 +835,52 @@ Please grant permission in System Preferences > Security & Privacy > Privacy > F
830835
}
831836

832837
let onRight = (input, state: AppState) => {
838+
global.log(`[path] onRight called: input=${input}, flaggedValue=${state?.flaggedValue}, focused=${state?.focused?.name}`)
833839
if (!state.flaggedValue) {
834-
downDir(state.focused)
840+
// Handle case where focused is stale/invalid (e.g., __app__/no-choice sentinel)
841+
// This can happen due to timing issues when the renderer hasn't updated focusedChoiceAtom yet
842+
const focused = state.focused
843+
if (!focused?.value || focused?.name === '__app__/no-choice') {
844+
global.log(`[path] onRight: focused is invalid (${focused?.name}), using first available choice`)
845+
// Try to get the first valid choice from kitPrevChoices
846+
const firstChoice = global.kitPrevChoices?.find(c => c?.value && !c?.miss && c?.name !== '__app__/no-choice')
847+
if (firstChoice) {
848+
global.log(`[path] onRight: using fallback choice: ${firstChoice.name}`)
849+
downDir(firstChoice)
850+
} else {
851+
global.log(`[path] onRight: no valid fallback choice available`)
852+
}
853+
} else {
854+
downDir(focused)
855+
}
835856
}
836857
}
837858

838859
let onLeft = (input, state) => {
860+
global.log(`[path] onLeft called: input=${input}, flaggedValue=${state?.flaggedValue}, focused=${state?.focused?.name}`)
839861
if (!state.flaggedValue) {
840-
upDir(state.focused)
862+
// Handle case where focused is stale/invalid - upDir doesn't strictly need a valid focused
863+
// since it navigates up regardless, but we pass the focused for history tracking
864+
const focused = state.focused
865+
if (!focused?.value || focused?.name === '__app__/no-choice') {
866+
global.log(`[path] onLeft: focused is invalid (${focused?.name}), using first available choice`)
867+
const firstChoice = global.kitPrevChoices?.find(c => c?.value && !c?.miss && c?.name !== '__app__/no-choice')
868+
upDir(firstChoice || focused)
869+
} else {
870+
upDir(focused)
871+
}
841872
}
842873
}
843874

844875
// Map FORWARD/BACK channels to right/left navigation
845-
let onForward = onRight
846-
let onBack = onLeft
876+
let onForward = (input, state) => {
877+
global.log(`[path] onForward called: input=${input}`)
878+
onRight(input, state)
879+
}
880+
let onBack = (input, state) => {
881+
global.log(`[path] onBack called: input=${input}`)
882+
onLeft(input, state)
883+
}
847884

848885
let sort = 'name'
849886
let dir = 'desc'

0 commit comments

Comments
 (0)