about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorhailey <me@haileyok.com>2025-05-02 13:23:39 -0700
committerGitHub <noreply@github.com>2025-05-02 13:23:39 -0700
commit544f7befe0f7d3e61fb03365ec588a2ab3c5a17a (patch)
tree9d6276058aeeb44e77cba2b11bcc9e95cb8b7521 /src
parent46ea3fdbeeab4e31657638955401145683738fbf (diff)
downloadvoidsky-544f7befe0f7d3e61fb03365ec588a2ab3c5a17a.tar.zst
bump it bop it upgrade it (rn 79/expo 53) (#8281)
* basic bumps

* more tweaking

* fix rn patch

* fix crop picker patch

* fix media library patch

* rm unnecessary patch

* fix notifications patch

* update bottomsheet

* Update withAppDelegateReferrer.js

* Delete withNoBundleCompression.js

* rm withNoBundleCompression plugin

* rm findLast shim

* metro package exports is enabled by default

* update react/react-dom/react-compiler

* fix reanimated issue

* vendor expo-ized emoji popup

* fix types

* hackfix view full thread

* Update EmojiPickerModule.podspec

* more upgrades

* fix multiformats package version

* add baseurl

* bump mmkv

* bumps

* update react-keyed-flatten-children

* bump locale packages

* fix emoji picker dark mode

* rn upgrades

* Revert "bump locale packages"

This reverts commit fc82f0f173032127dd7c18ed0316ae26f53db51d.

* upgrade testing-library

* rm test renderer

* update patch name minors

* rm findNodeHandle from tabbar

* only do scrollview tag thing on ios

* disable package exports

* update expo notifications handler

* memoize emoji picker styles

* fix tests, mock multiformats

* bump some dev deps with RC versions

* completely rearchitect toasts

* rm logs

* layout animation config for composer footer

* disable autolinking for patched libs

* undo lingui changes

* version bump from release candidate to 0.1

* update atproto deps

* rm @did-plc/server

* fix key issue (maybe)

* move URL polyfill to the polyfill file

* fix yarn lock

* upgrade to 53.0.3

* reanimated layout anim bug patch

* workletize a function that wasn't getting autoworkletized anymore (#8309)

* bump to expo 53.0.4

* bump RN to 0.79.2

* fix yarn lock ci

* Revert "completely rearchitect toasts"

This reverts commit 2e2fcaeeed527580a6c485718544b85e8b4f52b9.

* final upgrades

* chore: cleanup yarn lock

* prettier

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/App.native.tsx1
-rw-r--r--src/alf/atoms.ts4
-rw-r--r--src/alf/typography.tsx14
-rw-r--r--src/components/StarterPack/ProfileStarterPacks.tsx3
-rw-r--r--src/components/dms/EmojiPopup.android.tsx29
-rw-r--r--src/components/dms/EmojiPopup.tsx2
-rw-r--r--src/lib/api/index.ts30
-rw-r--r--src/lib/hooks/useDraggableScrollView.ts8
-rw-r--r--src/lib/hooks/useNotificationHandler.ts13
-rw-r--r--src/lib/strings/starter-pack.ts2
-rw-r--r--src/platform/polyfills.ts5
-rw-r--r--src/platform/polyfills.web.ts5
-rw-r--r--src/screens/Profile/Sections/Feed.tsx4
-rw-r--r--src/screens/Profile/Sections/Labels.tsx4
-rw-r--r--src/view/com/composer/Composer.tsx74
-rw-r--r--src/view/com/composer/ComposerReplyTo.tsx4
-rw-r--r--src/view/com/feeds/ProfileFeedgens.tsx4
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx18
-rw-r--r--src/view/com/lists/ProfileLists.tsx4
-rw-r--r--src/view/com/pager/DraggableScrollView.tsx23
-rw-r--r--src/view/com/pager/TabBar.tsx29
-rw-r--r--src/view/com/pager/TabBar.web.tsx8
-rw-r--r--src/view/com/posts/ViewFullThread.tsx7
-rw-r--r--src/view/com/util/Toast.tsx2
-rw-r--r--src/view/com/util/Toast.web.tsx7
-rw-r--r--src/view/com/util/forms/NativeDropdown.web.tsx3
-rw-r--r--src/view/com/util/load-latest/LoadLatestBtn.tsx5
-rw-r--r--src/view/shell/bottom-bar/BottomBarStyles.tsx5
-rw-r--r--src/view/shell/desktop/LeftNav.tsx6
-rw-r--r--src/view/shell/index.web.tsx6
30 files changed, 178 insertions, 151 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 5023c48bb..ea50fdfb9 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -1,4 +1,3 @@
-import 'react-native-url-polyfill/auto'
 import '#/logger/sentry/setup'
 import '#/logger/bitdrift/setup'
 import '#/view/icons'
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index 5a3e6d675..c60af4862 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -951,8 +951,8 @@ export const atoms = {
     userSelect: 'all',
   },
   outline_inset_1: {
-    outlineOffset: '-1px',
-  } as StyleProp<ViewStyle>,
+    outlineOffset: -1,
+  },
 
   /*
    * Text decoration
diff --git a/src/alf/typography.tsx b/src/alf/typography.tsx
index 4f6947e0e..a3bed4e4c 100644
--- a/src/alf/typography.tsx
+++ b/src/alf/typography.tsx
@@ -1,12 +1,13 @@
-import React, {Children} from 'react'
-import {TextProps as RNTextProps} from 'react-native'
-import {StyleProp, TextStyle} from 'react-native'
+import {Children} from 'react'
+import {type TextProps as RNTextProps} from 'react-native'
+import {type StyleProp, type TextStyle} from 'react-native'
 import {UITextView} from 'react-native-uitextview'
 import createEmojiRegex from 'emoji-regex'
+import type React from 'react'
 
 import {isNative} from '#/platform/detection'
 import {isIOS} from '#/platform/detection'
-import {Alf, applyFonts, atoms, flatten} from '#/alf'
+import {type Alf, applyFonts, atoms, flatten} from '#/alf'
 
 /**
  * Util to calculate lineHeight from a text size atom and a leading atom
@@ -110,7 +111,10 @@ export function renderChildrenWithEmoji(
     return child.split(EMOJI).map((stringPart, index) => [
       stringPart,
       emojis[index] ? (
-        <UITextView {...props} style={[props?.style, {fontFamily: 'System'}]}>
+        <UITextView
+          {...props}
+          style={[props?.style, {fontFamily: 'System'}]}
+          key={index}>
           {emojis[index]}
         </UITextView>
       ) : null,
diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx
index d56789506..d7afa37d2 100644
--- a/src/components/StarterPack/ProfileStarterPacks.tsx
+++ b/src/components/StarterPack/ProfileStarterPacks.tsx
@@ -23,6 +23,7 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {type NavigationProp} from '#/lib/routes/types'
 import {parseStarterPackUri} from '#/lib/strings/starter-pack'
 import {logger} from '#/logger'
+import {isIOS} from '#/platform/detection'
 import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs'
 import {List, type ListRef} from '#/view/com/util/List'
 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
@@ -111,7 +112,7 @@ export const ProfileStarterPacks = React.forwardRef<
   }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
 
   useEffect(() => {
-    if (enabled && scrollElRef.current) {
+    if (isIOS && enabled && scrollElRef.current) {
       const nativeTag = findNodeHandle(scrollElRef.current)
       setScrollViewTag(nativeTag)
     }
diff --git a/src/components/dms/EmojiPopup.android.tsx b/src/components/dms/EmojiPopup.android.tsx
index 4b646608b..2205dcdea 100644
--- a/src/components/dms/EmojiPopup.android.tsx
+++ b/src/components/dms/EmojiPopup.android.tsx
@@ -1,15 +1,14 @@
 import {useState} from 'react'
 import {Modal, Pressable, View} from 'react-native'
-// @ts-expect-error internal component, not supposed to be used directly
-// waiting on more customisability: https://github.com/okwasniewski/react-native-emoji-popup/issues/1#issuecomment-2737463753
-import EmojiPopupView from 'react-native-emoji-popup/src/EmojiPopupViewNativeComponent'
+import {SafeAreaView} from 'react-native-safe-area-context'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonIcon} from '#/components/Button'
-import {TimesLarge_Stroke2_Corner0_Rounded} from '#/components/icons/Times'
+import {TimesLarge_Stroke2_Corner0_Rounded as CloseIcon} from '#/components/icons/Times'
 import {Text} from '#/components/Typography'
+import {EmojiPicker} from '../../../modules/expo-emoji-picker'
 
 export function EmojiPopup({
   children,
@@ -34,13 +33,14 @@ export function EmojiPopup({
 
       <Modal
         animationType="slide"
-        transparent={true}
         visible={modalVisible}
-        onRequestClose={() => setModalVisible(false)}>
-        <View style={[a.flex_1, {backgroundColor: t.palette.white}]}>
+        onRequestClose={() => setModalVisible(false)}
+        transparent
+        statusBarTranslucent
+        navigationBarTranslucent>
+        <SafeAreaView style={[a.flex_1, t.atoms.bg]}>
           <View
             style={[
-              t.atoms.bg,
               a.pl_lg,
               a.pr_md,
               a.py_sm,
@@ -61,21 +61,16 @@ export function EmojiPopup({
               variant="ghost"
               color="secondary"
               shape="round">
-              <ButtonIcon icon={TimesLarge_Stroke2_Corner0_Rounded} />
+              <ButtonIcon icon={CloseIcon} />
             </Button>
           </View>
-          <EmojiPopupView
-            onEmojiSelected={({
-              nativeEvent: {emoji},
-            }: {
-              nativeEvent: {emoji: string}
-            }) => {
+          <EmojiPicker
+            onEmojiSelected={emoji => {
               setModalVisible(false)
               onEmojiSelected(emoji)
             }}
-            style={[a.flex_1, a.w_full]}
           />
-        </View>
+        </SafeAreaView>
       </Modal>
     </>
   )
diff --git a/src/components/dms/EmojiPopup.tsx b/src/components/dms/EmojiPopup.tsx
index a8f2f83e7..a988d00b5 100644
--- a/src/components/dms/EmojiPopup.tsx
+++ b/src/components/dms/EmojiPopup.tsx
@@ -1 +1 @@
-export {EmojiPopup} from 'react-native-emoji-popup'
+export {EmojiPicker as EmojiPopup} from '../../../modules/expo-emoji-picker'
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index d1f304d4a..7621fbb4c 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,23 +1,23 @@
 import {
-  $Typed,
-  AppBskyEmbedExternal,
-  AppBskyEmbedImages,
-  AppBskyEmbedRecord,
-  AppBskyEmbedRecordWithMedia,
-  AppBskyEmbedVideo,
-  AppBskyFeedPost,
+  type $Typed,
+  type AppBskyEmbedExternal,
+  type AppBskyEmbedImages,
+  type AppBskyEmbedRecord,
+  type AppBskyEmbedRecordWithMedia,
+  type AppBskyEmbedVideo,
+  type AppBskyFeedPost,
   AtUri,
   BlobRef,
-  BskyAgent,
-  ComAtprotoLabelDefs,
-  ComAtprotoRepoApplyWrites,
-  ComAtprotoRepoStrongRef,
+  type BskyAgent,
+  type ComAtprotoLabelDefs,
+  type ComAtprotoRepoApplyWrites,
+  type ComAtprotoRepoStrongRef,
   RichText,
 } from '@atproto/api'
 import {TID} from '@atproto/common-web'
 import * as dcbor from '@ipld/dag-cbor'
 import {t} from '@lingui/macro'
-import {QueryClient} from '@tanstack/react-query'
+import {type QueryClient} from '@tanstack/react-query'
 import {sha256} from 'js-sha256'
 import {CID} from 'multiformats/cid'
 import * as Hasher from 'multiformats/hashes/hasher'
@@ -35,9 +35,9 @@ import {
   threadgateAllowUISettingToAllowRecordValue,
 } from '#/state/queries/threadgate'
 import {
-  EmbedDraft,
-  PostDraft,
-  ThreadDraft,
+  type EmbedDraft,
+  type PostDraft,
+  type ThreadDraft,
 } from '#/view/com/composer/state/composer'
 import {createGIFDescription} from '../gif-alt-text'
 import {uploadBlob} from './upload-blob'
diff --git a/src/lib/hooks/useDraggableScrollView.ts b/src/lib/hooks/useDraggableScrollView.ts
index 3471d0d06..05fda9a9f 100644
--- a/src/lib/hooks/useDraggableScrollView.ts
+++ b/src/lib/hooks/useDraggableScrollView.ts
@@ -1,6 +1,6 @@
-import {ForwardedRef, useEffect, useMemo, useRef} from 'react'
-import type {ScrollView} from 'react-native'
-import {findNodeHandle, Platform} from 'react-native'
+import {type ForwardedRef, useEffect, useMemo, useRef} from 'react'
+import {type ScrollView} from 'react-native'
+import {Platform} from 'react-native'
 
 import {mergeRefs} from '#/lib/merge-refs'
 
@@ -19,7 +19,7 @@ export function useDraggableScroll<Scrollable extends ScrollView = ScrollView>({
     if (Platform.OS !== 'web' || !ref.current) {
       return
     }
-    const slider = findNodeHandle(ref.current) as unknown as HTMLDivElement
+    const slider = ref.current as unknown as HTMLDivElement
     if (!slider) {
       return
     }
diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts
index b5566f8a6..9c9522aa5 100644
--- a/src/lib/hooks/useNotificationHandler.ts
+++ b/src/lib/hooks/useNotificationHandler.ts
@@ -4,7 +4,7 @@ import {CommonActions, useNavigation} from '@react-navigation/native'
 import {useQueryClient} from '@tanstack/react-query'
 
 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
-import {NavigationProp} from '#/lib/routes/types'
+import {type NavigationProp} from '#/lib/routes/types'
 import {logEvent} from '#/lib/statsig/statsig'
 import {Logger} from '#/logger'
 import {isAndroid} from '#/platform/detection'
@@ -41,10 +41,11 @@ type NotificationPayload =
     }
 
 const DEFAULT_HANDLER_OPTIONS = {
-  shouldShowAlert: false,
+  shouldShowBanner: false,
+  shouldShowList: false,
   shouldPlaySound: false,
   shouldSetBadge: true,
-}
+} satisfies Notifications.NotificationBehavior
 
 // These need to stay outside the hook to persist between account switches
 let storedPayload: NotificationPayload | undefined
@@ -195,11 +196,13 @@ export function useNotificationsHandler() {
           payload.reason === 'chat-message' &&
           payload.recipientDid === currentAccount?.did
         ) {
+          const shouldAlert = payload.convoId !== currentConvoId
           return {
-            shouldShowAlert: payload.convoId !== currentConvoId,
+            shouldShowList: shouldAlert,
+            shouldShowBanner: shouldAlert,
             shouldPlaySound: false,
             shouldSetBadge: false,
-          }
+          } satisfies Notifications.NotificationBehavior
         }
 
         // Any notification other than a chat message should invalidate the unread page
diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts
index ced947b59..01e0256b5 100644
--- a/src/lib/strings/starter-pack.ts
+++ b/src/lib/strings/starter-pack.ts
@@ -1,6 +1,6 @@
 import {AtUri} from '@atproto/api'
 
-import * as bsky from '#/types/bsky'
+import type * as bsky from '#/types/bsky'
 
 export function createStarterPackLinkFromAndroidReferrer(
   referrerQueryString: string,
diff --git a/src/platform/polyfills.ts b/src/platform/polyfills.ts
index 807cc4923..47581eff4 100644
--- a/src/platform/polyfills.ts
+++ b/src/platform/polyfills.ts
@@ -1,10 +1,7 @@
+import 'react-native-url-polyfill/auto'
 import 'fast-text-encoding'
-// @ts-ignore no decl -prf
-import findLast from 'array.prototype.findlast'
 export {}
 
-findLast.shim()
-
 /**
 https://github.com/MaxArt2501/base64-js
 The MIT License (MIT)
diff --git a/src/platform/polyfills.web.ts b/src/platform/polyfills.web.ts
index 462f65a26..7c5a1c00a 100644
--- a/src/platform/polyfills.web.ts
+++ b/src/platform/polyfills.web.ts
@@ -1,9 +1,6 @@
-// @ts-ignore no decl -prf
-import * as findLast from 'array.prototype.findlast'
+import 'array.prototype.findlast/auto'
 /// <reference lib="dom" />
 
-findLast.shim()
-
 // @ts-ignore whatever typescript wants to complain about here, I dont care about -prf
 window.setImmediate = (cb: () => void) => setTimeout(cb, 0)
 
diff --git a/src/screens/Profile/Sections/Feed.tsx b/src/screens/Profile/Sections/Feed.tsx
index 007f9ea8f..e0c3e221f 100644
--- a/src/screens/Profile/Sections/Feed.tsx
+++ b/src/screens/Profile/Sections/Feed.tsx
@@ -5,7 +5,7 @@ import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
 
 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
-import {isNative} from '#/platform/detection'
+import {isIOS, isNative} from '#/platform/detection'
 import {type FeedDescriptor} from '#/state/queries/post-feed'
 import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
 import {truncateAndInvalidate} from '#/state/queries/util'
@@ -67,7 +67,7 @@ export const ProfileFeedSection = React.forwardRef<
   }, [_])
 
   React.useEffect(() => {
-    if (isFocused && scrollElRef.current) {
+    if (isIOS && isFocused && scrollElRef.current) {
       const nativeTag = findNodeHandle(scrollElRef.current)
       setScrollViewTag(nativeTag)
     }
diff --git a/src/screens/Profile/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx
index b7f702f11..c04c047c4 100644
--- a/src/screens/Profile/Sections/Labels.tsx
+++ b/src/screens/Profile/Sections/Labels.tsx
@@ -14,7 +14,7 @@ import {useLingui} from '@lingui/react'
 import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
 import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation'
 import {useScrollHandlers} from '#/lib/ScrollContext'
-import {isNative} from '#/platform/detection'
+import {isIOS, isNative} from '#/platform/detection'
 import {type ListRef} from '#/view/com/util/List'
 import {atoms as a, useTheme} from '#/alf'
 import {Divider} from '#/components/Divider'
@@ -92,7 +92,7 @@ export const ProfileLabelsSection = React.forwardRef<
   }))
 
   React.useEffect(() => {
-    if (isFocused && scrollElRef.current) {
+    if (isIOS && isFocused && scrollElRef.current) {
       const nativeTag = findNodeHandle(scrollElRef.current)
       setScrollViewTag(nativeTag)
     }
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index aa27adb3d..b6d269d28 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -118,7 +118,7 @@ import {LazyQuoteEmbed, QuoteX} from '#/view/com/util/post-embeds/QuoteEmbed'
 import {Text} from '#/view/com/util/text/Text'
 import * as Toast from '#/view/com/util/Toast'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, native, useTheme} from '#/alf'
+import {atoms as a, native, useTheme, web} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
 import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
@@ -1266,39 +1266,41 @@ function ComposerFooter({
         a.justify_between,
       ]}>
       <View style={[a.flex_row, a.align_center]}>
-        {video && video.status !== 'done' ? (
-          <VideoUploadToolbar state={video} />
-        ) : (
-          <ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
-            <SelectPhotoBtn
-              size={images.length}
-              disabled={media?.type === 'images' ? isMaxImages : !!media}
-              onAdd={onImageAdd}
-            />
-            <SelectVideoBtn
-              onSelectVideo={asset => onSelectVideo(post.id, asset)}
-              disabled={!!media}
-              setError={onError}
-            />
-            <OpenCameraBtn
-              disabled={media?.type === 'images' ? isMaxImages : !!media}
-              onAdd={onImageAdd}
-            />
-            <SelectGifBtn onSelectGif={onSelectGif} disabled={!!media} />
-            {!isMobile ? (
-              <Button
-                onPress={onEmojiButtonPress}
-                style={a.p_sm}
-                label={_(msg`Open emoji picker`)}
-                accessibilityHint={_(msg`Opens emoji picker`)}
-                variant="ghost"
-                shape="round"
-                color="primary">
-                <EmojiSmile size="lg" />
-              </Button>
-            ) : null}
-          </ToolbarWrapper>
-        )}
+        <LayoutAnimationConfig skipEntering skipExiting>
+          {video && video.status !== 'done' ? (
+            <VideoUploadToolbar state={video} />
+          ) : (
+            <ToolbarWrapper style={[a.flex_row, a.align_center, a.gap_xs]}>
+              <SelectPhotoBtn
+                size={images.length}
+                disabled={media?.type === 'images' ? isMaxImages : !!media}
+                onAdd={onImageAdd}
+              />
+              <SelectVideoBtn
+                onSelectVideo={asset => onSelectVideo(post.id, asset)}
+                disabled={!!media}
+                setError={onError}
+              />
+              <OpenCameraBtn
+                disabled={media?.type === 'images' ? isMaxImages : !!media}
+                onAdd={onImageAdd}
+              />
+              <SelectGifBtn onSelectGif={onSelectGif} disabled={!!media} />
+              {!isMobile ? (
+                <Button
+                  onPress={onEmojiButtonPress}
+                  style={a.p_sm}
+                  label={_(msg`Open emoji picker`)}
+                  accessibilityHint={_(msg`Opens emoji picker`)}
+                  variant="ghost"
+                  shape="round"
+                  color="primary">
+                  <EmojiSmile size="lg" />
+                </Button>
+              ) : null}
+            </ToolbarWrapper>
+          )}
+        </LayoutAnimationConfig>
       </View>
       <View style={[a.flex_row, a.align_center, a.justify_between]}>
         {showAddButton && (
@@ -1515,10 +1517,10 @@ const styles = StyleSheet.create({
     paddingVertical: 6,
     marginLeft: 12,
   },
-  stickyFooterWeb: {
+  stickyFooterWeb: web({
     position: 'sticky',
     bottom: 0,
-  },
+  }),
   errorLine: {
     flexDirection: 'row',
     alignItems: 'center',
diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx
index 6f1cc4f84..bafac18f5 100644
--- a/src/view/com/composer/ComposerReplyTo.tsx
+++ b/src/view/com/composer/ComposerReplyTo.tsx
@@ -15,7 +15,7 @@ import {sanitizeHandle} from '#/lib/strings/handles'
 import {type ComposerOptsPostRef} from '#/state/shell/composer'
 import {MaybeQuoteEmbed} from '#/view/com/util/post-embeds/QuoteEmbed'
 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
-import {atoms as a, useTheme} from '#/alf'
+import {atoms as a, useTheme, web} from '#/alf'
 import {Text} from '#/components/Typography'
 import {useSimpleVerificationState} from '#/components/verification'
 import {VerificationCheck} from '#/components/verification/VerificationCheck'
@@ -76,7 +76,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
         a.mx_lg,
         a.border_b,
         t.atoms.border_contrast_medium,
-        a.user_select_text,
+        web(a.user_select_text),
       ]}
       onPress={onPress}
       accessibilityRole="button"
diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx
index 2bf95de48..76b57d0ee 100644
--- a/src/view/com/feeds/ProfileFeedgens.tsx
+++ b/src/view/com/feeds/ProfileFeedgens.tsx
@@ -12,7 +12,7 @@ import {useQueryClient} from '@tanstack/react-query'
 
 import {cleanError} from '#/lib/strings/errors'
 import {logger} from '#/logger'
-import {isNative, isWeb} from '#/platform/detection'
+import {isIOS, isNative, isWeb} from '#/platform/detection'
 import {usePreferencesQuery} from '#/state/queries/preferences'
 import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
 import {EmptyState} from '#/view/com/util/EmptyState'
@@ -175,7 +175,7 @@ export const ProfileFeedgens = React.forwardRef<
   )
 
   React.useEffect(() => {
-    if (enabled && scrollElRef.current) {
+    if (isIOS && enabled && scrollElRef.current) {
       const nativeTag = findNodeHandle(scrollElRef.current)
       setScrollViewTag(nativeTag)
     }
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 41a54eba6..bb3c39f59 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -510,20 +510,22 @@ function LightboxImage({
           // This is a bug in Reanimated, but for now we'll work around it like this.
           dismissSwipeTranslateY.set(1)
         }
-        dismissSwipeTranslateY.set(() =>
-          withDecay({
+        dismissSwipeTranslateY.set(() => {
+          'worklet'
+          return withDecay({
             velocity: e.velocityY,
             velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow.
             deceleration: 1, // Danger! This relies on the reaction below stopping it.
-          }),
-        )
+          })
+        })
       } else {
-        dismissSwipeTranslateY.set(() =>
-          withSpring(0, {
+        dismissSwipeTranslateY.set(() => {
+          'worklet'
+          return withSpring(0, {
             stiffness: 700,
             damping: 50,
-          }),
-        )
+          })
+        })
       }
     })
 
diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx
index 437648c62..e264bd6c6 100644
--- a/src/view/com/lists/ProfileLists.tsx
+++ b/src/view/com/lists/ProfileLists.tsx
@@ -12,7 +12,7 @@ import {useQueryClient} from '@tanstack/react-query'
 
 import {cleanError} from '#/lib/strings/errors'
 import {logger} from '#/logger'
-import {isNative, isWeb} from '#/platform/detection'
+import {isIOS, isNative, isWeb} from '#/platform/detection'
 import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists'
 import {EmptyState} from '#/view/com/util/EmptyState'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
@@ -171,7 +171,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
     )
 
     React.useEffect(() => {
-      if (enabled && scrollElRef.current) {
+      if (isIOS && enabled && scrollElRef.current) {
         const nativeTag = findNodeHandle(scrollElRef.current)
         setScrollViewTag(nativeTag)
       }
diff --git a/src/view/com/pager/DraggableScrollView.tsx b/src/view/com/pager/DraggableScrollView.tsx
index fc06b72e8..c98e34054 100644
--- a/src/view/com/pager/DraggableScrollView.tsx
+++ b/src/view/com/pager/DraggableScrollView.tsx
@@ -1,16 +1,25 @@
-import React, {ComponentProps} from 'react'
+import {type ComponentPropsWithRef} from 'react'
 import {ScrollView} from 'react-native'
 
 import {useDraggableScroll} from '#/lib/hooks/useDraggableScrollView'
+import {atoms as a, web} from '#/alf'
 
-export const DraggableScrollView = React.forwardRef<
-  ScrollView,
-  ComponentProps<typeof ScrollView>
->(function DraggableScrollView(props, ref) {
+export function DraggableScrollView({
+  ref,
+  style,
+  ...props
+}: ComponentPropsWithRef<typeof ScrollView>) {
   const {refs} = useDraggableScroll<ScrollView>({
     outerRef: ref,
     cursor: 'grab', // optional, default
   })
 
-  return <ScrollView ref={refs} horizontal {...props} />
-})
+  return (
+    <ScrollView
+      ref={refs}
+      style={[style, web(a.user_select_none)]}
+      horizontal
+      {...props}
+    />
+  )
+}
diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx
index 49f8ead80..04803fa9b 100644
--- a/src/view/com/pager/TabBar.tsx
+++ b/src/view/com/pager/TabBar.tsx
@@ -1,11 +1,16 @@
 import {useCallback} from 'react'
-import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native'
+import {
+  type LayoutChangeEvent,
+  ScrollView,
+  StyleSheet,
+  View,
+} from 'react-native'
 import Animated, {
   interpolate,
   runOnJS,
   runOnUI,
   scrollTo,
-  SharedValue,
+  type SharedValue,
   useAnimatedReaction,
   useAnimatedRef,
   useAnimatedStyle,
@@ -267,15 +272,27 @@ export function TabBar({
         {
           translateX: interpolate(
             dragProgress.get(),
-            layoutsValue.map((l, i) => i),
-            layoutsValue.map(l => l.x + l.width / 2 - contentSize.get() / 2),
+            layoutsValue.map((l, i) => {
+              'worklet'
+              return i
+            }),
+            layoutsValue.map(l => {
+              'worklet'
+              return l.x + l.width / 2 - contentSize.get() / 2
+            }),
           ),
         },
         {
           scaleX: interpolate(
             dragProgress.get(),
-            textLayoutsValue.map((l, i) => i),
-            textLayoutsValue.map((l, i) => getScaleX(i)),
+            textLayoutsValue.map((l, i) => {
+              'worklet'
+              return i
+            }),
+            textLayoutsValue.map((l, i) => {
+              'worklet'
+              return getScaleX(i)
+            }),
           ),
         },
       ],
diff --git a/src/view/com/pager/TabBar.web.tsx b/src/view/com/pager/TabBar.web.tsx
index d44b7b60c..8afea0019 100644
--- a/src/view/com/pager/TabBar.web.tsx
+++ b/src/view/com/pager/TabBar.web.tsx
@@ -1,7 +1,7 @@
 import {useCallback, useEffect, useRef} from 'react'
-import {ScrollView, StyleSheet, View} from 'react-native'
+import {type ScrollView, StyleSheet, View} from 'react-native'
 
-import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
 import {Text} from '#/components/Typography'
 import {PressableWithHover} from '../util/PressableWithHover'
 import {DraggableScrollView} from './DraggableScrollView'
@@ -161,7 +161,7 @@ const desktopStyles = StyleSheet.create({
   },
   itemInner: {
     alignItems: 'center',
-    overflowX: 'hidden',
+    ...web({overflowX: 'hidden'}),
   },
   itemText: {
     textAlign: 'center',
@@ -204,7 +204,7 @@ const mobileStyles = StyleSheet.create({
   itemInner: {
     flexGrow: 1,
     alignItems: 'center',
-    overflowX: 'hidden',
+    ...web({overflowX: 'hidden'}),
   },
   itemText: {
     textAlign: 'center',
diff --git a/src/view/com/posts/ViewFullThread.tsx b/src/view/com/posts/ViewFullThread.tsx
index 0b347f22c..0f083c330 100644
--- a/src/view/com/posts/ViewFullThread.tsx
+++ b/src/view/com/posts/ViewFullThread.tsx
@@ -2,7 +2,8 @@ import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import Svg, {Circle, Line} from 'react-native-svg'
 import {AtUri} from '@atproto/api'
-import {Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 import {usePalette} from '#/lib/hooks/usePalette'
 import {makeProfileLink} from '#/lib/routes/links'
@@ -22,6 +23,7 @@ export function ViewFullThread({uri}: {uri: string}) {
     const urip = new AtUri(uri)
     return makeProfileLink({did: urip.hostname, handle: ''}, 'post', urip.rkey)
   }, [uri])
+  const {_} = useLingui()
 
   return (
     <Link
@@ -53,7 +55,8 @@ export function ViewFullThread({uri}: {uri: string}) {
       </View>
 
       <Text type="md" style={[pal.link, {paddingTop: 18, paddingBottom: 4}]}>
-        <Trans>View full thread</Trans>
+        {/* HACKFIX: Trans isn't working after SDK 53 upgrade -sfn */}
+        {_(msg`View full thread`)}
       </Text>
     </Link>
   )
diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx
index b18ea4b4f..56c6780ad 100644
--- a/src/view/com/util/Toast.tsx
+++ b/src/view/com/util/Toast.tsx
@@ -19,7 +19,7 @@ import RootSiblings from 'react-native-root-siblings'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {
   FontAwesomeIcon,
-  Props as FontAwesomeProps,
+  type Props as FontAwesomeProps,
 } from '@fortawesome/react-native-fontawesome'
 
 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
diff --git a/src/view/com/util/Toast.web.tsx b/src/view/com/util/Toast.web.tsx
index 96798e61c..7e22fcefc 100644
--- a/src/view/com/util/Toast.web.tsx
+++ b/src/view/com/util/Toast.web.tsx
@@ -2,13 +2,14 @@
  * Note: the dataSet properties are used to leverage custom CSS in public/index.html
  */
 
-import React, {useEffect, useState} from 'react'
+import {useEffect, useState} from 'react'
 import {Pressable, StyleSheet, Text, View} from 'react-native'
 import {
   FontAwesomeIcon,
-  FontAwesomeIconStyle,
-  Props as FontAwesomeProps,
+  type FontAwesomeIconStyle,
+  type Props as FontAwesomeProps,
 } from '@fortawesome/react-native-fontawesome'
+import type React from 'react'
 
 const DURATION = 3500
 
diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx
index 9b4a84e05..cab7bac51 100644
--- a/src/view/com/util/forms/NativeDropdown.web.tsx
+++ b/src/view/com/util/forms/NativeDropdown.web.tsx
@@ -239,7 +239,6 @@ const getKey = (label: string, index: number, id?: string) => {
   return `${label}_${index}`
 }
 
-// @ts-expect-error - web only styles. the only style that should be broken here is `outline`
 const styles = StyleSheet.create({
   separator: {
     height: 1,
@@ -264,7 +263,6 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between',
     alignItems: 'center',
     columnGap: 20,
-    // @ts-ignore -web
     cursor: 'pointer',
     paddingTop: 8,
     paddingBottom: 8,
@@ -273,6 +271,7 @@ const styles = StyleSheet.create({
     borderRadius: 8,
     fontFamily:
       '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
+    // @ts-expect-error web only
     outline: 0,
     border: 0,
   },
diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx
index 89e5784b7..f991991b0 100644
--- a/src/view/com/util/load-latest/LoadLatestBtn.tsx
+++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx
@@ -12,9 +12,8 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {clamp} from '#/lib/numbers'
 import {useGate} from '#/lib/statsig/statsig'
 import {colors} from '#/lib/styles'
-import {isWeb} from '#/platform/detection'
 import {useSession} from '#/state/session'
-import {useLayoutBreakpoints} from '#/alf'
+import {atoms as a, useLayoutBreakpoints} from '#/alf'
 
 export function LoadLatestBtn({
   onPress,
@@ -80,7 +79,7 @@ export function LoadLatestBtn({
 const styles = StyleSheet.create({
   loadLatest: {
     zIndex: 20,
-    position: isWeb ? 'fixed' : 'absolute',
+    ...a.fixed,
     left: 18,
     borderWidth: StyleSheet.hairlineWidth,
     width: 52,
diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx
index 62c677ced..c5f31c94e 100644
--- a/src/view/shell/bottom-bar/BottomBarStyles.tsx
+++ b/src/view/shell/bottom-bar/BottomBarStyles.tsx
@@ -1,6 +1,7 @@
 import {StyleSheet} from 'react-native'
 
 import {colors} from '#/lib/styles'
+import {atoms as a} from '#/alf'
 
 export const styles = StyleSheet.create({
   bottomBar: {
@@ -13,9 +14,7 @@ export const styles = StyleSheet.create({
     paddingLeft: 5,
     paddingRight: 10,
   },
-  bottomBarWeb: {
-    position: 'fixed',
-  },
+  bottomBarWeb: a.fixed,
   ctrl: {
     flex: 1,
     paddingTop: 13,
diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx
index 7d7c0ac8d..0688fb5dc 100644
--- a/src/view/shell/desktop/LeftNav.tsx
+++ b/src/view/shell/desktop/LeftNav.tsx
@@ -32,7 +32,7 @@ import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 import {PressableWithHover} from '#/view/com/util/PressableWithHover'
 import {UserAvatar} from '#/view/com/util/UserAvatar'
 import {NavSignupCard} from '#/view/shell/NavSignupCard'
-import {atoms as a, tokens, useLayoutBreakpoints, useTheme} from '#/alf'
+import {atoms as a, tokens, useLayoutBreakpoints, useTheme, web} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {type DialogControlProps} from '#/components/Dialog'
 import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
@@ -718,7 +718,7 @@ export function DesktopLeftNav() {
 
 const styles = StyleSheet.create({
   leftNav: {
-    position: 'fixed',
+    ...a.fixed,
     top: 0,
     paddingTop: 10,
     paddingBottom: 10,
@@ -736,7 +736,7 @@ const styles = StyleSheet.create({
     height: '100%',
     width: 86,
     alignItems: 'center',
-    overflowX: 'hidden',
+    ...web({overflowX: 'hidden'}),
   },
   backBtn: {
     position: 'absolute',
diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx
index e194a49de..898ff8fa8 100644
--- a/src/view/shell/index.web.tsx
+++ b/src/view/shell/index.web.tsx
@@ -8,7 +8,7 @@ import {RemoveScrollBar} from 'react-remove-scroll-bar'
 import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
 import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
-import {NavigationProp} from '#/lib/routes/types'
+import {type NavigationProp} from '#/lib/routes/types'
 import {colors} from '#/lib/styles'
 import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
 import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut'
@@ -130,7 +130,7 @@ const styles = StyleSheet.create({
     backgroundColor: colors.black, // TODO
   },
   drawerMask: {
-    position: 'fixed',
+    ...a.fixed,
     width: '100%',
     height: '100%',
     top: 0,
@@ -138,7 +138,7 @@ const styles = StyleSheet.create({
   },
   drawerContainer: {
     display: 'flex',
-    position: 'fixed',
+    ...a.fixed,
     top: 0,
     left: 0,
     height: '100%',