Skip to content

Add zoom scale support to tab bar metrics and views#21

Open
moyashin63 wants to merge 1 commit intomanaflow-ai:mainfrom
moyashin63:ui-zoom-tab-bar
Open

Add zoom scale support to tab bar metrics and views#21
moyashin63 wants to merge 1 commit intomanaflow-ai:mainfrom
moyashin63:ui-zoom-tab-bar

Conversation

@moyashin63
Copy link

@moyashin63 moyashin63 commented Mar 8, 2026

Summary

  • Add bonsplitZoomScale environment key for host apps to inject a zoom scale factor
  • Convert all TabBarMetrics constants to static func(scale:) so tab bar dimensions scale dynamically
  • Apply zoom scale to TabBarView, TabItemView, and TabDragPreview

Why

Host apps (e.g. cmux) implement UI-wide zoom scaling. Bonsplit tab bars need to participate in that scaling by reading a host-provided scale factor via SwiftUI environment.

Testing

  • swift build (package compiles)
  • Validated in cmux Debug build with ./scripts/reload.sh --tag ui-zoom
  • Verified tab bar height, font size, icon size, padding all scale correctly at 0.5x–2.0x

Related

  • cmux UI Zoom feature branch: ui-zoom

Summary by cubic

Add zoom scaling to tab bars via a new SwiftUI environment value so all tab dimensions, fonts, and icons scale consistently with host app UI zoom.

  • New Features

    • Introduced bonsplitZoomScale environment key (default 1.0).
    • Converted TabBarMetrics from constants to static func(scale:) to compute scaled values.
    • Applied scale across TabBarView, TabItemView, and TabDragPreview (heights, widths, fonts, icons, padding, indicators).
  • Migration

    • No changes needed if you don’t use zoom (defaults to 1.0).
    • To enable zoom, set .environment(\.bonsplitZoomScale, <scale>) at your app’s root view.

Written for commit 3c45519. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added zoom scaling functionality to the tab bar and related UI components, allowing visual elements including tabs, icons, fonts, and spacing to scale dynamically.

Scale all tab bar dimensions (height, width, font size, icon size,
padding, indicators) via bonsplitZoomScale environment value.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

The changes introduce scale-aware UI metrics for the tab bar by converting fixed sizing constants to scale-dependent functions and adding a new SwiftUI environment value that propagates zoom scaling across tab bar components.

Changes

Cohort / File(s) Summary
Scale-Aware Metrics
Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift
Replaces 15 public static constant values (barHeight, tabHeight, tabMinWidth, etc.) with corresponding static functions that accept a CGFloat scale parameter and return scaled values.
Environment Value Infrastructure
Sources/Bonsplit/Public/ZoomScale.swift
New file introducing BonsplitZoomScaleKey environment key with default value 1.0 and public bonsplitZoomScale property on EnvironmentValues for SwiftUI integration.
View Layer Integration
Sources/Bonsplit/Internal/Views/TabBarView.swift, TabDragPreview.swift, TabItemView.swift
Adopts @Environment(\.bonsplitZoomScale) and updates layout metrics, font sizes, padding, icon sizes, and indicator dimensions to scale with the zoom value via updated TabBarMetrics function calls.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Scales grow where constants once stood tall,
A zoom-aware whisper through views, through it all,
From metric to view, the scale does flow,
Making tabs dance as they ebb and grow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding zoom scale support to tab bar metrics and views, which is the primary objective of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

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

Choose a reason for hiding this comment

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

No issues found across 5 files


Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

@greptile-apps
Copy link

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR introduces a SwiftUI environment key (bonsplitZoomScale) that allows host apps to inject a UI-wide zoom scale factor, then threads that factor through all tab bar dimensions by converting every TabBarMetrics static constant into a static func(_ scale: CGFloat). The approach is consistent with SwiftUI conventions and the implementation is thorough across TabBarView, TabItemView, and TabDragPreview.

Key changes:

  • New public file ZoomScale.swift defines BonsplitZoomScaleKey (default 1.0) and exposes EnvironmentValues.bonsplitZoomScale.
  • All visual TabBarMetrics constants become scale-aware functions; dimension-agnostic values (corner radius, 0-valued spacings, animation durations) are correctly left as static lets.
  • HStack(spacing: 2) in TabItemView.closeOrDirtyIndicator (the notification badge + dirty-dot row) was missed — the only unscaled spacing among all the changes.

Confidence Score: 4/5

  • Safe to merge with one minor fix to the badge/dirty-dot row spacing.
  • The implementation is systematic and well-executed — all visual metrics are converted and the environment plumbing is clean. One small gap was found: an unscaled HStack(spacing: 2) in the badge/dirty-dot row creates a visible regression at non-default scales. The fix is trivial (multiply by zoomScale).
  • Sources/Bonsplit/Internal/Views/TabItemView.swift (line 434 — badge spacing)

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Host App] -->|".environment bonsplitZoomScale scale"| B[SwiftUI Environment]

    B -->|"@Environment bonsplitZoomScale"| C[TabBarView]
    B -->|"@Environment bonsplitZoomScale"| D[TabItemView]
    B -->|"@Environment bonsplitZoomScale"| E[TabDragPreview]

    C -->|zoomScale| F[TabBarMetrics scale funcs]
    D -->|zoomScale| F
    E -->|zoomScale| F

    F --> G["barHeight · tabHeight · iconSize<br/>titleFontSize · closeButtonSize<br/>contentSpacing · dropIndicatorWidth<br/>dropIndicatorHeight · etc"]

    style A fill:#4A90D9,color:#fff
    style B fill:#7B68EE,color:#fff
    style F fill:#2ECC71,color:#fff
Loading

Comments Outside Diff (1)

  1. Sources/Bonsplit/Internal/Views/TabItemView.swift, line 434 (link)

    The HStack spacing of 2 here is the only dimensional constant in closeOrDirtyIndicator that wasn't scaled with zoomScale. Because the circle sizes (notificationBadgeSize and dirtyIndicatorSize) are correctly scaled, the spacing between them will appear too tight at scales > 1.0 and too loose at scales < 1.0, creating a visual inconsistency in the dirty/badge row.

Last reviewed commit: 3c45519

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift (1)

7-33: Consider finishing the zoom-metric centralization here.

This file now owns most scale-aware sizing, but there are still raw * zoomScale literals in Sources/Bonsplit/Internal/Views/TabDragPreview.swift at Lines 23-24, Sources/Bonsplit/Internal/Views/TabItemView.swift at Lines 137, 230, 247-248, and Sources/Bonsplit/Internal/Views/TabBarView.swift at Lines 414, 447-488, and 494-495. Pulling those into TabBarMetrics would keep future zoom tuning in one place and reduce visual drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift` around lines 7 - 33,
TabBarMetrics centralizes scale-aware sizing, but several raw "* zoomScale"
literals remain in TabDragPreview, TabItemView, and TabBarView; add new static
metric helpers in TabBarMetrics (following existing pattern like iconSize(_:),
titleFontSize(_:), tabHorizontalPadding(_:), activeIndicatorHeight(_:)) for each
literal used in those files (e.g., drag preview offsets/sizes, extra
padding/margins, hover/active indicator sizes, and any font or spacing values
referenced at the listed lines) and then replace the raw "* zoomScale"
expressions in TabDragPreview, TabItemView, and TabBarView with calls to the new
TabBarMetrics methods passing the current scale. Ensure names clearly map to
their use (e.g., dragPreviewWidth(_:), tabItemExtraPadding(_:),
barDropSpacing(_:)) and keep the API consistent (static funcs taking scale:
CGFloat returning CGFloat).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/Bonsplit/Public/ZoomScale.swift`:
- Around line 8-10: The public setter for bonsplitZoomScale must validate and
normalize incoming values before storing them in BonsplitZoomScaleKey.self:
ensure the value is finite and positive, then clamp it to a safe range (e.g.,
min 0.5, max 3.0) and store that clamped value; if the input is non-finite or <=
0, replace it with the minimum sane value. Update the setter for
bonsplitZoomScale to perform this check/clamp so TabBarView, TabItemView, and
TabDragPreview never receive 0, negative, or infinite scales.

---

Nitpick comments:
In `@Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift`:
- Around line 7-33: TabBarMetrics centralizes scale-aware sizing, but several
raw "* zoomScale" literals remain in TabDragPreview, TabItemView, and
TabBarView; add new static metric helpers in TabBarMetrics (following existing
pattern like iconSize(_:), titleFontSize(_:), tabHorizontalPadding(_:),
activeIndicatorHeight(_:)) for each literal used in those files (e.g., drag
preview offsets/sizes, extra padding/margins, hover/active indicator sizes, and
any font or spacing values referenced at the listed lines) and then replace the
raw "* zoomScale" expressions in TabDragPreview, TabItemView, and TabBarView
with calls to the new TabBarMetrics methods passing the current scale. Ensure
names clearly map to their use (e.g., dragPreviewWidth(_:),
tabItemExtraPadding(_:), barDropSpacing(_:)) and keep the API consistent (static
funcs taking scale: CGFloat returning CGFloat).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a826bd88-91c1-4c5d-90c8-4a02b346b215

📥 Commits

Reviewing files that changed from the base of the PR and between fa452db and 3c45519.

📒 Files selected for processing (5)
  • Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Internal/Views/TabDragPreview.swift
  • Sources/Bonsplit/Internal/Views/TabItemView.swift
  • Sources/Bonsplit/Public/ZoomScale.swift

Comment on lines +8 to +10
public var bonsplitZoomScale: CGFloat {
get { self[BonsplitZoomScaleKey.self] }
set { self[BonsplitZoomScaleKey.self] = newValue }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clamp the public zoom scale before storing it.

Every tab metric now depends on this value, so 0, negative, or non-finite input will flow straight into widths, heights, and font sizes in TabBarView, TabItemView, and TabDragPreview. Normalizing it here prevents one bad host-provided value from breaking the entire tab strip.

Proposed fix
 extension EnvironmentValues {
     public var bonsplitZoomScale: CGFloat {
         get { self[BonsplitZoomScaleKey.self] }
-        set { self[BonsplitZoomScaleKey.self] = newValue }
+        set {
+            let normalized = newValue.isFinite ? max(0.5, newValue) : 1.0
+            self[BonsplitZoomScaleKey.self] = normalized
+        }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public var bonsplitZoomScale: CGFloat {
get { self[BonsplitZoomScaleKey.self] }
set { self[BonsplitZoomScaleKey.self] = newValue }
public var bonsplitZoomScale: CGFloat {
get { self[BonsplitZoomScaleKey.self] }
set {
let normalized = newValue.isFinite ? max(0.5, newValue) : 1.0
self[BonsplitZoomScaleKey.self] = normalized
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Bonsplit/Public/ZoomScale.swift` around lines 8 - 10, The public
setter for bonsplitZoomScale must validate and normalize incoming values before
storing them in BonsplitZoomScaleKey.self: ensure the value is finite and
positive, then clamp it to a safe range (e.g., min 0.5, max 3.0) and store that
clamped value; if the input is non-finite or <= 0, replace it with the minimum
sane value. Update the setter for bonsplitZoomScale to perform this check/clamp
so TabBarView, TabItemView, and TabDragPreview never receive 0, negative, or
infinite scales.

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