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
skills/adom-shotlog-dev/SKILL.mddeleted−118@@ -1,118 +0,0 @@-name: adom-shotlog-dev-tier: dev-description: "DEV skill for building, debugging, and laying out adom-shotlog (the Rust CLI + server + embedded HTML viewer). Read before editing the viewer, the server, or the CLI. Covers the ralph-test-yourself loop, the hard fluid-layout rules (no outer-html scrollbars, ever; pretty scrollbars always), the build/deploy cycle, and the central-broker architecture. Trigger words: shotlog dev, build shotlog, shotlog layout, ralph test shotlog, viewer css, skinny webview layout, horizontal scrollbar, fluid layout, shotlog architecture, shotlog server."--# adom-shotlog - DEV (build, layout, architecture)--The app is one Rust binary: `src/main.rs` (CLI), `src/server.rs` (HTTP + WS +-webview brokering), `src/store.rs` (channels + presence), `src/open.rs` (Hydrogen-tabs), and `src/html/index.html` (the viewer, embedded via `include_str!`).--## ALWAYS ralph-test yourself after a layout change--Never trust a viewer change by eye at desktop width. Load it at the **skinny ~20%-webview width (≈360px)** - that's where the layout actually has to hold - screenshot-it, measure it, fix, repeat. The skinny view is the one that matters.--Headless ralph check (a real 360px VIEWPORT, so the CSS media queries fire - unlike-CSS-narrowing an element, which does not):--```js-// ralph-check.cjs - node ralph-check.cjs <live-viewer-url>-const puppeteer = require('/home/adom/project/node_modules/puppeteer-core');-(async()=>{- const b = await puppeteer.launch({executablePath: process.env.CHROME, headless:'new',- args:['--no-sandbox','--disable-gpu','--disable-dev-shm-usage']});- const p = await b.newPage();- await p.setViewport({width:360, height:820, deviceScaleFactor:2});- await p.goto(process.argv[2], {waitUntil:'networkidle2', timeout:25000}).catch(()=>{});- await new Promise(r=>setTimeout(r,1800));- console.log(JSON.stringify(await p.evaluate(()=>{- const de=document.documentElement, vw=de.clientWidth;- const off=[...document.querySelectorAll('*')].map(el=>{const r=el.getBoundingClientRect();- return {t:el.tagName+'.'+((''+el.className).split(' ')[0]||''), right:Math.round(r.right)};})- .filter(x=>x.right>vw+1).sort((a,b)=>b.right-a.right).slice(0,10);- return {vw, htmlScrollW:de.scrollWidth, outerHScroll: de.scrollWidth>vw, off};- })));- await b.close();-})();-```-`CHROME=$(ls -d ~/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome|head -1) node ralph-check.cjs <url>`-**Pass = `outerHScroll:false` AND `off:[]`.** If anything is in `off`, that element-is wider than the panel - make it fluid; don't paper over it with a scrollbar.--## HARD RULE: ONE designated scroller - the outer html NEVER shows a scrollbar--The outermost html/body never scroll, either axis: `html, body { overflow: hidden }`.-The `.feed` is the one designated scroller; overlays (modal) scroll themselves.-Nested outer+inner scrollbars are an instant audit failure.--Hard-won: `overflow-x: hidden` alone is NOT enough. At fractional devicePixelRatio-(a pup window at DPR 1.5), `100dvh` computes fractionally, `scrollHeight` rounds-1px past `clientHeight`, and a phantom OUTER vertical scrollbar appears beside the-feed's real one - even though a DPR-1 headless audit says everything fits. Root-`overflow: hidden` makes the phantom impossible. Audit BOTH axes in the ralph-check: `outerVScroll` and `outerHScroll` must both be false.--A skinny webview must never show a horizontal scrollbar on the outermost html. If-it does, the layout isn't fluid. Rules that keep it fluid:--- `html, body { overflow: hidden; max-width: 100%; }` (the guarantee), plus- `img { max-width: 100%; }`.-- **No fixed-width children.** The classic offender was the child-window row: it- used fixed `168px` cells + `overflow-x:auto`, which overflowed the ~360px panel- and made an ugly scrollbar. Fix: `flex-wrap: wrap` with cells `flex: 1 1 130px;- min-width: 0; max-width: 220px` so they share the row and wrap, never scroll.-- **Wrap the header at narrow widths** (`@media (max-width: 520px)`): stack it into- rows (brand / channel + count / actions) so nothing truncates or overflows.-- Long values wrap: `word-break: break-word` / `pre-wrap`, never a fixed min-width- that forces overflow.--## HARD RULE: scrollbars are ALWAYS pretty--Every scrollable area gets styled - thin, rounded, themed, transparent track,-teal on hover. Never ship a default OS scrollbar.--```css-* { scrollbar-width: thin; scrollbar-color: rgba(139,148,158,.38) transparent; }-::-webkit-scrollbar { width: 10px; height: 10px; }-::-webkit-scrollbar-track { background: transparent; }-::-webkit-scrollbar-thumb { background: rgba(139,148,158,.32); border-radius: 999px;- border: 2px solid transparent; background-clip: padding-box; }-::-webkit-scrollbar-thumb:hover { background: rgba(0,184,176,.55); background-clip: padding-box; }-```--## Build + deploy cycle--```bash-cargo build --release-# targeted restart of the ONE server (never a broad pkill):-kill "$(pgrep -f '[a]dom-shotlog serve' | head -1)"; sleep 1-sudo cp target/release/adom-shotlog /usr/local/bin/adom-shotlog-adom-shotlog serve & # data dir: ./screenshots/shotlog-```-`SKILL.md` (and the viewer HTML) are embedded via `include_str!` - **you must-rebuild** after editing them. Bump `Cargo.toml` + `VERSION` together.--## Architecture: one server = the central broker (per container)--- The **same binary** is both the server and the CLI. `adom-shotlog serve` spawns- ONE broker on **port 8820**; `inject`/`open`/`viewers`/`channels` are CLI verbs- that HTTP-POST/GET to it.-- **Every AI thread in the container** runs `adom-shotlog inject`, which posts to- that one local server. So the server is the central broker: it owns all channels,- tracks every viewer's surface + on-screen state (presence over the WebSocket),- 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.-- 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.