Skip to content

Overlay the hover-revealed tab bar instead of reflowing content - #14947

Open
bobrnor wants to merge 1 commit into
warpdotdev:masterfrom
bobrnor:jolly-foraging-willow
Open

Overlay the hover-revealed tab bar instead of reflowing content#14947
bobrnor wants to merge 1 commit into
warpdotdev:masterfrom
bobrnor:jolly-foraging-willow

Conversation

@bobrnor

@bobrnor bobrnor commented Aug 11, 2026

Copy link
Copy Markdown

Description

In fullscreen, a hover-revealed tab bar flickered open and closed while the pointer stood still at the top edge of the screen, reflowing every pane by the height of the bar on each flip.

The point that revealed the bar was not inside the region that kept it revealed. The reveal strip is a positioned stack child anchored at the window's top-left, TAB_BAR_HOVER_HEIGHT (12) tall. The revealed bar was added to the content column, which is inset by WORKSPACE_PADDING, so it started one pixel lower. Measured from the rendered scene at a 1024x768 window:

element before after
reveal strip RectF(<0, 0, 1024, 12>) unchanged
revealed bar RectF(<1, 1, 1023, 36>) RectF(<0, 0, 1024, 35>)

The top pixel row revealed the bar and then sat outside it. In fullscreen the cursor clamps to y = 0 at the screen edge, which is exactly that band, so the bar oscillated with no mouse movement at all: build_scene replays the last pointer position as a synthetic MouseMoved after every frame, and there is no mouse-exit event to invalidate that stored position.

This adds ShowTabBar::Overlay, used for every hover reveal. The bar is positioned at the window's top-left over the content instead of taking a row in the column, so it now covers a superset of the strip — the pointer that opened it stays over it — and revealing it no longer moves any pane. Two things follow from leaving the column: the overlay stretches itself to the full window width (a positioned stack child is laid out with a zero minimum width), and it paints its own background, flattened to a solid color because theme.background() may be a gradient whose stops would otherwise be squeezed into a 35 px strip.

Revealing now takes a 250 ms hover-in delay, so a sweep across the top of the window no longer pops the bar open. Hiding deliberately stays undelayed: Hoverable drops the redraw for a state change whose previous change was also synthetic, and with both delays set every transition resolves on the synthetic MouseMoved replayed after a frame — the bar would flip to hidden without scheduling the frame that repaints it. That constraint is recorded next to the constant.

Linked Issue

Fixes #14945.

Related: #10784 is the same mechanism seen from the other side — the reveal pushing content down made a tmux top status line escape the cursor, so chasing it re-triggered the bar. This removes that chase: content no longer moves when the bar appears, and the 250 ms hover-in delay means a deliberate move to click the status line lands before the bar reveals at all. It is not a complete fix for that report, though — once the bar is revealed it sits over the terminal's top row, so a tmux status line there is covered while the bar is up. Leaving #10784 open for that remainder.

Testing

Added test_hover_revealed_tab_bar_covers_its_hover_strip, which renders both states and asserts the invariant the bug violated: the revealed bar must cover the strip that reveals it. It also asserts the overlay stays a strip at the top rather than growing into the full-window constraint it is now laid out against.

Both assertions were verified to be capable of failing, not just passing:

  • With hovered_visibility reverted to Stacked, the coverage assertion fails with revealed tab bar RectF(<1, 1, 1023, 36>) must cover the hover strip RectF(<0, 0, 1024, 12>) — this is where the measurements above come from.
  • Tightening the height bound reports the overlay as RectF(<0, 0, 1024, 35>), confirming it is a top strip and that the bound is not vacuous.

Existing coverage only asserted the tab_bar_mode enum, never the geometry, which is why this survived.

Also run: cargo test -p warp --lib workspace:: (209 passed), the full cargo test -p warp --lib suite compared against a clean-tree baseline (identical failure set; the one extra failure floats between different tests in secret_redaction across runs on unchanged code), cargo clippy -p warp --all-targets --tests -- -D warnings, and cargo fmt.

  • I have manually tested my changes locally with ./script/run.

Not manually verified in the GUI — this needs a human pass before merge. Worth checking specifically: the revealed bar looks right over a transparent background and under a gradient theme, hiding feels right now that it is immediate rather than delayed by 500 ms, and dragging a tab out of the revealed bar still behaves.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

🤖 Generated with Claude Code

https://claude.ai/code/session_01CoefEcZFpauaaUcSVANoMn

@cla-bot cla-bot Bot added the cla-signed label Aug 11, 2026
@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label Aug 11, 2026
@oz-for-oss

oz-for-oss Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@bobrnor

Every PR must be linked to a same-repo issue before Oz can review it.

This PR is linked to #14945, but no linked issue is marked ready-to-implement yet. Only repository maintainers apply that label, so please wait for a maintainer to mark the issue. Once it is marked, push a new commit or comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

@oz-for-oss oz-for-oss 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.

@bobrnor

Every PR must be linked to a same-repo issue before Oz can review it.

This PR is linked to #14945, but no linked issue is marked ready-to-implement yet. Only repository maintainers apply that label, so please wait for a maintainer to mark the issue. Once it is marked, push a new commit or comment /oz-review to re-trigger review.

See the contribution guidelines for the full readiness model.

Powered by Oz

…4945, GH#10784)

While the tab bar is hidden, a 12px strip anchored at the window's
top-left reveals it on hover. The revealed bar was added to the content
column, which is inset by WORKSPACE_PADDING, so it started one pixel
below the strip that opened it. Measured at a 1024x768 window: the strip
covers RectF(<0, 0, 1024, 12>) while the bar covered RectF(<1, 1, 1023,
36>).

The top pixel row therefore revealed the bar and then sat outside it. In
fullscreen the cursor clamps to y=0 at the screen edge, which is exactly
that band, so the bar oscillated open and closed with the pointer
standing still: build_scene replays the last pointer position as a
synthetic MouseMoved after every frame, and there is no mouse-exit event
to invalidate it. Every flip reflowed the panes by the height of the
bar, jumping the whole terminal about twice a second.

Add ShowTabBar::Overlay, used for every hover reveal: the bar is
positioned at the window's top-left over the content rather than taking
a row in the column. It now covers RectF(<0, 0, 1024, 35>), a superset
of the strip, so the pointer that opened it stays over it, and revealing
it no longer moves any pane. That also addresses GH#10784, where the
reflow pushed a tmux top status line out from under the cursor.

Because the overlay no longer sits in the column, it stretches itself to
the full window width (a positioned stack child is laid out with a zero
minimum width) and paints its own background, flattened to a solid color
since theme.background() may be a gradient whose stops would be squeezed
into the strip.

Reveal now takes a 250ms hover-in delay so a sweep across the top of the
window doesn't pop the bar open. Hiding stays undelayed on purpose:
Hoverable drops the redraw for a state change whose previous change was
also synthetic, and with both delays every transition resolves on the
synthetic MouseMoved replayed after a frame, so the bar would flip to
hidden without scheduling the frame that repaints it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoefEcZFpauaaUcSVANoMn
@bobrnor
bobrnor force-pushed the jolly-foraging-willow branch from 898e256 to e10f4bd Compare August 12, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fullscreen tab bar flickers open and closed when the pointer rests at the top edge of the screen

1 participant