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.23.9
9 files changed
+71−6
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.23.8"+version = "0.23.9" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.23.8"+version = "0.23.9" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1@@ -1 +1 @@-0.23.8+0.23.9
bin/adom-shotlog⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.23.8",+ "version": "0.23.9", "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.23.8",+ "version": "0.23.9", "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/open.rs+1−1@@ -292,7 +292,7 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) { /// Does the broker currently see ANY pup viewer connected (the real proof a /// pup page is alive)? Minimal HTTP GET, no external deps.-fn broker_has_pup_viewer(port: u16) -> bool {+pub fn broker_has_pup_viewer(port: u16) -> bool { use std::io::{Read, Write}; let Ok(mut st) = std::net::TcpStream::connect(("127.0.0.1", port)) else { return false }; let _ = st.set_read_timeout(Some(std::time::Duration::from_secs(3)));
src/server.rs+55@@ -375,6 +375,15 @@ async fn set_prefs( if c != "pup" && c != "webview" { return Json(serde_json::json!({"ok": false, "error": "default_canvas must be pup|webview"})); }+ if c != p.default_canvas {+ // The human just made a NEW explicit choice - stale in-memory+ // click-notes must not override it ("all channels adhere to the+ // GLOBAL default"; notes only exist for clicks made SINCE).+ let cleared = state.store.clear_all_canvas_prefs().await;+ if cleared > 0 {+ println!("{} DECISION default canvas changed to {c} - cleared {cleared} in-memory canvas note(s).", ts());+ }+ } p.default_canvas = c; } if let Some(h) = patch.read_hook {@@ -670,6 +679,15 @@ fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], let has_webview = infos.iter().any(|i| i.surface == "webview"); if has_webview { 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+ // 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.+ if pref == "pup" && !has_pup {+ recover_pup_from_fallback(channel, port);+ } return; } if infos.is_empty() && std::env::var("SHOTLOG_NO_FORCE_OPEN").is_err() {@@ -722,6 +740,43 @@ fn force_open_pup(channel: &str, fallback_ratio: f64) { }); } +/// Recover from the webview fallback once pup is reachable again: try pup once+/// (cooldown 5 min), wait for a REAL pup viewer connect, then close the+/// fallback webview. Never closes anything while pup isn't live.+fn recover_pup_from_fallback(channel: &str, port: u16) {+ if !debounce_ok("puprecover", 300) {+ return;+ }+ let channel = channel.to_string();+ std::thread::spawn(move || {+ println!("{} DECISION default canvas is pup but only a webview is connected (fallback residue) - retrying pup.", ts());+ let exe = std::env::current_exe().unwrap_or_else(|_| "adom-shotlog".into());+ let ok = std::process::Command::new(&exe)+ .args(["open", "-c", &channel, "--pup"])+ .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null())+ .status().map(|s| s.success()).unwrap_or(false);+ if !ok {+ println!("{} DECISION pup still unreachable - staying on the fallback webview.", ts());+ return;+ }+ for _ in 0..15 {+ std::thread::sleep(std::time::Duration::from_secs(2));+ if crate::open::broker_has_pup_viewer(port) {+ println!("{} DECISION pup is back and CONNECTED - closing the fallback webview (it was never a choice).", ts());+ let out = std::process::Command::new("adom-cli")+ .args(["hydrogen", "workspace", "remove-tab", "--name", "Shotlog"])+ .output();+ let closed = out.map(|o| o.status.success()).unwrap_or(false);+ if !closed {+ println!("{} WARN: fallback webview tab close failed - pup is live, human may see both surfaces.", ts());+ }+ return;+ }+ }+ println!("{} DECISION pup open reported ok but no pup viewer connected within 30s - leaving the webview alone.", ts());+ });+}+ /// Orange taskbar flash on the pup window hosting a viewer. Debounced per session /// so a burst of injects flashes once, not strobe. fn flash_pup_session(session: &str) {
src/store.rs+10@@ -607,6 +607,16 @@ impl Store { self.canvas_prefs.write().await.remove(channel); } + /// Forget EVERY in-memory canvas note. Used when the human changes the+ /// global default: the newest explicit intent wins, so stale click-notes+ /// must never override it.+ pub async fn clear_all_canvas_prefs(&self) -> usize {+ let mut m = self.canvas_prefs.write().await;+ let n = m.len();+ m.clear();+ n+ }+ pub async fn canvas_pref(&self, channel: &str) -> Option<String> { self.canvas_prefs.read().await.get(channel).cloned() }