fix(mobile): iOS compose button no longer grows while a tab is pressed#28955

merged1
main
GitHub fork: fix/ios-compose-size-during-tab-press
opened7d agobyJJerematix
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

On iPhone with the floating glass tab bar (iOS 26+), the compose button next to the tabs can grow to about 1.16× (62pt → ~72pt) when you switch tabs, for example Settings → Chats. It stays enlarged until the bar happens to lay out again, which can take a second or more.

Repro: on iPhone, open Settings from the floating tab bar, then tap Chats. Watch the compose button at the bottom right.

Change

WorkspaceTabBar.layoutSubviews sizes compose from the union of the tab buttons. When the workspace-tab-* identifiers aren't found, it falls back to every visible control in the bar. The existing comment notes that iOS 26.6 doesn't copy those identifiers onto its tab buttons. The bar also holds a second set of tab buttons for the glass lens. While a tab is pressed, that set is scaled to about 1.16×, and the fallback measured it too. The row height then went from 54 to 62.64, and compose (row.height + 8) from 62 to 70.64.

The fix measures only controls whose converted size equals their own bounds, so anything drawn under a scaling transform is skipped. If nothing qualifies, compose keeps its last frame, as it already does when the row is empty.

Scope and approval

This is a small, focused fix for an obvious visual bug: one filter in one native view. It changes no behavior beyond the compose frame.

Verification

Full app: I built the dev client (expo prebuild + xcodebuild) and ran it on an iPhone 17 Pro simulator (iOS 27.0), paired with a local dev server that holds only test projects and threads. Taps and press-and-hold were real HID touches delivered with AXe. For measurement only, I temporarily added NSLog to WorkspaceTabBar and layoutCompose; none of that is in this PR.

  • iOS 27.0, real identifier path: the identifiers are found on every pass (identified=4 buttons=4 rowH=54), and compose stays 62pt with and without the fix. The simulator doesn't hit the fallback, so the bug doesn't show there unmodified.
  • Fallback path (iOS 26.6 behavior): I emulated the missing identifiers by temporarily forcing identified empty. Then I tapped and held Settings → Chats three times, holding 0.05 s, 0.3 s and 1 s.
    • Before: identified=0 buttons=8, with rowH=62.64 on 6 of 18 passes and compose=70.64 on 3 of 9 layouts. The button visibly grows for about 1–2 s per press.
    • After: compose=62.00 on 9 of 9 layouts. On the scaled passes the filter keeps 4 of 8 buttons and the row stays 54.
  • Standalone UIKit harness with the same WorkspaceTabBar code (iOS 27.0 simulator): it hit the fallback on its own (identified=0 buttons=8, four buttons at 95×62.6 instead of 82×54). It gave COMPOSE size=70.64 in 3 of 3 runs before the fix and 62 in 3 of 3 runs after.
  • swiftlint lint --config .swiftlint.yml --strict on the changed file: 0 violations.
  • Not checked: the fix on a physical device. The bug was reported on a physical iPhone, where the enlarged button measured about 72pt against 62pt, matching the 70.64pt above. I didn't check which iOS version that device runs.

Full app, fallback path (simulator, test data). Tap Settings, then Chats:

Before: compose jumps while the tab is pressedAfter: compose keeps its size
External imageExternal image

MP4: before · after · harness: before · after

Written with Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Checks

24 successful · 12 skipped
All checks passed
44ea3d5

Added 1 commit

  • fix(mobile): keep iOS compose button size while a tab is pressed44ea3d5
Synced from GitHub

Approvability

Verdict: Approved at 44ea3d5

Macroscope's review found this PR approvable — The change excludes magnified tab copies from compose-button sizing, preventing the button from growing during tab presses. Its effects are confined to UI layout, with repeated simulator verification supporting the fix.

Notes:

  • All code in this push has already been reviewed. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between fa31b66bbb2ae5234205a8628a78e038d917202f and 44ea3d5fcb24c62aa627e33565601cdc35c85cf3.

External image Files selected for processing: 1
  • apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

WorkspaceTabBar.layoutSubviews now excludes tab-button candidates whose converted bounds size differs from their own bounds size. Identifier-based selection and the fallback to all tab buttons remain unchanged.

Changes: iOS tab-bar measurement

Layer / File(s)Summary
Filter tab-button candidates
apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift
layoutSubviews filters candidates by converted bounds size while retaining identifier-based selection and the existing fallback.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Possibly related issues

  • pingdotgg/t3code#26774 reports abnormal New chat compose-button sizing during iOS tab navigation. This change affects tab-button measurement related to compose-button sizing.

Possibly related PRs

  • pingdotgg/t3code#28950: This PR changes the same WorkspaceTabBar.layoutSubviews measurement behavior to filter magnified tab-button copies.
  • pingdotgg/t3code#24492: This PR adds the fallback to measure tab controls when workspace-tab identifiers are missing; this change filters candidates in that fallback.
  • pingdotgg/t3code#23841: This PR introduces the WorkspaceTabBar measurement and compose-button positioning code that this change modifies.

Suggested reviewers: wojas

Merge Risk: External image Minimal · up to 44ea3

No actionable merge-blocking risk is established for this change.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the main change: preventing the iOS compose button from growing while a tab is pressed.
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Description checkPassedThe description includes all required sections. It explains the problem and reproduction steps, the technical change, the focused scope rationale, detailed verification results, limitations, UI eviden…
Full details: Description check

Explanation

The description includes all required sections. It explains the problem and reproduction steps, the technical change, the focused scope rationale, detailed verification results, limitations, UI evidence, and agent attribution.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Synced from GitHub

Heads-up from a fork: the size check here breaks when the compact tab bar has five items.

The app already plans for five items: NATIVE_WORKSPACE_TAB_SLOTS (workspace-tabs-state.ts#L149-L155) reserves four section slots plus Settings. A compact bar with four sections plus Settings is an expected layout, not an unusual one.

My fork adds a Notes tab, so the bar shows Chats, PRs, Notes, Usage and Settings. With five items the exact == size check rejects every tab button (possibly scaling, possibly floating-point rounding from fractional item widths), so row is null and compose never moves. Today's four-item bar doesn't hit this now, but a fifth item would.

Skipping only the magnified buttons still fixes what this PR fixed, and it works with a five-item row:

T3SidebarTabBarView.swift#L14

diff
1--- a/apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift
2+++ b/apps/mobile/modules/t3-native-controls/ios/T3SidebarTabBarView.swift
3@@ -11,7 +11,7 @@ private final class WorkspaceTabBar: UITabBar {
4 let candidates = identified.isEmpty ? tabButtons(in: self) { _ in true } : identified
5- let buttons = candidates.filter { convert($0.bounds, from: $0).size == $0.bounds.size }
6+ let buttons = candidates.filter { convert($0.bounds, from: $0).width <= $0.bounds.width + 0.5 }
7 let row = buttons.reduce(CGRect.null) { $0.union(convert($1.bounds, from: $1)) }
8 if !row.isNull { onContentLayout?(row) }

I can't say I fully understand what's happening here, 5 items and 6 items fail very differently, but with the fix, even six items works:

I checked this on an iPhone 16 Pro (iOS 26) with a Release build: compose sits beside the five-item bar again, and still doesn't grow while a tab is pressed.

Synced from GitHub

Follow-up: joaoheusi/t3code#93 found the same bug on the stock four-item bar (393pt iPhone 15 Pro; the 402pt iPhone 17 Pro was fine), and it explains the difference between five and six items that I mentioned above.

Converting a translated rect isn't exact, so at some widths a button fails the == check from rounding alone. With five items only the last tab failed: row ended one tab early, and compose landed on Settings. With six, every button failed, so compose was never placed at all. More items means more fractional widths, which means more failures. Nothing is being scaled.

So this isn't only a fork problem. #93's symmetric 1pt tolerance (abs(size.width - bounds.width) < 1 && abs(size.height - bounds.height) < 1) fixes it and still excludes the lens copy. It would be worth sending upstream.