← Commit history

Thread Tiles: brand the taskbar button reliably + add an AUMID janitor. Write the per-thread AUMID registry IconResource even under minimal-touch so the thread-name tile wins over the default pup icon, and add a janitor plus uninstall cleanup that reap orphaned Adom.Pup registry keys and prior-generation tile icon files.

John Lauer ·e090821f42 ·2mo ago ·parent eefc0d1
2 files changed +71−16
adom-bridge-puppeteer-manifest.json+2−2
@@ -3,8 +3,8 @@   "name": "puppeteer",   "version": "2.0.161",   "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.161/adom-bridge-puppeteer-v2.0.161.zip",-  "sha256": "93e2695653173f79f7fd596cb2ecb1c9098e5d6a296681948b9521a903367c28",-  "size": 908047,+  "sha256": "cf373fcc082cf16d27cab394fe9cff5ad20669f84490f20e8fb1ce362f8e95f4",+  "size": 909406,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/server.js+69−14
@@ -4383,36 +4383,70 @@ async function renderThreadTileIco(page, name) { // <sid> is not a live session. Never touch the pinnable base id. function pruneStaleAumids() {   if (process.platform !== 'win32') return;+  // v2.0.161 — rewritten to key off the LIVE appIds directly, across EVERY AUMID shape+  // (per-session Adom.Pup.<sid>.<cat>.<gen>, category-group Adom.Pup.<cat>.<gen>, dashboard+  // Adom.Pup.Dashboard.<gen>, and THREAD TILES Adom.Pup.thread.<slug>.<gen>). The old code derived+  // a <sid> by stripping known suffixes and kept a key iff that sid was a live session — which+  // BROKE thread tiles: a thread key strips to "thread.<slug>.<gen>" (never a session id), so a+  // perfectly live thread's key looked orphaned and got deleted on every sweep, un-branding the+  // taskbar button. Instead: gather every appId any live session is (or is about to be) stamped+  // with — _curAppId (live), _frozenAppId (frozen), _desiredAppId (mid-open, set before the stamp+  // lands), the pre-registration _earlyStamped map, and _registeredPupAumids — and delete only the+  // Adom.Pup.* keys NOT in that set. The pinnable base launcher (Adom.Pup) always stays.+  const liveAppIds = new Set([PUP_APP_ID]);+  for (const [, s] of sessions) {+    for (const id of [s && s._curAppId, s && s._frozenAppId, s && s._desiredAppId]) { if (id) liveAppIds.add(id); }+  }+  for (const id of _earlyStamped.values()) { if (id) liveAppIds.add(id); }+  for (const id of _registeredPupAumids) { if (id) liveAppIds.add(id); }   try {     const out = execFileSync('reg', ['query', 'HKCU\\Software\\Classes\\AppUserModelId'],       { encoding: 'utf8', timeout: 8000, windowsHide: true });-    const live = new Set(sessions.keys());     let dropped = 0;     for (const line of out.split(/\r?\n/)) {-      const m = line.match(/\\(Adom\.Pup\.(.+))$/);+      const m = line.match(/\\(Adom\.Pup(?:\..+)?)\s*$/);       if (!m) continue;-      const appId = m[1], rest = m[2];-      if (appId === PUP_APP_ID) continue;                 // the pinnable launcher stays-      const sid = rest.replace(/\.i\d+$/, '').replace(/\.(wiki-in|wiki|app|web|mixed|plain)$/, '');-      // v1.9.189 (John's taskbar audit): a LIVE session can own SEVERAL keys, because every category-      // change registered a NEW AUMID and nothing removed the previous one (audit found one window-      // holding 3: .kimi-about-mockup, .kimi-about-mockup.app.i3, .kimi-about-mockup.plain.i3). Keep-      // only the key the session is CURRENTLY stamped with; drop its superseded ones.-      if (live.has(sid)) {-        const cur = (sessions.get(sid) || {})._curAppId;-        if (!cur || cur === appId) continue;   // current key (or unknown) — keep-      }+      const appId = m[1];+      if (appId === PUP_APP_ID || liveAppIds.has(appId)) continue;   // launcher + in-use keys stay       try {         execFileSync('reg', ['delete', `HKCU\\Software\\Classes\\AppUserModelId\\${appId}`, '/f'],           { encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'ignore' });         dropped++;       } catch (e) {}     }-    if (dropped) console.log(`[identity] pruned ${dropped} stale Adom.Pup.* AUMID key(s) from HKCU`);+    if (dropped) console.log(`[identity] pruned ${dropped} stale Adom.Pup.* AUMID key(s) from HKCU (kept ${liveAppIds.size - 1} live)`);   } catch (e) {}+  // The .ico tiles those keys pointed at also accumulate in %TEMP% across generation bumps+  // (t1→t2→t3, i6→i7). Keep ONLY the current-generation dir under each cache root; every sibling is+  // a prior gen no live key references. Cheap readdir, so it rides the same janitor cadence.+  pruneStaleIconGenDirs();+}+// v2.0.161 (John: "you need a janitor AND uninstall cleanup for the aumid icons you register"):+// reap prior-generation icon-cache directories so tile .ico files never pile up in %TEMP%.+function pruneStaleIconGenDirs() {+  const roots = [+    { dir: path.dirname(THREAD_TILE_DIR), keep: path.basename(THREAD_TILE_DIR) },   // pup-thread-tiles/<gen>+    { dir: path.dirname(_NOTCH_CACHE),    keep: path.basename(_NOTCH_CACHE) },       // pup-notched-icons/<gen>+  ];+  for (const { dir, keep } of roots) {+    try {+      if (!fs.existsSync(dir)) continue;+      for (const name of fs.readdirSync(dir)) {+        if (name === keep) continue;+        try { fs.rmSync(path.join(dir, name), { recursive: true, force: true, maxRetries: 2, retryDelay: 200 }); } catch (e) {}+      }+    } catch (e) {}+  } } async function unregisterPupAumid(appId) {   if (process.platform !== 'win32' || !appId || appId === PUP_APP_ID) return;+  // v2.0.161 THREAD TILES: a thread AUMID (Adom.Pup.thread.<slug>.<gen>) is SHARED by every window+  // of that thread, so closing ONE window must NOT unregister the key the siblings still wear —+  // that would un-brand their taskbar button and drop their jump list. Bail if any OTHER live+  // session is still stamped with this appId. (Harmless for unique per-session ids: none will match.)+  for (const [, s] of sessions) {+    if (s && (s._curAppId === appId || s._frozenAppId === appId || s._desiredAppId === appId)) return;+  }   _registeredPupAumids.delete(appId);   // v1.9.52: CLEAR this AUMID's jump list before unregistering it. Otherwise its CustomDestinations   // file lingers in the shell (measured: 6 stale jump-list files for 3 live windows, because every@@ -4422,6 +4456,14 @@ async function unregisterPupAumid(appId) {     try { await chrome.abCommand('desktop_set_window_jumplist', { appId, tasks: [] }, { timeoutMs: 5000 }); } catch (e) {}   }   try { await chrome.abCommand('desktop_unregister_app_identity', { appId }, { timeoutMs: 5000 }); } catch (e) {}+  // v2.0.161: also drop the HKCU IconResource key DIRECTLY. Under minimal-touch we write that key via+  // reg.exe (AD's register roundtrip is skipped), so AD's unregister above may leave it behind — delete+  // it here so a closed window's key never lingers until the 5-min janitor. reg.exe is idempotent+  // (a missing key just errors, swallowed). The sibling-guard above already protects shared thread keys.+  try {+    execFileSync('reg', ['delete', `HKCU\\Software\\Classes\\AppUserModelId\\${appId}`, '/f'],+      { encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'ignore' });+  } catch (e) {} } // v1.9.320 (John: "i turned OFF jump lists in settings but i still see them in the taskbar. did you // ralph test this at all?"). No — I only tested ON. Toggling OFF never CLEARED the already-committed@@ -13085,6 +13127,11 @@ const server = http.createServer(async (req, res) => {             { path: P('pup-annotate-extension'), label: 'annotate extension' },             { path: P('pup-avatars'), label: 'pup-avatars' },             { path: path.join(require('os').tmpdir(), 'pup-badge-debug'), label: 'badge-debug temp' },+            // v2.0.161: the AUMID icon caches (ALL generations) — thread-name tiles + notched category+            // tiles the taskbar keys pointed at. The registry keys are wiped below; wipe their .ico+            // files too so uninstall leaves zero pup footprint in %TEMP%.+            { path: path.join(require('os').tmpdir(), 'pup-thread-tiles'), label: 'thread-tile icons (AUMID)' },+            { path: path.join(require('os').tmpdir(), 'pup-notched-icons'), label: 'category tile icons (AUMID)' },           ];           const fileTargets = [             { path: PUP_SETTINGS_PATH, label: 'pup-settings.json' },@@ -17031,6 +17078,14 @@ const _onPupBound = async () => {   // v1.9.279: the reconciler runs UNGATED (bookkeeping + identity only, never a process kill), so   // closed windows are reaped and stamp/settings drift is corrected within seconds, in every mode.   setInterval(() => { reconcileModel('periodic').catch(() => {}); }, 8000);+  // v2.0.161 (John: AUMID icons need a JANITOR, not just close-time cleanup): sweep orphaned+  // Adom.Pup.* registry keys + prior-generation .ico caches on a slow cadence, so leaks from+  // generation bumps, renamed/closed threads, and crashed windows never accumulate over a long+  // uptime. The 60s startup prune skips an empty session map (boot-recovery guard); this steady-state+  // sweep runs every 5 min and reaps even when nothing is live (an empty map long after boot means+  // the keys are genuinely orphaned, and a re-stamp self-heals the rare reclaim race). reg.exe is+  // only spawned every 5 min, so it adds no ab-log noise.+  setInterval(() => { try { pruneStaleAumids(); } catch (e) {} }, 300000);   // Build the notched category tiles once at boot (John's clean-backdrop-for-the-overlay idea).   // Fire-and-forget: pupIconPath serves the un-notched tile until this finishes, then it re-stamps.   ensureNotchedIcons().catch(() => {});