about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/auth/create/CreateAccount.tsx6
-rw-r--r--src/view/com/auth/create/Step2.tsx139
-rw-r--r--src/view/com/auth/create/state.ts5
-rw-r--r--src/view/com/feeds/FeedPage.tsx2
-rw-r--r--src/view/com/home/HomeHeader.tsx71
-rw-r--r--src/view/com/home/HomeHeaderLayout.tsx1
-rw-r--r--src/view/com/home/HomeHeaderLayout.web.tsx50
-rw-r--r--src/view/com/home/HomeHeaderLayoutMobile.tsx (renamed from src/view/com/pager/FeedsTabBarMobile.tsx)88
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx1
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx11
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx1
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx1
-rw-r--r--src/view/com/pager/FeedsTabBar.tsx1
-rw-r--r--src/view/com/pager/FeedsTabBar.web.tsx138
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx4
-rw-r--r--src/view/com/util/forms/PostDropdownBtn.tsx31
-rw-r--r--src/view/screens/Home.tsx4
-rw-r--r--src/view/screens/PreferencesFollowingFeed.tsx (renamed from src/view/screens/PreferencesHomeFeed.tsx)10
-rw-r--r--src/view/screens/Settings/index.tsx8
-rw-r--r--src/view/screens/Storybook/Palette.tsx103
20 files changed, 327 insertions, 348 deletions
diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx
index 8aefffa6d..d193802fe 100644
--- a/src/view/com/auth/create/CreateAccount.tsx
+++ b/src/view/com/auth/create/CreateAccount.tsx
@@ -23,7 +23,7 @@ import {Step3} from './Step3'
 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {TextLink} from '../../util/Link'
 import {getAgent} from 'state/session'
-import {createFullHandle} from 'lib/strings/handles'
+import {createFullHandle, validateHandle} from 'lib/strings/handles'
 
 export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
   const {screen} = useAnalytics()
@@ -78,6 +78,10 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
     }
 
     if (uiState.step === 2) {
+      if (!validateHandle(uiState.handle, uiState.userDomain).overall) {
+        return
+      }
+
       uiDispatch({type: 'set-processing', value: true})
       try {
         const res = await getAgent().resolveHandle({
diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx
index 87d414bb9..a38920309 100644
--- a/src/view/com/auth/create/Step2.tsx
+++ b/src/view/com/auth/create/Step2.tsx
@@ -1,15 +1,22 @@
 import React from 'react'
-import {StyleSheet, View} from 'react-native'
+import {View} from 'react-native'
 import {CreateAccountState, CreateAccountDispatch} from './state'
 import {Text} from 'view/com/util/text/Text'
 import {StepHeader} from './StepHeader'
 import {s} from 'lib/styles'
 import {TextInput} from '../util/TextInput'
-import {createFullHandle} from 'lib/strings/handles'
+import {
+  createFullHandle,
+  IsValidHandle,
+  validateHandle,
+} from 'lib/strings/handles'
 import {usePalette} from 'lib/hooks/usePalette'
-import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
+import {atoms as a, useTheme} from '#/alf'
+import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
+import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
+import {useFocusEffect} from '@react-navigation/native'
 
 /** STEP 3: Your user handle
  * @field User handle
@@ -23,41 +30,111 @@ export function Step2({
 }) {
   const pal = usePalette('default')
   const {_} = useLingui()
+  const t = useTheme()
+
+  const [validCheck, setValidCheck] = React.useState<IsValidHandle>({
+    handleChars: false,
+    frontLength: false,
+    totalLength: true,
+    overall: false,
+  })
+
+  useFocusEffect(
+    React.useCallback(() => {
+      setValidCheck(validateHandle(uiState.handle, uiState.userDomain))
+
+      // Disabling this, because we only want to run this when we focus the screen
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, []),
+  )
+
+  const onHandleChange = React.useCallback(
+    (value: string) => {
+      if (uiState.error) {
+        uiDispatch({type: 'set-error', value: ''})
+      }
+
+      setValidCheck(validateHandle(value, uiState.userDomain))
+      uiDispatch({type: 'set-handle', value})
+    },
+    [uiDispatch, uiState.error, uiState.userDomain],
+  )
+
   return (
     <View>
       <StepHeader uiState={uiState} title={_(msg`Your user handle`)} />
-      {uiState.error ? (
-        <ErrorMessage message={uiState.error} style={styles.error} />
-      ) : undefined}
       <View style={s.pb10}>
-        <TextInput
-          testID="handleInput"
-          icon="at"
-          placeholder="e.g. alice"
-          value={uiState.handle}
-          editable
-          autoFocus
-          autoComplete="off"
-          autoCorrect={false}
-          onChange={value => uiDispatch({type: 'set-handle', value})}
-          // TODO: Add explicit text label
-          accessibilityLabel={_(msg`User handle`)}
-          accessibilityHint={_(msg`Input your user handle`)}
-        />
-        <Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
-          <Trans>Your full handle will be</Trans>{' '}
-          <Text type="lg-bold" style={pal.text}>
-            @{createFullHandle(uiState.handle, uiState.userDomain)}
+        <View style={s.mb20}>
+          <TextInput
+            testID="handleInput"
+            icon="at"
+            placeholder="e.g. alice"
+            value={uiState.handle}
+            editable
+            autoFocus
+            autoComplete="off"
+            autoCorrect={false}
+            onChange={onHandleChange}
+            // TODO: Add explicit text label
+            accessibilityLabel={_(msg`User handle`)}
+            accessibilityHint={_(msg`Input your user handle`)}
+          />
+          <Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
+            <Trans>Your full handle will be</Trans>{' '}
+            <Text type="lg-bold" style={pal.text}>
+              @{createFullHandle(uiState.handle, uiState.userDomain)}
+            </Text>
           </Text>
-        </Text>
+        </View>
+        <View
+          style={[
+            a.w_full,
+            a.rounded_sm,
+            a.border,
+            a.p_md,
+            a.gap_sm,
+            t.atoms.border_contrast_low,
+          ]}>
+          {uiState.error ? (
+            <View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
+              <IsValidIcon valid={false} />
+              <Text style={[t.atoms.text, a.text_md, a.flex]}>
+                {uiState.error}
+              </Text>
+            </View>
+          ) : undefined}
+          <View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
+            <IsValidIcon valid={validCheck.handleChars} />
+            <Text style={[t.atoms.text, a.text_md, a.flex]}>
+              <Trans>May only contain letters and numbers</Trans>
+            </Text>
+          </View>
+          <View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
+            <IsValidIcon
+              valid={validCheck.frontLength && validCheck.totalLength}
+            />
+            {!validCheck.totalLength ? (
+              <Text style={[t.atoms.text]}>
+                <Trans>May not be longer than 253 characters</Trans>
+              </Text>
+            ) : (
+              <Text style={[t.atoms.text, a.text_md]}>
+                <Trans>Must be at least 3 characters</Trans>
+              </Text>
+            )}
+          </View>
+        </View>
       </View>
     </View>
   )
 }
 
-const styles = StyleSheet.create({
-  error: {
-    borderRadius: 6,
-    marginBottom: 10,
-  },
-})
+function IsValidIcon({valid}: {valid: boolean}) {
+  const t = useTheme()
+
+  if (!valid) {
+    return <Check size="md" style={{color: t.palette.negative_500}} />
+  }
+
+  return <Times size="md" style={{color: t.palette.positive_700}} />
+}
diff --git a/src/view/com/auth/create/state.ts b/src/view/com/auth/create/state.ts
index 68cfaceec..7a727ec0b 100644
--- a/src/view/com/auth/create/state.ts
+++ b/src/view/com/auth/create/state.ts
@@ -8,7 +8,7 @@ import {msg} from '@lingui/macro'
 import * as EmailValidator from 'email-validator'
 import {getAge} from 'lib/strings/time'
 import {logger} from '#/logger'
-import {createFullHandle} from '#/lib/strings/handles'
+import {createFullHandle, validateHandle} from '#/lib/strings/handles'
 import {cleanError} from '#/lib/strings/errors'
 import {useOnboardingDispatch} from '#/state/shell/onboarding'
 import {useSessionApi} from '#/state/session'
@@ -282,7 +282,8 @@ function compute(state: CreateAccountState): CreateAccountState {
       !!state.email &&
       !!state.password
   } else if (state.step === 2) {
-    canNext = !!state.handle
+    canNext =
+      !!state.handle && validateHandle(state.handle, state.userDomain).overall
   } else if (state.step === 3) {
     // Step 3 will automatically redirect as soon as the captcha completes
     canNext = false
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx
index d8da569b1..60814e837 100644
--- a/src/view/com/feeds/FeedPage.tsx
+++ b/src/view/com/feeds/FeedPage.tsx
@@ -138,7 +138,7 @@ export function FeedPage({
           {hasSession && (
             <TextLink
               type="title-lg"
-              href="/settings/home-feed"
+              href="/settings/following-feed"
               style={{fontWeight: 'bold'}}
               accessibilityLabel={_(msg`Feed Preferences`)}
               accessibilityHint=""
diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx
new file mode 100644
index 000000000..5ffa31f39
--- /dev/null
+++ b/src/view/com/home/HomeHeader.tsx
@@ -0,0 +1,71 @@
+import React from 'react'
+import {RenderTabBarFnProps} from 'view/com/pager/Pager'
+import {HomeHeaderLayout} from './HomeHeaderLayout'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {usePinnedFeedsInfos} from '#/state/queries/feed'
+import {useNavigation} from '@react-navigation/native'
+import {NavigationProp} from 'lib/routes/types'
+import {isWeb} from 'platform/detection'
+import {TabBar} from '../pager/TabBar'
+import {usePalette} from '#/lib/hooks/usePalette'
+
+export function HomeHeader(
+  props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
+) {
+  const {isDesktop} = useWebMediaQueries()
+  if (isDesktop) {
+    return null
+  }
+  return <HomeHeaderInner {...props} />
+}
+
+export function HomeHeaderInner(
+  props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
+) {
+  const navigation = useNavigation<NavigationProp>()
+  const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
+  const pal = usePalette('default')
+
+  const items = React.useMemo(() => {
+    const pinnedNames = feeds.map(f => f.displayName)
+
+    if (!hasPinnedCustom) {
+      return pinnedNames.concat('Feeds ✨')
+    }
+    return pinnedNames
+  }, [hasPinnedCustom, feeds])
+
+  const onPressFeedsLink = React.useCallback(() => {
+    if (isWeb) {
+      navigation.navigate('Feeds')
+    } else {
+      navigation.navigate('FeedsTab')
+      navigation.popToTop()
+    }
+  }, [navigation])
+
+  const onSelect = React.useCallback(
+    (index: number) => {
+      if (!hasPinnedCustom && index === items.length - 1) {
+        onPressFeedsLink()
+      } else if (props.onSelect) {
+        props.onSelect(index)
+      }
+    },
+    [items.length, onPressFeedsLink, props, hasPinnedCustom],
+  )
+
+  return (
+    <HomeHeaderLayout>
+      <TabBar
+        key={items.join(',')}
+        onPressSelected={props.onPressSelected}
+        selectedPage={props.selectedPage}
+        onSelect={onSelect}
+        testID={props.testID}
+        items={items}
+        indicatorColor={pal.colors.link}
+      />
+    </HomeHeaderLayout>
+  )
+}
diff --git a/src/view/com/home/HomeHeaderLayout.tsx b/src/view/com/home/HomeHeaderLayout.tsx
new file mode 100644
index 000000000..70bf064d4
--- /dev/null
+++ b/src/view/com/home/HomeHeaderLayout.tsx
@@ -0,0 +1 @@
+export {HomeHeaderLayoutMobile as HomeHeaderLayout} from './HomeHeaderLayoutMobile'
diff --git a/src/view/com/home/HomeHeaderLayout.web.tsx b/src/view/com/home/HomeHeaderLayout.web.tsx
new file mode 100644
index 000000000..47cb00235
--- /dev/null
+++ b/src/view/com/home/HomeHeaderLayout.web.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import {StyleSheet} from 'react-native'
+import Animated from 'react-native-reanimated'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
+import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
+import {useShellLayout} from '#/state/shell/shell-layout'
+
+export function HomeHeaderLayout({children}: {children: React.ReactNode}) {
+  const {isMobile} = useWebMediaQueries()
+  if (isMobile) {
+    return <HomeHeaderLayoutMobile>{children}</HomeHeaderLayoutMobile>
+  } else {
+    return <HomeHeaderLayoutTablet>{children}</HomeHeaderLayoutTablet>
+  }
+}
+
+function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) {
+  const pal = usePalette('default')
+  const {headerMinimalShellTransform} = useMinimalShellMode()
+  const {headerHeight} = useShellLayout()
+
+  return (
+    // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
+    <Animated.View
+      style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
+      onLayout={e => {
+        headerHeight.value = e.nativeEvent.layout.height
+      }}>
+      {children}
+    </Animated.View>
+  )
+}
+
+const styles = StyleSheet.create({
+  tabBar: {
+    // @ts-ignore Web only
+    position: 'sticky',
+    zIndex: 1,
+    // @ts-ignore Web only -prf
+    left: 'calc(50% - 300px)',
+    width: 600,
+    top: 0,
+    flexDirection: 'row',
+    alignItems: 'center',
+    borderLeftWidth: 1,
+    borderRightWidth: 1,
+  },
+})
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx
index 4eba241ae..6c4b911f0 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx
@@ -1,7 +1,5 @@
 import React from 'react'
 import {StyleSheet, TouchableOpacity, View} from 'react-native'
-import {TabBar} from 'view/com/pager/TabBar'
-import {RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {usePalette} from 'lib/hooks/usePalette'
 import {Link} from '../util/Link'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@@ -13,11 +11,7 @@ import {useLingui} from '@lingui/react'
 import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
 import {useSetDrawerOpen} from '#/state/shell/drawer-open'
 import {useShellLayout} from '#/state/shell/shell-layout'
-import {useSession} from '#/state/session'
-import {usePinnedFeedsInfos} from '#/state/queries/feed'
 import {isWeb} from 'platform/detection'
-import {useNavigation} from '@react-navigation/native'
-import {NavigationProp} from 'lib/routes/types'
 import {Logo} from '#/view/icons/Logo'
 
 import {IS_DEV} from '#/env'
@@ -25,49 +19,17 @@ import {atoms} from '#/alf'
 import {Link as Link2} from '#/components/Link'
 import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
 
-export function FeedsTabBar(
-  props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
-) {
+export function HomeHeaderLayoutMobile({
+  children,
+}: {
+  children: React.ReactNode
+}) {
   const pal = usePalette('default')
-  const {hasSession} = useSession()
   const {_} = useLingui()
   const setDrawerOpen = useSetDrawerOpen()
-  const navigation = useNavigation<NavigationProp>()
-  const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
   const {headerHeight} = useShellLayout()
   const {headerMinimalShellTransform} = useMinimalShellMode()
 
-  const items = React.useMemo(() => {
-    if (!hasSession) return []
-
-    const pinnedNames = feeds.map(f => f.displayName)
-
-    if (!hasPinnedCustom) {
-      return pinnedNames.concat('Feeds ✨')
-    }
-    return pinnedNames
-  }, [hasSession, hasPinnedCustom, feeds])
-
-  const onPressFeedsLink = React.useCallback(() => {
-    if (isWeb) {
-      navigation.navigate('Feeds')
-    } else {
-      navigation.navigate('FeedsTab')
-      navigation.popToTop()
-    }
-  }, [navigation])
-
-  const onSelect = React.useCallback(
-    (index: number) => {
-      if (hasSession && !hasPinnedCustom && index === items.length - 1) {
-        onPressFeedsLink()
-      } else if (props.onSelect) {
-        props.onSelect(index)
-      }
-    },
-    [items.length, onPressFeedsLink, props, hasSession, hasPinnedCustom],
-  )
-
   const onPressAvi = React.useCallback(() => {
     setDrawerOpen(true)
   }, [setDrawerOpen])
@@ -113,35 +75,21 @@ export function FeedsTabBar(
               <ColorPalette size="md" />
             </Link2>
           )}
-
-          {hasSession && (
-            <Link
-              testID="viewHeaderHomeFeedPrefsBtn"
-              href="/settings/home-feed"
-              hitSlop={HITSLOP_10}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Home Feed Preferences`)}
-              accessibilityHint="">
-              <FontAwesomeIcon
-                icon="sliders"
-                style={pal.textLight as FontAwesomeIconStyle}
-              />
-            </Link>
-          )}
+          <Link
+            testID="viewHeaderHomeFeedPrefsBtn"
+            href="/settings/following-feed"
+            hitSlop={HITSLOP_10}
+            accessibilityRole="button"
+            accessibilityLabel={_(msg`Following Feed Preferences`)}
+            accessibilityHint="">
+            <FontAwesomeIcon
+              icon="sliders"
+              style={pal.textLight as FontAwesomeIconStyle}
+            />
+          </Link>
         </View>
       </View>
-
-      {items.length > 0 && (
-        <TabBar
-          key={items.join(',')}
-          onPressSelected={props.onPressSelected}
-          selectedPage={props.selectedPage}
-          onSelect={onSelect}
-          testID={props.testID}
-          items={items}
-          indicatorColor={pal.colors.link}
-        />
-      )}
+      {children}
     </Animated.View>
   )
 }
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
index 003ad61ba..414f98a61 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -37,6 +37,7 @@ type Props = {
   onTap: () => void
   onZoom: (isZoomed: boolean) => void
   isScrollViewBeingDragged: boolean
+  showControls: boolean
 }
 const ImageItem = ({
   imageSrc,
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
index cf4ba71df..383490f4f 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -37,11 +37,18 @@ type Props = {
   onTap: () => void
   onZoom: (scaled: boolean) => void
   isScrollViewBeingDragged: boolean
+  showControls: boolean
 }
 
 const AnimatedImage = Animated.createAnimatedComponent(Image)
 
-const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
+const ImageItem = ({
+  imageSrc,
+  onTap,
+  onZoom,
+  onRequestClose,
+  showControls,
+}: Props) => {
   const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
   const translationY = useSharedValue(0)
   const [loaded, setLoaded] = useState(false)
@@ -144,7 +151,7 @@ const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
           accessibilityLabel={imageSrc.alt}
           accessibilityHint=""
           onLoad={() => setLoaded(true)}
-          enableLiveTextInteraction={!scaled}
+          enableLiveTextInteraction={showControls && !scaled}
         />
       </Animated.ScrollView>
     </GestureDetector>
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
index 16688b820..08b99bf9e 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
@@ -10,6 +10,7 @@ type Props = {
   onTap: () => void
   onZoom: (scaled: boolean) => void
   isScrollViewBeingDragged: boolean
+  showControls: boolean
 }
 
 const ImageItem = (_props: Props) => {
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index b6835793d..ff8fdb86d 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -122,6 +122,7 @@ function ImageViewing({
                 imageSrc={imageSrc}
                 onRequestClose={onRequestClose}
                 isScrollViewBeingDragged={isDragging}
+                showControls={showControls}
               />
             </View>
           ))}
diff --git a/src/view/com/pager/FeedsTabBar.tsx b/src/view/com/pager/FeedsTabBar.tsx
deleted file mode 100644
index aa0ba7b24..000000000
--- a/src/view/com/pager/FeedsTabBar.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from './FeedsTabBarMobile'
diff --git a/src/view/com/pager/FeedsTabBar.web.tsx b/src/view/com/pager/FeedsTabBar.web.tsx
deleted file mode 100644
index fb52b913a..000000000
--- a/src/view/com/pager/FeedsTabBar.web.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import React from 'react'
-import {View, StyleSheet} from 'react-native'
-import Animated from 'react-native-reanimated'
-import {TabBar} from 'view/com/pager/TabBar'
-import {RenderTabBarFnProps} from 'view/com/pager/Pager'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
-import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
-import {useShellLayout} from '#/state/shell/shell-layout'
-import {usePinnedFeedsInfos} from '#/state/queries/feed'
-import {useSession} from '#/state/session'
-import {TextLink} from '#/view/com/util/Link'
-import {CenteredView} from '../util/Views'
-import {isWeb} from 'platform/detection'
-import {useNavigation} from '@react-navigation/native'
-import {NavigationProp} from 'lib/routes/types'
-
-export function FeedsTabBar(
-  props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
-) {
-  const {isMobile, isTablet} = useWebMediaQueries()
-  const {hasSession} = useSession()
-
-  if (isMobile) {
-    return <FeedsTabBarMobile {...props} />
-  } else if (isTablet) {
-    if (hasSession) {
-      return <FeedsTabBarTablet {...props} />
-    } else {
-      return <FeedsTabBarPublic />
-    }
-  } else {
-    return null
-  }
-}
-
-function FeedsTabBarPublic() {
-  const pal = usePalette('default')
-
-  return (
-    <CenteredView sideBorders>
-      <View
-        style={[
-          pal.view,
-          {
-            flexDirection: 'row',
-            alignItems: 'center',
-            justifyContent: 'space-between',
-            paddingHorizontal: 18,
-            paddingVertical: 12,
-          },
-        ]}>
-        <TextLink
-          type="title-lg"
-          href="/"
-          style={[pal.text, {fontWeight: 'bold'}]}
-          text="Bluesky "
-        />
-      </View>
-    </CenteredView>
-  )
-}
-
-function FeedsTabBarTablet(
-  props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
-) {
-  const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
-  const pal = usePalette('default')
-  const {hasSession} = useSession()
-  const navigation = useNavigation<NavigationProp>()
-  const {headerMinimalShellTransform} = useMinimalShellMode()
-  const {headerHeight} = useShellLayout()
-
-  const items = React.useMemo(() => {
-    if (!hasSession) return []
-
-    const pinnedNames = feeds.map(f => f.displayName)
-
-    if (!hasPinnedCustom) {
-      return pinnedNames.concat('Feeds ✨')
-    }
-    return pinnedNames
-  }, [hasSession, hasPinnedCustom, feeds])
-
-  const onPressDiscoverFeeds = React.useCallback(() => {
-    if (isWeb) {
-      navigation.navigate('Feeds')
-    } else {
-      navigation.navigate('FeedsTab')
-      navigation.popToTop()
-    }
-  }, [navigation])
-
-  const onSelect = React.useCallback(
-    (index: number) => {
-      if (hasSession && !hasPinnedCustom && index === items.length - 1) {
-        onPressDiscoverFeeds()
-      } else if (props.onSelect) {
-        props.onSelect(index)
-      }
-    },
-    [items.length, onPressDiscoverFeeds, props, hasSession, hasPinnedCustom],
-  )
-
-  return (
-    // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
-    <Animated.View
-      style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
-      onLayout={e => {
-        headerHeight.value = e.nativeEvent.layout.height
-      }}>
-      <TabBar
-        key={items.join(',')}
-        {...props}
-        onSelect={onSelect}
-        items={items}
-        indicatorColor={pal.colors.link}
-      />
-    </Animated.View>
-  )
-}
-
-const styles = StyleSheet.create({
-  tabBar: {
-    // @ts-ignore Web only
-    position: 'sticky',
-    zIndex: 1,
-    // @ts-ignore Web only -prf
-    left: 'calc(50% - 300px)',
-    width: 600,
-    top: 0,
-    flexDirection: 'row',
-    alignItems: 'center',
-    borderLeftWidth: 1,
-    borderRightWidth: 1,
-  },
-})
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 2ef1b1447..ebd739839 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -449,7 +449,7 @@ let PostThreadItemLoaded = ({
                       styles.replyLine,
                       {
                         flexGrow: 1,
-                        backgroundColor: pal.colors.border,
+                        backgroundColor: pal.colors.replyLine,
                         marginBottom: 4,
                       },
                     ]}
@@ -487,7 +487,7 @@ let PostThreadItemLoaded = ({
                         styles.replyLine,
                         {
                           flexGrow: 1,
-                          backgroundColor: pal.colors.border,
+                          backgroundColor: pal.colors.replyLine,
                           marginTop: 4,
                         },
                       ]}
diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx
index e56c88d2c..1dfb687df 100644
--- a/src/view/com/util/forms/PostDropdownBtn.tsx
+++ b/src/view/com/util/forms/PostDropdownBtn.tsx
@@ -2,6 +2,7 @@ import React, {memo} from 'react'
 import {StyleProp, View, ViewStyle} from 'react-native'
 import Clipboard from '@react-native-clipboard/clipboard'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useNavigation} from '@react-navigation/native'
 import {
   AppBskyActorDefs,
   AppBskyFeedPost,
@@ -19,6 +20,8 @@ import * as Toast from '../Toast'
 import {EventStopper} from '../EventStopper'
 import {useModalControls} from '#/state/modals'
 import {makeProfileLink} from '#/lib/routes/links'
+import {CommonNavigatorParams} from '#/lib/routes/types'
+import {getCurrentRoute} from 'lib/routes/helpers'
 import {getTranslatorLink} from '#/locale/helpers'
 import {usePostDeleteMutation} from '#/state/queries/post'
 import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
@@ -63,6 +66,7 @@ let PostDropdownBtn = ({
   const hiddenPosts = useHiddenPosts()
   const {hidePost} = useHiddenPostsApi()
   const openLink = useOpenLink()
+  const navigation = useNavigation()
 
   const rootUri = record.reply?.root?.uri || postUri
   const isThreadMuted = mutedThreads.includes(rootUri)
@@ -82,13 +86,38 @@ let PostDropdownBtn = ({
     postDeleteMutation.mutateAsync({uri: postUri}).then(
       () => {
         Toast.show(_(msg`Post deleted`))
+
+        const route = getCurrentRoute(navigation.getState())
+        if (route.name === 'PostThread') {
+          const params = route.params as CommonNavigatorParams['PostThread']
+          if (
+            currentAccount &&
+            isAuthor &&
+            (params.name === currentAccount.handle ||
+              params.name === currentAccount.did)
+          ) {
+            const currentHref = makeProfileLink(postAuthor, 'post', params.rkey)
+            if (currentHref === href && navigation.canGoBack()) {
+              navigation.goBack()
+            }
+          }
+        }
       },
       e => {
         logger.error('Failed to delete post', {message: e})
         Toast.show(_(msg`Failed to delete post, please try again`))
       },
     )
-  }, [postUri, postDeleteMutation, _])
+  }, [
+    navigation,
+    postUri,
+    postDeleteMutation,
+    postAuthor,
+    currentAccount,
+    isAuthor,
+    href,
+    _,
+  ])
 
   const onToggleThreadMute = React.useCallback(() => {
     try {
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index cb2abf1bc..856c237f6 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -6,7 +6,7 @@ import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
 import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
 import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
 import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
-import {FeedsTabBar} from '../com/pager/FeedsTabBar'
+import {HomeHeader} from '../com/home/HomeHeader'
 import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
 import {FeedPage} from 'view/com/feeds/FeedPage'
 import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
@@ -118,7 +118,7 @@ function HomeScreenReady({
   const renderTabBar = React.useCallback(
     (props: RenderTabBarFnProps) => {
       return (
-        <FeedsTabBar
+        <HomeHeader
           key="FEEDS_TAB_BAR"
           selectedPage={props.selectedPage}
           onSelect={props.onSelect}
diff --git a/src/view/screens/PreferencesHomeFeed.tsx b/src/view/screens/PreferencesFollowingFeed.tsx
index 7ad870937..b4acbcd44 100644
--- a/src/view/screens/PreferencesHomeFeed.tsx
+++ b/src/view/screens/PreferencesFollowingFeed.tsx
@@ -78,9 +78,9 @@ function RepliesThresholdInput({
 
 type Props = NativeStackScreenProps<
   CommonNavigatorParams,
-  'PreferencesHomeFeed'
+  'PreferencesFollowingFeed'
 >
-export function PreferencesHomeFeed({navigation}: Props) {
+export function PreferencesFollowingFeed({navigation}: Props) {
   const pal = usePalette('default')
   const {_} = useLingui()
   const {isTabletOrDesktop} = useWebMediaQueries()
@@ -101,14 +101,14 @@ export function PreferencesHomeFeed({navigation}: Props) {
         styles.container,
         isTabletOrDesktop && styles.desktopContainer,
       ]}>
-      <ViewHeader title={_(msg`Home Feed Preferences`)} showOnDesktop />
+      <ViewHeader title={_(msg`Following Feed Preferences`)} showOnDesktop />
       <View
         style={[
           styles.titleSection,
           isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
         ]}>
         <Text type="xl" style={[pal.textLight, styles.description]}>
-          <Trans>Fine-tune the content you see on your home screen.</Trans>
+          <Trans>Fine-tune the content you see on your Following feed.</Trans>
         </Text>
       </View>
 
@@ -260,7 +260,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
             <Text style={[pal.text, s.pb10]}>
               <Trans>
                 Set this setting to "Yes" to show samples of your saved feeds in
-                your following feed. This is an experimental feature.
+                your Following feed. This is an experimental feature.
               </Trans>
             </Text>
             <ToggleButton
diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx
index 9abf0f2bd..00b507a99 100644
--- a/src/view/screens/Settings/index.tsx
+++ b/src/view/screens/Settings/index.tsx
@@ -241,8 +241,8 @@ export function SettingsScreen({}: Props) {
     Toast.show(_(msg`Copied build version to clipboard`))
   }, [_])
 
-  const openHomeFeedPreferences = React.useCallback(() => {
-    navigation.navigate('PreferencesHomeFeed')
+  const openFollowingFeedPreferences = React.useCallback(() => {
+    navigation.navigate('PreferencesFollowingFeed')
   }, [navigation])
 
   const openThreadsPreferences = React.useCallback(() => {
@@ -529,7 +529,7 @@ export function SettingsScreen({}: Props) {
             pal.view,
             isSwitchingAccounts && styles.dimmed,
           ]}
-          onPress={openHomeFeedPreferences}
+          onPress={openFollowingFeedPreferences}
           accessibilityRole="button"
           accessibilityHint=""
           accessibilityLabel={_(msg`Opens the home feed preferences`)}>
@@ -540,7 +540,7 @@ export function SettingsScreen({}: Props) {
             />
           </View>
           <Text type="lg" style={pal.text}>
-            <Trans>Home Feed Preferences</Trans>
+            <Trans>Following Feed Preferences</Trans>
           </Text>
         </TouchableOpacity>
         <TouchableOpacity
diff --git a/src/view/screens/Storybook/Palette.tsx b/src/view/screens/Storybook/Palette.tsx
index b521fe860..900754681 100644
--- a/src/view/screens/Storybook/Palette.tsx
+++ b/src/view/screens/Storybook/Palette.tsx
@@ -2,99 +2,26 @@ import React from 'react'
 import {View} from 'react-native'
 
 import * as tokens from '#/alf/tokens'
-import {atoms as a} from '#/alf'
+import {atoms as a, useTheme} from '#/alf'
 
 export function Palette() {
+  const t = useTheme()
   return (
     <View style={[a.gap_md]}>
       <View style={[a.flex_row, a.gap_md]}>
-        <View
-          style={[a.flex_1, {height: 60, backgroundColor: tokens.color.gray_0}]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_25},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_50},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_100},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_200},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_300},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_400},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_500},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_600},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_700},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_800},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_900},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_950},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_975},
-          ]}
-        />
-        <View
-          style={[
-            a.flex_1,
-            {height: 60, backgroundColor: tokens.color.gray_1000},
-          ]}
-        />
+        <View style={[a.flex_1, t.atoms.bg_contrast_25, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_50, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_100, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_200, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_300, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_400, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_500, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_600, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_700, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_800, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_900, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_950, {height: 60}]} />
+        <View style={[a.flex_1, t.atoms.bg_contrast_975, {height: 60}]} />
       </View>
 
       <View style={[a.flex_row, a.gap_md]}>