Skip to content

Fix minimal mode tab bar disappearing in fullscreen - #2375

Merged
lawrencecchen merged 1 commit into
mainfrom
fix-2317-minimal-fullscreen-tab-bar
Mar 30, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
fix-2317-minimal-fullscreen-tab-bar

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes the Bonsplit horizontal tab bar disappearing when entering fullscreen with minimal mode enabled.

Three root causes:

  • ignoresSafeArea(.container, edges: .top) was applied unconditionally in minimal mode, pushing content behind the fullscreen menu bar area
  • effectiveTitlebarPadding returned a negative offset in fullscreen minimal mode even though there is no native titlebar to compensate for
  • Traffic light leading inset (80px) was applied in fullscreen where there are no traffic light buttons

Based on community PR #2341 by @solssak, which identified the ignoresSafeArea issue but missed the padding and inset problems.

Closes #2317

Test plan

  • Enable minimal mode (Settings or command palette)
  • Open 2+ terminal tabs in a workspace
  • Enter fullscreen (Ctrl+Cmd+F)
  • Verify horizontal tab bar is visible at the top with no clipping
  • Verify no wasted 80px leading space in the tab bar
  • Exit fullscreen, verify tab bar still works correctly in windowed minimal mode
  • Verify non-minimal mode is unaffected in both windowed and fullscreen

Summary by cubic

Fixes the horizontal tab bar disappearing in fullscreen when minimal mode is on. The tab bar now stays visible and aligned, with no wasted leading space, in both fullscreen and windowed modes.

  • Bug Fixes
    • Stop applying .ignoresSafeArea(.container, edges: .top) in fullscreen minimal mode.
    • Return 0 from effectiveTitlebarPadding in fullscreen minimal mode; keep negative padding only in windowed minimal mode.
    • Disable the 80px traffic-light leading inset in fullscreen and resync inset on fullscreen enter/exit.

Written for commit 05b6751. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed window controls positioning when entering and exiting fullscreen mode.
    • Improved safe area handling in fullscreen to prevent interface overlapping with window chrome.
    • Enhanced padding calculations for fullscreen workspace layouts.

Three issues caused the Bonsplit horizontal tab bar to be hidden
when entering fullscreen with minimal mode enabled:

1. ignoresSafeArea(.container, edges: .top) was applied unconditionally
   in minimal mode, pushing content behind the fullscreen menu bar area.
   Now gated on !isFullScreen.

2. effectiveTitlebarPadding returned -titlebarPadding in minimal mode
   regardless of fullscreen state. In fullscreen there is no native
   titlebar to compensate for, so the negative offset pushed content
   off the top of the screen. Now returns 0 in fullscreen.

3. Traffic light leading inset (80px) was applied in fullscreen minimal
   mode even though there are no traffic light buttons. Now gated on
   !isFullScreen, and syncTrafficLightInset is called on fullscreen
   enter/exit.

Closes #2317
Based on #2341
@vercel

vercel Bot commented Mar 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 30, 2026 11:33pm

@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR fixes a layout issue where the horizontal tab bar disappears in fullscreen mode with minimal mode enabled. Changes thread the isFullScreen state through ContentView and WorkspaceContentView, then conditionally suppress the safe-area top-edge ignoring modifier when fullscreen to preserve tab bar visibility.

Changes

Cohort / File(s) Summary
Fullscreen state threading and layout adjustment
Sources/ContentView.swift
Threads isFullScreen parameter to WorkspaceContentView; adjusts effectiveTitlebarPadding to return 0 when fullscreen in minimal mode; guards traffic-light inset synchronization against fullscreen; explicitly syncs inset on fullscreen transitions.
Safe area conditional guard
Sources/WorkspaceContentView.swift
Adds isFullScreen property; changes bonsplitView safe-area top-edge ignoring from if isMinimalMode to if isMinimalMode && !isFullScreen to prevent tab bar disappearance in fullscreen.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • #2218: Both PRs coordinate minimal mode and fullscreen state to manage titlebar padding and layout inset behavior.

Poem

🐰 In fullscreen we hop with glee,
No safe areas block our view so free!
Tabs now dance where they're meant to be,
Minimal mode in fullscreen—finally! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main bug being fixed: the disappearing tab bar in fullscreen with minimal mode enabled.
Description check ✅ Passed The PR description is comprehensive, covering the root causes, fixes, test plan, and references to related issues, aligning well with the provided template.
Linked Issues check ✅ Passed The code changes directly address all three root causes identified in issue #2317: gating ignoresSafeArea, fixing effectiveTitlebarPadding, and controlling traffic-light inset in fullscreen.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the minimal mode tab bar disappearing in fullscreen; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-2317-minimal-fullscreen-tab-bar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes three related bugs that caused the Bonsplit horizontal tab bar to disappear or mis-render when entering fullscreen with minimal mode enabled: unconditional ignoresSafeArea(.container, edges: .top) in WorkspaceContentView, a negative effectiveTitlebarPadding in fullscreen minimal mode, and an 80 px traffic-light leading inset applied where no traffic-light buttons exist. The changes are targeted and well-reasoned, but a small gap remains for the app-restore-to-fullscreen case.

Key changes:

  • WorkspaceContentView: isFullScreen parameter added; ignoresSafeArea is now gated on isMinimalMode && !isFullScreen.
  • ContentView.effectiveTitlebarPadding: Returns 0 (not -titlebarPadding) in fullscreen minimal mode.
  • ContentView.syncTrafficLightInset: Guards the 80 px inset behind !isFullScreen.
  • syncTrafficLightInset() is now called on didEnterFullScreenNotification and didExitFullScreenNotification.

Issue found:

  • The window-observer block that initialises isFullScreen from the window's actual styleMask (for session restore / launch-into-fullscreen scenarios) does not call syncTrafficLightInset(). Because onAppear runs first with isFullScreen == false, the 80 px inset is applied and never corrected on restore — the very regression this PR is trying to fix. One additional syncTrafficLightInset() call inside the observer DispatchQueue.main.async block would close this gap.

Confidence Score: 4/5

  • Safe to merge after addressing the missing syncTrafficLightInset() call in the window observer block.
  • The three root causes described in the PR description are all correctly fixed. However, a P1 gap remains: restoring or launching the app into fullscreen with minimal mode enabled leaves the 80 px traffic-light inset in place, because syncTrafficLightInset() is called in onAppear before isFullScreen is initialised from the window's styleMask. One additional line closes this gap; once addressed, this is a solid 5.
  • Sources/ContentView.swift — the window observer block (~line 3149) needs syncTrafficLightInset() after isFullScreen is set.

Important Files Changed

Filename Overview
Sources/ContentView.swift Three targeted fixes: effectiveTitlebarPadding now returns 0 in fullscreen minimal mode, syncTrafficLightInset guards against fullscreen, and syncTrafficLightInset is wired into enter/exit fullscreen notifications. However the window-observer block that initialises isFullScreen on app restore does not call syncTrafficLightInset, leaving an 80 px leading inset when restoring into fullscreen.
Sources/WorkspaceContentView.swift Adds isFullScreen: Bool parameter and guards ignoresSafeArea(.container, edges: .top) behind !isFullScreen; straightforward and correct.

Sequence Diagram

sequenceDiagram
    participant App as App Launch / Restore
    participant OA as onAppear
    participant WO as WindowObserver (async)
    participant FSN as FullScreen Notification

    App->>OA: view appears
    OA->>OA: isFullScreen = false (default)
    OA->>OA: syncTrafficLightInset() → inset=80px ❌ (if in FS)

    App->>WO: DispatchQueue.main.async
    WO->>WO: isFullScreen = styleMask.contains(.fullScreen)
    Note over WO: syncTrafficLightInset() NOT called ← gap

    App->>FSN: NSWindow.didEnterFullScreenNotification
    FSN->>FSN: isFullScreen = true
    FSN->>FSN: syncTrafficLightInset() → inset=0 ✅

    App->>FSN: NSWindow.didExitFullScreenNotification
    FSN->>FSN: isFullScreen = false
    FSN->>FSN: syncTrafficLightInset() → inset=80px ✅
Loading

Comments Outside Diff (1)

  1. Sources/ContentView.swift, line 3149-3157 (link)

    P1 Missing syncTrafficLightInset() after restoring fullscreen state

    When the window observer fires for the first time (e.g. app launch or session restore while the window is already in fullscreen), isFullScreen is updated to true via window.styleMask.contains(.fullScreen) — but syncTrafficLightInset() is never called afterward. The only call that runs before this async block is the one in onAppear (line 2682), which executes with isFullScreen == false (the default). This means minimal-mode users who restore or launch into fullscreen will see the incorrect 80 px leading inset in the tab bar until they manually exit and re-enter fullscreen.

    The fix is to call syncTrafficLightInset() immediately after isFullScreen is set in the window observer:

Reviews (1): Last reviewed commit: "Fix minimal mode tab bar disappearing in..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05b6751729

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/ContentView.swift

private func syncTrafficLightInset() {
let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible) ? 80 : 0
let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible && !isFullScreen) ? 80 : 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resync tab inset when fullscreen state is initialized

syncTrafficLightInset() now depends on isFullScreen, but isFullScreen is also initialized asynchronously from window.styleMask.contains(.fullScreen) (outside the enter/exit fullscreen notifications). Since syncTrafficLightInset() is only called on appear and on specific change handlers, a window restored already in fullscreen can keep the old 80px tab leading inset until another trigger occurs. Please also resync when the initial fullscreen state is set (for example via an onChange(of: isFullScreen) handler or immediately after that assignment).

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/ContentView.swift (1)

3052-3068: ⚠️ Potential issue | 🟡 Minor

Add a sync path for non-notification fullscreen state changes.

Enter/exit fullscreen notifications are handled, but isFullScreen can also change from window attachment state initialization. In that path, inset sync may stay stale until another event fires.

💡 Proposed fix
+        view = AnyView(view.onChange(of: isFullScreen) { _, _ in
+            syncTrafficLightInset()
+        })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 3052 - 3068, The fullscreen
enter/exit handlers update isFullScreen and call syncTrafficLightInset() but the
code path that initializes or attaches the window (where observedWindow is set
and isFullScreen may be derived) doesn’t perform the same sync, so inset/state
can remain stale; in the window attachment/initialization path (the code that
sets observedWindow or initializes the view/window) ensure you mirror the
notification handlers by setting isFullScreen appropriately, calling
setTitlebarControlsHidden(_:in:) with the current window, assigning or clearing
AppDelegate.shared?.fullscreenControlsViewModel, and calling
syncTrafficLightInset() so the traffic light inset is always synced regardless
of whether fullscreen change came from notifications or initial attachment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 3052-3068: The fullscreen enter/exit handlers update isFullScreen
and call syncTrafficLightInset() but the code path that initializes or attaches
the window (where observedWindow is set and isFullScreen may be derived) doesn’t
perform the same sync, so inset/state can remain stale; in the window
attachment/initialization path (the code that sets observedWindow or initializes
the view/window) ensure you mirror the notification handlers by setting
isFullScreen appropriately, calling setTitlebarControlsHidden(_:in:) with the
current window, assigning or clearing
AppDelegate.shared?.fullscreenControlsViewModel, and calling
syncTrafficLightInset() so the traffic light inset is always synced regardless
of whether fullscreen change came from notifications or initial attachment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99d336dc-4851-44be-935a-f426d404fdbb

📥 Commits

Reviewing files that changed from the base of the PR and between c2abfb9 and 05b6751.

📒 Files selected for processing (2)
  • Sources/ContentView.swift
  • Sources/WorkspaceContentView.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:2521">
P2: Because this inset now depends on `isFullScreen`, restored windows that start in fullscreen can keep a stale 80px leading gap if `isFullScreen` is initialized later without triggering another sync. Resync the tab inset immediately after initial fullscreen-state assignment (or observe `isFullScreen` changes) so startup/restored fullscreen state is applied consistently.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift

private func syncTrafficLightInset() {
let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible) ? 80 : 0
let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible && !isFullScreen) ? 80 : 0

@cubic-dev-ai cubic-dev-ai Bot Mar 30, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Because this inset now depends on isFullScreen, restored windows that start in fullscreen can keep a stale 80px leading gap if isFullScreen is initialized later without triggering another sync. Resync the tab inset immediately after initial fullscreen-state assignment (or observe isFullScreen changes) so startup/restored fullscreen state is applied consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/ContentView.swift, line 2521:

<comment>Because this inset now depends on `isFullScreen`, restored windows that start in fullscreen can keep a stale 80px leading gap if `isFullScreen` is initialized later without triggering another sync. Resync the tab inset immediately after initial fullscreen-state assignment (or observe `isFullScreen` changes) so startup/restored fullscreen state is applied consistently.</comment>

<file context>
@@ -2514,7 +2518,7 @@ struct ContentView: View {
 
     private func syncTrafficLightInset() {
-        let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible) ? 80 : 0
+        let inset: CGFloat = (isMinimalMode && !sidebarState.isVisible && !isFullScreen) ? 80 : 0
         for tab in tabManager.tabs {
             if tab.bonsplitController.configuration.appearance.tabBarLeadingInset != inset {
</file context>
Fix with Cubic

@lawrencecchen
lawrencecchen merged commit d015ace into main Mar 30, 2026
20 checks passed
@lawrencecchen
lawrencecchen deleted the fix-2317-minimal-fullscreen-tab-bar branch March 30, 2026 23:41
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
Three issues caused the Bonsplit horizontal tab bar to be hidden
when entering fullscreen with minimal mode enabled:

1. ignoresSafeArea(.container, edges: .top) was applied unconditionally
   in minimal mode, pushing content behind the fullscreen menu bar area.
   Now gated on !isFullScreen.

2. effectiveTitlebarPadding returned -titlebarPadding in minimal mode
   regardless of fullscreen state. In fullscreen there is no native
   titlebar to compensate for, so the negative offset pushed content
   off the top of the screen. Now returns 0 in fullscreen.

3. Traffic light leading inset (80px) was applied in fullscreen minimal
   mode even though there are no traffic light buttons. Now gated on
   !isFullScreen, and syncTrafficLightInset is called on fullscreen
   enter/exit.

Closes manaflow-ai#2317
Based on manaflow-ai#2341

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 05b67517 Deployed Mar 30, 2026 by vercel[bot]
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.

Horizontal tab bar disappears in fullscreen with minimal mode enabled

1 participant