sbuffkin_hexmaker/dev/snapshot-coord-slip.mjs
isaprettycoolguy@protonmail.com 0e65565e25 fix: coord labels no longer slip up-left during zoom bake
The bug the user has been chasing for several sessions. Sandbox
repro at dev/coord-slip-sandbox.html, snapshot-coord-slip.mjs
captures the half-baked frame so we can SEE the slip.

Root cause: bakeZoom mutates the viewport font-size mid-function,
which triggers a layout reflow growing every em-based hex
dimension. The path-overlay SVG holds coord labels at pixel
positions computed against the PRE-bake hex sizes, so any paint
frame between the font-size change and the SVG rebuild shows the
labels at where the hexes USED to be — visibly shifted up-left of
the now-larger hexes. updatePathOverlay() further down in bakeZoom
eventually fixes it ("snap back into place"), but the intermediate
slip-then-snap is what the user reported across multiple zoom
sessions.

Fix: tear down the path / faction / region SVGs at the START of
bakeZoom, before the font-size mutation. The CSS rule
`.duckmage-svg-labels-active .duckmage-hex-label { visibility:
hidden }` is keyed off a class on the viewport — that class gets
removed alongside the SVG, so the intermediate paint falls back to
the HTML `.duckmage-hex-label` spans. Those live inside each hex's
own DOM tree, so they always track the hex's layout perfectly
across font-size changes. No slip.

The overlays are then re-rendered later in the function via the
existing updatePathOverlay() / updateFactionOverlay() /
updateRegionOverlay() / updateTokenLayer() calls. Net result: zero
extra rebuild work, just earlier tear-down.

Sandbox confirms:
  buggy-mid: SVG labels visibly shifted up-left of small hexes
  fixed-mid: HTML labels inside hexes, tracking correctly

351 unit tests / 18 e2e pass.
2026-06-21 11:09:12 -04:00

46 lines
1.8 KiB
JavaScript

#!/usr/bin/env node
/** Capture the half-baked frame between font-size change and SVG rebuild
* in both the buggy order (SVG torn down last) and the fixed order
* (SVG torn down first). The 300ms setTimeout in the sandbox is the
* paint window — snapshot DURING that window, while the SVG is in its
* stale-vs-absent state, to see which one shows misplaced labels. */
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import fs from "node:fs/promises";
import { withBrowser } from "/mnt/c/Users/markr/work/tools/chromium-driver/lib/browser.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const sandbox = path.join(here, "coord-slip-sandbox.html");
const outDir = path.join(here, "out");
await fs.mkdir(outDir, { recursive: true });
await withBrowser(
async ({ page }) => {
await page.goto(pathToFileURL(sandbox).href, { waitUntil: "networkidle0" });
const snap = async (name) => {
const el = await page.$(".viewport");
await el.screenshot({ path: path.join(outDir, `${name}.png`) });
console.log(path.join(outDir, `${name}.png`));
};
await snap("slip-1-baseline");
await page.click("#reset");
await new Promise((r) => setTimeout(r, 100));
await page.click("#buggy-bake");
// Capture mid-transition — during the 300ms SVG-rebuild delay
await new Promise((r) => setTimeout(r, 100));
await snap("slip-2-buggy-mid");
await new Promise((r) => setTimeout(r, 400));
await snap("slip-3-buggy-final");
await page.click("#reset");
await new Promise((r) => setTimeout(r, 100));
await page.click("#fixed-bake");
await new Promise((r) => setTimeout(r, 100));
await snap("slip-4-fixed-mid");
await new Promise((r) => setTimeout(r, 400));
await snap("slip-5-fixed-final");
},
{ viewport: { width: 900, height: 600 } },
);