@@ -282,13 +282,15 @@ interface ChatInputInnerProps {
282282 isProcessing : boolean
283283 presetMessage ?: string
284284 onPresetMessageConsumed ?: ( ) => void
285+ runId ?: string | null
285286}
286287
287288function ChatInputInner ( {
288289 onSubmit,
289290 isProcessing,
290291 presetMessage,
291292 onPresetMessageConsumed,
293+ runId,
292294} : ChatInputInnerProps ) {
293295 const controller = usePromptInputController ( )
294296 const message = controller . textInput . value
@@ -320,8 +322,9 @@ function ChatInputInner({
320322 < div className = "flex items-center gap-2 bg-background border border-border rounded-3xl shadow-xl px-4 py-2.5" >
321323 < PromptInputTextarea
322324 placeholder = "Type your message..."
323- disabled = { isProcessing }
324325 onKeyDown = { handleKeyDown }
326+ autoFocus
327+ focusTrigger = { runId }
325328 className = "min-h-6 py-0 border-0 shadow-none focus-visible:ring-0 rounded-none"
326329 />
327330 < Button
@@ -350,6 +353,7 @@ interface ChatInputWithMentionsProps {
350353 isProcessing : boolean
351354 presetMessage ?: string
352355 onPresetMessageConsumed ?: ( ) => void
356+ runId ?: string | null
353357}
354358
355359function ChatInputWithMentions ( {
@@ -360,6 +364,7 @@ function ChatInputWithMentions({
360364 isProcessing,
361365 presetMessage,
362366 onPresetMessageConsumed,
367+ runId,
363368} : ChatInputWithMentionsProps ) {
364369 return (
365370 < PromptInputProvider knowledgeFiles = { knowledgeFiles } recentFiles = { recentFiles } visibleFiles = { visibleFiles } >
@@ -368,6 +373,7 @@ function ChatInputWithMentions({
368373 isProcessing = { isProcessing }
369374 presetMessage = { presetMessage }
370375 onPresetMessageConsumed = { onPresetMessageConsumed }
376+ runId = { runId }
371377 />
372378 </ PromptInputProvider >
373379 )
@@ -376,6 +382,8 @@ function ChatInputWithMentions({
376382function App ( ) {
377383 // File browser state (for Knowledge section)
378384 const [ selectedPath , setSelectedPath ] = useState < string | null > ( null )
385+ const [ fileHistoryBack , setFileHistoryBack ] = useState < string [ ] > ( [ ] )
386+ const [ fileHistoryForward , setFileHistoryForward ] = useState < string [ ] > ( [ ] )
379387 const [ fileContent , setFileContent ] = useState < string > ( '' )
380388 const [ editorContent , setEditorContent ] = useState < string > ( '' )
381389 const [ tree , setTree ] = useState < TreeNode [ ] > ( [ ] )
@@ -404,6 +412,7 @@ function App() {
404412 const [ currentReasoning , setCurrentReasoning ] = useState < string > ( '' )
405413 const [ , setModelUsage ] = useState < LanguageModelUsage | null > ( null )
406414 const [ runId , setRunId ] = useState < string | null > ( null )
415+ const runIdRef = useRef < string | null > ( null )
407416 const [ isProcessing , setIsProcessing ] = useState ( false )
408417 const [ agentId ] = useState < string > ( 'copilot' )
409418 const [ presetMessage , setPresetMessage ] = useState < string | undefined > ( undefined )
@@ -426,6 +435,11 @@ function App() {
426435 // Onboarding state
427436 const [ showOnboarding , setShowOnboarding ] = useState ( false )
428437
438+ // Keep runIdRef in sync with runId state (for use in event handlers to avoid stale closures)
439+ useEffect ( ( ) => {
440+ runIdRef . current = runId
441+ } , [ runId ] )
442+
429443 // Load directory tree
430444 const loadDirectory = useCallback ( async ( ) => {
431445 try {
@@ -722,15 +736,17 @@ function App() {
722736 } , [ ] )
723737
724738 // Listen to run events
739+ // Listen to run events - use ref to avoid stale closure issues
725740 useEffect ( ( ) => {
726741 const cleanup = window . ipc . on ( 'runs:events' , ( ( event : unknown ) => {
727742 handleRunEvent ( event as RunEventType )
728743 } ) as ( event : null ) => void )
729744 return cleanup
730- } , [ runId ] )
745+ } , [ ] )
731746
732747 const handleRunEvent = ( event : RunEventType ) => {
733- if ( event . runId !== runId ) return
748+ // Use ref to get current runId to avoid stale closure issues
749+ if ( event . runId !== runIdRef . current ) return
734750
735751 console . log ( 'Run event:' , event . type , event )
736752
@@ -1043,6 +1059,7 @@ function App() {
10431059 setRunId ( null )
10441060 setMessage ( '' )
10451061 setModelUsage ( null )
1062+ setIsProcessing ( false )
10461063 setPendingPermissionRequests ( new Map ( ) )
10471064 setPendingAskHumanRequests ( new Map ( ) )
10481065 setAllPermissionRequests ( new Map ( ) )
@@ -1060,6 +1077,52 @@ function App() {
10601077 setIsGraphOpen ( false )
10611078 } , [ ] )
10621079
1080+ // File navigation with history tracking
1081+ const navigateToFile = useCallback ( ( path : string | null ) => {
1082+ if ( path === selectedPath ) return
1083+
1084+ // Push current path to back history (if we have one)
1085+ if ( selectedPath ) {
1086+ setFileHistoryBack ( prev => [ ...prev , selectedPath ] )
1087+ }
1088+ // Clear forward history when navigating to a new file
1089+ setFileHistoryForward ( [ ] )
1090+ setSelectedPath ( path )
1091+ } , [ selectedPath ] )
1092+
1093+ const navigateBack = useCallback ( ( ) => {
1094+ if ( fileHistoryBack . length === 0 ) return
1095+
1096+ const newBack = [ ...fileHistoryBack ]
1097+ const previousPath = newBack . pop ( ) !
1098+
1099+ // Push current path to forward history
1100+ if ( selectedPath ) {
1101+ setFileHistoryForward ( prev => [ ...prev , selectedPath ] )
1102+ }
1103+
1104+ setFileHistoryBack ( newBack )
1105+ setSelectedPath ( previousPath )
1106+ } , [ fileHistoryBack , selectedPath ] )
1107+
1108+ const navigateForward = useCallback ( ( ) => {
1109+ if ( fileHistoryForward . length === 0 ) return
1110+
1111+ const newForward = [ ...fileHistoryForward ]
1112+ const nextPath = newForward . pop ( ) !
1113+
1114+ // Push current path to back history
1115+ if ( selectedPath ) {
1116+ setFileHistoryBack ( prev => [ ...prev , selectedPath ] )
1117+ }
1118+
1119+ setFileHistoryForward ( newForward )
1120+ setSelectedPath ( nextPath )
1121+ } , [ fileHistoryForward , selectedPath ] )
1122+
1123+ const canNavigateBack = fileHistoryBack . length > 0
1124+ const canNavigateForward = fileHistoryForward . length > 0
1125+
10631126 // Handle image upload for the markdown editor
10641127 const handleImageUpload = useCallback ( async ( file : File ) : Promise < string | null > => {
10651128 try {
@@ -1113,7 +1176,7 @@ function App() {
11131176
11141177 const toggleExpand = ( path : string , kind : 'file' | 'dir' ) => {
11151178 if ( kind === 'file' ) {
1116- setSelectedPath ( path )
1179+ navigateToFile ( path )
11171180 setIsGraphOpen ( false )
11181181 return
11191182 }
@@ -1297,9 +1360,9 @@ function App() {
12971360 const openWikiLink = useCallback ( async ( wikiPath : string ) => {
12981361 const resolvedPath = await ensureWikiFile ( wikiPath )
12991362 if ( resolvedPath ) {
1300- setSelectedPath ( resolvedPath )
1363+ navigateToFile ( resolvedPath )
13011364 }
1302- } , [ ensureWikiFile , setSelectedPath ] )
1365+ } , [ ensureWikiFile , navigateToFile ] )
13031366
13041367 const wikiLinkConfig = React . useMemo ( ( ) => ( {
13051368 files : knowledgeFiles ,
@@ -1601,7 +1664,7 @@ function App() {
16011664 error = { graphStatus === 'error' ? ( graphError ?? 'Failed to build graph' ) : null }
16021665 onSelectNode = { ( path ) => {
16031666 setIsGraphOpen ( false )
1604- setSelectedPath ( path )
1667+ navigateToFile ( path )
16051668 } }
16061669 />
16071670 </ div >
@@ -1614,6 +1677,10 @@ function App() {
16141677 placeholder = "Start writing..."
16151678 wikiLinks = { wikiLinkConfig }
16161679 onImageUpload = { handleImageUpload }
1680+ onNavigateBack = { navigateBack }
1681+ onNavigateForward = { navigateForward }
1682+ canNavigateBack = { canNavigateBack }
1683+ canNavigateForward = { canNavigateForward }
16171684 />
16181685 </ div >
16191686 ) : (
@@ -1715,6 +1782,7 @@ function App() {
17151782 isProcessing = { isProcessing }
17161783 presetMessage = { presetMessage }
17171784 onPresetMessageConsumed = { ( ) => setPresetMessage ( undefined ) }
1785+ runId = { runId }
17181786 />
17191787 </ div >
17201788 </ div >
0 commit comments