skill
Adom UI Design System
Public Made by Adomby adom
How the AI builds and tests interfaces people love to use in Adom. Ships as one installable skill that bundles focused skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths).
← Commit history
2.2.0: dash-screenshot - capture ONLY your dashboard's webview pane in ah, not the whole window, with the tool
3 files changed
+232−1
assets/tools/shot-webview.pyadded+100@@ -0,0 +1,100 @@+#!/usr/bin/env python3+"""Capture ONLY a dock dashboard's webview pane inside ah.++A full-window hd_shot spends ~80% of its pixels on ah's chrome. This activates your tab,+reads your iframe's rect from the shell target, takes the shot, derives the scale from+hd_shot's OWN windowOuter metadata (never hardcoded), and crops to your pane.++ python3 shot-webview.py --tab "Parts Search" --slug dock-parts-search \+ --thread "my ai thread" --out /tmp/frame.png+ ... --box 33,160,216,240,red --box 249,160,424,240,lime # your page's coords+"""+import argparse, json, subprocess, sys++def bridge(verb, payload, thread, reason):+ cmd = ['adom-bridge', '--target', 'AdomLapper', verb, json.dumps(payload),+ '--ai-thread', thread, '--reason', reason]+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=180)+ try:+ return json.loads(p.stdout)+ except Exception:+ sys.exit('bridge call failed: ' + (p.stdout or p.stderr)[:400])++def cdp(js, target, thread, reason):+ r = bridge('hd_api', {"method": "POST", "path": "/cdp-eval",+ "body": {"target": target, "js": js}}, thread, reason)+ return (r.get('body') or {}).get('value')++def main():+ ap = argparse.ArgumentParser()+ ap.add_argument('--tab', required=True, help='tab name exactly as the tab strip shows it')+ ap.add_argument('--slug', required=True, help='a unique substring of your iframe src, e.g. your dock slug')+ ap.add_argument('--thread', required=True, help='your ai thread name, for the activity log')+ ap.add_argument('--out', default='/tmp/webview.png')+ ap.add_argument('--pad', type=int, default=0, help='extra px of ah chrome to keep around the pane')+ ap.add_argument('--box', action='append', default=[],+ help='x,y,w,h,colour in YOUR page coordinates; repeatable')+ a = ap.parse_args()++ # 1. ACTIVATE. An unforegrounded tab reports zero-height rects and SPECIFIED css.+ act = cdp("""(() => {+ const t = [...document.querySelectorAll('.tab-bar .tab')]+ .find(x => ((x.querySelector('span.tab-name')||{}).textContent||'').trim() === %s);+ if (!t) return 'NOTAB';+ if (!t.classList.contains('active')) t.click();+ return 'active';+ })()""" % json.dumps(a.tab), 'shell', a.thread,+ f'activate {a.tab} before measuring, an unforegrounded tab measures wrong')+ if act != 'active':+ sys.exit(f'could not activate tab {a.tab!r}: {act}')++ # 2. RECT, from the shell. Your own page cannot see where it sits in the window.+ raw = cdp("""(() => {+ const f = [...document.querySelectorAll('iframe')]+ .find(x => (x.getAttribute('src')||'').includes(%s));+ if (!f) return 'NOFRAME';+ const r = f.getBoundingClientRect();+ return JSON.stringify({x:Math.round(r.x), y:Math.round(r.y),+ w:Math.round(r.width), h:Math.round(r.height),+ shellW:innerWidth, dpr:devicePixelRatio});+ })()""" % json.dumps(a.slug), 'shell', a.thread,+ f'read the {a.slug} pane rect for a webview-only capture')+ if not raw or raw == 'NOFRAME':+ sys.exit(f'no iframe matching {a.slug!r}')+ rect = json.loads(raw)+ if rect['w'] == 0 or rect['h'] == 0:+ sys.exit('pane rect is zero: the tab did not actually foreground')++ # 3. SHOT, then scale from the RETURNED metadata. Never hardcode a scale or an origin.+ shot = bridge('hd_shot', {"region": "full"}, a.thread, f'capture the {a.tab} pane')+ safe = shot.get('localSafePath') or shot.get('safePath')+ if not safe:+ sys.exit('hd_shot did not return localSafePath: ' + json.dumps(shot)[:300])++ from PIL import Image, ImageDraw+ im = Image.open(safe).convert('RGB')+ # Scale from the SHELL'S CSS VIEWPORT WIDTH, because the rect above is in CSS px.+ # NOT from hd_shot's windowOuter: that is the OS window in PHYSICAL px (2582 where the+ # CSS viewport is 1706 at dpr 1.5), and using it produces a crop about 34% too small+ # that still looks like a plausible picture of a dashboard. Measured, not assumed.+ scale = im.width / float(rect['shellW'])++ if a.box:+ d = ImageDraw.Draw(im)+ for spec in a.box:+ x, y, w, h, colour = spec.split(',')+ x, y, w, h = (int(v) for v in (x, y, w, h))+ d.rectangle([(rect['x']+x)*scale, (rect['y']+y)*scale,+ (rect['x']+x+w)*scale, (rect['y']+y+h)*scale],+ outline=colour.strip(), width=3)++ p = a.pad+ im.crop((max(0, int((rect['x']-p)*scale)), max(0, int((rect['y']-p)*scale)),+ min(im.width, int((rect['x']+rect['w']+p)*scale)),+ min(im.height, int((rect['y']+rect['h']+p)*scale)))).save(a.out)++ print(json.dumps({"out": a.out, "paneRect": rect,+ "scale": round(scale, 4), "fullForShotlog": shot.get('localFullPath')}, indent=1))++if __name__ == '__main__':+ main()
package.json+3−1@@ -1,6 +1,6 @@ { "slug": "adom-ui-design",- "version": "2.1.7",+ "version": "2.2.0", "type": "skill", "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.", "dependencies": {},@@ -13,6 +13,7 @@ "assets/icons/action-send.svg", "assets/icons/settings-cog-outline.svg", "assets/module/newbie-panel.js",+ "assets/tools/shot-webview.py", "install.sh", "page.json", "screenshots/tokens.png",@@ -22,6 +23,7 @@ "skills/dark-default/SKILL.md", "skills/dash-demo/SKILL.md", "skills/dash-header/SKILL.md",+ "skills/dash-screenshot/SKILL.md", "skills/dock-dashboard/SKILL.md", "skills/floating-windows/SKILL.md", "skills/icons/SKILL.md",
skills/dash-screenshot/SKILL.mdadded+129@@ -0,0 +1,129 @@+---+name: dash-screenshot+user-invocable: true+description: >-+ Screenshot ONLY your dock dashboard's webview pane inside ah, not the whole Hydrogen+ window. A full-window hd_shot spends about 80% of its pixels on ah's rail, tab strip,+ editor and status bar, and after the Claude-safe resize your dashboard's 14px text is+ unreadable and its colours are resampled, so every measurement you try to eyeball from it+ is wrong. This skill gives the exact crop recipe (activate the tab by name, read the+ iframe rect from the shell target, derive the scale from hd_shot's own metadata, crop),+ the ready-made script, and the traps: unforegrounded tabs report zero-height rects, and+ the capture is scaled so a hardcoded offset silently drifts. Read BEFORE capturing any+ dock dashboard frame for a review, an issue, or the shotlog. Trigger words: screenshot the+ dashboard, capture my panel, dock dashboard screenshot, hd_shot region, crop the webview,+ frame for the shotlog, show my dashboard in ah, webview only screenshot.+---++Parent skill: **adom-ui-design** (works with **dock-dashboard**, **newbie-panel**)++# Screenshot the webview, not the whole of ah++**The problem, John's words, 2026-08-25:** a dock thread capturing its dashboard "wastes+getting a screenshot of ALL of ah's UI".++At his working layout the ah window is about 1706 logical px wide and a dock app's pane is+**744**. So a full-window shot spends roughly **80% of its pixels on ah's chrome** and gives+your dashboard about a fifth of the frame. Then `hd_shot` resizes the result to 1400px for+Claude-safe reading, which shrinks your pane again. Your 14px phrases land around 9px, the+`0.67px` borders you are trying to check disappear into resampling, and any colour you read+off it has been through two resamples. **You cannot verify a panel from a picture of a+window.**++## There is no built-in region for this yet++`hd_shot` regions are deterministic and named, and today the list is:++```+full · titlebar · vscode · setup-panel · wiki · claude+```++**None of them is "the dock app's webview".** Adding one means extending `KNOWN_REGIONS` and+`region_selector()` in `hd_bridge.rs`, which belongs to the ah build thread. Until that lands,+crop it yourself with the recipe below. When it lands, use the region and delete your crop+code.++`hd_api` has no per-target screenshot route either (39 endpoints, the only relevant ones are+`GET /cdp-info` and `POST /eval`), so there is no way to ask ah for just your frame.++## The recipe++Three steps, and the third is the one people get wrong.++**1. ACTIVATE YOUR TAB BY NAME FIRST.** An unforegrounded tab reports **zero-height rects and+specified rather than computed CSS**. Every measurement you take from one is garbage, and it+is garbage that looks like a number. This has produced at least three wrong conclusions in+this fleet.++```js+// run against target "shell"+const t = [...document.querySelectorAll('.tab-bar .tab')]+ .find(x => ((x.querySelector('span.tab-name')||{}).textContent||'').trim() === 'Parts Search');+if (!t) return 'NOTAB';+if (!t.classList.contains('active')) t.click();+```++**2. READ YOUR IFRAME'S RECT FROM THE SHELL TARGET**, not from inside your own page. Your page+does not know where it sits in the window, and `window.frameElement` is cross-origin.++```js+// run against target "shell", after activating+const f = [...document.querySelectorAll('iframe')]+ .find(x => (x.getAttribute('src')||'').includes('dock-parts-search')); // your dock slug+const r = f.getBoundingClientRect();+return JSON.stringify({x: Math.round(r.x), y: Math.round(r.y),+ w: Math.round(r.width), h: Math.round(r.height)});+```++Only the ACTIVE tab's iframe has a non-zero rect. If you get `0,0,0,0`, step 1 did not take.++**3. DERIVE THE SCALE FROM hd_shot's OWN METADATA. NEVER HARDCODE IT.**++`hd_shot` returns `windowOuter:[w,h]` and the safe copy is resized to `resizeMax` (1400 by+default). So:++```python+SCALE = image.width / windowOuter[0]+```++**Do not hardcode a scale or an origin.** An earlier version of this pipeline used+`SCALE = im.width / 1706.0` with a fixed `FX, FY = 963, 117`, which worked only for one+window size and one rail state, and drifts silently the moment John resizes the window,+opens the dock rail, or moves the pane. Silently: the boxes just land slightly wrong and the+frame still looks plausible.++## The script++`assets/tools/shot-webview.py` in this pack does all three. Point it at your tab name and+your dock slug:++```sh+python3 shot-webview.py --tab "Parts Search" --slug dock-parts-search \+ --thread "your ai thread name" --out /tmp/frame.png+```++It activates the tab, reads the rect, takes the full shot, derives the scale from the+returned `windowOuter`, crops, and prints the path. Add `--box x,y,w,h,colour` to outline an+element you measured (in your page's own coordinates; it converts them for you).++## Then inject it, do not just look at it++```sh+adom-shotlog inject -c dock-dashboards -d "<what this shows>" --orig-file <full.png> <safe.png>+```++`--orig-file` matters: shotlog rejects a pre-shrunk image that cannot declare its original+resolution, so the viewer never has to show "orig missing".++## Traps++- **A shot of an unforegrounded tab looks fine and measures wrong.** Activate first, always.+- **`hd_shot` falls back to the full window when a region is not found**, and still returns+ `ok:true` with `regionFound:false`. Check that field rather than assuming you got a region.+- **`webview open-or-refresh` can report `{"action":"refreshed"}` without navigating.** Force+ the reload from inside the page (`location.replace` with a cache-buster, or CDP+ `Page.reload {ignoreCache:true}`) and confirm the version rendered.+- **Read the SAFE path, archive the FULL path.** The full-res PNG can exceed the per-image+ budget; the safe copy is already sized for reading.+- **Never open a webview in another workspace to take a picture of it.** Dock apps verify in+ ah only.