app
Shotlog - Screenshot Log
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 really watching and on which surface, and ingests Adom Bridge child-window arrays via --meta.
← Commit history
Publish 0.25.0
12 files changed
+116−98
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.24.6"+version = "0.25.0" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.24.6"+version = "0.25.0" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1@@ -1 +1 @@-0.24.6+0.25.0
bin/adom-shotlog⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+14@@ -367,3 +367,17 @@ constant snap-back annoying: "rely on the user deciding what their webview column width is." All 3 open_webview callers (CLI open, force_open_viewer, force_open_pup fallback) thread the pref through notify_surface. An explicit CLI --ratio still counts as autosize (deliberate size request).++## The 2026-08 rename: adom-desktop -> adom-bridge-cli, browser_* -> pup_* (0.25.0)+The bridge renamed: CLI `adom-desktop` -> `adom-bridge-cli`, verbs `browser_*` ->+`pup_*` (browser_readiness/prewarm -> `pup_status`; desktop_* OS verbs UNCHANGED).+This FUNCTIONALLY BROKE the broker (open.rs/server.rs shelled to the dead+`adom-desktop`), which is why pup went flaky. Branding per adom/definitions:+AD/Adom Desktop -> **Bridge** (formal Adom Bridge, ab); ABE -> **NB** (Adom+Native Browser); HD/Hydrogen Desktop -> **Hydrogen** (ah). Chips: AD·window ->+Bridge·window, AD·screen -> Bridge·screen, ABE -> NB, HD -> Hydrogen·self (HD and+the workspace "hydrogen" chip both became Hydrogen, so the app self-shot is+Hydrogen·self). Kept INTERNAL source keys (ad-window/abe/hd) so existing shots+still filter; only labels changed. The read-hook path match also updated:+screenshots/adom-desktop/ -> adom-bridge/. "Preferences" dialog -> "Settings"+(ah calls it that). Env var SHOTLOG_AD_TARGET kept (internal, not user-facing).
hooks/shotlog-on-read.sh+6−4@@ -59,13 +59,15 @@ if [ -n "$TP" ] && [ -f "$TP" ]; then fi # PROVENANCE: infer the capture endpoint from the path bucket, and attach the-# AD sidecar (coordMap, child windows, orig size) when this is an AD capture.+# Adom Bridge sidecar (coordMap, child windows, orig size) when this is a+# Bridge capture. NOTE the bridge renamed its on-disk dir: adom-desktop ->+# adom-bridge (match both so old + new shots both attribute correctly). SRC="read-hook" META_ARGS="" case "$f" in- */screenshots/adom-desktop/window/*) SRC="read-hook/ad-window" ;;- */screenshots/adom-desktop/screen/*) SRC="read-hook/ad-screen" ;;- */adom-desktop-screenshots/*) SRC="read-hook/ad-window" ;;+ */screenshots/adom-bridge/window/*|*/screenshots/adom-desktop/window/*) SRC="read-hook/ad-window" ;;+ */screenshots/adom-bridge/screen/*|*/screenshots/adom-desktop/screen/*) SRC="read-hook/ad-screen" ;;+ */adom-bridge-screenshots/*|*/adom-desktop-screenshots/*) SRC="read-hook/ad-window" ;; *pup*|*/browser-shots/*) SRC="read-hook/pup" ;; */recordings/*|*hydrogen*) SRC="read-hook/hydrogen" ;; esac
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.24.6",+ "version": "0.25.0", "type": "app", "title": "Adom Shotlog", "description": "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 really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.24.6",+ "version": "0.25.0", "type": "app", "title": "Adom Shotlog", "description": "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 really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
src/html/index.html+47−45@@ -383,7 +383,7 @@ header { } .toast.show { opacity: 1; } -/* In-app helper dialog (e.g. "pup needs Adom Desktop") - NEVER a native+/* In-app helper dialog (e.g. "pup needs Adom Bridge") - NEVER a native alert/confirm. Backdrop + card, dismiss via X / backdrop / Esc. */ .helper { position: fixed; inset: 0; background: rgba(0,0,0,.72); display: none; align-items: center; justify-content: center; z-index: 400; padding: 14px; }@@ -495,7 +495,7 @@ header { <button class="hdr-btn" id="canvasBtn" style="display:none"></button> <button class="hdr-btn icon-only" id="toTopBtn" data-tooltip="Scroll to newest (top)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg></button> <button class="hdr-btn icon-only" id="filterBtn" data-tooltip="Filter shots: child-window arrays, capture endpoint, safe/full pairs, direct vs auto-captured."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 3H2l8 9.46V19l4 2v-8.54L22 3z"/></svg></button>- <button class="hdr-btn icon-only" id="prefsBtn" data-tooltip="Preferences: the auto-capture read-hook, and whether channels default to webview or pup."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg></button>+ <button class="hdr-btn icon-only" id="prefsBtn" data-tooltip="Settings: the auto-capture read-hook, and whether channels default to webview or pup."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg></button> <button class="hdr-btn danger" id="clearBtn" data-tooltip="Delete ALL screenshots in this channel (cannot be undone)">clear</button> <span class="ver-pill" id="verPill" tabindex="0" data-tooltip="Loading version…">v…</span> <span class="ws-led" id="wsLed" data-tooltip="WebSocket: grey = disconnected, green = live. While green the server knows you're watching and won't force-open a duplicate tab on new shots."></span>@@ -517,21 +517,21 @@ header { <div class="helper" id="pupHelp" role="dialog" aria-modal="true" aria-label="Pup setup help"> <div class="helper-card"> <button class="h-close" id="pupHelpClose" data-tooltip="Close">✕</button>- <div class="h-title" id="pupHelpTitle">Pup needs Adom Desktop</div>+ <div class="h-title" id="pupHelpTitle">Pup needs Adom Bridge</div> <div class="h-body" id="pupHelpBody"></div> <div class="h-lbl">copy this prompt into any AI thread and it will fix it for you</div> <div class="h-prompt" id="pupHelpPrompt"></div> <div class="h-actions"> <button class="h-btn" id="pupHelpCopy">⧉ Copy prompt</button> <button class="h-btn secondary" id="pupHelpDismiss">Close</button>- <a class="h-link" href="https://wiki.adom.inc/adom/adom-desktop" target="_blank" rel="noopener">Adom Desktop wiki ↗</a>+ <a class="h-link" href="https://wiki.adom.inc/adom/adom-bridge" target="_blank" rel="noopener">Adom Bridge wiki ↗</a> </div> </div> </div>-<div class="helper" id="prefsDlg" role="dialog" aria-modal="true" aria-label="Preferences">+<div class="helper" id="prefsDlg" role="dialog" aria-modal="true" aria-label="Settings"> <div class="helper-card"> <button class="h-close" id="prefsClose">✕</button>- <div class="h-title">Preferences</div>+ <div class="h-title">Settings</div> <div class="pref-row"> <div class="pref-info"> <div class="pref-name">Auto-capture what AIs read</div>@@ -559,7 +559,7 @@ header { <div class="pref-row"> <div class="pref-info"> <div class="pref-name">Default canvas</div>- <div class="pref-desc">Where shotlog auto-opens a channel when nobody is watching. A channel you explicitly opened in pup or webview keeps ITS choice; this covers everything else. Closing a pup window reverts that channel to webview. Pup needs Adom Desktop. Pick the other canvas while you're watching and closing this dialog moves your shotlog there too, after a 3-2-1 countdown you can cancel.</div>+ <div class="pref-desc">Where shotlog auto-opens a channel when nobody is watching. A channel you explicitly opened in pup or webview keeps ITS choice; this covers everything else. Closing a pup window reverts that channel to webview. Pup needs Adom Bridge. Pick the other canvas while you're watching and closing this dialog moves your shotlog there too, after a 3-2-1 countdown you can cancel.</div> <div class="ai-chip"><span class="lbl">or say to your AI</span>make shotlog default to pup</div> </div> <div class="seg" id="prefCanvas">@@ -613,19 +613,19 @@ header { <div class="h-title">Filters</div> <div class="pref-info"><div class="pref-name">What kind of shot</div></div> <div class="frow">- <button class="fchip" data-f="arrays" data-tooltip="Only shots carrying an Adom Desktop CHILD-WINDOW ARRAY (parent hwnd + its owned dropdowns/dialogs).">child-window arrays</button>+ <button class="fchip" data-f="arrays" data-tooltip="Only shots carrying an Adom Bridge CHILD-WINDOW ARRAY (parent hwnd + its owned dropdowns/dialogs).">child-window arrays</button> <button class="fchip" data-f="full" data-tooltip="Only shots stored as a SAFE/FULL pair (a full-resolution original is viewable).">safe/full pairs</button> <button class="fchip" data-f="direct" data-tooltip="Only shots an AI deliberately injected (not auto-captured by the read-hook).">direct injects</button> <button class="fchip" data-f="auto" data-tooltip="Only auto-captures from the read-hook (images AIs read).">auto-captured</button> </div> <div class="pref-info" style="margin-top:8px"><div class="pref-name">Capture endpoint</div></div> <div class="frow" id="provRow">- <button class="fchip" data-p="ad-window" data-tooltip="Adom Desktop window screenshots (desktop_screenshot_window).">AD·window</button>- <button class="fchip" data-p="ad-screen" data-tooltip="Adom Desktop full-screen screenshots.">AD·screen</button>- <button class="fchip" data-p="pup" data-tooltip="pup (Puppeteer browser bridge) page captures.">pup</button>- <button class="fchip" data-p="hydrogen" data-tooltip="Hydrogen workspace screenshots.">hydrogen</button>- <button class="fchip" data-p="abe" data-tooltip="Adom Browser Extension captures (the user's real browser).">ABE</button>- <button class="fchip" data-p="hd" data-tooltip="Hydrogen Desktop's built-in screenshot feature.">HD</button>+ <button class="fchip" data-p="ad-window" data-tooltip="Adom Bridge window screenshots (desktop_screenshot_window).">Bridge·window</button>+ <button class="fchip" data-p="ad-screen" data-tooltip="Adom Bridge full-screen screenshots.">Bridge·screen</button>+ <button class="fchip" data-p="pup" data-tooltip="pup (Puppeteer bridge on Adom Bridge) page captures.">pup</button>+ <button class="fchip" data-p="hydrogen" data-tooltip="Adom Hydrogen workspace screenshots.">Hydrogen</button>+ <button class="fchip" data-p="abe" data-tooltip="NB (Adom Native Browser) captures of the user's real signed-in browser.">NB</button>+ <button class="fchip" data-p="hd" data-tooltip="Adom Hydrogen's built-in self-screenshot feature.">Hydrogen·self</button> <button class="fchip" data-p="crop" data-tooltip="Crops from larger originals.">crop</button> <button class="fchip" data-p="clipboard" data-tooltip="Human clipboard pastes (Ctrl+V).">paste</button> </div>@@ -643,7 +643,7 @@ header { <div class="m-img-wrap" id="mImgWrap"> <img id="mImg" alt=""> <button class="m-fill-btn" id="mFillBtn" aria-label="Fill window"- data-tooltip="Fill the window — zoom this image to a big lightbox. In Hydrogen Desktop it escapes the panel to 90% of the window; in a pup/browser it goes fullscreen. Esc to exit."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg></button>+ data-tooltip="Fill the window — zoom this image to a big lightbox. In Adom Hydrogen it escapes the panel to 90% of the window; in a pup/browser it goes fullscreen. Esc to exit."><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg></button> </div> <div class="m-popups" id="mPopups" style="display:none"></div> <div id="mNerd"></div>@@ -757,17 +757,19 @@ header { // to labeled chips explaining WHICH capture endpoint produced the image. const SRC_MAP={ 'read-hook':{label:'auto',cls:'auto',tip:'Auto-captured by the read-hook: an AI thread read this image and shotlog copied it here.'},- 'ad-window':{label:'AD·window',cls:'ad',tip:'Adom Desktop window screenshot (desktop_screenshot_window): one app window, captured even when backgrounded; may include child windows (dropdowns/dialogs).'},- 'desktop_screenshot_window':{label:'AD·window',cls:'ad',tip:'Adom Desktop window screenshot (desktop_screenshot_window): one app window, captured even when backgrounded; may include child windows.'},- 'ad-screen':{label:'AD·screen',cls:'ad',tip:'Adom Desktop full-screen screenshot (desktop_screenshot_screen): the whole monitor, popups and all.'},- 'desktop_screenshot_screen':{label:'AD·screen',cls:'ad',tip:'Adom Desktop full-screen screenshot: the whole monitor.'},- 'pup':{label:'pup',cls:'',tip:'Captured by pup (the Puppeteer browser bridge): a real rendered browser page, lossless.'},- 'pup_screenshot':{label:'pup',cls:'',tip:'Captured by pup (the Puppeteer browser bridge): a real rendered browser page, lossless.'},- 'hydrogen':{label:'hydrogen',cls:'',tip:'Captured by Hydrogen\'s internal workspace screenshotter.'},- 'abe':{label:'ABE',cls:'',tip:'Captured by the Adom Browser Extension driving the user\'s real signed-in browser.'},- 'nbrowser':{label:'ABE',cls:'',tip:'Captured by the Adom Browser Extension (nbrowser) in the user\'s real browser.'},- 'hd':{label:'HD',cls:'',tip:'Captured by Hydrogen Desktop\'s built-in screenshot feature.'},- 'hd_shot':{label:'HD',cls:'',tip:'Captured by Hydrogen Desktop\'s built-in screenshot feature.'},+ 'ad-window':{label:'Bridge·window',cls:'ad',tip:'Adom Bridge window screenshot (desktop_screenshot_window): one app window, captured even when backgrounded; may include child windows (dropdowns/dialogs).'},+ 'desktop_screenshot_window':{label:'Bridge·window',cls:'ad',tip:'Adom Bridge window screenshot (desktop_screenshot_window): one app window, captured even when backgrounded; may include child windows.'},+ 'ad-window-bridge':{label:'Bridge·window',cls:'ad',tip:'Adom Bridge window screenshot: one app window, captured even when backgrounded; may include child windows.'},+ 'ad-screen':{label:'Bridge·screen',cls:'ad',tip:'Adom Bridge full-screen screenshot (desktop_screenshot_screen): the whole monitor, popups and all.'},+ 'desktop_screenshot_screen':{label:'Bridge·screen',cls:'ad',tip:'Adom Bridge full-screen screenshot: the whole monitor.'},+ 'pup':{label:'pup',cls:'',tip:'Captured by pup (the Puppeteer bridge on Adom Bridge): a real rendered browser page, lossless.'},+ 'pup_screenshot':{label:'pup',cls:'',tip:'Captured by pup (the Puppeteer bridge on Adom Bridge): a real rendered browser page, lossless.'},+ 'hydrogen':{label:'Hydrogen',cls:'',tip:'Captured by Adom Hydrogen\'s internal workspace screenshotter.'},+ 'abe':{label:'NB',cls:'',tip:'Captured by NB (Adom Native Browser): the user\'s real signed-in browser.'},+ 'nbrowser':{label:'NB',cls:'',tip:'Captured by NB (Adom Native Browser, nbrowser): the user\'s real signed-in browser.'},+ 'nb':{label:'NB',cls:'',tip:'Captured by NB (Adom Native Browser): the user\'s real signed-in browser.'},+ 'hd':{label:'Hydrogen·self',cls:'',tip:'Captured by Adom Hydrogen\'s built-in self-screenshot feature.'},+ 'hd_shot':{label:'Hydrogen·self',cls:'',tip:'Captured by Adom Hydrogen\'s built-in self-screenshot feature.'}, 'crop':{label:'crop',cls:'',tip:'Cropped from a larger original image.'}, 'clipboard':{label:'paste',cls:'',tip:'Pasted from the clipboard by the human (Ctrl+V in the viewer).'}, };@@ -896,7 +898,7 @@ header { // Additional path: jump STRAIGHT to fullscreen, no modal first. The click // is the user gesture the Fullscreen API needs. const fillBtn=document.createElement('button'); fillBtn.className='card-fill-btn'; fillBtn.setAttribute('aria-label','Fullscreen this shot');- fillBtn.setAttribute('data-tooltip','Zoom STRAIGHT to fullscreen (skip the popup).\nIn Hydrogen Desktop it fills 90% of the window; in pup/browser it goes fullscreen. Esc / click / × to exit.');+ fillBtn.setAttribute('data-tooltip','Zoom STRAIGHT to fullscreen (skip the popup).\nIn Adom Hydrogen it fills 90% of the window; in pup/browser it goes fullscreen. Esc / click / × to exit.'); fillBtn.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>'; fillBtn.addEventListener('click', ev=>{ ev.stopPropagation(); fillWindow(imgUrl(e), e.description||e.filename); }); imgWrap.appendChild(fillBtn);@@ -1012,7 +1014,7 @@ header { $('mClose').addEventListener('click', e => { e.stopPropagation(); closeModal(); }); // ── Fill window: zoom the image to fill the surface via the standard- // Fullscreen API. In Hydrogen Desktop's native webview this grows the panel+ // Fullscreen API. In Adom Hydrogen's native webview this grows the panel // to a 90%-of-window lightbox (HD implements the standard API specially); in // a pup/browser it's true fullscreen; where the API is unavailable (some // phones), the .open class alone makes the lightbox a full fixed overlay.@@ -1230,7 +1232,7 @@ header { btn._ttHtml = toPup ? '<div class="tt-title">Show in pup</div>'+ '<div class="tt-p">A full browser window: real zoom, whole-screen images. Closes this slim webview tab.</div>'+- '<div class="tt-req">Requires Adom Desktop installed (pup is a default bridge that auto-installs with it).</div>'++ '<div class="tt-req">Requires Adom Bridge installed (pup is a default bridge that auto-installs with it).</div>'+ '<div class="tt-prompt"><span class="lbl">say to your AI</span>show me shotlog in pup</div>' : '<div class="tt-title">Show in webview</div>'+ '<div class="tt-p">The slim panel docked in Hydrogen. Closes this pup tab.</div>'+@@ -1260,13 +1262,13 @@ header { let opened=false; try{ showToast(toPup?'Opening the pup window…':'Opening the webview panel…', 3500);- // Stage 0: availability (pup needs Adom Desktop). `?simulate=no-pup` in+ // Stage 0: availability (pup needs Adom Bridge). `?simulate=no-pup` in // the viewer URL demos the not-installed path without touching anything. if(toPup){ busy('checking pup…'); const sim=new URLSearchParams(location.search).get('simulate'); const chk = sim==='no-pup'- ? {available:false, reason:'no-adom-desktop', simulated:true}+ ? {available:false, reason:'no-adom-bridge', simulated:true} : await fetch('api/canvas-check').then(r=>r.json()); if(!chk.available){ showPupHelp(chk); restore(); return; } }@@ -1323,26 +1325,26 @@ header { // ── Pup helper dialog: reason-specific explanation + a COPYABLE AI prompt ── function showPupHelp(chk){- const reason=chk.reason||'no-adom-desktop';+ const reason=chk.reason||'no-adom-bridge'; const C={- 'no-adom-desktop':{- title:'Pup needs Adom Desktop',- body:'Pup opens shotlog in a full browser window on YOUR computer, driven by Adom Desktop. Adom Desktop is not installed (or not reachable) yet - pup is a default bridge that installs automatically right after it.',- prompt:'Install Adom Desktop on my computer so shotlog can open pup windows. Start from wiki.adom.inc/adom/adom-desktop, walk me through the install, make sure it connects to my container, verify the pup bridge with `adom-desktop browser_readiness`, then show my shotlog channel "'+channel+'" in pup.'},+ 'no-adom-bridge':{+ title:'Pup needs Adom Bridge',+ body:'Pup opens shotlog in a full browser window on YOUR computer, driven by Adom Bridge. Adom Bridge is not installed (or not reachable) yet - pup is a default bridge that installs automatically right after it.',+ prompt:'Install Adom Bridge on my computer so shotlog can open pup windows. Start from wiki.adom.inc/adom/adom-bridge, walk me through the install, make sure it connects to my container, verify the pup bridge with `adom-bridge-cli pup_status`, then show my shotlog channel "'+channel+'" in pup.'}, 'desktop-not-connected':{- title:'Adom Desktop is not connected',- body:'Adom Desktop is installed but not linked to this container right now. Launch it on your computer and it reconnects automatically.',- prompt:'My Adom Desktop is installed but not connected to this container. Help me get it running and linked, verify with `adom-desktop browser_readiness`, then show my shotlog channel "'+channel+'" in pup.'},+ title:'Adom Bridge is not connected',+ body:'Adom Bridge is installed but not linked to this container right now. Launch it on your computer and it reconnects automatically.',+ prompt:'My Adom Bridge is installed but not connected to this container. Help me get it running and linked, verify with `adom-bridge-cli pup_status`, then show my shotlog channel "'+channel+'" in pup.'}, 'pup-not-ready':{ title:'Pup bridge is still warming up',- body:'Adom Desktop is connected, but the pup browser bridge is not ready yet. It auto-installs on first use - this usually resolves in under a minute.',- prompt:'The pup bridge on my Adom Desktop is not ready. Run `adom-desktop browser_prewarm`, verify with `adom-desktop browser_readiness`, then show my shotlog channel "'+channel+'" in pup.'},+ body:'Adom Bridge is connected, but the pup browser bridge is not ready yet. It auto-installs on first use - this usually resolves in under a minute.',+ prompt:'The pup bridge on my Adom Bridge is not ready. Run `adom-bridge-cli pup_status`, verify with `adom-bridge-cli pup_status`, then show my shotlog channel "'+channel+'" in pup.'}, 'multiple-desktops':{- title:'More than one Adom Desktop is connected',+ title:'More than one Adom Bridge is connected', body:'The broker cannot tell which computer should host the pup window. It needs a target.',- prompt:'Multiple Adom Desktops are connected to my container. Figure out which one I am actually using, set SHOTLOG_AD_TARGET=<that name> on my shotlog broker, then show my shotlog channel "'+channel+'" in pup.'}+ prompt:'Multiple Adom Bridges are connected to my container. Figure out which one I am actually using, set SHOTLOG_AD_TARGET=<that name> on my shotlog broker, then show my shotlog channel "'+channel+'" in pup.'} };- const c=C[reason]||C['no-adom-desktop'];+ const c=C[reason]||C['no-adom-bridge']; $('pupHelpTitle').textContent=(chk.simulated?'(simulated) ':'')+c.title; $('pupHelpBody').textContent=c.body+(chk.hint&&!chk.simulated?' ('+chk.hint+')':''); $('pupHelpPrompt').textContent=c.prompt;@@ -1417,7 +1419,7 @@ header { const r=await fetch('api/prefs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(x=>x.json()); if(r.ok){ renderPrefs(r.prefs); showToast(okMsg, 3500); } else showToast('could not save: '+(r.error||'unknown'), 6000);- }catch(e){ showToast('could not save preferences: '+e.message, 6000); }+ }catch(e){ showToast('could not save settings: '+e.message, 6000); } } hook.addEventListener('click',()=>{ const now=hook.getAttribute('aria-checked')==='true';
src/open.rs+17−17@@ -198,7 +198,7 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { let url = format!("{proxy_uri}log/?surface=pup&session=shotlog&channel={channel}"); let ad = |args: &[&str]| -> (bool, String) {- let mut cmd = Command::new("adom-desktop");+ let mut cmd = Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }@@ -222,11 +222,11 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { // The bridge's tab REGISTRY can outlive the actual Chrome window (a closed // window leaves GHOST tab records; verbs then fail "browser is not // connected"). So: try the cheap path, but ANY failure of switch/open_tab- // falls through to browser_open_window, which revives a dead session.+ // falls through to pup_open_window, which revives a dead session. let open_window = |ad: &dyn Fn(&[&str]) -> (bool, String)| -> bool { let win_json = format!("{{\"sessionId\":\"shotlog\",\"profile\":\"shotlog\",\"url\":\"{url}\"}}");- let (mut ok, mut out) = ad(&["browser_open_window", &win_json]);- // A COLD BRIDGE is not a failure: AD auto-starts Puppeteer+Chrome on+ let (mut ok, mut out) = ad(&["pup_open_window", &win_json]);+ // A COLD BRIDGE is not a failure: Bridge auto-starts Puppeteer+Chrome on // demand (10-15s, sometimes more after a reboot). The response says // status:"bridge_starting"/pending:true - WAIT and retry instead of // surrendering (this misread stranded humans on the webview fallback@@ -235,16 +235,16 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { while !ok && waited < 9 && (out.contains("bridge_starting") || out.contains("\"pending\":true") || out.contains("\"pending\": true")) {- eprintln!("Hint: pup bridge is cold - Adom Desktop is auto-starting Chrome, waiting 10s (attempt {}/9)...", waited + 1);+ eprintln!("Hint: pup bridge is cold - Adom Bridge is auto-starting Chrome, waiting 10s (attempt {}/9)...", waited + 1); std::thread::sleep(std::time::Duration::from_secs(10)); waited += 1;- let r = ad(&["browser_open_window", &win_json]);+ let r = ad(&["pup_open_window", &win_json]); ok = r.0; out = r.1; } ok };- let (listed, list_out) = ad(&["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]);+ let (listed, list_out) = ad(&["pup_list_tabs", "{\"sessionId\":\"shotlog\"}"]); let mut opened = false; let mut via_window = false; if !listed || list_out.contains("session_not_found") {@@ -258,10 +258,10 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { })); if let Some(tid) = existing { let sw = format!("{{\"sessionId\":\"shotlog\",\"tabId\":\"{tid}\"}}");- opened = ad(&["browser_switch_tab", &sw]).0;+ opened = ad(&["pup_switch_tab", &sw]).0; } else { let tab_json = format!("{{\"sessionId\":\"shotlog\",\"url\":\"{url}\"}}");- opened = ad(&["browser_open_tab", &tab_json]).0;+ opened = ad(&["pup_open_tab", &tab_json]).0; } if !opened { opened = open_window(&ad);@@ -288,8 +288,8 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { } if !opened {- eprintln!("ERROR: could not open the pup window (is adom-desktop connected?).");- eprintln!("Hint: check `adom-desktop targets`; with multiple desktops set SHOTLOG_AD_TARGET=<name>.");+ eprintln!("ERROR: could not open the pup window (is Adom Bridge connected?).");+ eprintln!("Hint: check `adom-bridge-cli targets`; with multiple desktops set SHOTLOG_AD_TARGET=<name>."); std::process::exit(1); } println!("OK: pup canvas open for '{channel}' (tab in the shared 'shotlog' pup window).");@@ -330,7 +330,7 @@ pub fn broker_has_pup_viewer(port: u16) -> bool { /// Used when swapping a channel back to the webview canvas. pub fn close_pup_tab(channel: &str) -> bool { let ad = |args: &[&str]| -> (bool, String) {- let mut cmd = Command::new("adom-desktop");+ let mut cmd = Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }@@ -342,14 +342,14 @@ pub fn close_pup_tab(channel: &str) -> bool { }; let _ = &channel; let needle = "/log/".to_string();- let (ok, out) = ad(&["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]);+ let (ok, out) = ad(&["pup_list_tabs", "{\"sessionId\":\"shotlog\"}"]); if !ok { return false; } let Ok(v) = serde_json::from_str::<serde_json::Value>(&out) else { return false }; let Some(tab) = v.get("tabs").and_then(|t| t.as_array()).and_then(|tabs| { tabs.iter().find(|t| t.get("url").and_then(|u| u.as_str()).map(|u| u.contains(&needle)).unwrap_or(false)) }) else { return false }; let Some(tid) = tab.get("tabId").or_else(|| tab.get("id")).and_then(|x| x.as_str()) else { return false };- ad(&["browser_close_tab", &format!("{{\"sessionId\":\"shotlog\",\"tabId\":\"{tid}\"}}")]).0+ ad(&["pup_close_tab", &format!("{{\"sessionId\":\"shotlog\",\"tabId\":\"{tid}\"}}")]).0 } /// Switch the shared shotlog pup window's ACTIVE tab to this channel's tab@@ -357,7 +357,7 @@ pub fn close_pup_tab(channel: &str) -> bool { /// pull-to-front: a new shot should be what the user sees when they look. pub fn switch_pup_tab(channel: &str) -> bool { let ad = |args: &[&str]| -> (bool, String) {- let mut cmd = Command::new("adom-desktop");+ let mut cmd = Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }@@ -368,7 +368,7 @@ pub fn switch_pup_tab(channel: &str) -> bool { } }; let needle = format!("/log/{channel}/");- let (ok, out) = ad(&["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]);+ let (ok, out) = ad(&["pup_list_tabs", "{\"sessionId\":\"shotlog\"}"]); if !ok { return false; } let Ok(v) = serde_json::from_str::<serde_json::Value>(&out) else { return false }; let tabs = v.get("tabs").and_then(|t| t.as_array());@@ -377,5 +377,5 @@ pub fn switch_pup_tab(channel: &str) -> bool { // already front? let tid = tab.get("tabId").or_else(|| tab.get("id")).and_then(|x| x.as_str()).unwrap_or(""); if v.get("activeTabId").and_then(|a| a.as_str()) == Some(tid) { return true; }- ad(&["browser_switch_tab", &format!("{{\"sessionId\":\"shotlog\",\"tabId\":\"{tid}\"}}")]).0+ ad(&["pup_switch_tab", &format!("{{\"sessionId\":\"shotlog\",\"tabId\":\"{tid}\"}}")]).0 }
src/server.rs+26−26@@ -336,8 +336,8 @@ async fn get_channels(State(state): State<Arc<AppState>>) -> Json<serde_json::Va Json(serde_json::json!(rows)) } -/// Is the pup canvas available on this machine? Checks the adom-desktop CLI and-/// the pup bridge (browser_readiness), cached for 2 minutes. Powers the viewer's+/// Is the pup canvas available on this machine? Checks the adom-bridge-cli and+/// the pup bridge (pup_status), cached for 2 minutes. Powers the viewer's /// canvas-toggle button, including the "how to install" guidance. async fn canvas_check() -> Json<serde_json::Value> { let v = tokio::task::spawn_blocking(do_canvas_check)@@ -356,28 +356,28 @@ fn do_canvas_check() -> serde_json::Value { } } let result = (|| {- if std::process::Command::new("adom-desktop").arg("--version").output().is_err() {- return serde_json::json!({"available": false, "reason": "no-adom-desktop",- "hint": "pup needs Adom Desktop. Install it on your computer from wiki.adom.inc/adom/adom-desktop - the pup bridge is a default bridge that auto-installs right after it."});+ if std::process::Command::new("adom-bridge-cli").arg("--version").output().is_err() {+ return serde_json::json!({"available": false, "reason": "no-adom-bridge",+ "hint": "pup needs Adom Bridge. Install it on your computer from wiki.adom.inc/adom/adom-bridge - the pup bridge is a default bridge that auto-installs right after it."}); }- let mut cmd = std::process::Command::new("adom-desktop");+ let mut cmd = std::process::Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }- match cmd.args(["browser_readiness", "{}"]).output() {+ match cmd.args(["pup_status", "{}"]).output() { Ok(o) => { let text = format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr)); if text.contains("ambiguous_target") { serde_json::json!({"available": false, "reason": "multiple-desktops",- "hint": "More than one Adom Desktop is connected. Set SHOTLOG_AD_TARGET=<desktop name> where the broker runs."})+ "hint": "More than one Adom Bridge is connected. Set SHOTLOG_AD_TARGET=<desktop name> where the broker runs."}) } else if text.contains("\"ready\": true") || text.contains("\"ready\":true") || text.contains("\"ok\": true") { serde_json::json!({"available": true}) } else if text.contains("No clients") || text.contains("not connected") || text.contains("no desktop") { serde_json::json!({"available": false, "reason": "desktop-not-connected",- "hint": "Adom Desktop is installed but not connected right now. Launch it on your computer and it links to this container automatically."})+ "hint": "Adom Bridge is installed but not connected right now. Launch it on your computer and it links to this container automatically."}) } else { serde_json::json!({"available": false, "reason": "pup-not-ready",- "hint": "Adom Desktop is here but the pup bridge is not ready yet (it auto-installs on first use). Give it a moment, or run: adom-desktop browser_prewarm"})+ "hint": "Adom Bridge is here but the pup bridge is not ready yet (it auto-installs on first use). Give it a moment, or run: adom-bridge-cli pup_status"}) } } Err(e) => serde_json::json!({"available": false, "reason": "probe-failed",@@ -846,7 +846,7 @@ fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], bring_webview_forward(channel, port, alerts); // The webview FALLBACK is a lifeboat, not a destination. If the human's // canvas says pup and the only thing connected is a webview (classic:- // AD was offline, we fell back, AD came back), each new shot buys one+ // Bridge was offline, we fell back, Bridge came back), each new shot buys one // cooled-down attempt to bring pup up - and once pup CONNECTS, the // fallback webview closes. Staged like the UI switch: nothing closes // until the destination is live.@@ -877,7 +877,7 @@ fn force_open_pup(channel: &str, fallback_ratio: f64, autosize: bool) { } let channel = channel.to_string(); std::thread::spawn(move || {- // PATIENT: right after a laptop reboot Adom Desktop takes a minute to+ // PATIENT: right after a laptop reboot Adom Bridge takes a minute to // reconnect. Retry pup before surrendering to the webview fallback - // the user's pref says pup, so give pup a real chance to come up. let tries: u32 = std::env::var("SHOTLOG_PUP_RETRY_N").ok().and_then(|v| v.parse().ok()).unwrap_or(6);@@ -891,11 +891,11 @@ fn force_open_pup(channel: &str, fallback_ratio: f64, autosize: bool) { .status().map(|s| s.success()).unwrap_or(false); if ok { if attempt > 1 {- println!("OK: pup force-open for '{channel}' succeeded on attempt {attempt} (Adom Desktop is back).");+ println!("OK: pup force-open for '{channel}' succeeded on attempt {attempt} (Adom Bridge is back)."); } return; }- println!("WARN: pup force-open for '{channel}' attempt {attempt}/{tries} failed (Adom Desktop offline / reconnecting?){}",+ println!("WARN: pup force-open for '{channel}' attempt {attempt}/{tries} failed (Adom Bridge offline / reconnecting?){}", if attempt < tries { format!(" - retrying in {wait_s}s") } else { String::new() }); if attempt < tries { std::thread::sleep(std::time::Duration::from_secs(wait_s));@@ -965,12 +965,12 @@ fn flash_pup_session(session: &str) { } let session = session.to_string(); std::thread::spawn(move || {- let mut cmd = std::process::Command::new("adom-desktop");+ let mut cmd = std::process::Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); } let _ = cmd- .args(["browser_alert_window", &format!("{{\"sessionId\":\"{session}\"}}")])+ .args(["pup_alert_window", &format!("{{\"sessionId\":\"{session}\"}}")]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .status();@@ -1048,7 +1048,7 @@ struct InjectRequest { /// Caller-reported original dimensions (when the caller resized before sending). orig_w: Option<u32>, orig_h: Option<u32>,- /// Free-form capture metadata (Adom Desktop coordMap / dpiScale / screenRect /+ /// Free-form capture metadata (Adom Bridge coordMap / dpiScale / screenRect / /// screen resolution, shotId, ownedPopupCount, …). Stored verbatim on the entry. meta: Option<serde_json::Value>, /// Owned-popup screenshots. Each object carries an `image` data-URL plus any@@ -1216,7 +1216,7 @@ async fn post_shots( ch = req.channel)); } if source_str.contains("desktop_screenshot") && !had_meta && hint_gate(&format!("teach:meta:{}", req.channel), 2) {- hints.push("mini-skill [child-windows]: this source looks like an Adom Desktop window capture. AD returns the window's CHILD WINDOWS (owned dropdowns/dialogs, `screenshots[]`/`ownedPopupCount`) + coordMap in the SAME response — pass that whole JSON via `--meta` and they all render inline on this card, and the original resolution comes free.".into());+ hints.push("mini-skill [child-windows]: this source looks like an Adom Bridge window capture. Bridge returns the window's CHILD WINDOWS (owned dropdowns/dialogs, `screenshots[]`/`ownedPopupCount`) + coordMap in the SAME response — pass that whole JSON via `--meta` and they all render inline on this card, and the original resolution comes free.".into()); } if infos.iter().any(|i| i.surface == "pup" && i.session.is_none()) && hint_gate(&format!("teach:pup-session:{}", req.channel), 2) { hints.push("mini-skill [surfacing]: a pup viewer is watching WITHOUT a ?session= handle, so I cannot flash its taskbar for you. Open pup viewers with `?surface=pup&session=<pup sessionId>` and I flash that window orange automatically on every update.".into());@@ -1549,9 +1549,9 @@ async fn ws_all_connection(mut socket: WebSocket, state: Arc<AppState>) { tokio::time::sleep(std::time::Duration::from_secs(8)).await; if store.viewer_infos("_all").await.iter().any(|i| i.surface == "pup") { return; } let verdict: Option<bool> = tokio::task::spawn_blocking(move || {- let mut cmd = std::process::Command::new("adom-desktop");+ let mut cmd = std::process::Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }- match cmd.args(["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]).output() {+ match cmd.args(["pup_list_tabs", "{\"sessionId\":\"shotlog\"}"]).output() { Ok(o) => { let out = String::from_utf8_lossy(&o.stdout).to_string(); if let Ok(v) = serde_json::from_str::<serde_json::Value>(&out) {@@ -1676,17 +1676,17 @@ async fn ws_connection(mut socket: WebSocket, channel: String, state: Arc<AppSta } let ch2 = ch.clone(); // Three-way verdict - flip ONLY on DEFINITIVE evidence of a user close:- // Some(true) = AD reachable AND (session alive with the tab absent,- // OR session_not_found while AD is up) -> user closed it+ // Some(true) = Bridge reachable AND (session alive with the tab absent,+ // OR session_not_found while Bridge is up) -> user closed it // Some(false) = tab still there -> blip/reload- // None = AD offline / error / can't tell -> a REBOOT looks+ // None = Bridge offline / error / can't tell -> a REBOOT looks // exactly like this; never mistake it for a choice. let verdict: Option<bool> = tokio::task::spawn_blocking(move || {- let mut cmd = std::process::Command::new("adom-desktop");+ let mut cmd = std::process::Command::new("adom-bridge-cli"); if let Ok(t) = std::env::var("SHOTLOG_AD_TARGET") { cmd.args(["--target", &t]); }- match cmd.args(["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]).output() {+ match cmd.args(["pup_list_tabs", "{\"sessionId\":\"shotlog\"}"]).output() { Ok(o) => { let out = String::from_utf8_lossy(&o.stdout).to_string(); if let Ok(v) = serde_json::from_str::<serde_json::Value>(&out) {@@ -1695,7 +1695,7 @@ async fn ws_connection(mut socket: WebSocket, channel: String, state: Arc<AppSta .map(|u| u.contains(&format!("/log/{ch2}/"))).unwrap_or(false))); } if out.contains("session_not_found") {- // AD answered; the whole pup window is gone while+ // Bridge answered; the whole pup window is gone while // the laptop is up -> the user closed the window. return Some(true); }
tools/deploy.sh+1−1@@ -50,6 +50,6 @@ else echo "no webview tab open - nothing to refresh there" fi # Pup pages reload themselves via the boot-stamp on reconnect; nudge anyway.-adom-desktop --target "${SHOTLOG_AD_TARGET:-AdomLapper}" browser_reload '{"sessionId":"shotlog"}' >/dev/null 2>&1 \+adom-bridge-cli --target "${SHOTLOG_AD_TARGET:-AdomLapper}" pup_alert_window '{"sessionId":"shotlog"}' >/dev/null 2>&1 \ && echo "pup session nudged" || true echo "OK: deployed $V and refreshed surfaces."