Skip to content

fix(app): stop the browser pane jumping while resizing panels - #60757

Merged
thdxr merged 1 commit into
devfrom
fix-resize-flicker
Oct 31, 2026
Merged

thdxr merged 1 commit into
devfrom
fix-resize-flicker

Conversation

@thdxr

@thdxr thdxr commented Oct 31, 2026

Copy link
Copy Markdown
Member

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Linux (Wayland, high zoom), dragging a sidebar or panel edge made the side panel's browser page jump around on every move. It overlapped the chat, opened gaps, and fell out of line with its own tab strip.

The browser page is a native view. During a drag, EmbedView sent new bounds on every frame, and main applied them right away. The DOM around the view only reaches the screen a frame or more later, especially at a device pixel ratio of 4+, so the view and its panel drew from different frames. Panel open/close animations already avoid this: panelsMoving() hides native views and shows a still of the page in their place. Resize drags didn't count as movement.

  • ResizeHandle takes an onDraggingChange callback. It gets true on the first move (so a plain click on the handle doesn't blink the page) and false on release.
  • panel-motion adds setPanelResizing, and panelsMoving() is now true during a resize drag as well as during flights.
  • The vertical tabs sidebar, the chat/side panel edge, the bottom pane and the side region split pass setPanelResizing.

During a drag the page shows as a still, cropped to its box, that moves exactly with the DOM. The live view comes back when you release. Each frame also does less work, because the child page no longer re-lays out on every move.

How did you verify your code works?

  • Reproduced in the packaged beta inside a nested niri (in a headless sway), using real Wayland pointer input from a virtual pointer, at zoom 2 (DPR 4), with a browser tab in the side panel. Recorded frames show the native view drawn about 30 px off its panel and over the chat for several frames per drag. With this branch's renderer in the same app, the still stays aligned on every frame and the live view returns on release.
  • New component test in browser-pane.spec.ts, using the real ResizeHandle, panel motion and host embeds. It fails without the change.
  • bun run check; bun typecheck and typecheck:tsc in app, ui and gui-plugins; built e2e tabs, side-panels and panel-edges (46 passed); app components suite run cold with no optimizer reloads. The one failure, composer.spec.ts:3, is an existing flake in prompt history (it also fails 1 in 3 on repeat) and unrelated to this change.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none">
  <style>
    .move { animation: drag 3.2s ease-in-out infinite; }
    @keyframes drag { 0%, 15%, 85%, 100% { transform: translateX(0); } 45%, 55% { transform: translateX(-28px); } }
    @media (prefers-reduced-motion: reduce) { .move { animation: none; } }
  </style>
  <rect x="4.5" y="6.5" width="231" height="59" rx="7" stroke="currentColor" stroke-opacity=".25"/>
  <rect x="14" y="18" width="70" height="5" rx="2.5" fill="currentColor" fill-opacity=".35"/>
  <rect x="14" y="30" width="96" height="5" rx="2.5" fill="currentColor" fill-opacity=".2"/>
  <rect x="14" y="42" width="58" height="5" rx="2.5" fill="currentColor" fill-opacity=".2"/>
  <clipPath id="pane"><rect x="0" y="6" width="236" height="60" rx="7"/></clipPath>
  <g class="move" clip-path="url(#pane)">
    <rect x="132" y="6" width="140" height="60" fill="currentColor" fill-opacity=".06"/>
    <rect x="131" y="6" width="1" height="60" fill="currentColor" fill-opacity=".3"/>
    <rect x="129.5" y="26" width="4" height="20" rx="2" fill="currentColor" fill-opacity=".7"/>
    <rect x="142" y="16" width="84" height="40" rx="4" fill="currentColor" fill-opacity=".12"/>
    <rect x="150" y="24" width="44" height="5" rx="2.5" fill="currentColor" fill-opacity=".45"/>
    <rect x="150" y="35" width="64" height="4" rx="2" fill="currentColor" fill-opacity=".25"/>
    <rect x="150" y="44" width="52" height="4" rx="2" fill="currentColor" fill-opacity=".25"/>
  </g>
</svg>

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@opencodejr opencodejr 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.

The change looks sound. I couldn't reproduce the jump here because it needs the desktop app's native view on Wayland at a high device pixel ratio. I also couldn't run the component tests: Playwright's Chromium wouldn't start in this environment. typecheck:tsc passes in app and gui-plugins.

From reading the code: panelsMoving() only drives the embeds' moving input. So during a resize drag the native view switches to the still exactly as it does during panel animations, and nothing else changes. All four resize handles in the app are covered. A click without a move never hides the page, and if a panel closes mid-drag, the release listener on the document still clears the flag. The new test is small and checks the fixed behaviour without depending on frame timing.

Public API: unchanged. The only addition is an optional onDraggingChange prop on the internal UI ResizeHandle, which is backwards compatible.

No blocking concerns.

@thdxr
thdxr merged commit 591690c into dev Oct 31, 2026
21 checks passed
@thdxr
thdxr deleted the fix-resize-flicker branch October 31, 2026 00:57
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