Download

name: ralph-loop-test description: >- The Adom "ralph test" (Ralph Wiggum loop) — a screenshot-driven fix loop for apps. Point it at the user's running app (almost always a WEB APP in a webview / pup / the native browser, sometimes a DESKTOP APP driven via an adom-desktop bridge): load it, screenshot it, feed the shot to the AI, the AI analyzes what's wrong and fixes it, then reload the page (or reload the file in the desktop app), screenshot again, and loop until it's right. Covers every Adom screenshot surface — Hydrogen webview (adom-cli), pup, the native Chrome/Edge extension (nbrowser_*), and desktop bridges — plus the shotlog viewer workflow (how to actually see shots when the VS Code extension can't show images inline). Use when the user says "ralph", "ralph test", "ralph wiggum", "ralph loop", "screenshot loop", "visual fix loop", "loop until it's fixed", "test my app with screenshots", or "keep screenshotting and fixing until it works".

Ralph Test (Adom) — the screenshot-driven fix loop

In the Adom world a ralph test (a Ralph Wiggum loop) is a visual fix loop, not a code-build loop. You point it at the user's running app — almost always a web app (shown in a Hydrogen webview, in pup, or in the native Chrome/Edge browser), sometimes a desktop app driven through an adom-desktop bridge — and you grind this cycle:

  1. Load the app on a surface you can screenshot.
  2. Screenshot it.
  3. Feed the shot to the AI → it analyzes what's wrong (broken layout, wrong/missing data, a console error, off-brand styling, an element that should be there and isn't).
  4. Fix the cause — edit the app's code / config / data (or the desktop file).
  5. Reload — reload the page in the browser, or reload the file in the desktop app.
  6. Screenshot again, and shotlog inject it — that's how you and the user actually SEE the shot (the Claude Code VS Code extension can't show images inline).
  7. Loop — repeat until the screenshot matches intent.

"Ralph" because it's gloriously dumb and persistent: the same look → fix → reload cycle, over and over, until the shot is correct. Memory of what's been tried lives in the app's own state + the shotlog history, not the context window.

Where you're running

The AI always runs from a container, and they all carry the same screenshot tools, so this loop works identically everywhere:

  • Hydrogen Web — a Docker cloud container.
  • Hydrogen Desktop — a WSL2 container (Windows), a Lima container (Mac), or an Ubuntu container (Ubuntu).

What changes per task is only which surface you load the app on. Pick it in Step 1.

Targeting: adom-desktop verbs hit the user's laptop. If more than one desktop is on the relay you MUST name one (-t AdomLapper) or it errors ambiguous_target (adom-desktop targets lists them).


⛔ ALWAYS run the surface in the BACKGROUND

A ralph loop runs for a long time, so it must never steal the user's focus or disturb their main work. Always launch the browser/app surface in the background — never foreground to "look at" your app. Adom engineers have put enormous work into making every web surface fully operable while buried, so background operation is always possible:

  • pup — its default launch puts Chrome-for-Testing into a non-occluding background mode: the page keeps painting even when fully covered, so 3D views still render and screen recording runs at full framerate in the background. Open and capture; never raise the window.
  • Native Chrome/Edge (nbrowser_*) — opens windows unfocused by default, and nbrowser_screenshot captures an occluded window with zero OS-window interaction. If you need the real browser to keep painting while buried (background 3D / full-fps recording), it can offer a full relaunch of the native browser in non-occlusion "full refresh" mode (the user's profile + logins survive), then offer to revert afterward.
  • adom-desktopdesktop_screenshot_window captures a window by hwnd in the background (no foregrounding). It also returns a full ARRAY of screenshots including the window's CHILD windows, so error/dialog boxes — almost always child windows of the parent hwnd — are captured automatically. A ralph loop therefore sees the error popup without hunting for it.
  • Hydrogen webview (adom-cli) — captures the user's workspace panels without foregrounding.

Only ever foreground when the user explicitly asks to watch (desktop_bring_to_front).


Window, tab & taskbar etiquette (be a tidy guest)

The loop opens browser surfaces on the user's real machine, so don't clutter or hijack it:

  • One window per topic — use TABS for the rest. Keep a pup or native-browser window pinned to a single topic; when the task spans several pages, open them as tabs in that same window, not a new window each. browser_open_tab {sessionId,url} "adds a tab to the same Chrome window … avoids a new taskbar icon" (native: nbrowser_open_tab). Editing five wiki pages? Five tabs in one window, then browser_list_tabsbrowser_switch_tab {tabId} (or just screenshot a specific tabId) as you go. One taskbar button, not five.
  • NEVER take over a window that's open for another reason. Always open your OWN fresh window (browser_open_window / nbrowser_open_window). Navigating or reloading a window the user — or another AI thread — already had open yanks their page out from under them; it's the rudest move the loop can make, and the AI drifts into it unless it stops to check first. Open fresh, every time.
  • Clean up after yourself. When done with a window, close it (browser_close_window / nbrowser_close_window; close_tab for stray tabs). Long loops otherwise leave a graveyard of stale pup/native windows cluttering the taskbar — tidy as you go and again at the end.
  • Use the taskbar FLASH to say "come look," gently. Whenever the loop updates a window during a ralph test (reloaded the page, applied a fix, grabbed a fresh shot), flash its taskbar icon orangebrowser_alert_window {sessionId} (native: nbrowser_flash {sessionId}). It does NOT steal foreground; it just glows, so the user thinks "oh, the AI updated that window — I'll peek if I want." That gentle nudge is the whole point: never force a window to the foreground to show progress — that's rude and disruptive.

Step 1 — Load + screenshot the app (pick the surface)

A. Web app in a Hydrogen webview — the default for the user's OWN app

The user's app usually renders right in their Hydrogen workspace.

adom-cli hydrogen screenshot workspace               # the whole workspace
adom-cli hydrogen screenshot panel --panel-id <id>   # just the app's panel  (ids: hydrogen workspace get)

Reload between iterations by reloading the webview — or just save a file if the app hot-reloads.

Pro/con — adom-cli hydrogen screenshots are SMART but Web Hydrogen pays a price. These are the only smart captures: they target a specific webview / panel / tab (not just an OS window), which adom-desktop cannot do — it only targets hwnds. The catch is the permission model, and it splits by environment:

  • Web Hydrogen (cloud): the first adom-cli hydrogen screenshot makes the user approve the browser's native screen-sharing prompt and pick the window (a one-time browser security gate). That also flips the machine into a screen-share mode like a Google Meet, with side effects the user may not expect during a long loop: displays won't sleep, screensavers won't fire, a laptop panel won't turn off at the hardware level, and (likely) Windows suppresses notifications.
  • Hydrogen Desktop: does everything Web Hydrogen's smart targeting can (webviews/panels/tabs), but with no permission prompt and none of the Google-Meet side effects — it turns screenshotting (and screen recording) on and off on demand because it never has to ask.
  • pup and the native browser (with the extension) also capture with no prompt, but they target a window/page, not a Hydrogen panel.

So for an unattended ralph loop, prefer Hydrogen Desktop / pup / native browser; reach for Web Hydrogen's smart panel targeting knowing it imposes the screen-share mode for the session.

B. Web app in pup (Puppeteer) — fast, scriptable

adom-desktop -t AdomLapper browser_open_window '{"sessionId":"app","profile":"Default","url":"<url>"}'
adom-desktop -t AdomLapper browser_screenshot  '{"sessionId":"app","fullPage":true}'   # → /tmp/adom-desktop-screenshots
adom-desktop -t AdomLapper browser_reload      '{"sessionId":"app"}'

Great for your own apps and open public pages. But pup is an empty controlled-Chromium: more and more sites detect and block it (e.g. digikey.com), and it's logged in to nothing. When that bites, jump to C.

C. Web app in the NATIVE browser via the Adom extension — logged-in, undetectable ⭐

When a site blocks pup or needs you logged in, drive the user's real Chrome/Edge with the nbrowser_* verbs. Real cookies, SSO, saved logins and human-trust all apply, so it sails past the login walls and captchas that stop pup. This is your best friend for surfing the web (or local apps) with a full logged-in context.

adom-desktop -t AdomLapper nbrowser_status                                         # confirm connected
adom-desktop -t AdomLapper nbrowser_open_window '{"sessionId":"app","url":"<url>"}' # opens in the BACKGROUND
adom-desktop -t AdomLapper nbrowser_screenshot  '{"sessionId":"app","toFile":true}' # → a laptop PNG path
adom-desktop -t AdomLapper pull_file '{"filePaths":["<path>"],"saveTo":"/tmp"}'      # bring it into the container
adom-desktop -t AdomLapper nbrowser_reload '{"sessionId":"app"}'

Rules that matter (full reference: the driving-the-extension skill):

  • One-time install — AD installs it for you (Step 1.5); it's not there by default.
  • Never reuse a window the user is working in — always open a FRESH one, and nbrowser_close_window it when done. Yanking their live page is hostile.
  • Stay in the background — never foreground to grab a shot. nbrowser_screenshot captures an occluded window fine (it reads the page bitmap, no OS-window interaction).
  • Active clicking/typing makes Chrome show the yellow "Adom… started debugging this browser" bar (that's CDP). navigate / reload / screenshot go through native paths and keep it quiet.
  • Bonus: nbrowser_login_state {url} checks if you're signed in now; nbrowser_fetch_url grabs a file behind a login using the real cookies — no clicking.

D. Desktop app via an adom-desktop bridge

If the "app" is a desktop program (KiCad, Fusion 360, …), reload the file through its bridge and screenshot the window:

adom-desktop -t AdomLapper desktop_list_windows                                    # find the hwnd
adom-desktop -t AdomLapper desktop_screenshot_window '{"hwnd":<hwnd>}'             # any native window
adom-desktop -t AdomLapper kicad_screenshot_all '{}'                              # or fusion_screenshot_all

"Reload" = re-open / re-load the file in the app (e.g. kicad_open_board again after you edit it). desktop_screenshot_window captures the window in the background and returns an array that includes child windows, so a popped-up error/dialog box is captured automatically — exactly what a ralph loop needs to diagnose a failed reload.

Step 1.5 — Installing the native browser extension (adom-desktop installs it for you)

It isn't there by default, but it largely self-installs — don't just hand the user manual steps. Your container never talks to the extension directly; it drives adom-desktop (AD) on the laptop and AD does the setup. First-time flow (the extension page's INSTALL-FLOW.md walks every step):

  1. Get the skills + mechanics: adom-wiki skills install adom/adom-browser-extension — installs driving-the-extension, native-browser-safety, driving-the-desktop, the site recipes, etc.
  2. AD installs the bridge (one call) and registers the tiny native-messaging host on the laptop.
  3. The extension gets loaded into Chrome/Edge. Chrome 137+ removed --load-extension, so this is a one-time chrome://extensions → Developer mode → Load unpacked. AD can drive that OS-level flow for the user — the driving-the-desktop skill owns the extension's whole install / uninstall / reload lifecycle with a proven, validated recipe. Production uses a Chrome Web Store force-install.
  4. Confirm: nbrowser_status → connected, and the full nbrowser_* surface (navigate, screenshot, reload, click, type, login_state, fetch_url…) is live.

So install it FOR the user, not at them — only ask them to approve a click when their machine isn't Adom-managed and AD can't complete the OS step silently.

Step 2 — Analyze → fix → reload

  • Analyze: read the screenshot. Compare against intent — what's broken, wrong, missing, or ugly.
  • Fix: edit the app's source / config / data file (or the desktop file).
  • Reload: the matching reload for your surface — reload the Hydrogen webview, browser_reload, nbrowser_reload, or re-open the desktop file. (Web apps with hot-reload refresh on save.)

Step 3 — shotlog: how you AND the user actually SEE every shot (do NOT skip)

This is the most important habit in the loop, and the AI keeps forgetting it — don't. The Claude Code VS Code extension can't render images inline (Claude Desktop can; the VS Code one still can't), so shotlog is the surface where both you and the user actually look at each screenshot as the loop runs. It is not mainly a phone thing — the phone is ~5% of why it matters.

Two rules, every single iteration:

  1. Open a shotlog TAB in the topic's browser window — up front. Add the channel's live gallery as a tab alongside your ralph-test tabs (browser_open_tab {sessionId,url:"<shotlog gallery url>"} / nbrowser_open_tab) so every new shot appears right next to the app you're debugging. The AI routinely forgets this — make it the first thing you do when a ralph test starts.
  2. Inject EVERY screenshot from EVERY surface. Hydrogen, pup, native browser, desktop window — no matter where the shot came from, it goes into shotlog immediately:
    shotlog inject -c <channel> -d "iter-<N>-<what-changed>" -s <source> <file.png>   # +--orig-w/--orig-h if pre-resized
    
    The gallery live-updates over WebSocket, newest-first, in that tab (and on the phone if the user wants it). Never take a shot that doesn't land in shotlog.

Running the loop

  • Unattended: drive it with the /loop skill (self-paced). Each pass: screenshot → analyze → fix → reload → screenshot → shotlog inject. /loop keeps the harness + Adom tools live between passes.
  • Stop when fixed: the loop ends when the screenshot matches intent (the AI judges the shot), or on a manual stop. Always cap iterations so a stubborn fix doesn't spin forever.

Surface cheat-sheet

App / situation Load + screenshot Reload
User's own web app in their workspace adom-cli hydrogen screenshot workspace | panel reload webview / hot-reload on save
Your web app or a public page (no login, allows automation) pup: browser_open_windowbrowser_screenshot browser_reload
Site that blocks pup or needs login (DigiKey, vendor portals) native: nbrowser_open_windownbrowser_screenshot + pull_file nbrowser_reload
Desktop app (KiCad / Fusion / …) desktop_screenshot_window / kicad_screenshot_all re-open the file via its bridge

Then always finish each iteration with shotlog inject → the shotlog tab in your topic window (where you and the user both watch — the VS Code extension can't show images inline). Phone is a bonus.