Debug Guide -- Iterative Visual Feedback Loop
Full reference for debugging in the Adom environment. Sections 0-9 match the operational debug SKILL (with tool installation as section 0 and shotlog setup as section 2). Sections 10+ are deep-dive reference only.
0. Auto-Install Debug Tools
Run this FIRST before any debug session. Installs missing tools silently -- skips anything already present.
# shotlog — screenshot log viewer (port 8820)
if ! command -v shotlog &>/dev/null; then
echo "Installing shotlog..."
gh release download v0.2.0 -R adom-inc/shotlog -p "shotlog" -D /tmp --clobber
chmod +x /tmp/shotlog
sudo mv /tmp/shotlog /usr/local/bin/shotlog
echo "shotlog installed: $(shotlog --version 2>&1 || echo 'ok')"
fi
# adom-desktop — desktop bridge CLI
if ! command -v adom-desktop &>/dev/null; then
# Check for local build first
if [ -f ~/project/adom-desktop/cli/target/release/adom-desktop ]; then
sudo ln -sf ~/project/adom-desktop/cli/target/release/adom-desktop /usr/local/bin/adom-desktop
else
echo "Installing adom-desktop..."
gh release download -R adom-inc/adom-desktop -p "adom-desktop" -D /tmp --clobber
chmod +x /tmp/adom-desktop
sudo mv /tmp/adom-desktop /usr/local/bin/adom-desktop
fi
echo "adom-desktop installed: $(adom-desktop --version 2>&1 || echo 'ok')"
fi
After this, shotlog and adom-desktop are on PATH. Run the block once per container — subsequent calls are no-ops.
1. The Ralph Wiggum Philosophy
Four rules:
- Always screenshot and analyze yourself. Never say "check your viewer" or "it should work now." YOU must visually verify every iteration.
- Never stop until the screenshot looks correct. Loop until the visual output matches what's expected. No exceptions.
- Failures are data. Each broken screenshot tells you exactly what to fix next. Iteration beats perfection -- don't aim for perfect on the first try.
- Always log to shotlog. Every screenshot gets injected into shotlog so the user can watch the debug session unfold in real-time. The shotlog panel is the user's window into what you're doing -- without it, they're blind.
For automated iteration on well-defined tasks with clear completion criteria, see /ralph-loop.
2. Start Shotlog First
Before entering the debug loop, always set up shotlog. This opens a panel where the user can watch every screenshot you take in real-time -- it's their live feed of the debug session. Without it, the user has no visibility into what you're doing.
# 1. Ensure shotlog server is running
shotlog health || shotlog serve &
# 2. Pick a descriptive channel name based on what you're debugging
# Examples: "nav-overflow-fix", "3d-viewer-lighting", "signup-form-validation"
CHANNEL="<descriptive-name>"
# 3. Open the shotlog viewer panel in hydrogen so the user can watch
shotlog open -c "$CHANNEL"
Do this once at the start of any debug session. The user will see a shotlog panel appear in their workspace showing an empty timeline that fills up as you work.
3. The Debug Loop
1. Edit code
2. Refresh the debug surface (webview refresh, pup reload, or restart server)
3. Interact -- send commands to exercise the change (rotate camera, click buttons, toggle states)
4. Screenshot the panel and save to file
5. Inject into shotlog (user sees it appear in real-time)
6. Read the PNG -- analyze it visually yourself
7. If broken --> identify what's wrong --> fix code --> go to 1
8. If correct --> done
Steps 4-5 together, every time:
# Screenshot
adom-cli hydrogen screenshot panel --panel-id <id> -o /tmp/debug-v1.png
# Immediately inject into shotlog -- the user sees this appear live
shotlog inject -c "$CHANNEL" \
-d "v1: Initial render after adding nav dropdown component" \
-s hydrogen /tmp/debug-v1.png
Increment filenames and prefix descriptions with the version: v1:, v2:, v3:... so the shotlog timeline tells a clear story.
Descriptions matter -- they become filenames and are what the user reads in the shotlog panel. Write what you see and why. Bad: "screenshot". Good: "v3: Nav dropdown now renders but clips at bottom edge, needs overflow fix".
Step 3 is what makes this truly autonomous. Don't just screenshot the initial render -- drive the UI. Send eval commands to your server to rotate views, click buttons, fill forms, trigger edge cases. Test multiple states before declaring victory.
4. Screen Sharing Setup
Required for hydrogen screenshots to work. One-time setup per session.
- Tell the user: "Click the monitor icon in the hydrogen nav bar (top right)"
- A browser dialog appears asking what to share:
- "Share this tab" -- enables
panel+workspacescreenshot scopes (recommended for most debugging) - "Share entire screen" -- enables all scopes including
screen
- "Share this tab" -- enables
- Persists for the entire session -- only needs to be done once
If you get a 504 timeout on any screenshot command, the user hasn't enabled sharing yet. Tell them:
To enable screenshots, click the monitor icon in the top-right of the hydrogen nav bar, then select "Share this tab" in the browser dialog that appears. You only need to do this once per session.
5. Screenshots
Always prefer the most targeted scope. Panel capture is far more efficient than workspace or screen.
Panel capture (primary -- fastest)
# Get panel IDs first
adom-cli hydrogen workspace get
# Capture a specific panel
adom-cli hydrogen screenshot panel --panel-id <leaf-id> -o /tmp/debug-v1.png
Use for: webview panels, sandbox panels, any single panel you're debugging.
Workspace capture
adom-cli hydrogen screenshot workspace -o /tmp/ws.png
Use for: seeing full context across multiple panels, layout verification.
Screen capture
adom-cli hydrogen screenshot screen -o /tmp/screen.png
Use for: seeing the full hydrogen workspace including nav bar and surroundings.
Adom Desktop screenshots (native apps, background windows)
When debugging involves native desktop apps (KiCad, Fusion 360) or you need to capture a window that isn't in the foreground, use the Adom Desktop app via adom-desktop CLI. This is the only way to capture native desktop apps and background windows.
# Check desktop is connected
adom-desktop ping
# List all windows to find HWNDs
adom-desktop desktop_list_windows
# Screenshot a specific window (works even if in background)
adom-desktop desktop_screenshot_window \
'{"hwnd":<hwnd>,"savePath":"project-content/screenshots/desktop-debug.png"}'
# Screenshot the entire desktop
adom-desktop desktop_screenshot_screen \
'{"savePath":"project-content/screenshots/desktop-full.png"}'
# Bring a window to the foreground
adom-desktop browser_focus_window '{"sessionId":"debug"}'
Requires the Adom Desktop app running on the user's machine.
When to use which
| What you're debugging | Screenshot method |
|---|---|
| Content in a hydrogen panel (webview, sandbox) | hydrogen screenshot panel |
| Multiple hydrogen panels at once | hydrogen screenshot workspace |
| Full hydrogen workspace + nav bar | hydrogen screenshot screen |
| A pup browser session on desktop | browser_screenshot via adom-desktop CLI |
| KiCad, Fusion 360, native apps | desktop_screenshot_window via adom-desktop CLI |
| Background window you can't see | desktop_screenshot_window (captures by HWND even in background) |
| Full desktop with all apps visible | desktop_screenshot_screen via adom-desktop CLI |
6. Debug Surfaces
Webview panels (primary)
The main debug surface. Pattern: start HTTP server, create webview panel, refresh after changes, screenshot.
Create a webview panel pointing at your server:
- Panel type:
adom/a1b2c3d4-0031-4000-a000-000000000031 - Use workspace API to create via split or add-tab
- Pass
initialState: { "url": "https://<service-url>.adom.cloud/" }
Control:
# Navigate to a URL
adom-cli hydrogen webview navigate --panel-id <id> <url>
# Refresh after code changes
adom-cli hydrogen webview refresh --panel-id <id>
# Hide address bar for clean screenshots
adom-cli hydrogen webview set-header --panel-id <id> true
Pup browser sessions (desktop browser)
For when you need a real Chrome window on the user's desktop (full DevTools, console access, no iframe restrictions).
# Open a browser window
adom-desktop browser_open_window \
'{"sessionId":"debug","profile":"debug","url":"http://localhost:3000"}'
# After code changes: reload + alert (flash taskbar)
adom-desktop browser_reload '{"sessionId":"debug"}'
adom-desktop browser_alert_window '{"sessionId":"debug"}'
# Screenshot
adom-desktop browser_screenshot '{"sessionId":"debug"}'
# Check JS errors
adom-desktop browser_errors '{"sessionId":"debug"}'
# Evaluate JS in the page
adom-desktop browser_eval \
'{"sessionId":"debug","expr":"document.title"}'
Rules: Always pass sessionId. Always browser_reload + browser_alert_window after changes.
Sandbox panels
For isolated JS execution. Screenshot with element-capture (requires screen sharing like webview panels).
7. Serving Content with Remote Control
Your mini web server should expose a command/eval endpoint so Claude can interact with the page programmatically. This is what makes the debug loop truly autonomous -- Claude can edit code, reload, simulate user actions, screenshot, and verify without human intervention.
Build your server with an /eval endpoint:
// Express server with eval endpoint
const express = require('express');
const { WebSocketServer } = require('ws');
const app = express();
app.use(express.json());
app.use(express.static('./dist'));
const server = app.listen(3000);
const wss = new WebSocketServer({ server });
app.post('/eval', (req, res) => {
// Broadcast the JS expression to all connected clients
wss.clients.forEach(ws => ws.send(JSON.stringify({ type: 'eval', expr: req.body.expr })));
res.json({ ok: true });
});
Client-side handler (in your HTML):
const ws = new WebSocket(`ws://${location.host}`);
ws. => {
const msg = JSON.parse(e.data);
if (msg.type === 'eval') {
try { eval(msg.expr); } catch(err) { console.error(err); }
}
};
Then Claude can drive the UI via curl:
# Rotate the 3D camera
curl -s http://127.0.0.1:3000/eval -X POST -H "Content-Type: application/json" \
-d '{"expr":"viewer.camera.position.set(5, 3, 5); viewer.camera.lookAt(0,0,0);"}'
# Click a toolbar button
curl -s http://127.0.0.1:3000/eval -X POST -H "Content-Type: application/json" \
-d '{"expr":"document.querySelector(\"#toggle-wireframe\").click()"}'
# Simulate a mouse click at coordinates
curl -s http://127.0.0.1:3000/eval -X POST -H "Content-Type: application/json" \
-d '{"expr":"document.elementFromPoint(400,300)?.click()"}'
# Toggle a setting
curl -s http://127.0.0.1:3000/eval -X POST -H "Content-Type: application/json" \
-d '{"expr":"toggleNightMode()"}'
This is especially powerful for 3D viewers, interactive dashboards, multi-state UIs, and anything where you need to test more than just the initial render.
For pup sessions, use browser_eval instead of curl:
adom-desktop browser_eval \
'{"sessionId":"debug","expr":"document.querySelector(\"#btn\").click()"}'
Quick-start for simple static servers (no eval needed):
python3 -m http.server 3000 --directory ./dist &
# or
npx serve -p 3000 ./dist &
8. Decision Tree
Need visual debug feedback?
|
+-- Content in a hydrogen panel? (webview, sandbox)
| +-- Screen sharing enabled?
| | +-- YES --> screenshot panel --panel-id <id> (fastest)
| | +-- NO --> Tell user to click monitor icon to enable sharing
| |
| +-- After changes: webview refresh, then interact, then screenshot
|
+-- Content in a desktop browser window? (pup session)
| +-- browser_reload --> browser_eval (interact) --> browser_screenshot
|
+-- Content in a native desktop app? (KiCad, Fusion 360)
| +-- Adom Desktop connected? (ping)
| | +-- YES --> desktop_list_windows --> desktop_screenshot_window
| | +-- NO --> Guide user to install/connect Adom Desktop
| |
| +-- Need window in foreground? --> browser_focus_window first
|
+-- Need to see multiple panels or full layout?
| +-- screenshot workspace
|
+-- Need to see the user's full desktop?
+-- desktop_screenshot_screen (Adom Desktop, captures everything)
+-- or screenshot screen (hydrogen, limited to browser tab)
9. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 504 timeout on screenshot | Screen sharing not enabled | Click monitor icon in nav bar, share tab |
| Panel ID not found | Wrong ID or panel closed | adom-cli hydrogen workspace get to list current IDs |
| Webview shows blank | Server not running or wrong port | curl http://127.0.0.1:PORT/ to check |
| Screenshot shows stale content | Page not refreshed | adom-cli hydrogen webview refresh --panel-id <id> |
| Pup can't connect | Desktop Conduit not running | adom-desktop ping |
| desktop_screenshot fails | Adom Desktop not connected | Guide user through desktop app setup |
| Can't find window HWND | Window closed or title changed | desktop_list_windows to refresh |
| Need to see background window | Window behind other apps | desktop_screenshot_window captures by HWND even in background |
| Eval endpoint not responding | Server missing /eval route or WS not connected | Check server code has /eval POST handler and client has WS listener |
10. Canvas/WebGL Screenshot Wiring (Legacy AV)
This section applies only to content displayed in the Adom Viewer (AV) panel. Webview panels use hydrogen's element-capture API and need no wiring.
AV uses a cooperative capture protocol via postMessage. When av_capture is called, the parent document sends a mgmt_capture_request message to the content iframe. The iframe must respond with mgmt_canvas_capture containing the rendered image data.
Canvas-based AV widgets
If your AV widget renders to <canvas> (WebGL, 2D canvas, charts), add this handler:
window.addEventListener('message', (e) => {
let msg;
try { msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; } catch { return; }
if (msg?.type === 'mgmt_capture_request') {
const canvas = document.querySelector('canvas');
if (canvas) {
// Force a render frame if using requestAnimationFrame
parent.postMessage({
type: 'mgmt_canvas_capture',
_reqId: msg._reqId,
data: canvas.toDataURL('image/png')
}, '*');
}
}
});
Sub-iframes in AV widgets
If your AV widget embeds sub-iframes, each must implement its own capture handler. The parent forwards mgmt_capture_request down and relays mgmt_canvas_capture responses back up:
// Parent widget: forward capture requests to sub-iframe
window.addEventListener('message', (e) => {
let msg;
try { msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; } catch { return; }
if (msg?.type === 'mgmt_capture_request') {
document.getElementById('my-sub-iframe')?.contentWindow?.postMessage(msg, '*');
}
// Relay sub-iframe responses up to AV parent
if (msg?.type === 'mgmt_canvas_capture') {
parent.postMessage(msg, '*');
}
});
Note: Do not use html2canvas for screenshot wiring. It is deprecated -- it doesn't work with WebGL, 3D content, or cross-iframe scenarios.
11. Puppeteer Deep Dive
For complex debugging that needs full browser control, console visibility, and network inspection beyond what browser_eval and browser_errors provide.
Full Puppeteer Script Template
const puppeteer = require('puppeteer');
const path = require('path');
const SESSION_ID = `pup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const SESSION_DIR = path.join(__dirname, 'sessions', SESSION_ID);
(async () => {
const results = { sessionId: SESSION_ID, console: [], errors: [], screenshots: [], data: null };
let browser;
try {
browser = await puppeteer.launch({
headless: false,
defaultViewport: { width: 1280, height: 900 },
args: ['--no-sandbox', `--user-data-dir=${SESSION_DIR}`]
});
const page = await browser.newPage();
// Capture ALL console output
page.on('console', msg => {
results.console.push({ type: msg.type(), text: msg.text(), ts: new Date().toISOString() });
});
// Capture uncaught page errors
page.on('pageerror', err => {
results.errors.push({ message: err.message, ts: new Date().toISOString() });
});
await page.goto('http://localhost:3000', { waitUntil: 'networkidle2', timeout: 30000 });
await new Promise(r => setTimeout(r, 2000)); // Wait for async rendering
const screenshot = await page.screenshot({ encoding: 'base64', fullPage: false });
results.screenshots.push({ name: 'debug-shot', base64: screenshot });
results.data = await page.evaluate(() => ({
title: document.title,
url: window.location.href,
bodyText: document.body?.innerText?.slice(0, 500) || '',
}));
} catch (err) {
results.errors.push({ message: err.message, stack: err.stack, ts: new Date().toISOString() });
} finally {
if (browser) await browser.close();
}
console.log('__PUPPETEER_RESULTS__');
console.log(JSON.stringify(results, null, 2));
})();
Console Error Analysis
The biggest advantage of Puppeteer over hydrogen screenshots is JS console visibility:
- Check
errors[]first -- uncaught exceptions crash widgets silently - Check
console[]fortype: 'error'entries -- failed fetches, CORS blocks, missing resources - Common widget killers:
ReferenceError: X is not defined-- missing library or typoTypeError: Cannot read properties of null-- DOM element not found (wrong selector or script before DOM ready)CORS error/blocked by CSP-- widget needs a real origin404 on resource-- wrong path for a dependency
Keep Browser Open for Inspection
For interactive debugging (WebGL, CesiumJS, 3D), keep the browser open so the user can interact:
// DON'T close browser in finally block
} finally {
console.log('Browser open for inspection. Close manually when done.');
}
// Keep node alive
await new Promise(r => setTimeout(r, 300000)); // 5 min
12. Screen Sharing Architecture
Hydrogen screenshots use the browser's Screen Capture API (getDisplayMedia). When the user clicks the monitor icon and grants permission:
"Share this tab" grants access to capture the hydrogen editor tab. The
element-capturemethod can then isolate individual panels by their bounding rect. This is why panel capture is so efficient -- it captures only the pixels for that panel, not the entire viewport."Share entire screen" grants access to the full display. The
screenscope captures everything -- taskbar, other windows, desktop. More data to transfer and process, which is why it's slower.
Why element-capture is better than html2canvas: Element-capture reads actual rendered pixels from the compositor. It captures everything exactly as the user sees it: WebGL, CSS filters, backdrop-blur, canvas content, nested iframes, video elements. html2canvas attempts to re-render DOM elements to a canvas, which fails for anything beyond basic HTML/CSS.
13. Image Sizing
All screenshot methods auto-resize images to <=1568px on the longest edge before returning them. This is Claude's ideal image size -- larger images provide zero quality benefit.
| Source | Where resize happens |
|---|---|
| Hydrogen screenshots | Server-side in the hydrogen API |
| Pup browser_screenshot | adom-desktop CLI, defaults to maxWidth: 1568 |
| Adom Desktop screenshots | MCP server via sharp |
For manual resizing (e.g., large images from other sources):
shotlog resize large-image.png # Resize in-place to max 1400px
shotlog resize large-image.png -o small.png # Resize to new file