{
  "schema_version": 1,
  "type": "skill",
  "slug": "gallia-screenshot",
  "title": "gallia-screenshot",
  "brief": "Three capture layers let the AI see what it produces and self-correct without asking the user to screenshot manually. This is the core enabler for autonomous visual feedback loops.",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "---\nname: gallia-screenshot\ndescription: How to capture screenshots at three levels — gvShot (GV panel), tabShot (full browser tab), deskShot (full desktop). Enables AI visual feedback loops for self-verification and iterative correction.\n---\n\n# Screenshot & Visual Feedback\n\nThree capture layers let the AI see what it produces and self-correct without asking the user to screenshot manually. This is the core enabler for autonomous visual feedback loops.\n\n## Quick Reference\n\n| Need to see... | Layer | Tool | Setup |\n|---|---|---|---|\n| Rendered content (3D, SVG, widget) | **gvShot** | `gv_capture` | None |\n| Toolbar, buttons, multi-pane layouts, nested iframes | **tabShot** | `gv_tab_capture` | One-time user permission |\n| KiCad, Fusion 360, desktop apps | **deskShot** | `desktop_screenshot_screen/window` | Conduit app running |\n\n## Visual Feedback Loop (CRITICAL)\n\nThis is the pattern that makes AI autonomous for visual work:\n\n```\n1. Make change (edit HTML, generate SVG, load 3D model, add toolbar button)\n2. Push to viewer (gv_display, gv_3d_display, restart server + reload)\n3. Capture screenshot (choose the right layer — see table above)\n4. Analyze the screenshot — does it look right?\n5. If not → identify what's wrong → fix → go to step 1\n```\n\n**Always capture after visual changes.** Don't commit UI changes without verifying them visually first.\n\n### When to use which layer\n\n- Added a toolbar button → **tabShot** (gvShot can't see HTML overlays)\n- Loaded a 3D model → **gvShot** (fast, no setup, captures the canvas)\n- Changed a symbol SVG → **gvShot** (captures rendered SVG content)\n- Updated a multi-pane layout (LibView, split-pane) → **tabShot** (crosses iframe boundaries)\n- Sent a file to KiCad → **deskShot** with `desktop_screenshot_window` (captures desktop app)\n- Need to see the full IDE layout → **tabShot** (captures entire browser tab)\n- Need to see multiple desktop windows → **deskShot** with `desktop_screenshot_screen`\n\n---\n\n## Layer 1: gvShot (`gv_capture`)\n\n**Scope**: GV panel canvas content only (the rendered output inside iframes)\n\n```\ngv_capture()  →  PNG of whatever the GV panel is showing\n```\n\n- **MCP tool**: `gv_capture` — no parameters\n- **Saves to**: `project-content/screenshots/gv/mgmt-screenshot-{timestamp}.png`\n- **Setup**: None — works automatically when a viewer is connected\n- **Timeout**: 10 seconds\n\n### How it works\n\n```\nMCP tool → mgmt relay (port 8772) → WebSocket broadcast to browser\n→ browser tries 3 capture strategies in order:\n  1. postMessage to iframe → iframe serializes its own content → parent renders PNG\n  2. Direct SVG serialization (for SVG in main content area)\n  3. Direct <img> capture (fallback for image elements)\n→ sends PNG back via WebSocket → saved to disk → returned to MCP\n```\n\nThe key technique is **cooperative iframe capture**: the parent asks the iframe to export its content via `postMessage`, and the iframe voluntarily serializes its own canvas/SVG. This avoids cross-origin tainted canvas errors.\n\n### What it CAN capture\n- 3D model renders (Babylon.js canvas via `canvas.toDataURL()`)\n- Symbol/footprint SVGs (serialized to XML, re-rendered at 8x scale)\n- Image content displayed in the viewer\n- Any iframe that implements the `mgmt_capture_request` protocol\n\n### What it CANNOT capture\n- HTML overlays (toolbar buttons, tooltips, info bars) — these are DOM elements above the canvas\n- Nested iframes (LibView 3-pane layout) — cross-iframe postMessage doesn't chain\n- Content that hasn't implemented the capture protocol\n\n---\n\n## Layer 2: tabShot (`gv_tab_capture`)\n\n**Scope**: Full browser tab — IDE, editor, terminal, GV panel, overlays, nested iframes, everything visible in the tab\n\n```\ngv_tab_capture()  →  PNG of the entire browser tab\n```\n\n- **MCP tool**: `gv_tab_capture` — no parameters\n- **Saves to**: `project-content/screenshots/gv/tab-capture-{timestamp}.png`\n- **Timeout**: 10 seconds\n\n### How it works\n\nA companion tab holds a persistent `getDisplayMedia` video stream of the shared browser tab. When `gv_tab_capture` is called, the server asks the companion tab to grab a single frame from the video stream.\n\n```\nMCP tool → main viewer API (port 8771) action: 'tab_capture'\n→ server sends capture_request to companion tab via WebSocket\n→ companion tab grabs frame: canvas.drawImage(video, 0, 0) → toDataURL()\n→ sends frame back → server saves to disk → returns to MCP\n```\n\n### Setup (one-time per session)\n\nIf `gv_tab_capture` returns an error about \"no capture tab\" or \"not sharing\", prompt the user:\n\n1. Click the **camera button** in GV (or press Alt+S) — opens the capture companion tab\n2. Click **\"Share This Tab\"** in the popup\n3. Select the **IDE tab** in the browser's sharing dialog\n4. Done — the stream persists even across GV reloads\n\n### Key properties\n- **NOT affected by foreground window changes** — captures the shared tab regardless of which window is in front\n- **Captures everything in the tab** — toolbar icons, button active/inactive states, multi-pane layouts, nested iframes\n- **Near-zero CPU cost when idle** — the video stream only decodes frames on demand\n- **Survives GV restarts** — the companion tab connects to the mgmt relay (port 8772), which is independent of the main GV server\n\n### When to prefer tabShot over gvShot\n- You need to verify toolbar button icons, active states, or hover effects\n- You're working with multi-pane layouts (LibView, split-pane views)\n- gvShot returned a blank or incorrect capture (some content types aren't capturable by gvShot)\n- You need to see the full context of what the user sees\n\n---\n\n## Layer 3: deskShot (`desktop_screenshot_screen` / `desktop_screenshot_window`)\n\n**Scope**: Full desktop or specific application window — native OS-level capture via Conduit\n\n### Tools\n\n| Tool | What it captures | Parameters |\n|---|---|---|\n| `desktop_list_windows` | List all visible windows | None → returns `[{ hwnd, title, className, rect }]` |\n| `desktop_screenshot_window` | Specific window by handle | `hwnd` (required), `savePath` (optional) |\n| `desktop_screenshot_screen` | Entire desktop (all monitors) | `savePath` (optional) |\n\n### How it works\n\n```\nMCP tool → Conduit relay (port 8766) → WebSocket → Desktop Conduit app\n→ native OS capture API (Windows: BitBlt, Mac: CoreGraphics)\n→ base64 PNG → returned to MCP\n```\n\n### Setup\n- Adom Desktop Conduit app must be running on the user's machine\n- Desktop client must be connected (check with `conduit_status`)\n\n### Key properties\n- **`desktop_screenshot_screen` IS affected by foreground window** — captures whatever is on top\n- **`desktop_screenshot_window` captures a specific window** by HWND — use `desktop_list_windows` first to find the handle\n- **15-second timeout** per capture\n- **Cross-application** — can see KiCad, Fusion 360, browser, terminal, anything on the desktop\n\n### Typical workflow: verify KiCad delivery\n\n```\n1. Send file to KiCad: kicad_open_board({ path: '/tmp/board.kicad_pcb' })\n2. List windows: desktop_list_windows() → find KiCad's HWND\n3. Capture: desktop_screenshot_window({ hwnd: 12345 })\n4. Analyze → verify the board loaded correctly\n```\n\n---\n\n## Adding Capture Support to a New Iframe Widget\n\nWhen creating a new iframe-based viewer widget that should be capturable by `gv_capture` (Layer 1):\n\n1. Add a `message` event listener for `mgmt_capture_request`:\n```javascript\nwindow.addEventListener('message', (e) => {\n  const msg = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;\n  if (msg.type === 'mgmt_capture_request') {\n    // Capture your content...\n  }\n});\n```\n\n2. **For SVG content**: Clone, inline dynamic styles, serialize to XML:\n```javascript\nconst xml = new XMLSerializer().serializeToString(svgClone);\nwindow.parent.postMessage({\n  type: 'mgmt_canvas_capture',\n  _reqId: msg._reqId,\n  svgXml: xml,\n  svgWidth: width,\n  svgHeight: height,\n  bgColor: '#0d1117'\n}, '*');\n```\n\n3. **For canvas content** (WebGL, 2D): call `toDataURL()` directly:\n```javascript\nscene.render();\nconst dataUrl = canvas.toDataURL('image/png');\nwindow.parent.postMessage({\n  type: 'mgmt_canvas_capture',\n  _reqId: msg._reqId,\n  data: dataUrl\n}, '*');\n```\n\nThe parent receives the data and saves it — the iframe never needs to worry about cross-origin restrictions because it's exporting its *own* content cooperatively.\n\n## Key Files\n\n| File | Role |\n|------|------|\n| `~/gallia/viewer/mcp/server.js` | MCP tool definitions (`gv_capture`, `gv_tab_capture`) |\n| `~/gallia/viewer/mgmt-server.js` | Management relay (port 8772) — routes gvShot requests |\n| `~/gallia/viewer/server.js` | Main viewer server — handles tabShot API |\n| `~/gallia/viewer/viewer/index.html` | Browser-side gvShot orchestration |\n| `~/gallia/viewer/viewer/capture.html` | Screen Capture API companion tab for tabShot |\n| `~/gallia/server/mcp/server.js` | Conduit MCP tools (`desktop_screenshot_*`) |\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "kyle@adom.inc"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "sample_prompts": [],
  "discovery_triggers": [],
  "discovery_pitch": null,
  "metadata": {},
  "created_at": "2026-05-28T05:30:32.284Z",
  "updated_at": "2026-05-28T05:30:32.284Z",
  "sub_skills": [],
  "parent_app": null
}