fix(desktop): calibrate browser element capture against guest viewport - #3775
fix(desktop): calibrate browser element capture against guest viewport#3775kassamkhoja wants to merge 1 commit into
Conversation
f34271f to
604c743
Compare
6e5ec29 to
4809b76
Compare
|
| Filename | Overview |
|---|---|
| packages/desktop/src/features/browser-webviews/capture-element.ts | Adds calibrated full-frame cropping and stability checks, but the selector fallback can still retarget capture after the marked node is replaced. |
| packages/app/src/desktop/browser/pane/element-selector.electron.ts | Stamps the selected node with a session-specific capture marker and includes it in the selection result. |
| packages/desktop/src/features/browser-capture.ts | Routes validated capture requests through the calibrated element-capture module. |
| packages/desktop/src/preload.ts | Extends the context-isolated bridge with selector and capture-ID arguments. |
| packages/desktop/src/main.ts | Keeps IPC capture requests scoped to the sender-owned browser guest while forwarding the new identity fields. |
| packages/desktop/src/features/browser-webviews/capture-element.test.ts | Exercises calibration, marker lookup, movement retries, failure reporting, and frame validation through the capture interface. |
Sequence Diagram
sequenceDiagram
participant UI as Browser pane
participant IPC as Preload/Main IPC
participant Guest as Browser guest
UI->>IPC: capture(rect, selector, captureId)
IPC->>Guest: measure marked element
Guest-->>IPC: viewport and element rect
IPC->>Guest: capture full frame
IPC->>Guest: remeasure marked element
alt Measurements stable
IPC-->>UI: calibrated element crop
else Measurements diverge
IPC->>Guest: retry once
end
Reviews (4): Last reviewed commit: "fix(desktop): calibrate browser element ..." | Re-trigger Greptile
4809b76 to
bfeb102
Compare
bfeb102 to
0cce28d
Compare
Element screenshots for annotations cropped the wrong region whenever the display scale factor or page zoom was not 1: the CSS-pixel rect from getBoundingClientRect() was passed straight to capturePage(), whose frame is device-scaled, so the crop landed offset and undersized on HiDPI screens. capture-element.ts now captures the full frame, derives the CSS-to-pixel scale from the actual frame size versus the measured guest viewport, and crops itself, clamped to frame bounds. The element is re-measured by its selector at capture time so a scroll between selection and capture cannot skew the region. Falls back to the legacy rect path when the guest cannot be measured.
0cce28d to
d2d0353
Compare
| if (!el && selector) { | ||
| el = document.querySelector(selector); | ||
| } |
There was a problem hiding this comment.
Selector fallback retargets capture
When an SPA render or virtualized-list update replaces the selected element before capture, the capture marker disappears and this fallback resolves the original positional or duplicate-ID selector. Two stable measurements of that replacement are then accepted, causing the annotation or clipboard screenshot to contain a different element.
Linked issue
No issue filed yet, happy to open one with the raw evidence if preferred. Repro details are in QA below.
Type of change: Bug fix
Reasoning
Element screenshots for browser annotations were inaccurate: agents received an image that did not match the annotated div. Pixel analysis of a real case showed the captured image contained entirely different content than the expected element.
The
paseo:browser:capture-elementhandler passed the CSS-pixel rect fromgetBoundingClientRect()straight intocontents.capturePage(rect). The returned frame is device-scaled, and page zoom shifts the mapping further, so whenever display scale or zoom was not exactly 1 the crop landed offset and undersized. A second failure mode: any scroll between clicking an element and the screenshot landing used the stale click-time rect.This matters to users because annotated element screenshots are what agents see when driving the browser pane, a wrong crop means the agent reasons about pixels the user never pointed at. Also, I presume many people use Paseo
Reproduced with real Electron under Xvfb + MacOS (27.0 Beta 26A5416b) against a fixture page (fraction = share of captured pixels matching the annotated element):
capturePage(rect)The fix captures the full guest frame and crops itself, deriving the CSS→pixel scale from the actual frame size versus the measured guest viewport (
innerWidth/innerHeight). That makes the result independent of how Electron maps rects internally across platforms, display configurations, and versions. The selector flow stamps a uniquedata-paseo-capture-idon the clicked node, and the element is re-measured by that marker at capture time so scroll drift cannot skew the region and a stale/ambiguous selector cannot retarget the crop. The guest is measured again after the frame lands and the crop is only applied when both measurements agree (one retry). The crop is clamped to frame bounds. The CSS rect is never passed tocapturePage; when no trustworthy crop can be produced the call returns null rather than an unrelated screenshot.New module
packages/desktop/src/features/browser-webviews/capture-element.ts, wired through the existingbrowserCaptureservice; preload and both call sites passselection.selector+selection.captureId.Goals
Non-goals
QA
--force-device-scale-factor=1.25+ page zoom 1.25 under Xvfb; legacycapturePage(rect)captured only 67.6% of the target element (54.9% after a scroll), confirming both failure modes above.npx vitest run src/features/browser-webviews/capture-element.test.ts src/features/browser-capture.test.ts→ 18 passed.npm run typecheck -w @getpaseo/desktop -w @getpaseo/app: clean.npm run linton changed files: 0 warnings/errors.Platforms: tested on Linux (Xvfb,Electron). and MacOS (27.0 Beta 26A5416b). Not tested Windows, I don't have a windows machine. However, the calibration does not depend on platform capture internals, but confirmation on a HiDPI Windows display would be welcome.
Before and After Screenshots
Reproduced at 1.5x zoom on the Paseo desktop app on MacOS.
Before screenshots from website:
Screenshot 1:


Image sent to agent:
Screenshot 2:


Image sent to agent:
In each “before” pair, the image sent to the agent showed different content than the annotated element: e.g., highlighting the stats card delivered a crop of an unrelated region.
After screenshots from website:
Screenshot 1:


Image sent to agent:
Screenshot 2:


Image sent to agent:
As seen in the after screenshots, the images sent to the agents matched identically to the annotation area selected with the annotation tool.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses