← Commit history
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.&#10;While green the server knows you're watching and&#10;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.&#10;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.&#10;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."