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( )}