diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index 2ec0c6a4c..63501c595 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -46,7 +46,12 @@ import { useProfileBlockMutationQueue, useProfileMuteMutationQueue, } from '#/state/queries/profile' -import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' +import { + InvalidInteractionSettingsError, + MAX_HIDDEN_REPLIES, + MaxHiddenRepliesError, + useToggleReplyVisibilityMutation, +} from '#/state/queries/threadgate' import {useRequireAuth, useSession} from '#/state/session' import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import * as Toast from '#/view/com/util/Toast' @@ -339,10 +344,30 @@ let PostMenuItems = ({ : _(msg({message: 'Reply visibility updated', context: 'toast'})), ) } catch (e: any) { - Toast.show( - _(msg({message: 'Updating reply visibility failed', context: 'toast'})), - ) - logger.error(`Failed to ${action} reply`, {safeMessage: e.message}) + if (e instanceof MaxHiddenRepliesError) { + Toast.show( + _( + msg({ + message: `You can hide a maximum of ${MAX_HIDDEN_REPLIES} replies.`, + context: 'toast', + }), + ), + ) + } else if (e instanceof InvalidInteractionSettingsError) { + Toast.show( + _(msg({message: 'Invalid interaction settings.', context: 'toast'})), + ) + } else { + Toast.show( + _( + msg({ + message: 'Updating reply visibility failed', + context: 'toast', + }), + ), + ) + logger.error(`Failed to ${action} reply`, {safeMessage: e.message}) + } } } diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index eeb9f7035..305acc5d0 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -25,6 +25,11 @@ import * as bsky from '#/types/bsky' export * from '#/state/queries/threadgate/types' export * from '#/state/queries/threadgate/util' +/** + * Must match the threadgate lexicon record definition. + */ +export const MAX_HIDDEN_REPLIES = 300 + export const threadgateRecordQueryKeyRoot = 'threadgate-record' export const createThreadgateRecordQueryKey = (uri: string) => [ threadgateRecordQueryKeyRoot, @@ -205,6 +210,7 @@ export async function upsertThreadgate( }) const next = await callback(prev) if (!next) return + validateThreadgateRecordOrThrow(next) await writeThreadgateRecord({ agent, postUri, @@ -358,3 +364,31 @@ export function useToggleReplyVisibilityMutation() { }, }) } + +export class MaxHiddenRepliesError extends Error { + constructor(message?: string) { + super(message || 'Maximum number of hidden replies reached') + this.name = 'MaxHiddenRepliesError' + } +} + +export class InvalidInteractionSettingsError extends Error { + constructor(message?: string) { + super(message || 'Invalid interaction settings') + this.name = 'InvalidInteractionSettingsError' + } +} + +export function validateThreadgateRecordOrThrow( + record: AppBskyFeedThreadgate.Record, +) { + const result = AppBskyFeedThreadgate.validateRecord(record) + + if (result.success) { + if ((result.value.hiddenReplies?.length ?? 0) > MAX_HIDDEN_REPLIES) { + throw new MaxHiddenRepliesError() + } + } else { + throw new InvalidInteractionSettingsError() + } +} -- 2.51.2 From 84cae44cd2af8eb183ddbcf109bd5157872dbe11 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 18:24:42 +0300 Subject: [PATCH 02/22] Ship activation experiments (#9170) * ship onboarding experiments * delete logged out cta * delete other loggedoutcta --- src/components/LoggedOutCTA.tsx | 82 ------------------- src/lib/statsig/gates.ts | 4 - src/screens/Onboarding/index.tsx | 15 ++-- .../components/ThreadItemAnchor.tsx | 6 +- src/screens/PostThread/index.tsx | 3 - 5 files changed, 7 insertions(+), 103 deletions(-) delete mode 100644 src/components/LoggedOutCTA.tsx diff --git a/src/components/LoggedOutCTA.tsx b/src/components/LoggedOutCTA.tsx deleted file mode 100644 index de0e23dc9..000000000 --- a/src/components/LoggedOutCTA.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import {View, type ViewStyle} from 'react-native' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {type Gate} from '#/lib/statsig/gates' -import {useGate} from '#/lib/statsig/statsig' -import {isWeb} from '#/platform/detection' -import {useSession} from '#/state/session' -import {useLoggedOutViewControls} from '#/state/shell/logged-out' -import {Logo} from '#/view/icons/Logo' -import {atoms as a, useTheme} from '#/alf' -import {Button, ButtonText} from '#/components/Button' -import {Text} from '#/components/Typography' - -interface LoggedOutCTAProps { - style?: ViewStyle - gateName: Gate -} - -export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) { - const {hasSession} = useSession() - const {requestSwitchToAccount} = useLoggedOutViewControls() - const gate = useGate() - const t = useTheme() - const {_} = useLingui() - - // Only show for logged-out users on web - if (hasSession || !isWeb) { - return null - } - - // Check gate at the last possible moment to avoid counting users as exposed when they won't see the element - if (!gate(gateName)) { - return null - } - - return ( - - - - - - - Join Bluesky - - - The open social network. - - - - - - - ) -} diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index befa99319..1f6c84301 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,8 +1,6 @@ export type Gate = // Keep this alphabetic please. | 'alt_share_icon' - | 'cta_above_post_heading' - | 'cta_above_post_replies' | 'debug_show_feedcontext' | 'debug_subscriptions' | 'disable_onboarding_policy_update_notice' @@ -10,8 +8,6 @@ export type Gate = | 'feed_reply_button_open_thread' | 'old_postonboarding' | 'onboarding_add_video_feed' - | 'onboarding_suggested_accounts' - | 'onboarding_value_prop' | 'post_follow_profile_suggested_accounts' | 'remove_show_latest_button' | 'test_gate_1' diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx index f13402ece..b587ac785 100644 --- a/src/screens/Onboarding/index.tsx +++ b/src/screens/Onboarding/index.tsx @@ -2,7 +2,6 @@ import {useMemo, useReducer} from 'react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useGate} from '#/lib/statsig/statsig' import { Layout, OnboardingControls, @@ -13,21 +12,19 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished' import {StepInterests} from '#/screens/Onboarding/StepInterests' import {StepProfile} from '#/screens/Onboarding/StepProfile' import {Portal} from '#/components/Portal' -import {ENV} from '#/env' import {StepSuggestedAccounts} from './StepSuggestedAccounts' export function Onboarding() { const {_} = useLingui() - const gate = useGate() - const showValueProp = ENV !== 'e2e' && gate('onboarding_value_prop') - const showSuggestedAccounts = - ENV !== 'e2e' && gate('onboarding_suggested_accounts') + const [state, dispatch] = useReducer(reducer, { ...initialState, - totalSteps: showSuggestedAccounts ? 4 : 3, + totalSteps: 4, experiments: { - onboarding_suggested_accounts: showSuggestedAccounts, - onboarding_value_prop: showValueProp, + // let's leave this flag logic in for now to avoid rebase churn + // TODO: remove this flag logic once we've finished with all experiments -sfn + onboarding_suggested_accounts: true, + onboarding_value_prop: true, }, }) diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index 301124aaa..b789297fe 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -39,13 +39,12 @@ import { OUTER_SPACE, REPLY_LINE_WIDTH, } from '#/screens/PostThread/const' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {colors} from '#/components/Admonition' import {Button} from '#/components/Button' import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import {InlineLinkText, Link} from '#/components/Link' -import {LoggedOutCTA} from '#/components/LoggedOutCTA' import {ContentHider} from '#/components/moderation/ContentHider' import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' import {PostAlerts} from '#/components/moderation/PostAlerts' @@ -180,7 +179,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ const {_} = useLingui() const {openComposer} = useOpenComposer() const {currentAccount, hasSession} = useSession() - const {gtTablet} = useBreakpoints() const feedFeedback = useFeedFeedback(postSource?.feedSourceInfo, hasSession) const formatPostStatCount = useFormatPostStatCount() @@ -315,8 +313,6 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ }, isRoot && [a.pt_lg], ]}> - {/* Show CTA for logged-out visitors - hide on desktop and check gate */} - {!gtTablet && } - {/* Show CTA for logged-out visitors */} - ) } else { -- 2.51.2 From ec8c36e6146d06ac5ec12d48f04214c5cb6e392b Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 18:31:38 +0300 Subject: [PATCH 03/22] add 10% sample rate to sentry (#9182) --- src/env/common.ts | 4 ++-- src/logger/sentry/setup/index.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/env/common.ts b/src/env/common.ts index 210ad037c..ef3f80a33 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -21,12 +21,12 @@ export const ENV: string = process.env.EXPO_PUBLIC_ENV export const IS_TESTFLIGHT = ENV === 'testflight' /** - * Indicates whether the app is __DEV__ + * Indicates whether the app is `__DEV__` */ export const IS_DEV = __DEV__ /** - * Indicates whether the app is __DEV__ or TestFlight + * Indicates whether the app is `__DEV__` or TestFlight */ export const IS_INTERNAL = IS_DEV || IS_TESTFLIGHT diff --git a/src/logger/sentry/setup/index.ts b/src/logger/sentry/setup/index.ts index d062f05d2..4fe07e97e 100644 --- a/src/logger/sentry/setup/index.ts +++ b/src/logger/sentry/setup/index.ts @@ -29,4 +29,5 @@ init({ * @see https://docs.sentry.io/platforms/react-native/configuration/options/#attach-stacktrace */ attachStacktrace: false, + sampleRate: env.IS_INTERNAL ? 1.0 : 0.1, }) -- 2.51.2 From 6cd6a3447d9250d7af4496bc0d451604c2450c05 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 19:15:48 +0300 Subject: [PATCH 04/22] Tweak greens (#9177) * tweak greens * undo test code --- src/components/AccountList.tsx | 2 +- src/components/PostControls/RepostButton.tsx | 2 +- src/components/PostControls/RepostButton.web.tsx | 4 ++-- src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx | 2 +- src/components/dialogs/EmailDialog/screens/Update.tsx | 2 +- src/components/dialogs/EmailDialog/screens/Verify.tsx | 4 ++-- src/screens/Search/modules/ExploreTrendingTopics.tsx | 2 +- src/screens/Settings/components/ChangeHandleDialog.tsx | 2 +- src/screens/Signup/StepHandle/index.tsx | 2 +- src/view/com/notifications/NotificationFeedItem.tsx | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index e3b2b7d12..e01ca31d2 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -172,7 +172,7 @@ function AccountItem({ {isCurrentAccount ? ( - + ) : ( )} diff --git a/src/components/PostControls/RepostButton.tsx b/src/components/PostControls/RepostButton.tsx index f34e37c29..d759f9457 100644 --- a/src/components/PostControls/RepostButton.tsx +++ b/src/components/PostControls/RepostButton.tsx @@ -57,7 +57,7 @@ let RepostButton = ({ @@ -100,7 +100,7 @@ export const RepostButton = ({ requireAuth(() => {})} active={isReposted} - activeColor={t.palette.positive_600} + activeColor={t.palette.positive_500} label={_(msg`Repost or quote post`)} big={big}> diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index c45bc3a2c..3146ddc80 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -159,7 +159,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { a.pt_lg, a.pb_md, ]}> - + Success diff --git a/src/components/dialogs/EmailDialog/screens/Update.tsx b/src/components/dialogs/EmailDialog/screens/Update.tsx index a0ec69c85..f92e76476 100644 --- a/src/components/dialogs/EmailDialog/screens/Update.tsx +++ b/src/components/dialogs/EmailDialog/screens/Update.tsx @@ -281,7 +281,7 @@ export function Update(_props: ScreenProps) { - + Success! diff --git a/src/components/dialogs/EmailDialog/screens/Verify.tsx b/src/components/dialogs/EmailDialog/screens/Verify.tsx index 74ff9c96b..6116acf2f 100644 --- a/src/components/dialogs/EmailDialog/screens/Verify.tsx +++ b/src/components/dialogs/EmailDialog/screens/Verify.tsx @@ -176,7 +176,7 @@ export function Verify({config, showScreen}: ScreenProps) { - + {' '} Email verification complete! @@ -202,7 +202,7 @@ export function Verify({config, showScreen}: ScreenProps) { state.mutationStatus === 'success' ? ( <> - + {' '} Email sent! diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index 72de1419e..f96e66d78 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -194,7 +194,7 @@ function TrendingIndicator({type}: {type: TrendingIndicatorType | 'skeleton'}) { case 'new': { Icon = TrendingIcon text = _(msg`New`) - color = t.palette.positive_700 + color = t.palette.positive_600 backgroundColor = t.palette.positive_50 break } diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index 588a0a3ba..73bb21df6 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -636,7 +636,7 @@ function SuccessMessage({text}: {text: string}) { a.rounded_full, a.align_center, a.justify_center, - {backgroundColor: t.palette.positive_600}, + {backgroundColor: t.palette.positive_500}, ]}> diff --git a/src/screens/Signup/StepHandle/index.tsx b/src/screens/Signup/StepHandle/index.tsx index 696c4d468..6067430ac 100644 --- a/src/screens/Signup/StepHandle/index.tsx +++ b/src/screens/Signup/StepHandle/index.tsx @@ -169,7 +169,7 @@ export function StepHandle() { {isHandleAvailable?.available && ( )} diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 8f33f4b79..3034864f1 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -307,7 +307,7 @@ let NotificationFeedItem = ({ ) : ( {firstAuthorLink} reposted your post ) - icon = + icon = } else if (item.type === 'follow') { let isFollowBack = false @@ -519,7 +519,7 @@ let NotificationFeedItem = ({ ) : ( {firstAuthorLink} reposted your repost ) - icon = + icon = } else if (item.type === 'subscribed-post') { const postsCount = 1 + (item.additional?.length || 0) a11yLabel = hasMultipleAuthors -- 2.51.2 From a9efd949838f2037da4beda59dd762fe804aed83 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 10 Oct 2025 11:16:40 -0500 Subject: [PATCH 05/22] Placeholder style tweaks (#9107) * Align colors in loading placeholders * Same for new skele components * Increase opacity of disabled replies button slightly --- src/components/PostControls/index.tsx | 2 +- src/components/Skeleton.tsx | 6 ++--- src/view/com/util/LoadingPlaceholder.tsx | 33 ++++++++++-------------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index f91bcd8a5..c516c2477 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -210,7 +210,7 @@ let PostControls = ({ a.flex_1, a.align_start, {marginLeft: big ? -2 : -6}, - replyDisabled ? {opacity: 0.5} : undefined, + replyDisabled ? {opacity: 0.6} : undefined, ]}> }) { - const theme = useTheme() + const t = useTheme() return ( }) { - const t = useTheme_NEW() - const pal = usePalette('default') + const t = useTheme() return ( - + }) { - const pal = usePalette('default') + const t = useTheme() return ( - + - + @@ -184,9 +178,8 @@ export function ProfileCardLoadingPlaceholder({ }: { style?: StyleProp }) { - const pal = usePalette('default') return ( - + - + }) { - const t = useTheme_NEW() + const t = useTheme() const random = useMemo(() => Math.random(), []) return ( -- 2.51.2 From fe31cf4508f3fb54295c04b187371e957e64bf79 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 19:17:57 +0300 Subject: [PATCH 06/22] fix background colors (#9174) --- src/components/PostControls/ShareMenu/ShareMenuItems.tsx | 4 +++- src/screens/Settings/components/AddAppPasswordDialog.tsx | 1 - src/screens/Settings/components/ChangeHandleDialog.tsx | 4 ++-- src/screens/Settings/components/CopyButton.tsx | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx index 03b113708..2a70a248e 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx @@ -136,7 +136,9 @@ let ShareMenuItems = ({ {hideInPWI && ( - + This post is only visible to logged-in users. diff --git a/src/screens/Settings/components/AddAppPasswordDialog.tsx b/src/screens/Settings/components/AddAppPasswordDialog.tsx index 8e1cc0dee..506bac78f 100644 --- a/src/screens/Settings/components/AddAppPasswordDialog.tsx +++ b/src/screens/Settings/components/AddAppPasswordDialog.tsx @@ -195,7 +195,6 @@ function CreateDialogInner({passwords}: {passwords: string[]}) { value={data.password} label={_(msg`Copy App Password`)} size="large" - variant="solid" color="secondary"> {data.password} diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index 73bb21df6..79840a5ed 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -428,10 +428,10 @@ function OwnHandlePage({goToServiceHandle}: {goToServiceHandle: () => void}) { _atproto @@ -449,10 +449,10 @@ function OwnHandlePage({goToServiceHandle}: {goToServiceHandle: () => void}) { diff --git a/src/screens/Settings/components/CopyButton.tsx b/src/screens/Settings/components/CopyButton.tsx index 2fc531abb..23c4fa8dd 100644 --- a/src/screens/Settings/components/CopyButton.tsx +++ b/src/screens/Settings/components/CopyButton.tsx @@ -58,9 +58,9 @@ export function CopyButton({ pointerEvents="none"> Copied! -- 2.51.2 From 3561fc81127a5179c497b7464eec92530d0132de Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 19:20:41 +0300 Subject: [PATCH 07/22] move aspect ratio to atom (#9171) --- bskyembed/src/components/embed.tsx | 4 ++-- src/alf/atoms.ts | 13 ++++++++++++- src/components/InterestTabs.tsx | 4 ++-- src/components/MediaPreview.tsx | 5 +++-- src/components/Post/Embed/ExternalEmbed/index.tsx | 4 +--- src/components/StarterPack/ShareDialog.tsx | 2 +- src/components/StarterPack/StarterPackCard.tsx | 2 +- src/components/dialogs/GifSelect.tsx | 3 ++- src/components/live/LiveStatusDialog.tsx | 2 +- src/lib/constants.ts | 1 + src/screens/Onboarding/StepFinished.tsx | 2 +- src/view/com/util/images/ImageLayoutGrid.tsx | 8 ++++---- 12 files changed, 31 insertions(+), 19 deletions(-) diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 52618a89d..ddaa7bf03 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -330,7 +330,7 @@ function ExternalEmbed({ {content.external.thumb && ( )}
@@ -435,7 +435,7 @@ function StarterPackEmbed({ - +
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 3168b1da1..6d40962e0 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -1,6 +1,7 @@ import {type StyleProp, type ViewStyle} from 'react-native' import {atoms as baseAtoms} from '@bsky.app/alf' +import {CARD_ASPECT_RATIO} from '#/lib/constants' import {native, platform, web} from '#/alf/util/platform' import * as Layout from '#/components/Layout' @@ -31,6 +32,16 @@ export const atoms = { backgroundColor: 'transparent', }, + /** + * Aspect ratios + */ + aspect_square: { + aspectRatio: 1, + }, + aspect_card: { + aspectRatio: CARD_ASPECT_RATIO, + }, + /* * Transition */ @@ -67,7 +78,7 @@ export const atoms = { }), /* - * Animaations + * Animations */ fade_in: web({ animation: 'fadeIn ease-out 0.15s', diff --git a/src/components/InterestTabs.tsx b/src/components/InterestTabs.tsx index aec421768..6b4a46837 100644 --- a/src/components/InterestTabs.tsx +++ b/src/components/InterestTabs.tsx @@ -258,7 +258,7 @@ export function InterestTabs({ t.atoms.border_contrast_low, t.atoms.bg, a.h_full, - {aspectRatio: 1}, + a.aspect_square, a.rounded_full, ]}> @@ -292,7 +292,7 @@ export function InterestTabs({ t.atoms.border_contrast_low, t.atoms.bg, a.h_full, - {aspectRatio: 1}, + a.aspect_square, a.rounded_full, ]}> diff --git a/src/components/MediaPreview.tsx b/src/components/MediaPreview.tsx index c2603a4d7..d8d2e430f 100644 --- a/src/components/MediaPreview.tsx +++ b/src/components/MediaPreview.tsx @@ -87,7 +87,7 @@ export function ImageItem({ }) { const t = useTheme() return ( - + diff --git a/src/components/Post/Embed/ExternalEmbed/index.tsx b/src/components/Post/Embed/ExternalEmbed/index.tsx index 7911df726..4e970ad7f 100644 --- a/src/components/Post/Embed/ExternalEmbed/index.tsx +++ b/src/components/Post/Embed/ExternalEmbed/index.tsx @@ -97,9 +97,7 @@ export const ExternalEmbed = ({ ]}> {imageUri && !embedPlayerParams ? ( diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx index 6c282f117..dd0373b9f 100644 --- a/src/components/StarterPack/ShareDialog.tsx +++ b/src/components/StarterPack/ShareDialog.tsx @@ -91,8 +91,8 @@ function ShareDialogInner({ source={{uri: imageUrl}} style={[ a.rounded_sm, + a.aspect_card, { - aspectRatio: 1200 / 630, transform: [{scale: gtMobile ? 0.85 : 1}], marginTop: gtMobile ? -20 : 0, }, diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx index e58c2ed27..18b994523 100644 --- a/src/components/StarterPack/StarterPackCard.tsx +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -191,7 +191,7 @@ export function Embed({ diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index d26cec2c6..6ac87f4e0 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -300,7 +300,8 @@ export function GifPreview({ a.flex_1, a.mb_sm, a.rounded_sm, - {aspectRatio: 1, opacity: pressed ? 0.8 : 1}, + a.aspect_card, + {opacity: pressed ? 0.8 : 1}, t.atoms.bg_contrast_25, ]} source={{ diff --git a/src/components/live/LiveStatusDialog.tsx b/src/components/live/LiveStatusDialog.tsx index 835b69e28..f15650e48 100644 --- a/src/components/live/LiveStatusDialog.tsx +++ b/src/components/live/LiveStatusDialog.tsx @@ -102,7 +102,7 @@ export function LiveStatus({ style={[ t.atoms.bg_contrast_25, a.w_full, - {aspectRatio: 1.91}, + a.aspect_card, android([ a.overflow_hidden, { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 0ce6f88b8..c777480fc 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -17,6 +17,7 @@ export const EMBED_SERVICE = 'https://embed.bsky.app' export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` export const BSKY_DOWNLOAD_URL = 'https://bsky.app/download' export const STARTER_PACK_MAX_SIZE = 150 +export const CARD_ASPECT_RATIO = 1200 / 630 // HACK // Yes, this is exactly what it looks like. It's a hard-coded constant diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index 3ed268a57..98a95bda6 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -401,7 +401,7 @@ function ValueProposition({ ]}> {alt} diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index 757d952a1..e1d125744 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -67,7 +67,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { const containerRefs = [containerRef1, containerRef2] return ( - + - + - + - + Date: Fri, 10 Oct 2025 19:26:48 +0300 Subject: [PATCH 08/22] fix hider alignment in thread (#9168) --- src/components/moderation/PostHider.tsx | 3 +++ src/screens/PostThread/components/ThreadItemPost.tsx | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index 057c77023..77910ec1a 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -34,6 +34,7 @@ interface Props extends ComponentProps { modui: ModerationUI profile: AppBskyActorDefs.ProfileViewBasic interpretFilterAsBlur?: boolean + hiderStyle?: StyleProp } export function PostHider({ @@ -42,6 +43,7 @@ export function PostHider({ disabled, modui, style, + hiderStyle, children, iconSize, iconStyles, @@ -100,6 +102,7 @@ export function PostHider({ }, override ? {paddingBottom: 0} : undefined, t.atoms.bg, + hiderStyle, ]}> -- 2.51.2 From 54b8eacba1b991179d53b04ccaef23b5b247e4fa Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 10 Oct 2025 19:30:29 +0300 Subject: [PATCH 09/22] Fix chat request buttons not moving with swipe gesture (#9155) * portal in buttons so they move with swipe * remove outline style buttons --- .../Messages/components/ChatListItem.tsx | 370 +++++++++--------- .../Messages/components/ChatStatusInfo.tsx | 5 +- .../Messages/components/RequestButtons.tsx | 8 +- .../Messages/components/RequestListItem.tsx | 65 +-- 4 files changed, 231 insertions(+), 217 deletions(-) diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index cc21a2688..f4e786367 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -42,11 +42,14 @@ import {Trash_Stroke2_Corner0_Rounded} from '#/components/icons/Trash' import {Link} from '#/components/Link' import {useMenuControl} from '#/components/Menu' import {PostAlerts} from '#/components/moderation/PostAlerts' +import {createPortalGroup} from '#/components/Portal' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' import {VerificationCheck} from '#/components/verification/VerificationCheck' import type * as bsky from '#/types/bsky' +export const ChatListItemPortal = createPortalGroup() + export let ChatListItem = ({ convo, showMenu = true, @@ -331,200 +334,215 @@ function ChatListItemReady({ const hasUnread = convo.unreadCount > 0 && !isDeletedAccount return ( - - + + - - + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + // @ts-expect-error web only + onFocus={onFocus} + onBlur={onMouseLeave} + style={[a.relative, t.atoms.bg]}> + + + + + + {({hovered, pressed, focused}) => ( + + {/* Avatar goes here */} + - - {({hovered, pressed, focused}) => ( - - {/* Avatar goes here */} - + + + + + {displayName} + + + {verification.showBadge && ( + + + + )} + {lastMessageSentAt && ( + + + {({timeElapsed}) => ( + + · {timeElapsed} + + )} + + + )} + {(convo.muted || moderation.blocked) && ( + + {' '} + ·{' '} + + + )} + - - - + {!isDeletedAccount && ( - {displayName} - - - {verification.showBadge && ( - - - - )} - {lastMessageSentAt && ( - - - {({timeElapsed}) => ( - - · {timeElapsed} - - )} - - - )} - {(convo.muted || moderation.blocked) && ( - - {' '} - ·{' '} - + @{profile.handle} )} - - {!isDeletedAccount && ( - @{profile.handle} + emoji + numberOfLines={2} + style={[ + a.text_sm, + a.leading_snug, + hasUnread ? a.font_semi_bold : t.atoms.text_contrast_high, + isDimStyle && t.atoms.text_contrast_medium, + ]}> + {lastMessage} - )} - - {lastMessage} - - - - - {children} - + - {hasUnread && ( - - )} - - )} - + {children} + - {showMenu && ( - + )} + + )} + + + + + {showMenu && ( + 0} + hideTrigger={isNative} + blockInfo={blockInfo} + style={[ + a.absolute, + a.h_full, + a.self_end, + a.justify_center, + { + right: tokens.space.lg, + opacity: + !gtMobile || showActions || menuControl.isOpen ? 1 : 0, + }, + ]} + latestReportableMessage={latestReportableMessage} + /> + )} + 0} - hideTrigger={isNative} - blockInfo={blockInfo} - style={[ - a.absolute, - a.h_full, - a.self_end, - a.justify_center, - { - right: tokens.space.lg, - opacity: !gtMobile || showActions || menuControl.isOpen ? 1 : 0, - }, - ]} - latestReportableMessage={latestReportableMessage} /> - )} - - - + + + ) } diff --git a/src/screens/Messages/components/ChatStatusInfo.tsx b/src/screens/Messages/components/ChatStatusInfo.tsx index c02034ff3..0cebabfd1 100644 --- a/src/screens/Messages/components/ChatStatusInfo.tsx +++ b/src/screens/Messages/components/ChatStatusInfo.tsx @@ -46,7 +46,7 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) { label={_(msg`Block or report`)} convo={convoState.convo} profile={otherUser} - color="negative" + color="negative_subtle" size="small" currentScreen="conversation" /> @@ -70,8 +70,7 @@ export function ChatStatusInfo({convoState}: {convoState: ActiveConvoStates}) { diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx index 560888552..4437a9a43 100644 --- a/src/screens/Messages/components/RequestButtons.tsx +++ b/src/screens/Messages/components/RequestButtons.tsx @@ -36,7 +36,6 @@ export function RejectMenu({ convo, profile, size = 'tiny', - variant = 'outline', color = 'secondary', label, showDeleteConvo, @@ -117,7 +116,6 @@ export function RejectMenu({ label={triggerProps.accessibilityLabel} style={[a.flex_1]} color={color} - variant={variant} size={size}> {label || ( @@ -129,7 +127,7 @@ export function RejectMenu({ )} - + {showDeleteConvo && ( @@ -266,7 +262,6 @@ export function AcceptChatButton({ export function DeleteChatButton({ convo, size = 'tiny', - variant = 'outline', color = 'secondary', label, currentScreen, @@ -315,7 +310,6 @@ export function DeleteChatButton({