Skip to content

Tab bar / header shifts horizontally when switching tabs (macOS Tahoe, windowed) #3375

@rcp-git

Description

@rcp-git

Summary

On macOS (Tahoe), in a windowed (non-full-screen) window, the entire left side of the title bar — the Wave AI / workspace icons and the tab row — shifts horizontally by roughly the width of the macOS traffic-light buttons when switching between tabs. The first tab vs. other tabs is the most obvious trigger, but the row visibly jumps left/right and settles in different positions depending on which tab is active.

This is purely a cosmetic/layout jitter, but it's very noticeable.

Environment

  • Wave Terminal: v0.14.5
  • macOS: 26.5.1 ("Tahoe", build 25F80)
  • Arch: Apple Silicon (arm64)
  • Window state: windowed (not full-screen), default zoom
  • Multiple tabs open (≥3)

Steps to reproduce

  1. Open a window with several tabs on macOS Tahoe, windowed (not full-screen).
  2. Switch between tabs (e.g. tab 1 → tab 3 → tab 4 and back).
  3. Watch the left cluster: the sparkle (Wave AI) icon, the workspace switcher, and the whole tab strip shift horizontally — by approximately the traffic-light reservation width — and land in different positions depending on the active tab.

Expected

The left origin of the header (icons + tab strip) should stay fixed regardless of which tab is active.

Actual

The header's left origin moves by ~the traffic-light width (~74–80px) as the active tab changes; switching away from the first tab is the most reliable way to see it.

Investigation (read against the v0.14.5 tag)

I read frontend/app/tab/tabbar.tsx and frontend/app/tab/tab.scss at v0.14.5 (blob SHAs b404afc / ad10fc8, identical to main at time of writing).

  • The tabs themselves are not what reflows: in tab.scss, .tab is position: absolute with an explicit width, and the .active class only restyles the inner pill (background, text color/weight). In tabbar.tsx, setSizeAndPosition() assigns every tab the same computed width and positions each by translate3d(index * idealTabWidth). setSizeAndPosition is also not re-run on active-tab change (its effect deps are tabIds, tabsLoaded, newTabId, hideAiButton, appUpdateStatus, zoomFactor, showMenuBaractiveTabId is absent). So inter-tab geometry is active-tab-independent.

  • That means the visible shift is the row's left origin moving, not the tabs reflowing. The only left-origin quantity is windowDragLeftWidth — the left drag spacer that reserves room for the macOS traffic lights:

    const MacOSTrafficLightsWidth = 74;
    const MacOSTahoeTrafficLightsWidth = 80;
    ...
    let windowDragLeftWidth = 10;
    if (env.isMacOS() && !isFullScreen) {
        const trafficLightsWidth = isMacOSTahoeOrLater()
            ? MacOSTahoeTrafficLightsWidth
            : MacOSTrafficLightsWidth;
        windowDragLeftWidth = zoomFactor > 0 ? trafficLightsWidth / zoomFactor : trafficLightsWidth;
    }
  • The observed shift magnitude matches this reservation (~80px on Tahoe), which strongly implicates this spacer. In code the value depends only on isMacOS()/isFullScreen/isMacOSTahoeOrLater()/zoomFactor — none of which change on tab switch — so the trigger appears to be a render/measurement timing issue: the reservation (or the measured non-tab width feeding setSizeAndPosition) is being applied inconsistently across re-renders, and tab switching is what forces the re-render that exposes it.

    Note: isMacOSTahoeOrLater() returns false until MacOSVersion is set asynchronously, so an early render can reserve 74px and a later one 80px — one plausible source of the desync on Tahoe specifically.

I could not pin the exact line that flips the value at runtime from a static read, but the magnitude and the macOS-Tahoe/windowed-only conditions point squarely at the traffic-light reservation. Happy to provide more screenshots/recordings.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions