diff --git a/ios/patching/001-social-app-ios-config.patch b/ios/patching/001-social-app-ios-config.patch
index 6526b61..4c78864 100644
--- a/ios/patching/001-social-app-ios-config.patch
+++ b/ios/patching/001-social-app-ios-config.patch
@@ -1,150 +1,44 @@
-diff --git a/Dockerfile b/Dockerfile
-index 371e8402c..2e139503e 100644
---- a/Dockerfile
-+++ b/Dockerfile
-@@ -66,7 +66,8 @@ RUN \. "$NVM_DIR/nvm.sh" && \
- echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env && \
- echo "EXPO_PUBLIC_SENTRY_DSN=$EXPO_PUBLIC_SENTRY_DSN" >> .env && \
- npm install --global yarn && \
-- yarn && \
-+ yarn config set registry https://registry.npmjs.org/ && \
-+ yarn install --frozen-lockfile --network-timeout 100000 && \
- yarn intl:build 2>&1 | tee i18n.log && \
- if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compile errors!\n\n"; fi && \
- SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN SENTRY_RELEASE=$EXPO_PUBLIC_RELEASE_VERSION SENTRY_DIST=$EXPO_PUBLIC_BUNDLE_IDENTIFIER yarn build-web
diff --git a/app.config.js b/app.config.js
-index 246d8abd3..a6582864b 100644
+index 246d8abd3..56f42202b 100644
--- a/app.config.js
+++ b/app.config.js
-@@ -33,8 +33,8 @@ module.exports = function (_config) {
- return {
- expo: {
- version: VERSION,
-- name: 'Bluesky',
-- slug: 'bluesky',
-+ name: 'Aiat',
-+ slug: 'aiat',
- scheme: 'bluesky',
- owner: 'blueskysocial',
+@@ -40,7 +40,7 @@ module.exports = function (_config) {
runtimeVersion: {
-@@ -45,15 +45,20 @@ module.exports = function (_config) {
+ policy: 'appVersion',
+ },
+- icon: './assets/app-icons/ios_icon_default_next.png',
++ icon: './assets/icon.png',
+ userInterfaceStyle: 'automatic',
primaryColor: '#1083fe',
newArchEnabled: false,
- ios: {
-+ infoPlist: {
-+ NSAppTransportSecurity: {
-+ NSAllowsArbitraryLoads: true,
-+ },
-+ },
- supportsTablet: false,
-- bundleIdentifier: 'xyz.blueskyweb.app',
-+ bundleIdentifier: 'ai.syui.at',
- config: {
- usesNonExemptEncryption: false,
- },
- icon:
- PLATFORM === 'web' // web build doesn't like .icon files
- ? './assets/app-icons/ios_icon_default_next.png'
-- : './assets/app-icons/ios_icon_default.icon',
-+ : './assets/app-icons/ios_icon_default_next.png',
- infoPlist: {
- UIBackgroundModes: ['remote-notification'],
- NSCameraUsageDescription:
-@@ -113,7 +118,7 @@ module.exports = function (_config) {
- entitlements: {
- 'com.apple.developer.kernel.increased-memory-limit': true,
- 'com.apple.developer.kernel.extended-virtual-addressing': true,
-- 'com.apple.security.application-groups': 'group.app.bsky',
-+ 'com.apple.security.application-groups': 'group.ai.syui.at',
- },
- privacyManifests: {
- NSPrivacyCollectedDataTypes: [
-@@ -182,7 +187,7 @@ module.exports = function (_config) {
- backgroundColor: '#006AFF',
- },
- googleServicesFile: './google-services.json',
-- package: 'xyz.blueskyweb.app',
-+ package: 'ai.syui.at',
- intentFilters: [
- {
- action: 'VIEW',
-@@ -220,7 +225,7 @@ module.exports = function (_config) {
+@@ -213,9 +213,9 @@ module.exports = function (_config) {
+ : undefined,
+ codeSigningMetadata: UPDATES_ENABLED
+ ? {
+- keyid: 'main',
+- alg: 'rsa-v1_5-sha256',
+- }
++ keyid: 'main',
++ alg: 'rsa-v1_5-sha256',
++ }
+ : undefined,
checkAutomatically: 'NEVER',
},
- plugins: [
-- 'expo-video',
-+ 'expo-video', './plugins/withCodeSignEntitlements.js',
- 'expo-localization',
+@@ -225,7 +225,7 @@ module.exports = function (_config) {
'expo-web-browser',
[
-@@ -239,6 +244,11 @@ module.exports = function (_config) {
- 'expo-build-properties',
- {
- ios: {
-+ infoPlist: {
-+ NSAppTransportSecurity: {
-+ NSAllowsArbitraryLoads: true,
-+ },
-+ },
- deploymentTarget: '15.1',
- buildReactNativeFromSource: true,
+ 'react-native-edge-to-edge',
+- {android: {enforceNavigationBarContrast: false}},
++ { android: { enforceNavigationBarContrast: false } },
+ ],
+ USE_SENTRY && [
+ '@sentry/react-native/expo',
+@@ -386,7 +386,7 @@ module.exports = function (_config) {
},
-@@ -264,7 +274,6 @@ module.exports = function (_config) {
- networkInstrumentation: true,
},
],
-- './plugins/starterPackAppClipExtension/withStarterPackAppClip.js',
- './plugins/withGradleJVMHeapSizeIncrease.js',
- './plugins/withAndroidManifestLargeHeapPlugin.js',
- './plugins/withAndroidManifestFCMIconPlugin.js',
-@@ -296,6 +305,11 @@ module.exports = function (_config) {
- 'expo-splash-screen',
- {
- ios: {
-+ infoPlist: {
-+ NSAppTransportSecurity: {
-+ NSAllowsArbitraryLoads: true,
-+ },
-+ },
- enableFullScreenImage_legacy: true,
- backgroundColor: '#ffffff',
- image: './assets/splash.png',
-@@ -394,29 +408,30 @@ module.exports = function (_config) {
- build: {
- experimental: {
- ios: {
-+ infoPlist: {
-+ NSAppTransportSecurity: {
-+ NSAllowsArbitraryLoads: true,
-+ },
-+ },
- appExtensions: [
- {
- targetName: 'Share-with-Bluesky',
-- bundleIdentifier: 'xyz.blueskyweb.app.Share-with-Bluesky',
-+ bundleIdentifier: 'ai.syui.at.Share-with-Bluesky',
- entitlements: {
- 'com.apple.security.application-groups': [
-- 'group.app.bsky',
-+ 'group.ai.syui.at',
- ],
- },
- },
- {
- targetName: 'BlueskyNSE',
-- bundleIdentifier: 'xyz.blueskyweb.app.BlueskyNSE',
-+ bundleIdentifier: 'ai.syui.at.BlueskyNSE',
- entitlements: {
- 'com.apple.security.application-groups': [
-- 'group.app.bsky',
-+ 'group.ai.syui.at',
- ],
- },
- },
-- {
-- targetName: 'BlueskyClip',
-- bundleIdentifier: 'xyz.blueskyweb.app.AppClip',
-- },
- ],
- },
- },
+- ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
++ ['expo-screen-orientation', { initialOrientation: 'PORTRAIT_UP' }],
+ ['expo-location'],
+ ].filter(Boolean),
+ extra: {
diff --git a/ios/patching/011-social-app-ios-splash-license-footer.patch b/ios/patching/011-social-app-ios-splash-license-footer.patch
index a8f182e..086172e 100644
--- a/ios/patching/011-social-app-ios-splash-license-footer.patch
+++ b/ios/patching/011-social-app-ios-splash-license-footer.patch
@@ -1,37 +1,54 @@
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
-index 3442d1bdf..2b059af52 100644
+index 3442d1bdf..034310a17 100644
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
-@@ -40,16 +40,6 @@ export const SplashScreen = ({
-
-
-
--
--
-- What's up?
--
-
+@@ -1,18 +1,18 @@
+-import {View} from 'react-native'
+-import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
+-import {useSafeAreaInsets} from 'react-native-safe-area-context'
+-import {msg, Trans} from '@lingui/macro'
+-import {useLingui} from '@lingui/react'
++import { View } from 'react-native'
++import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
++import { useSafeAreaInsets } from 'react-native-safe-area-context'
++import { msg, Trans } from '@lingui/macro'
++import { useLingui } from '@lingui/react'
-
-@@ -102,6 +92,17 @@ export const SplashScreen = ({
+-import {useHaptics} from '#/lib/haptics'
+-import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
+-import {CenteredView} from '#/view/com/util/Views'
+-import {Logo} from '#/view/icons/Logo'
+-import {Logotype} from '#/view/icons/Logotype'
+-import {atoms as a, useTheme} from '#/alf'
+-import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
+-import {Button, ButtonText} from '#/components/Button'
+-import {Text} from '#/components/Typography'
++import { useHaptics } from '#/lib/haptics'
++import { ErrorBoundary } from '#/view/com/util/ErrorBoundary'
++import { CenteredView } from '#/view/com/util/Views'
++import { Logo } from '#/view/icons/Logo'
++import { Logotype } from '#/view/icons/Logotype'
++import { atoms as a, useTheme } from '#/alf'
++import { AppLanguageDropdown } from '#/components/AppLanguageDropdown'
++import { Button, ButtonText } from '#/components/Button'
++import { Text } from '#/components/Typography'
+
+ export const SplashScreen = ({
+ onPressSignin,
+@@ -22,7 +22,7 @@ export const SplashScreen = ({
+ onPressCreateAccount: () => void
+ }) => {
+ const t = useTheme()
+- const {_} = useLingui()
++ const { _ } = useLingui()
+
+ const playHaptic = useHaptics()
+ const insets = useSafeAreaInsets()
+@@ -102,7 +102,18 @@ export const SplashScreen = ({
+-
+
+
-
++
-diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx
-index 22dd23d7f..08cd8aa33 100644
---- a/src/view/com/auth/SplashScreen.web.tsx
-+++ b/src/view/com/auth/SplashScreen.web.tsx
-@@ -3,7 +3,9 @@ import {Pressable, View} from 'react-native'
- import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
- import {msg, Trans} from '@lingui/macro'
- import {useLingui} from '@lingui/react'
-+import {useNavigation} from '@react-navigation/native'
-
-+import {NavigationProp} from '#/lib/routes/types'
- import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
- import {useKawaiiMode} from '#/state/preferences/kawaii'
- import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
-@@ -93,15 +95,6 @@ export const SplashScreen = ({
-
-
- )}
--
--
-- What's up?
--
-
-
-
--
-- Business
--
--
-- Blog
--
--
--
-- Jobs
--
--
--
-
-
-+
-+ © syui
-+
-+
-
-
- )
+
diff --git a/ios/patching/020-social-app-ios-bypass-age-assurance.patch b/ios/patching/020-social-app-ios-bypass-age-assurance.patch
index 7744943..fc15abc 100644
--- a/ios/patching/020-social-app-ios-bypass-age-assurance.patch
+++ b/ios/patching/020-social-app-ios-bypass-age-assurance.patch
@@ -1,5 +1,7 @@
---- a/b/src/ageAssurance/index.tsx 2025-12-07 15:18:15
-+++ b/src/ageAssurance/index.tsx 2025-12-07 15:18:16
+diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx
+index 9a0a9c9d5..5da399795 100644
+--- a/src/ageAssurance/index.tsx
++++ b/src/ageAssurance/index.tsx
@@ -1,10 +1,10 @@
-import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'
+import { createContext, useCallback, useContext, useEffect, useMemo } from 'react'
@@ -17,7 +19,7 @@
import {
useAgeAssuranceState,
useOnAgeAssuranceAccessUpdate,
-@@ -14,7 +14,7 @@
+@@ -14,7 +14,7 @@ import {
type AgeAssuranceState,
AgeAssuranceStatus,
} from '#/ageAssurance/types'
@@ -26,7 +28,7 @@
export {
prefetchConfig as prefetchAgeAssuranceConfig,
-@@ -23,7 +23,7 @@
+@@ -23,7 +23,7 @@ export {
usePatchOtherRequiredData as usePatchAgeAssuranceOtherRequiredData,
usePatchServerState as usePatchAgeAssuranceServerState,
} from '#/ageAssurance/data'
@@ -35,7 +37,7 @@
const AgeAssuranceStateContext = createContext<{
Access: typeof AgeAssuranceAccess
-@@ -56,7 +56,7 @@
+@@ -56,7 +56,7 @@ export function useAgeAssurance() {
return useContext(AgeAssuranceStateContext)
}
@@ -44,7 +46,7 @@
return (
-@@ -66,9 +66,9 @@
+@@ -66,9 +66,9 @@ export function Provider({children}: {children: React.ReactNode}) {
)
}
@@ -56,7 +58,7 @@
const getAndRegisterPushToken = useGetAndRegisterPushToken()
const handleAccessUpdate = useCallback(
-@@ -82,28 +82,25 @@
+@@ -82,7 +82,7 @@ function InnerProvider({children}: {children: React.ReactNode}) {
useOnAgeAssuranceAccessUpdate(handleAccessUpdate)
useEffect(() => {
@@ -65,14 +67,7 @@
}, [state])
return (
- {
-- const chatDisabled = state.access !== AgeAssuranceAccess.Full
-- const isUnderage = data?.birthdate
-- ? isUserUnderAdultAge(data.birthdate)
-- : true
-- const adultContentDisabled =
-- state.access !== AgeAssuranceAccess.Full || isUnderage
+@@ -97,7 +97,10 @@ function InnerProvider({children}: {children: React.ReactNode}) {
return {
Access: AgeAssuranceAccess,
Status: AgeAssuranceStatus,
@@ -82,14 +77,5 @@
+ access: AgeAssuranceAccess.Full,
+ },
flags: {
-- adultContentDisabled,
-- chatDisabled,
-+ adultContentDisabled: false,
-+ chatDisabled: false,
- },
- }
-- }, [state, data])}>
-+ }, [state])}>
- {children}
-
- )
+ adultContentDisabled,
+ chatDisabled,
diff --git a/ios/patching/021-social-app-ios-clean-feed.patch b/ios/patching/021-social-app-ios-clean-feed.patch
new file mode 100644
index 0000000..8f3c3b6
--- /dev/null
+++ b/ios/patching/021-social-app-ios-clean-feed.patch
@@ -0,0 +1,445 @@
+diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
+index e058e2883..1d6bc4f45 100644
+--- a/src/view/screens/Home.tsx
++++ b/src/view/screens/Home.tsx
+@@ -1,342 +1,139 @@
+ import React from 'react'
+-import {ActivityIndicator, StyleSheet} from 'react-native'
+-import {useFocusEffect} from '@react-navigation/native'
+-
+-import {PROD_DEFAULT_FEED} from '#/lib/constants'
+-import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
+-import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
+-import {useSetTitle} from '#/lib/hooks/useSetTitle'
+-import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
+-import {
+- type HomeTabNavigatorParams,
+- type NativeStackScreenProps,
+-} from '#/lib/routes/types'
+-import {logEvent} from '#/lib/statsig/statsig'
+-import {isWeb} from '#/platform/detection'
+-import {emitSoftReset} from '#/state/events'
+-import {
+- type SavedFeedSourceInfo,
+- usePinnedFeedsInfos,
+-} from '#/state/queries/feed'
+-import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
+-import {usePreferencesQuery} from '#/state/queries/preferences'
+-import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
+-import {useSession} from '#/state/session'
+-import {useSetMinimalShellMode} from '#/state/shell'
+-import {useLoggedOutViewControls} from '#/state/shell/logged-out'
+-import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
+-import {FeedPage} from '#/view/com/feeds/FeedPage'
+-import {HomeHeader} from '#/view/com/home/HomeHeader'
+-import {
+- Pager,
+- type PagerRef,
+- type RenderTabBarFnProps,
+-} from '#/view/com/pager/Pager'
+-import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
+-import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
+-import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
+-import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
++import { ActivityIndicator, StyleSheet } from 'react-native'
++import { useFocusEffect } from '@react-navigation/native'
++import { PROD_DEFAULT_FEED } from '#/lib/constants'
++import { useNonReactiveCallback } from '#/lib/hooks/useNonReactiveCallback'
++import { useOTAUpdates } from '#/lib/hooks/useOTAUpdates'
++import { useSetTitle } from '#/lib/hooks/useSetTitle'
++import { useRequestNotificationsPermission } from '#/lib/notifications/notifications'
++import { type HomeTabNavigatorParams, type NativeStackScreenProps } from '#/lib/routes/types'
++import { logEvent } from '#/lib/statsig/statsig'
++import { isWeb } from '#/platform/detection'
++import { emitSoftReset } from '#/state/events'
++import { type SavedFeedSourceInfo, usePinnedFeedsInfos } from '#/state/queries/feed'
++import { type FeedDescriptor, type FeedParams } from '#/state/queries/post-feed'
++import { usePreferencesQuery } from '#/state/queries/preferences'
++import { type UsePreferencesQueryResponse } from '#/state/queries/preferences/types'
++import { useSession } from '#/state/session'
++import { useSetMinimalShellMode } from '#/state/shell'
++import { useLoggedOutViewControls } from '#/state/shell/logged-out'
++import { useSelectedFeed, useSetSelectedFeed } from '#/state/shell/selected-feed'
++import { FeedPage } from '#/view/com/feeds/FeedPage'
++import { HomeHeader } from '#/view/com/home/HomeHeader'
++import { Pager, type PagerRef, type RenderTabBarFnProps } from '#/view/com/pager/Pager'
++import { CustomFeedEmptyState } from '#/view/com/posts/CustomFeedEmptyState'
++import { FollowingEmptyState } from '#/view/com/posts/FollowingEmptyState'
++import { FollowingEndOfFeed } from '#/view/com/posts/FollowingEndOfFeed'
++import { NoFeedsPinned } from '#/screens/Home/NoFeedsPinned'
+ import * as Layout from '#/components/Layout'
+-import {useDemoMode} from '#/storage/hooks/demo-mode'
++import { useDemoMode } from '#/storage/hooks/demo-mode'
++
++const DEFAULT_PINNED_FEEDS = [{
++ feedDescriptor: 'following',
++ displayName: 'Following',
++ id: 'following',
++ type: 'feed',
++ savedFeed: undefined,
++ pinned: true,
++}]
+
+ type Props = NativeStackScreenProps
+ export function HomeScreen(props: Props) {
+- const {setShowLoggedOut} = useLoggedOutViewControls()
+- const {data: preferences} = usePreferencesQuery()
+- const {currentAccount} = useSession()
+- const {data: pinnedFeedInfos, isLoading: isPinnedFeedsLoading} =
+- usePinnedFeedsInfos()
++ const { setShowLoggedOut } = useLoggedOutViewControls()
++ const { data: preferences } = usePreferencesQuery()
++ const { currentAccount } = useSession()
++ const { data: pinnedFeedInfos } = usePinnedFeedsInfos()
++
++ const safePreferences = preferences || { feedViewPrefs: { lab_mergeFeedEnabled: false }, savedFeeds: [] } as any
++ const safePinnedFeedInfos = [{
++ feedDescriptor: 'following',
++ displayName: 'Following',
++ id: 'following',
++ type: 'feed',
++ savedFeed: undefined,
++ pinned: true,
++ }]
+
+ React.useEffect(() => {
+ if (isWeb && !currentAccount) {
+ const getParams = new URLSearchParams(window.location.search)
+ const splash = getParams.get('splash')
+- if (splash === 'true') {
+- setShowLoggedOut(true)
+- return
+- }
++ if (splash === 'true') { setShowLoggedOut(true); return }
+ }
+-
+ const params = props.route.params
+- if (
+- currentAccount &&
+- props.route.name === 'Start' &&
+- params?.name &&
+- params?.rkey
+- ) {
+- props.navigation.navigate('StarterPack', {
+- rkey: params.rkey,
+- name: params.name,
+- })
++ if (currentAccount && props.route.name === 'Start' && params?.name && params?.rkey) {
++ props.navigation.navigate('StarterPack', { rkey: params.rkey, name: params.name })
+ }
+- }, [
+- currentAccount,
+- props.navigation,
+- props.route.name,
+- props.route.params,
+- setShowLoggedOut,
+- ])
++ }, [currentAccount, props.navigation, props.route.name, props.route.params, setShowLoggedOut])
+
+- if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
+- return (
+-
+-
+-
+- )
+- } else {
+- return (
+-
+-
+-
+-
+-
+- )
+- }
++ return (
++
++
++
++ )
+ }
+
+-function HomeScreenReady({
+- preferences,
+- pinnedFeedInfos,
+-}: Props & {
+- preferences: UsePreferencesQueryResponse
+- pinnedFeedInfos: SavedFeedSourceInfo[]
+-}) {
+- const allFeeds = React.useMemo(
+- () => pinnedFeedInfos.map(f => f.feedDescriptor),
+- [pinnedFeedInfos],
+- )
+- const maybeRawSelectedFeed: FeedDescriptor | undefined =
+- useSelectedFeed() ?? allFeeds[0]
++function HomeScreenReady({ preferences, pinnedFeedInfos }: any) {
++ const allFeeds = React.useMemo(() => pinnedFeedInfos.map(f => f.feedDescriptor), [pinnedFeedInfos])
++ const maybeRawSelectedFeed = useSelectedFeed() ?? allFeeds[0]
+ const setSelectedFeed = useSetSelectedFeed()
+ const maybeFoundIndex = allFeeds.indexOf(maybeRawSelectedFeed)
+ const selectedIndex = Math.max(0, maybeFoundIndex)
+- const maybeSelectedFeed: FeedDescriptor | undefined = allFeeds[selectedIndex]
++ const maybeSelectedFeed = allFeeds[selectedIndex]
+ const requestNotificationsPermission = useRequestNotificationsPermission()
+
+ useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName)
+ useOTAUpdates()
+-
+- React.useEffect(() => {
+- requestNotificationsPermission('Home')
+- }, [requestNotificationsPermission])
++ React.useEffect(() => { requestNotificationsPermission('Home') }, [requestNotificationsPermission])
+
+ const pagerRef = React.useRef(null)
+ const lastPagerReportedIndexRef = React.useRef(selectedIndex)
+ React.useLayoutEffect(() => {
+- // Since the pager is not a controlled component, adjust it imperatively
+- // if the selected index gets out of sync with what it last reported.
+- // This is supposed to only happen on the web when you use the right nav.
+ if (selectedIndex !== lastPagerReportedIndexRef.current) {
+ lastPagerReportedIndexRef.current = selectedIndex
+ pagerRef.current?.setPage(selectedIndex)
+ }
+ }, [selectedIndex])
+
+- const {hasSession} = useSession()
++ const { hasSession } = useSession()
+ const setMinimalShellMode = useSetMinimalShellMode()
+- useFocusEffect(
+- React.useCallback(() => {
+- setMinimalShellMode(false)
+- }, [setMinimalShellMode]),
+- )
+-
+- useFocusEffect(
+- useNonReactiveCallback(() => {
+- if (maybeSelectedFeed) {
+- logEvent('home:feedDisplayed', {
+- index: selectedIndex,
+- feedType: maybeSelectedFeed.split('|')[0],
+- feedUrl: maybeSelectedFeed,
+- reason: 'focus',
+- })
+- }
+- }),
+- )
++ useFocusEffect(React.useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]))
+
+- const onPageSelected = React.useCallback(
+- (index: number) => {
+- setMinimalShellMode(false)
+- const maybeFeed = allFeeds[index]
+-
+- // Mutate the ref before setting state to avoid the imperative syncing effect
+- // above from starting a loop on Android when swiping back and forth.
+- lastPagerReportedIndexRef.current = index
+- setSelectedFeed(maybeFeed)
+-
+- if (maybeFeed) {
+- logEvent('home:feedDisplayed', {
+- index,
+- feedType: maybeFeed.split('|')[0],
+- feedUrl: maybeFeed,
+- })
+- }
+- },
+- [setSelectedFeed, setMinimalShellMode, allFeeds],
+- )
++ const onPageSelected = React.useCallback((index) => {
++ setMinimalShellMode(false)
++ const maybeFeed = allFeeds[index]
++ lastPagerReportedIndexRef.current = index
++ setSelectedFeed(maybeFeed)
++ }, [setSelectedFeed, setMinimalShellMode, allFeeds])
+
+- const onPressSelected = React.useCallback(() => {
+- emitSoftReset()
+- }, [])
+-
+- const onPageScrollStateChanged = React.useCallback(
+- (state: 'idle' | 'dragging' | 'settling') => {
+- 'worklet'
+- if (state === 'dragging') {
+- setMinimalShellMode(false)
+- }
+- },
+- [setMinimalShellMode],
+- )
++ const onPressSelected = React.useCallback(() => { emitSoftReset() }, [])
++ const onPageScrollStateChanged = React.useCallback((state) => {
++ 'worklet'
++ if (state === 'dragging') setMinimalShellMode(false)
++ }, [setMinimalShellMode])
+
+ const [demoMode] = useDemoMode()
+-
+- const renderTabBar = React.useCallback(
+- (props: RenderTabBarFnProps) => {
+- if (demoMode) {
+- return (
+-
+- )
+- }
+- return (
+-
+- )
+- },
+- [onPressSelected, pinnedFeedInfos, demoMode],
+- )
+-
+- const renderFollowingEmptyState = React.useCallback(() => {
+- return
+- }, [])
+-
+- const renderCustomFeedEmptyState = React.useCallback(() => {
+- return
+- }, [])
+-
+- const homeFeedParams = React.useMemo(() => {
+- return {
+- mergeFeedEnabled: Boolean(preferences.feedViewPrefs.lab_mergeFeedEnabled),
+- mergeFeedSources: preferences.feedViewPrefs.lab_mergeFeedEnabled
+- ? preferences.savedFeeds
+- .filter(f => f.type === 'feed' || f.type === 'list')
+- .map(f => f.value)
+- : [],
+- }
+- }, [preferences])
+-
+- if (demoMode) {
+- return (
+-
+-
+-
+-
+- )
+- }
+-
+- return hasSession ? (
+-
+- {pinnedFeedInfos.length ? (
+- pinnedFeedInfos.map((feedInfo, index) => {
+- const feed = feedInfo.feedDescriptor
+- if (feed === 'following') {
+- return (
+-
+- )
+- }
+- const savedFeedConfig = feedInfo.savedFeed
+- return (
+-
+- )
+- })
+- ) : (
+-
+- )}
+-
+- ) : (
+-
+-
++ const renderTabBar = React.useCallback((props) => {
++ return
++ }, [onPressSelected, pinnedFeedInfos])
++
++ const renderFollowingEmptyState = React.useCallback(() => , [])
++ const renderCustomFeedEmptyState = React.useCallback(() => , [])
++
++ const homeFeedParams = React.useMemo(() => ({
++ mergeFeedEnabled: false, mergeFeedSources: []
++ }), [preferences])
++
++ return (
++
++ {pinnedFeedInfos.map((feedInfo, index) => {
++ const feed = feedInfo.feedDescriptor
++ if (feed === 'following') {
++ return
++ }
++ return
++ })}
+
+ )
+ }
+-
+-const styles = StyleSheet.create({
+- loading: {
+- height: '100%',
+- alignContent: 'center',
+- justifyContent: 'center',
+- paddingBottom: 100,
+- },
+-})
++const styles = StyleSheet.create({ loading: { height: '100%', alignContent: 'center', justifyContent: 'center', paddingBottom: 100 } })
diff --git a/task.md b/task.md
new file mode 100644
index 0000000..3136801
--- /dev/null
+++ b/task.md
@@ -0,0 +1,4 @@
+## Rules & Constraints
+- Do not reformat existing code unless explicitly requested.
+- Maintain existing import styles (newlines, sorting).
+- Keep patches minimal (clean deltas).