diff --git a/tests/browser/prompt-inspector.spec.mjs b/tests/browser/prompt-inspector.spec.mjs index eacf24b..f23efc5 100644 --- a/tests/browser/prompt-inspector.spec.mjs +++ b/tests/browser/prompt-inspector.spec.mjs @@ -172,3 +172,24 @@ test('filtering away a selected image aborts its pending metadata request',async await expect.poll(()=>page.evaluate(()=>window.inspectionAborts),{timeout:1500}).toBeGreaterThan(baseline); } finally {release();} }); + +test('responsive gallery defers observed size writes and still fits the new viewport',async({page})=>{ + await page.addInitScript(()=>{ + const Native=window.ResizeObserver;window.inlineResizeWrites=0; + window.ResizeObserver=class extends Native { + constructor(callback){super((entries,observer)=>{ + const sizes=entries.map(entry=>entry.target.style.height); + callback(entries,observer); + entries.forEach((entry,index)=>{ + if(entry.target.classList.contains('image-grid')&&entry.target.style.height!==sizes[index])window.inlineResizeWrites++; + }); + });} + }; + }); + await page.setViewportSize({width:1280,height:900});await page.goto(workspace); + await expect(page.locator('.image-card')).toHaveCount(4); + await page.setViewportSize({width:390,height:844}); + await expect.poll(()=>page.locator('.image-card').first().evaluate(node=>node.getBoundingClientRect().width)).toBeLessThan(390); + await expect.poll(()=>page.locator('.image-grid').evaluate(node=>Number.parseFloat(node.style.height))).toBeGreaterThan(900); + expect(await page.evaluate(()=>window.inlineResizeWrites)).toBe(0); +}); diff --git a/web/js/workspace/images.mjs b/web/js/workspace/images.mjs index 22e0f5d..1566cf8 100644 --- a/web/js/workspace/images.mjs +++ b/web/js/workspace/images.mjs @@ -287,9 +287,19 @@ export function mountImages(main, api, parentScope) { grid.style.height = `${geometry.height}px`; renderVisible(); } - const resize = new ResizeObserver(updateLayout); resize.observe(grid); + let resizeFrame = 0, observedWidth; + const resize = new ResizeObserver(entries => { + const width = entries[0].contentRect.width; + if (width === observedWidth) return; + observedWidth = width; + if (!resizeFrame) resizeFrame = requestAnimationFrame(() => { + resizeFrame = 0; + updateLayout(); + }); + }); + resize.observe(grid); scope.listen(window, 'scroll', scheduleVisible); - scope.cleanup(() => { resize.disconnect(); observer?.disconnect(); cancelAnimationFrame(frame); cancelAnimationFrame(loadFrame); }); + scope.cleanup(() => { resize.disconnect(); observer?.disconnect(); cancelAnimationFrame(resizeFrame); cancelAnimationFrame(frame); cancelAnimationFrame(loadFrame); }); function renderState(next) { const changed = state?.items !== next.items; if (changed) {