fix(file-viewer): COD-341 complete search transitions

This commit is contained in:
Aamer Akhter
2026-08-23 22:27:26 -04:00
parent 921933775b
commit acd9ffedc8
2 changed files with 816 additions and 10 deletions
+599 -1
View File
@@ -189,6 +189,7 @@ function loadPanel(options: { sessionId?: string | null; showHidden?: boolean }
app.fileBrowserAllExpanded = false;
app.fileBrowserShowHidden = options.showHidden ?? false;
app.openFilePreview = vi.fn();
app.showToast = vi.fn();
app.loadAppSettingsFromStorage = () => ({ showFileBrowser: false });
app.getDefaultSettings = () => ({});
app.saveAppSettingsToStorage = vi.fn();
@@ -317,6 +318,8 @@ function loadRealSelectSessionHarness(options: { terminalFailure?: boolean } = {
}
async function startSearch(app: Record<string, any>, query = 'widget') {
const input = app.$('fileBrowserSearch');
if (input) input.value = query;
app.filterFileBrowser(query);
await vi.advanceTimersByTimeAsync(250);
}
@@ -471,14 +474,21 @@ describe('File Viewer server search', () => {
expect(elements.fileBrowserStatus.textContent).toBe('2 matches (truncated)');
expect(elements.fileBrowserExpandBtn.disabled).toBe(true);
app._openFileBrowserSearchDirectory = vi.fn();
app.activeSessionId = 'later-session';
const rows = elements.fileBrowserTree.querySelectorAll('.file-tree-item');
rows[0].click();
rows[1].click();
expect(app.openFilePreview).toHaveBeenCalledWith('src/widget.ts', 'session/A');
expect(app._fileBrowserState.deferredDirectoryTarget).toEqual({
expect(app._openFileBrowserSearchDirectory).toHaveBeenCalledWith({
ownerSessionId: 'session/A',
showHidden: false,
treeEpoch: 0,
searchEpoch: 1,
rawInput: 'widget',
query: 'widget',
path: 'docs/widgets',
view: 'search-results',
});
});
@@ -730,6 +740,594 @@ describe('File Viewer server search', () => {
expect(html).not.toContain('<img src=x');
});
describe('hidden-preference transitions', () => {
it('owns normal loading immediately and reruns an exact valid query only after the replacement tree succeeds', async () => {
const { app, elements, pending } = loadPanel();
app.fileBrowserData = successfulTree('old-preference.ts').data;
await settleSearch(
app,
pending,
successfulData({
matches: [{ name: 'old-result.ts', path: 'old-result.ts', type: 'file' }],
matchCount: 1,
})
);
elements.fileBrowserSearch.value = ' widget ';
app._fileBrowserState.filter = ' widget ';
app.fileBrowserFilter = ' widget ';
const beforeSearchEpoch = app._fileBrowserState.searchEpoch;
const beforeTreeEpoch = app._fileBrowserState.treeEpoch;
const toggle = app.toggleFileBrowserHidden();
expect(app.fileBrowserShowHidden).toBe(true);
expect(elements.fileBrowserHiddenBtn.attrs['aria-pressed']).toBe('true');
expect(app._fileBrowserState.searchEpoch).toBe(beforeSearchEpoch + 1);
expect(app._fileBrowserState.treeEpoch).toBe(beforeTreeEpoch + 1);
expect(app._fileBrowserState.view).toBe('normal');
expect(app._fileBrowserState.matches).toEqual([]);
expect(app._fileBrowserState.normalState).toMatchObject({
sessionId: 'session/A',
showHidden: true,
treeEpoch: beforeTreeEpoch + 1,
phase: 'loading',
});
expect(app.fileBrowserData).toBeNull();
expect(elements.fileBrowserSearch.value).toBe(' widget ');
expect(elements.fileBrowserExpandBtn.disabled).toBe(true);
expect(elements.fileBrowserTree.innerHTML).toContain('Loading files');
expect(elements.fileBrowserTree.innerHTML).not.toContain('old-result.ts');
expect(elements.fileBrowserTree.innerHTML).not.toContain('old-preference.ts');
expect(pending[1].url).toBe('/api/sessions/session%2FA/files?depth=5&showHidden=true');
pending[1].reply.resolve(response(successfulTree('new-preference.ts')));
await toggle;
expect(app._fileBrowserState.view).toBe('search-pending');
expect(elements.fileBrowserTree.innerHTML).toContain('Searching');
await vi.advanceTimersByTimeAsync(250);
expect(pending[2].url).toBe('/api/sessions/session%2FA/files?depth=5&showHidden=true&q=widget');
pending[2].reply.resolve(
response(
successfulData({
matches: [{ name: 'new-result.ts', path: 'new-result.ts', type: 'file' }],
matchCount: 1,
})
)
);
await vi.advanceTimersByTimeAsync(0);
expect(elements.fileBrowserTree.innerHTML).toContain('new-result.ts');
});
it('lets input during the reload own the UI and invalidates the captured continuation', async () => {
const { app, elements, pending } = loadPanel();
await settleSearch(
app,
pending,
successfulData({ matches: [{ name: 'old.ts', path: 'old.ts', type: 'file' }], matchCount: 1 })
);
const toggle = app.toggleFileBrowserHidden();
await startSearch(app, 'new query');
expect(pending[2].url).toContain('showHidden=true&q=new%20query');
pending[2].reply.resolve(
response(
successfulData({ matches: [{ name: 'new-query.ts', path: 'new-query.ts', type: 'file' }], matchCount: 1 })
)
);
await vi.advanceTimersByTimeAsync(0);
expect(elements.fileBrowserTree.innerHTML).toContain('new-query.ts');
pending[1].reply.resolve(response(successfulTree('behind-new-query.ts')));
await toggle;
expect(app._fileBrowserState.normalState.phase).toBe('ready');
expect(elements.fileBrowserTree.innerHTML).toContain('new-query.ts');
expect(elements.fileBrowserTree.innerHTML).not.toContain('behind-new-query.ts');
await vi.advanceTimersByTimeAsync(250);
expect(pending).toHaveLength(3);
});
it.each([
['tree epoch', (app: Record<string, any>) => app._fileBrowserState.treeEpoch++],
['search epoch', (app: Record<string, any>) => app._fileBrowserState.searchEpoch++],
['owner', (app: Record<string, any>) => (app._fileBrowserState.ownerSessionId = 'session/B')],
['active session', (app: Record<string, any>) => (app.activeSessionId = 'session/B')],
['hidden preference', (app: Record<string, any>) => (app.fileBrowserShowHidden = false)],
['raw filter', (app: Record<string, any>) => (app._fileBrowserState.filter = 'changed')],
[
'raw input',
(_app: Record<string, any>, elements: Record<string, FakeElement>) =>
(elements.fileBrowserSearch.value = 'changed'),
],
[
'panel visibility',
(_app: Record<string, any>, elements: Record<string, FakeElement>) =>
elements.fileBrowserPanel.classList.remove('visible'),
],
])('does not rerun a captured query after its %s context changes', async (_case, mutate) => {
const { app, elements, pending } = loadPanel();
await settleSearch(app, pending, successfulData());
const toggle = app.toggleFileBrowserHidden();
mutate(app, elements);
pending[1].reply.resolve(response(successfulTree('replacement.ts')));
await toggle;
await vi.advanceTimersByTimeAsync(250);
expect(pending).toHaveLength(2);
});
it('invalidates the first load and continuation when two toggles happen rapidly', async () => {
const { app, elements, pending } = loadPanel();
await settleSearch(
app,
pending,
successfulData({ matches: [{ name: 'old.ts', path: 'old.ts', type: 'file' }], matchCount: 1 })
);
const firstToggle = app.toggleFileBrowserHidden();
const secondToggle = app.toggleFileBrowserHidden();
expect(pending.slice(1).map(({ url }) => url)).toEqual([
'/api/sessions/session%2FA/files?depth=5&showHidden=true',
'/api/sessions/session%2FA/files?depth=5&showHidden=false',
]);
pending[1].reply.resolve(response(successfulTree('stale-hidden.ts')));
await firstToggle;
expect(elements.fileBrowserTree.innerHTML).toContain('Loading files');
expect(elements.fileBrowserTree.innerHTML).not.toContain('stale-hidden.ts');
pending[2].reply.resolve(response(successfulTree('fresh-visible.ts')));
await secondToggle;
await vi.advanceTimersByTimeAsync(250);
expect(pending).toHaveLength(4);
expect(pending[3].url).toBe('/api/sessions/session%2FA/files?depth=5&showHidden=false&q=widget');
expect(app.fileBrowserShowHidden).toBe(false);
});
it('stores and escapes a tree failure, keeps the new preference, and exposes the error on clear', async () => {
const { app, elements, pending } = loadPanel();
await settleSearch(
app,
pending,
successfulData({ matches: [{ name: 'old.ts', path: 'old.ts', type: 'file' }], matchCount: 1 })
);
const toggle = app.toggleFileBrowserHidden();
pending[1].reply.reject(new Error('<img src=x onerror=boom>'));
await toggle;
expect(app.fileBrowserShowHidden).toBe(true);
expect(app._fileBrowserState.normalState).toMatchObject({ phase: 'error', showHidden: true });
expect(app._fileBrowserState.normalState.error).toBe('<img src=x onerror=boom>');
expect(elements.fileBrowserTree.innerHTML).toContain('&lt;img src=x onerror=boom&gt;');
expect(elements.fileBrowserTree.innerHTML).not.toContain('<img src=x');
expect(pending).toHaveLength(2);
elements.fileBrowserSearch.value = '';
app.filterFileBrowser('');
expect(elements.fileBrowserTree.innerHTML).toContain('&lt;img src=x onerror=boom&gt;');
});
it('keeps a newer search result visible when the hidden reload fails, then reveals the stored error on clear', async () => {
const { app, elements, pending } = loadPanel();
await settleSearch(
app,
pending,
successfulData({ matches: [{ name: 'old.ts', path: 'old.ts', type: 'file' }], matchCount: 1 })
);
const toggle = app.toggleFileBrowserHidden();
await startSearch(app, 'new query');
pending[2].reply.resolve(
response(successfulData({ matches: [{ name: 'newer.ts', path: 'newer.ts', type: 'file' }], matchCount: 1 }))
);
await vi.advanceTimersByTimeAsync(0);
pending[1].reply.reject(new Error('new preference failed'));
await toggle;
expect(elements.fileBrowserTree.innerHTML).toContain('newer.ts');
expect(app._fileBrowserState.normalState).toMatchObject({ phase: 'error', error: 'new preference failed' });
elements.fileBrowserSearch.value = '';
app.filterFileBrowser('');
expect(elements.fileBrowserTree.innerHTML).toContain('new preference failed');
});
it.each([
['ready', false],
['error', true],
])(
'reloads behind an unchanged overlength query-error and reveals the new normal %s state on clear',
async (_case, fail) => {
const { app, elements, pending } = loadPanel();
const query = 'x'.repeat(257);
elements.fileBrowserSearch.value = query;
app.filterFileBrowser(query);
const message = elements.fileBrowserTree.innerHTML;
const beforeSearchEpoch = app._fileBrowserState.searchEpoch;
const beforeTreeEpoch = app._fileBrowserState.treeEpoch;
const toggle = app.toggleFileBrowserHidden();
expect(app._fileBrowserState.searchEpoch).toBe(beforeSearchEpoch + 1);
expect(app._fileBrowserState.treeEpoch).toBe(beforeTreeEpoch + 1);
expect(app._fileBrowserState.view).toBe('query-error');
expect(elements.fileBrowserTree.innerHTML).toBe(message);
expect(pending).toHaveLength(1);
expect(pending[0].url).not.toContain('&q=');
if (fail) pending[0].reply.reject(new Error('hidden tree failed'));
else pending[0].reply.resolve(response(successfulTree('hidden-ready.ts')));
await toggle;
await vi.advanceTimersByTimeAsync(500);
expect(elements.fileBrowserTree.innerHTML).toBe(message);
expect(pending).toHaveLength(1);
elements.fileBrowserSearch.value = '';
app.filterFileBrowser('');
if (fail) expect(elements.fileBrowserTree.innerHTML).toContain('hidden tree failed');
else expect(elements.fileBrowserTree.innerHTML).toContain('hidden-ready.ts');
}
);
it('recovers a failed hidden transition through refresh without reverting the preference', async () => {
const { app, elements, pending } = loadPanel();
await settleSearch(app, pending, successfulData());
const toggle = app.toggleFileBrowserHidden();
pending[1].reply.reject(new Error('toggle failed'));
await toggle;
const refresh = app.refreshFileBrowser();
expect(app.fileBrowserShowHidden).toBe(true);
expect(pending[2].url).toContain('showHidden=true');
pending[2].reply.resolve(response(successfulTree('refresh-recovered.ts')));
await refresh;
expect(elements.fileBrowserTree.innerHTML).toContain('refresh-recovered.ts');
});
it('drops the prior-preference normal cache safely when the tree surface is missing', async () => {
const { app, elements } = loadPanel();
app.fileBrowserData = successfulTree('old-preference.ts').data;
app._ensureFileBrowserState();
delete elements.fileBrowserTree;
await expect(app.toggleFileBrowserHidden()).resolves.toBeUndefined();
expect(app.fileBrowserShowHidden).toBe(true);
expect(app.fileBrowserData).toBeNull();
expect(app._fileBrowserState.normalState).toBeNull();
});
});
describe('Expand and Collapse gating', () => {
it.each(['search-pending', 'search-results', 'search-error', 'query-error', 'normal'])(
'disables the Expand control for trimmed input while the %s view owns the panel',
(view) => {
const { app, elements } = loadPanel();
const state = app._ensureFileBrowserState();
state.view = view;
state.filter = ' query ';
elements.fileBrowserSearch.value = ' query ';
app._syncFileBrowserExpandBtn();
expect(elements.fileBrowserExpandBtn.disabled).toBe(true);
state.filter = ' ';
elements.fileBrowserSearch.value = ' ';
app._syncFileBrowserExpandBtn();
expect(elements.fileBrowserExpandBtn.disabled).toBe(false);
}
);
it('guards Expand and Collapse without repainting or changing expansion state during a query', () => {
const { app, elements } = loadPanel();
app.fileBrowserData = successfulTree('tree.ts').data;
app._ensureFileBrowserState().filter = 'query';
elements.fileBrowserSearch.value = 'query';
elements.fileBrowserTree.innerHTML = '<div>search result sentinel</div>';
elements.fileBrowserExpandBtn.innerHTML = '\u229E';
const render = vi.spyOn(app, 'renderFileBrowserTree');
app.toggleFileBrowserExpand();
expect(app.fileBrowserAllExpanded).toBe(false);
expect(app.fileBrowserExpandedDirs.size).toBe(0);
expect(elements.fileBrowserExpandBtn.innerHTML).toBe('\u229E');
expect(elements.fileBrowserTree.innerHTML).toBe('<div>search result sentinel</div>');
expect(render).not.toHaveBeenCalled();
});
it('restores the control without changing its expansion state or icon when a search is cleared', () => {
const { app, elements } = loadPanel();
app.fileBrowserData = successfulTree('tree.ts').data;
app.fileBrowserAllExpanded = true;
app.fileBrowserExpandedDirs.add('existing');
elements.fileBrowserExpandBtn.innerHTML = '\u229F';
elements.fileBrowserSearch.value = 'query';
app.filterFileBrowser('query');
elements.fileBrowserSearch.value = '';
app.filterFileBrowser('');
expect(elements.fileBrowserExpandBtn.disabled).toBe(false);
expect(elements.fileBrowserExpandBtn.innerHTML).toBe('\u229F');
expect(app.fileBrowserAllExpanded).toBe(true);
expect(app.fileBrowserExpandedDirs.has('existing')).toBe(true);
});
});
describe('search directory navigation', () => {
const unixTree = [
{
name: 'src',
path: 'src',
type: 'directory',
children: [
{
name: 'components',
path: 'src/components',
type: 'directory',
children: [
{
name: 'widgets',
path: 'src/components/widgets',
type: 'directory',
children: [{ name: 'index.ts', path: 'src/components/widgets/index.ts', type: 'file' }],
},
],
},
],
},
];
it('finds a directory and its object-derived ancestors without parsing separators, including Windows paths', () => {
const { app } = loadPanel();
const windowsTree = [
{
name: 'src',
path: 'C:\\repo\\src',
type: 'directory',
children: [
{
name: 'widgets',
path: 'C:\\repo\\src\\widgets',
type: 'directory',
children: [],
},
],
},
];
const found = app._findFileBrowserDirectory(windowsTree, 'C:\\repo\\src\\widgets');
expect(found.target.path).toBe('C:\\repo\\src\\widgets');
expect(found.ancestors).toEqual(['C:\\repo\\src']);
});
it('leaves search explicitly for a ready directory and resets every search field without synthetic input', async () => {
const { app, elements, pending } = loadPanel();
app.fileBrowserData = successfulTree('unused', {
tree: unixTree,
totalFiles: 1,
totalDirectories: 3,
}).data;
app.fileBrowserAllExpanded = true;
app.fileBrowserExpandedDirs.add('already-open');
elements.fileBrowserExpandBtn.innerHTML = '\u229F';
await settleSearch(
app,
pending,
successfulData({
matches: [{ name: 'widgets', path: 'src/components/widgets', type: 'directory' }],
matchCount: 1,
})
);
const staleTimer = vi.fn();
app._fileBrowserState.inFlight = { timer: setTimeout(staleTimer, 10) };
app._fileBrowserState.deferredDirectoryTarget = { stale: true };
const beforeEpoch = app._fileBrowserState.searchEpoch;
const filter = vi.spyOn(app, 'filterFileBrowser');
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
await vi.advanceTimersByTimeAsync(20);
expect(app._fileBrowserState.searchEpoch).toBe(beforeEpoch + 1);
expect(app._fileBrowserState.inFlight).toBeNull();
expect(app._fileBrowserState.filter).toBe('');
expect(app.fileBrowserFilter).toBe('');
expect(app._fileBrowserState.matches).toEqual([]);
expect(app._fileBrowserState.deferredDirectoryTarget).toBeNull();
expect(app._fileBrowserState.view).toBe('normal');
expect(elements.fileBrowserSearch.value).toBe('');
expect(elements.fileBrowserExpandBtn.disabled).toBe(false);
expect(elements.fileBrowserExpandBtn.innerHTML).toBe('\u229F');
expect(app.fileBrowserAllExpanded).toBe(true);
expect([...app.fileBrowserExpandedDirs]).toEqual([
'already-open',
'src',
'src/components',
'src/components/widgets',
]);
expect(app.fileBrowserData).toBe(app._fileBrowserState.normalState.data);
expect(elements.fileBrowserTree.innerHTML).toContain('index.ts');
expect(staleTimer).not.toHaveBeenCalled();
expect(filter).not.toHaveBeenCalled();
});
it('defers a directory transfer during the exact normal load while keeping results visible', async () => {
const { app, elements, pending } = loadPanel();
const treeLoad = app.loadFileBrowser('session/A');
await startSearch(app);
pending[1].reply.resolve(
response(
successfulData({
matches: [{ name: 'widgets', path: 'src/components/widgets', type: 'directory' }],
matchCount: 1,
})
)
);
await vi.advanceTimersByTimeAsync(0);
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
expect(elements.fileBrowserTree.innerHTML).toContain('widgets');
expect(app._fileBrowserState.view).toBe('search-results');
expect(app._fileBrowserState.deferredDirectoryTarget).toEqual({
ownerSessionId: 'session/A',
showHidden: false,
treeEpoch: 0,
searchEpoch: 1,
rawInput: 'widget',
query: 'widget',
path: 'src/components/widgets',
view: 'search-results',
});
pending[0].reply.resolve(
response(successfulTree('unused', { tree: unixTree, totalFiles: 1, totalDirectories: 3 }))
);
await treeLoad;
expect(app._fileBrowserState.view).toBe('normal');
expect(elements.fileBrowserTree.innerHTML).toContain('index.ts');
expect(app.fileBrowserExpandedDirs.has('src/components/widgets')).toBe(true);
});
it('cancels a deferred directory when a new query arrives', async () => {
const { app, elements, pending } = loadPanel();
const treeLoad = app.loadFileBrowser('session/A');
await startSearch(app);
pending[1].reply.resolve(
response(
successfulData({
matches: [{ name: 'widgets', path: 'src/components/widgets', type: 'directory' }],
matchCount: 1,
})
)
);
await vi.advanceTimersByTimeAsync(0);
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
expect(app._fileBrowserState.deferredDirectoryTarget).not.toBeNull();
elements.fileBrowserSearch.value = 'new query';
app.filterFileBrowser('new query');
expect(app._fileBrowserState.deferredDirectoryTarget).toBeNull();
pending[0].reply.resolve(
response(successfulTree('unused', { tree: unixTree, totalFiles: 1, totalDirectories: 3 }))
);
await treeLoad;
expect(app._fileBrowserState.view).toBe('search-pending');
expect(elements.fileBrowserTree.innerHTML).toContain('Searching');
});
it('retains results, clears the deferred target, and toasts when the exact normal load fails', async () => {
const { app, elements, pending } = loadPanel();
const treeLoad = app.loadFileBrowser('session/A');
await startSearch(app);
pending[1].reply.resolve(
response(
successfulData({
matches: [{ name: 'widgets', path: 'src/components/widgets', type: 'directory' }],
matchCount: 1,
})
)
);
await vi.advanceTimersByTimeAsync(0);
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
pending[0].reply.reject(new Error('tree failed'));
await treeLoad;
expect(app._fileBrowserState.deferredDirectoryTarget).toBeNull();
expect(app._fileBrowserState.view).toBe('search-results');
expect(elements.fileBrowserTree.innerHTML).toContain('widgets');
expect(app.showToast).toHaveBeenCalledWith('Reload files before opening this folder', 'info');
expect(app._fileBrowserState.normalState).toMatchObject({ phase: 'error', error: 'tree failed' });
elements.fileBrowserSearch.value = '';
app.filterFileBrowser('');
expect(elements.fileBrowserTree.innerHTML).toContain('tree failed');
});
it.each([
['error', { phase: 'error', error: 'cached failure' }],
[
'ready cache missing the directory',
{
phase: 'ready',
data: successfulTree('other.ts').data,
},
],
])('retains search results and prompts for reload for a compatible %s normal state', async (_case, normal) => {
const { app, elements, pending } = loadPanel();
const state = app._ensureFileBrowserState();
state.normalState = {
sessionId: 'session/A',
showHidden: false,
treeEpoch: 0,
...normal,
};
await settleSearch(
app,
pending,
successfulData({ matches: [{ name: 'missing', path: 'missing', type: 'directory' }], matchCount: 1 })
);
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
expect(app._fileBrowserState.view).toBe('search-results');
expect(app._fileBrowserState.filter).toBe('widget');
expect(elements.fileBrowserTree.innerHTML).toContain('missing');
expect(app.showToast).toHaveBeenCalledWith('Reload files before opening this folder', 'info');
expect(app._fileBrowserState.deferredDirectoryTarget).toBeNull();
});
it.each(['session', 'hidden preference', 'search epoch', 'tree epoch'])(
'does not complete a deferred transfer after a new %s invalidates its binding',
async (scenario) => {
const { app, elements, pending } = loadPanel();
app.loadFileBrowser('session/A');
await startSearch(app);
pending[1].reply.resolve(
response(
successfulData({
matches: [{ name: 'widgets', path: 'src/components/widgets', type: 'directory' }],
matchCount: 1,
})
)
);
await vi.advanceTimersByTimeAsync(0);
elements.fileBrowserTree.querySelectorAll('.file-tree-item')[0].click();
const ready = {
sessionId: 'session/A',
showHidden: false,
treeEpoch: 0,
phase: 'ready',
data: successfulTree('unused', { tree: unixTree, totalFiles: 1, totalDirectories: 3 }).data,
};
if (scenario === 'session') {
app.activeSessionId = 'session/B';
app._fileBrowserState.ownerSessionId = 'session/B';
} else if (scenario === 'hidden preference') {
app.fileBrowserShowHidden = true;
} else if (scenario === 'search epoch') {
app._fileBrowserState.searchEpoch++;
} else {
app._fileBrowserState.treeEpoch++;
}
app._completeDeferredFileBrowserDirectory(ready);
expect(app._fileBrowserState.view).toBe('search-results');
expect(elements.fileBrowserSearch.value).toBe('widget');
expect(elements.fileBrowserTree.innerHTML).toContain('widgets');
expect(app.fileBrowserExpandedDirs.size).toBe(0);
}
);
});
describe('normal tree loads', () => {
it('deduplicates compatible in-flight loads and reuses compatible ready and error states', async () => {
const ready = loadPanel();