← Commit history

v1.9.19: one session one window, monochrome category icons per the brand icon law, and emoji stripped from user-visible strings

John Lauer ·4ca092bb72 ·3mo ago ·parent d27d88d
10 files changed +151−30
CHANGELOG.md+32
@@ -1,5 +1,37 @@ # Changelog — Adom Desktop Puppeteer Bridge +## 1.9.19 — 2026-07-20 — One session one window, category icons, and no emoji anywhere++Three things, in the order John asked for them.++1. ONE SESSION = ONE OS WINDOW. browser.newPage() opens a TAB in whatever window a profile already+   has, so two sessions sharing the adom-wiki-authed jar collapsed into a single window. A window+   title reflects only its ACTIVE tab, so AD could not resolve "the window titled (session: <id>)"+   for the background one, and every per-window operation keyed on that title silently failed:+   icon stamp, taskbar overlay, flash, jump list. That is why the login icon would not repaint+   after a view toggle. Sessions now get their own window via CDP Target.createTarget+   {newWindow:true}, same cookie jar, separate window.++2. CATEGORY ICONS. A pup window's icon now says what KIND of thing it is showing. Each is the teal+   Adom tile carrying one monochrome #e6edf3 glyph on the 24x24 house grid, per the brand icon law+   (custom hand-drawn, single colour, no gradients, no shadows, no emoji):+     wiki      open book, outline    Adom wiki, public+     wiki-in   open book, SOLID      Adom wiki, signed in+     app       window + prompt       an Adom app (localhost or a cloud slug proxy URL)+     web       globe                 anything else on the internet+     mixed     stacked windows       tabs spanning more than one category+   Solid vs hollow is the one difference that survives Windows painting the button at 24px; a+   padlock turned to mush there. Mixed content gets its own icon rather than picking a winner.++3. NO EMOJI. The brand rule is absolute ("no emoji, anywhere"), and the bridge was full of them.+   Tab titles now lead with a monochrome geometric TEXT glyph, U+25CF solid for signed in and+   U+25CB hollow for public, mirroring the icons. Jump-list titles dropped their emoji entirely+   (the task already carries a real icon), and captions are plain words. The title strip regex+   clears both the new glyphs and legacy emoji so old windows heal instead of stacking prefixes.++Also reverted the green overlay plate from 1.9.16 to 1.9.18: the category icon carries login state+now, so tinting the favicon plate was a redundant and off-law second colour signal.+ ## 1.9.18 — 2026-07-20 — Fix: the green plate never painted (overlay ran before auth was known)  The 1.9.16/1.9.17 plate colour was correct but never appeared. Ordering bug in browser_open_window:
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.9.18",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.18/adom-bridge-puppeteer-v1.9.18.zip",-  "sha256": "a5e8553f6e7398eef66c7616fc7ac02796616d0d58562395116321c726be60bf",-  "size": 236631,+  "version": "1.9.19",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.19/adom-bridge-puppeteer-v1.9.19.zip",+  "sha256": "8964a74ad67104723bcf6a7e08931455118242d468798c807eb63926470b9474",+  "size": 306883,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-07-20T01:00:00Z"+  "released_at": "2026-07-20T01:30:00Z" }
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.18\ No newline at end of file+1.9.19\ No newline at end of file
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.9.18",+  "version": "1.9.19",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. browser_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. browser_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/pup-cat-app.svgadded+1
@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 244"><rect x="2" y="2" width="240" height="240" rx="35" fill="#00b8b0"/><g transform="translate(47,47) scale(6.25)" fill="none" stroke="#e6edf3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 8.5h18"/><path d="M7 12.5l2.4 2.4L7 17.3"/><path d="M12.4 17.3h4.4"/></g></svg>\ No newline at end of file
src/pup-cat-mixed.svgadded+1
@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 244"><rect x="2" y="2" width="240" height="240" rx="35" fill="#00b8b0"/><g transform="translate(47,47) scale(6.25)" fill="none" stroke="#e6edf3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="8" width="13" height="12" rx="2"/><path d="M2.5 11.5h13"/><path d="M8.5 4h11a2 2 0 0 1 2 2v9.5"/></g></svg>\ No newline at end of file
src/pup-cat-web.svgadded+1
@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 244"><rect x="2" y="2" width="240" height="240" rx="35" fill="#00b8b0"/><g transform="translate(47,47) scale(6.25)" fill="none" stroke="#e6edf3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.5 2.7 3.8 5.8 3.8 9s-1.3 6.3-3.8 9c-2.5-2.7-3.8-5.8-3.8-9S9.5 5.7 12 3z"/></g></svg>\ No newline at end of file
src/pup-cat-wiki-in.svgadded+1
@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 244"><rect x="2" y="2" width="240" height="240" rx="35" fill="#00b8b0"/><g transform="translate(47,47) scale(6.25)" fill="#e6edf3" stroke="none"><path d="M12 6.5C10.5 4.8 8.3 4 6 4H3v13h3c2.3 0 4.5.8 6 2.5"/><path d="M12 6.5C13.5 4.8 15.7 4 18 4h3v13h-3c-2.3 0-4.5.8-6 2.5"/><path d="M12 6.5v13"/></g></svg>\ No newline at end of file
src/pup-cat-wiki.svgadded+1
@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 244 244"><rect x="2" y="2" width="240" height="240" rx="35" fill="#00b8b0"/><g transform="translate(47,47) scale(6.25)" fill="none" stroke="#e6edf3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6.5C10.5 4.8 8.3 4 6 4H3v13h3c2.3 0 4.5.8 6 2.5"/><path d="M12 6.5C13.5 4.8 15.7 4 18 4h3v13h-3c-2.3 0-4.5.8-6 2.5"/><path d="M12 6.5v13"/></g></svg>\ No newline at end of file
src/server.js+107−23
@@ -1702,20 +1702,63 @@ let _jumplistSupported = null;   // null = unprobed (desktop_set_window_jumplist // downscaling; fine shape detail does not), and the padlock adds the semantic meaning on top. // The OVERLAY slot is NOT used for this — it belongs to the active tab's favicon (v1.9.0). // NEW FILENAME on any art change, always: the shell + AD cache HICONs by PATH (stale-tile trap).+// v1.9.19: CATEGORY ICONS. A pup window's taskbar icon says WHAT KIND of thing it is showing.+// Every icon is the teal Adom tile (the pup identity, the container) carrying ONE monochrome+// #e6edf3 glyph drawn on the 24x24 house grid, per the brand icon law: custom hand-drawn, single+// colour, no gradients, no shadows, NO EMOJI. Glyphs match the shipped family style (see the brand+// page icons: symbol, step, shotlog).+//   wiki     open book, outline   Adom wiki, public / logged out+//   wiki-in  open book, SOLID     Adom wiki, signed in   (solid vs hollow is the one mass change+//                                 that survives Windows painting the button at ~24px; a padlock+//                                 turned to mush at that size)+//   app      window + prompt      an Adom app: localhost or a cloud slug proxy URL+//   web      globe                anything else on the internet+//   mixed    stacked windows      tabs spanning more than one category const PUP_ICON_DEFAULT = 'adom-pup-browser3.ico';-const PUP_ICON_WIKI_AUTHED = 'adom-pup-wiki-authed3.ico';+const PUP_CAT_ICONS = {+  wiki: 'pup-cat-wiki1.ico',+  'wiki-in': 'pup-cat-wiki-in1.ico',+  app: 'pup-cat-app1.ico',+  web: 'pup-cat-web1.ico',+  mixed: 'pup-cat-mixed1.ico',+};+function classifyUrl(u) {+  try {+    if (!u) return null;+    if (isAdomUrl(u)) return 'wiki';+    const h = new URL(u).hostname;+    if (/^(localhost|127\.0\.0\.1|\[::1\])$/i.test(h)) return 'app';+    if (/\.adom\.cloud$/i.test(h) && /\/proxy\//.test(new URL(u).pathname)) return 'app';+    if (/^(about|chrome|edge|devtools):/i.test(u) || u.startsWith('file:')) return null; // ignore chrome pages+    return 'web';+  } catch (e) { return null; }+}+// The window's category across ALL its tabs. One kind -> that kind. Several -> 'mixed'.+function pupCategory(sessionId) {+  try {+    const s = sessionId && sessions.get(sessionId);+    if (!s) return null;+    const kinds = new Set();+    for (const t of (s.tabs || [])) {+      let k = null;+      try { k = classifyUrl(t.page && t.page.url()); } catch (e) {}+      if (k) kinds.add(k);+    }+    if (!kinds.size) return null;+    if (kinds.size > 1) return 'mixed';+    const only = [...kinds][0];+    if (only === 'wiki' && s._wikiAuthed) return 'wiki-in';+    return only;+  } catch (e) { return null; }+} function pupSessionAuthed(sessionId) {   try { const s = sessionId && sessions.get(sessionId); return !!(s && s._wikiAuthed); } catch (e) { return false; } } function pupIconPath(sessionId) {-  return path.join(__dirname, 'icons', pupSessionAuthed(sessionId) ? PUP_ICON_WIKI_AUTHED : PUP_ICON_DEFAULT);+  const cat = pupCategory(sessionId);+  const file = (cat && PUP_CAT_ICONS[cat]) || PUP_ICON_DEFAULT;+  return path.join(__dirname, 'icons', file); }-// v1.9.11: the RelaunchCommand half of the Relaunch* trio (see stampPupIdentity) — Windows will-// not honor RelaunchIconResource for the jump-list header row unless RelaunchCommand is set too.-// It is also literally what a header click / taskbar PIN launches, so it must genuinely refocus-// THIS session. browser_focus_window is gated (foreground_reason_required), so we supply an-// honest reason: a header/pin click IS the user asking to see the window. Inner quotes are-// BACKSLASH-escaped because this is a Windows command line (same argv trap as jump-list args). function pupRelaunchCommand(sessionId) {   const cli = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'Adom Desktop', 'adom-desktop-cli.exe');   const payload = JSON.stringify({ sessionId, foregroundReason: 'user clicked the Adom Pup taskbar header or pinned launcher' }).replace(/"/g, '\\"');@@ -1753,7 +1796,7 @@ async function stampPupIdentity(sessionId) {     // brand an UNREGISTERED AUMID's button, and the registered plain "Adom.Pup" stays as the     // pinnable Start Menu launcher.     const r = await chrome.adCommand('desktop_set_window_identity', {-      titleContains: `(session: ${sessionId}`, cacheKey: `pup-${sessionId}-${pupSessionAuthed(sessionId) ? 'auth' : 'plain'}`,+      titleContains: `(session: ${sessionId}`, cacheKey: `pup-${sessionId}-${pupCategory(sessionId) || 'plain'}`,       appId: pupSettings().taskbarGrouping === 'grouped' ? PUP_APP_ID : `${PUP_APP_ID}.${sessionId}`, displayName: PUP_DISPLAY_NAME, iconPath: pupIconPath(sessionId),       // v1.9.11 (AD ≥1.9.141): brand the taskbar RIGHT-CLICK MENU HEADER row (it showed the       // chrome.exe icon for months). There is NO "ApplicationIcon" registry value on AUMID keys@@ -1793,7 +1836,7 @@ const _registeredSessionAumids = new Map(); async function registerPerSessionAumid(appId) {   if (_identitySupported === false || appId === PUP_APP_ID) return;   const sid = appId.startsWith(PUP_APP_ID + '.') ? appId.slice(PUP_APP_ID.length + 1) : null;-  const state = pupSessionAuthed(sid) ? 'auth' : 'plain';+  const state = pupCategory(sid) || 'plain';   if (_registeredSessionAumids.get(appId) === state) return; // already registered in THIS state   try {     const r = await chrome.adCommand('desktop_register_app_identity', {@@ -1867,9 +1910,9 @@ async function updateWikiJumplist(session, sessionId) {   const taskArgs = (v) => `browser_wiki_set_view "${JSON.stringify({ sessionId, view: v }).replace(/"/g, '\\"')}"`;   let tasks = [];   if (view === 'authed') {-    tasks = [{ title: '\u{1F4D6} Adom wiki: switch to public view', description: 'Reload this Adom wiki page logged OUT — exactly what the public/world sees', iconPath: _taskIcon, args: taskArgs('public') }];+    tasks = [{ title: 'Adom wiki: switch to public view', description: 'Reload this Adom wiki page logged OUT — exactly what the public/world sees', iconPath: _taskIcon, args: taskArgs('public') }];   } else if (view === 'public') {-    tasks = [{ title: '\u{1F513} Adom wiki: switch to logged-in view', description: 'Reload this Adom wiki page SIGNED IN — private source, drafts, owner-only cards', iconPath: _taskIcon, args: taskArgs('authed') }];+    tasks = [{ title: 'Adom wiki: switch to logged-in view', description: 'Reload this Adom wiki page SIGNED IN — private source, drafts, owner-only cards', iconPath: _taskIcon, args: taskArgs('authed') }];   }   // The shell ignores a jump list on an unregistered AUMID — ensure it's registered first   // (idempotent; only registers when there IS a menu to show).@@ -2038,8 +2081,9 @@ async function checkWikiAuth(page) { } // v1.8.97: gentle IN-TAB mode indicator (John: "indicate which mode that pup window is in ... // in the chrome ui in a gentle way"). Can't touch the native tab-strip empty space (browser UI,-// not reachable), but the TAB TITLE is — a single leading glyph shows right on the tab: 🔓 =-// logged-in wiki view, 📖 = public/anonymous. Idempotent + MutationObserver so the SPA can't wipe+// not reachable), but the TAB TITLE is: a single leading monochrome glyph shows on the tab.+// U+25CF solid = logged-in wiki view, U+25CB hollow = public/anonymous (geometric text glyphs,+// NOT emoji, per the brand icon law). Idempotent + MutationObserver so the SPA can't wipe // it, and it doesn't fight the session-suffix injector (that touches the END, this the START). // v1.9.1: WORD label, not a cryptic emoji (John saw a bare 📖 and had no idea what it meant). // The tab title leads with "🔓 Logged in · " or "📖 Public · " so the mode is unmistakable.@@ -2048,7 +2092,9 @@ async function setWikiModeGlyph(page, label) {     try {       window.__pupWikiLabel = lbl;       const prefix = lbl + ' · ';-      const strip = (t) => t.replace(/^(?:🔓 Logged in|📖 Public) · /, '');+      // v1.9.19: strip the CURRENT monochrome prefixes and any LEGACY emoji one, so a window+      // carried over from an older bridge heals instead of stacking two prefixes.+      const strip = (t) => t.replace(/^(?:[\u25CF\u25CB]|\uD83D[\uDD13\uDCD6])\s?(?:Logged in|Public)\s\u00b7\s/, '');       const ensure = () => { try { const cur = document.title || ''; if (!cur.startsWith(prefix)) document.title = prefix + strip(cur); } catch (e) {} };       ensure();       const t = document.querySelector('title') || document.head || document.documentElement;@@ -2104,8 +2150,11 @@ async function applyAppOverlay(session, sessionId) {     // recolour the PLATE it sits on. Signed in to the Adom wiki -> green plate; everything else     // -> the normal dark plate. You still see which site you're on AND the wiki login state, and     // at 16px the plate is a far bigger colour target than any glyph tucked in the base icon.-    const plateAuthed = !!session._wikiAuthed && isAdomUrl((() => { try { return activePage.url(); } catch (e) { return null; } })());-    const plateFill = plateAuthed ? '#3fb950' : '#0b2325'; // #3fb950 = the Adom design-system --green token ("success only"), NOT an ad-hoc green+    // v1.9.19: plate is ALWAYS the neutral dark one again. Login state moved to the CATEGORY ICON+    // (monochrome, per the brand icon law), so tinting this plate would be a second, redundant and+    // off-law colour signal. The favicon keeps this slot untouched, which is the whole point of it.+    const plateAuthed = false;+    const plateFill = '#0b2325';     const cacheKey = sessionId + ':' + (src.href || 'explicit') + ':' + (plateAuthed ? 'auth' : 'plain');     let overlay = _appOverlayCache.get(cacheKey);     if (!overlay) {@@ -3503,6 +3552,28 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer   } } +// v1.9.19: create a page in a BRAND NEW OS WINDOW of an existing browser. puppeteer's newPage()+// always makes a tab; only CDP Target.createTarget accepts newWindow. Page identity is resolved by+// diffing browser.pages() before/after rather than matching internal target ids, so this stays+// correct across puppeteer versions. Returns null if the window never materialises.+async function createPageInNewWindow(browser) {+  let cdp = null;+  try {+    const before = new Set(await browser.pages());+    cdp = await browser.target().createCDPSession();+    await cdp.send('Target.createTarget', { url: 'about:blank', newWindow: true });+    for (let i = 0; i < 50; i++) {+      const fresh = (await browser.pages()).find(p => !before.has(p));+      if (fresh) return fresh;+      await new Promise(r => setTimeout(r, 100));+    }+    return null;+  } catch (e) {+    console.log(`[window] createPageInNewWindow failed: ${e.message}`);+    return null;+  } finally { try { if (cdp) await cdp.detach(); } catch (e) {} }+}+ // Launch a session as a tab in a Chrome browser // profile: which Chrome profile to use (defaults to sessionId) // If another session already uses that profile, this creates a NEW TAB in the same Chrome@@ -3599,13 +3670,26 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st   const claimedPages = new Set();   for (const s of sessions.values()) { if (s.page) claimedPages.add(s.page); }   const existingPages = await browser.pages();-  let page = existingPages.find(p => {+  // v1.9.19: ONE SESSION = ONE OS WINDOW, even when sessions share a profile.+  // browser.newPage() opens a TAB in whatever window the profile already has, so two sessions on+  // the shared adom-wiki-authed jar collapsed into one window. A window's title reflects only its+  // ACTIVE tab, so AD could not resolve "the window titled (session: <id>)" for the background+  // one -- every per-window operation keyed on that title silently failed (icon stamp, taskbar+  // overlay, flash, jump list), which is why the login icon would not repaint after a view toggle.+  // Fix: when another live session already owns a window on this profile, create the page in its+  // OWN window via CDP Target.createTarget {newWindow:true}. Same cookie jar, separate window.+  const profileAlreadyInUse = [...sessions.values()].some(s => s.profileName === profileName && s.page);+  let page = profileAlreadyInUse ? null : existingPages.find(p => {     if (claimedPages.has(p)) return false;     try { return isBlank(p.url()); } catch { return false; }   });    if (page) {     console.log(`Reusing existing blank tab (${page.url()}) for session "${sessionId}"`);+  } else if (profileAlreadyInUse) {+    page = await createPageInNewWindow(browser);+    if (page) console.log(`Session "${sessionId}" opened in its OWN window on shared profile "${profileName}"`);+    else { console.log(`[window] newWindow target failed for "${sessionId}" -- falling back to a tab`); page = await browser.newPage(); }   } else {     page = await browser.newPage();   }@@ -4577,7 +4661,7 @@ const server = http.createServer(async (req, res) => {           const wikiHint = wikiViewHint(args.url, args.wikiView === 'authed' ? 'authed' : 'public', _wikiAuth);           // v1.8.97: gentle in-tab glyph — 🔓 authed (logged in), 📖 public.           if (session && isAdomUrl(args.url)) {-            const label = (args.wikiView === 'authed' && _wikiAuth && _wikiAuth.authed) ? '\u{1F513} Logged in' : '\u{1F4D6} Public';+            const label = (args.wikiView === 'authed' && _wikiAuth && _wikiAuth.authed) ? '\u25CF Logged in' : '\u25CB Public';             try { await setWikiModeGlyph(session.page, label); } catch (e) {}           }           const info = await getSessionInfo(sessionId, session);@@ -4913,7 +4997,7 @@ const server = http.createServer(async (req, res) => {           // RE-check that corrects the glyph once the authed cookies/SPA settle (the first check           // races the page load, so a logged-in window was briefly mislabeling "📖 Public" — John           // saw exactly this).-          try { await setWikiModeGlyph(s2.page, view === 'authed' ? '\u{1F513} Logged in' : '\u{1F4D6} Public'); } catch (e) {}+          try { await setWikiModeGlyph(s2.page, view === 'authed' ? '\u25CF Logged in' : '\u25CB Public'); } catch (e) {}           sendJSON(res, { success: true, output: JSON.stringify({             ok: true, view, url, foregrounded: true,             _hint: `This window is now the ${view === 'authed' ? 'LOGGED-IN' : 'PUBLIC'} view (auth is re-confirmed a moment later; the tab label self-corrects if the ~30-day login has expired).`,@@ -4933,12 +5017,12 @@ const server = http.createServer(async (req, res) => {               await new Promise(r => setTimeout(r, 300));               let a = null;               try { a = await checkWikiAuth(s2.page); } catch (e) {}-              try { await setWikiModeGlyph(s2.page, (a && a.authed) ? '\u{1F513} Logged in' : '\u{1F4D6} Public'); } catch (e) {}+              try { await setWikiModeGlyph(s2.page, (a && a.authed) ? '\u25CF Logged in' : '\u25CB Public'); } catch (e) {}               try { await setWikiAuthedState(s2, sessionId, a && a.authed); } catch (e) {}               // v1.9.9: result caption — names WHO you're signed in as (John's ask), or says the               // one-time sign-in is still needed. Replaces the "Switching…" caption (same id).               const _doneMsg = a && a.authed-                ? `\u{1F513} Adom wiki: now the LOGGED-IN view${a.user ? ` (${a.user})` : ''}`+                ? `Adom wiki: now the logged-in view${a.user ? ` (${a.user})` : ''}`                 : '\u{1F513} Adom wiki: logged-in view needs the one-time sign-in — sign in in this window';               wikiCaption(_doneMsg, 6000).catch(() => {});             } else {@@ -6200,7 +6284,7 @@ const server = http.createServer(async (req, res) => {               setTimeout(async () => {                 try {                   const a = await checkWikiAuth(session.page);-                  await setWikiModeGlyph(session.page, a && a.authed ? '\u{1F513} Logged in' : '\u{1F4D6} Public');+                  await setWikiModeGlyph(session.page, a && a.authed ? '\u25CF Logged in' : '\u25CB Public');                   await setWikiAuthedState(session, resolvedId, a && a.authed);                 } catch (e) {}               }, 1800);