Part of #3607 (visual-capture convergence epic).
Context
src/review/visual/shot.ts forces theme via page.emulateMediaFeatures([{ name: "prefers-color-scheme", value: opts.theme }]) (lines 233, 333 as of 2026-07-07) — the ONLY theme-forcing mechanism, no localStorage-based approach exists. If a target app switches theme by reading a localStorage key on load (rather than an OS-level prefers-color-scheme media query), this has ZERO effect and both "light" and "dark" captures render identically today. metagraphed's own contributor skill documents forcing theme via localStorage.setItem(themeKey, "dark"|"light"); location.reload() as the mechanism its own manual screenshot convention relies on — the two approaches may not agree.
Separately, shot.ts currently has exactly 2 viewports — DESKTOP_VIEWPORT (1440×900) and MOBILE_VIEWPORT (390×844) (lines 41-42) — versus metagraphed's own documented manual convention of 3 viewports (Mobile 375×812, Tablet 768×1024, Desktop 1280×800, chosen to straddle Tailwind md/lg breakpoints).
Note: fullPage: true (added #3669, bounded #3712 with MAX_SCREENSHOT_HEIGHT/MAX_SCREENSHOT_PIXELS/MAX_SCREENSHOT_BYTES) is CORRECT and already shipped — do NOT revert to fixed-viewport-only capture; that would reintroduce the below-the-fold bug #3669 fixed.
Requirements
- Empirically verify (against a real target, e.g. gittensory's own UI or a metagraphed preview) whether
emulateMediaFeatures actually changes rendered theme, or is a no-op for apps using localStorage-based theming. Do not assume either way.
- If it's a no-op for
localStorage-driven apps: add a page.evaluate(() => localStorage.setItem(...)) + reload path as a fallback/alternative, with the storage key name configurable per-repo (likely under the existing review.visual config namespace).
- Decide whether to add a third (tablet) viewport and/or reconcile the exact desktop/mobile dimensions with metagraphed's convention (1280×800/375×812) — or document why the current 1440×900/390×844 pair is intentionally different. Either is acceptable; the decision must be explicit and written up, not silently left inconsistent.
Deliverables
Expected outcome
Theme-forced captures actually reflect the intended theme for every real target, and the viewport matrix is a deliberate, documented choice.
Effort
S-M — depends on what the empirical verification finds.
Part of #3607 (visual-capture convergence epic).
Context
src/review/visual/shot.tsforces theme viapage.emulateMediaFeatures([{ name: "prefers-color-scheme", value: opts.theme }])(lines 233, 333 as of 2026-07-07) — the ONLY theme-forcing mechanism, nolocalStorage-based approach exists. If a target app switches theme by reading alocalStoragekey on load (rather than an OS-levelprefers-color-schememedia query), this has ZERO effect and both "light" and "dark" captures render identically today.metagraphed's own contributor skill documents forcing theme vialocalStorage.setItem(themeKey, "dark"|"light"); location.reload()as the mechanism its own manual screenshot convention relies on — the two approaches may not agree.Separately,
shot.tscurrently has exactly 2 viewports —DESKTOP_VIEWPORT(1440×900) andMOBILE_VIEWPORT(390×844) (lines 41-42) — versusmetagraphed's own documented manual convention of 3 viewports (Mobile 375×812, Tablet 768×1024, Desktop 1280×800, chosen to straddle Tailwindmd/lgbreakpoints).Note:
fullPage: true(added #3669, bounded #3712 withMAX_SCREENSHOT_HEIGHT/MAX_SCREENSHOT_PIXELS/MAX_SCREENSHOT_BYTES) is CORRECT and already shipped — do NOT revert to fixed-viewport-only capture; that would reintroduce the below-the-fold bug #3669 fixed.Requirements
emulateMediaFeaturesactually changes rendered theme, or is a no-op for apps using localStorage-based theming. Do not assume either way.localStorage-driven apps: add apage.evaluate(() => localStorage.setItem(...))+ reload path as a fallback/alternative, with the storage key name configurable per-repo (likely under the existingreview.visualconfig namespace).Deliverables
emulateMediaFeatureswork for the actual capture targets in practice?Expected outcome
Theme-forced captures actually reflect the intended theme for every real target, and the viewport matrix is a deliberate, documented choice.
Effort
S-M — depends on what the empirical verification finds.