← Commit history
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-shotlog"-version = "0.23.3"+version = "0.23.4" dependencies = [  "axum",  "base64",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.23.3"+version = "0.23.4" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
SKILL.md+15
@@ -135,6 +135,21 @@ the shot. Ways through: none of `--orig-w`+`--orig-h` / `--orig-file` / `--native` / `--meta`, the inject is refused with exit 1 and a hint. +### HTTP-only inject: `POST /api/paste`++No CLI on your machine? POST a data URL. **Declare your `source`** (same values as+`-s`: `pup`, `ad-window`, `hd`, ...); undeclared defaults to `api` (a neutral,+chip-less deliberate inject). Do NOT claim `clipboard` - that chip means a HUMAN+pasted with Ctrl+V, and only the viewer's paste handler sends it. `thread` and+`context` are accepted too and power the viewer's attribution and filters.++```bash+curl -s -X POST localhost:8820/api/paste -H 'Content-Type: application/json' -d '{+  "channel":"proj", "dataUrl":"data:image/png;base64,...",+  "description":"what you see and why", "source":"pup",+  "thread":"my thread title", "context":"the prompt being served"}'+```+ ## `--meta` - Adom Desktop child-window arrays  As of adom-desktop 1.8.177, `desktop_screenshot_window {hwnd}` returns the primary
VERSION+1−1
@@ -1 +1 @@-0.23.3+0.23.4
bin/adom-shotlog
⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+17
@@ -238,3 +238,20 @@ rebuild** after editing them. Bump `Cargo.toml` + `VERSION` together.   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.++## Metadata display rule (John, 2026-07-17)+When the human asks to see metadata, show ALL of it: every entry field plus the+entire sidecar flattened (meta.coordMap.*, popups[n].*, png_meta.*) plus the raw+JSON, inline. Never a curated subset. "Almost nothing" summaries got this exact+complaint: "when i ask you to show me metadata i mean all of it".++## Broker process naming trap (2026-07-17)+The read-hook's self-heal starts the broker as `shotlog serve` (argv[0]=shotlog),+NOT `adom-shotlog serve`. Managing the broker means checking BOTH names:+`pgrep -x adom-shotlog; pgrep -x shotlog`. And `pgrep -f "adom-shotlog serve"`+MATCHES YOUR OWN SHELL WRAPPER (the shell-snapshot eval embeds the pattern) —+killing head -1 of that killed my own shell (exit 144). Kill by exact name or+explicit PID from ps, never by -f pattern. Also: any deploy-restart window can+be resurrected mid-window by a hook self-heal running the OLD recovery state —+after migrating on-disk data, verify the SERVING process started AFTER the+migration (check prod-srv.log recovery line), not just that the port answers.
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.23.3",+  "version": "0.23.4",   "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.3",+  "version": "0.23.4",   "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+4−2
@@ -645,7 +645,9 @@ header {     'clipboard':{label:'paste',cls:'',tip:'Pasted from the clipboard by the human (Ctrl+V in the viewer).'},   };   function srcChips(e){-    if(!e.source || e.source==='cli') return [];+    // 'cli' and 'api' are deliberate injects with no capture endpoint declared —+    // chip-less on purpose. Only DECLARED provenance gets a chip.+    if(!e.source || e.source==='cli' || e.source==='api') return [];     return e.source.split('/').map(part=>{       const m=SRC_MAP[part];       return m || {label:part, cls:'', tip:'Capture source: '+part};@@ -1333,7 +1335,7 @@ header {       pasteOverlay.classList.add('active');       try {         const dataUrl = await new Promise((res,rej)=>{ const r=new FileReader(); r.onload=()=>res(r.result); r.onerror=rej; r.readAsDataURL(blob); });-        const res = await fetch('api/paste',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({channel,dataUrl})});+        const res = await fetch('api/paste',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({channel,dataUrl,source:'clipboard'})});         const r = await res.json();         if (r.ok) {           let oi = (r.original_w&&r.original_h)?`<div class="detail">Original: <span class="val">${r.original_w}×${r.original_h}</span></div>`:'';
src/server.rs+14−5
@@ -975,6 +975,13 @@ struct PasteRequest {     #[serde(rename = "dataUrl")]     data_url: String,     description: Option<String>,+    // Provenance is DECLARED, never assumed. The viewer's Ctrl+V handler sends+    // source:"clipboard"; programmatic clients say who they are (or default to+    // "api", which renders chip-less like a CLI inject). Stamping every paste+    // "clipboard" mislabeled AI-pushed shots as human pastes.+    source: Option<String>,+    thread: Option<String>,+    context: Option<String>, }  async fn post_paste(@@ -992,15 +999,17 @@ async fn post_paste(      let was_resized = w != orig_w || h != orig_h; +    let source = req.source.clone().unwrap_or_else(|| "api".into());+    let kind = if source == "clipboard" { "Clipboard paste" } else { "API paste" };     let description = req.description.unwrap_or_else(|| {         if was_resized {             format!(-                "Clipboard paste (resized from {}x{}, {:.0} KB to {}x{}, {:.0} KB)",-                orig_w, orig_h, orig_bytes as f64 / 1024.0,+                "{} (resized from {}x{}, {:.0} KB to {}x{}, {:.0} KB)",+                kind, orig_w, orig_h, orig_bytes as f64 / 1024.0,                 w, h, bytes.len() as f64 / 1024.0,             )         } else {-            format!("Clipboard paste ({}x{}, {:.0} KB)", w, h, bytes.len() as f64 / 1024.0)+            format!("{} ({}x{}, {:.0} KB)", kind, w, h, bytes.len() as f64 / 1024.0)         }     }); @@ -1011,10 +1020,10 @@ async fn post_paste(     let entry = state         .store         .add_entry(-            &req.channel, bytes, filename, description, "clipboard".into(), w, h,+            &req.channel, bytes, filename, description, source, w, h,             if was_resized { Some(orig_w) } else { None },             if was_resized { Some(orig_h) } else { None },-            None, None, None, None, None,+            None, None, req.thread.clone(), req.context.clone(), None,         )         .await;