Skip to content

Full-page evidence: hide fixed sheets over element shots, stitch pages that relay for the capture - #1149

Merged
pbakaus merged 12 commits into
mainfrom
corpus/round13-harness
Oct 23, 2026
Merged

pbakaus merged 12 commits into
mainfrom
corpus/round13-harness

Conversation

@pbakaus

@pbakaus pbakaus commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Round 13 of the detector corpus, branch F: the evidence a URL scan hands a reviewer. Nothing here changes what the engine detects. It changes the pictures and anchors that go with the findings, so a judge sees what the visitor sees.

Row 21: a fixed sheet over every element shot

On theguardian.com's phone pages, a fixed "This is the catch" support sheet covers the lower two thirds of the viewport. Every element shot past the screenshot's cut showed the sheet instead of the flagged element: 9 of 9 tiles on the international front, all 27 on an article.

Element shots now hide the fixed layers painted over the flagged element, for that shot only, and put them back afterwards. At 9 points over the element's visible part the engine walks document.elementsFromPoint down to the element, and hides each position: fixed layer it passes. Guards:

  • A point counts only when the walk reaches the element or something inside it. A layer painted under the element (a fixed backdrop, a WebGL canvas behind content) is never hidden.
  • The element is made hit-testable for the walk (pointer-events: auto, restored right after), so content that takes no pointer events, such as card text under a link stretched over the card, is listed at its true paint position.
  • Never the layer that holds the element, and never a fixed box under a transformed, filtered or contained ancestor (it scrolls with the page, the same containment list stitch uses).
  • An open dialog or an aria-modal layer stays in the shot, and so does a fixed backdrop that wraps one: the visitor sees it.
  • A tile is reused for a later element only when no layer it left showing covers that element.

Each shot records what was hidden in ElementShot::hidden_covers, so a judge can tell "chrome removed for this shot" from "what the visitor sees".

Live, on the revised head: theguardian.com/international on a phone, 32 of 32 element shots hide the sheet and no tile shows it (base: every tile showed it). The article on desktop: 15 of 15. The article on a phone: 61 of 61. The international front on desktop has no fixed sheet and records none. The hit-testable step was added after review: without it, 6 of the 32 phone shots (card labels under a stretched link) kept the sheet on 5 of 11 tiles.

Row 23: a page that re-lays itself for the beyond-viewport capture

developer.apple.com's guideline pages answer the capture's resize (Chromium dispatches it at 1x1, then at the real size) by folding the navigator and sliding the column back over about 300ms, and the capture paints that state: the column at x 309 while the rects say 390.

The engine now measures a sample of layout boxes (a 6x6 grid of elements in the first viewport, offset geometry, so transforms do not count) before and right after the capture. When at least 2 boxes and a quarter of the sample moved sideways, it waits up to 2 seconds for the boxes that moved to come back to where they were measured, twice in a row 100ms apart, and then stitches the page from viewport tiles. A page that keeps moving (a marquee on margin-left) never settles and keeps the beyond-viewport capture, and so does any page where a box that moved at the capture keeps its new geometry. Vertical moves are ignored: an image loading during the capture pushes content down without a relayout.

This does not fix developer.apple.com itself. On the live page five navigator link labels change width at the capture (96px to 84px and similar) and keep their new widths, while the column comes back within 100ms. The engine cannot tell whether the image or the rects hold those labels' true place, so the page fails safe to the beyond-viewport capture, as on main, after a 2 second wait. Widening the rule to let it through would also let through pages whose picture really is wrong. The fallback is proven by a browser fixture that folds on resize and slides back; the never-ending ticker and the slide-only page are its twins.

The stitched fallback also needed a fix to stitch: every tile after the first re-tests sticky boxes and hides one stuck at its own top offset once its own place in the document is above that tile, so an earlier tile already painted it. A section bar is no longer repeated at the top of every tile, and a bar whose section starts on the current tile (or exactly on its top edge) stays.

Row 24: page-level anchors for organic clip paths and bounce easing

page_form_anchor now anchors organic-clip-path (an element whose computed clipPath is a polygon passing the scan's own off-grid and not-rectilinear tests, through a shared css_scan::organic_polygon_vertices) and bounce-easing (a computed timing function with the same bezier, or an animation the snippet names). None of the corpus's listed findings gain an anchor from it today: apple.com's polygon is on a media-controls pseudo-element, and framer's bounce curve is declared but computed by nothing at capture. A local page with all three page forms gets anchors for the halo and the polygon.

Corpus numbers

Replays on main's runs, against main's replay of the same run (c51e529):

Run Captures Violations (branch / main) Rule table and every other field
1009 (craft) 132 0 / 0 identical
1006 (corpus) 243 16 / 16 identical
1004 (corpus) 177 8 / 8 identical
47 (corpus) 226 14 / 14 identical

The violations are main's own against those runs' judged labels; this branch adds none and removes none.

No craft mark is removed on run 1009 (legit 0, harmless 0, false positive 0, unclear 0). Replays exercise detection, not the evidence step, so identical tables are the expected result; the evidence changes were checked live on the pages above.

Tests

  • Browser tests in crates/browser/tests/fullpage_screenshot.rs: the sheet is hidden (an_element_shot_hides_the_fixed_sheet_over_the_element, ..._over_content_the_hit_test_misses), with twins that keep a fixed backdrop, a backdrop under content that takes no pointer events, a fixed box that scrolls along, an open modal, a modal wrapped in a fixed backdrop, and a shared tile only for elements no shown layer covers. The relayout fixture is stitched, with sticky bars that stick on a later tile, stick on the tile their section starts on, and start on a tile's top edge; a ticker that never stops and a page that only slides keep the beyond-viewport capture. Assertions read solid colour blocks, never text pixels.
  • Unit tests for layout_moved, moved_boxes and settled, including the apple shape (labels that move at the capture and stay keep the page unsettled), and for the anchors, with a zigzag polygon decoy.
  • No oracle case changed. No text or static-HTML check changed, so no hook cost is owed.

Gates on the merged head 1437fd2 (origin/main 5cb95ee merged in, no conflicts), before the review-round commit 8772352, which touches only fullpage.rs and its browser tests (all 21 pass, and the bundle check holds): cargo build --release -p impeccable, cargo xtask bundle --check (both live assets up to date), the node oracle (no case changed, no re-record, no DELTAS entry), cargo test --workspace (2257 passed, 0 failed), bun run build with no tracked changes, and bun run test with the branch binary.

AI assistance: prepared by Claude Code, under instructions from @pbakaus.

🤖 Generated with Claude Code


Note

Medium Risk
Changes screenshot capture timing (up to 2s layout wait), in-page DOM opacity during shots, and new evidence fields; detection tables are unchanged but evidence output behavior shifts on real sites.

Overview
Improves evidence screenshots without changing detection rules: reviewers get clearer full-page and per-element images, plus better anchors for some page-level CSS findings.

Element viewport shots now temporarily hide position: fixed layers that paint over the flagged element (support appeals, cookie sheets), using a 9-point elementsFromPoint walk with guards for backdrops, modals, transformed “fixed” content, and pointer-events edge cases. Shots record what was hidden in ElementShot::hidden_covers, and tile reuse only applies when the previous capture left no covering layer.

Full-page capture probes layout before/after a beyond-viewport screenshot; when enough boxes shift horizontally and then settle back, it stitches viewport tiles instead of keeping a capture painted during a resize-driven relayout (e.g. folding side nav). Continuous motion or permanent geometry changes still keep beyond-viewport. Stitching also hides sticky bars that were already painted on earlier tiles.

Evidence anchoring extends page_form_anchor to organic-clip-path and bounce-easing via shared organic_polygon_vertices in CSS scan logic.

Browser and unit tests cover sheets, modals, shared tiles, relayout/sticky stitching, and layout probe helpers.

Reviewed by Cursor Bugbot for commit 0d0276f. Bugbot is set up for automated code reviews on this repo. Configure here.

pbakaus and others added 9 commits October 22, 2026 15:42
An organic-clip-path or bounce-easing finding reported on body now records,
like radial-halo and dark-glow, the painted element whose computed style
carries the declaration: a polygon() clip with the finding's vertex count,
or the overshooting timing function or bounce animation it names. Evidence
only; the finding still names the page.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…ge that relays for the capture

- An element shot hides, for the shot only, each position: fixed layer the
  hit test lists above the flagged element (theguardian.com's support
  appeal over the lower two thirds of a phone viewport). Never the layer
  holding the element, one that scrolls along, or a backdrop painted under
  it. A shared tile serves another element only when no layer it left
  showing covers that element.
- The beyond-viewport capture measures a sample of layout boxes before and
  after; when a quarter of them moved sideways (developer.apple.com folds
  its navigator for the capture's resize and slides the column back while
  it paints), the page is stitched from viewport tiles once settled.
- Stitched tiles after the first also hide a sticky box the document's
  scroll has stuck at its offset, so a section bar under a header that
  scrolls away no longer repeats on every tile.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…c anchor shape

- Remove four debug writes to a local path left in the fullpage tests.
- A point's covering layers count only when the hit-test stack reaches the
  flagged element or something inside it: under a layer that takes no
  pointer events the stack runs past the element to the fixed backdrop
  beneath it, which stayed hidden as a cover.
- An open modal (dialog[open], aria-modal="true") is never hidden.
- Each element shot records the fixed layers hidden for it
  (ElementShot::hidden_covers).
- The relayout fallback stitches only when the sample settles back to where
  it was measured (two measures in a row); a page in steady motion (a
  margin-left marquee) keeps its beyond-viewport capture.
- Stitch tiles re-test sticky boxes on every later tile, so one that sticks
  only further down is hidden too.
- The organic-clip-path anchor applies the scan's own polygon tests
  (count, grid, rectilinear, zigzag), shared through
  organic_polygon_vertices.
- Twins: a backdrop under a pointerless element, a fixed box under a
  transformed ancestor, a shared tile with one covered element, an open
  modal, a never-ending marquee.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
A page measured before the capture is stitched only once the boxes that
moved during the capture are back where they were measured. Boxes that
change later were painted in their measured place and no longer block.
A box that moved at the capture and keeps its new geometry still keeps
the beyond-viewport capture.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Card content under a link stretched over the card is not in the hit
stack, so the walk met the card first and dropped the sheet over it
(theguardian.com mobile: the support sheet stayed on 5 of 11 tiles).
The element is made hit-testable for the walk only, so it is listed at
its true paint position; the backdrop under it still stays.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@impeccable-reviews

impeccable-reviews Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Retrigger
5 of 5 5/5 · Ready: no major issues found
This push leaves nothing to fix.

Nothing to fix.

About this review
  • crates/live/assets/detect-antipatterns-browser.js is too large to read.
  • Read from the code alone; no page was opened.

Reviewed 1 file on 23 Oct 2026, 18:25 UTC · Run page

Reviews (4) · Last reviewed commit: Merge origin/main into corpus/round13-harness

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/browser/src/fullpage.rs Outdated
@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes evidence capture for URL scans, not the findings themselves.

Fix cover removal for sheets with opacity transitions before merging.

Fix All in CodexFindings

  1. P1 Fading sheets remain in screenshots ▶

Summary

Improves screenshot evidence and adds page-level anchors without changing detection rules.

  • Element shots show the flagged element instead of fixed layers painted over it.
  • Full-page captures stitch pages that settle after a capture resize.
  • Page-level anchors point to elements carrying organic clips and bounce easing.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Capture full page] --> B{Layout moved and returned?}
  B -->|Yes| C[Stitch viewport tiles]
  B -->|No| D[Keep beyond-viewport image]
  C --> E[Find flagged elements outside image]
  D --> E
  E --> F[Scroll element into view]
  F --> G[Hide fixed covers]
  G --> H[Capture element picture]
  H --> I[Restore covers and record hidden layers]
Loading

Reviews (4) · Last reviewed commit: "Merge origin/main into corpus/round13-ha..." · Reviewed by Greptile

Comment thread crates/browser/src/fullpage.rs Outdated
Comment thread crates/browser/src/fullpage.rs Outdated
…ackdrop

A stitch hid any sticky box sitting at its top offset. That is also a bar
whose section starts on that tile's top edge, or one stuck on the tile its
section starts on: neither was painted before, so the image lost it. Now
a stuck box is hidden only once its own place in the document is above
the tile.

Element shots spared an open dialog only when it was the fixed layer or
inside it. A fixed backdrop that wraps an aria-modal dialog is spared too.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread crates/browser/src/fullpage.rs Outdated
A cookie banner holding a closed preferences panel marked aria-modal kept
the banner over the flagged element. The wrapped modal now counts only
when it is visible.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread crates/browser/src/fullpage.rs
Regenerated the in-page bundle after branches A and C landed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Comment thread crates/browser/src/fullpage.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0d0276f. Configure here.

Comment thread crates/browser/src/fullpage.rs
@pbakaus
pbakaus merged commit f7fd78d into main Oct 23, 2026
20 checks passed
@linear-code

linear-code Bot commented Oct 23, 2026

Copy link
Copy Markdown

REN-1212

abdulwahabone added a commit that referenced this pull request Oct 24, 2026
A day of main comes in: the round-13 measurement defects (#1151) and
the straggler fix (#1135), hidden product mockups (#1137), legal and
footnote blocks (#1146), full-page evidence capture (#1149), the Tune
value fixes (#1108, #1154), the launcher and ignore-boundary round
(#1116), the craft-floor launcher fallback (#1153), the reference docs
reconciliation (#1117), the CSP meta reference fix (#1145), keeping
installed skills when the bundle is unreachable (#1144), the debuginfo
cut (#1150), and the generated provider syncs. No source conflicts: the
in-page bundle and five sweep goldens conflicted as generated files.
The bundle was regenerated from the merged source with `cargo xtask
bundle`; the sweep goldens are main's as they stand, since main carries
them as accepted deltas in DELTAS.md (its own new fixtures are not
recorded in them yet), and this branch's sweep entries are covered by
its existing DELTAS entries the same way.

Prepared with AI assistance (Claude Code).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant