← Commit history
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-shotlog"-version = "0.18.1"+version = "0.18.2" dependencies = [  "axum",  "base64",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.18.1"+version = "0.18.2" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-0.18.1+0.18.2
bin/adom-shotlog
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.18.1",+  "version": "0.18.2",   "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.18.1",+  "version": "0.18.2",   "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+6
@@ -778,9 +778,12 @@ header {     // destination viewer's WebSocket actually connects (the full signal) ->     // only then close this (source) surface. Any failure leaves this surface     // open and reports exactly what happened. Nothing is ever closed blind.+    const uiLog=(line)=>{ fetch('api/log',{method:'POST',headers:{'Content-Type':'application/json'},+      body:JSON.stringify({line:'UI '+line})}).catch(()=>{}); };     btn.addEventListener('click', async ()=>{       if(btn.disabled) return;       btn.disabled=true;+      uiLog('switch click: '+channel+' -> '+dest);       let opened=false;       try{         showToast(toPup?'Opening the pup window…':'Opening the webview panel…', 3500);@@ -802,6 +805,7 @@ header {         const op=await fetch('api/canvas-open',{method:'POST',headers:{'Content-Type':'application/json'},           body:JSON.stringify({channel, to:dest})}).then(r=>r.json());         if(!op.ok){+          uiLog('switch FAILED opening '+dest+' for '+channel+': '+String(op.error||op.detail||'').slice(0,160));           showToast('could not open '+dest+': '+String(op.error||op.detail||'unknown').slice(0,300)+' - this tab stays open.', 10000);           restore(); return;         }@@ -823,11 +827,13 @@ header {           busy('rolling back…');           try{ await fetch('api/canvas-close',{method:'POST',headers:{'Content-Type':'application/json'},             body:JSON.stringify({channel, surface:dest})}); }catch{}+          uiLog('switch ROLLBACK: '+dest+' never connected for '+channel+' (30s)');           showToast(dest+' opened but never connected (waited 30s) - rolled back, this tab stays open. '+(toPup?'Is the pup window blocked or the laptop asleep?':'Is Hydrogen open?'), 12000);           restore(); return;         }          // Stage 3: destination is LIVE. Now (and only now) close this surface.+        uiLog('switch OK: '+channel+' now on '+dest);         busy('switched ✓');         showToast(dest+' is live - closing this '+src+' tab.', 4000);         await fetch('api/canvas-close',{method:'POST',headers:{'Content-Type':'application/json'},
src/open.rs+11−8
@@ -211,11 +211,18 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) {     //   tab exists      -> switch_tab   (2 trips)     //   session, no tab -> open_tab     (2 trips)     let needle = format!("/log/{channel}/");+    // 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.+    let open_window = |ad: &dyn Fn(&[&str]) -> (bool, String)| -> bool {+        let win_json = format!("{{\"sessionId\":\"shotlog\",\"profile\":\"shotlog\",\"url\":\"{url}\"}}");+        ad(&["browser_open_window", &win_json]).0+    };     let (listed, list_out) = ad(&["browser_list_tabs", "{\"sessionId\":\"shotlog\"}"]);     let mut opened = false;     if !listed || list_out.contains("session_not_found") {-        let win_json = format!("{{\"sessionId\":\"shotlog\",\"profile\":\"shotlog\",\"url\":\"{url}\"}}");-        opened = ad(&["browser_open_window", &win_json]).0;+        opened = open_window(&ad);     } else {         let existing = serde_json::from_str::<serde_json::Value>(&list_out).ok()             .and_then(|v| v.get("tabs").and_then(|t| t.as_array()).and_then(|tabs| {@@ -224,14 +231,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(&["browser_switch_tab", &sw]).0 || open_window(&ad);         } else {             let tab_json = format!("{{\"sessionId\":\"shotlog\",\"url\":\"{url}\"}}");-            let (ok, out) = ad(&["browser_open_tab", &tab_json]);-            opened = ok || (out.contains("session_not_found") && {-                let win_json = format!("{{\"sessionId\":\"shotlog\",\"profile\":\"shotlog\",\"url\":\"{url}\"}}");-                ad(&["browser_open_window", &win_json]).0-            });+            opened = ad(&["browser_open_tab", &tab_json]).0 || open_window(&ad);         }     } 
src/server.rs+5
@@ -385,6 +385,11 @@ async fn canvas_open(     })     .await     .unwrap_or_else(|_| serde_json::json!({"ok": false, "error": "spawn failed"}));+    let okf = v.get("ok").and_then(|b| b.as_bool()).unwrap_or(false);+    let mut detail = v.get("detail").or_else(|| v.get("error")).and_then(|d| d.as_str()).unwrap_or("").replace('\n', " | ");+    detail.truncate(220);+    println!("{} DECISION canvas-open result ok={okf}{}", ts(),+        if okf { String::new() } else { format!(" detail: {detail}") });     Json(v) }