- {post.collectionSources?.length > 0 && (
+ {collectionSources.length > 0 && (
diff --git a/packages/shared/src/hooks/useShareBriefingDigest.ts b/packages/shared/src/hooks/useShareBriefingDigest.ts
new file mode 100644
index 00000000000..0686075d4c9
--- /dev/null
+++ b/packages/shared/src/hooks/useShareBriefingDigest.ts
@@ -0,0 +1,16 @@
+import { featureShareBriefingDigest } from '../lib/featureManagement';
+import { useConditionalFeature } from './useConditionalFeature';
+import { useSharingVisibility } from './useSharingVisibility';
+
+// Per-topic gate for the briefing/digest sharing surfaces. Requires the master
+// `sharing_visibility` kill-switch to be on as well, so the initiative can be
+// disabled wholesale without touching this flag.
+export const useShareBriefingDigest = (shouldEvaluate = true): boolean => {
+ const { isEnabled: isSharingVisible } = useSharingVisibility(shouldEvaluate);
+ const { value } = useConditionalFeature({
+ feature: featureShareBriefingDigest,
+ shouldEvaluate: shouldEvaluate && isSharingVisible,
+ });
+
+ return isSharingVisible && value;
+};
diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts
index 97b38cbdd2b..4dce8c2b4a3 100644
--- a/packages/shared/src/lib/featureManagement.ts
+++ b/packages/shared/src/lib/featureManagement.ts
@@ -308,3 +308,13 @@ export const featureSharingVisibility = new Feature(
// high-traffic icon, so it ramps on its own flag to watch share/copy metrics.
// Keep the default `false` (control = existing `LinkIcon`).
export const featureShareCopyIcon = new Feature('share_copy_icon', false);
+
+// Sharing affordances on the presidential briefing and the personalized digest
+// post: per-item copy actions on the briefing list, a full share popover on the
+// briefing header, and share parity for the digest post (which ships with no
+// share affordance today). Also gated by `sharing_visibility`. Keep the default
+// `false` — GrowthBook ramps it.
+export const featureShareBriefingDigest = new Feature(
+ 'share_briefing_digest',
+ false,
+);
diff --git a/packages/shared/src/lib/share.ts b/packages/shared/src/lib/share.ts
index b6bb78125b7..3651f7adeb2 100644
--- a/packages/shared/src/lib/share.ts
+++ b/packages/shared/src/lib/share.ts
@@ -10,6 +10,7 @@ export enum ShareProvider {
LinkedIn = 'linkedin',
Telegram = 'telegram',
Email = 'email',
+ CopyText = 'copy text',
}
export const getWhatsappShareLink = (link: string): string =>
diff --git a/packages/storybook/stories/components/BriefSharing.stories.tsx b/packages/storybook/stories/components/BriefSharing.stories.tsx
new file mode 100644
index 00000000000..d4be4cb50ea
--- /dev/null
+++ b/packages/storybook/stories/components/BriefSharing.stories.tsx
@@ -0,0 +1,157 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import React from 'react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { BriefCopyMenu } from '@dailydotdev/shared/src/components/brief/BriefCopyMenu';
+import { BriefListItem } from '@dailydotdev/shared/src/components/brief/BriefListItem';
+import { getLogContextStatic } from '@dailydotdev/shared/src/contexts/LogContext';
+import AuthContext from '@dailydotdev/shared/src/contexts/AuthContext';
+import type { LoggedUser } from '@dailydotdev/shared/src/lib/user';
+import type { Post } from '@dailydotdev/shared/src/graphql/posts';
+import { Origin, TargetId } from '@dailydotdev/shared/src/lib/log';
+import { fn } from 'storybook/test';
+
+const briefPost = {
+ id: 'brief-1',
+ slug: 'presidential-briefing-jul-22',
+ title: 'Presidential briefing',
+ summary:
+ 'Rust 1.90 lands async closures, Node 24 ships a stable permission model, and three CVEs hit the npm supply chain.',
+ commentsPermalink: 'https://app.daily.dev/posts/presidential-briefing-jul-22',
+ createdAt: '2026-07-22T06:00:00.000Z',
+ readTime: 4,
+ read: false,
+ flags: { posts: 42, sources: 18 },
+} as unknown as Post;
+
+const withProviders = (Story: () => JSX.Element) => {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false, staleTime: Infinity } },
+ });
+ // Mock the short-URL resolution so copy actions don't hit network.
+ queryClient.setQueryData(['shortUrl'], 'https://dly.to/abc123');
+
+ const LogContext = getLogContextStatic();
+
+ const mockUser = {
+ id: '1',
+ name: 'Test User',
+ username: 'testuser',
+ email: 'test@example.com',
+ image: 'https://daily-now-res.cloudinary.com/image/upload/placeholder.jpg',
+ providers: ['google'],
+ createdAt: '2024-01-01T00:00:00.000Z',
+ permalink: 'https://daily.dev/testuser',
+ } as unknown as LoggedUser;
+
+ return (
+
+
+ false,
+ }}
+ >
+
+
+
+
+
+
+ );
+};
+
+const meta: Meta
= {
+ title: 'Components/Share/BriefSharing',
+ component: BriefCopyMenu,
+ args: {
+ post: briefPost,
+ origin: Origin.BriefPage,
+ },
+ decorators: [withProviders],
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+// The per-item copy menu on its own: copy link, copy summary, copy title + link.
+export const CopyMenu: Story = {};
+
+// A brief with no generated summary hides the "Copy summary" entry.
+export const CopyMenuWithoutSummary: Story = {
+ args: { post: { ...briefPost, summary: undefined } as Post },
+};
+
+// The briefing-list row with the copy menu enabled (flag on).
+export const ListItemWithCopyActions: StoryObj = {
+ render: () => (
+
+ ),
+};
+
+// Flag-off control: the row renders exactly as it does on main today.
+// `showCopyActions` is pinned explicitly because Storybook aliases GrowthBook to
+// a mock that returns the string 'control' — truthy — for every flag, so the
+// gate always reads as ON here. Flag-off behaviour is asserted in Jest instead.
+export const ListItemControl: StoryObj = {
+ render: () => (
+
+ ),
+};
+
+// Read state — the copy menu stays available on already-read briefings.
+export const ListItemRead: StoryObj = {
+ render: () => (
+
+ ),
+};