← Commit history
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-shotlog"-version = "0.28.6"+version = "0.28.7" dependencies = [  "axum",  "base64",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.28.6"+version = "0.28.7" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-0.28.6+0.28.7
bin/adom-shotlog
⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+4
@@ -478,3 +478,7 @@ A wiki PR against adom/adom-shotlog is closed ONLY after its change has been app  When you fix something Ray reported (issue or PR), DM him one simple line on Google Chat as John via `adom-google chat send` (to [email protected]): what shipped and the version. One line, no essay; the detail lives in the issue/PR reply. +## Proxy prefixes (wiki issue #6, 2026-10-14)++The viewer's `<base>` is derived from the page's own path: everything in front of `/log/`. Never match a fixed `/proxy/N/` prefix: code-server serves `/proxy/8820/`, Mac Hydrogen serves `/__cs/proxy/8820/`, and a direct open has none. With the wrong base every relative fetch and the WebSocket go to the host app instead of the broker and the gallery sits empty with a gray LED. `open --webview` now polls `viewer-status` for 8s after the tab opens and prints WARN + the direct `localhost:<port>/log/` URL when nothing connects; `broker_viewer_count(port, channel)` is the helper. Filelog carries the identical derivation.+
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.28.6",+  "version": "0.28.7",   "type": "app",   "title": "Shotlog - Screenshot Log",   "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 Bridge child-window arrays via --meta.",
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.28.6",+  "version": "0.28.7",   "type": "app",   "title": "Shotlog - Screenshot Log",   "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 Bridge child-window arrays via --meta.",
src/open.rs+28
@@ -200,6 +200,22 @@ fn report(out: std::io::Result<std::process::Output>, name: &str, channel: &str,             println!("Channel: {channel}");             println!("URL: {url}");             surface_tab(name);+            // TRUST BUT VERIFY (wiki issue #6): the tab opening is not the same+            // as the page reaching the broker. Poll until a webview viewer+            // connects; if none does, say so instead of reporting success.+            let port = url.split("/proxy/").nth(1).and_then(|r| r.split('/').next()).and_then(|p| p.parse::<u16>().ok()).unwrap_or(8820);+            let mut connected = false;+            for _ in 0..8 {+                std::thread::sleep(std::time::Duration::from_secs(1));+                if broker_viewer_count(port, channel) > 0 { connected = true; break; }+            }+            if connected {+                println!("OK: a viewer connected to '{channel}' - the human can see it.");+            } else {+                eprintln!("WARN: the tab opened but NO viewer connected to '{channel}' within 8s - the page is not reaching the broker.");+                eprintln!("Hint: open the URL above in a browser tab and check the LED; if it stays gray the proxy prefix in front of /log/ is not reaching port {port}. Direct URL that always works inside the workspace: http://localhost:{port}/log/?surface=webview&channel={channel}");+                eprintln!("Hint: gate on `adom-shotlog viewers -c {channel}` before telling the human the shot is visible.");+            }         }         Ok(output) => {             eprintln!("ERROR: adom-cli failed: {}{}", String::from_utf8_lossy(&output.stdout).trim(), String::from_utf8_lossy(&output.stderr).trim());@@ -498,6 +514,18 @@ pub fn open_pup(channel: &str, port: u16, swap: bool) {     } } +/// How many viewers the broker sees on a channel right now (0 on any error).+pub fn broker_viewer_count(port: u16, channel: &str) -> usize {+    use std::io::{Read, Write};+    let Ok(mut st) = std::net::TcpStream::connect(("127.0.0.1", port)) else { return 0 };+    let _ = st.set_read_timeout(Some(std::time::Duration::from_secs(3)));+    let req = format!("GET /api/viewer-status?channel={} HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n", channel.replace(' ', "%20"));+    if st.write_all(req.as_bytes()).is_err() { return 0; }+    let mut buf = String::new();+    let _ = st.read_to_string(&mut buf);+    buf.split("\"viewers\":").nth(1).and_then(|r| r.trim_start().split(|c: char| !c.is_ascii_digit()).next()).and_then(|n| n.parse().ok()).unwrap_or(0)+}+ /// Does the broker currently see ANY pup viewer connected (the real proof a /// pup page is alive)? Minimal HTTP GET, no external deps. pub fn broker_has_pup_viewer(port: u16) -> bool {
src/server.rs+6−1
@@ -260,10 +260,15 @@ async fn serve_favicon() -> Response {     res } +// The viewer's <base> is everything in front of /log/ on the page's own path,+// so every relative fetch and the WebSocket land on the broker through+// WHATEVER proxy prefix served the page: code-server's /proxy/8820/, Mac+// Hydrogen's /__cs/proxy/8820/ (wiki issue #6: that one fell back to '/' and+// every call went to Hydrogen itself, so the gallery sat empty), or none. async fn serve_viewer() -> Response {     let html = INDEX_HTML.replacen(         "<head>",-        "<head><script>document.write('<base href=\"'+((location.pathname.match(/^\\/proxy\\/\\d+\\//)||['/'])[0])+'\">')</script>",+        "<head><script>document.write('<base href=\"'+(function(){var p=location.pathname,i=p.lastIndexOf('/log/');return i>=0?p.slice(0,i+1):'/';})()+'\">')</script>",         1,     );     // no-cache: the viewer UI must NEVER go stale in a long-lived webview/pup