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.0
10 files changed
+52−22
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.16.1"+version = "0.17.0" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.16.1"+version = "0.17.0" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1@@ -1 +1 @@-0.16.1+0.17.0
bin/adom-shotlog⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.16.1",+ "version": "0.17.0", "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.16.1",+ "version": "0.17.0", "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+13@@ -394,6 +394,14 @@ header { </div> <button class="toggle" id="prefHook" role="switch" aria-checked="true" data-tooltip="Toggle the read-hook on or off. Takes effect on the next image an AI reads."></button> </div>+ <div class="pref-row">+ <div class="pref-info">+ <div class="pref-name">Update alerts (orange flashes)</div>+ <div class="pref-desc">When new shots arrive: the webview tab highlights orange and the pup window flashes its taskbar. Turn off for calmer eyeballs - shotlog still brings the tab forward, just without the flashing.</div>+ <div class="ai-chip"><span class="lbl">or say to your AI</span>turn off the shotlog orange flashes</div>+ </div>+ <button class="toggle" id="prefAlerts" role="switch" aria-checked="true" data-tooltip="Toggle the orange update cues (webview tab highlight + pup taskbar flash) when new shots arrive."></button>+ </div> <div class="pref-row"> <div class="pref-info"> <div class="pref-name">Default canvas</div>@@ -879,6 +887,7 @@ header { document.addEventListener('keydown',e=>{ if(e.key==='Escape'&&dlg.classList.contains('open')) close(); }); function renderPrefs(p){ hook.setAttribute('aria-checked', String(!!p.read_hook));+ $('prefAlerts').setAttribute('aria-checked', String(p.update_alerts!==false)); seg.querySelectorAll('button').forEach(b=>b.classList.toggle('on', b.dataset.v===p.default_canvas)); $('prefMaxShots').value = p.max_shots ?? 200; $('prefRetention').value = p.retention_days ?? 30;@@ -894,6 +903,10 @@ header { const now=hook.getAttribute('aria-checked')==='true'; patch({read_hook: !now}, !now?'read-hook ON: images AIs read auto-capture again.':'read-hook OFF: nothing auto-captures until you turn it back on.'); });+ $('prefAlerts').addEventListener('click',()=>{+ const now=$('prefAlerts').getAttribute('aria-checked')==='true';+ patch({update_alerts: !now}, !now?'orange update flashes ON.':'orange update flashes OFF: tabs still come forward, no flashing.');+ }); seg.addEventListener('click',e=>{ const b=e.target.closest('button[data-v]'); if(!b) return; patch({default_canvas: b.dataset.v}, 'channels now default to '+b.dataset.v+'.');
src/main.rs+3−2@@ -640,8 +640,9 @@ fn run_inject( for a in acts { match a.get("action").and_then(|x| x.as_str()) { Some("webview_surface") => ok(&format!(- "Broker: pulling the '{}' webview tab to the front with an ORANGE update alert — surfacing is handled, do NOT open/foreground anything yourself.",- a.get("tab").and_then(|t| t.as_str()).unwrap_or("Shots"))),+ "Broker: '{}' webview: {} — surfacing is handled, do NOT open/foreground anything yourself.",+ a.get("tab").and_then(|t| t.as_str()).unwrap_or("Shots"),+ a.get("cue").and_then(|c| c.as_str()).unwrap_or("tab pulled to front"))), Some("pup_flash") => ok(&format!( "Broker: flashing the pup taskbar orange (session{}) — the human's gentle cue is handled.", a.get("sessions").and_then(|s| s.as_array()).map(|v| format!(" {}", v.iter().filter_map(|x| x.as_str()).collect::<Vec<_>>().join(", "))).unwrap_or_default())),
src/server.rs+25−14@@ -283,6 +283,7 @@ struct PrefsPatch { read_hook: Option<bool>, max_shots: Option<usize>, retention_days: Option<u64>,+ update_alerts: Option<bool>, } /// Patch global preferences from the viewer's Preferences dialog.@@ -306,6 +307,9 @@ async fn set_prefs( } p.max_shots = n; }+ if let Some(a) = patch.update_alerts {+ p.update_alerts = a;+ } if let Some(d) = patch.retention_days { if d > 3650 { return Json(serde_json::json!({"ok": false, "error": "retention_days must be 0-3650 (0 = never)"}));@@ -544,7 +548,7 @@ fn debounce_ok(key: &str, secs: u64) -> bool { /// hinted to flash the pup taskbar. /// - **nobody** watching → force-open a slim ~20% webview, unless /// `SHOTLOG_NO_FORCE_OPEN` is set.-fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], pref: &str) {+fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], pref: &str, alerts: bool) { // Pup viewers get an automatic ORANGE TASKBAR FLASH (FlashWindowEx) on their // hosting window — a gentle "update" cue, never a foreground steal. Works when // the viewer URL carried ?session=<pup sessionId> (reported in the WS hello).@@ -553,13 +557,15 @@ fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], .filter(|i| i.surface == "pup") .filter_map(|i| i.session.clone()) .collect();- for s in pup_sessions {- flash_pup_session(&s);+ if alerts {+ for s in pup_sessions {+ flash_pup_session(&s);+ } } let has_webview = infos.iter().any(|i| i.surface == "webview"); if has_webview {- bring_webview_forward(channel, port);+ bring_webview_forward(channel, port, alerts); return; } if infos.is_empty() && std::env::var("SHOTLOG_NO_FORCE_OPEN").is_err() {@@ -636,7 +642,7 @@ fn flash_pup_session(session: &str) { /// shotlog tabs are never cross-touched), then `move-tab` to its own pane: /// Hydrogen auto-activates it (surfaces to top) and `--alert` paints the ORANGE /// update highlight on the tab. Falls back to create-or-refresh if not found.-fn bring_webview_forward(channel: &str, port: u16) {+fn bring_webview_forward(channel: &str, port: u16, alerts: bool) { if !debounce_ok(&format!("fwd:{channel}"), 2) { return; }@@ -655,11 +661,13 @@ fn bring_webview_forward(channel: &str, port: u16) { && 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", &tab, "-d", "6000"])- .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null())- .status();+ // Orange update indicator on the (now-front) tab - user-toggleable.+ if alerts {+ let _ = std::process::Command::new("adom-cli")+ .args(["hydrogen", "alert", "set", "--name", &tab, "-d", "6000"])+ .stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null())+ .status();+ } return; } // Tab not found → create/refresh it instead.@@ -819,22 +827,25 @@ async fn post_shots( s.into_iter().collect() }; let force_open_on = std::env::var("SHOTLOG_NO_FORCE_OPEN").is_err();+ let app_prefs = state.store.app_prefs().await;+ let alerts_on = app_prefs.update_alerts; let canvas_pref = match state.store.canvas_pref(&req.channel).await { Some(c) => c,- None => state.store.app_prefs().await.default_canvas,+ None => app_prefs.default_canvas, }; let mut actions: Vec<serde_json::Value> = Vec::new();- if !pup_sessions.is_empty() {+ if !pup_sessions.is_empty() && alerts_on { actions.push(serde_json::json!({"action":"pup_flash","sessions":pup_sessions,"cue":"orange taskbar flash"})); } if has_webview {- actions.push(serde_json::json!({"action":"webview_surface","tab":format!("Shots: {}", req.channel),"cue":"tab pulled to front + orange alert"}));+ actions.push(serde_json::json!({"action":"webview_surface","tab":format!("Shots: {}", req.channel),+ "cue": if alerts_on {"tab pulled to front + orange alert"} else {"tab pulled to front (update alerts off)"}})); } else if infos.is_empty() && force_open_on { actions.push(serde_json::json!({"action":"force_open","canvas": canvas_pref, "layout": if canvas_pref=="pup" {"tab in the shared pup window"} else {"slim 20% right-side webview"}})); } let surfacing = Some(serde_json::json!({"actions": actions}));- notify_surface(&req.channel, state.port, &infos, &canvas_pref);+ notify_surface(&req.channel, state.port, &infos, &canvas_pref, alerts_on); // Progressive mini-skill hints — taught at the moment they're relevant, then // quiet (hint_gate caps how many times each lesson repeats).
src/store.rs+6−1@@ -97,6 +97,10 @@ pub struct AppPrefs { /// Env SHOTLOG_RETENTION_DAYS overrides when set. #[serde(default = "default_retention_days")] pub retention_days: u64,+ /// Orange update cues when NEW SHOTS arrive: the webview tab highlight and+ /// the pup taskbar flash. Off = still pulls the tab forward, just no flash.+ #[serde(default = "default_true")]+ pub update_alerts: bool, } fn default_canvas() -> String { "webview".into() } fn default_true() -> bool { true }@@ -104,7 +108,8 @@ fn default_max_shots() -> usize { 200 } fn default_retention_days() -> u64 { 30 } impl Default for AppPrefs { fn default() -> Self { Self { default_canvas: default_canvas(), read_hook: true,- max_shots: default_max_shots(), retention_days: default_retention_days() } }+ max_shots: default_max_shots(), retention_days: default_retention_days(),+ update_alerts: true } } } /// The flag file the read-hook script checks; present = hook disabled.