{
  "schema_version": 1,
  "type": "skill",
  "slug": "adom-hydrogen-viewer",
  "title": "Gallia Viewer",
  "brief": "The Adom Viewer (AV) displays visual content from the Docker container in the Adom App browser panel. Supports HTML widgets, SVG, images, markdown, 3D models (Babylon.js), screenshot paste with AI nam",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "# Adom Viewer\n\nThe Adom Viewer (AV) lets you send visual content from the Docker container to the Adom App front-end so the user can see it in their browser. Content appears in an iframe panel with tabs, dark theme, and auto-reconnecting WebSocket.\n\n## Supported content types\n\n| Type | Extensions / format | Notes |\n|------|-------------------|-------|\n| SVG | `.svg`, raw SVG markup | Vector graphics, diagrams, schematics |\n| Images | `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp` | Raster images |\n| HTML | `.html`, `.htm` | Interactive HTML with `<script>` tags runs in an iframe |\n| Markdown | `.md`, `.markdown` | Rendered client-side in the viewer |\n\n## How to display content\n\n### Option A: Display an existing file\n\nUse the `av_display_file` MCP tool:\n\n```\nmcp__adom-viewer__av_display_file({ file_path: '/absolute/path/to/file.svg', title: 'My Diagram' })\n```\n\nThis auto-detects the content type from the file extension.\n\n### Option B: Display generated content\n\nUse the `av_display` MCP tool:\n\n```\nmcp__adom-viewer__av_display({\n  content_type: 'html_interactive',\n  content: '<html>...</html>',\n  title: 'My Visualization'\n})\n```\n\nFor images, provide a data URI: `data:image/png;base64,...`\n\n### Option C: Internal HTTP API fallback\n\nIf MCP tools are not available, POST directly to the AV internal API:\n\n```javascript\nconst http = require('http');\nconst crypto = require('crypto');\n\nconst payload = JSON.stringify({\n  action: 'display',\n  content: {\n    contentType: 'svg',\n    content: '<svg>...</svg>',\n    title: 'My Diagram',\n    id: crypto.randomUUID(),\n    source: 'av_display',\n    skill: 'av-creator',\n    author: 'Your Name'\n  }\n});\n\nconst req = http.request({\n  hostname: '127.0.0.1',\n  port: 8771,\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' }\n}, (res) => {\n  let data = '';\n  res.on('data', c => data += c);\n  res.on('end', () => console.log(res.statusCode, data));\n});\nreq.write(payload);\nreq.end();\n```\n\nThe `source`, `skill`, and `author` fields appear in a tooltip when the user hovers over the tab. Always include them.\n\n## MCP Tools\n\n| Tool | Purpose |\n|------|---------|\n| `av_display` | Display generated content (HTML, SVG, markdown, images) |\n| `av_display_file` | Display an existing file by path |\n| `av_3d_display` | Display a GLB 3D model in the Babylon.js viewer |\n| `av_symbol_3d_display` | Split-pane symbol SVG + 3D model |\n| `av_library_review` | 3-pane library review (symbol + footprint + 3D) |\n| `av_screenshot_paste` | Open clipboard screenshot paste utility |\n| `av_file_explorer` | Browse files in the viewer |\n| `av_symbol_prefs` | Configure symbol generation preferences |\n| `av_clear` | Clear all displayed content |\n| `av_status` | Check if the viewer is connected |\n| `av_reload` | Force browser to reload the AV page (survives server restarts) |\n| `av_capture` | Capture a screenshot of just the AV panel content |\n| `av_tab_capture` | Capture full browser tab via Screen Capture API |\n| `av_set_camera` | Set the 3D camera position |\n| `av_set_view` | Set a named camera preset (front, back, top, etc.) |\n| `av_set_bottom_light` | Toggle the bottom light for underside inspection |\n| `av_stop_tour` | Stop the cinematic camera tour |\n\n## Screenshot Paste\n\nThe `av_screenshot_paste` MCP tool opens an interactive paste widget in AV. Users paste screenshots from their clipboard and the system automatically:\n\n- **Resizes** images to Claude's ideal 1568px max dimension (skips if already small enough — shows \"Original still in clipboard\")\n- **Compresses** PNGs with sharp (level 9) for minimal file size\n- **AI names** files via Haiku with descriptive kebab-case names including dimensions (e.g. `dart-green-line-route-editor-1568x975.png`)\n- **Saves both** Claude-optimized and original versions to `screenshots/sp/`\n- **Shows right-click copy hint** when clipboard write is unavailable (VS Code webview limitation) — auto-opens fullscreen preview with \"Right-click → Copy image\" for resized images\n\n### Widget UX\n\n- **Focus tracking**: Teal border + \"READY FOR PASTE\" label when focused. Click anywhere in the widget to focus.\n- **Paste detection**: Immediate teal flash animation + status bar update on every paste event\n- **Two-card UI**: Claude (resized) and Original side-by-side with click-to-preview\n- **Smart badges**: \"Original still in clipboard\" (no resize), \"Clipboard updated\" (success), or \"Right-click preview to copy\" (resize + clipboard unavailable)\n- **Tab icon**: Clipboard icon via `skill: 'screenshot-paste'` metadata\n\n### Architecture\n\n- Widget: `screenshot-paste.js` generates HTML served fresh via `GET /screenshot-paste`\n- Rendering: Non-sandboxed `<iframe srcdoc>` with `allow=\"clipboard-write\"`\n- Storage: `project-content/screenshots/sp/` (isolated from avShot/tabShot/deskShot)\n- Server: `handleScreenshot()` in `server.js` handles resize, sharp compression, save, AI naming via Haiku\n\n## Management Relay (port 8772)\n\nA separate lightweight server runs on port 8772, independent of the main AV server (8770/8771). The browser connects to BOTH WebSockets. This solves two problems:\n\n1. **Forced reload after server restart** — when you restart the main AV server (for HTML/JS changes), the main WS breaks. The mgmt relay stays alive, so `av_reload` can tell the browser to reload.\n\n2. **AI-initiated screenshot capture** — `av_capture` asks the browser to screenshot whatever it's showing and returns the image.\n\n## Tab Capture (Screen Capture API)\n\n`av_tab_capture` captures the **entire browser tab** as a pixel-perfect screenshot using the browser's Screen Capture API.\n\n- `av_capture` — captures just the AV panel. Fast, no setup. But fails on multi-iframe layouts.\n- `av_tab_capture` — captures the full browser tab. Works on all content including nested iframes. Requires one-time user setup.\n\n**Setup:** Click the camera button in AV → opens the capture companion tab → click \"Share This Tab\" → select the IDE tab.\n\n## 3D Model Display\n\nUse `av_3d_display` to display GLB files in the built-in Babylon.js 3D view. Includes orbit camera, ground plane, skybox, and laser-etched chip markings.\n\n`av_3d_display` blocks until the model is fully loaded (up to 15s). Call `av_capture` immediately after — no sleep needed.\n\n### Camera controls\n\n- `alpha` = azimuth (0 = front, π/2 = right side)\n- `beta` = elevation (0 = top-down, π/2 = eye-level, π = bottom-up)\n- `radius` = zoom multiplier\n- Bottom light: illuminates underside for inspecting board pads and heatsink copper\n\n## Adom Theme\n\nAll AV widgets MUST use the Adom theme (`viewer/adom-theme.js`).\n\n### Key color tokens\n\n| Token | Value | Usage |\n|-------|-------|-------|\n| `bg` | `#0d1117` | Page background |\n| `bgSurface` | `#161b22` | Cards, panels |\n| `border` | `#30363d` | Standard borders |\n| `text` | `#e6edf3` | Primary text |\n| `textSecondary` | `#8b949e` | Labels |\n| `accent` | `#00b8b0` | Primary teal accent |\n| `accentBright` | `#00e6dc` | Hover/focus |\n| `success` | `#3fb950` | Success |\n| `warning` | `#d29922` | Warning |\n| `danger` | `#f85149` | Error |\n\n## Ports\n\n| Port | Purpose |\n|------|---------|\n| 8770 | HTTP server + WebSocket (public) |\n| 8771 | Internal API (localhost only) |\n| 8772 | Management relay (reload, capture) |",
  "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:16.468Z",
  "updated_at": "2026-05-28T05:30:16.468Z",
  "sub_skills": [],
  "parent_app": null
}