mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-23 13:33:06 +00:00
## Summary Three follow-up fixes for #1065 gesture-hint discoverability: 1. **Touch-capability gate.** New `hasTouchCapability()` helper probes `'ontouchstart' in window`, `navigator.maxTouchPoints`, and `(pointer: coarse)`. Every `HINTS[*].relevant()` predicate now returns `false` immediately on mouse-only viewports, so desktop browsers no longer get "swipe a row left" tips. 2. **`width: fit-content` on the pill wrap.** The `.gesture-hint` block previously had no explicit width and defaulted to block-level full-width. Combined with `translateX(-50%)` on `.gesture-hint-bottom` this rendered as a 100vw-wide bar centered with a negative-X transform, i.e. pushed off-screen-left on narrow viewports (384px wrap on 390px viewport). 3. **CSS-parse safety.** Moved the in-body comment (which contained an em-dash) outside the rule block. An earlier attempt to add `width: fit-content` together with an in-body em-dash comment caused the parent `.gesture-hint` rule to vanish from the CSSOM in Chrome (children `.gesture-hint-*` remained). Putting the comment above the block sidesteps the parser bug. ## Test `test-issue-1065-gesture-hints-gates.js` — pure source-file assertions, no browser required. Red commit first (7 fails), green commit second (10/10 pass). Wired into `test-all.sh`. ## Verification After hot-deploy on staging: - Desktop (no touch): `document.querySelectorAll('.gesture-hint').length` === 0 - Mobile emulated (touch): hint rendered, `getBoundingClientRect().x >= 0`, `width <= 360`, `width < viewport_width` - CSSOM: parent `.gesture-hint` rule present with `width: fit-content` + `max-width: 360px` --------- Co-authored-by: openclaw-bot <bot@openclaw.local>
This commit is contained in:
1 parent
17d00c8366
commit
58282c91d8
5 files changed
+115
-5
No files matched your search
@@ -31,11 +31,24 @@
|
||||
var h = location.hash || '';
|
||||
return /^#\/live(\/|$|\?)/.test(h);
|
||||
}
|
||||
// #1065 follow-up: hints must only appear on touch-capable viewports.
|
||||
// Mouse-only desktops (e.g. analyzer.00id.net opened in Chrome on a
|
||||
// workstation) were getting "swipe a row left" tips that make no sense.
|
||||
// Three independent probes — any positive answer counts.
|
||||
function hasTouchCapability() {
|
||||
try {
|
||||
if ('ontouchstart' in window) return true;
|
||||
if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) return true;
|
||||
if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) return true;
|
||||
} catch (_e) {}
|
||||
return false;
|
||||
}
|
||||
var HINTS = {
|
||||
'row-swipe': {
|
||||
key: NS + 'row-swipe',
|
||||
text: 'Tip: swipe a row left for quick actions.',
|
||||
relevant: function () {
|
||||
if (!hasTouchCapability()) return false;
|
||||
if (onLiveRoute()) return false; // #1244
|
||||
var h = location.hash || '';
|
||||
return /^#\/(packets|nodes)/.test(h);
|
||||
@@ -46,6 +59,7 @@
|
||||
key: NS + 'tab-swipe',
|
||||
text: 'Tip: swipe left or right to switch tabs.',
|
||||
relevant: function () {
|
||||
if (!hasTouchCapability()) return false;
|
||||
if (onLiveRoute()) return false; // #1244
|
||||
return !!document.querySelector('[data-bottom-nav]');
|
||||
},
|
||||
@@ -55,7 +69,10 @@
|
||||
key: NS + 'edge-drawer',
|
||||
text: 'Tip: swipe in from the left edge to open navigation.',
|
||||
relevant: function () {
|
||||
if (!hasTouchCapability()) return false;
|
||||
if (onLiveRoute()) return false; // #1244
|
||||
// nav-drawer.js: NARROW_MAX=768; edge-swipe drawer is the WIDE
|
||||
// (>768) layout's nav UI. Below 768, the bottom-nav owns navigation.
|
||||
return window.innerWidth > 768 && !!document.querySelector('.nav-drawer, [data-nav-drawer]');
|
||||
},
|
||||
position: 'top-left',
|
||||
@@ -64,6 +81,7 @@
|
||||
key: NS + 'pull-refresh',
|
||||
text: 'Tip: pull down to refresh the connection.',
|
||||
relevant: function () {
|
||||
if (!hasTouchCapability()) return false;
|
||||
if (onLiveRoute()) return false; // #1244
|
||||
return !!document.querySelector('.pull-to-reconnect');
|
||||
},
|
||||
|
||||
+9
-3
@@ -4024,13 +4024,19 @@ body { touch-action: pan-y; }
|
||||
/* === end #1062 ====================================================== */ (feat(#1062): green — implement gesture system)
|
||||
|
||||
/* === Issue #1065 — Gesture discoverability hints =================== */
|
||||
/* width:fit-content makes the pill shrink to its content; without it
|
||||
* the wrap defaults to block (100% of body width). Combined with
|
||||
* .gesture-hint-bottom { left:50%; transform:translateX(-50%) } that
|
||||
* pushed the wrap off-screen-left on narrow viewports (#1065 follow-up).
|
||||
* Comments live ABOVE the rule body, not interleaved, because a stray
|
||||
* em-dash or sequence inside the body has historically caused some CSS
|
||||
* parsers to drop the entire rule from the CSSOM. */
|
||||
.gesture-hint {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
max-width: 360px;
|
||||
/* !important guards against any layered cascade enabling pointer-events
|
||||
* (e.g., a future overlay class with higher specificity). The hint
|
||||
* wrapper MUST never capture clicks — only its inner button does. */
|
||||
pointer-events: none !important;
|
||||
opacity: 1;
|
||||
animation-name: gesture-hint-slide-in;
|
||||
|
||||
@@ -12,6 +12,7 @@ echo "── Unit Tests ──"
|
||||
node test-packet-filter.js
|
||||
node test-packet-filter-ux.js
|
||||
node test-aging.js
|
||||
node test-issue-1065-gesture-hints-gates.js
|
||||
node test-frontend-helpers.js
|
||||
node test-url-state.js
|
||||
node test-perf-go-runtime.js
|
||||
|
||||
@@ -208,8 +208,11 @@ async function main() {
|
||||
|
||||
await ctx.close();
|
||||
|
||||
// ── (e) at 1024x800, edge-swipe hint visible on first visit ──
|
||||
const ctx2 = await browser.newContext({ viewport: { width: 1024, height: 800 } });
|
||||
// ── (e) at 1024x800 with touch, edge-swipe hint visible on first visit ──
|
||||
// #1065 follow-up: edge-swipe is a touch gesture; the hint must only
|
||||
// appear when the viewport reports touch capability. Test context must
|
||||
// pass hasTouch:true (real edge-swipe-on-tablet/touch-laptop scenario).
|
||||
const ctx2 = await browser.newContext({ viewport: { width: 1024, height: 800 }, hasTouch: true });
|
||||
const page2 = await ctx2.newPage();
|
||||
await page2.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
||||
await page2.evaluate((keys) => Object.values(keys).forEach((k) => localStorage.removeItem(k)), KEYS);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
/* Issue #1065 follow-up — gesture hints must:
|
||||
* 1. Define a hasTouchCapability() helper that probes ontouchstart,
|
||||
* maxTouchPoints, and (pointer: coarse).
|
||||
* 2. Gate every HINTS[*].relevant() body on hasTouchCapability() at the
|
||||
* very top (no hint should fire on mouse-only viewports).
|
||||
* 3. Ship a .gesture-hint parent CSS rule that includes
|
||||
* `width: fit-content` AND `max-width: 360px` so the pill shrinks to
|
||||
* its content instead of stretching full-bleed and being pushed
|
||||
* off-screen by translateX(-50%) on narrow viewports.
|
||||
*
|
||||
* Pure source-file assertions — no browser required.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const JS_PATH = path.join(__dirname, 'public', 'gesture-hints.js');
|
||||
const CSS_PATH = path.join(__dirname, 'public', 'style.css');
|
||||
|
||||
let failures = 0, passes = 0;
|
||||
const fail = (m) => { failures++; console.error(' FAIL: ' + m); };
|
||||
const pass = (m) => { passes++; console.log(' PASS: ' + m); };
|
||||
|
||||
const js = fs.readFileSync(JS_PATH, 'utf8');
|
||||
const css = fs.readFileSync(CSS_PATH, 'utf8');
|
||||
|
||||
// (1) helper exists and probes the three signals
|
||||
if (/function\s+hasTouchCapability\s*\(/.test(js)) pass('hasTouchCapability() defined');
|
||||
else fail('hasTouchCapability() not defined in gesture-hints.js');
|
||||
|
||||
if (/ontouchstart/.test(js)) pass('hasTouchCapability probes ontouchstart');
|
||||
else fail('hasTouchCapability missing ontouchstart probe');
|
||||
|
||||
if (/maxTouchPoints/.test(js)) pass('hasTouchCapability probes maxTouchPoints');
|
||||
else fail('hasTouchCapability missing maxTouchPoints probe');
|
||||
|
||||
if (/pointer:\s*coarse/.test(js)) pass('hasTouchCapability probes (pointer: coarse)');
|
||||
else fail('hasTouchCapability missing (pointer: coarse) probe');
|
||||
|
||||
// (2) every relevant() body must start with the touch gate
|
||||
// Find each `relevant: function () { ... }` block and check.
|
||||
const relevantRe = /relevant:\s*function\s*\(\s*\)\s*\{([\s\S]*?)\n\s{6}\}/g;
|
||||
let m, count = 0, gated = 0;
|
||||
while ((m = relevantRe.exec(js)) !== null) {
|
||||
count++;
|
||||
const body = m[1];
|
||||
// First non-comment statement must be hasTouchCapability gate
|
||||
if (/^\s*if\s*\(\s*!\s*hasTouchCapability\s*\(\s*\)\s*\)\s*return\s+false\s*;/m.test(body)) {
|
||||
gated++;
|
||||
}
|
||||
}
|
||||
if (count >= 4) pass(`found ${count} relevant() predicates`);
|
||||
else fail(`expected ≥4 relevant() predicates, found ${count}`);
|
||||
if (gated === count && count > 0) pass(`all ${gated}/${count} relevant() bodies start with !hasTouchCapability() return false`);
|
||||
else fail(`only ${gated}/${count} relevant() bodies gate on hasTouchCapability()`);
|
||||
|
||||
// (3) .gesture-hint parent rule has width: fit-content + max-width: 360px
|
||||
// Locate the rule block starting `.gesture-hint {` (NOT .gesture-hint-...).
|
||||
const ruleRe = /\n\.gesture-hint\s*\{([\s\S]*?)\}/;
|
||||
const ruleMatch = ruleRe.exec(css);
|
||||
if (!ruleMatch) {
|
||||
fail('.gesture-hint parent CSS rule not found in style.css');
|
||||
} else {
|
||||
pass('.gesture-hint parent CSS rule present');
|
||||
const body = ruleMatch[1];
|
||||
if (/\bwidth:\s*fit-content\b/.test(body)) pass('.gesture-hint declares width: fit-content');
|
||||
else fail('.gesture-hint missing width: fit-content (pill must shrink to content)');
|
||||
if (/\bmax-width:\s*360px\b/.test(body)) pass('.gesture-hint declares max-width: 360px');
|
||||
else fail('.gesture-hint missing max-width: 360px');
|
||||
}
|
||||
|
||||
// (4) defensive: no em-dash or stray "*/" inside .gesture-hint rule body
|
||||
if (ruleMatch) {
|
||||
const body = ruleMatch[1];
|
||||
if (/[\u2014\u2013]/.test(body)) fail('em-dash / en-dash inside .gesture-hint rule body (CSS-parse-fragile)');
|
||||
else pass('no em-dash inside .gesture-hint rule body');
|
||||
}
|
||||
|
||||
console.log(`\ntest-issue-1065-gesture-hints-gates.js: ${passes} passed, ${failures} failed`);
|
||||
process.exit(failures > 0 ? 1 : 0);
|
||||
Reference in new issue
Block a user