diff --git a/packages/shared/src/lib/constants.ts b/packages/shared/src/lib/constants.ts
index 8e962b8f031..3ba0b24fed0 100644
--- a/packages/shared/src/lib/constants.ts
+++ b/packages/shared/src/lib/constants.ts
@@ -49,6 +49,18 @@ export const isTesting =
process.env.NODE_ENV === 'test' || (!isDevelopment && !isProduction);
export const isGBDevMode = process.env.NEXT_PUBLIC_GB_DEV_MODE === 'true';
+/**
+ * Branch preview deployments, e.g. my-branch.preview.app.daily.dev. They run
+ * NODE_ENV=production against the production API, so neither `isDevelopment`
+ * nor GrowthBook's dev tools are available to open a flag for review — the
+ * host is the only thing that distinguishes them from app.daily.dev.
+ */
+export const PREVIEW_HOST_SUFFIX = '.preview.app.daily.dev';
+
+export const isPreviewHost = (): boolean =>
+ typeof window !== 'undefined' &&
+ window.location.hostname.endsWith(PREVIEW_HOST_SUFFIX);
+
export const isBrave = (): boolean => {
if (typeof window === 'undefined' || !window.Promise) {
return false;
diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts
index 19040d3eff0..15c07fa559a 100644
--- a/packages/shared/src/lib/featureManagement.ts
+++ b/packages/shared/src/lib/featureManagement.ts
@@ -339,3 +339,9 @@ export const featurePlusSale = new Feature(
// warning, bad creative or revenue anomaly can be stopped without a deploy
// and an ISR revalidation cycle. Never ramp or target with this flag.
export const featureReadAdsense = new Feature('read_adsense', true);
+
+// Sharing a custom feed: a link in the feed settings General tab that adds the
+// feed to whoever opens it. Experiment default is the off state — the rollout
+// is a GrowthBook decision. Branch previews force it on (see `isPreviewHost`),
+// because a preview runs as production and has no way to open a flag.
+export const featureShareMyFeed = new Feature('share_my_feed', false);
diff --git a/packages/webapp/pages/dev/share-my-feed.tsx b/packages/webapp/pages/dev/share-my-feed.tsx
new file mode 100644
index 00000000000..beb85e35e7d
--- /dev/null
+++ b/packages/webapp/pages/dev/share-my-feed.tsx
@@ -0,0 +1,700 @@
+import type { ReactElement, ReactNode } from 'react';
+import React, { useEffect, useState } from 'react';
+import { NextSeo } from 'next-seo';
+import {
+ Button,
+ ButtonSize,
+ ButtonVariant,
+} from '@dailydotdev/shared/src/components/buttons/Button';
+import {
+ AiIcon,
+ AlertIcon,
+ ArrowIcon,
+ BlockIcon,
+ CopyIcon,
+ EditIcon,
+ FilterIcon,
+ HashtagIcon,
+ LinkIcon,
+ MiniCloseIcon,
+ PlusIcon,
+ PlusUserIcon,
+ StarIcon,
+ TrashIcon,
+} from '@dailydotdev/shared/src/components/icons';
+
+/**
+ * /dev/share-my-feed — the Share my feed placement review, on the app rather
+ * than in Storybook, so the surfaces are read against real app CSS and the
+ * app's own theme switch.
+ *
+ * Controls are inert: this compares placement and copy, not behaviour. Nothing
+ * here is wired to a feed, and no production surface changes.
+ */
+
+const AVATAR =
+ 'https://res.cloudinary.com/daily-now/image/upload/s--O0TOmw4y--/f_auto/v1715772965/public/noProfile';
+
+const useTheme = (initial: 'dark' | 'light') => {
+ const [theme, setTheme] = useState<'dark' | 'light'>(initial);
+ useEffect(() => {
+ if (typeof document === 'undefined') {
+ return;
+ }
+ const root = document.documentElement;
+ if (theme === 'light') {
+ root.classList.add('light');
+ } else {
+ root.classList.remove('light');
+ }
+ }, [theme]);
+ return [theme, setTheme] as const;
+};
+
+/* ------------------------------------------------------------------ chrome */
+
+type DeviceName = 'Desktop' | 'Tablet' | 'Mobile';
+
+const DEVICES: Record = {
+ Desktop: { width: 680, viewport: '1020px and up' },
+ Tablet: { width: 560, viewport: '768px' },
+ Mobile: { width: 375, viewport: '375px' },
+};
+
+/** A surface drawn at one real viewport width, so density is comparable. */
+const Device = ({
+ name,
+ children,
+}: {
+ name: DeviceName;
+ children: ReactNode;
+}) => (
+
+
+ {name} · {DEVICES[name].viewport}
+
+
+ {children}
+
+
+);
+
+/** Devices sit in a scroller rather than wrapping, so widths stay honest. */
+const Rail = ({ children }: { children: ReactNode }) => (
+
+
+);
+
+/* -------------------------------------------------------- the recipient UI */
+
+type LandingSpot = 'preview' | 'added' | 'signin' | 'limit';
+
+const TAGS = [
+ '#typescript',
+ '#react',
+ '#webdev',
+ '#css',
+ '#nextjs',
+ '#tooling',
+];
+
+const SAMPLE_POSTS = [
+ 'Why iconic tech brands lost their dominance',
+ 'The case against microservices',
+ 'Postgres is all you need, again',
+];
+
+/**
+ * The recipient's landing. `/feeds/new?entityId=&entityType=` already creates
+ * a feed and follows one entity into it — this is the same flow with a set
+ * rather than a single tag, so the precedent exists in FeedSettingsCreate.
+ */
+const LandingScreen = ({
+ device,
+ spot,
+}: {
+ device: DeviceName;
+ spot: LandingSpot;
+}) => (
+
+
+
+
+ {spot === 'added' && (
+ <>
+
+ Tomer's feed is yours now
+
+
+ It is in your sidebar as a new feed. Rename it, add tags, or delete
+ it — from here it is your feed, not a copy of theirs.
+
+ >
+ )}
+ {spot === 'signin' && (
+ <>
+
+ Sign in to add Tomer's feed
+
+
+ A feed lives in an account, so there is nowhere to put this one yet.
+ Sign in or sign up, then open the link again.
+
+
+
+ You've reached the maximum number of feeds. Delete one or
+ upgrade to Plus to add this feed.
+
+ }
+ size={ButtonSize.XSmall}
+ type="button"
+ variant={ButtonVariant.Tertiary}
+ />
+
+ )}
+
+ {/* A sample of what the feed holds, not a reading surface: nothing here
+ is a link, so the only way in is to add the feed. */}
+
+ Custom feeds only, and not a snapshot. A custom feed is something
+ you built — a name, an icon, a tag set — so the thing worth
+ sending is the feed itself, not a picture of its posts. Opening a
+ shared link adds the feed to the recipient's account, named
+ after whoever shared it.
+
+
+ Revised from the sharing map (#6362), which had this down as
+ snapshot-only on the assumption there was nothing to link to.
+ There is, if the link creates something: Copy link leads, with a
+ text list as the fallback for anywhere a link will not do.
+