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.12.1
7 files changed
+46−22
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-shotlog"-version = "0.12.0"+version = "0.12.1" dependencies = [ "axum", "base64",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.12.0"+version = "0.12.1" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1@@ -1 +1 @@-0.12.0+0.12.1
bin/adom-shotlog⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-shotlog",- "version": "0.12.0",+ "version": "0.12.1", "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.12.0",+ "version": "0.12.1", "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+41−17@@ -52,6 +52,16 @@ img { max-width: 100%; } z-index: 99999; pointer-events: none; opacity: 0; display: none; transition: opacity .18s; } #global-tooltip.visible { display: block; } #global-tooltip.shown { opacity: 1; }+/* Structured (rich) tooltip content — title / body / requirement / prompt chip. */+#global-tooltip .tt-title { font: 600 12.5px 'Familjen Grotesk', sans-serif; color: var(--text); }+#global-tooltip .tt-p { margin-top: 6px; color: var(--text2); }+#global-tooltip .tt-req { margin-top: 7px; color: var(--text3); font-size: 11px; }+#global-tooltip .tt-count { margin-top: 8px; color: var(--text3); font-family: 'JetBrains Mono', monospace; font-size: 10px; }+#global-tooltip .tt-prompt { margin-top: 9px; padding: 7px 9px; border-radius: 7px;+ background: rgba(0,184,176,.10); border: 1px solid rgba(0,184,176,.28);+ color: var(--accent-bright); font-family: 'JetBrains Mono', monospace; font-size: 11px; line-height: 1.45; }+#global-tooltip .tt-prompt .lbl { display: block; color: var(--text3); font-family: 'Satoshi', sans-serif;+ font-size: 9.5px; letter-spacing: .04em; text-transform: uppercase; margin-bottom: 3px; } /* Header (adom-app-header, 44px) */ header {@@ -310,8 +320,11 @@ header { const tip = $('global-tooltip'); let cx=0, cy=0, timer=null; document.addEventListener('mousemove', e => { cx=e.clientX; cy=e.clientY; }, true); function show(el) {- const t = el.getAttribute('data-tooltip'); if (!t) return;- tip.textContent = t; tip.classList.add('visible');+ // Rich (authored, trusted) HTML via el._ttHtml; else plain text. Dynamic+ // values interpolated into _ttHtml are escaped by the builders below.+ if (el._ttHtml) { tip.innerHTML = el._ttHtml; }+ else { const t = el.getAttribute('data-tooltip'); if (!t) return; tip.textContent = t; }+ tip.classList.add('visible'); const tw=tip.offsetWidth, th=tip.offsetHeight, vw=innerWidth, vh=innerHeight, O=14, M=6; const c=[{x:cx+O,y:cy+O},{x:cx-tw-O,y:cy+O},{x:cx+O,y:cy-th-O},{x:cx-tw-O,y:cy-th-O}] .find(p=>p.x>=M&&p.y>=M&&p.x+tw<=vw-M&&p.y+th<=vh-M)||{x:cx+O,y:cy+O};@@ -543,24 +556,27 @@ header { for (let i=entries.length-1;i>=0;i--) feed.appendChild(card(entries[i])); // newest first updateCount(); }- function channelTooltip(countText){+ function channelTooltipHtml(countText){+ const cnt = countText ? '<div class="tt-count">'+esc(countText)+' captured</div>' : ''; // read-captures is the SPECIAL auto-capture channel — explain it in full,- // because even the shots' origin is non-obvious. Multi-paragraph is fine;- // the renderer clamps it on-screen.+ // because even the shots' origin is non-obvious. if (channel === 'read-captures') {- return 'read-captures — the automatic capture channel.\n\n'+- 'Whenever ANY AI thread in this container READS an image file — a screenshot, a render, a photo — to look at it, a hook instantly copies that exact image here. So this channel shows you everything your AIs have looked at, without them having to remember to send it.\n\n'+- 'That is why shots appear here on their own: they are a side effect of an AI opening an image to analyze it, not something it deliberately posts.\n\n'+- 'Powered by the shotlog read-hook (a Claude Code PostToolUse(Read) hook that runs `shotlog inject -c read-captures`).'+- (countText ? '\n\n'+countText+' captured so far.' : '');+ return '<div class="tt-title">read-captures — automatic capture</div>'++ '<div class="tt-p">Whenever any AI thread here READS an image file (a screenshot, a render, a photo) to look at it, a hook instantly copies that exact image into this channel. You see everything your AIs looked at — they never have to remember to send it.</div>'++ '<div class="tt-p">So shots appear on their own: a side effect of an AI opening an image to analyze it, not a deliberate post. Powered by the shotlog read-hook.</div>'++ '<div class="tt-prompt"><span class="lbl">want a deliberate log? say to your AI</span>ralph test your app and show me all the screenshots in shotlog</div>'++ cnt; }- return 'Channel: '+channel+(countText?' · '+countText:'')+'\nAI agents inject shots here; you watch them arrive live.';+ return '<div class="tt-title">Channel: '+esc(channel)+'</div>'++ '<div class="tt-p">AI agents inject shots into this channel; you watch them arrive live, newest first.</div>'++ '<div class="tt-prompt"><span class="lbl">say to your AI</span>show me your progress in shotlog as you go</div>'++ cnt; } function updateCount(){ const t = shotCount? shotCount+(shotCount===1?' shot':' shots'):''; $('shotCount').textContent = t; // Surface the count in the channel-name tooltip too, since .subject-meta is // hidden in the skinny panel (canonical adom-app-header: meta -> subject tooltip).- $('channelName').setAttribute('data-tooltip', channelTooltip(t)); }+ $('channelName')._ttHtml = channelTooltipHtml(t); } // ── Load ─────────────────────────────────────────────────────────────── fetch('api/shots?channel='+encodeURIComponent(channel)).then(r=>r.json()).then(data=>{@@ -582,11 +598,19 @@ header { btn.style.display=''; const render=()=>{ btn.innerHTML=ICON+'<span>'+label+'</span>'; }; render();- // Tooltip: the surface term (pup/webview), what it does, and the plain-English- // prompt the human can just say to their AI to do the same thing.- btn.setAttribute('data-tooltip', toPup- ? 'Show in pup — a full browser window. Real zoom, whole-screen images. Closes this slim webview tab.\nRequires Adom Desktop installed on your computer (pup is a default bridge that auto-installs with it).\nOr just tell your AI: "show me shotlog in pup"'- : 'Show in webview — the slim panel docked in Hydrogen. Closes this pup tab.\nOr just tell your AI: "show me shotlog in webview"');+ // Structured tooltip: title, what it does, requirement (pup only), and the+ // plain-English prompt the human can say to an AI to do the same thing.+ // NOTE: also set a plain data-tooltip so the hover handler (which matches+ // `[data-tooltip]`) fires for this button; the renderer prefers _ttHtml.+ btn.setAttribute('data-tooltip', toPup ? 'Show in pup' : 'Show in webview');+ btn._ttHtml = toPup+ ? '<div class="tt-title">Show in pup</div>'++ '<div class="tt-p">A full browser window: real zoom, whole-screen images. Closes this slim webview tab.</div>'++ '<div class="tt-req">Requires Adom Desktop installed (pup is a default bridge that auto-installs with it).</div>'++ '<div class="tt-prompt"><span class="lbl">say to your AI</span>show me shotlog in pup</div>'+ : '<div class="tt-title">Show in webview</div>'++ '<div class="tt-p">The slim panel docked in Hydrogen. Closes this pup tab.</div>'++ '<div class="tt-prompt"><span class="lbl">say to your AI</span>show me shotlog in webview</div>'; const busy=(t)=>{ btn.disabled=true; btn.innerHTML='<span>'+t+'</span>'; }; btn.addEventListener('click', async ()=>{ busy('checking…');