Skip to content

fix: recover Android snapshots from system-only helper output#861

Open
thymikee wants to merge 3 commits into
mainfrom
codex/android-fabric-snapshot-recovery
Open

fix: recover Android snapshots from system-only helper output#861
thymikee wants to merge 3 commits into
mainfrom
codex/android-fabric-snapshot-recovery

Conversation

@thymikee

@thymikee thymikee commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

Recover Android React Native/Fabric app snapshots when helper capture is technically successful but app content would otherwise disappear from the agent-facing tree.

This handles empty/system-only/content-poor helper output by falling back to stock UIAutomator. Android hierarchy shaping now ignores transparent full-screen Expo/RN helper wrappers without letting unlabeled full-screen foreground containers with real child actions re-expose lower drawing-order background controls.

Upgraded examples/test-app to Expo SDK 56 / React Native 0.85.3 using the official Expo upgrade flow and Expo Router SDK 56 migration guidance. Dogfood also fixed the Settings diagnostics label to report expo-sdk-56.

Closes #850

Touched files: 12. Scope expanded from Android snapshot recovery to include the Expo test app upgrade needed for live SDK 56/RN 0.85 verification.

Coordination: PR #862 is merged, and this branch is rebased onto current main; no remaining stacking dependency.

Validation

Passed locally on head 20a51d4bf: pnpm format, pnpm check:fallow --base origin/main, pnpm exec vitest run src/platforms/android/__tests__/index.test.ts src/platforms/android/__tests__/snapshot.test.ts, pnpm check:quick, and pnpm build.

Passed earlier for the upgraded test app: pnpm --dir examples/test-app typecheck and pnpm dlx expo-doctor with 21/21 checks.

Regression coverage now includes an unlabeled full-screen covering container with a visible child button; removing the pruning fix re-exposes Hidden drawer action. The transparent Expo tools overlay fixture still keeps underlying React Native app content visible because the full-screen wrapper only contains non-actionable helper chrome.

Live Android verification on emulator-5554: started the required Metro server with pnpm --dir examples/test-app start -- --host localhost --clear, ran adb reverse tcp:8081 tcp:8081, opened Agent Device Tester through Expo Go, and captured interactive snapshots plus overlay screenshots. Current blocker evidence artifacts are under /private/tmp/agent-device-861-blocker/{snapshots,screenshots}.

Updated live evidence covered Home, a foreground native confirmation alert, Catalog, Form, and Settings. The alert snapshot returned only Confirm catalog refresh, KEEP BROWSING, and CONFIRM REFRESH while the screenshot showed dimmed background controls, proving the foreground surface did not re-expose hidden background controls. Catalog/Form/Settings snapshots continued to return compact app content, and Settings captured Build: expo-sdk-56 / lab-fixture-1.

Before the parser fix, the shipped path returned only 8 nodes from android-helper (ComposeView plus Expo Tools) while raw UIAutomator saw the app. After the fix, the shipped snapshot path returned success=true, androidSnapshot.backend=android-helper, captureMode=interactive-windows, windowCount=2, helper nodeCount=272, and shaped nodes including Catalog, Search, Citrus Starter Kit, Form, and Settings.

Known residual risk: this was verified with the repo Expo Go SDK 56/RN 0.85.3 app rather than the original reporter app, but it exercises the live Fabric/Expo overlay failure mode that previously reproduced system/status/Tools-only output and the foreground-alert/background-control pruning risk raised in review.

Current PR state: ready for review on head 20a51d4bf; CI is green.

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.3 MB 1.3 MB +3.7 kB
JS gzip 431.1 kB 432.0 kB +838 B
npm tarball 567.4 kB 568.3 kB +902 B
npm unpacked 1.9 MB 1.9 MB +3.7 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.3 ms 27.1 ms -1.2 ms
CLI --help 47.8 ms 48.3 ms +0.5 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9722.js +3.6 kB +831 B
dist/src/7394.js +30 B +4 B
dist/src/android.js +27 B +3 B

@thymikee thymikee marked this pull request as draft June 24, 2026 16:00
@thymikee

Copy link
Copy Markdown
Member Author

Review: recover Android snapshots from degraded helper output

Verdict: not merge-ready as-is — your own PR description says as much. Two things to address before it lands.

1. The classifier doesn't cover the actual #850 repro (blocking)

classifyAndroidHelperContentRecovery only triggers a fallback in two cases:

  • empty-helper-output: summary.nodeCount === 0 || metadata.nodeCount === 0 || metadata.rootPresent === false
  • system-window-only: summary.windowRootCount > 0 && summary.applicationWindowRootCount === 0

The reported Fabric/newArch case is neither. Per your own live note the helper returned "status-bar nodes plus an Expo/Compose 'Tools' overlay, with backend=android-helper and no fallback" — i.e. an application window is present (or the content carries no window-type wrappers at all). Trace it:

  • nodeCount > 0 and rootPresent === true → branch (a) skipped
  • applicationWindowRootCount > 0 or windowRootCount === 0 (when content has no window-type attrs — exactly the shape of your own androidFabricAppXml() fixture) → branch (b) skipped
  • → returns undefined → no fallback

So the classifier handles "no app window" but not "app window present but content-poor", which is the bug. The new test keeps helper output when application and system windows are both present actually encodes this gap as intended behavior, and the other new tests only exercise the empty / system-only paths — so the suite goes green while #850 is still broken.

What's missing is a content-quality signal, e.g.:

2. Heavy overlap with #862 (blocking on ordering)

Both PRs rewrite the same region of captureAndroidUiHierarchyWithHelper in snapshot.ts. #862 renames getAndroidSnapshotHelperDeviceKeygetAndroidSnapshotHelperSessionDeviceKey, converts captureAndroidUiHierarchyFromHelper to a params object, and adds a finally that stops command-scoped sessions. This PR inserts content recovery into the same function and calls stopAndroidSnapshotHelperSession / resetAndroidSnapshotHelperRuntime in a new recovery path. They will conflict.

Suggested order: land #862 first, rebase this on top, and let recoverAndroidHelperContentUnavailable rely on #862's finally for session teardown rather than stopping again (idempotent, so not a correctness bug — just redundant work).

Recommend converting to draft until the classifier covers the content-poor app-window case; otherwise this merges a passing test suite that doesn't fix the linked issue.

🤖 Automated review via Claude Code

@thymikee thymikee changed the base branch from main to fix/android-snapshot-helper-lifecycle June 24, 2026 16:24
@thymikee thymikee force-pushed the codex/android-fabric-snapshot-recovery branch from 43558e1 to 3085731 Compare June 24, 2026 16:24
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review blockers:\n\n- Stacked #861 on #862 by retargeting the base to fix/android-snapshot-helper-lifecycle; the recovery path now relies on #862's command-scoped session cleanup and only resets the helper runtime before stock UIAutomator fallback.\n- Broadened the classifier beyond empty/system-only output. It now treats content-poor foreground-app helper captures as degraded using bounded app-content quality counts, with appBundleId threaded from the Android interactor.\n- Added production-route coverage in snapshotAndroid for the content-poor foreground-app case, plus a healthy app+system window case that stays on the helper path.\n\nValidation after the rebase: pnpm check:quick, pnpm build, and pnpm exec vitest run src/platforms/android/__tests__/snapshot.test.ts pass. pnpm check:unit still has three unrelated Android fill timeout failures in src/platforms/android/__tests__/index.test.ts.

Base automatically changed from fix/android-snapshot-helper-lifecycle to main June 24, 2026 17:29
@thymikee thymikee force-pushed the codex/android-fabric-snapshot-recovery branch from 3085731 to 64d24e6 Compare June 24, 2026 18:09
@thymikee thymikee marked this pull request as ready for review June 24, 2026 18:09
@thymikee thymikee marked this pull request as draft June 24, 2026 18:15
@thymikee thymikee force-pushed the codex/android-fabric-snapshot-recovery branch from 64d24e6 to 3105aa8 Compare June 24, 2026 18:18
@thymikee thymikee marked this pull request as ready for review June 24, 2026 19:20
@thymikee thymikee marked this pull request as draft June 24, 2026 19:53
@thymikee

Copy link
Copy Markdown
Member Author

Latest review found a blocker: src/platforms/android/ui-hierarchy.ts now only treats a covering sibling as real if the covering node itself has label/id/hittable state. That fixes the transparent Expo container case, but a common unlabeled full-screen RN/modal/navigation container with visible child actions could stop covering lower drawing-order siblings and re-expose hidden background controls. The regression test was weakened by adding content-desc="Foreground surface", so it no longer protects the unlabeled-container case. Please restore coverage for an unlabeled covering container with visible children, adjust pruning so transparent helper containers are ignored without re-exposing background controls, and update the e2e evidence before moving this out of draft.

@thymikee thymikee marked this pull request as ready for review June 24, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Snapshot on Android RN New Architecture (Fabric) returns only the system window, never the app content

1 participant