visual-adjust
Visually iterate on UI layout by comparing screenshots against ground truth
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Visually iterate on UI layout by comparing screenshots against ground truth
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
Picks up after /build to publish a GitHub release. Pre-flights the staged artifacts, drafts release_notes.md from commits since the last tag, tags master, then hands the gh release create upload command to the user. Stops short of running the upload โ that's user-driven.
Spawn a fresh-context Opus 4.7 agent to give an independent take when Claude and the user are talking past each other on a contested logic interpretation, OR to validate behavioral self-observations from /session-recap Step 0. User-triggered or claude self-proposed at impasse.
End-of-session recap โ codify learnings synchronously into their canonical homes; daily logs hold narrative continuity only.
Review dirty code and fix iteratively using Ralph loop pattern. When user say to "loop to fix dirty" or "review+fix"
Review dirty code changes using Claude Code Agent tool. When user say to "review" or "review changes" or "review dirty code"
| name | visual-adjust |
| description | Visually iterate on UI layout by comparing screenshots against ground truth |
| triggers | ["/visual-adjust","adjust ui","visual adjust","fix layout"] |
You are adjusting pygame display layout by comparing rendered screenshots against a real-world ground truth photo provided by the user.
preview_display.py supports --screenshot, --mode, --stop, --pa flagsupper_lcd.py)When the task spans more than one element, start wide before going deep:
Ask the user for a reference screenshot if they haven't provided one. Study it carefully โ note font sizes, spacing, alignment, colors, margins.
uv run preview_display.py --screenshot screenshot_<name>.png --mode english --stop 0 --pa 2
Read the PNG to see the current rendering. Compare against ground truth.
Call out specific differences:
Edit the renderer code. Change one thing at a time:
Take a new screenshot, read it, compare against ground truth. Present to user for feedback.
For non-trivial size/position/font work, build a side-by-side composite that stacks the reference photo above the cropped render, height-aligned โ see "Side-by-side composites" below.
rm screenshot_v<N-1>_*.png; the freshly-rendered
screenshot_v<N>_*.png stays. Never end a turn with no recent screenshot presentcd prefix to avoid path issuesWhen iterating on size, vertical placement, weight, or font choice, eyeballing two separate images side-by-side is unreliable. Build a composite that:
REFERENCE: <name> on top, RENDER (<label>): <name> belowcompare_<label>_<station>.png_dev_scripts/compare_fonts.py is a working implementation. Pattern:
# 1. render variants under a label
uv run preview_display.py --screenshot screenshot_v1_bold_tokyo.png --mode english --stop 0 --pa 2
# ... one per reference station
# 2. build composites
uv run _dev_scripts/compare_fonts.py v1_bold
For A/B/C comparison of multiple variants at once, see _dev_scripts/compare_grid.py โ
stacks reference + N candidate renders for each station.
Use this whenever there's a static reference photo and you're iterating โ much more reliable than mental overlay.
Your eyes are not precise enough โ write code so the user can fine-tune it.
When writing or adjusting layout code:
Extract all tuneable values into a labeled params block at the top of the
method (or __init__ for fonts). Group them visually so they're easy to find:
# --- Badge params (adjust freely) ---
badge_x = 222
badge_w = 68
ring_black = 7
text_gap = -10
# -------------------------------------
All positioning must derive from those params โ no magic numbers scattered
below. If the user changes badge_w, interior width, centering, and text
positions should all recompute automatically.
Font sizes live in __init__ (must be preloaded), but add a comment pointing
to the method so the user knows where to look. Layout in the draw method should
use font.get_size() / surface.get_size() to react to whatever size was set.
Do not add overflow guards (e.g. max(0, ...)). If the user sets a font too
large for the container, they should see the overflow โ that's their signal to
tune the value.
Confirm each param's reactivity before coding โ including coupling to OTHER elements: before writing a new element or wiring a tuneable, describe the intended reactive behaviour (e.g. "text group centered vertically in the white interior") AND name what derives from what across elements โ "digit size = ratio ร circle radius (changes when you resize the circle)" vs "digit size = its own param". Confirm with the user per relationship. A silently-coupled param surprises the user mid-tuning; a silently-independent one forces them to eyeball N copies of the same proportion. (2026-06-12: 5-station countdown digit was hard-derived from circle radius at a guessed ratio โ one circle fit, another broke; the derive-or-independent question belonged up front.) Saves back-and-forth on the wrong layout model.
# Flags
--screenshot <file.png> # Save one frame and exit
--mode <kanji|furigana|english> # Force display mode
--stop <index> # Station index (0-based)
--pa <0|1|2> # 0=ๆฌกใฏ/Next, 1=ใพใใชใ/Arriving at, 2=ใใ ใใพ/Now stopping at
--route <name|path> # Route shorthand (e.g. yamanote, sobu/1217F) or path; default _mock/main
--lower-view <full|eight|cycle> # Force lower LCD view; default 'cycle' (slot rotation on the beat schedule).
# 'eight' or 'full' freezes the view-cycler for deterministic frames.
--debug-grid # Tint each upper-LCD region's clear rect with a unique color
# Examples
uv run preview_display.py --screenshot out.png --mode english --stop 0 --pa 2
uv run preview_display.py --screenshot out.png --mode kanji --stop 3 --pa 0
uv run preview_display.py --screenshot 8sta.png --route sobu/1217F --stop 7 --lower-view eight
uv run preview_display.py --debug-grid --route sobu/1217F --mode english --stop 18 --pa 2
When the work is about region territory (where an element's drawing should and should not land) rather than glyph appearance, use --debug-grid. Each region's clear rect paints in a distinct color (red=dest, blue=prefix, yellow=clock, magenta=station, orange=pa_hint, gray=upper_bg). What it surfaces:
The principle: anything a region draws (bg fill, glyphs, decorations) must visually stay inside that region's confinement. Clear rect is not special โ it's just one of the things drawn. Same rule for all of them. See DISPLAY.md "Element Clear-Background Convention" for the full statement.
Pygame font surfaces include leading โ empty (transparent) pixels above visible glyph caps, ~10โ15px for big fonts. So:
blit_y โฅ confinement.top. Pure analytical, no probing. If it passes โ containment guaranteed by construction. If it fails โ signal to probe, not auto-violation.D2 is the rule that ships. D1 is a useful pre-check. If the user requires a specific font size for IRL accuracy and D1 would forbid it, that's not actually a violation โ probe D2 to confirm what visible pixels do.
When pixel-probing for containment, isolate the region so neighbors' content doesn't masquerade as the target's:
.ttf Helvetica has macron artifacts at large sizes โ use .otf variants
(fonts/HelveticaNeue-Bold.otf is the canonical English-station font; the
old HelveticaNeueBold.ttf has been removed)_bg("<region>") as the first step of its draw method โ not just the current
glyph footprint. All three mode renderers (Japanese / Furigana / English) share the
same territory for the same element. Adding a new region: register it in
_DEBUG_COLORS AND the Region Map comment block at the top of the LCD module.
See DISPLAY.md "Element Clear-Background Convention" for full rules,
including the band-bottom clamp that prevents the station's tall 2-line variant
from clobbering the prefix/clock above.SDL_VIDEODRIVER=dummy automatically โ no window neededroute.json at audio/_mock/main/route.json
(not in code). Default for preview_display.py. Edit that file directly to add
test cases. Reference station indices for compare scripts: see
audio/_mock/main/README.md.