adom-desktop v1.4.10 follow-ups — two remaining v1.4.9 issues

Paste into Claude on the desktop. v1.4.9 ships smart-pick refinement + browser_fetch_url saveTo split + tab_not_found error. Verified on container side:

  • browser_fetch_url saveTo:container works perfectly (TI INA226 datasheet, 1.5 MB, valid PDF, lands at the path stated, savedToFilesystem:"container" flag set). 🎉
  • ⚠️ modal-scoped smart-pick has a false-positive on Vue-Toastification toast containers
  • tab_not_found error is not actually implemented — browser_eval still silently falls back to the active tab

Concrete repros for both below.


Item 1 — modal detection false-positives on Vue-Toastification (and similar empty toast containers)

The bug

WAGO's product page (https://www.wago.com/global/p/2601-3105) ships Vue-Toastification. Even with no toast active, four full-page-height toast containers exist in DOM, each <div class="Vue-Toastification__container ..."> with position:fixed; z-index:9999; height:100vh. They cover >25% viewport, beat the v1.4.9 modal-detection heuristic, and become the "modal root."

The actual Generate-Datasheet modal is a sibling <div> rendered by Vue Teleport at body > div, with no [role=dialog], no aria-modal, lower z-index than the toast container. So smart-pick scopes to the wrong "modal" and the Generate button matches outside it.

adom-desktop browser_input_dispatch '{
  "sessionId":"chip-fetcher",
  "type":"click",
  "selector":"button.wg-button--primary"
}'
# → matchedCount: 3
#   modalDetected: true
#   insideModal: false
#   pickStrategy: "visible-text-largest-no-modal-match"
#   clickedText: "Add to shopping cart"   ← wrong; Generate is in the real modal

DOM proof:

// Run in pup eval against an open WAGO modal
JSON.stringify({
  fixedOver25pct: Array.from(document.querySelectorAll('*'))
    .filter(el => {
      const cs = getComputedStyle(el);
      if (!['fixed','absolute'].includes(cs.position)) return false;
      const r = el.getBoundingClientRect();
      return r.width * r.height / (innerWidth * innerHeight) > 0.25;
    })
    .map(el => ({
      tag: el.tagName,
      cls: (el.className || '').toString().slice(0,40),
      z: getComputedStyle(el).zIndex,
      hasInteractiveChild: !!el.querySelector('button, input, a, [role=button]'),
      childTextLen: (el.textContent || '').trim().length
    }))
});
// Returns 5 entries:
//   - wg-nav-overlay (z=-1, hidden behind page)
//   - 4× Vue-Toastification__container (z=9999, hasInteractiveChild=false, childTextLen=0)
// The actual Generate modal is missed because it's a body-level <div> not in
// the >25%-viewport-fixed-or-absolute set.

What I want

The detection heuristic should require the candidate to contain at least one visible interactive element with non-empty text. Empty toast containers fail that filter. The actual modal (which has the Generate button + cancel link + checkbox grid) passes it.

// In pickElementForInputDispatch's modal-root detection:
const isPlausibleModal = (el) => {
  // Must contain at least one visible button/link/input with non-empty text
  const interactives = Array.from(el.querySelectorAll('button, a, input, [role=button]'));
  return interactives.some(child => {
    const cs = getComputedStyle(child);
    if (cs.display === 'none' || cs.visibility === 'hidden') return false;
    if ((child.textContent || '').trim().length === 0 && child.tagName !== 'INPUT') return false;
    return true;
  });
};
const candidates = fixedOver25pct.filter(isPlausibleModal);

This change should be small — fixedOver25pct list is already computed; just add the post-filter.

Add a modalRootInfo debug field to the response so callers can see what got picked (or didn't):

{
  "modalDetected": true,
  "modalRoot": { "tag": "DIV", "cls": "datasheet-modal", "z": "1000" },
  "insideModal": true,
  "pickStrategy": "modal-scoped-largest"
}

Acceptance test

After fix, the WAGO recipe with selector:"button.wg-button--primary" should return clickedText:"Generate data sheet", pickStrategy:"modal-scoped-largest", insideModal:true, with modalRoot referencing a div that actually contains the modal contents (not a Vue-Toastification container).

Workaround for now

Same as before v1.4.9: compute the button's bounding rect via browser_eval and pass {x, y} coords. Defeats the smart-pick value-add for modals.


Item 2 — tab_not_found error path is not implemented (regression / missing implementation)

The bug

The v1.4.9 release notes promised:

When you pass tabId:"tab-2" but the popup has auto-closed, you no longer silently get the active tab's response.

But it still does:

adom-desktop browser_eval '{
  "sessionId":"chip-fetcher",
  "tabId":"tab-999",
  "expr":"location.href"
}'
{
  "ok": true,
  "result": "https://www.wago.com/global/p/2601-3105",
  "sessionId": "chip-fetcher",
  "tabId": "tab-1"
}

tab-999 doesn't exist (only tab-1 is open). Eval routed to tab-1 and reported tabId:"tab-1" in the response. No errorCode, no currentTabs, no _hint. Same silent-fallback as v1.4.7.

The bridge-side code path that was supposed to land in v1.4.9 didn't (or got reverted, or didn't make it into the build). v1.4.10 should ship it.

What I want

The promised v1.4.9 behavior:

{
  "ok": false,
  "error": "Tab \"tab-999\" not found in session \"chip-fetcher\".",
  "errorCode": "tab_not_found",
  "currentTabs": ["tab-1"],
  "_hint": "Most likely the popup auto-closed (Chrome PDF viewer with Content-Disposition: attachment closes after download). Use browser_list_tabs IMMEDIATELY after the click to capture the popup URL, then browser_fetch_url with that URL — never eval-against-popup-then-fetch."
}

This applies to all tab-aware verbs: browser_eval, browser_screenshot, browser_errors, browser_reload, browser_navigate, browser_close_tab. Each should hard-error on missing tabId rather than fall back to active.

Acceptance test

# After fix
adom-desktop browser_eval '{"sessionId":"x","tabId":"tab-bogus","expr":"1"}'
# → {"ok":false,"errorCode":"tab_not_found",...}
adom-desktop browser_screenshot '{"sessionId":"x","tabId":"tab-bogus"}'
# → {"ok":false,"errorCode":"tab_not_found",...}
adom-desktop browser_navigate '{"sessionId":"x","tabId":"tab-bogus","url":"https://example.com"}'
# → {"ok":false,"errorCode":"tab_not_found",...}

Workaround for now

Always check browser_list_tabs for the tab's existence before eval-ing against it. Or trust that browser_eval tabId arg silently falls back to active and never assume it actually targeted the tab you passed. Both are bad — please ship v1.4.9's promised behavior in v1.4.10.


Suggested PR shape

  • Item 1pickElementForInputDispatch.detectModalRoot adds the isPlausibleModal filter (interactive + non-empty text child required). Returns modalRoot debug field. Test: WAGO scenario should land on the real modal.
  • Item 2 — for each tab-aware verb in the bridge dispatch, look up the tabId and return the structured tab_not_found error before falling back. Add the listed _hint. Add an integration test that opens a session, closes a tab, then attempts eval against the closed tabId and asserts errorCode:"tab_not_found".

Also worth considering: after Item 2, when the bridge gets tab_not_found for a popup tabId that was just listed as opener:"popup" (auto-closed mid-flow), include the popup's last-known URL in the error: lastKnownUrl: "...", so the caller can immediately call browser_fetch_url with it instead of needing to re-list_tabs (which by definition won't show the gone tab).


Container-side update flow

curl -L -o /tmp/cli-fresh \
  https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/adom-desktop-linux
sudo install -m 755 /tmp/cli-fresh /usr/local/bin/adom-desktop
adom-desktop --version
# Need 1.4.10 or newer

Plus the taskkill //f //im node.exe reminder on the desktop side (we still have to do that manually after every release; if there's a way to trigger that automatically from adom-desktop install, please do).

— sent from john's container running chip-fetcher v0.1.x against adom-desktop v1.4.9