Files
meshcore-analyzer/test-issue-1065-gesture-hints-gates.js
T
Kpa-clawbot 58282c91d8 fix(#1065): gesture hints touch-gate + width:fit-content + CSS-parse safety (#1452)
## 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>
2026-05-27 23:21:18 -07:00

83 lines
3.7 KiB
JavaScript

#!/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);