diff --git a/ui/src/features/asset-picker/remoteCatalog.ts b/ui/src/features/asset-picker/remoteCatalog.ts index b668b92f..88542d75 100644 --- a/ui/src/features/asset-picker/remoteCatalog.ts +++ b/ui/src/features/asset-picker/remoteCatalog.ts @@ -14,6 +14,14 @@ import type { AssetConstraints, CatalogSort, PickerItem } from './types.ts' const DEFAULT_KINDS: AssetKind[] = ['image', 'video', 'audio', 'model3d', 'scene'] +export function remoteCatalogFilterKey( + kind: AssetKind | '', + constraints?: AssetConstraints, +): string { + if (kind) return kind + return (constraints?.kinds || []).join(',') +} + export function useRemoteCatalogPage(input: { enabled: boolean workspaceId?: string @@ -28,10 +36,11 @@ export function useRemoteCatalogPage(input: { const [items, setItems] = useState([]) const [total, setTotal] = useState(0) const [status, setStatus] = useState<'ready' | 'loading' | 'error'>(input.enabled ? 'loading' : 'ready') + const kindsKey = remoteCatalogFilterKey(input.kind, input.constraints) useEffect(() => () => session.current.dispose(), []) useEffect(() => { if (!input.enabled || !input.workspaceId) return - const kinds = input.kind ? [input.kind] : input.constraints?.kinds + const kinds = kindsKey ? kindsKey.split(',') as AssetKind[] : undefined void session.current.run({ workspace: input.workspaceId, search: input.query.trim() || undefined, @@ -45,7 +54,7 @@ export function useRemoteCatalogPage(input: { setTotal(result.total) setStatus('ready') }).catch(() => setStatus('error')) - }, [input.constraints, input.enabled, input.kind, input.page, input.query, input.retry, input.sort, input.workspaceId]) + }, [input.enabled, input.page, input.query, input.retry, input.sort, input.workspaceId, kindsKey]) return { items, total, status } } diff --git a/ui/tests/assetExplorerRemote.test.tsx b/ui/tests/assetExplorerRemote.test.tsx index 55fe9ffa..12ecabef 100644 --- a/ui/tests/assetExplorerRemote.test.tsx +++ b/ui/tests/assetExplorerRemote.test.tsx @@ -87,3 +87,50 @@ test('remote explorer pages the catalog at 24 and can find a hit past the first cleanup() } }) + +test('remote explorer does not refetch when the parent rebuilds the same constraints object', { concurrency: false }, async () => { + const { render, screen, waitFor, cleanup } = await import('@testing-library/react') + const { AssetExplorerDialog } = await import('../src/components/common/AssetExplorerDialog.tsx') + const calls: string[] = [] + const originalFetch = globalThis.fetch + globalThis.fetch = (async (input: RequestInfo | URL) => { + calls.push(String(input)) + return new Response(JSON.stringify({ + total: 1, + assets: [catalogAsset(0, 'clip.mp4')], + }), { status: 200, headers: { 'Content-Type': 'application/json' } }) + }) as typeof fetch + try { + const view = render( + undefined} + onChoose={() => undefined} + />, + ) + await waitFor(() => assert.ok(screen.getByTitle('clip.mp4'))) + assert.equal(calls.length, 1) + view.rerender( + undefined} + onChoose={() => undefined} + />, + ) + await waitFor(() => assert.ok(screen.getByTitle('clip.mp4'))) + assert.equal(calls.length, 1) + } finally { + globalThis.fetch = originalFetch + cleanup() + } +}) diff --git a/ui/tests/remoteCatalog.test.ts b/ui/tests/remoteCatalog.test.ts index 8f497268..f4b5f260 100644 --- a/ui/tests/remoteCatalog.test.ts +++ b/ui/tests/remoteCatalog.test.ts @@ -6,6 +6,7 @@ import { explorerCanConfirm, explorerListModel, explorerToolbarKinds, + remoteCatalogFilterKey, remotePageCount, resolveExplorerSelection, } from '../src/features/asset-picker/remoteCatalog.ts' @@ -39,6 +40,15 @@ function outputFrom(item: PickerItem): ApiOutput { } } +test('catalog filter key ignores constraints object identity', () => { + const first = { kinds: ['video'] as const, maxCount: 1, optional: false } + const second = { kinds: ['video'] as const, maxCount: 1, optional: false } + assert.equal(remoteCatalogFilterKey('', first), 'video') + assert.equal(remoteCatalogFilterKey('', second), remoteCatalogFilterKey('', first)) + assert.equal(remoteCatalogFilterKey('audio', first), 'audio') + assert.equal(remoteCatalogFilterKey('', undefined), '') +}) + test('remote pages are 24 wide and never collapse to zero', () => { assert.equal(ASSET_PICKER_PAGE_SIZE, 24) assert.equal(remotePageCount(0), 1)