mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-27 18:31:00 +00:00
Partial fix for #1402 ## Summary Re-fix two of the four #1402 regressions on mobile after `#1452` silently reverted the prior fix (`6ec08acb`). Two predicate flips in `public/gesture-hints.js` + extended E2E coverage to prevent another silent revert. This PR is intentionally **scoped to Bug 2 and Bug 4 only**. Bug 1 and Bug 3 were also dropped by `#1452` and are NOT restored here — `#1402` remains open for the rest. ## Changes - `public/gesture-hints.js` (edge-drawer): `window.innerWidth > 768` → `window.innerWidth <= 768`. The edge-swipe drawer is the MOBILE layout's nav per #1064/#1184; `nav-drawer.js` `NARROW_MAX=768` (inclusive — narrow when width <= NARROW_MAX). Above 768 the sidebar is persistent, no edge-swipe is needed. - `public/gesture-hints.js` (row-swipe): widen route filter from `/^#\/(packets|nodes)/` to `/^#\/(packets|nodes|channels|observers)/`. Channels and observers also render swipable row tables. - `public/gesture-hints.js`: expose read-only `window.__gestureHintsDefs` test hook (frozen) for direct predicate probes (avoids race with render path). - `test-gesture-hints-1065-e2e.js`: add assertions (i)+(j) at vw=393 — edge-drawer relevant on `/#/home`, row-swipe relevant on `/#/channels`; (k) negative-direction gate at vw=1024 asserts `edge-drawer.relevant() === false` on desktop. Retarget (e) from 1024x800 → 393x800 to match the corrected mobile-only gate. ## TDD - Red commit: `1e7545d1` — test additions fail against current production code (edge-drawer relevant returns false at vw=393, row-swipe filter rejects /channels). - Green commit: `6f844d5b` — predicate flips + route widening make both assertions pass. - Polish commit (round-1 fixes): boundary <= 768, doc-header refresh, freeze the test hook, negative-direction gate (k), precondition assertion on (i). ## Acceptance criteria from #1402 - [ ] Bug 1 (`window 'load'` rescheduler + `pointer: coarse` gate) — dropped by #1452, NOT restored in this PR. Tracked in #1402. - [x] Bug 2 (edge-drawer mobile-only) — fixed here. - [ ] Bug 3 (pull-refresh touch-gate decoupling) — dropped by #1452, NOT restored in this PR. Tracked in #1402. - [x] Bug 4 (row-swipe widening → /channels + /observers) — fixed here. - [x] E2E mutation gate: assertions (i)+(j)+(k) provably fail if either predicate is reverted or re-broadened. ## Notes - Silently reverted by #1452 — re-fix here, with regression gates so the next reviewer of the next refactor will see the assertions fail rather than the production behavior change unnoticed. ## Preflight All gates pass (PII, branch scope, red commit, CSS vars, XSS sinks, etc.). --------- Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: fix-1166-bot <bot@corescope.local>
This commit is contained in:
co-authored by
meshcore-bot
fix-1166-bot
parent
7533b3b67b
commit
116efe4bd7
+19
-5
@@ -9,7 +9,8 @@
|
|||||||
* - prefers-reduced-motion: animation-name: none (style.css handles via media query).
|
* - prefers-reduced-motion: animation-name: none (style.css handles via media query).
|
||||||
* - Singleton + cleanup: module-scoped guard; SPA re-mount must not re-show dismissed.
|
* - Singleton + cleanup: module-scoped guard; SPA re-mount must not re-show dismissed.
|
||||||
* - Pull-to-refresh hint only when .pull-to-reconnect element exists in DOM.
|
* - Pull-to-refresh hint only when .pull-to-reconnect element exists in DOM.
|
||||||
* - Edge-drawer hint only at viewport > 768px (where edge-swipe drawer applies).
|
* - Edge-drawer hint only at viewport <= 768px (mobile layout, where the
|
||||||
|
* edge-swipe drawer is the nav UI; nav-drawer.js NARROW_MAX=768, inclusive).
|
||||||
* - Row-swipe hint only on table pages: /#/packets, /#/nodes, etc.
|
* - Row-swipe hint only on table pages: /#/packets, /#/nodes, etc.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
if (!hasTouchCapability()) return false;
|
if (!hasTouchCapability()) return false;
|
||||||
if (onLiveRoute()) return false; // #1244
|
if (onLiveRoute()) return false; // #1244
|
||||||
var h = location.hash || '';
|
var h = location.hash || '';
|
||||||
return /^#\/(packets|nodes)/.test(h);
|
return /^#\/(packets|nodes|channels|observers)/.test(h);
|
||||||
},
|
},
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
},
|
},
|
||||||
@@ -71,9 +72,12 @@
|
|||||||
relevant: function () {
|
relevant: function () {
|
||||||
if (!hasTouchCapability()) return false;
|
if (!hasTouchCapability()) return false;
|
||||||
if (onLiveRoute()) return false; // #1244
|
if (onLiveRoute()) return false; // #1244
|
||||||
// nav-drawer.js: NARROW_MAX=768; edge-swipe drawer is the WIDE
|
// nav-drawer.js: NARROW_MAX=768; "narrow" is inclusive — narrow when
|
||||||
// (>768) layout's nav UI. Below 768, the bottom-nav owns navigation.
|
// width <= NARROW_MAX (nav-drawer treats width > NARROW_MAX as the
|
||||||
return window.innerWidth > 768 && !!document.querySelector('.nav-drawer, [data-nav-drawer]');
|
// non-narrow / sidebar layout). The edge-swipe drawer is the MOBILE
|
||||||
|
// (≤768) layout's nav UI per #1064/#1184. Above 768, the persistent
|
||||||
|
// sidebar is visible and no edge-swipe is needed.
|
||||||
|
return window.innerWidth <= 768 && !!document.querySelector('.nav-drawer, [data-nav-drawer]');
|
||||||
},
|
},
|
||||||
position: 'top-left',
|
position: 'top-left',
|
||||||
},
|
},
|
||||||
@@ -220,6 +224,16 @@
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #1402 test hook: expose hint definitions for E2E predicate probes.
|
||||||
|
// Read-only by convention; tests call .relevant() to verify routing/viewport gates.
|
||||||
|
window.__gestureHintsDefs = HINTS;
|
||||||
|
// M3: freeze the hint defs to prevent tests / page scripts from mutating
|
||||||
|
// production state via the test hook. Shallow-freeze HINTS + each def.
|
||||||
|
try {
|
||||||
|
Object.keys(HINTS).forEach(function (id) { Object.freeze(HINTS[id]); });
|
||||||
|
Object.freeze(HINTS);
|
||||||
|
} catch (_e) {}
|
||||||
|
|
||||||
window.GestureHints = {
|
window.GestureHints = {
|
||||||
show: show,
|
show: show,
|
||||||
dismiss: dismiss,
|
dismiss: dismiss,
|
||||||
|
|||||||
@@ -208,11 +208,11 @@ async function main() {
|
|||||||
|
|
||||||
await ctx.close();
|
await ctx.close();
|
||||||
|
|
||||||
// ── (e) at 1024x800 with touch, edge-swipe hint visible on first visit ──
|
// ── (e) at vw=393 with touch, edge-swipe hint visible on first visit ──
|
||||||
// #1065 follow-up: edge-swipe is a touch gesture; the hint must only
|
// #1065 follow-up: edge-swipe is a touch gesture. #1402 fix: edge-drawer
|
||||||
// appear when the viewport reports touch capability. Test context must
|
// is the MOBILE layout's nav UI (per #1064/#1184, nav-drawer.js NARROW_MAX=768);
|
||||||
// pass hasTouch:true (real edge-swipe-on-tablet/touch-laptop scenario).
|
// hint must appear at narrow viewports, not wide ones.
|
||||||
const ctx2 = await browser.newContext({ viewport: { width: 1024, height: 800 }, hasTouch: true });
|
const ctx2 = await browser.newContext({ viewport: { width: 393, height: 800 }, hasTouch: true });
|
||||||
const page2 = await ctx2.newPage();
|
const page2 = await ctx2.newPage();
|
||||||
await page2.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
await page2.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
||||||
await page2.evaluate((keys) => Object.values(keys).forEach((k) => localStorage.removeItem(k)), KEYS);
|
await page2.evaluate((keys) => Object.values(keys).forEach((k) => localStorage.removeItem(k)), KEYS);
|
||||||
@@ -220,9 +220,9 @@ async function main() {
|
|||||||
await page2.waitForTimeout(HINT_SETTLE_MS);
|
await page2.waitForTimeout(HINT_SETTLE_MS);
|
||||||
const edgeHint = await hintVisible(page2, 'edge-drawer');
|
const edgeHint = await hintVisible(page2, 'edge-drawer');
|
||||||
if (edgeHint.present && edgeHint.visible) {
|
if (edgeHint.present && edgeHint.visible) {
|
||||||
pass('(e) edge-drawer hint visible at 1024x800');
|
pass('(e) edge-drawer hint visible at 393x800 (mobile)');
|
||||||
} else {
|
} else {
|
||||||
fail(`(e) edge-drawer hint NOT visible at 1024x800 — state=${JSON.stringify(edgeHint)}`);
|
fail(`(e) edge-drawer hint NOT visible at 393x800 — state=${JSON.stringify(edgeHint)}`);
|
||||||
}
|
}
|
||||||
await ctx2.close();
|
await ctx2.close();
|
||||||
|
|
||||||
@@ -245,6 +245,81 @@ async function main() {
|
|||||||
}
|
}
|
||||||
await ctx3.close();
|
await ctx3.close();
|
||||||
|
|
||||||
|
// ── (i) #1402 regression — at vw=393 (mobile), edge-drawer hint IS relevant on /#/home ──
|
||||||
|
// Bug 2 in #1402: edge-drawer.relevant had window.innerWidth > 768 (inverted).
|
||||||
|
// nav-drawer.js NARROW_MAX=768; the edge-swipe drawer is the MOBILE feature
|
||||||
|
// per #1064/#1184. At vw=393 with a .nav-drawer in the DOM, the hint MUST
|
||||||
|
// be classified as relevant. Asserts the predicate directly so the test
|
||||||
|
// does not depend on the schedule/render path.
|
||||||
|
const ctx4 = await browser.newContext({ viewport: { width: 393, height: 800 }, hasTouch: true });
|
||||||
|
const page4 = await ctx4.newPage();
|
||||||
|
await page4.goto(`${BASE}/#/home`, { waitUntil: 'domcontentloaded' });
|
||||||
|
await page4.evaluate((keys) => Object.values(keys).forEach((k) => localStorage.removeItem(k)), KEYS);
|
||||||
|
await page4.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
await page4.waitForTimeout(HINT_SETTLE_MS);
|
||||||
|
const probe1402 = await page4.evaluate(() => {
|
||||||
|
const hints = window.__gestureHintsDefs || null;
|
||||||
|
return {
|
||||||
|
hintsExposed: !!hints,
|
||||||
|
vw: window.innerWidth,
|
||||||
|
navDrawerInDom: !!document.querySelector('.nav-drawer, [data-nav-drawer]'),
|
||||||
|
edgeDrawerRelevant: hints && hints['edge-drawer'] ? !!hints['edge-drawer'].relevant() : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (probe1402.hintsExposed && probe1402.navDrawerInDom && probe1402.edgeDrawerRelevant === true) {
|
||||||
|
pass(`(i) #1402 — edge-drawer relevant at vw=393 on /#/home (navDrawer=${probe1402.navDrawerInDom})`);
|
||||||
|
} else if (!probe1402.navDrawerInDom) {
|
||||||
|
fail(`(i) #1402 — precondition failed: .nav-drawer NOT in DOM at vw=393 — state=${JSON.stringify(probe1402)}`);
|
||||||
|
} else {
|
||||||
|
fail(`(i) #1402 — edge-drawer NOT relevant at vw=${probe1402.vw} — state=${JSON.stringify(probe1402)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── (j) #1402 regression — at vw=393, row-swipe hint IS relevant on /#/channels ──
|
||||||
|
// Bug 4 in #1402: row-swipe filter was /^#\/(packets|nodes)/ — must widen to
|
||||||
|
// include channels and observers (both render swipable row tables).
|
||||||
|
await page4.goto(`${BASE}/#/channels`, { waitUntil: 'domcontentloaded' });
|
||||||
|
await page4.waitForTimeout(HINT_SETTLE_MS);
|
||||||
|
const probe1402b = await page4.evaluate(() => {
|
||||||
|
const hints = window.__gestureHintsDefs || null;
|
||||||
|
return {
|
||||||
|
hintsExposed: !!hints,
|
||||||
|
hash: location.hash,
|
||||||
|
rowSwipeRelevant: hints && hints['row-swipe'] ? !!hints['row-swipe'].relevant() : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (probe1402b.hintsExposed && probe1402b.rowSwipeRelevant === true) {
|
||||||
|
pass(`(j) #1402 — row-swipe relevant at vw=393 on ${probe1402b.hash}`);
|
||||||
|
} else {
|
||||||
|
fail(`(j) #1402 — row-swipe NOT relevant on /#/channels — state=${JSON.stringify(probe1402b)}`);
|
||||||
|
}
|
||||||
|
await ctx4.close();
|
||||||
|
|
||||||
|
// ── (k) #1402 negative-direction regression gate — at vw=1024 (desktop),
|
||||||
|
// edge-drawer.relevant() MUST return false. This locks the predicate so
|
||||||
|
// it cannot be re-broadened to fire on desktop (the original #1402 Bug 2
|
||||||
|
// had the inequality inverted; this assertion guards against the reverse
|
||||||
|
// mistake — over-broadening — going forward).
|
||||||
|
const ctx5 = await browser.newContext({ viewport: { width: 1024, height: 800 }, hasTouch: true });
|
||||||
|
const page5 = await ctx5.newPage();
|
||||||
|
await page5.goto(`${BASE}/#/home`, { waitUntil: 'domcontentloaded' });
|
||||||
|
await page5.evaluate((keys) => Object.values(keys).forEach((k) => localStorage.removeItem(k)), KEYS);
|
||||||
|
await page5.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
await page5.waitForTimeout(HINT_SETTLE_MS);
|
||||||
|
const probe1402c = await page5.evaluate(() => {
|
||||||
|
const hints = window.__gestureHintsDefs || null;
|
||||||
|
return {
|
||||||
|
hintsExposed: !!hints,
|
||||||
|
vw: window.innerWidth,
|
||||||
|
edgeDrawerRelevant: hints && hints['edge-drawer'] ? !!hints['edge-drawer'].relevant() : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (probe1402c.hintsExposed && probe1402c.edgeDrawerRelevant === false) {
|
||||||
|
pass(`(k) #1402 negative gate — edge-drawer NOT relevant at vw=${probe1402c.vw} (desktop)`);
|
||||||
|
} else {
|
||||||
|
fail(`(k) #1402 negative gate FAILED — edge-drawer relevant at desktop width — state=${JSON.stringify(probe1402c)}`);
|
||||||
|
}
|
||||||
|
await ctx5.close();
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
console.log(`\ntest-gesture-hints-1065-e2e.js: ${passes} passed, ${failures} failed`);
|
console.log(`\ntest-gesture-hints-1065-e2e.js: ${passes} passed, ${failures} failed`);
|
||||||
process.exit(failures > 0 ? 1 : 0);
|
process.exit(failures > 0 ? 1 : 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user