From c14171b53462460508e9ead7d86528dc2ce0a001 Mon Sep 17 00:00:00 2001 From: Aamer Akhter Date: Sun, 23 Aug 2026 22:42:10 -0400 Subject: [PATCH] fix(file-viewer): COD-341 finalize deferred navigation --- src/web/public/panels-ui.js | 17 +++++++++------ test/file-browser-search.test.ts | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/web/public/panels-ui.js b/src/web/public/panels-ui.js index 57526bbf..ea00821e 100644 --- a/src/web/public/panels-ui.js +++ b/src/web/public/panels-ui.js @@ -3364,8 +3364,8 @@ Object.assign(CodemanApp.prototype, { const nextNormalState = { sessionId, showHidden, treeEpoch, phase: 'ready', data }; state.normalState = nextNormalState; this.fileBrowserData = data; - this._completeDeferredFileBrowserDirectory?.(nextNormalState); - this._renderFileBrowserNormalState(nextNormalState); + const deferredRendered = this._completeDeferredFileBrowserDirectory?.(nextNormalState) === true; + if (!deferredRendered) this._renderFileBrowserNormalState(nextNormalState); } catch (error) { if (!this._isFileBrowserTreeContextCurrent(record, true)) return; const nextNormalState = { @@ -3708,7 +3708,11 @@ Object.assign(CodemanApp.prototype, { _completeDeferredFileBrowserDirectory(normalState) { const state = this._ensureFileBrowserState(); const target = state.deferredDirectoryTarget; - if (!target || !this._isFileBrowserDirectoryContextCurrent(target)) return; + if (!target) return false; + if (!this._isFileBrowserDirectoryContextCurrent(target)) { + if (state.deferredDirectoryTarget === target) state.deferredDirectoryTarget = null; + return false; + } if ( !this._isFileBrowserNormalCompatible( normalState, @@ -3718,21 +3722,22 @@ Object.assign(CodemanApp.prototype, { ) || (normalState.phase !== 'ready' && normalState.phase !== 'error') ) { - return; + return false; } state.deferredDirectoryTarget = null; if (normalState.phase === 'error') { this._promptFileBrowserDirectoryReload(); - return; + return false; } const found = this._findFileBrowserDirectory(normalState.data?.tree, target.path); if (!found) { this._promptFileBrowserDirectoryReload(); - return; + return false; } this._leaveFileBrowserSearchForDirectory([...found.ancestors, found.target.path], normalState); + return true; }, _leaveFileBrowserSearchForDirectory(paths, normalState) { diff --git a/test/file-browser-search.test.ts b/test/file-browser-search.test.ts index 28040b55..13e804b8 100644 --- a/test/file-browser-search.test.ts +++ b/test/file-browser-search.test.ts @@ -1183,14 +1183,17 @@ describe('File Viewer server search', () => { path: 'src/components/widgets', view: 'search-results', }); + const renderNormalTree = vi.spyOn(app, 'renderFileBrowserTree'); pending[0].reply.resolve( response(successfulTree('unused', { tree: unixTree, totalFiles: 1, totalDirectories: 3 })) ); await treeLoad; + expect(renderNormalTree).toHaveBeenCalledTimes(1); expect(app._fileBrowserState.view).toBe('normal'); expect(elements.fileBrowserTree.innerHTML).toContain('index.ts'); + expect(elements.fileBrowserTree.querySelectorAll('.file-tree-item')).toHaveLength(4); expect(app.fileBrowserExpandedDirs.has('src/components/widgets')).toBe(true); }); @@ -1324,8 +1327,41 @@ describe('File Viewer server search', () => { expect(elements.fileBrowserSearch.value).toBe('widget'); expect(elements.fileBrowserTree.innerHTML).toContain('widgets'); expect(app.fileBrowserExpandedDirs.size).toBe(0); + expect(app._fileBrowserState.deferredDirectoryTarget).toBeNull(); } ); + + it('does not clear a newer deferred replacement while compare-and-clearing an obsolete binding', () => { + const { app } = loadPanel(); + const state = app._ensureFileBrowserState(); + const obsolete = { + ownerSessionId: 'session/A', + showHidden: false, + treeEpoch: 0, + searchEpoch: 0, + rawInput: 'old', + query: 'old', + path: 'old/path', + view: 'search-results', + }; + const replacement = { ...obsolete, searchEpoch: 1, rawInput: 'new', query: 'new', path: 'new/path' }; + state.deferredDirectoryTarget = obsolete; + vi.spyOn(app, '_isFileBrowserDirectoryContextCurrent').mockImplementationOnce((target) => { + expect(target).toBe(obsolete); + state.deferredDirectoryTarget = replacement; + return false; + }); + + app._completeDeferredFileBrowserDirectory({ + sessionId: 'session/A', + showHidden: false, + treeEpoch: 0, + phase: 'ready', + data: successfulTree('ready.ts').data, + }); + + expect(state.deferredDirectoryTarget).toBe(replacement); + }); }); describe('normal tree loads', () => {