← Commit history
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-shotlog"-version = "0.17.0"+version = "0.17.1" dependencies = [  "axum",  "base64",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.17.0"+version = "0.17.1" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-0.17.0+0.17.1
bin/adom-shotlog
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.17.0",+  "version": "0.17.1",   "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.17.0",+  "version": "0.17.1",   "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+29
@@ -386,6 +386,18 @@ header {   <div class="helper-card">     <button class="h-close" id="prefsClose">✕</button>     <div class="h-title">Preferences</div>+    <div class="pref-row">+      <div class="pref-info">+        <div class="pref-name">This channel: <span id="prefChName"></span></div>+        <div class="pref-desc">Which canvas THIS channel uses when shotlog auto-opens it. Opening or switching a canvas records a choice here; closing a pup window flips it to webview. "Follow default" forgets the channel's own choice and uses the global default below.</div>+        <div class="ai-chip"><span class="lbl">or say to your AI</span>let this shotlog channel follow the default again</div>+      </div>+      <div class="seg" id="prefChCanvas">+        <button data-v="webview">Webview</button>+        <button data-v="pup">Pup</button>+        <button data-v="default">Follow default</button>+      </div>+    </div>     <div class="pref-row">       <div class="pref-info">         <div class="pref-name">Auto-capture what AIs read</div>@@ -920,8 +932,25 @@ header {     }     wireNum('prefMaxShots','max_shots', v=>'keeping the newest '+v+' shots per channel.');     wireNum('prefRetention','retention_days', v=> v===0?'idle channels are never pruned.':'idle channels prune after '+v+' days.');+    const chSeg=$('prefChCanvas');+    $('prefChName').textContent=channel;+    function renderChPref(v){ // v = "webview" | "pup" | null (follow default)+      chSeg.querySelectorAll('button').forEach(b=>b.classList.toggle('on', b.dataset.v===(v||'default')));+    }+    chSeg.addEventListener('click', async e=>{+      const b=e.target.closest('button[data-v]'); if(!b) return;+      try{+        const r=await fetch('api/canvas-pref',{method:'POST',headers:{'Content-Type':'application/json'},+          body:JSON.stringify({channel, canvas:b.dataset.v})}).then(x=>x.json());+        if(r.ok){ renderChPref(b.dataset.v==='default'?null:b.dataset.v);+          showToast(b.dataset.v==='default'?'this channel now follows the global default.':'this channel is now a '+b.dataset.v+' channel.', 4000); }+        else showToast('could not save: '+(r.error||'unknown'), 6000);+      }catch(err){ showToast('could not save: '+err.message, 6000); }+    });     $('prefsBtn').addEventListener('click', async ()=>{       try{ renderPrefs(await fetch('api/prefs').then(r=>r.json())); }catch{}+      try{ const st=await fetch('api/viewer-status?channel='+encodeURIComponent(channel)).then(r=>r.json());+        renderChPref(st.canvas_pref||null); }catch{}       dlg.classList.add('open');     });   })();
src/server.rs+13−5
@@ -266,10 +266,15 @@ async fn set_canvas_pref(     State(state): State<Arc<AppState>>,     Json(req): Json<CanvasPrefRequest>, ) -> Json<serde_json::Value> {-    if req.canvas != "pup" && req.canvas != "webview" || req.channel.is_empty() {-        return Json(serde_json::json!({"ok": false, "error": "canvas must be pup|webview"}));+    if req.channel.is_empty() {+        return Json(serde_json::json!({"ok": false, "error": "channel required"}));+    }+    match req.canvas.as_str() {+        "pup" | "webview" => state.store.set_canvas_pref(&req.channel, &req.canvas).await,+        // "default" = forget the channel's own choice; follow the global default.+        "default" => state.store.clear_canvas_pref(&req.channel).await,+        _ => return Json(serde_json::json!({"ok": false, "error": "canvas must be pup|webview|default"})),     }-    state.store.set_canvas_pref(&req.channel, &req.canvas).await;     Json(serde_json::json!({"ok": true})) } @@ -670,8 +675,11 @@ fn bring_webview_forward(channel: &str, port: u16, alerts: bool) {             }             return;         }-        // Tab not found → create/refresh it instead.-        crate::open::open_webview(&channel, None, None, port, false, 0.2);+        // Tab not found: do NOTHING. A webview VIEWER with no Hydrogen tab is a+        // plain page reporting surface=webview (e.g. a headless/test client) -+        // it already receives updates over its WebSocket. Creating a tab here+        // materialized phantom webview tabs against the user's pup preference.+        let _ = port;     }); } 
src/store.rs+10
@@ -541,6 +541,16 @@ impl Store {         }     } +    /// Clear a channel's canvas choice so it follows the global default again.+    pub async fn clear_canvas_pref(&self, channel: &str) {+        let mut m = self.canvas_prefs.write().await;+        if m.remove(channel).is_some() {+            if let Ok(j) = serde_json::to_string_pretty(&*m) {+                let _ = fs::write(self.base_dir.join(".canvas-prefs.json"), j);+            }+        }+    }+     pub async fn canvas_pref(&self, channel: &str) -> Option<String> {         self.canvas_prefs.read().await.get(channel).cloned()     }