Files
Anna GarciaandGitHub 8c24bc1cac perf(react-native): coalesce storage writes with a debounce window (#3701)
* perf(react-native): coalesce storage writes with a debounce window

RN persists the whole storage blob on every change, so a burst of captures
re-serializes a growing blob N times (O(n^2)) and does N disk writes. Batch
writes into a 100ms window while keeping the in-memory copy current; event
flush, AppState background, shutdown, and login/logout still drain
synchronously so the queue and identity stay durable.
Refine captureException durability. Drain both storage pipelines to disk only
when the exception is fatal (tagged via $exception_level), rather than
unconditionally on every captureException: console autocapture turns every
console.error/warn into a captureException, so an unconditional drain would
re-introduce write amplification. Handled exceptions persist via the normal
debounce. Soften the durability comments to say writes are initiated
(best-effort, async) rather than guaranteed to land before a crash.
- Drain on optIn() / optOut(). Consent must be durable: a hard-kill within
  the debounce window before any flush/background/shutdown would lose the
  OptedOut write, and the optedOut getter falls back to !defaultOptIn so
  capture silently resumes for a user who explicitly opted out. Same
  crash-window the PR closes for logout/identify.
- New storage test: the debounced write fires on its own after the window.
  Every other functional test forced the write via waitForPersist, so a
  regression where the timer never armed would ship green.
- New storage test: the timer is not reset by later mutations (arm-once,
  no starvation). Guards against a textbook clear-and-reschedule debounce.
- Refined the fatal-exception assertion to check the parsed events queue
  for an item whose message.event is \$exception, rather than a substring
  match that could pass on only the level tag round-tripping.
- Trimmed the verbose drain-site comments and centralized the
  waitForPersist contract on the method doc (addresses the PR feedback).
- Updated the changeset to note custom-storage write-timing and the
  ~100ms loss window.
2026-05-29 18:35:41 -04:00

77 lines
2.7 KiB
TypeScript

import { Tabs } from 'expo-router'
import React from 'react'
import { Platform } from 'react-native'
import { HapticTab } from '@/components/HapticTab'
import { IconSymbol } from '@/components/ui/IconSymbol'
import TabBarBackground from '@/components/ui/TabBarBackground'
import { Colors } from '@/constants/Colors'
import { useColorScheme } from '@/hooks/useColorScheme'
export default function TabLayout() {
const colorScheme = useColorScheme()
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarButton: HapticTab,
tabBarBackground: TabBarBackground,
tabBarStyle: Platform.select({
ios: {
// Use a transparent background on iOS to show the blur effect
position: 'absolute',
},
default: {},
}),
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: 'Explore',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
}}
/>
<Tabs.Screen
name="error-tracking"
options={{
title: 'Error Tracking',
tabBarIcon: ({ color }) => (
<IconSymbol size={28} name="exclamationmark.warninglight" color={color} />
),
}}
/>
<Tabs.Screen
name="tracing-headers"
options={{
title: 'Tracing Headers',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="link" color={color} />,
}}
/>
<Tabs.Screen
name="logs"
options={{
title: 'Logs',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="text.bubble.fill" color={color} />,
}}
/>
<Tabs.Screen
name="storage-burst"
options={{
title: 'Storage',
tabBarIcon: ({ color }) => <IconSymbol size={28} name="list.bullet.rectangle" color={color} />,
}}
/>
</Tabs>
)
}