# 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.

```bash
# 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:

1. **Always screenshot and analyze yourself.** Never say "check your viewer" or "it should work now." YOU must visually verify every iteration.
2. **Never stop until the screenshot looks correct.** Loop until the visual output matches what's expected. No exceptions.
3. **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.
4. **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.

```bash
# 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:**
```bash
# 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.

1. Tell the user: "Click the **monitor icon** in the hydrogen nav bar (top right)"
2. A browser dialog appears asking what to share:
   - **"Share this tab"** -- enables `panel` + `workspace` screenshot scopes (recommended for most debugging)
   - **"Share entire screen"** -- enables all scopes including `screen`
3. 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)

```bash
# 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

```bash
adom-cli hydrogen screenshot workspace -o /tmp/ws.png
```

Use for: seeing full context across multiple panels, layout verification.

### Screen capture

```bash
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.

```bash
# 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:**
```bash
# 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).

```bash
# 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:**

```javascript
// 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):**

```javascript
const ws = new WebSocket(`ws://${location.host}`);
ws.onmessage = (e) => {
  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:**

```bash
# 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:
```bash
adom-desktop browser_eval \
  '{"sessionId":"debug","expr":"document.querySelector(\"#btn\").click()"}'
```

**Quick-start for simple static servers** (no eval needed):
```bash
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:

```javascript
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:

```javascript
// 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

```javascript
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:

1. Check `errors[]` first -- uncaught exceptions crash widgets silently
2. Check `console[]` for `type: 'error'` entries -- failed fetches, CORS blocks, missing resources
3. Common widget killers:
   - `ReferenceError: X is not defined` -- missing library or typo
   - `TypeError: 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 origin
   - `404 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:

```javascript
// 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-capture` method 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 `screen` scope 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):
```bash
shotlog resize large-image.png              # Resize in-place to max 1400px
shotlog resize large-image.png -o small.png # Resize to new file
```