Skip to content

fix(web): trackpad swipes over inline renders no longer drop after a direction change - #29477

Open
CadenEl wants to merge 7 commits into
pingdotgg:mainfrom
CadenEl:fix/html-render-scroll-latching
Open

CadenEl wants to merge 7 commits into
pingdotgg:mainfrom
CadenEl:fix/html-render-scroll-latching

Conversation

@CadenEl

@CadenEl CadenEl commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

On macOS, trackpad swipes with the pointer over an inline HTML render are often lost right after a direction change, so you have to swipe twice. Some fitted render frames end up 0.5 px shorter than their page. Chromium latches a swipe to any frame that can scroll even one device pixel, so the swipe never reaches the thread. Details and measurements are in #23864.

This replaces #23865, which was closed for missing a recording. The recordings are below.

Change

Inline render frames set scrolling="no" while they are fitted to their page: before the page first reports its height, and while that height is within HTML_RENDER_MAX_FIT_HEIGHT. The page can then no longer scroll its own viewport by user input, so Chromium never picks the frame as the swipe's scroll target. Script-driven scrolling, scrollable elements inside the page, and wheel handlers in the page are unaffected.

  • The fix is on the host frame because the page is cross-origin and sandboxed, and the bootstrap is baked into each render when it is published. A bootstrap change would not reach existing renders.
  • The guest share view (GuestHtmlRender) fits its frame the same way but is left as is. It has no fullscreen, so a page wider than its frame would have no way to scroll sideways. It can get the fix once it has a full-size view.
  • Pages past the fit cap still scroll inside their frame. The fullscreen dialog and file previews are unchanged.
  • A page that takes the screen itself (renders allow fullscreen) drops the attribute while it is the fullscreen element, so a page taller than the screen can still scroll there. Chromium applies the attribute change to a live frame in both directions.
  • A frame with no reported height yet counts as fitted. A remounted frame starts at HTML_RENDER_MAX_HEIGHT until its page reports, and a taller page could otherwise latch swipes during that time.

One behavior changes: a page wider than the chat column can no longer be scrolled sideways inline. It still can in fullscreen (third clip below).

The frame being 0.5 px short is a separate bug, left for its own fix.

Scope and approval

Fixes #23864.

Verification

Recordings

Dev desktop build on macOS, recorded through the DevTools protocol screencast. "This branch" is f38853e; the later commits only change a page that takes the screen and leave the guest view out. The thread is a demo thread with four published pages of made-up data. Two of them are 0.5 px taller than their frame, which is the bug's trigger. The yellow dot, the swipe labels and the result labels are an overlay drawn by the recording script; each result label is the thread's measured scroll change. Swipes are synthesized trackpad-style scroll gestures (Input.synthesizeScrollGesture) at a fixed point over the render, alternating direction.

1. Alternating swipes over a render. main (c8a601c) first, then this branch.

Alternating swipes, before and after

MP4

Build Swipes that moved the thread
main 0 / 8
This branch 8 / 8

2. A page wider than the column. main inline, this branch inline, then this branch in fullscreen.

Wide page, before and after

MP4

Build Sideways scroll of the page
main, inline 250 px per swipe
This branch, inline none
This branch, fullscreen 212 px per swipe (its full range)

Other checks

  • Real trackpad, real thread. Dev builds of main and of this fix on a thread of real renders, with scroll events logged from the trackpad. Reversed swipes over a render were lost 19 / 44 times (43%) on main and 2 / 24 (8%) with the fix. The remaining losses came before the window was in front or overlapped momentum from the previous swipe.
  • Minimal reproduction. Headless Chrome, a 1,500 px cross-site sandbox="allow-scripts" iframe holding a 1,500.5 px page: 7 / 10 alternating swipes lost without scrolling="no", 0 / 10 with it.
  • Content that scrolls. A scrollable box inside the page, a sideways-scrolling table, a chart that zooms on wheel events, and a page taller than the fit cap behave the same with and without the change.
  • Wide pages in practice. Of 37 real renders I checked at 600, 728 and 900 px, none was too wide at 600 or 728 px; one was 43 px too wide at 900 px, from a faded text label.
  • vp run check:changed passes: format, lint, knip, typecheck, the tests beside the changed files, and the React compiler check.

There is no automated test. The bug is in Chromium's gesture latching across out-of-process frames, which the web browser tests can't drive, and a test that the attribute is set would only mirror the implementation.

Not checked: Windows, Linux, and mouse wheels.

…direction change

Fitted inline render frames can end up a fraction of a pixel shorter than
their page. Chromium latches a trackpad swipe to a frame that can scroll even
one device pixel, so a reversed swipe moved neither the page nor the thread.
Fitted frames now set scrolling="no".
…l-latching

# Conflicts:
#	apps/web/src/components/files/BrowserDocumentFrame.tsx
…l-latching

# Conflicts:
#	apps/web/src/components/files/BrowserDocumentFrame.tsx
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Nov 2, 2026
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 8657eec and e94a5bc.

Files selected for processing: 3
  • apps/web/src/components/chat/HtmlRenderFrame.tsx
  • apps/web/src/components/files/BrowserDocumentFrame.tsx
  • apps/web/src/components/guest/GuestHtmlRender.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The change disables iframe-level scrolling for fitted HTML renders, including while content height is unknown. The shared document frame leaves scrolling unspecified when fullscreen or not fitted. Guest renders apply the height threshold directly.

Changes: Fit-aware render scrolling

Layer / File(s) Summary
Fit-aware iframe scrolling
apps/web/src/components/files/BrowserDocumentFrame.tsx, apps/web/src/components/chat/HtmlRenderFrame.tsx, apps/web/src/components/guest/GuestHtmlRender.tsx
The shared document frame disables scrolling when fitted and not fullscreen. Chat and guest renders treat unknown content height or height at or below HTML_RENDER_MAX_FIT_HEIGHT as fitted. Taller content retains unspecified iframe scrolling.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: juliusmarminge, maria-rcks

Merge Risk: Moderate · up to e94a5

Guest share pages may clip content, either horizontally for wide pages or vertically if the first height report is missed, and the guest view has no way to scroll it back into reach. Address both before merging. The chat view's behavior is unaffected by these issues.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title names the main change: trackpad swipes over inline HTML renders no longer drop after a direction change. The change adds scrolling="no" to fitted render frames, which the title does not name…
Linked Issues check Passed Linked issue #23864 is the only direct, active target. Its coding requirement is to stop Chromium from latching macOS trackpad swipes to fitted inline render frames, while keeping scrolling for pages …
Out of Scope Changes check Passed The whole-PR diff (merge base c8a601c to head e94a5bc) changes only three files: apps/web/src/components/chat/HtmlRenderFrame.tsx, apps/web/src/components/files/BrowserDocumentFrame.tsx, and `apps…
Description check Passed The description includes all required sections. It clearly explains the problem, expected behavior, implementation, scope, linked issue, verification evidence, limitations, and unchecked environments.
Full details: Title check

Explanation

The title names the main change: trackpad swipes over inline HTML renders no longer drop after a direction change. The change adds scrolling="no" to fitted render frames, which the title does not name. It is a conventional commit title in plain language, and it matches the objectives and the linked issue #23864.

Full details: Linked Issues check

Explanation

Linked issue #23864 is the only direct, active target. Its coding requirement is to stop Chromium from latching macOS trackpad swipes to fitted inline render frames, while keeping scrolling for pages past the fit cap and for content inside the page. The reviewed head implements this in three places:

  • HtmlRenderFrame.tsx passes fitted={contentHeight === undefined || contentHeight <= HTML_RENDER_MAX_FIT_HEIGHT} to HtmlRenderDocument. A frame with no reported height counts as fitted.
  • BrowserDocumentFrame.tsx HtmlRenderDocument sets scrolling={props.fitted && !fullscreen ? "no" : undefined}. The fullscreen value comes from useSyncExternalStore on document.fullscreenElement === frameRef.current, so a page that takes the screen can still scroll.
  • GuestHtmlRender.tsx sets scrolling to "no" when contentHeight is undefined or at most HTML_RENDER_MAX_FIT_HEIGHT, and leaves it undefined above the cap.
    Frames past the cap keep the default scrolling. The issue asks for no automated test, so the lack of one does not fail this check. The macOS recordings, the real-trackpad counts, and the minimal reproduction in the PR description are author reports. I did not re-run them. The code change matches the requirement.
Full details: Out of Scope Changes check

Explanation

The whole-PR diff (merge base c8a601c to head e94a5bc) changes only three files: apps/web/src/components/chat/HtmlRenderFrame.tsx, apps/web/src/components/files/BrowserDocumentFrame.tsx, and apps/web/src/components/guest/GuestHtmlRender.tsx. Every hunk applies the fitted-frame scroll lock or supports it. This covers the new fitted prop, the fullscreen snapshot that restores scrolling, the scrolling attribute, and the guest share view. The guest share view uses the same render path as the issue, and the PR objectives name it. No unrelated changes appear. One effect goes beyond the issue text. Inline pages wider than the column can no longer scroll sideways, because the frame no longer scrolls its viewport. The PR describes this as a deliberate trade-off. It is a direct result of the approach the issue proposes, not an unrelated change. Fullscreen keeps the sideways scroll.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Fix comments: Fix CodeRabbit comments on this PR

The guest view has no fullscreen, so a fitted frame that cannot scroll
would cut off a page wider than the column with no way to reach it.
@maria-rcks

maria-rcks commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at bac19f0c43e1f1106c1fe134a40bf2e8b29b0004 on linux, using the repo's playwright dependency and /usr/bin/google-chrome against vp run dev. baseline was github/main at c3cfb5e1e3 in a separate worktree. only one dev server ran at a time, with isolated state.

created the real git project and thread in the web app. real codex gpt-6.1-sol turns published the review documents through html_render, including another successful provider turn on the pr head. no mocks, seeded fixtures, source edits, commits, or pushes.

  • main reproduced the remount failure: a 2,195px document briefly had a 2,000px viewport and a 195px inner scroll range. 4 of 12 downward 350px gestures moved the thread 0px. a later recorded alternating run also dropped a gesture.
  • the pr passed 12/12 downward gestures, 12/12 alternating gestures, then 24/24 alternating gestures in the final recording run. the latter moved the thread 334–350px per 350px input, including remounts observed at 2,000px. gestures used CDP Input.synthesizeScrollGesture with gestureSourceType: "mouse", normal CPU rate, and the pointer over the real inline frame. each trial scrolled away and returned to exercise remounting.
  • nested overflow scrolled 250px and the page's wheel handler received input.
  • page-owned fullscreen removed the scroll lock, scrolled vertically 500px, and restored scrolling="no" on exit.
  • wide pages stayed locked horizontally inline; the normal expand dialog restored sideways access (328px at a 1,100px window). zoom out reached 75%, reset and close worked.
  • the 22,016px document retained default iframe scrolling above the fit cap and scrolled 600px inside its 20,000px viewport.
  • blacksmith: vp test run apps/web/src/components/files/BrowserDocumentFrame.browser.test.tsx apps/web/src/components/chat/HtmlRenderFrame.browser.test.tsx passed both files, 3 tests. the worker's missing chromium was installed before rerunning. existing github checks are green.

matching main and pr screenshots show the same provider-published document. the short clip shows main followed by the pr; captions are recording annotations, not app controls. uploaded playback and seeking were checked.

main: provider-published remount review

pr: same provider-published remount review

main then pr: alternating scroll gestures over the inline render

Not verified

native macos trackpad input, the exact persistent 0.5px macos frame mismatch, electron shell behavior, windows, ios/android, guest sharing, and remote/relay/tunnel connections were not exercised on this linux vm. the linux reproduction covers the remount/latching path, not a claim about physical macos input. html_preview timed out on main; published pages loaded through the real client, and html_render succeeded on both revisions. no source fix was needed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Trackpad swipes over inline HTML renders are dropped after a direction change (macOS desktop)

2 participants