Files
mokosh/scripts/04-06-welcome-hero-screenshots.mjs
Mark d66cbf6900 chore(04-06): add operator-empirical screenshot harness (Task 4 artifact)
Per the orchestrator checkpoint protocol + the saved-memory feedback
"trust harness over manual UAT", Task 4's dark-mode aesthetic
judgment uses Puppeteer-produced screenshots (NOT a manual Chrome
session). This script:

1. Loads dist/ via puppeteer.launch enableExtensions.
2. Resolves the runtime extension ID via the canonical
   browser.extensions() Map (mirrors tests/uat/lib/launch.ts
   resolveExtensionIdWithPolling).
3. Opens chrome-extension://<id>/src/welcome/welcome.html.
4. Captures the .welcome-hero bounding-box region in LIGHT surface
   (default OS appearance — the regression-baseline shot, matching
   the Plan 01-10 cycle-2 operator ack 2026-05-20).
5. Sets [data-theme="dark"] on <html> (Mokosh's tokens.css cascade
   uses the explicit .dark / [data-theme="dark"] selector at line
   234; emulateMediaFeatures alone does NOT trigger it because
   tokens.css has no @media (prefers-color-scheme: dark) block — a
   fact verified live this session). emulateMediaFeatures is also
   set, forward-compatible with any future @media block.
6. Re-screenshot the hero region — the DARK-surface aesthetic shot.

Output paths (canonical per the 04-06-PLAN Task 4 contract):
  - /tmp/04-06-welcome-hero-light.png
  - /tmp/04-06-welcome-hero-dark.png

Run results (this session):
  - LIGHT: computed stroke = rgb(250, 247, 241) — linen-50; the
    --mks-fg-inverse value on the LIGHT cascade flowing through
    .welcome-hero__mark to the inline <svg>'s currentColor.
  - DARK:  computed stroke = rgb(24, 27, 42) — ink-900; the
    --mks-fg-inverse value AFTER the .dark cascade override
    (tokens.css 244 sets --mks-fg-inverse: var(--mks-ink-900)) —
    the strategy's contrast flip is empirically verified.

Implementation notes (deviation Rule 3 — observed environment
constraints fixed inline):
  - Initial extension ID resolver used browser.targets() polling +
    regex; rewritten to use the canonical Puppeteer 22.x
    browser.extensions() Map approach.
  - Initial screenshot used ElementHandle.screenshot(); Puppeteer
    Runtime.callFunctionOn timed out on the second elementHandle
    evaluate in headless extension page context. Rewritten to a
    single page.evaluate() that returns getBoundingClientRect() +
    computedStroke in one CDP round trip, then page.screenshot({clip})
    against those coordinates — succeeds reliably.
  - protocolTimeout set to 120s to match the UAT harness baseline.

References:
  - .planning/phases/04-harden-clean-up-optional/04-06-PLAN.md Task 4.
  - tests/uat/lib/launch.ts (the canonical extension-loading pattern).
  - https://pptr.dev/api/puppeteer.browser.extensions
  - https://pptr.dev/api/puppeteer.page.screenshot
2026-05-26 09:11:46 +02:00

7.5 KiB