← Commit history

New dev skill pup-taskbar-identity: the birth-hwnd capture architecture (serialized set-diff, position-independent) and the mandatory ralph test (ralph_icons.sh) that must pass before claiming any taskbar-identity fix; records the ban on position-matching and log-only proof. Baseline: two warm 18-window runs, 0 failures each

John Lauer ·02d064f0ed ·2mo ago ·parent 7895c8b
2 files changed +121
dev-skills/pup-taskbar-identity/SKILL.mdadded+65
@@ -0,0 +1,65 @@+---+name: pup-taskbar-identity+description: DEV skill (source-only) - how pup maps each window to its OS taskbar button (AUMID tile + overlay badge), the hard-won architecture, and the RALPH TEST you must run before claiming any taskbar-identity fix works. Read before touching birth-hwnd capture, resolveSessionHwndByBounds, hwndBelongsToPup, stampPupIdentity, or applyAppOverlay.+---++# pup taskbar identity: birth-hwnd capture + the ralph gate++Every pup window must present as its own teal "Adom Pup" taskbar button (per-session AUMID) with a+favicon overlay badge. All pup windows share ONE Chrome process (the `adom-you` profile), so the+only way to address a specific window for the AUMID stamp and overlay is its **OS window handle+(hwnd)**. Getting that handle reliably is the entire problem, and it has broken ~a dozen times.++## THE RALPH RULE (John, 2026-08-09: "you need to do your own ralph test")++A SINGLE open looking right proves nothing — every taskbar-identity regression here passed a+one-window spot check and failed under repetition/concurrency. Before claiming ANY fix works, run+`ralph_icons.sh` (in this folder): 9 rounds cycling single / concurrent-3 / multi-tab-pair opens,+asserting each window gets a DISTINCT handle + a successful `stamped appId` + an `overlay PAINT/+ENFORCED`, and that no two sessions ever share a handle (COLLISION). Warm the bridge first+(`pup_readiness` until ok) — the first 1-2 rounds after a `bridge_install` respawn fail on ab+cold-start flakiness, NOT icon logic, and will mislead you. A clean run is 18/18 windows, 0 failures,+18 distinct handles. Baseline proof (2.0.39): two consecutive warm runs, 0 failures each.++```bash+bash dev-skills/pup-taskbar-identity/ralph_icons.sh 9+```++## How handle capture actually works (2.0.38, the version that finally held)++1. **Every** pup window on Windows is created via `createPageInNewWindow` (CDP+   `Target.createTarget {newWindow:true}`) — including the FIRST window on a profile. (Pre-2.0.37 the+   first window used `browser.newPage()`, which captures no handle; that window was permanently+   unresolvable and showed a raw Chrome button. THE bug John kept catching.)+2. Capture is a **serialized set-diff**, NOT position matching:+   - A global `_birthLock` serializes creation so only one window is born at a time.+   - Snapshot all Chrome `Chrome_WidgetWin_1` handles BEFORE `createTarget`; the ONE new handle+     after (not in the snapshot, not owned by another live session) is this window's, wherever+     Chrome placed it. **Do not identify the window by position** — Chrome clamps the -32000 birth+     coordinate on-screen on some boxes, so position matching finds nothing or cross-wires (the+     fusion+nb-shared-handle collision).+3. `hwndBelongsToPup` trusts a birth-captured handle as proof by construction (no title tag, no pid+   membership). `hwndOwnedByOtherSession` guards every resolve path so two sessions can never share+   one handle.+4. Only then does `stampPupIdentity` set the per-session AUMID (creates the teal button) and+   `applyAppOverlay` paint the favicon badge — deferred until the handle exists, stamp-once then+   overlay-once (see the AUMID/overlay ordering in server.js).++## Bans / traps (each shipped and hurt)++- **Never identify a window by screen position** — Chrome clamps off-screen births; positions+  collide under concurrency. Set-diff by handle only.+- **Never remove the title tag without replacing its handle-resolution role** — 2.0.24 killed the+  title tag purely as user-facing cosmetics, silently removing the shared-profile fallback that the+  stamp AND `hwndBelongsToPup` relied on. That single change caused weeks of raw-Chrome icons.+- **Never trust a "stamped ✓" log as proof the tile is right** — the log says the AUMID call+  succeeded; it does NOT prove Windows repainted the button or that the handle was the RIGHT window.+  Verify with the ralph handle-distinctness + a real taskbar screenshot.+- **Warm the bridge before asserting** — cold-start (post-install/idle respawn) flakiness is an ab+  lifecycle issue (ab#61), not icon logic; it produces false failures in rounds 1-2.++## Known remaining gap++Cold-start: the first 1-2 windows opened immediately after a bridge respawn can miss their handle+(ab wedge / slow spawn). `refresh_bridges` + a warm `pup_readiness` clears it. Tracked as the ab+lifecycle work; pup's own capture is clean once warm (see the ralph baseline).
dev-skills/pup-taskbar-identity/ralph_icons.shadded+56
@@ -0,0 +1,56 @@+#!/bin/bash+# Ralph test for pup taskbar identity: repeatedly open windows (single, concurrent, multi-tab),+# assert EVERY window gets a distinct OS handle + a successful AUMID stamp + an overlay paint,+# and NO collision (two sessions on one handle) and NO stamp FAILED. Closes what it opens each round.+TGT=AdomLapper+LOG=C:/Users/john/.adom/bridge-logs/puppeteer.log+ROUNDS=${1:-8}+ab() { adom-bridge-cli --target $TGT "$@" 2>&1; }+jget() { python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('$1',''))" 2>/dev/null; }++total_windows=0 fail=0+declare -A allhandles+for r in $(seq 1 $ROUNDS); do+  mode=$((r % 3))+  case $mode in+    1) threads=("rl-$r-a") ;;                                  # single+    2) threads=("rl-$r-a" "rl-$r-b" "rl-$r-c") ;;              # concurrent burst+    0) threads=("rl-$r-a" "rl-$r-b") ;;                        # pair+  esac+  # open (concurrent when >1)+  for t in "${threads[@]}"; do+    ab pup_open_window '{"url":"https://example.com","foreground":false}' --ai-thread "$t" >/dev/null 2>&1 &+  done+  wait+  # round 0: also add tabs to make them multi-tab+  if [ $mode -eq 0 ]; then+    for t in "${threads[@]}"; do+      ab pup_open_tab '{"url":"https://wiki.adom.inc/adom/pup-bridge"}' --ai-thread "$t" >/dev/null 2>&1+    done+  fi+  sleep 13+  # pull the log ONCE for this round and assert+  ab pull_file "{\"filePaths\":[\"${LOG//\//\\\\}\"],\"saveTo\":\"/tmp/ralphlog\"}" --ai-thread "pup-bridge build" >/dev/null 2>&1+  LF=/tmp/ralphlog/puppeteer.log+  for t in "${threads[@]}"; do+    sid="ai-$(echo "$t" | tr 'A-Z' 'a-z')"+    total_windows=$((total_windows+1))+    handle=$(grep -E "\"$sid\" resolved OS handle [0-9]+" "$LF" 2>/dev/null | tail -1 | sed -E 's/.*handle ([0-9]+).*/\1/')+    stamped=$(grep -cE "\"$sid\" stamped appId" "$LF" 2>/dev/null)+    failed=$(grep -cE "\"$sid\".*stamp FAILED" "$LF" 2>/dev/null)+    overlay=$(grep -cE "\"$sid\".*(PAINT|ENFORCED)" "$LF" 2>/dev/null)+    problem=""+    [ -z "$handle" ] && problem="$problem NO-HANDLE"+    [ "$stamped" = "0" ] && problem="$problem NOT-STAMPED"+    [ -n "$handle" ] && [ -n "${allhandles[$handle]}" ] && [ "${allhandles[$handle]}" != "$sid" ] && problem="$problem COLLISION-with-${allhandles[$handle]}"+    [ "$overlay" = "0" ] && problem="$problem NO-OVERLAY"+    [ -n "$handle" ] && allhandles[$handle]=$sid+    if [ -n "$problem" ]; then echo "  r$r $sid hwnd=$handle stamp=$stamped fail=$failed ovl=$overlay ->$problem"; fail=$((fail+1)); fi+  done+  # cleanup this round's windows+  for t in "${threads[@]}"; do ab pup_close_window '{}' --ai-thread "$t" >/dev/null 2>&1; done+  sleep 2+  echo "round $r ($([ $mode -eq 1 ] && echo single || ([ $mode -eq 2 ] && echo concurrent-3 || echo pair-multitab))): ${#threads[@]} windows, running fails=$fail"+done+echo "===================="+echo "RALPH DONE: $total_windows windows across $ROUNDS rounds | FAILURES: $fail | distinct handles: ${#allhandles[@]}"