diff --git a/public/style.css b/public/style.css index ea1f02304..0b6067fb4 100644 --- a/public/style.css +++ b/public/style.css @@ -4987,7 +4987,8 @@ body[data-cb-preset="achromat"] { .fux-ctx-item { display: block; width: 100%; text-align: left; background: transparent; border: none; color: var(--text); padding: 5px 12px; font-size: 12px; cursor: pointer; font-family: var(--mono); } .fux-ctx-item:hover { background: var(--bg-hover, rgba(120,160,255,0.12)); } -.fux-saved-menu { position: absolute; top: 100%; left: 0; min-width: 320px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; z-index: var(--z-dropdown); box-shadow: 0 4px 14px rgba(0,0,0,0.3); margin-top: 4px; padding: 4px 0; } +/* #72: the Saved trigger sits at the right end of the filter row — anchor right and bound the width so long expressions ellipsize instead of widening #pktLeft. */ +.fux-saved-menu { position: absolute; top: 100%; right: 0; width: max-content; min-width: 320px; max-width: min(480px, calc(100vw - 32px)); background: var(--surface); border: 1px solid var(--border); border-radius: 6px; z-index: var(--z-dropdown); box-shadow: 0 4px 14px rgba(0,0,0,0.3); margin-top: 4px; padding: 4px 0; } .fux-saved-menu.hidden { display: none; } .fux-saved-header { padding: 6px 10px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); } .fux-saved-item { display: flex; align-items: center; gap: 8px; padding: 5px 10px; cursor: pointer; font-size: 12px; } diff --git a/test-filter-ux-e2e.js b/test-filter-ux-e2e.js index dc008667a..b256dc8d8 100644 --- a/test-filter-ux-e2e.js +++ b/test-filter-ux-e2e.js @@ -173,6 +173,44 @@ function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); } await page.evaluate(() => localStorage.removeItem('corescope_saved_filters_v1')); }); + // #72: the Saved trigger sits at the right end of the filter row; a + // left-anchored, unbounded menu ran past the viewport and made #pktLeft + // scroll horizontally. Below 601px the filter expression row is hidden. + for (const width of [1400, 1024, 768]) { + await step(`Saved-filter menu stays inside the viewport at ${width}px (#72)`, async () => { + await page.setViewportSize({ width, height: 900 }); + await page.evaluate(() => localStorage.setItem('corescope_saved_filters_v1', JSON.stringify([{ + name: 'E2E long filter', + expr: 'type == ADVERT && payload.flags.repeater == true && snr > 5 && hops > 1 && payload.name contains "a very long repeater name"', + ts: 0, + }]))); + await page.evaluate(() => document.getElementById('filterSavedMenu').classList.add('hidden')); + await page.click('#filterSavedTrigger'); + await page.waitForSelector('#filterSavedMenu:not(.hidden)'); + const r = await page.evaluate(() => { + const menu = document.getElementById('filterSavedMenu'); + const mr = menu.getBoundingClientRect(); + const left = document.getElementById('pktLeft'); + const exprs = Array.from(menu.querySelectorAll('.fux-saved-expr')); + const long = exprs[exprs.length - 1]; + return { + left: mr.left, right: mr.right, vw: document.documentElement.clientWidth, + paneOverflow: left.scrollWidth - left.clientWidth, + longTruncated: long.scrollWidth > long.clientWidth, + longText: long.textContent, + }; + }); + await page.evaluate(() => { + document.getElementById('filterSavedMenu').classList.add('hidden'); + localStorage.removeItem('corescope_saved_filters_v1'); + }); + assert(r.left >= 0 && r.right <= r.vw, 'menu outside viewport: ' + JSON.stringify(r)); + assert(r.paneOverflow <= 1, '#pktLeft scrolls horizontally: ' + JSON.stringify(r)); + assert(/a very long repeater name/.test(r.longText), 'long filter not rendered: ' + JSON.stringify(r)); + assert(r.longTruncated, 'long expression not ellipsized (menu width unbounded): ' + JSON.stringify(r)); + }); + } + await browser.close(); console.log(`\n=== Results: passed ${passed} failed ${failed} ===`);