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.8.2
8 files changed
+29−93
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.8.1"+version = "0.8.2" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.8.1"+version = "0.8.2" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1@@ -1 +1 @@-0.8.1+0.8.2
bin/adom-shotlog⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+9−7@@ -106,12 +106,14 @@ rebuild** after editing them. Bump `Cargo.toml` + `VERSION` together. and manages all the Hydrogen webviews (auto-open a slim 20% panel, bring the right channel's tab forward - by its unique `Shots: <channel>` name so parallel shotlogs are never cross-touched).-- **Webview activate = the BOUNCE.** Hydrogen has no "activate tab" verb;- `webview open-or-refresh` does NOT change `activeTabIndex`, and `move-tab`- rejects same-pane moves. The working recipe (in `bring_webview_forward`): find- the tab by its `panelState` URL (`/log/<ch>/`), `move-tab` it to any OTHER pane,- then `move-tab` back to `--target-index <orig>` with `--alert` - move-tab- auto-activates in its target pane, so the tab surfaces to top with the orange- update highlight, order preserved.+- **Webview activate = `workspace active-tab` - NEVER move-tab.** To surface a+ channel's tab: `adom-cli hydrogen workspace active-tab --name "Shots: <ch>"`+ (brings it to the front WITHIN its own pane), then `alert set --name` for the+ orange indicator. HARD-WON DON'T: an earlier version "bounced" the tab through+ another pane with two `move-tab` calls (move-tab auto-activates in its target+ pane). That re-parents iframes and re-renders the ENTIRE Hydrogen workspace,+ interrupting the user mid-keystroke in their VS Code tab. If a verb seems+ missing, search `--help` output for hyphenated forms (`active-tab` does not+ match a grep for `activate`) before building a workaround. - It's **per-container**: threads in a different container get their own server. - `shotlog channels` prints the live overview - how every thread is using it.
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.8.1",+ "version": "0.8.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.8.1",+ "version": "0.8.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/server.rs+15−81@@ -303,37 +303,22 @@ fn bring_webview_forward(channel: &str, port: u16) { } let channel = channel.to_string(); std::thread::spawn(move || {- if let Some(hit) = find_webview_tab(&channel) {- // Hydrogen has no "activate tab" verb, and move-tab rejects same-pane- // moves — but move-tab AUTO-ACTIVATES in its target pane. So: BOUNCE.- // Move to any other pane, then straight back to the original position- // with --alert: net effect = tab surfaces to top with the ORANGE- // update highlight, order preserved via --target-index.- if let Some(other) = hit.other_pane {- let m1 = std::process::Command::new("adom-cli")- .args(["hydrogen", "workspace", "move-tab",- "--from-panel-id", &hit.pane, "--tab-id", &hit.tab,- "--to-panel-id", &other])- .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null())- .status().map(|s| s.success()).unwrap_or(false);- if m1 {- let m2 = std::process::Command::new("adom-cli")- .args(["hydrogen", "workspace", "move-tab",- "--from-panel-id", &other, "--tab-id", &hit.tab,- "--to-panel-id", &hit.pane,- "--target-index", &hit.index.to_string(),- "--alert", "--alert-duration", "6000"])- .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null())- .status().map(|s| s.success()).unwrap_or(false);- if m2 {- return;- }- }- }- // Single-pane workspace (or a move failed): at least blink the tab.+ // GENTLE activate: `workspace active-tab` brings the tab to the front+ // WITHIN ITS OWN PANE — no tab moves, no iframe re-parenting, no+ // workspace-tree re-render. (The old approach bounced the tab through+ // the user's VS Code pane via move-tab, which re-rendered the whole+ // Hydrogen page and interrupted typing. Never do that.)+ let tab = format!("Shots: {channel}");+ let activated = std::process::Command::new("adom-cli")+ .args(["hydrogen", "workspace", "active-tab", "--name", &tab])+ .output()+ .map(|o| o.status.success()+ && String::from_utf8_lossy(&o.stdout).contains("204"))+ .unwrap_or(false);+ if activated {+ // Orange update indicator on the (now-front) tab. let _ = std::process::Command::new("adom-cli")- .args(["hydrogen", "alert", "set", "--name",- &format!("Shots: {channel}"), "-d", "6000"])+ .args(["hydrogen", "alert", "set", "--name", &tab, "-d", "6000"]) .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()) .status(); return;@@ -343,57 +328,6 @@ fn bring_webview_forward(channel: &str, port: u16) { }); } -/// A located webview tab: its pane, tab id, index within the pane, and some-/// OTHER pane usable for the activate-bounce (None in a single-pane workspace).-struct TabHit {- pane: String,- tab: String,- index: usize,- other_pane: Option<String>,-}--/// Locate the webview tab whose panelState URL contains `/log/<channel>/`.-fn find_webview_tab(channel: &str) -> Option<TabHit> {- let out = std::process::Command::new("adom-cli")- .args(["hydrogen", "workspace", "get"])- .output()- .ok()?;- let v: serde_json::Value = serde_json::from_slice(&out.stdout).ok()?;- let needle = format!("/log/{channel}/");- // Collect all leaves in order, then find the tab.- fn leaves<'a>(n: &'a serde_json::Value, acc: &mut Vec<&'a serde_json::Value>) {- match n.get("type").and_then(|t| t.as_str()) {- Some("leaf") => acc.push(n),- Some("split") => {- if let Some(c) = n.get("first") { leaves(c, acc); }- if let Some(c) = n.get("second") { leaves(c, acc); }- }- _ => {}- }- }- let root = v.get("root").unwrap_or(&v);- let mut ls = Vec::new();- leaves(root, &mut ls);- let empty = Vec::new();- for leaf in &ls {- let pane = leaf.get("id").and_then(|i| i.as_str())?.to_string();- for (idx, t) in leaf.get("tabs").and_then(|x| x.as_array()).unwrap_or(&empty).iter().enumerate() {- // panelState may be a JSON object or a stringified one — either way- // its serialization contains the viewer URL.- let ps = t.get("panelState").map(|p| p.to_string()).unwrap_or_default();- if ps.contains(&needle) {- let tab = t.get("id").and_then(|i| i.as_str())?.to_string();- let other_pane = ls.iter()- .filter_map(|l| l.get("id").and_then(|i| i.as_str()))- .find(|id| *id != pane)- .map(String::from);- return Some(TabHit { pane, tab, index: idx, other_pane });- }- }- }- None-}- /// Force-open the viewer for a channel when a shot lands with NOBODY watching. /// Fallback only (see notify_surface). Debounced per-channel. Defaults to a slim /// ~20%-wide right-side panel so cross-thread shots auto-appear without taking