Flatten the selection and the menu highlight; kill the Aero afterglow
Three things looked wrong, three different causes. The selected row carried a border. 7.css outlines tr.highlighted (#aaddfa) on top of its fill, and we had been restating that outline in the new blue — so the band had an edge, and on the cursor row the dotted focus rect made three concentric outlines around a single row. The reserved 1px border stays (it is what stops selection from shifting rows) but is transparent in every state; the fill paints under it, since background-clip is border-box. --w7-sel-bd and --w7-sel-bd-idle are gone with it. The context menu highlighted items with 7.css's translucent gradient wash — the same gradient the ListView selection just dropped. It now uses --w7-sel-bg, so selection reads identically wherever it appears. The hover-off animation was button.css bleeding into menu items, which ARE buttons: button::before is a glow overlay that fades out over a full second, and menu.css suppresses it with content:none on the :hover arm ONLY — so leaving an item brought the pseudo back with opacity animating 1 -> 0, a ghost trailing the pointer. Killing content in every state removes it. Same family, also fixed: button:focus runs pulse-anim infinitely, and this menu moves real focus between items for keyboard nav. menu-probe.ts now checks the highlight too, using hover-probe's :hover -> .simhover rewrite: it measures colour spread down a text-free strip of a force-hovered item, so "is it flat?" is a measurement. Verified the check can fail by injecting the old wash into the built stylesheet — spread 9, exit 2; the flat fill reads spread 0 at exactly #cbe8f6. The harness lost its selection outlines and nothing else, in both themes. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+86
-4
@@ -1,4 +1,4 @@
|
||||
// Programmatic POSITIONING test for the win7 context menu.
|
||||
// Programmatic POSITIONING + HIGHLIGHT test for the win7 context menu.
|
||||
//
|
||||
// bun scripts/menu-probe.ts
|
||||
//
|
||||
@@ -16,6 +16,16 @@
|
||||
// i.e. the bug verbatim. That one must stay invisible — if it ever shows up,
|
||||
// 7.css stopped forcing `relative` and this probe's premise is stale.
|
||||
//
|
||||
// It also checks the item HIGHLIGHT, using hover-probe.ts's trick (`:hover`
|
||||
// rewritten to a `.simhover` class, since headless Firefox has no pointer):
|
||||
// 7.css paints hovered items with a translucent gradient wash, and this app
|
||||
// wants one flat colour. A gradient is detectable as vertical colour spread
|
||||
// down a text-free strip of the item, so "is it flat?" is a measurement, not
|
||||
// an opinion. What canNOT be measured here is the 1s afterglow fade — a
|
||||
// temporal effect — but its cause is structural (`button::before` still has
|
||||
// `content`), and this probe would show the glow's colour if the pseudo ever
|
||||
// came back.
|
||||
//
|
||||
// Inherits shot.ts's constraints: dedicated Firefox profile (a running Firefox
|
||||
// holds the default profile's lock), and the page must be written INTO the
|
||||
// dist folder or strict_origin_policy silently unstyles it.
|
||||
@@ -35,6 +45,12 @@ if (!existsSync(DIST)) fail(`no build output at ${DIST} — run: bun run build:u
|
||||
const styles = readdirSync(DIST).find((f) => f.startsWith("styles-") && f.endsWith(".css"));
|
||||
if (!styles) fail(`no styles-*.css in ${DIST} — run: bun run build:ui`);
|
||||
|
||||
// Hover as a CLASS: same rewrite hover-probe.ts uses, for the same reason —
|
||||
// `--screenshot` has no pointer. `:hover` has no partial-token hazard.
|
||||
const css = await Bun.file(path.join(DIST, styles)).text();
|
||||
const PROBE_CSS = "_menu-probe.css";
|
||||
await Bun.write(path.join(DIST, PROBE_CSS), css.replaceAll(":hover", ".simhover"));
|
||||
|
||||
// ---- the probe page ----
|
||||
|
||||
// Geometry (CSS px = image px; headless Firefox applies no DPR scaling).
|
||||
@@ -51,7 +67,7 @@ const menu = (x: number, marker: boolean) => `
|
||||
<ul role="menu" tabindex="-1"
|
||||
class="${marker ? "w7-ctx " : ""}fixed z-50 outline-none"
|
||||
style="left: ${x}px; top: ${MENU_Y}px; width: ${MENU_W}px">
|
||||
<li role="menuitem"><button type="button">Send to Other Panel</button></li>
|
||||
<li role="menuitem"><button type="button"${marker ? ' class="simhover"' : ""}>Send to Other Panel</button></li>
|
||||
<li role="menuitem" class="has-divider"><button type="button">Rename</button></li>
|
||||
<li role="menuitem"><button type="button">Copy Path</button></li>
|
||||
<li role="menuitem" aria-disabled="true"><button type="button">Delete</button></li>
|
||||
@@ -64,7 +80,7 @@ const menu = (x: number, marker: boolean) => `
|
||||
// it) purely so "is anything drawn here?" is an unambiguous question.
|
||||
const page = `<!doctype html>
|
||||
<html class="win7" lang="en">
|
||||
<head><meta charset="utf-8"><link rel="stylesheet" href="${styles}">
|
||||
<head><meta charset="utf-8"><link rel="stylesheet" href="${PROBE_CSS}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="window glass w7-frame active" style="display: flex; flex-direction: column; height: 100vh">
|
||||
@@ -121,6 +137,46 @@ async function inkCount(img: string, x: number, y: number, w: number, h: number)
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colour spread down a text-free strip of the first (force-hovered) item: the
|
||||
* ul's own 2px padding + the item's 1px border put the item's background at
|
||||
* x >= menu+3, and its 32px left padding keeps label text out to menu+32. So a
|
||||
* column at menu+5..menu+26 is pure fill — except for the gutter stripe, which
|
||||
* 7.css puts at left: 28px. A flat colour reads 0 spread; a gradient does not.
|
||||
*/
|
||||
async function fillSpread(
|
||||
img: string,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
): Promise<{ spread: number; mean: [number, number, number] }> {
|
||||
const p = Bun.spawnSync(["magick", img, "-crop", `${w}x${h}+${x}+${y}`, "+repage", "txt:"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
const lo: number[] = [255, 255, 255];
|
||||
const hi: number[] = [0, 0, 0];
|
||||
const sum = [0, 0, 0];
|
||||
let n = 0;
|
||||
for (const line of p.stdout.toString().split("\n").slice(1)) {
|
||||
const m = /\((\d+),(\d+),(\d+)/.exec(line);
|
||||
if (!m) continue;
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const v = Number(m[c + 1]);
|
||||
lo[c] = Math.min(lo[c], v);
|
||||
hi[c] = Math.max(hi[c], v);
|
||||
sum[c] += v;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
if (!n) return { spread: -1, mean: [0, 0, 0] };
|
||||
return {
|
||||
spread: Math.max(hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]),
|
||||
mean: [Math.round(sum[0] / n), Math.round(sum[1] / n), Math.round(sum[2] / n)],
|
||||
};
|
||||
}
|
||||
|
||||
// Inset by 2px so the scan cannot straddle the region's own edge.
|
||||
const BOX = { w: MENU_W - 4, h: 60 };
|
||||
const placed = await inkCount(out, MENU_X + 2, MENU_Y + 2, BOX.w, BOX.h);
|
||||
@@ -134,10 +190,36 @@ console.log(
|
||||
console.log(
|
||||
` control (no marker) ${String(control).padStart(6)} ${control > 0 ? "NOTE: 7.css no longer forces relative — premise stale" : "off-viewport, as expected"}`,
|
||||
);
|
||||
// The force-hovered first item: fill strip, and the same strip on the item
|
||||
// below it as a not-hovered reference (which must stay the menu surface).
|
||||
const FILL = { x: MENU_X + 5, w: 21, h: 14 };
|
||||
const hovered = await fillSpread(out, FILL.x, MENU_Y + 7, FILL.w, FILL.h);
|
||||
const resting = await fillSpread(out, FILL.x, MENU_Y + 34, FILL.w, FILL.h);
|
||||
const FLAT_MAX = 6; // antialiasing tolerance; 7.css's wash spreads far wider
|
||||
|
||||
console.log("\n[menu-probe] item highlight (force-hovered vs resting):\n");
|
||||
console.log(" item mean rgb spread verdict");
|
||||
console.log(
|
||||
` hovered (.simhover) ${hovered.mean.join(",").padEnd(15)} ${String(hovered.spread).padStart(6)} ${
|
||||
hovered.spread <= FLAT_MAX ? "flat" : "GRADIENT: highlight is not a flat colour"
|
||||
}`,
|
||||
);
|
||||
console.log(` resting ${resting.mean.join(",").padEnd(15)} ${String(resting.spread).padStart(6)} reference`);
|
||||
|
||||
console.log(`\n[menu-probe] image: ${out}`);
|
||||
|
||||
let failed = false;
|
||||
if (placed === 0) {
|
||||
console.log("[menu-probe] the context menu is positioned in FLOW again — see 7like.css .w7-ctx");
|
||||
process.exit(2);
|
||||
failed = true;
|
||||
}
|
||||
if (hovered.spread > FLAT_MAX) {
|
||||
console.log("[menu-probe] the item highlight regained a gradient — see the menu block in 7like.css");
|
||||
failed = true;
|
||||
}
|
||||
if (hovered.mean.join(",") === resting.mean.join(",")) {
|
||||
console.log("[menu-probe] hovered and resting items are identical — the highlight is not painting at all");
|
||||
failed = true;
|
||||
}
|
||||
if (failed) process.exit(2);
|
||||
console.log("[menu-probe] clean");
|
||||
|
||||
Reference in New Issue
Block a user