Skip to content

fix(visual): Android captures see CMP dialog windows, Compose nodes and SVG data URIs - #3583

Merged
stamp-build[bot] merged 1 commit into
mainfrom
agent/lagoon/visual-capture-fix
Oct 23, 2026
Merged

stamp-build[bot] merged 1 commit into
mainfrom
agent/lagoon/visual-capture-fix

Conversation

@stamp-build

@stamp-build stamp-build Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Lagoon from raft opened this pull request through Stamp
View in Stamp →


Android visual capture has failed on main since 10-20, so no cross-platform report has been published. The same 12 cases fail in every full run from 39440171589 (86449d0) to 39923576664. The last full green run was the 10-19 nightly 39328865111 (a52d784). Instrumentation exits 0 in each of those runs; the failures come from capture validation. Two changes in that window explain all of them: the Kuikly to Compose Multiplatform switch (#3192) and the fixture alignment in #3231. The fix for each group is below.

Pin menus (search Channel/DM/Agent/Human) and the compact message menu (first/middle/last). These menus render in RaftModalAnchoredPopover, which sits inside Modal. Since #3192, Modal is its own Android Dialog window. The long-press render poll and its before frame drew only the activity decor, so the open menu never appeared in them (changedRatio 0, 0 saturated pixels). The fixture's own "menu open" token fired in every run. #3231 already composites fixture-owned windows for the final capture, but it left the probes on activity-only capture. The probes now use the same composite. The pin case's "selected" frame stays activity-only because it checks the page's own pin state after the menu closes.

Pending-mention batch partial/details and create-agent dialog-empty. findAccessibilityNodeInfosByText asks the view's AccessibilityNodeProvider. Compose's provider (checked in compose-ui 1.11.2 and 1.12.1) implements only createAccessibilityNodeInfo and performAction, so a by-text lookup finds no Compose node. That is why the failure listed "View" among the visible texts and still could not find it. Both lookups now walk the accessibility tree for an exact text match.

File preview image actions. The fixture image is a percent-encoded data:image/svg+xml;utf8, URI. Coil's DataUriFetcher accepts only ;base64, and throws "invalid data uri", so the preview showed its "IMG" placeholder. The image editor cases have the same bug: their canvas is black. They passed only because a black canvas is not "one colour". raftImageSourceOf now decodes textual SVG data URIs into the existing InlineSvg source, which restores what the Kuikly adapter did. Base64 data URIs still go to Coil. RaftImageSourceDataUriTest covers this.

Status dot states. #3231 aligned the fixture with the web case (four presence dots in a 342x92 frame, no "Waiting pulse" row). The render now matches the web, and a correct frame is 98.8% canvas, which trips the "98.5% one colour" blank-frame proxy. No comparison tolerance changes here. Instead, this crop declares its content: the sticker ink plus the four presence fills, each at least a 4dp square (144 px at 3x). That check replaces the one-colour proxy for this case only. It still fails a blank frame, and it also fails when any single dot is missing, which the proxy could not catch. Measured on the failing frame: the smallest fill is 196 px and the largest anti-aliased blend is 63 px.

No thresholds or tolerances were loosened, and no case was deleted. No changeset: this is a test-harness fix plus a fixture-image decode that only fixtures use today.

Verification: visual-testing.yml run 39930831956 on this branch (full manifest, publish=false) passes all 8 Android capture shards and the report job. I checked the image-actions, compact-menu and dialog-empty captures by eye: the preview image renders, the menu is painted, and the name field is empty.

🤖 Generated with Claude Code

…nd SVG data URIs

Android visual capture has failed 12 cases on main since the Kuikly to
Compose Multiplatform switch (#3192) and the fixture alignment in #3231
(first full failing run 39440171589 at 86449d0; last full green run
39328865111 at a52d784).

- Pin menus and compact message menus: RaftModalAnchoredPopover renders in
  Modal, which on CMP is its own Dialog window. The long-press render poll
  and its before frame drew only the activity decor, so the open menu was
  invisible (changedRatio 0). They now use the fixture-owned window
  composite, the same one the final capture has used since #3231.
- Pending-mention batch partial/details and create-agent dialog-empty:
  findAccessibilityNodeInfosByText goes through the view's
  AccessibilityNodeProvider. Compose's provider does not implement by-text
  search, so the lookup found nothing even with "View" on screen. Walk the
  accessibility tree for an exact text match instead.
- File preview image actions (and the image editor canvas): the fixture
  image is a percent-encoded data:image/svg+xml URI. Coil's DataUriFetcher
  accepts only ;base64, and threw, so the preview fell back to its
  placeholder. raftImageSourceOf now decodes textual SVG data URIs into the
  existing InlineSvg source.
- Status dot states: #3231 aligned the fixture with the web case (four
  presence dots in 342x92, no pulse row), so a correct frame is 98.8%
  canvas and tripped the blank-frame proxy. The crop now declares its
  content (sticker ink plus four presence fills, each >= a 4dp square),
  which replaces the one-colour proxy for this case only and still fails a
  blank frame or a missing dot.

Signed-off-by: Lagoon <raft+lagoon@agents.stamp.build>

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stamp-build
stamp-build Bot merged commit 857e1fe into main Oct 23, 2026
35 checks passed
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.

0 participants