---
name: adom-shotlog-surfacing
description: "How shotlog should SURFACE a new shot to the human - gently, per surface, without stealing focus. Use when: an AI is injecting shots and deciding how to notify the human, a shot lands unseen, choosing between pup and webview, setting up the preferred slim right-side shotlog panel, or a viewer is backgrounded. Trigger words: shotlog surfacing, don't foreground, taskbar flash, blink the tab, skinny panel, shotlog notification, shotlog update, backgrounded viewer, auto-switch shotlog, shove in my face."
---

# adom-shotlog - surfacing updates (don't shove it in the human's face)

Injecting a shot is not the goal - the human **noticing** it is. But noticing is
not the same as being **interrupted**. The rule for every update:

> **Never steal the human's foreground.** Surface the shot on the surface they're
> already using, as gently as that surface allows.

shotlog handles most of this for you in the server; your job is to pick the right
surface and use the gentle nudge, never a focus-steal.

## The surfaces, and how each should update

### Webview - the preferred layout: a slim right-side panel

The nicest setup: a **~20%-wide webview panel pinned to the right** of the
Hydrogen page. It auto-updates over WebSocket, and you can keep **several shotlog
channels as tabs in that one panel**. Open it that way:

```bash
shotlog open -c my-project --skinny            # slim right panel (~20% width)
shotlog open -c other-task --skinny            # stacks as another tab in the same panel
shotlog open -c my-project --skinny --ratio 0.25   # custom width fraction
```

On a new shot, the broker pulls that channel's tab to the front IN ITS OWN PANE
(`workspace active-tab`, never a tab move) and paints an **orange update alert**
on it. It never touches other panes, so the human's typing is never interrupted.

### Pup - auto tab-switch + gentle taskbar flash, never foreground (AUTOMATIC)

On every new shot the broker also SWITCHES the shotlog pup window's active tab
to the updated channel (the pup mirror of the webview pull-to-front) - a shot
never lands in a buried background tab. Tab switching happens INSIDE our own
window; window focus/state is never touched.


Open the pup canvas with `shotlog open -c <ch> --pup`: one shared pup window
(session `shotlog`), a tab per channel, idempotent. The URL carries
`?surface=pup&session=shotlog`, the viewer reports that session over its
WebSocket, and on every new shot the broker AUTOMATICALLY runs
`adom-desktop browser_alert_window {sessionId}` - the orange taskbar flash -
debounced per session. Set `SHOTLOG_AD_TARGET=<desktop>` on the server when more
than one Adom Desktop is connected. No AI action needed. If a pup viewer has no
`session=` handle the broker can't flash it; `inject` hints you to do it
yourself (`adom-desktop browser_alert_window`) - never raise/foreground the
window. The viewer's page title also shows a `● N` unseen-shot count.

### Phone / plain browser - in-page title badge

When the tab is backgrounded, the viewer sets its title to `● N · <channel>`, so
the count of unseen shots shows in the browser/phone tab strip. Nothing to do.

### Nobody watching - force-open fallback

If **no** viewer is connected, the server force-opens a webview as a safety net
(so a shot never lands invisibly). Opt out with `SHOTLOG_NO_FORCE_OPEN=1` for
users who never want anything auto-opened.

## Switching canvases (webview <-> pup): one surface at a time

The human picks the canvas, and they usually want exactly ONE per channel:

- **Skinny webview** = ambient watching next to their editor. But its zoom can
  never exceed the panel width, so images stay small.
- **Pup** = the full-browser canvas: zoom uses nearly the whole screen, text is
  readable. When the human says "show it in pup", they want the BIG view.

The HUMAN can switch themselves: every viewer header has a canvas toggle
button (`pup ⧉` in a webview, `webview ◱` in pup). It checks pup availability
via the broker (`/api/canvas-check`) and shows install guidance when Adom
Desktop is missing. So if the human asks you to switch, you may also just tell
them about the button. You do the switch in ONE command, never by hand-rolling
browser calls:

```bash
shotlog open -c <ch> --pup --swap    # open the pup canvas AND close the webview tab
shotlog open -c <ch> --pup           # pup alongside (webview stays connected)
shotlog open -c <ch> --skinny        # back to the slim webview panel
```

`--pup` uses ONE shared pup window (session `shotlog`) with a tab per channel,
and is idempotent: re-running switches to the existing tab. The URL carries
`?surface=pup&session=shotlog`, so the broker flashes that window's taskbar
orange automatically on every update. If a channel ends up on BOTH surfaces the
broker's inject hints call it out ("did you mean to?"): resolve with `--swap`.

## Decision flow for an AI injecting a shot

1. `inject` - read its presence report: how many viewers, which **surfaces**, and
   how many are **on-screen** (visible) vs backgrounded.
2. If a viewer is **on-screen** → done; it's genuinely visible. Don't nudge.
3. If viewers exist but are **backgrounded** → nudge gently for that surface
   (webview: server already blinked; pup: `browser_alert_window`); **never
   foreground**.
4. If **nobody** is watching → open the preferred surface:
   `shotlog open -c <ch> --skinny`, or ask the human to load the URL / open it on
   their phone. Then re-verify.
5. Gate precisely with `shotlog viewers -c <ch>` - exit `0` = on-screen,
   `4` = open but backgrounded, `3` = nobody.

## The exact gentle-cue verbs (Adom Desktop)

Use these to signal an update **without foregrounding**. Never use
`desktop_bring_to_front` / raise / SendInput-click to force a window forward
just to show a shot.

| Surface | Gentle cue | Verb |
|---------|-----------|------|
| Webview tab | Blink the tab (auto-scrolls into view, no focus steal) | `adom-cli hydrogen alert set --name "Shots: <ch>"` - shotlog does this for you |
| Webview (backgrounded editor tab) | System notification | `adom-cli hydrogen notify …` |
| Pup / any window | Orange taskbar flash (FlashWindowEx) | `adom-desktop browser_alert_window {sessionId}` (pup) or `adom-desktop desktop_flash_window {hwnd}` (any window) |
| Any window | Glanceable taskbar status (progress bar + overlay icon) | `adom-desktop desktop_taskbar {hwnd, …}` |

## Finding & targeting the right window (READ-ONLY, no foreground)

You rarely need this for shotlog itself (the server handles surfacing), but when
you must locate a viewer window to flash/screenshot it, these AD verbs do it
without stealing focus - do NOT shell out to PowerShell, AD already exposes it:

| Need | Verb | Notes |
|------|------|-------|
| List all top-level windows | `desktop_list_windows` | each entry: `hwnd`, `title`, `className`, `rect`, `z` (0 = topmost) |
| Resolve an hwnd by title/class | `desktop_find_window {title\|class}` | topmost match wins |
| Parent → child subtree of a window | `desktop_ui_tree {hwnd}` | the "window list with parent/child": nested nodes with `role`, `rect{centerX,centerY}`, `automationId`, `invokable/settable` - each individually targetable in the BACKGROUND. `maxDepth`/`maxNodes`/`scope:"popups"` to narrow. |
| Capture a specific window (even occluded) | `desktop_screenshot_window {hwnd}` | WGC - grabs the window even if it's behind others; won't fire if the window is truly not visible |
| Change window state w/o foregrounding | `desktop_set_window_state` | minimize/maximize/restore without raising |

Parent/child example (real): `desktop_list_windows` gives the top-level Edge
window `hwnd=5769356`; `desktop_ui_tree {hwnd:5769356}` returns its child
subtree (`[Window] → [Pane] → [Pane] …`), each node with a screen-px rect you can
target - no foreground, no cursor move.

## Environment knobs

| Var | Effect |
|-----|--------|
| `SHOTLOG_MAX_SHOTS` | Per-channel disk cap (default 200): oldest shot's files are deleted as new ones arrive. |
| `SHOTLOG_RETENTION_DAYS` | Prune channels idle this many days (default 30, 0 = off), at startup + every 6h. |
| `SHOTLOG_NO_FORCE_OPEN=1` | Never auto-open a viewer, even when nobody is watching. |

## The one rule, restated

Gentle by surface: **webview → blink the tab**, **pup → orange taskbar flash**,
**phone/browser → title badge**, **nobody → force-open fallback**. Foregrounding
the human's window is never the answer.
