Adom Shotlog
Public Made by Adomby adom
See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is
name: adom-shotlog description: "Screenshot log viewer and injector. Use when: logging screenshots during debug loops, documenting visual progress, reviewing screenshot history, opening the screenshot log panel, injecting a screenshot into the log, ingesting an Adom Desktop screenshot response with child windows (owned popups). Trigger words: adom-shotlog, shotlog, screenshot log, shot log, visual log, screenshot history, log screenshot, open shotlog, screenshot viewer."
adom-shotlog - Screenshot Log
A Rust CLI + HTTP server (port 8820) that captures, stores, and displays screenshots in channels. AI agents inject screenshots as they work; you watch them arrive live in a gallery (desktop or mobile), newest first, each with its description, source, and both the stored and original resolution.
Binary: /usr/local/bin/adom-shotlog (the shotlog command is a back-compat alias).
This skill pack (main + sub-skills)
This is the main (user) skill. Deeper topics ship in the same
adom/adom-shotlog repo using the wiki's three-tier layout: skills/ = USER
(ships + installs for everyone), dev-skills/ = DEV (source-only), and
publish-skills/ = PUBLISH (source-only).
| Skill | Tier | What it's for |
|---|---|---|
| adom-shotlog-surfacing | user | surfacing a shot gently per surface (webview activate / pup taskbar flash / phone badge), never stealing foreground; plus AD window-discovery + cue verbs |
| adom-shotlog-child-windows | user | capturing a parent window plus its child windows (owned dropdowns/dialogs) via --meta, and how they render inline |
| adom-shotlog-dev | dev | build/deploy, the central-broker architecture, and the layout rules (ralph-test the skinny view, no outer-html scrollbars, pretty scrollbars) |
| adom-shotlog-publishing | publish | publishing to wiki.adom.inc (org ownership, OSS source, hero, the wiki traps) |
⚠ WHY THIS EXISTS: you can't show the human images in a text thread
When you run as Claude Code (VSCode / any text-only thread) you CANNOT
display an image to the human. Calling Read on a PNG, or taking a
desktop_screenshot / pup screenshot, loads the image into your context -
the human sees nothing in the thread. (Maybe someday Claude Code renders
images inline; until then, assume it can't.)
So, as an ironclad rule:
- NEVER write "see the screenshot above", "look at this image", "as shown below", or any phrase that assumes the human can see a picture in the thread. To them it's a blank reference - this exact failure is the whole reason shotlog exists.
- The only way the human sees an image is on a surface they actually have
open: the shotlog viewer (webview / pup / phone), or a URL you hand them.
So
injectit into a channel they're viewing, verify they're watching (shotlog viewers -c <ch>- see below), and refer them to that surface ("in the shotlog viewer" / "the pup window on your screen" / a link), never to an inline image.
The CLI teaches you as you go (trust its output)
shotlog is hint-driven IN CODE, not just in this skill. Every inject reply
tells you two things:
OK: Broker: ...lines = what the broker is ALREADY doing (pulling the channel's webview tab to the front with an orange alert, flashing a pup taskbar, force-opening a slim 20% webview). When you see one, surfacing is handled - do NOT open, refresh, or foreground anything yourself.Hint: mini-skill [...]lines = progressive teaching, revealed at the moment they're relevant (first shot in a channel, an AD capture missing--meta, a pup viewer without a?session=handle) and then they go quiet. Read and apply them; they are the condensed version of this skill pack.
The read-hook: auto-capture of every image you Read
shotlog ships and manages a PostToolUse(Read) hook: any image file you Read
is automatically injected into the shared read-captures channel (source
read-hook), so the human sees every screenshot you analyze even if you never
call inject. Do not disable or duplicate it. It is installed by
adom-shotlog install, re-verified at every serve startup, and self-heals the
broker (from $SHOTLOG_HOME, default ~/project) if it is down. If you see
read-captures filling up: that is this hook working as designed, and retention
keeps it bounded.
Preferences and canvas control (AI-drivable)
Everything the human can click, YOU can do - and the human is told the matching prompts in the UI, so expect requests like "turn the shotlog read-hook off" or "make shotlog default to pup". The broker endpoints (port 8820):
curl -s localhost:8820/api/prefs # read prefs
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
-d '{"read_hook": false}' # hook off/on
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
-d '{"default_canvas": "pup"}' # global default
Canvas rules the broker enforces (do not fight them):
- Every channel follows the GLOBAL default_canvas. The only exception is an IN-MEMORY note from the human's explicit click in the viewer (canvas button) or their deliberate pup-window close - forgotten on broker restart.
- YOUR opens (
shotlog open ...) do NOT record any preference. Never try to pin a channel's canvas; if the human wants that, they say so. - The skinny panel width comes from the webview_width pref (default 20%); do not pass --ratio unless the human asked for a specific width.
shotlog open -c <ch> --pup --swap/--skinny --swapis the switch verb.- The viewer is ONE single-window app with ITS OWN channel tabs (read-captures
pinned first). You never open one surface per channel anymore - plain
shotlog open -c <ch>opens/deep-links THE app with that tab active.
ONE window rule: never open your own shotlog surface
The human runs ONE shotlog window (webview or pup) with a tab per channel
INSIDE the app. adom-shotlog open -c CH handles this: if the app is already
open anywhere, it switches that window's internal tab (the human sees a toast
explaining the consolidation) and opens nothing new. Only when NOTHING is open
does it create a surface, on the human's default canvas.
NEVER load the viewer URL into your own pup session or a new window/tab
(browser_open_window/browser_open_tab with a /log/ URL) - that gives the
human a confusing second shotlog. Always go through adom-shotlog open.
Inject - one command
adom-shotlog inject -c CHANNEL -d "what you see and why" -s SOURCE /path/to/image.png
adom-shotlog always records the original capture resolution - and rejects any inject that doesn't establish it, so the viewer never shows "orig missing".
Resizing before inject is fine and expected. Oversized images crash Claude's vision analysis, so most Adom screenshot verbs return a shrunk copy on purpose. adom-shotlog doesn't fight that - it just requires the original size to travel with the shot. Ways through:
- Pre-shrunk + the original dimensions (the common case - your screenshot
step reports the pre-resize W×H, you pass them through).
adom-shotlog inject -c proj -d "…" --orig-w 1920 --orig-h 1080 /tmp/shrunk.png - Pre-shrunk + the original image - adom-shotlog measures it (keep the full-res
file around for this).
adom-shotlog inject -c proj -d "…" --orig-file /tmp/fullres.png /tmp/shrunk.png --native- assert the image is at original capture resolution (never resized). Use only when true.- Hand it the full-res image - adom-shotlog resizes anything over 1500px (either edge) down to 1400px and records the original automatically.
--meta <ad-response.json>- ingest an Adom Desktop screenshot response (see below). The original W×H comes from the response'sorigWidth/origHeight, so this satisfies the original-resolution rule on its own.
The rejection: if FILE is ≤1500px (so adom-shotlog won't resize it) and you give
none of --orig-w+--orig-h / --orig-file / --native / --meta, the inject is
refused with exit 1 and a hint.
HTTP-only inject: POST /api/paste
No CLI on your machine? POST a data URL. Declare your source (same values as
-s: pup, ad-window, hd, ...); undeclared defaults to api (a neutral,
chip-less deliberate inject). Do NOT claim clipboard - that chip means a HUMAN
pasted with Ctrl+V, and only the viewer's paste handler sends it. thread and
context are accepted too and power the viewer's attribution and filters.
curl -s -X POST localhost:8820/api/paste -H 'Content-Type: application/json' -d '{
"channel":"proj", "dataUrl":"data:image/png;base64,...",
"description":"what you see and why", "source":"pup",
"thread":"my thread title", "context":"the prompt being served"}'
--meta - Adom Desktop child-window arrays
As of adom-desktop 1.8.177, desktop_screenshot_window {hwnd} returns the primary
window image plus its child windows - the owned dropdowns, confirm bubbles, and
native dialogs - each captured separately, plus coordMap / DPI / screen metadata.
Pass that whole response JSON via --meta:
adom-shotlog inject -c CH -d "checkout form with autofill open" primary.png --meta ad-response.json
adom-shotlog will:
- store the coordMap / DPI / screenRect / screen-resolution as a sidecar (revealed in the viewer's ⌗ capture metadata panel, copyable as JSON),
- read each child-window PNG by its
localSafePath, save it next to the primary, and attach it - the card shows a +N child windows badge and achild windows ▸row rendering each child window inline (no zoom), - derive the original resolution from
origWidth/origHeight.
The response shape it reads (extra fields are ignored, all optional except a
primary image + screenshots[] for child windows):
{
"shotId": "…", "origWidth": 1920, "origHeight": 1200,
"width": 1400, "height": 875,
"coordMap": { "screenRect": {…}, "dpiScale": 1.25, "imageToScreen": {…}, "screenResolution": "1920x1200" },
"ownedPopupCount": 2,
"screenshots": [
{ "localSafePath": "/path/popup1.png", "hwnd": 12345, "title": "Autofill", "kind": "dropdown", "coordMap": {…} }
]
}
Plain single-image injects (no --meta) work exactly as before - child windows /
metadata are additive.
| Flag | Required | Description |
|---|---|---|
-c |
yes | Channel name (groups screenshots by task) |
-d |
yes | Why this screenshot was taken (becomes the filename, min 6 chars) |
-s |
no | Source: pup_screenshot, desktop_screenshot_window, hd_shot, etc. (default cli) |
--orig-file |
for pre-shrunk images | Path to the original full-res image; measured |
--orig-w / --orig-h |
for pre-shrunk images | Original dims (alternative to --orig-file) |
--native |
for unresized small images | Assert FILE is at original resolution |
--thread |
no | Which AI thread produced this shot (human-readable). The read-hook fills it automatically from the session transcript; pass your thread's name when injecting deliberately so the human sees WHO. |
--meta |
optional | Adom Desktop screenshot-response JSON: child windows + coordMap + orig dims |
FILE |
yes | Primary image path (use - for stdin) |
CRITICAL: Always write great descriptions
The -d description becomes the filename (slugified) and is shown prominently on
every gallery card. Make it specific.
Bad: "screenshot", "test", "after fix"
Good: "PCB layout after routing power traces", "Checkout form with autofill dropdown open"
The viewer
adom-shotlog open -c my-project --skinny # slim 20% webview panel (ambient watching)
adom-shotlog open -c my-project --pup # FULL-BROWSER pup canvas (real zoom); --swap closes the webview
The human picks the canvas: webview for ambient, pup when they want images big
(zoom in the skinny panel cannot exceed the panel). --pup --swap is the
one-command switch; the broker auto-flashes the pup taskbar on updates.
- Gallery, newest-first. Each card: description, timestamp, size (shown as
1400×788 · orig 1920×1080when they differ), KB, source, a +N child windows badge when the capture grabbed child windows, and an ⓘ meta chip when it has capture metadata. Child windows show inline; tap a card to zoom + see the copyable metadata panel. - Mobile-ready, green LED = a viewer is connected, Ctrl+V pastes a screenshot, live updates over WebSocket.
Do not place a shotlog tab on the VS Code pane. Prefer computing a non-VS-Code
--panel-idand callingadom-cli hydrogen workspace add-tab.
Force-open safety net
If a screenshot is injected into a channel with no viewer connected, the server auto-opens a viewer tab so the shot is never logged invisibly.
CRITICAL: verify the HUMAN is actually seeing it
Injecting a shot is not the job - the human seeing it is. shotlog tells you whether a live viewer is connected, and on which surface (webview / pup / mobile / browser), in the inject response itself - no guessing, no extra turns:
- On-screen:
OK: 1 of 1 viewer(s) ON-SCREEN [pup] - a human can see this shot. - Open but backgrounded:
WARN: 1 viewer(s) connected … but ALL are BACKGROUNDED / minimized - the human is NOT looking right now.Bring the pup window / webview tab / phone to the FOREGROUND, then re-check. - Nobody watching:
WARN: NO live viewer is connected - the human is NOT seeing this shot.A viewer is force-opened as a fallback, but do not assume it worked. Open the viewer on the human's actual surface and re-check.
The signal is real: each viewer holds a WebSocket open and reports its surface (webview/pup/mobile/browser) AND whether it's actually on-screen (Page Visibility) vs a background tab / minimized window.
Gate programmatically instead of burning turns - shotlog viewers exit codes:
0 = a human has it on-screen, 4 = connected but all backgrounded, 3 = nobody:
shotlog viewers -c my-project # 0=on-screen, 4=backgrounded, 3=none; prints surfaces
shotlog viewers -c my-project --url "$URL" # also HTTP-checks the endpoint you opened
Typical flow: shotlog open -c ch (or load it in pup / on the phone) → keep
injecting → if inject ever reports 0 viewers, stop and re-establish the viewer.
The viewer reports its surface automatically; when you open it in pup, append
?surface=pup to the URL so presence labels it correctly.
Other commands
adom-shotlog serve # start the server (port 8820)
adom-shotlog health # check server status
adom-shotlog viewers -c CHANNEL # is a human watching? exit 0=yes, 3=no (+ surfaces)
adom-shotlog resize FILE # rarely needed - inject auto-resizes
adom-shotlog install # install binary + this skill + completions + `shotlog` alias
Channel naming
Use the project or task name: board-review, hd-setup-steps, debug-session.
---
name: adom-shotlog
description: "Screenshot log viewer and injector. Use when: logging screenshots during debug loops, documenting visual progress, reviewing screenshot history, opening the screenshot log panel, injecting a screenshot into the log, ingesting an Adom Desktop screenshot response with child windows (owned popups). Trigger words: adom-shotlog, shotlog, screenshot log, shot log, visual log, screenshot history, log screenshot, open shotlog, screenshot viewer."
---
# adom-shotlog - Screenshot Log
A Rust CLI + HTTP server (port **8820**) that captures, stores, and displays
screenshots in channels. AI agents inject screenshots as they work; you watch
them arrive live in a gallery (desktop or mobile), newest first, each with its
description, source, and **both** the stored and original resolution.
Binary: `/usr/local/bin/adom-shotlog` (the `shotlog` command is a back-compat alias).
## This skill pack (main + sub-skills)
This is the **main** (user) skill. Deeper topics ship in the same
`adom/adom-shotlog` repo using the wiki's three-tier layout: `skills/` = USER
(ships + installs for everyone), `dev-skills/` = DEV (source-only), and
`publish-skills/` = PUBLISH (source-only).
| Skill | Tier | What it's for |
|-------|------|---------------|
| [adom-shotlog-surfacing](skills/adom-shotlog-surfacing/SKILL.md) | user | surfacing a shot *gently* per surface (webview activate / pup taskbar flash / phone badge), never stealing foreground; plus AD window-discovery + cue verbs |
| [adom-shotlog-child-windows](skills/adom-shotlog-child-windows/SKILL.md) | user | capturing a parent window **plus its child windows** (owned dropdowns/dialogs) via `--meta`, and how they render inline |
| [adom-shotlog-dev](dev-skills/adom-shotlog-dev/SKILL.md) | dev | build/deploy, the central-broker architecture, and the layout rules (ralph-test the skinny view, no outer-html scrollbars, pretty scrollbars) |
| [adom-shotlog-publishing](publish-skills/adom-shotlog-publishing/SKILL.md) | publish | publishing to wiki.adom.inc (org ownership, OSS source, hero, the wiki traps) |
## ⚠ WHY THIS EXISTS: you can't show the human images in a text thread
When you run as **Claude Code (VSCode / any text-only thread)** you **CANNOT
display an image to the human.** Calling `Read` on a PNG, or taking a
`desktop_screenshot` / pup screenshot, loads the image into **your** context -
the human sees **nothing** in the thread. (Maybe someday Claude Code renders
images inline; until then, assume it can't.)
So, as an ironclad rule:
- **NEVER** write "see the screenshot above", "look at this image", "as shown
below", or any phrase that assumes the human can see a picture in the thread.
To them it's a blank reference - this exact failure is the whole reason shotlog
exists.
- The **only** way the human sees an image is on a surface they actually have
open: the **shotlog viewer** (webview / pup / phone), or a **URL you hand them**.
So `inject` it into a channel they're viewing, **verify** they're watching
(`shotlog viewers -c <ch>` - see below), and refer them to *that surface*
("in the shotlog viewer" / "the pup window on your screen" / a link), never to
an inline image.
## The CLI teaches you as you go (trust its output)
shotlog is hint-driven IN CODE, not just in this skill. Every `inject` reply
tells you two things:
- **`OK: Broker: ...` lines = what the broker is ALREADY doing** (pulling the
channel's webview tab to the front with an orange alert, flashing a pup
taskbar, force-opening a slim 20% webview). When you see one, surfacing is
handled - do NOT open, refresh, or foreground anything yourself.
- **`Hint: mini-skill [...]` lines = progressive teaching**, revealed at the
moment they're relevant (first shot in a channel, an AD capture missing
`--meta`, a pup viewer without a `?session=` handle) and then they go quiet.
Read and apply them; they are the condensed version of this skill pack.
## The read-hook: auto-capture of every image you Read
shotlog ships and manages a `PostToolUse(Read)` hook: any image file you `Read`
is automatically injected into the shared `read-captures` channel (source
`read-hook`), so the human sees every screenshot you analyze even if you never
call inject. Do not disable or duplicate it. It is installed by
`adom-shotlog install`, re-verified at every `serve` startup, and self-heals the
broker (from `$SHOTLOG_HOME`, default `~/project`) if it is down. If you see
`read-captures` filling up: that is this hook working as designed, and retention
keeps it bounded.
## Preferences and canvas control (AI-drivable)
Everything the human can click, YOU can do - and the human is told the matching
prompts in the UI, so expect requests like "turn the shotlog read-hook off" or
"make shotlog default to pup". The broker endpoints (port 8820):
```bash
curl -s localhost:8820/api/prefs # read prefs
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
-d '{"read_hook": false}' # hook off/on
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
-d '{"default_canvas": "pup"}' # global default
```
Canvas rules the broker enforces (do not fight them):
- Every channel follows the GLOBAL default_canvas. The only exception is an
IN-MEMORY note from the human's explicit click in the viewer (canvas button)
or their deliberate pup-window close - forgotten on broker restart.
- YOUR opens (`shotlog open ...`) do NOT record any preference. Never try to
pin a channel's canvas; if the human wants that, they say so.
- The skinny panel width comes from the webview_width pref (default 20%);
do not pass --ratio unless the human asked for a specific width.
- `shotlog open -c <ch> --pup --swap` / `--skinny --swap` is the switch verb.
- The viewer is ONE single-window app with ITS OWN channel tabs (read-captures
pinned first). You never open one surface per channel anymore - plain
`shotlog open -c <ch>` opens/deep-links THE app with that tab active.
## ONE window rule: never open your own shotlog surface
The human runs ONE shotlog window (webview or pup) with a tab per channel
INSIDE the app. `adom-shotlog open -c CH` handles this: if the app is already
open anywhere, it switches that window's internal tab (the human sees a toast
explaining the consolidation) and opens nothing new. Only when NOTHING is open
does it create a surface, on the human's default canvas.
NEVER load the viewer URL into your own pup session or a new window/tab
(`browser_open_window`/`browser_open_tab` with a /log/ URL) - that gives the
human a confusing second shotlog. Always go through `adom-shotlog open`.
## Inject - one command
```bash
adom-shotlog inject -c CHANNEL -d "what you see and why" -s SOURCE /path/to/image.png
```
adom-shotlog **always** records the original capture resolution - and **rejects any
inject that doesn't establish it**, so the viewer never shows "orig missing".
**Resizing before inject is fine and expected.** Oversized images crash Claude's
vision analysis, so most Adom screenshot verbs return a shrunk copy on purpose.
adom-shotlog doesn't fight that - it just requires the original size to travel with
the shot. Ways through:
1. **Pre-shrunk + the original dimensions** (the common case - your screenshot
step reports the pre-resize W×H, you pass them through).
```bash
adom-shotlog inject -c proj -d "…" --orig-w 1920 --orig-h 1080 /tmp/shrunk.png
```
2. **Pre-shrunk + the original image** - adom-shotlog measures it (keep the full-res
file around for this).
```bash
adom-shotlog inject -c proj -d "…" --orig-file /tmp/fullres.png /tmp/shrunk.png
```
3. **`--native`** - assert the image is at original capture resolution (never
resized). Use only when true.
4. **Hand it the full-res image** - adom-shotlog resizes anything over 1500px (either
edge) down to 1400px and records the original automatically.
5. **`--meta <ad-response.json>`** - ingest an Adom Desktop screenshot response
(see below). The original W×H comes from the response's `origWidth/origHeight`,
so this satisfies the original-resolution rule on its own.
**The rejection:** if FILE is ≤1500px (so adom-shotlog won't resize it) and you give
none of `--orig-w`+`--orig-h` / `--orig-file` / `--native` / `--meta`, the inject is
refused with exit 1 and a hint.
### HTTP-only inject: `POST /api/paste`
No CLI on your machine? POST a data URL. **Declare your `source`** (same values as
`-s`: `pup`, `ad-window`, `hd`, ...); undeclared defaults to `api` (a neutral,
chip-less deliberate inject). Do NOT claim `clipboard` - that chip means a HUMAN
pasted with Ctrl+V, and only the viewer's paste handler sends it. `thread` and
`context` are accepted too and power the viewer's attribution and filters.
```bash
curl -s -X POST localhost:8820/api/paste -H 'Content-Type: application/json' -d '{
"channel":"proj", "dataUrl":"data:image/png;base64,...",
"description":"what you see and why", "source":"pup",
"thread":"my thread title", "context":"the prompt being served"}'
```
## `--meta` - Adom Desktop child-window arrays
As of adom-desktop 1.8.177, `desktop_screenshot_window {hwnd}` returns the primary
window image **plus its child windows** - the owned dropdowns, confirm bubbles, and
native dialogs - each captured separately, plus coordMap / DPI / screen metadata.
Pass that whole response JSON via `--meta`:
```bash
adom-shotlog inject -c CH -d "checkout form with autofill open" primary.png --meta ad-response.json
```
adom-shotlog will:
- store the coordMap / DPI / screenRect / screen-resolution as a sidecar (revealed
in the viewer's **⌗ capture metadata** panel, copyable as JSON),
- read each child-window PNG by its `localSafePath`, save it next to the primary,
and attach it - the card shows a **+N child windows** badge and a `child windows ▸`
row rendering each child window inline (no zoom),
- derive the original resolution from `origWidth/origHeight`.
The response shape it reads (extra fields are ignored, all optional except a
primary image + `screenshots[]` for child windows):
```json
{
"shotId": "…", "origWidth": 1920, "origHeight": 1200,
"width": 1400, "height": 875,
"coordMap": { "screenRect": {…}, "dpiScale": 1.25, "imageToScreen": {…}, "screenResolution": "1920x1200" },
"ownedPopupCount": 2,
"screenshots": [
{ "localSafePath": "/path/popup1.png", "hwnd": 12345, "title": "Autofill", "kind": "dropdown", "coordMap": {…} }
]
}
```
Plain single-image injects (no `--meta`) work exactly as before - child windows /
metadata are additive.
| Flag | Required | Description |
|------|----------|-------------|
| `-c` | yes | Channel name (groups screenshots by task) |
| `-d` | yes | **Why this screenshot was taken** (becomes the filename, min 6 chars) |
| `-s` | no | Source: `pup_screenshot`, `desktop_screenshot_window`, `hd_shot`, etc. (default `cli`) |
| `--orig-file` | for pre-shrunk images | Path to the original full-res image; measured |
| `--orig-w` / `--orig-h` | for pre-shrunk images | Original dims (alternative to `--orig-file`) |
| `--native` | for unresized small images | Assert FILE is at original resolution |
| `--thread` | no | Which AI thread produced this shot (human-readable). The read-hook fills it automatically from the session transcript; pass your thread's name when injecting deliberately so the human sees WHO. |
| `--meta` | optional | Adom Desktop screenshot-response JSON: child windows + coordMap + orig dims |
| `FILE` | yes | Primary image path (use `-` for stdin) |
## CRITICAL: Always write great descriptions
The `-d` description becomes the filename (slugified) and is shown prominently on
every gallery card. Make it specific.
**Bad:** `"screenshot"`, `"test"`, `"after fix"`
**Good:** `"PCB layout after routing power traces"`, `"Checkout form with autofill dropdown open"`
## The viewer
```bash
adom-shotlog open -c my-project --skinny # slim 20% webview panel (ambient watching)
adom-shotlog open -c my-project --pup # FULL-BROWSER pup canvas (real zoom); --swap closes the webview
```
The human picks the canvas: webview for ambient, pup when they want images big
(zoom in the skinny panel cannot exceed the panel). `--pup --swap` is the
one-command switch; the broker auto-flashes the pup taskbar on updates.
- **Gallery**, newest-first. Each card: description, timestamp, size (shown as
`1400×788 · orig 1920×1080` when they differ), KB, source, a **+N child windows**
badge when the capture grabbed child windows, and an **ⓘ meta** chip when it has
capture metadata. Child windows show inline; tap a card to zoom + see the copyable
metadata panel.
- **Mobile-ready**, **green LED** = a viewer is connected, **Ctrl+V** pastes a
screenshot, **live updates** over WebSocket.
> **Do not place a shotlog tab on the VS Code pane.** Prefer computing a
> non-VS-Code `--panel-id` and calling `adom-cli hydrogen workspace add-tab`.
### Force-open safety net
If a screenshot is injected into a channel with **no viewer connected**, the
server auto-opens a viewer tab so the shot is never logged invisibly.
## CRITICAL: verify the HUMAN is actually seeing it
Injecting a shot is not the job - the human *seeing* it is. shotlog tells you
whether a live viewer is connected, and on which **surface** (webview / pup /
mobile / browser), **in the inject response itself** - no guessing, no extra turns:
- **On-screen:** `OK: 1 of 1 viewer(s) ON-SCREEN [pup] - a human can see this shot.`
- **Open but backgrounded:** `WARN: 1 viewer(s) connected … but ALL are
BACKGROUNDED / minimized - the human is NOT looking right now.` Bring the pup
window / webview tab / phone to the FOREGROUND, then re-check.
- **Nobody watching:** `WARN: NO live viewer is connected - the human is NOT
seeing this shot.` A viewer is force-opened as a fallback, but **do not assume**
it worked. Open the viewer on the human's actual surface and re-check.
The signal is real: each viewer holds a WebSocket open and reports its **surface**
(webview/pup/mobile/browser) AND whether it's actually **on-screen** (Page
Visibility) vs a background tab / minimized window.
**Gate programmatically instead of burning turns** - `shotlog viewers` exit codes:
`0` = a human has it on-screen, `4` = connected but all backgrounded, `3` = nobody:
```bash
shotlog viewers -c my-project # 0=on-screen, 4=backgrounded, 3=none; prints surfaces
shotlog viewers -c my-project --url "$URL" # also HTTP-checks the endpoint you opened
```
Typical flow: `shotlog open -c ch` (or load it in pup / on the phone) → keep
injecting → if inject ever reports 0 viewers, stop and re-establish the viewer.
The viewer reports its surface automatically; when *you* open it in pup, append
`?surface=pup` to the URL so presence labels it correctly.
## Other commands
```bash
adom-shotlog serve # start the server (port 8820)
adom-shotlog health # check server status
adom-shotlog viewers -c CHANNEL # is a human watching? exit 0=yes, 3=no (+ surfaces)
adom-shotlog resize FILE # rarely needed - inject auto-resizes
adom-shotlog install # install binary + this skill + completions + `shotlog` alias
```
## Channel naming
Use the project or task name: `board-review`, `hd-setup-steps`, `debug-session`.