Add opt-in fill tab-width mode to the tab strip#141
Conversation
Introduces BonsplitConfiguration.Appearance.tabWidthMode (TabWidthMode): - .fixed (default) preserves the historical fixed-width + horizontal-scroll layout byte-for-byte. - .fill stretches tabs to fill the pane's available tab-bar width, distributing the slack equally across tabs. A single tab spans the full width; multiple tabs share it evenly. When tabs would overflow at their natural width, the strip falls back to fixed sizing and scrolls. Implementation: in fill mode each TabItemView becomes flexible (maxWidth: .infinity) and the tab row is given a minimum width equal to the viewport (tabRowFillMinWidth) so the horizontal ScrollView hands it the full width and SwiftUI distributes the slack. The drop zone and overflow/scroll machinery are untouched. Extracted the scroll content into tabScrollContent to keep the body within the type-checker's budget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR introduces a ChangesTab Width Mode Configuration and Rendering
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryAdds an opt-in
Confidence Score: 4/5Safe to merge. The change is strictly opt-in, the fixed-width path is preserved without modification, and there are no data or correctness risks. The fill-mode layout math is correct: trailingTabContentInset reserves the split-button lane before fillRowMinWidth imposes containerWidth, so tabs never overflow into the button area. The one notable rough edge is that tabRowFillMinWidth uses a @ViewBuilder if let producing two structurally distinct view types. A runtime switch between .fill and .fixed would unmount and remount the entire tab-row subtree, resetting per-tab @State (hover, favicon, loading) momentarily. Because tabWidthMode is a configuration-level setting rather than a per-interaction toggle, this remount is unlikely to be noticed in practice, but the fix is straightforward. Sources/Bonsplit/Internal/Views/TabItemView.swift — specifically the tabRowFillMinWidth helper and its conditional view-type branching. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[TabBarView body] --> B[GeometryReader containerWidth]
B --> C[ScrollView horizontal]
C --> D[tabScrollContent ViewBuilder]
D --> E[HStack with tabs and drop zone]
E --> E2[ForEach TabItemViews]
E --> E3[dropZoneAfterTabs 30pt fixed]
D --> F[padding horizontal barPadding]
F --> G[padding trailing trailingTabContentInset]
G --> H{fillRowMinWidth}
H -- fill mode AND containerWidth gt 0 --> I[frame minWidth containerWidth]
H -- fixed mode OR containerWidth is 0 --> J[no-op self]
I --> K[frame height tabBarHeight]
J --> K
E2 --> L[TabItemView]
L --> M{fillsWidth}
M -- fill --> N[maxWidth infinity]
M -- fixed --> O[maxWidth tabMaxWidth]
Reviews (1): Last reviewed commit: "Add opt-in fill tab-width mode to the ta..." | Re-trigger Greptile |
| @ViewBuilder | ||
| func tabRowFillMinWidth(_ minWidth: CGFloat?) -> some View { | ||
| if let minWidth { | ||
| frame(minWidth: minWidth, alignment: .leading) | ||
| } else { | ||
| self | ||
| } | ||
| } |
There was a problem hiding this comment.
The
@ViewBuilder if let produces two structurally different view types — self vs self.frame(minWidth:alignment:). When tabWidthMode changes at runtime the swap causes SwiftUI to unmount and remount the entire tab-row subtree, resetting @State in every TabItemView (hover, favicon, loading states). Replacing the conditional with an unconditional frame(minWidth:) where nil maps to 0 keeps the view type stable across mode changes, eliminating the remount.
| @ViewBuilder | |
| func tabRowFillMinWidth(_ minWidth: CGFloat?) -> some View { | |
| if let minWidth { | |
| frame(minWidth: minWidth, alignment: .leading) | |
| } else { | |
| self | |
| } | |
| } | |
| func tabRowFillMinWidth(_ minWidth: CGFloat?) -> some View { | |
| frame(minWidth: minWidth ?? 0, alignment: .leading) | |
| } |
|
Consumer PR: manaflow-ai/cmux#5147 (bumps the submodule pointer to this branch and exposes the mode via the |
Summary
Adds an opt-in tab-width mode so surface tabs can stretch to fill their pane's available tab-bar width instead of using a fixed width. The current fixed-width behavior remains the default — this is strictly opt-in.
New public API:
BonsplitConfiguration.Appearance.tabWidthMode: TabWidthMode.fixed(default) — historical fixed-width (tabMinWidth...tabMaxWidth) + horizontal-scroll layout, unchanged byte-for-byte..fill— tabs stretch to fill the pane's available tab-bar width, distributing the slack equally. A single tab spans the full width; multiple tabs share it evenly. When tabs would overflow at their natural width, the strip falls back to fixed sizing and scrolls (fill never shrinks tabs below their natural width).Implementation
The fixed width is enforced by a per-tab
.frame(maxWidth:)inside a horizontalScrollViewwhoseHStacksizes to content, so naively settingmaxWidth: .infinitywould not fill the viewport. Instead:.fill, eachTabItemViewbecomes flexible (maxWidth: .infinity, min unchanged).tabRowFillMinWidth) so the horizontalScrollViewhands it the full width; SwiftUI then distributes the slack equally across the flexible tabs. When natural content exceeds the viewport, the row grows past it and scrolls as before.tabScrollContentto keepTabBarView.bodywithin the SwiftUI type-checker's budget.Tests
testTabWidthModeDefaultsToFixed— asserts the opt-in default (every preset stays.fixed).testTabWidthModeFillIsSettableAndDistinct— fill is settable and distinct.Related
Consumed by cmux issue manaflow-ai/cmux#5091 (cmux PR linked below once opened), which exposes this via the
surface-tabs-fill-pane-widthGhostty config key + a Settings toggle.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds an opt-in tab-width mode that lets tabs stretch to fill their pane’s tab bar. Fixed-width remains the default, and overflow still scrolls.
New Features
BonsplitConfiguration.Appearance.tabWidthMode(TabWidthMode)..fixedkeeps the historical fixed-width + horizontal scroll..fillstretches tabs to share available width; falls back to fixed and scroll when natural width would overflow.Refactors
tabScrollContentto reducebodycomplexity.tabRowFillMinWidth(_:)to give the row the viewport width in fill mode.Written for commit 887ece7. Summary will update on new commits.
Summary by CodeRabbit
Release Notes
fixed(default, with horizontal scrolling) orfill(stretches to evenly fill the tab bar, with automatic fallback to fixed scrolling if natural sizing would overflow).