Adom Browser Extension
Public Made by Adomby adom
Your AI's hands in your real, signed-in browser. The Adom Browser Extension (abe) works with both Chrome and Edge: build, drive, and test web apps, and log past vendor walls, in the browser you already use - cookies, SSO, saved logins and captcha trust intact. The nbrowser_* bridge (pup's counterpart) runs in your actual logged-in profile.
name: installing-the-extension description: "How WE (this repo) install the Adom browser extension into the user's real Chrome/Edge in DEVELOPER MODE ("Load unpacked"), driving adom-desktop's GENERIC OS verbs. This is OUR orchestration, not an adom-desktop feature - AD only exposes primitives (desktop_navigate / desktop_find_control / desktop_ui_click / desktop_ui_set / desktop_screenshot); the recipe, hardening, and recovery live here. This is the DAY-ONE install path: Chrome Web Store / Edge Add-ons review is ~2 months out, so until then dev-mode load-unpacked is how a real user gets the extension. Use when the user says "install the extension", "install it for me", "load the extension into my browser", "set up the browser extension", "the extension isn't loaded yet". For the low-level click mechanics (popups, the PWA guard, uninstall/reload), see driving-the-desktop. Part of the adom-browser-extension."
Part of adom-browser-extension. Low-level OS-driving mechanics: driving-the-desktop. Safety rules: native-browser-safety.
Installing the extension (dev-mode Load unpacked) - the orchestration WE own
Whose code is this? Ours. adom-desktop stays generic - it only gives us OS primitives (
desktop_navigate,desktop_find_control,desktop_ui_click,desktop_ui_set,desktop_screenshot). The sequence that turns those into "the Adom extension is now loaded and connected" is this skill (plus the repo'sinstall/scripts). AD never grows a "dev-mode install" verb; we own and harden the recipe. This is the day-one path until the Chrome Web Store / Edge Add-ons listings clear review (budget ~2 months for the broad-permission set), so it has to be clean and repeatable, and we keep iterating it.
What makes this tractable: the extension ID is deterministic
The extension's manifest.json pins a key, so its ID is the same on every machine
(nncjgceobmlenfliamnibncjojpedbcc). That means the native-messaging host manifest's
allowed_origins is known ahead of time (host/host-manifest.json already hardcodes it) - we do
not have to capture the ID at install time to wire the host. So the only genuinely hard part is
getting "Load unpacked" to pick the right folder; everything else is deterministic.
Preconditions (do these once, before driving any UI)
- The
extension/folder is on the laptop at a known path (e.g.C:\Users\<user>\adom-browser-extension\extension). Get it there from the package/repo (push_filethe folder, or the user clones it). Load-unpacked reads the on-disk build, so this path is what the picker must select. - The native-messaging host is registered (
install/install.ps1, or its steps): host manifest written withallowed_origins= the pinned ID, andHKCU\...\NativeMessagingHosts\inc.adom.native_browserpointing at it (Chrome and/or Edge). No UAC. - The bridge is installed in AD's cache (
bridge_install, once). See the install-flow doc.
⚠ The host's Node - do NOT assume system Node exists (SDK runtime contract)
The bridge (server.js) is spawned by adom-desktop, so AD guarantees its Node (v1.9.63+ runtime
contract: system Node if on PATH, else a portable no-UAC copy AD provisions). But the
native-messaging host is launched by CHROME, not AD, so it does NOT inherit AD's managed-runtime
PATH. A machine with no system Node would then fail to start the host ('node' is not recognized)
even though the bridge is healthy - the extension would connect and immediately drop.
This is handled automatically, and you usually do nothing:
- The bridge publishes the exact Node AD gave it (
process.execPath) to~/.adom/bridges/native-browser/node-path.txtevery time it starts (bridge >= 0.0.22). native-host.batreads that file first, falls back tonodeon PATH only if it's absent. Since the bridge always runs before the extension is installed, the file is already there by first host launch. Self-healing: an AD Node upgrade rewrites it on the next bridge respawn.
If you ever need to write it explicitly (e.g. a repair, or the host somehow launched before the bridge ever ran): ask AD for the managed Node path and drop it in yourself -
adom-desktop runtimes # -> runtimes.node.exe (source: system|cache), e.g. C:/Program Files/nodejs/node.exe
# then write that absolute path (one line, no newline games) to:
# %USERPROFILE%\.adom\bridges\native-browser\node-path.txt
Never winget install node / prompt the user to install Node - AD already has one; reuse it.
⭐ Verify by SCREENSHOT, not by control state (the rule for the whole flow)
desktop_find_control / UIA state is a fast hint and it lies often enough to burn you: a toggle can
read ambiguously, the folder picker can select the wrong directory silently, and Load unpacked can
"succeed" with a broken manifest (the card shows a red Errors button) - none of which a
find_control "is it there?" check catches. So at every checkpoint below, take a real screenshot
and actually read it before you believe the step worked:
desktop_screenshot_window {hwnd:<W>} # WGC, BACKGROUND - works on the privileged chrome:// page that
# the extension's own CDP cannot shoot (and it isn't loaded yet)
Then look at the image: is Developer mode visibly ON? did the picker show the right path? is the Adom
card in the list, Enabled, with NO Errors button? Do not assert success off control state alone.
Tip (verified): shooting the BROWSER hwnd returns a top-level screenshots[] array of its owned
popups too - so when the folder picker is open, desktop_screenshot_window {hwnd:<browser>} hands
back the page AND the picker (screenshots[0] = the #32770 dialog) in one call; you don't need
the picker's hwnd to read it.
⭐ Narrate to the user with on-screen captions (hand-hold them)
This runs on the USER's screen and some steps are fragile or need them. Keep them oriented with
desktop_caption {text:"..."} - a large, always-on-top, click-through desktop banner - at each step:
what you're doing ("Adom: turning on Developer mode", "Adom: selecting the extension folder...") and
especially what THEY must do ("Click Select Folder to finish", "Paste the path with Ctrl+V"). Clear
it when done. Treat the install as a guided, narrated process, never a silent one.
⛔⛔ NEVER force the browser to the foreground (rude AND it corrupts your own work)
Foregrounding the user's browser - desktop_bring_to_front, OR any SendInput verb (desktop_type,
desktop_press_key, desktop_click) which foreground the target first - is disruptive and actively
dangerous while the user is working. It steals focus, so the user's very next keystroke lands in
whatever control YOU just focused - e.g. the extensions Search box - which silently changes your
filter and hides the card you were acting on. Learned the hard way: while foregrounded, the user kept
typing and the search became Adoms / Adom for Chromeing, so the Adom card vanished and the flow
derailed. It is also just rude - they are mid-task on their own machine.
Rules, in order:
- Drive entirely with BACKGROUND verbs -
desktop_ui_click(UIA Invoke),desktop_ui_set(SetValue),desktop_navigate,desktop_screenshot_window. None steal focus, so the user keeps typing safely and your control state can't be corrupted by their keystrokes. This covers almost the entire flow. - The only steps that might tempt a foreground are the folder-picker path-fill and the
Remove-confirm bubble. Do NOT reach for
bring_to_front/SendInput by default - use the background or clipboard paths (below) first. - If a step truly cannot be done in the background, treat foreground as a last resort:
desktop_captionthe user to pause typing for a moment, do the single action, and return immediately. Never hold the foreground, never do it silently, and never do it while the user is actively typing if there is any other way. - ✅
desktop_navigateis now focus-neutral (AD 1.9.34+). It restores your prior foreground window after committing and returns an honestnavigatedsignal (+titleChanged). Verified live: achrome://extensionsnavigate committed (navigated:true, tab title "Extensions") while my window stayed atz:1and the user's window keptz:0. (On OLDER AD it foregrounded - the general principle still holds: judge foreground by the outcome,desktop_list_windowsz:0= topmost = you surfaced it.) Still screenshot-verify the page actually rendered, not just thenavigatedflag.
The flow (background; never foreground the user's work)
Open a fresh background window you own, get its hwnd, and drive that (never the user's tabs):
nbrowser_open_window {sessionId:"install", thread:"<you>", purpose:"driving chrome://extensions to load the extension", profile, url:"about:blank"} # your own window; note hwnd <W>
desktop_navigate {hwnd:<W>, url:"chrome://extensions"} # edge://extensions for Edge - scheme MUST match the browser
desktop_screenshot_window {hwnd:<W>} # verify the page loaded (posted Enter is app-specific)
Step A - ensure Developer mode is ON (verify it VISUALLY)
The page's Developer mode is a toggle (top-right on Chrome, bottom-left on Edge).
desktop_find_control {hwnd:<W>, contains:"Developer mode"} # fast hint + best.rect of the toggle
desktop_screenshot_window {hwnd:<W>} # GROUND TRUTH - read it: is the toggle ON?
⚠️ Verified: the "Developer mode" control is invokable:false, so desktop_ui_click will NOT flip
it. If the screenshot shows it OFF, click its rect with SendInput (foreground, brief):
desktop_click {x:<best.rect.centerX>, y:<best.rect.centerY>} # toggle it (the label/toggle isn't UIA-invokable)
desktop_screenshot_window {hwnd:<W>} # confirm it flipped AND Load unpacked / Pack / Update appeared
Do not proceed until the screenshot shows Developer mode ON and the Load unpacked button visible. If the row didn't appear, the toggle didn't flip - re-read and retry, don't proceed blind.
Step B - Load unpacked -> the native folder picker (WORKING method, verified live 2026-06-28)
Run this as a TIGHT SCRIPT (back-to-back calls, minimal sleeps) - a slow step-by-step with a caption "sitting there" while you idle is infuriating to the user. The picker fill is agent-driven and fast. ⛔ Never ask the user to type or paste the path (it is ~50 chars - awful UX), and do not rely on the clipboard (see note). The whole trick is one line: bring the picker to the front before typing.
desktop_caption {text:"Adom: installing - 2 seconds, please dont type"} # warn ONCE, briefly
desktop_ui_click {hwnd:<W>, automationId:"loadUnpacked"} # opens the picker (#32770) - it appears BEHIND the user's windows
# ensure exactly one picker; grab its hwnd <PK> via desktop_list_windows (cancel extras)
desktop_bring_to_front {hwnd:<PK>} # ⭐ THE KEY: picker opens at z:1/z:2; SendInput only lands once it is z:0
desktop_press_key {hwnd:<PK>, keys:"ctrl+l"} # focus the picker address bar
desktop_type {hwnd:<PK>, text:"C:\\Users\\<user>\\adom-browser-extension\\extension"} # agent types the path - fast
desktop_press_key {hwnd:<PK>, keys:"enter"} # navigates INTO the folder
desktop_screenshot_window {hwnd:<PK>} # VERIFY breadcrumb shows ...\extension BEFORE committing
desktop_ui_click {hwnd:<PK>, automationId:"1"} # commit (Select Folder = IDOK) -> page toasts "Extension loaded"
desktop_caption {text:""} # clear the caption immediately
⚠ Select Folder may need a REAL click, not a UIA invoke (verified live on Edge / Win11, ADOMBASELINE
2026-07-03). The picker is a Win32 common dialog (#32770), and on Edge its Select Folder / IDOK
did NOT respond to desktop_ui_click (UIA Invoke) - the call returned success:true but the dialog
stayed open. A foreground SendInput click closed it. So: after verifying the breadcrumb, try the
UIA click; if desktop_list_windows still shows the #32770 open, fall back to
desktop_find_control {hwnd:<PK>, contains:"Select Folder"} -> desktop_bring_to_front {hwnd:<PK>} ->
desktop_click {x:<best.rect.centerX>, y:<best.rect.centerY>} (SendInput). ALWAYS confirm the picker is
gone (#32770 count == 0) before believing it committed.
⚠ The subfolder-drill trap. After ctrl+l -> type path -> Enter you are now standing INSIDE the
extension folder (breadcrumb ...\extension, Folder field = "extension", list shows the extension's own
icons/, src/, manifest.json). Commit from HERE. Do NOT let a subfolder get selected first - if the
Folder field or breadcrumb reads ...\extension\src, Select Folder loads src (no manifest -> silent
no-load, or a manifest error). If you drilled too far, re-run ctrl+l + the full path + Enter to reset
to ...\extension, re-screenshot, THEN commit.
Edge vs Chrome are identical here except the URL scheme (edge://extensions) and, on a fresh box, the
Developer-mode toggle is a shadow-DOM switch not exposed as settable - front the window and
desktop_click its screen coords (just right of the "Developer mode" label) to flip it.
Why bring_to_front is the whole game: the picker opens BEHIND the user's active windows (observed
z:1/z:2). SendInput without fronting it first sends the keys to the USER's foreground window (their
editor) - they never reach the picker (learned the hard way: a stray paste/Enter into the user's VS
Code). Front the picker (it goes z:0), and ctrl+l -> type -> enter -> Select Folder works
every time. This is a brief (~2s) necessary foreground - caption a "don't type" warning, do it fast,
clear the caption. (Step 1's desktop_navigate is focus-neutral on AD 1.9.34+, so this picker fill is
the ONLY foreground in the whole install.)
⛔ Do NOT use the clipboard for this: desktop_clipboard_set did not stick on a real machine
(set -> get returned ""), so a "paste the path" handoff leaves the user with an empty clipboard.
Agent-types-it-after-fronting is the method. (Clipboard verbs exist - AD 1.9.35 - but are unreliable for
this; flagged.)
Consent / handoff with notify (use it for the ONE gate, not the path): before driving,
notify_user {id, title, body, buttons:[{label:"Start"}], scenario:"reminder"} (sticky toast + action
button) and poll notify_response {id} until they click Start. Verified round-trip. Use it to get a go
-ahead, then run the fast script above - never to ask them to type a path.
Gotchas (live):
- Load unpacked can open MULTIPLE pickers (saw 2).
desktop_list_windows, ensure exactly one#32770"Select the extension directory.", cancel extras. - Cancel =
desktop_ui_click {hwnd:<PK>, contains:"Cancel"}(the name;automationId:"2"reported success but did NOT close it). Likewise prefercontains:"Select Folder"overautomationId:"1". - ALWAYS screenshot + READ the path BEFORE committing - the wrong folder loads a broken/old build silently.
Step C - verify it loaded clean AND connected (screenshot FIRST, three checks)
- Screenshot the extensions list and READ it - this is the check that actually matters:
Confirm in the image: (a) the Adom card is present, (b) it is Enabled (toggle on), and (c) it shows NO red "Errors" button. ⛔ If an Errors button is present, the extension loaded broken (bad manifest / SW threw) - it is NOT installed successfully even though the card exists. Open it (desktop_screenshot_window {hwnd:<W>} # then look at the imagedesktop_ui_click {hwnd:<W>, contains:"Errors"}), screenshot, read the actual error, fix it, and reload. A card-present-with-errors is the most common false "success" - the screenshot is the only thing that catches it. - Card present (fast secondary):
desktop_find_control {hwnd:<W>, contains:"Adom"}. Optional: read the card's ID and confirm it equals the pinned ID. - Connected to the bridge: poll
nbrowser_status/nbrowser_profiles- thechrome:/edge:key appears (and the popup shows "connected") within ~1-3s once the SW starts. A loaded-but-not- connected extension usually means the profile has no open window (SW asleep) or the host manifest / HKCU registration is missing or has the wrong ID - check the preconditions.
Step D - first useful action
Drive a real verb in a window you open (see native-browser-safety): e.g.
nbrowser_open_window {sessionId:"verify", thread:"<you>", purpose:"verify the extension drives", url:"https://www.digikey.com"} -> nbrowser_screenshot.
Chrome AND Edge
They are independent. Repeat the flow per browser (matching scheme: chrome:// vs edge://; the toggle
sits top-right on Chrome, bottom-left on Edge). The host is registered per browser, so each connects its
own host. Load into whichever the user wants, or both.
Recovery / common failures
- Load unpacked missing -> Developer mode is off (Step A) or the page didn't navigate (re-
desktop_navigate, verify by screenshot). - Picker selected the wrong folder -> the silent killer; always screenshot the picker path before Select.
- Card present but never connects -> SW asleep (open a window in that profile), or host not registered / wrong
allowed_originsID (re-runinstall/install.ps1). - "refusing to drive a PWA / installed-app window" -> you grabbed an installed-app window; open a fresh
normalwindow (see the PWA guard indriving-the-desktop).
Honest status
The end-to-end recipe works; the folder-picker path-fill is the part we are actively making robust (a deterministic helper instead of "remembers the last dir"). Until the Web Store listings clear review, this dev-mode flow is the real install path, so getting it clean is a priority - report any picker flakiness so we can harden the helper.
---
name: installing-the-extension
description: "How WE (this repo) install the Adom browser extension into the user's real Chrome/Edge in DEVELOPER MODE (\"Load unpacked\"), driving adom-desktop's GENERIC OS verbs. This is OUR orchestration, not an adom-desktop feature - AD only exposes primitives (desktop_navigate / desktop_find_control / desktop_ui_click / desktop_ui_set / desktop_screenshot); the recipe, hardening, and recovery live here. This is the DAY-ONE install path: Chrome Web Store / Edge Add-ons review is ~2 months out, so until then dev-mode load-unpacked is how a real user gets the extension. Use when the user says \"install the extension\", \"install it for me\", \"load the extension into my browser\", \"set up the browser extension\", \"the extension isn't loaded yet\". For the low-level click mechanics (popups, the PWA guard, uninstall/reload), see driving-the-desktop. Part of the adom-browser-extension."
---
Part of **adom-browser-extension**. Low-level OS-driving mechanics: **driving-the-desktop**. Safety rules: **native-browser-safety**.
# Installing the extension (dev-mode Load unpacked) - the orchestration WE own
> **Whose code is this?** Ours. adom-desktop stays **generic** - it only gives us OS primitives
> (`desktop_navigate`, `desktop_find_control`, `desktop_ui_click`, `desktop_ui_set`,
> `desktop_screenshot`). The sequence that turns those into "the Adom extension is now loaded and
> connected" is **this skill** (plus the repo's `install/` scripts). AD never grows a "dev-mode
> install" verb; we own and harden the recipe. **This is the day-one path** until the Chrome Web Store /
> Edge Add-ons listings clear review (budget ~2 months for the broad-permission set), so it has to be
> clean and repeatable, and we keep iterating it.
## What makes this tractable: the extension ID is deterministic
The extension's `manifest.json` pins a `key`, so its ID is the **same on every machine**
(`nncjgceobmlenfliamnibncjojpedbcc`). That means the native-messaging **host manifest's
`allowed_origins` is known ahead of time** (`host/host-manifest.json` already hardcodes it) - we do
**not** have to capture the ID at install time to wire the host. So the only genuinely hard part is
getting "Load unpacked" to pick the right folder; everything else is deterministic.
## Preconditions (do these once, before driving any UI)
1. **The `extension/` folder is on the laptop** at a known path (e.g. `C:\Users\<user>\adom-browser-extension\extension`). Get it there from the package/repo (`push_file` the folder, or the user clones it). Load-unpacked reads the **on-disk** build, so this path is what the picker must select.
2. **The native-messaging host is registered** (`install/install.ps1`, or its steps): host manifest written with `allowed_origins` = the pinned ID, and `HKCU\...\NativeMessagingHosts\inc.adom.native_browser` pointing at it (Chrome and/or Edge). No UAC.
3. **The bridge is installed** in AD's cache (`bridge_install`, once). See the install-flow doc.
### ⚠ The host's Node - do NOT assume system Node exists (SDK runtime contract)
The bridge (`server.js`) is spawned by adom-desktop, so **AD guarantees its Node** (v1.9.63+ runtime
contract: system Node if on PATH, else a portable no-UAC copy AD provisions). **But the
native-messaging host is launched by CHROME, not AD**, so it does NOT inherit AD's managed-runtime
PATH. A machine with **no system Node** would then fail to start the host (`'node' is not recognized`)
even though the bridge is healthy - the extension would connect and immediately drop.
This is handled automatically, and you usually do nothing:
- **The bridge publishes the exact Node AD gave it** (`process.execPath`) to
`~/.adom/bridges/native-browser/node-path.txt` every time it starts (bridge >= 0.0.22).
- **`native-host.bat` reads that file first**, falls back to `node` on PATH only if it's absent. Since
the bridge always runs before the extension is installed, the file is already there by first host
launch. Self-healing: an AD Node upgrade rewrites it on the next bridge respawn.
**If you ever need to write it explicitly** (e.g. a repair, or the host somehow launched before the
bridge ever ran): ask AD for the managed Node path and drop it in yourself -
```
adom-desktop runtimes # -> runtimes.node.exe (source: system|cache), e.g. C:/Program Files/nodejs/node.exe
# then write that absolute path (one line, no newline games) to:
# %USERPROFILE%\.adom\bridges\native-browser\node-path.txt
```
Never `winget install node` / prompt the user to install Node - AD already has one; reuse it.
## ⭐ Verify by SCREENSHOT, not by control state (the rule for the whole flow)
`desktop_find_control` / UIA state is a fast **hint** and it lies often enough to burn you: a toggle can
read ambiguously, the folder picker can select the wrong directory silently, and **Load unpacked can
"succeed" with a broken manifest** (the card shows a red **Errors** button) - none of which a
`find_control` "is it there?" check catches. So at **every** checkpoint below, take a real screenshot
and **actually read it** before you believe the step worked:
```
desktop_screenshot_window {hwnd:<W>} # WGC, BACKGROUND - works on the privileged chrome:// page that
# the extension's own CDP cannot shoot (and it isn't loaded yet)
```
Then look at the image: is Developer mode visibly ON? did the picker show the right path? is the Adom
card in the list, **Enabled, with NO Errors button**? Do not assert success off control state alone.
**Tip (verified):** shooting the BROWSER hwnd returns a top-level `screenshots[]` array of its **owned
popups** too - so when the folder picker is open, `desktop_screenshot_window {hwnd:<browser>}` hands
back the page AND the picker (`screenshots[0]` = the `#32770` dialog) in **one call**; you don't need
the picker's hwnd to read it.
## ⭐ Narrate to the user with on-screen captions (hand-hold them)
This runs on the USER's screen and some steps are fragile or need them. Keep them oriented with
`desktop_caption {text:"..."}` - a large, always-on-top, click-through desktop banner - at each step:
what you're doing ("Adom: turning on Developer mode", "Adom: selecting the extension folder...") and
especially **what THEY must do** ("Click Select Folder to finish", "Paste the path with Ctrl+V"). Clear
it when done. Treat the install as a guided, narrated process, never a silent one.
## ⛔⛔ NEVER force the browser to the foreground (rude AND it corrupts your own work)
Foregrounding the user's browser - `desktop_bring_to_front`, OR any SendInput verb (`desktop_type`,
`desktop_press_key`, `desktop_click`) which foreground the target first - is **disruptive and actively
dangerous while the user is working**. It steals focus, so **the user's very next keystroke lands in
whatever control YOU just focused** - e.g. the extensions **Search box** - which silently changes your
filter and **hides the card you were acting on**. Learned the hard way: while foregrounded, the user kept
typing and the search became `Adoms` / `Adom for Chromeing`, so the Adom card vanished and the flow
derailed. It is also just rude - they are mid-task on their own machine.
Rules, in order:
- **Drive entirely with BACKGROUND verbs** - `desktop_ui_click` (UIA Invoke), `desktop_ui_set` (SetValue),
`desktop_navigate`, `desktop_screenshot_window`. **None steal focus**, so the user keeps typing safely
and your control state can't be corrupted by their keystrokes. This covers almost the entire flow.
- The only steps that might tempt a foreground are the **folder-picker path-fill** and the
**Remove-confirm bubble**. Do NOT reach for `bring_to_front`/SendInput by default - use the background
or clipboard paths (below) first.
- **If a step truly cannot be done in the background**, treat foreground as a last resort: `desktop_caption`
the user to pause typing for a moment, do the single action, and return immediately. Never hold the
foreground, never do it silently, and never do it while the user is actively typing if there is any
other way.
- ✅ **`desktop_navigate` is now focus-neutral (AD 1.9.34+).** It restores your prior foreground window
after committing and returns an honest `navigated` signal (+ `titleChanged`). Verified live: a
`chrome://extensions` navigate committed (`navigated:true`, tab title "Extensions") while my window
stayed at `z:1` and the user's window kept `z:0`. (On OLDER AD it foregrounded - the general principle
still holds: judge foreground by the **outcome**, `desktop_list_windows` `z:0` = topmost = you surfaced
it.) Still screenshot-verify the page actually rendered, not just the `navigated` flag.
## The flow (background; never foreground the user's work)
Open a fresh background window you own, get its `hwnd`, and drive **that** (never the user's tabs):
```
nbrowser_open_window {sessionId:"install", thread:"<you>", purpose:"driving chrome://extensions to load the extension", profile, url:"about:blank"} # your own window; note hwnd <W>
desktop_navigate {hwnd:<W>, url:"chrome://extensions"} # edge://extensions for Edge - scheme MUST match the browser
desktop_screenshot_window {hwnd:<W>} # verify the page loaded (posted Enter is app-specific)
```
### Step A - ensure Developer mode is ON (verify it VISUALLY)
The page's **Developer mode** is a toggle (top-right on Chrome, bottom-left on Edge).
```
desktop_find_control {hwnd:<W>, contains:"Developer mode"} # fast hint + best.rect of the toggle
desktop_screenshot_window {hwnd:<W>} # GROUND TRUTH - read it: is the toggle ON?
```
⚠️ **Verified:** the "Developer mode" control is `invokable:false`, so `desktop_ui_click` will NOT flip
it. If the screenshot shows it OFF, click its **rect** with SendInput (foreground, brief):
```
desktop_click {x:<best.rect.centerX>, y:<best.rect.centerY>} # toggle it (the label/toggle isn't UIA-invokable)
desktop_screenshot_window {hwnd:<W>} # confirm it flipped AND Load unpacked / Pack / Update appeared
```
Do not proceed until the screenshot shows Developer mode ON and the **Load unpacked** button visible. If
the row didn't appear, the toggle didn't flip - re-read and retry, don't proceed blind.
### Step B - Load unpacked -> the native folder picker (WORKING method, verified live 2026-06-28)
**Run this as a TIGHT SCRIPT** (back-to-back calls, minimal sleeps) - a slow step-by-step with a caption
"sitting there" while you idle is infuriating to the user. The picker fill is **agent-driven and fast**.
⛔ **Never ask the user to type or paste the path** (it is ~50 chars - awful UX), and **do not rely on
the clipboard** (see note). The whole trick is one line: **bring the picker to the front before typing.**
```
desktop_caption {text:"Adom: installing - 2 seconds, please dont type"} # warn ONCE, briefly
desktop_ui_click {hwnd:<W>, automationId:"loadUnpacked"} # opens the picker (#32770) - it appears BEHIND the user's windows
# ensure exactly one picker; grab its hwnd <PK> via desktop_list_windows (cancel extras)
desktop_bring_to_front {hwnd:<PK>} # ⭐ THE KEY: picker opens at z:1/z:2; SendInput only lands once it is z:0
desktop_press_key {hwnd:<PK>, keys:"ctrl+l"} # focus the picker address bar
desktop_type {hwnd:<PK>, text:"C:\\Users\\<user>\\adom-browser-extension\\extension"} # agent types the path - fast
desktop_press_key {hwnd:<PK>, keys:"enter"} # navigates INTO the folder
desktop_screenshot_window {hwnd:<PK>} # VERIFY breadcrumb shows ...\extension BEFORE committing
desktop_ui_click {hwnd:<PK>, automationId:"1"} # commit (Select Folder = IDOK) -> page toasts "Extension loaded"
desktop_caption {text:""} # clear the caption immediately
```
⚠ **Select Folder may need a REAL click, not a UIA invoke (verified live on Edge / Win11, ADOMBASELINE
2026-07-03).** The picker is a Win32 common dialog (`#32770`), and on Edge its **Select Folder / IDOK
did NOT respond to `desktop_ui_click` (UIA Invoke) - the call returned `success:true` but the dialog
stayed open.** A foreground **SendInput** click closed it. So: after verifying the breadcrumb, try the
UIA click; if `desktop_list_windows` still shows the `#32770` open, fall back to
`desktop_find_control {hwnd:<PK>, contains:"Select Folder"}` -> `desktop_bring_to_front {hwnd:<PK>}` ->
`desktop_click {x:<best.rect.centerX>, y:<best.rect.centerY>}` (SendInput). ALWAYS confirm the picker is
gone (`#32770` count == 0) before believing it committed.
⚠ **The subfolder-drill trap.** After `ctrl+l` -> type path -> Enter you are now standing INSIDE the
extension folder (breadcrumb `...\extension`, Folder field = "extension", list shows the extension's own
`icons/`, `src/`, `manifest.json`). Commit from HERE. Do NOT let a subfolder get selected first - if the
Folder field or breadcrumb reads `...\extension\src`, Select Folder loads `src` (no manifest -> silent
no-load, or a manifest error). If you drilled too far, re-run `ctrl+l` + the full path + Enter to reset
to `...\extension`, re-screenshot, THEN commit.
**Edge vs Chrome are identical here** except the URL scheme (`edge://extensions`) and, on a fresh box, the
Developer-mode toggle is a shadow-DOM switch not exposed as settable - front the window and
`desktop_click` its screen coords (just right of the "Developer mode" label) to flip it.
**Why `bring_to_front` is the whole game:** the picker opens BEHIND the user's active windows (observed
`z:1`/`z:2`). SendInput without fronting it first sends the keys to the USER's foreground window (their
editor) - they never reach the picker (learned the hard way: a stray paste/Enter into the user's VS
Code). Front the picker (it goes `z:0`), and `ctrl+l` -> `type` -> `enter` -> `Select Folder` works
every time. This is a brief (~2s) **necessary** foreground - caption a "don't type" warning, do it fast,
clear the caption. (Step 1's `desktop_navigate` is focus-neutral on AD 1.9.34+, so this picker fill is
the ONLY foreground in the whole install.)
⛔ **Do NOT** use the clipboard for this: `desktop_clipboard_set` did **not** stick on a real machine
(`set` -> `get` returned `""`), so a "paste the path" handoff leaves the user with an empty clipboard.
Agent-types-it-after-fronting is the method. (Clipboard verbs exist - AD 1.9.35 - but are unreliable for
this; flagged.)
**Consent / handoff with notify (use it for the ONE gate, not the path):** before driving,
`notify_user {id, title, body, buttons:[{label:"Start"}], scenario:"reminder"}` (sticky toast + action
button) and poll `notify_response {id}` until they click Start. Verified round-trip. Use it to get a go
-ahead, then run the fast script above - never to ask them to type a path.
**Gotchas (live):**
- **Load unpacked can open MULTIPLE pickers** (saw 2). `desktop_list_windows`, ensure exactly one `#32770`
"Select the extension directory.", cancel extras.
- **Cancel = `desktop_ui_click {hwnd:<PK>, contains:"Cancel"}`** (the name; `automationId:"2"` reported
success but did NOT close it). Likewise prefer `contains:"Select Folder"` over `automationId:"1"`.
- **ALWAYS screenshot + READ the path BEFORE committing** - the wrong folder loads a broken/old build
silently.
### Step C - verify it loaded clean AND connected (screenshot FIRST, three checks)
1. **Screenshot the extensions list and READ it** - this is the check that actually matters:
```
desktop_screenshot_window {hwnd:<W>} # then look at the image
```
Confirm in the image: (a) the **Adom** card is present, (b) it is **Enabled** (toggle on), and (c) it
shows **NO red "Errors" button**. ⛔ If an **Errors** button is present, the extension loaded
**broken** (bad manifest / SW threw) - it is NOT installed successfully even though the card exists.
Open it (`desktop_ui_click {hwnd:<W>, contains:"Errors"}`), screenshot, read the actual error, fix
it, and reload. A card-present-with-errors is the most common false "success" - the screenshot is the
only thing that catches it.
2. **Card present (fast secondary):** `desktop_find_control {hwnd:<W>, contains:"Adom"}`. Optional: read
the card's ID and confirm it equals the pinned ID.
3. **Connected to the bridge:** poll `nbrowser_status` / `nbrowser_profiles` - the `chrome:`/`edge:`
key appears (and the popup shows "connected") within ~1-3s once the SW starts. A loaded-but-not-
connected extension usually means the profile has no open window (SW asleep) or the host manifest /
HKCU registration is missing or has the wrong ID - check the preconditions.
### Step D - first useful action
Drive a real verb in a window **you** open (see `native-browser-safety`): e.g.
`nbrowser_open_window {sessionId:"verify", thread:"<you>", purpose:"verify the extension drives", url:"https://www.digikey.com"}` -> `nbrowser_screenshot`.
## Chrome AND Edge
They are independent. Repeat the flow per browser (matching scheme: `chrome://` vs `edge://`; the toggle
sits top-right on Chrome, bottom-left on Edge). The host is registered per browser, so each connects its
own host. Load into whichever the user wants, or both.
## Recovery / common failures
- **Load unpacked missing** -> Developer mode is off (Step A) or the page didn't navigate (re-`desktop_navigate`, verify by screenshot).
- **Picker selected the wrong folder** -> the silent killer; always screenshot the picker path before Select.
- **Card present but never connects** -> SW asleep (open a window in that profile), or host not registered / wrong `allowed_origins` ID (re-run `install/install.ps1`).
- **"refusing to drive a PWA / installed-app window"** -> you grabbed an installed-app window; open a fresh `normal` window (see the PWA guard in `driving-the-desktop`).
## Honest status
The end-to-end recipe works; the **folder-picker path-fill** is the part we are actively making robust
(a deterministic helper instead of "remembers the last dir"). Until the Web Store listings clear review,
this dev-mode flow is the real install path, so getting it clean is a priority - report any picker
flakiness so we can harden the helper.