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