Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import CustomTabBar from './Examples/CustomTabBar';
import NativeBottomTabsTabBarHidden from './Examples/NativeBottomTabsTabBarHidden';
import OriginalIconColors from './Examples/OriginalIconColors';
import NativeBottomTabsOriginalIcons from './Examples/NativeBottomTabsOriginalIcons';
import KeyboardAvoidingViewExample from './Examples/KeyboardAvoidingView';
import { useLogger } from '@react-navigation/devtools';
import LazyTabs from './Examples/LazyTabs';
import { LogBox } from 'react-native';
Expand Down Expand Up @@ -196,6 +197,11 @@ const examples = [
component: NativeBottomTabsOriginalIcons,
name: 'Native Bottom Tabs with original icons',
},
{
component: KeyboardAvoidingViewExample,
name: 'Keyboard Avoiding View',
screenOptions: { headerShown: false },
},
{ component: NativeBottomTabs, name: 'Native Bottom Tabs' },
{ component: JSBottomTabs, name: 'JS Bottom Tabs' },
{
Expand Down
105 changes: 105 additions & 0 deletions apps/example/src/Examples/KeyboardAvoidingView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation';
import {
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { useBottomTabBarHeight } from 'react-native-bottom-tabs';

const Tab = createNativeBottomTabNavigator();

function HomeScreen() {
const tabBarHeight = useBottomTabBarHeight();
const hasLiquidGlassBottomTabBar =
Platform.OS === 'ios' &&
!Platform.isPad &&
!Platform.isTV &&
parseFloat(String(Platform.Version)) >= 26;

return (
<View style={styles.container}>
<KeyboardAvoidingView
behavior="padding"
style={[
styles.keyboardAvoidingView,
hasLiquidGlassBottomTabBar && { marginBottom: tabBarHeight },
]}
>
<View style={styles.content}>
<Text style={styles.title}>KeyboardAvoidingView + Bottom Tabs</Text>
<View style={styles.spacer} />
<TextInput
testID="keyboard-avoiding-input"
style={styles.input}
placeholder="Tap here to open keyboard"
placeholderTextColor="#666"
/>
</View>
</KeyboardAvoidingView>
</View>
);
}

function SettingsScreen() {
return (
<View style={styles.container}>
<Text>Settings</Text>
</View>
);
}

export default function KeyboardAvoidingViewExample() {
return (
<Tab.Navigator initialRouteName="Home">
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
tabBarButtonTestID: 'keyboard-avoiding-home-tab',
tabBarIcon: () => require('../../assets/icons/article_dark.png'),
}}
/>
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarButtonTestID: 'keyboard-avoiding-settings-tab',
tabBarIcon: () => require('../../assets/icons/grid_dark.png'),
}}
/>
</Tab.Navigator>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
keyboardAvoidingView: {
flex: 1,
},
content: {
flex: 1,
padding: 20,
justifyContent: 'flex-end',
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
},
spacer: {
flex: 1,
},
input: {
borderWidth: 1,
borderColor: '#555',
borderRadius: 8,
padding: 15,
fontSize: 16,
},
});
33 changes: 33 additions & 0 deletions artifacts/issue-505/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Issue 505 validation evidence

Issue: <https://github.com/callstack/react-native-bottom-tabs/issues/505>

## Environment

- `agent-device` 0.20.10
- Xcode 27.0 beta 4
- iPhone 17 Pro simulators on iOS 26.5 (`951CC10B-1B07-48EE-8636-59386FD2C58C`) and iOS 27.0 (`CD1ADD09-474F-4618-899B-689243327BC6`)
- Pixel 10 Pro emulator (`emulator-5554`)
- React Native 0.81.4 example app, New Architecture enabled

## iOS 26.5 reproduction

The baseline reproduction used a full-height `KeyboardAvoidingView` with a bottom-aligned `TextInput` and no manual tab-bar margin.

Before the fix, the input frame began at `y=803.67` while the native tab bar began at `y=791`. The accessibility snapshot marked the input as covered, and `agent-device press @e4` refused the unsafe tap. The baseline screenshot and recording are under `baseline/ios-26.5`.

After the final remediation, the input begins at `y=720.67` and ends at `y=770`, above the tab bar. A direct semantic tap succeeds. With the keyboard open, the input begins at `y=468.67`, remains hittable, and accepts `issue-505-remediated`. The remediated screenshots, recording, and launch log are under `verification/ios-26.5`.

The final example applies the measured tab-bar offset only to its `KeyboardAvoidingView`, inside a full-screen background view. Earlier target-state captures from a rejected broad scene-height adjustment are retained alongside the remediated captures for comparison.

## Scope checks

The example offset is restricted to iPhone on iOS 26 and later. The library's scene dimensions remain unchanged, preserving full-bleed and translucent backgrounds, scroll-edge behavior, existing `useBottomTabBarHeight` offsets, iPad/sidebar/tvOS layouts, minimized and accessory bars, custom or hidden bars, older iOS, and Android.

The remediated app build was also installed on iOS 27.0. The input again begins at `y=720.67`, and a direct selector-based tap succeeds. The remediated screenshot and launch log are under `verification/ios-27.0`.

Android was also exercised on the Pixel emulator. Its separate behavior—BottomNavigationView rising above the IME—reproduces and is already addressed by open PR #549, so this branch does not duplicate that Android change. Android screenshots are under `baseline/android`.

Two native-only experiments were rejected: moving SwiftUI measurement into each tab scene and respecting the container bottom safe area. Neither changed the explicit React Native scene height; their screenshots are retained under `rejected-native-candidates`.

Android video capture was attempted through `agent-device`, but a pre-existing device recovery manifest prevented recording. The two command diagnostics are retained under `baseline/android/logs`; screenshots still capture both rest and keyboard-open states.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"ts":"2026-09-05T00:11:59.240Z","level":"info","phase":"request_start","session":"issue505-android-baseline","requestId":"9ed3ebe58f397f5e","command":"record","data":{"publicSession":"issue505-android-baseline","effectiveSession":"issue505-android-baseline","command":"record","requestLogPath":"/Users/thiagobrez/.agent-device/sessions/issue505-android-baseline/requests/9ed3ebe58f397f5e.ndjson","runnerLogPath":"/Users/thiagobrez/.agent-device/sessions/issue505-android-baseline/runner.log"}}
{"ts":"2026-09-05T00:11:59.362Z","level":"error","phase":"request_failed","session":"issue505-android-baseline","requestId":"9ed3ebe58f397f5e","command":"record","data":{"code":"UNKNOWN","message":"Android screenrecord native recovery evidence already exists"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"ts":"2026-09-05T00:12:03.528Z","level":"info","phase":"request_start","session":"issue505-android-baseline","requestId":"9398ddd4b730eef2","command":"record","data":{"publicSession":"issue505-android-baseline","effectiveSession":"issue505-android-baseline","command":"record","requestLogPath":"/Users/thiagobrez/.agent-device/sessions/issue505-android-baseline/requests/9398ddd4b730eef2.ndjson","runnerLogPath":"/Users/thiagobrez/.agent-device/sessions/issue505-android-baseline/runner.log"}}
{"ts":"2026-09-05T00:12:03.573Z","level":"debug","phase":"exec_command","session":"issue505-android-baseline","requestId":"9398ddd4b730eef2","command":"record","durationMs":41,"data":{"command":"adb","argsPrefix":["-s","emulator-5554","shell","test -e '/data/local/tmp/agent-device-recording-active.json'"]}}
{"ts":"2026-09-05T00:12:03.575Z","level":"debug","phase":"exec_command","session":"issue505-android-baseline","requestId":"9398ddd4b730eef2","command":"record","durationMs":44,"data":{"command":"adb","argsPrefix":["-s","emulator-5554","shell","test -e '/sdcard/agent-device-recording-active.json'"]}}
{"ts":"2026-09-05T00:12:03.628Z","level":"debug","phase":"exec_command","session":"issue505-android-baseline","requestId":"9398ddd4b730eef2","command":"record","durationMs":53,"data":{"command":"adb","argsPrefix":["-s","emulator-5554","shell","cat '/sdcard/agent-device-recording-active.json'"]}}
{"ts":"2026-09-05T00:12:03.628Z","level":"error","phase":"request_failed","session":"issue505-android-baseline","requestId":"9398ddd4b730eef2","command":"record","data":{"code":"UNKNOWN","message":"Android screenrecord native recovery evidence already exists"}}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions artifacts/issue-505/baseline/ios-26.5/logs/launch-console.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
2026-09-05 01:08:02.953 ReactTestApp[17006:8903825] You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
WARNING: Logging before InitGoogleLogging() is written to STDERR
W0905 01:08:03.091425 12459520 InspectorPackagerConnection.cpp:245] Couldn't connect to packager, will silently retry
objc[17006]: Class UIAccessibilityLoaderWebShared is implemented in both /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebCore.axbundle/WebCore (0x125e9c310) and /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebKit.axbundle/WebKit (0x124cb0398). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 1,
"generatedAt": "2026-09-05T00:09:11.877Z",
"events": [
{
"kind": "tap",
"tMs": 7827.68701171875,
"x": 60,
"y": 830,
"referenceWidth": 402,
"referenceHeight": 874
}
]
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions artifacts/issue-505/review/codex-remediation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Remediated the valid Fable findings with a consumer-scoped fix instead of changing global scene layout.

### Findings

- Fixed — iPadOS/tvOS wrong-edge subtraction: removed `getSceneDimensions` entirely and restored the original full-size scene behavior.
- Fixed — full-bleed/translucent regressions and doubled offsets: `TabView.tsx` is back to its unmodified baseline; existing `useBottomTabBarHeight` usage remains unchanged.
- Fixed — `minimizeBehavior` dead strip: no scene height is reserved globally. Documentation explicitly excludes minimized bars from the fixed-content pattern.
- Fixed — bottom-accessory overlap: removed the incomplete global adjustment and scoped the documented workaround to layouts without bottom accessories.
- Rejected — binary evidence should be removed/gitignored: the user explicitly required durable device evidence. The retained evidence is catalogued in its [README](/Users/thiagobrez/.codex/worktrees/4916/react-native-bottom-tabs/artifacts/issue-505/README.md:13).
- Deferred — none.

### Changes

- Kept the reproducible [KeyboardAvoidingView example](/Users/thiagobrez/.codex/worktrees/4916/react-native-bottom-tabs/apps/example/src/Examples/KeyboardAvoidingView.tsx:14).
- Applied `useBottomTabBarHeight` only to the affected keyboard-avoiding content on iPhone/iOS 26+, while an outer view preserves the full-bleed background.
- Registered the example in [App.tsx](/Users/thiagobrez/.codex/worktrees/4916/react-native-bottom-tabs/apps/example/src/App.tsx:200).
- Added focused usage and scope documentation in [handling-scrollview-insets.md](/Users/thiagobrez/.codex/worktrees/4916/react-native-bottom-tabs/docs/docs/docs/guides/handling-scrollview-insets.md:11).
- Removed the proposed helper, invalid scene-resizing tests, and inaccurate changeset.
- Made no Android changes, avoiding duplication of PR #549.

### Verification

Passed:

- Focused ESLint: zero errors; two pre-existing warnings.
- `react-native-bottom-tabs` TypeScript build.
- iOS example Metro bundle.
- Documentation production build.
- Prettier check.
- `git diff --check`.

Existing infrastructure blockers:

- Example-wide TypeScript reports the pre-existing `LabeledTabs/showLabels` component-union error in `App.tsx`.
- Jest first encountered sandboxed Watchman permissions, then a pre-existing Babel parse failure in React Native’s Jest setup when rerun with Watchman disabled. No applicable runtime unit logic remains after removing the invalid scene-sizing helper.
39 changes: 39 additions & 0 deletions artifacts/issue-505/review/fable-findings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"findings": [
{
"title": "Height subtracted from wrong edge on iPadOS/tvOS 26 where the tab bar is not at the bottom",
"body": "getSceneDimensions gates only on platform === ios and version >= 26, but Platform.OS is ios on iPad (and on tvOS with react-native-tvos). On iPadOS 18+/26 the native tab bar renders at the top of the screen (or as a sidebar with sidebarAdaptable), and on tvOS it is always at the top, yet onTabBarMeasured still reports tabController.tabBar.frame.size.height with no placement info. The scene View is absolutely positioned at the top-left, so subtracting tabBarHeight shrinks the scene at the bottom: every iPad/tvOS 26 screen gets a dead blank strip at the bottom equal to the bar height while content still underlaps the top bar. In sidebar mode the measured frame height is not validated at all, so an unexpected large value would collapse the scene toward Math.max(0, ...). Evidence covers only iPhone simulators; there is no Platform.isPad/idiom or bar-placement check.",
"priority": 1,
"file": "packages/react-native-bottom-tabs/src/utils/getSceneDimensions.ts",
"line": 26
},
{
"title": "iOS 26 scenes no longer render under the translucent Liquid Glass bar; breaks documented useBottomTabBarHeight offsets for all apps",
"body": "This changes layout for every iOS 26 screen, not just keyboard scenarios. Scenes were deliberately full-bleed so backgrounds and scroll content extend behind the translucent floating bar. After this change the scene ends exactly at the bar top, so the region behind glass can show the clear window background, ScrollViews cannot reach the bar, and existing useBottomTabBarHeight offsets become doubled. Existing iOS 26 apps regress visually with no opt-out.",
"priority": 1,
"file": "packages/react-native-bottom-tabs/src/TabView.tsx",
"line": 516
},
{
"title": "minimizeBehavior leaves a permanent dead strip: scene stays shortened by the full bar height after the bar minimizes",
"body": "The bar can shrink to a compact pill while tabBarHeight is emitted only from the introspection closure, leaving the scene shortened by the original full height or causing a mid-scroll jump if it re-emits.",
"priority": 2,
"file": "packages/react-native-bottom-tabs/src/TabView.tsx",
"line": 297
},
{
"title": "Fix does not account for renderBottomAccessoryView: bottom content still obscured on iOS 26",
"body": "The accessory floats above the tab bar, but the scene adjustment subtracts only tabBarHeight, so bottom-aligned controls can still be covered when an accessory is present.",
"priority": 3,
"file": "packages/react-native-bottom-tabs/src/TabView.tsx",
"line": 292
},
{
"title": "Binary evidence artifacts are untracked and not gitignored",
"body": "Committing screenshots and MP4s grows repository history. Ordinarily evidence should live in the PR or issue rather than the library repository.",
"priority": 3,
"file": "artifacts/issue-505/README.md",
"line": 1
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
2026-09-05 01:26:10.652 ReactTestApp[38963:8961026] You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
2026-09-05 01:26:10.904 ReactTestApp[38963:8961026] Unbalanced calls to begin/end appearance transitions for <UIViewController: 0x10bda2880>.
objc[38963]: Class UIAccessibilityLoaderWebShared is implemented in both /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebCore.axbundle/WebCore (0x1131f8310) and /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebKit.axbundle/WebKit (0x1130e4398). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
2026-09-05 01:48:12.339 ReactTestApp[63218:9019562] You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
WARNING: Logging before InitGoogleLogging() is written to STDERR
W0905 01:48:12.463690 38510080 InspectorPackagerConnection.cpp:245] Couldn't connect to packager, will silently retry
objc[63218]: Class UIAccessibilityLoaderWebShared is implemented in both /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebCore.axbundle/WebCore (0x12749c310) and /Library/Developer/CoreSimulator/Volumes/iOS_23F77/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 26.5.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebKit.axbundle/WebKit (0x126bcc398). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": 1,
"generatedAt": "2026-09-05T00:49:27.043Z",
"events": [
{
"kind": "tap",
"tMs": 5177.934326171875,
"x": 201,
"y": 489,
"referenceWidth": 402,
"referenceHeight": 874
},
{
"kind": "tap",
"tMs": 15831.77197265625,
"x": 201,
"y": 745,
"referenceWidth": 402,
"referenceHeight": 874
},
{
"kind": "tap",
"tMs": 30099,
"x": 201,
"y": 493,
"referenceWidth": 402,
"referenceHeight": 874
}
]
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
2026-09-05 01:50:13.657 ReactTestApp[65616:9027717] You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
WARNING: Logging before InitGoogleLogging() is written to STDERR
W0905 01:50:14.017598 75832832 InspectorPackagerConnection.cpp:245] Couldn't connect to packager, will silently retry
objc[65616]: Class UIAccessibilityLoaderWebShared is implemented in both /Library/Developer/CoreSimulator/Volumes/iOS_24A5390f/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 27.0.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebKit.axbundle/WebKit (0x111090398) and /Library/Developer/CoreSimulator/Volumes/iOS_24A5390f/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 27.0.simruntime/Contents/Resources/RuntimeRoot/System/Library/AccessibilityBundles/WebCore.axbundle/WebCore (0x119b6c310). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading