mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-26 04:14:04 +00:00
Partial fix for #1139 — closes Bug B (desktop More menu degenerate). Bug A (mobile hamburger) blocked on user device info; left for separate PR. ## What this changes `public/app.js` `applyNavPriority()` (the >1100px measurement branch): add a "minimum More menu size" floor. After the greedy `fits()` loop terminates, if exactly one link ended up in `is-overflow`, promote one more from the overflow queue so the dropdown contains ≥2 items. ```diff let i = 0; while (!fits() && i < overflowQueue.length) { overflowQueue[i].classList.add('is-overflow'); i++; } + // #1139 Bug B: floor the More menu at >=2 items. + var overflowedCount = allLinks.filter(a => a.classList.contains('is-overflow')).length; + if (overflowedCount === 1 && i < overflowQueue.length) { + overflowQueue[i].classList.add('is-overflow'); + i++; + } rebuildMoreMenu(); ``` The ≤1100px Priority+ design contract (5 high-priority + More) is unchanged; the floor only applies on the measurement branch. ## Why Above 1100px the measurement loop greedily fills inline links until something overflows. If exactly one non-priority link is wider than the remaining slack, the loop pushes only it into overflow and stops — producing a one-item "More ▾" dropdown. With the fixture stats this reproduces deterministically at 1600px (overflow=`["🎵 Lab"]`); the prod report on 1101–1278px is the same root cause with realistic `#navStats` width consuming most of the remaining slack. ## TDD - Red: `test-nav-more-floor-1139-e2e.js` sweeps 1101, 1150, 1200, 1240, 1278, 1280, 1340, 1500, 1600, 1700px and asserts `#navMoreMenu.children.length` is 0 or ≥2 — never 1. On master it fails at 1600px (`items=1, overflow=[#/audio-lab]`). - Green: with the floor in place all 10 viewports pass. - Existing `test-nav-priority-1102-e2e.js` and `test-nav-fluid-1055-e2e.js` still pass (5/5 and 20/20). - Wired into CI alongside the other nav E2E tests. ## Out of scope (Bug A) The mobile hamburger inert-button report needs a console snapshot from the affected device (pasted in the issue body) to pin the root cause. Left open for a follow-up PR. This PR uses "Partial fix" intentionally and does NOT include `Fixes #1139` so the issue stays open. --------- Co-authored-by: Kpa-clawbot <bot@kpa-clawbot.local>
This commit is contained in:
@@ -228,6 +228,7 @@ jobs:
|
||||
BASE_URL=http://localhost:13581 node test-map-modal-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-fluid-1055-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-priority-1102-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-nav-more-floor-1139-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
BASE_URL=http://localhost:13581 node test-channel-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
BASE_URL=http://localhost:13581 node test-table-fluid-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
BASE_URL=http://localhost:13581 node test-issue-1122-packets-filter-ux-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||
|
||||
@@ -1065,6 +1065,28 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
overflowQueue[i].classList.add('is-overflow');
|
||||
i++;
|
||||
}
|
||||
// #1139 Bug B: floor the More menu at >=2 items. The greedy
|
||||
// fits() loop above is happy to stop after pushing exactly ONE
|
||||
// link into overflow (commonly "🎵 Lab" at ~1600px viewports),
|
||||
// producing a degenerate single-item dropdown. If exactly one
|
||||
// link overflowed, promote one more from the queue so the user
|
||||
// sees a useful menu instead of a one-item fragment. Skip when
|
||||
// nothing overflowed (everything fits inline → More is hidden,
|
||||
// which is the correct UX) and skip when the queue is exhausted.
|
||||
var overflowedCount = allLinks.filter(a => a.classList.contains('is-overflow')).length;
|
||||
if (overflowedCount === 1) {
|
||||
if (i < overflowQueue.length) {
|
||||
overflowQueue[i].classList.add('is-overflow');
|
||||
i++;
|
||||
} else {
|
||||
// Defensive: queue exhausted with exactly 1 overflowed link
|
||||
// means we cannot satisfy the >=2 floor (only one promotable
|
||||
// link existed). Surface it loudly instead of silently
|
||||
// shipping the degenerate single-item dropdown the floor
|
||||
// was added to prevent.
|
||||
console.warn('[nav] More menu floor: overflowQueue exhausted with 1 item; cannot enforce >=2 floor');
|
||||
}
|
||||
}
|
||||
rebuildMoreMenu();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env node
|
||||
/* Issue #1139 Bug B — desktop "More ▾" overflow menu degenerate at 1101–1278px.
|
||||
*
|
||||
* Above 1100px the Priority+ measurement loop in public/app.js
|
||||
* (applyNavPriority) iteratively pushes one link at a time into the
|
||||
* overflow set until the inline strip "fits". At intermediate widths
|
||||
* (~1101-1278px) the loop terminates with exactly ONE link in
|
||||
* overflow — producing a degenerate "More ▾" dropdown that contains
|
||||
* just one item (often the active route).
|
||||
*
|
||||
* Acceptance for Bug B (band-specific):
|
||||
* Bug band (1101, 1150, 1200, 1240, 1278px): items >= 2 STRICTLY.
|
||||
* items===0 here would mean the More menu vanished entirely —
|
||||
* a different regression class this test must catch.
|
||||
* Wide band (1280, 1340, 1500, 1600, 1700px): items === 0
|
||||
* (everything fits inline) OR items >= 2 (overflow with floor).
|
||||
* In both bands items === 1 is the original Bug B and fails.
|
||||
*
|
||||
* This test FAILS on master @ origin/master (>=1 viewport returns 1)
|
||||
* and PASSES once the floor check is added to applyNavPriority().
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const BASE = process.env.BASE_URL || 'http://localhost:13581';
|
||||
// Two bands with DIFFERENT acceptance criteria:
|
||||
// - bug band (1101-1278px): the user-reported regression range. The
|
||||
// More menu MUST be present with >=2 items. items===0 here would
|
||||
// mean the menu vanished entirely (a different regression class
|
||||
// this test must catch). items===1 is the original Bug B.
|
||||
// - wide band (>=1280px): everything genuinely fits at some widths
|
||||
// (items===0 is acceptable) AND items>=2 is acceptable (overflow
|
||||
// with the floor enforced). items===1 is still the bug.
|
||||
// Narrower mobile/tablet widths are covered by separate E2E tests.
|
||||
const BUG_BAND = [1101, 1150, 1200, 1240, 1278];
|
||||
const WIDE_BAND = [1280, 1340, 1500, 1600, 1700];
|
||||
const VIEWPORTS = [...BUG_BAND, ...WIDE_BAND];
|
||||
const HEIGHT = 800;
|
||||
|
||||
async function main() {
|
||||
let browser;
|
||||
try {
|
||||
browser = await chromium.launch({
|
||||
headless: true,
|
||||
executablePath: process.env.CHROMIUM_PATH || undefined,
|
||||
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
||||
});
|
||||
} catch (err) {
|
||||
if (process.env.CHROMIUM_REQUIRE === '1') {
|
||||
console.error(`test-nav-more-floor-1139-e2e.js: FAIL — Chromium required but unavailable: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`test-nav-more-floor-1139-e2e.js: SKIP (Chromium unavailable: ${err.message.split('\n')[0]})`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let failures = 0;
|
||||
let passes = 0;
|
||||
try {
|
||||
const ctx = await browser.newContext();
|
||||
const page = await ctx.newPage();
|
||||
page.setDefaultTimeout(15000);
|
||||
|
||||
for (const w of VIEWPORTS) {
|
||||
await page.setViewportSize({ width: w, height: HEIGHT });
|
||||
await page.goto(`${BASE}/#/home`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('.top-nav .nav-links');
|
||||
await page.evaluate(() => document.fonts && document.fonts.ready ? document.fonts.ready : null);
|
||||
// Don't mutate stats text — leave whatever the page rendered. The
|
||||
// bug reproduces deterministically against fixture stats at ~1600px
|
||||
// (only "🎵 Lab" overflows → degenerate 1-item More menu) and the
|
||||
// sweep covers the prod-reported 1101-1278px band as well.
|
||||
await page.evaluate(() => { window.dispatchEvent(new Event('resize')); });
|
||||
// Two rAFs to let the rAF-debounced resize handler run + settle.
|
||||
await page.evaluate(() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))));
|
||||
await page.evaluate(() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))));
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.querySelector('.top-nav .nav-right');
|
||||
if (!el) return false;
|
||||
const r1 = el.getBoundingClientRect();
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
const r2 = el.getBoundingClientRect();
|
||||
resolve(r1.right === r2.right && r1.left === r2.left);
|
||||
}));
|
||||
});
|
||||
}, null, { timeout: 5000 });
|
||||
|
||||
const data = await page.evaluate(() => {
|
||||
const menu = document.getElementById('navMoreMenu');
|
||||
const wrap = document.querySelector('.nav-more-wrap');
|
||||
const moreVisible = wrap ? getComputedStyle(wrap).display !== 'none' : false;
|
||||
const items = menu ? menu.children.length : -1;
|
||||
const overflowInline = Array.from(document.querySelectorAll('.nav-links .nav-link.is-overflow'))
|
||||
.map(a => a.getAttribute('href'));
|
||||
return { items, moreVisible, overflowInline };
|
||||
});
|
||||
|
||||
// Band-specific acceptance:
|
||||
// - bug band (1101-1278px): items >= 2 STRICTLY. items===0
|
||||
// would mean More vanished (a different regression).
|
||||
// - wide band (>=1280px): items === 0 OR items >= 2.
|
||||
// In both bands items === 1 is the original Bug B and fails.
|
||||
const inBugBand = BUG_BAND.indexOf(w) !== -1;
|
||||
const ok = inBugBand
|
||||
? data.items >= 2
|
||||
: (data.items === 0 || data.items >= 2);
|
||||
const bandLabel = inBugBand ? 'bug-band' : 'wide-band';
|
||||
if (ok) {
|
||||
passes++;
|
||||
console.log(` ✅ ${w}px [${bandLabel}]: more menu items=${data.items} moreVisible=${data.moreVisible}`);
|
||||
} else {
|
||||
failures++;
|
||||
const why = inBugBand && data.items === 0
|
||||
? 'More menu vanished in bug band (regression: should have overflow)'
|
||||
: `degenerate More menu (items=${data.items})`;
|
||||
console.log(` ❌ ${w}px [${bandLabel}]: ${why} overflow=[${data.overflowInline.join(', ')}]`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Always close Chromium even if a page step (e.g. waitForFunction
|
||||
// timeout) throws — otherwise CI leaks browser processes.
|
||||
try { await browser.close(); } catch (_) { /* already gone */ }
|
||||
}
|
||||
console.log(`\ntest-nav-more-floor-1139-e2e.js: ${failures === 0 ? 'OK' : 'FAIL'} — ${passes}/${VIEWPORTS.length} passed`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
main().catch(err => { console.error(err); process.exit(1); });
|
||||
Reference in New Issue
Block a user