From 59b43cb6441f5192c92004344fdfcdf34024687d Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Fri, 28 Aug 2026 12:39:31 -0700 Subject: [PATCH] feat(discover-weekly): put the feature behind the discover_weekly flag Discover Weekly is merged to main but has never shipped, and web/desktop deploys are gated on a manual approval that batches everything sitting on main. Unrelated fixes are waiting on that approval, so the mix needs an off switch rather than a revert. Adds `discover_weekly`, defaulted off in production and on in development. Gated at three points, which is every way in: - The web banner (Explore desktop, Explore mobile-web, desktop feed) - The native banner (Explore, feed) - The web page, which is reachable by URL and so redirects to Explore rather than 404ing on a link that predates the rollout The banners already returned null on an empty or failed mix, so the surfaces around them are built to tolerate a missing banner -- the flag check rides that same return. The native screen is reachable only through the native banner, so it needs no separate gate. `enabled` is threaded into `useDiscoverWeekly` at each site so a disabled flag costs no request. `DISCOVER_WEEKLY_PAGE` stays in `staticRoutes`: dropping it would make `/explore/discover-weekly` parse as a user handle while the flag is off. Co-Authored-By: Claude Opus 5 --- .../services/remote-config/feature-flags.ts | 9 ++++--- .../components/DiscoverWeekly.tsx | 16 +++++++++--- .../discover-weekly/DiscoverWeeklyBanner.tsx | 18 ++++++++++--- .../DiscoverWeeklyPage.tsx | 25 ++++++++++++++++--- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/packages/common/src/services/remote-config/feature-flags.ts b/packages/common/src/services/remote-config/feature-flags.ts index 0583599ccfe..dcc312d9c87 100644 --- a/packages/common/src/services/remote-config/feature-flags.ts +++ b/packages/common/src/services/remote-config/feature-flags.ts @@ -16,7 +16,8 @@ export enum FeatureFlags { COLLAPSED_EXPLORE_HEADER = 'collapsed_explore_header', LAUNCHPAD_VERIFICATION = 'launchpad_verification', FAN_CLUB_TEXT_POST_POSTING = 'fan_club_text_post_posting', - QUEUE_NEW_FEATURE_BADGE = 'queue_new_feature_badge' + QUEUE_NEW_FEATURE_BADGE = 'queue_new_feature_badge', + DISCOVER_WEEKLY = 'discover_weekly' } type FlagDefaults = Record @@ -26,7 +27,8 @@ export const environmentFlagDefaults: Record< Partial > = { development: { - [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: true + [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: true, + [FeatureFlags.DISCOVER_WEEKLY]: true }, production: {} } @@ -49,5 +51,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.COLLAPSED_EXPLORE_HEADER]: false, [FeatureFlags.LAUNCHPAD_VERIFICATION]: true, [FeatureFlags.FAN_CLUB_TEXT_POST_POSTING]: false, - [FeatureFlags.QUEUE_NEW_FEATURE_BADGE]: false + [FeatureFlags.QUEUE_NEW_FEATURE_BADGE]: false, + [FeatureFlags.DISCOVER_WEEKLY]: false } diff --git a/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx b/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx index b01bb2b2868..a5bf2efc0ff 100644 --- a/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx +++ b/packages/mobile/src/screens/explore-screen/components/DiscoverWeekly.tsx @@ -1,9 +1,10 @@ import React, { useCallback, useEffect, useRef } from 'react' import { useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' import { Name, type DiscoverWeeklySurface } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { Image } from 'react-native' import { Flex, Paper, Text } from '@audius/harmony-native' @@ -33,9 +34,12 @@ export const DiscoverWeekly = ({ const { InViewWrapper, inView } = useExploreSectionTracking('Discover Weekly') const navigation = useNavigation() const { trackEvent } = useAnalytics() + const { isEnabled: isDiscoverWeeklyEnabled } = useFeatureFlag( + FeatureFlags.DISCOVER_WEEKLY + ) const { trackIds, isError, isSuccess } = useDiscoverWeekly( { limit: 30 }, - { enabled: inView } + { enabled: inView && isDiscoverWeeklyEnabled } ) // Fire the impression once, and only once there's a real mix behind it. @@ -61,7 +65,13 @@ export const DiscoverWeekly = ({ navigation.navigate('DiscoverWeeklyScreen') }, [navigation, trackEvent, surface, trackIds.length]) - if (isError || (isSuccess && trackIds.length === 0)) { + // The flag check sits with the empty/error case so both surfaces that render + // this banner -- Explore and the feed -- are gated by this one return. + if ( + !isDiscoverWeeklyEnabled || + isError || + (isSuccess && trackIds.length === 0) + ) { return null } diff --git a/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx b/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx index 1e6c4ede2ad..426ad778273 100644 --- a/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx +++ b/packages/web/src/components/discover-weekly/DiscoverWeeklyBanner.tsx @@ -1,9 +1,10 @@ import { useCallback, useEffect, useRef } from 'react' import { useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages as messages } from '@audius/common/messages' import { Name, type DiscoverWeeklySurface } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { route } from '@audius/common/utils' import { Artwork, @@ -53,9 +54,13 @@ export const DiscoverWeeklyBanner = ({ fallbackInView: true }) + const { isEnabled: isDiscoverWeeklyEnabled } = useFeatureFlag( + FeatureFlags.DISCOVER_WEEKLY + ) + const { trackIds, isError, isSuccess } = useDiscoverWeekly( { limit: 30 }, - { enabled: inView } + { enabled: inView && isDiscoverWeeklyEnabled } ) // Fire the impression once, and only once there's a real mix behind it -- @@ -84,8 +89,13 @@ export const DiscoverWeeklyBanner = ({ }, [navigate, trackEvent, surface, isMobile, trackIds.length]) // Hidden entirely when there's no mix to promote -- a banner advertising an - // empty page is worse than no banner. - if (isError || (isSuccess && trackIds.length === 0)) { + // empty page is worse than no banner. The flag check sits alongside it so + // every surface that renders the banner is gated by this one return. + if ( + !isDiscoverWeeklyEnabled || + isError || + (isSuccess && trackIds.length === 0) + ) { return null } diff --git a/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx b/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx index 023fa63299f..00fc91c94b8 100644 --- a/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx +++ b/packages/web/src/pages/discover-weekly-page/DiscoverWeeklyPage.tsx @@ -1,11 +1,13 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { useCurrentUserId, useDiscoverWeekly } from '@audius/common/api' -import { useAnalytics } from '@audius/common/hooks' +import { useAnalytics, useFeatureFlag } from '@audius/common/hooks' import { exploreMessages } from '@audius/common/messages' import { ID, Name, PlaybackSource } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { playbackActions, playbackSelectors } from '@audius/common/store' import type { PlaybackTrack } from '@audius/common/store' +import { route } from '@audius/common/utils' import { Artwork, Button, @@ -15,6 +17,7 @@ import { Text } from '@audius/harmony' import { useDispatch, useSelector } from 'react-redux' +import { Navigate } from 'react-router' import discoverWeeklyArt from 'assets/img/discoverWeekly.jpg' import { make } from 'common/store/analytics/actions' @@ -30,6 +33,8 @@ const messages = { 'A fresh mix of tracks picked for you, updated every Monday on Audius.' } +const { EXPLORE_PAGE } = route + const DISCOVER_WEEKLY_SOURCE = 'DISCOVER_WEEKLY_TRACKS' const PAGE_SIZE = 30 const ARTWORK_SIZE = 200 @@ -61,9 +66,16 @@ export const DiscoverWeeklyPage = () => { const mainContentRef = useMainContentRef() const { data: currentUserId } = useCurrentUserId() - const { trackIds, isPending, isFetching, isLoading } = useDiscoverWeekly({ - limit: PAGE_SIZE - }) + // The route stays registered while the flag is off -- the URL is public and + // shareable, so a link that predates the rollout should land somewhere real + // rather than 404. + const { isEnabled: isDiscoverWeeklyEnabled, isLoaded: isFlagLoaded } = + useFeatureFlag(FeatureFlags.DISCOVER_WEEKLY) + + const { trackIds, isPending, isFetching, isLoading } = useDiscoverWeekly( + { limit: PAGE_SIZE }, + { enabled: isDiscoverWeeklyEnabled } + ) // Fired once the mix resolves rather than on mount, so trackCount is real // and a failed load doesn't register as a page view. @@ -140,6 +152,11 @@ export const DiscoverWeeklyPage = () => { const isEmpty = !isLoading && trackIds.length === 0 + // Nothing until remote config resolves, so an enabled user doesn't get + // bounced to Explore on the first frame. + if (!isFlagLoaded) return null + if (!isDiscoverWeeklyEnabled) return + return (