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.17.1
9 files changed
+57−10
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() }