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.
Copy button on the ref/handle strip: copies "#158 hero-final" #5
Ray asked for a copy button beside the ref/handle pill, and specifically wondered whether it should copy the whole thing (#158 hero-final) or just one half.
It copies both. Reasoning:
- The ref is globally unambiguous; the handle is what a human actually says out loud ("hero-final is the one").
- Pasting both lets the AI resolve on either token, and survives a handle being reused on a later shot.
- Untagged shots copy just
#158, since there is nothing else to give.
What it looks like
#1 [hero-final] [copy]
Fades in on card hover and on :focus-visible, matching the existing .card-fill-btn pattern, so it never sits on top of the thumbnail. On touch it stays at 0.8 opacity (same media query the fill button uses). Shows copied for 1.2s on success.
Implementation notes
- Follows the house clipboard pattern at
index.html:1113(navigator.clipboard?.writeText+ a text swap for feedback). - Adds a
legacyCopyfallback, because this viewer also runs in a sandboxed readme frame where the async clipboard API is unavailable and the existing call would silently no-op.execCommandis deprecated but is the only thing that works there. ev.stopPropagation()so the click never opens the shot.
Verified
node --checkon the main script block: OK.cargo build --release: clean, 7 warnings, identical to the pre-change baseline.- Rendered in a real headless browser against a scratch server with a tagged shot; screenshot shows
#1 hero-final copylaid out correctly.
Not merging this myself, adom-shotlog is yours.
Diff Skip to comments (1)
@@ -1,2083 +1,2128 @@⋯ 256 unchanged lines ⋯ .card-refbar { display: flex; align-items: center; gap: 8px; padding: 3px 10px 2px; min-height: 16px; font: 600 10.5px/1.3 'JetBrains Mono', monospace; color: var(--text3); } .card-refbar .rb-ref { letter-spacing: .2px; }+.card-refbar .rb-copy { margin-left: 4px; padding: 0 5px; border-radius: 5px; cursor: pointer;+ font: inherit; line-height: 1.5; color: var(--text3); background: transparent;+ border: 1px solid var(--border-muted); opacity: 0; transition: opacity .12s, color .12s, border-color .12s; }+.shot-card:hover .card-refbar .rb-copy, .card-refbar .rb-copy:focus-visible { opacity: 1; }+.card-refbar .rb-copy:hover { color: var(--accent-bright); border-color: rgba(0,184,176,.5); }+.card-refbar .rb-copy.done { opacity: 1; color: var(--accent-bright); border-color: rgba(0,184,176,.5); }+@media (hover: none) { .card-refbar .rb-copy { opacity: .8; } } .card-refbar .rb-tag { color: var(--accent-bright); background: rgba(0,184,176,.12); border: 1px solid rgba(0,184,176,.45); border-radius: 5px; padding: 0 6px; max-width: 70%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .shot-card:not(.has-refbar) .card-refbar { }⋯ 28 unchanged lines ⋯ The thumbnail IMAGE also fills the window in one click (see the click handler on img, "ONE click on a thumbnail goes STRAIGHT to fill-window"), so this button is the discoverable, keyboard-focusable route to the same- thing rather than a separate shortcut past the modal. (Ray, PR #1) */+ thing rather than a separate shortcut past the modal. */ .card-img-wrap { position: relative; } .card-fill-btn { position: absolute; top: 10px; right: 10px; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;⋯ 626 unchanged lines ⋯ 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) {- // A card whose info overlay is REVEALED (compact mode ⓘ) is being read:- // no tooltip may float over it (Ray: "the tooltip obscures the popover").+ // Nothing incidental may cover something the human deliberately opened.+ // This started as a card-scoped check for the compact-mode ⓘ overlay+ // (Ray: "the tooltip obscures the popover"), but the same complaint came+ // back with a DIFFERENT surface open: the guard only fired when the+ // hovered element sat inside a .shot-card whose OWN overlay was showing,+ // so any other open popover still got covered. Suppress over any of them. if (el.closest && el.closest('.shot-card')?.querySelector('.card-info.show')) return;- // ...and nothing incidental may cover ANY surface the human deliberately- // opened: another card's overlay, or the brand hover card (Ray, PR #4). if (document.querySelector('.hover-popover.show, .card-info.show')) return; // Rich (authored, trusted) HTML via el._ttHtml; else plain text. Dynamic // values interpolated into _ttHtml are escaped by the builders below.⋯ 31 unchanged lines ⋯ })(); // ── Helpers ──────────────────────────────────────────────────────────────+ // execCommand('copy') is deprecated but is the only thing that works in a+ // sandboxed frame with no clipboard permission, which is where this viewer+ // sometimes lives. Used only when navigator.clipboard is missing or rejects.+ function legacyCopy(text, done) {+ try {+ const ta = document.createElement('textarea');+ ta.value = text; ta.setAttribute('readonly', '');+ ta.style.cssText = 'position:fixed;top:-1000px;opacity:0';+ document.body.appendChild(ta); ta.select();+ const ok = document.execCommand('copy');+ document.body.removeChild(ta);+ if (ok && done) done();+ } catch (e) { /* nothing else to try; leave the label alone */ }+ } let toastTimer=null; function showToast(msg, ms){ toast.textContent=msg; toast.classList.add('show'); toast.classList.toggle('lifted', !!document.querySelector('.cdown')); // never under the countdown card⋯ 240 unchanged lines ⋯ if (e.tag){ const tg=document.createElement('span'); tg.className='rb-tag'; tg.textContent=e.tag; tg.setAttribute('data-tooltip','Handle "'+e.tag+'" given by the AI that injected this shot (--tag). Say "'+e.tag+'" to mean exactly this shot.'); rb.appendChild(tg); }+ // Copy the WHOLE reference, "#158 hero-final", not just one half. The ref+ // is globally unambiguous and the handle is what a human actually says, so+ // pasting both lets the AI resolve it either way and survives a handle+ // being reused on a later shot. Untagged shots copy just "#158".+ { const txt = '#' + e.id + (e.tag ? ' ' + e.tag : '');+ const cp = document.createElement('button'); cp.className = 'rb-copy';+ cp.textContent = 'copy'; cp.setAttribute('aria-label', 'Copy ' + txt);+ cp.setAttribute('data-tooltip', 'Copy "' + txt + '" — paste it to your AI to mean exactly this shot.');+ cp.addEventListener('click', function (ev) {+ ev.stopPropagation(); // never let it open the shot+ const done = function () {+ cp.textContent = 'copied'; cp.classList.add('done');+ setTimeout(function () { cp.textContent = 'copy'; cp.classList.remove('done'); }, 1200);+ };+ // The viewer also runs in a sandboxed readme frame where the async+ // clipboard API is unavailable; fall back rather than silently no-op.+ if (navigator.clipboard && navigator.clipboard.writeText) {+ navigator.clipboard.writeText(txt).then(done, function () { legacyCopy(txt, done); });+ } else { legacyCopy(txt, done); }+ });+ rb.appendChild(cp); } c.appendChild(rb); } // info (hidden in compact) -> refbar -> imgWrap (appended below) // COMPACT mode's reveal chip: hover (or tap, on touch) the ⓘ on the image // to overlay the full info block; it hides again when the pointer leaves⋯ 863 unchanged lines ⋯ </script> </body> </html>+
Comments
Log in to comment.