From 5e4b809d8052a6715515e6b7edc0665e11033419 Mon Sep 17 00:00:00 2001
From: tomeredlich
Date: Sun, 27 Sep 2026 10:27:02 +0300
Subject: [PATCH 1/2] feat(snapshot): credit the post title on text snapshots
TLDR, paragraph, text-selection and Happening now snapshots only
credited the source, so a shared image never said which article the
passage came from. The title now sits under the divider, above the
source, at a smaller size than the copy so the passage stays the
subject. Happening now fetches the post (or shared post) title and
falls back to the highlight headline.
Co-Authored-By: Claude Opus 5.5
---
.../components/highlights/HighlightItem.tsx | 1 +
.../snapshot/HighlightShareActions.tsx | 5 +++
.../snapshot/HighlightTextSnapshotCard.tsx | 22 +++++++++--
.../snapshot/SelectionSnapshotBar.tsx | 5 +++
.../src/features/snapshot/SnapshotCredit.tsx | 39 +++++++++++++------
.../snapshot/TextSnapshotButton.spec.tsx | 8 ++++
.../features/snapshot/TextSnapshotButton.tsx | 1 +
packages/shared/src/graphql/highlights.ts | 4 ++
8 files changed, 70 insertions(+), 15 deletions(-)
diff --git a/packages/shared/src/components/highlights/HighlightItem.tsx b/packages/shared/src/components/highlights/HighlightItem.tsx
index 1b4127e894..2039946aca 100644
--- a/packages/shared/src/components/highlights/HighlightItem.tsx
+++ b/packages/shared/src/components/highlights/HighlightItem.tsx
@@ -98,6 +98,7 @@ export const HighlightItem = ({
diff --git a/packages/shared/src/features/snapshot/HighlightShareActions.tsx b/packages/shared/src/features/snapshot/HighlightShareActions.tsx
index 2fdd912cae..b93c378daa 100644
--- a/packages/shared/src/features/snapshot/HighlightShareActions.tsx
+++ b/packages/shared/src/features/snapshot/HighlightShareActions.tsx
@@ -36,11 +36,14 @@ export function HighlightShareActions({
highlight,
tldr,
tldrRef,
+ title,
source,
}: {
highlight: PostHighlightFeed;
tldr: string;
tldrRef: RefObject;
+ /** The article the TLDR summarizes, credited on both cards. */
+ title?: string;
/** Who wrote the TLDR, credited on both cards. */
source?: { name: string; image?: string };
}): ReactElement {
@@ -86,6 +89,7 @@ export function HighlightShareActions({
ref={cardRef}
seed={highlight.id}
source={source}
+ title={title}
/>
)}
@@ -96,6 +100,7 @@ export function HighlightShareActions({
onShare={logSelectionShare}
seed={highlight.id}
source={source}
+ title={title}
/>
>
);
diff --git a/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx b/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
index 963c7dea6c..4a54be8a3f 100644
--- a/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
+++ b/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
@@ -19,6 +19,8 @@ export interface HighlightTextSnapshotCardProps {
* leaves the paragraph unmarked rather than guessing at one.
*/
highlight?: HighlightRange;
+ /** The post the passage came from, so the card says what it is quoting. */
+ title?: string;
source?: { name: string; image?: string };
/** The surface's own label, on the logo row. */
label?: ReactNode;
@@ -29,6 +31,8 @@ export interface HighlightTextSnapshotCardProps {
const CONTEXT = 'rgba(255, 255, 255, 0.42)';
/** A marker tint, kept low enough that the bold copy stays the loud part. */
const MARK_BACKGROUND = 'rgba(217, 126, 254, 0.22)';
+/** A long headline is cut so the passage, not the title, fills the card. */
+const TITLE_LIMIT = 140;
/**
* The reader's selection shown where it came from: the whole paragraph, set
@@ -45,7 +49,14 @@ const MARK_BACKGROUND = 'rgba(217, 126, 254, 0.22)';
* unclickable in an image.
*/
function HighlightTextSnapshotCardComponent(
- { passage, highlight, source, label, seed }: HighlightTextSnapshotCardProps,
+ {
+ passage,
+ highlight,
+ title,
+ source,
+ label,
+ seed,
+ }: HighlightTextSnapshotCardProps,
ref: React.Ref,
): ReactElement {
const trimmed = passage.trim();
@@ -54,6 +65,7 @@ function HighlightTextSnapshotCardComponent(
highlight.start >= 0 &&
highlight.end > highlight.start &&
highlight.end <= trimmed.length;
+ const heading = title?.trim();
const windowed = isValid
? windowAroundHighlight(trimmed, highlight)
: undefined;
@@ -99,8 +111,12 @@ function HighlightTextSnapshotCardComponent(
- {source?.name && (
-
+ {(source?.name || heading) && (
+
)}
diff --git a/packages/shared/src/features/snapshot/SelectionSnapshotBar.tsx b/packages/shared/src/features/snapshot/SelectionSnapshotBar.tsx
index 666b2d11ec..cf25fef13d 100644
--- a/packages/shared/src/features/snapshot/SelectionSnapshotBar.tsx
+++ b/packages/shared/src/features/snapshot/SelectionSnapshotBar.tsx
@@ -69,6 +69,8 @@ export interface SelectionShareBarProps {
link: string;
/** Seeds the card's gradient and names the downloaded file. */
seed: string;
+ /** The post the quote was selected from, credited on the card. */
+ title?: string;
source?: { name: string; image?: string };
/** The surface's own label on the card's logo row. */
label?: ReactNode;
@@ -80,6 +82,7 @@ export function SelectionShareBar({
containerRef,
link,
seed,
+ title,
source,
label,
onShare,
@@ -187,6 +190,7 @@ export function SelectionShareBar({
passage={quote.passage}
seed={seed}
source={source}
+ title={title}
/>
>,
@@ -227,6 +231,7 @@ export function SelectionSnapshotBar({
onShare={onShare}
seed={post.id}
source={snapshotSource(post)}
+ title={post.title}
/>
);
}
diff --git a/packages/shared/src/features/snapshot/SnapshotCredit.tsx b/packages/shared/src/features/snapshot/SnapshotCredit.tsx
index d5d0badd1c..6d2bb4bbfa 100644
--- a/packages/shared/src/features/snapshot/SnapshotCredit.tsx
+++ b/packages/shared/src/features/snapshot/SnapshotCredit.tsx
@@ -6,8 +6,10 @@ const MUTED = colors.salt['90'];
const DIVIDER = colors.pepper['10'];
export interface SnapshotCreditProps {
- name: string;
+ name?: string;
image?: string;
+ /** The post the copy was lifted from, named above who wrote it. */
+ title?: string;
}
/**
@@ -21,27 +23,40 @@ export interface SnapshotCreditProps {
export function SnapshotCredit({
name,
image,
+ title,
}: SnapshotCreditProps): ReactElement {
return (
- {image && (
-

+ {title && (
+
+ {title}
+
+ )}
+ {name && (
+
+ {image && (
+

+ )}
+
+ {name}
+
+
)}
-
- {name}
-
);
}
diff --git a/packages/shared/src/features/snapshot/TextSnapshotButton.spec.tsx b/packages/shared/src/features/snapshot/TextSnapshotButton.spec.tsx
index 6f773ec01f..9c2b6d0d99 100644
--- a/packages/shared/src/features/snapshot/TextSnapshotButton.spec.tsx
+++ b/packages/shared/src/features/snapshot/TextSnapshotButton.spec.tsx
@@ -44,4 +44,12 @@ describe('TextSnapshotButton', () => {
expect(cardCopies()).toBe(1);
});
+
+ it('credits the post title on the card', () => {
+ renderButton();
+
+ fireEvent.focus(screen.getByLabelText('Snapshot'));
+
+ expect(screen.getByText(post.title as string)).toBeInTheDocument();
+ });
});
diff --git a/packages/shared/src/features/snapshot/TextSnapshotButton.tsx b/packages/shared/src/features/snapshot/TextSnapshotButton.tsx
index 0b5de063d6..4c334d3444 100644
--- a/packages/shared/src/features/snapshot/TextSnapshotButton.tsx
+++ b/packages/shared/src/features/snapshot/TextSnapshotButton.tsx
@@ -84,6 +84,7 @@ export function TextSnapshotButton({
ref={cardRef}
seed={post.id}
source={snapshotSource(post)}
+ title={post.title}
/>
,
document.body,
diff --git a/packages/shared/src/graphql/highlights.ts b/packages/shared/src/graphql/highlights.ts
index 60c083699d..f78f958c30 100644
--- a/packages/shared/src/graphql/highlights.ts
+++ b/packages/shared/src/graphql/highlights.ts
@@ -28,11 +28,13 @@ export interface PostHighlightFeed {
id: string;
type: string;
commentsPermalink: string;
+ title?: string;
summary?: string;
contentHtml?: string;
domain?: string;
source?: HighlightFeedSource;
sharedPost?: {
+ title?: string;
summary?: string;
contentHtml?: string;
domain?: string;
@@ -123,6 +125,7 @@ export const POST_HIGHLIGHT_FEED_FRAGMENT = gql`
id
type
commentsPermalink
+ title
summary
contentHtml
domain
@@ -131,6 +134,7 @@ export const POST_HIGHLIGHT_FEED_FRAGMENT = gql`
image
}
sharedPost {
+ title
summary
contentHtml
domain
From 981a268446071c16b3652ab5d88cfcb6e3f9914f Mon Sep 17 00:00:00 2001
From: tomeredlich
Date: Sun, 27 Sep 2026 10:30:20 +0300
Subject: [PATCH 2/2] refactor(snapshot): truncate the card title once
Co-Authored-By: Claude Opus 5.5
---
.../src/features/snapshot/HighlightTextSnapshotCard.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx b/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
index 4a54be8a3f..8437c4c918 100644
--- a/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
+++ b/packages/shared/src/features/snapshot/HighlightTextSnapshotCard.tsx
@@ -65,7 +65,7 @@ function HighlightTextSnapshotCardComponent(
highlight.start >= 0 &&
highlight.end > highlight.start &&
highlight.end <= trimmed.length;
- const heading = title?.trim();
+ const heading = title && truncateAtWord(title, TITLE_LIMIT);
const windowed = isValid
? windowAroundHighlight(trimmed, highlight)
: undefined;
@@ -115,7 +115,7 @@ function HighlightTextSnapshotCardComponent(
)}