Skip to content

feat(snapshot): snapshot an expanded highlight on Happening Now - #6562

Closed
tomeredlich wants to merge 19 commits into
mainfrom
snapshot-happening-now-preview
Closed

feat(snapshot): snapshot an expanded highlight on Happening Now#6562
tomeredlich wants to merge 19 commits into
mainfrom
snapshot-happening-now-preview

Conversation

@tomeredlich

@tomeredlich tomeredlich commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Every highlight on Happening Now is a self-contained claim with sources behind it, and today none of them can be lifted out. The page also has the shortest shelf life we publish, which is why the image matters more than the link: a URL sends someone to a page that has already moved on.

The placement

One control, on the expanded highlight only — Snapshot leading, copy link beside it, opposite "Read more". Expansion is the intent signal: the reader has already said which claim they care about, and there is room for a label without crowding the row. Collapsed rows are untouched, so a scan of the feed stays a scan.

Behind snapshot_highlight_expanded, defaulting to off.

Two placements were built and cut before this landed: an icon on every row (a button on every line of a page people scan rather than read) and a page-header control (a card of five headlines nobody reads at thumbnail size). Both are deleted rather than left dormant — flags, cards, buttons and tests.

What's in it

  • HighlightSnapshotCard — the 1080 square: a live "Happening now" eyebrow, the headline as the hero with the font size stepping down as it grows, the TLDR, and a channel/time footer. Built on the existing SnapshotFrame.
  • HighlightSnapshotButton — stages the card off-screen at full size, following the PollSnapshotButton pattern, since the capture reads live DOM.
  • HighlightShareActions — the copy-link + Snapshot pair the expanded footer ends on, matching the selection bar on the post page. Keeping it separate also keeps useCopyText out of HighlightItem, which would otherwise need a QueryClient for a placement that is off by default.

HighlightItem's diff is an import block, one hook and one line in the expanded footer. HighlightsPage.tsx is untouched.

Reviewing it

/dev/snapshot-happening-now stands the placement against what ships today, with the production control rather than a picture of it, plus the export card and a real capture button. noindex/nofollow, blocked on the canonical production hosts.

Note for anyone reviewing from a draft: the flags only open on *.preview.app.daily.dev, and that domain is not assigned until the PR is marked ready for review. The same build is served from vercel.app, but api.daily.dev rejects that origin on CORS, so the app never boots there — the /dev page works on it (no API), /highlights does not.

Verified

Captures exercised in the browser: real 1080×1080 PNGs. Strict typecheck clean, eslint clean, 21 snapshot + highlights tests pass.

🤖 Generated with Claude Code

tomeredlich and others added 16 commits August 31, 2026 15:19
Adds a Storybook page that draws the post page from its production
components — PostSourceInfo, PostActions, PostContentWidget and
MobilePostFloatingBar — at desktop, tablet and mobile, showing what
ships today and the one snapshot placement we want.

Today the post menu opens with 'Share via' and the engagement bar ends
with a labeled 'Copy'; below laptop PostHeaderActions is hidden, so on
tablet the only share action on the screen is that Copy. The post has a
real per-post OG image, so snapshot is limited to the text-selection
bar, where a highlighted line has no OG of its own.

Carries the Snapshot icon it needs; no production surface changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page argued for one snapshot placement — the text-selection bar — without
showing what pressing it produces, so the trade-off against Copy link had to be
taken on trust. The selection category now ends with the 1080x1080 card the
button exports, drawn from the real HighlightTextSnapshotCard (#6544) at a
third of its size and seeded with the highlighted line on the page above it.

Carries the four card files that page needs from #6544 — the frame, the
seeded gradient, the quote truncation and the highlight card itself — plus the
.snapshot-copy utility they rely on. No production surface renders them yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#6556 argued the case for one snapshot placement and drew what it exports;
this makes it real. Selecting a line of a post now raises a floating bar with
copy link, copy text and Snapshot, and Snapshot hands back the 1080x1080 PNG
of the quote — clipboard first, download where the clipboard refuses images.

Behind `snapshot_selection_share`, which defaults to off, and only on the post
page: the decision on the modal was to leave it alone. `useConditionalFeature`
evaluates on post pages only, so nothing else logs an exposure.

The capture pipeline and the button come from #6544 (snapdom, captureShareImage,
SnapshotButton). New here: `useTextSelection`, which reports a selection only
while it lives inside the post body, and the bar itself. The selection commits
on the trailing edge of the drag rather than behind a drag flag — a pointerup
lost to a release outside the window would otherwise strand the toolbar.

/dev/snapshot carries the review surface: the live bar, the card at every quote
length, and the real capture output. Unflagged there, so it can be reviewed
without GrowthBook, and blocked on the canonical production hosts.

Testing: shared 376 suites / 2652 tests, webapp 81 / 644, extension 6 / 52.
Drag-selected a quote in a browser and captured from the bar: clipboard on a
real press, download plus an "Image saved" toast on a scripted one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The version on this branch was a trimmed copy: two categories out of six. The
page on #6544 carries the whole post-page argument, and the missing four are
the load-bearing ones — the share strip, end-of-thread band and post-upvote
prompt; the audit showing seven post types breaking seven different ways; the
poll recommendation, which is the one type where snapshot beats a link
outright; and the modal and sticky nav the audit had missed.

Brings surfaceChrome along, since the page is drawn with its ShareSheet and
its three real viewport widths rather than the older device rail, and keeps
the exported-image variant next to the selection bar it belongs to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Storybook page argues for nine placements; only the selection bar could
actually be pressed. /dev/snapshot now carries all of them with working
controls rather than pictures of them: copy summary, the selection bar, the
share strip, the end-of-thread band, the post-upvote prompt, both poll
snapshots and the sticky nav copy-link. Every copy button copies for real and
every Snapshot rasterizes the card it would share.

Two new payloads this needed:

- PollSnapshotCard — the poll recommendation had no card. The winner is drawn
  in the accent and the rest stay quiet, so the answer reads before the bars.
- CopySummaryButton (#6350) — headline, TLDR and link as three paragraphs, so
  a paste into a thread is a usable message rather than a bare URL.

Both copy call sites now catch a rejected clipboard write. `navigator.clipboard`
refuses outright when the document is not focused, and the shared hook awaits
it without a catch, so the press produced no toast, no fallback and an
unhandled rejection. It now says the clipboard was blocked.

Only the selection bar is wired to the real post page; the rest are placement
mocks with real controls, which is what the review needs before any of them is
committed to a surface.

Testing: shared 377 suites / 2654 tests, webapp 81 / 644. Pressed every control
in a browser — real clicks copy (OS clipboard changes), blocked clipboard shows
the error toast, both poll Snapshots and both capture buttons return 1080²
PNGs (quote 423 KB, poll 378 KB).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every placement the Storybook page argues for is now production code on the
surface it belongs to, each behind its own flag defaulting to off:

- post_copy_summary — CopySummaryButton runs into the end of the TLDR in
  PostContent, evaluated only where a summary exists.
- post_share_prompts — DiscussionShareRow under the comment composer in
  PostEngagements, already shipping in the discussion panel but never on the
  post page, and EndOfThreadShare after the thread.
- poll_snapshot — PollSnapshotButton under the poll bars and on the post-vote
  prompt in PollPostContent, evaluated only on polls that have votes.
- post_nav_copy_link — a copy-link icon in PostNavigation, gated on
  isFixedNavigation so the inline navigation is untouched.

The post-upvote prompt needed nothing: PostContentShare already renders
"Should anyone else see this post?" with a copy-link input the moment you
upvote. The page proposed a placement production had already built.

pollSnapshotFromPost derives each bar from the options' own counts rather than
numPollVotes, which counts voters and would leave the bars adding up to
something other than the poll.

/dev/snapshot now mounts the real components instead of imitations. `/dev/*`
short-circuits to a QueryClient-only tree in _app, so the page stands in for
the auth and log contexts the share components reach for — signed out, no
squads, logging swallowed — and names the flag behind each placement.

Testing: shared 379 suites / 2661 tests, webapp 81 / 644, extension 6 / 52.
New specs cover the poll payload maths, the band's empty-thread case, the copy
payload and both flag states for copy summary. Pressed every control in a
browser: both poll snapshots capture, the real share row and band render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lint_shared runs eslint over the whole package with --max-warnings 0, and this
file was written after the last per-file fix pass, so a prettier break reached
CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The placements were production code on the real surfaces but invisible where
they most needed reviewing. A preview deployment runs NODE_ENV=production
against the production API, so `isDevelopment` is false and GrowthBook's dev
tools are off: there is no way to open a flag from the browser, and every
placement defaulted to off.

useSharePlacement wraps the flag: on, when the flag says so, or unconditionally
on a *.preview.app.daily.dev host. The host is read after mount rather than
during render, because the server cannot know what it will be served as and
disagreeing would break hydration.

The committed defaults stay false, so merging still changes nothing for anyone
on app.daily.dev and the rollout remains a GrowthBook decision. Only the
branch preview opens itself, and a surface that opted out with shouldEvaluate
stays out there too.

Testing: shared 380 suites / 2665 tests, webapp 81 / 644. New spec covers the
production host with the flag on and off, the preview host with no flag, and
the opted-out surface on a preview. Package lint clean for shared and webapp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found on the preview: the icon was missing on a real post that plainly had a
summary. Anonymous visitors get in-content AdSense, and then the post page
renders the summary itself through `renderSummarySegments` — ad slots between
the parts — so PostContent's own paragraph, the only branch carrying the
button, never runs. Every signed-out visitor saw nothing.

The segmented branch cannot take the icon inline the way the plain one does,
since the parts belong to the page's render prop, so it trails the block
instead.

Testing: shared 380 suites / 2667 tests, webapp 81 / 644, package lint clean.
Two new specs cover the segmented summary with the flag on and off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four placements were invisible, and they had one cause: they were wired to
PostContent and PostEngagements, which the redesigned post page does not
render. It draws PostFocusCard and PostDiscussionPanel instead, and the
post_redesign flag decides which layout a reader gets — so the work landed on
the layout half the audience does not see. The two that did work were the ones
that did not depend on it: the share strip, which the discussion panel already
shipped, and the poll, which is not redesign-eligible and is always classic.

- PostFocusCard now carries the selection bar, scoped to the card so a quote
  can only come from the post's own body, and copy summary on both summary
  variants — the plain paragraph and the truncated video one.
- PostDiscussionPanel carries the end-of-thread band, and PostContentShare,
  which is where the classic page's post-upvote prompt comes from.

The prompt also never fired from the post page at all: PostContentShare waits
on usePostActions, and only feed card buttons raised 'upvote'. Both post-page
bars — PostActions and FocusCardActionBar — now raise it when they upvote,
behind post_share_prompts so the prompt's existing behaviour is unchanged
wherever the flag is off.

Testing: shared 380 suites / 2667 tests, webapp 81 / 644, extension 6 / 52,
package lint clean for shared and webapp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two reasons it was missing from the modal. The gate was `isPostPage`, so the
classic modal never mounted it — I had read the page's "no snapshot on the post
page or modal" as covering selected text too, when selected text was the
exception it carved out. And the bar is portaled to the body at z-popup while
the modal overlay is z-modal, so even once mounted it rendered behind the modal
the quote was selected in.

The bar is now page and modal both, at z-max: a reader highlights a line
wherever they happen to be reading it, and on desktop that is mostly the modal.

Copy summary is still page-only — say the word and it can follow.

Also covers the redesigned layout with specs: PostFocusCard is what the
post_redesign flag serves, and the placements were wired to the classic layout
first.

Testing: shared 380 suites / 2673 tests, webapp 81 / 644, package lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nk with it

Design pass on the three prompts:

- The floating bar leads with Snapshot, labelled and solid, with copy link and
  copy text as the quiet pair beside it. It is the reason the bar exists and it
  was the last, smallest thing in the row.
- "Should anyone else see this post?" drops the bordered widget and the link in
  an input for a copy-link button with the icon trailing. There is one thing to
  do with that link, and an input asks to be read first. Behind
  post_share_prompts, so the shipping prompt is unchanged until rollout.
- The end-of-thread band drops its box for the same reason, reads "Send it to
  someone who'd have opinions" instead of counting comments, and offers a solid
  copy-link button. It still only appears where a thread exists.

A copied snapshot now carries the post link as text/plain beside the PNG, so a
rich composer takes the image and a plain one takes the link. Browsers that
refuse two representations in one item still get the image.

Testing: shared 381 suites / 2676 tests, webapp 81 / 644, extension 6 / 52,
package lint clean. New spec covers the clipboard payload, its single-item
fallback and the no-link case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The poll card carried its status and count as a footer, while the product
  puts that line under the question and above the options. It now reads the
  same way, and says what the product says: "Voting open · 1.3K votes · Today",
  or "Voting ended · N total votes" once it closes. The source keeps the
  bottom line as attribution.
- The upvote prompt moves out of the discussion panel and under the action bar
  that raises it — the classic page has always had it there, and on the focus
  layout it had ended up below the comment composer.
- Both prompts now read as a pair: title, subtext, and a solid Copy link with
  the icon trailing.
- Both primary buttons are ButtonV2.

Testing: shared 381 suites / 2677 tests, webapp 81 / 644, package lint clean.
The poll spec now freezes the clock rather than reading it: postDateFormat
answers "Now" inside the first minute, so a real clock decided the assertion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Tsahi's button" is SplitShareButton from #6369 — copy link and a chevron
holding the networks, meeting at one hairline — wrapped by ShareBand, which
pairs it with a title and a line of copy. #6369 built it for exactly these two
surfaces: the band below an active discussion and the prompt after an upvote.

Neither PR landed. #6369 is closed and #6378 merged into it, so the components
exist on no branch that ships. They are carried here — ShareBand, ShareActions,
SplitShareButton, CopyStateIcon and the Origin they log against — and both
prompts now render through ShareBand rather than the pair of hand-rolled rows
I had built. If that stack revives, these two callers should collapse into its
EndOfConversationShare and the copies here should go.

The copy is theirs too, so the two bands read as one pair.

Testing: shared 382 suites / 2680 tests, webapp 81 / 644, extension 6 / 52,
package lint clean for shared and webapp. The ported ShareActions spec runs
green here as it does there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The band sat 40px below the action bar and 16px above the comment box. The
focus card's column is already gap-4, so the mt-6 the band carried for the
classic layout — where PostContainer has no gap and every child hand-rolls its
margins — stacked on top of a gap that was doing the job. Spacing now belongs
to the host: the band defaults to my-4 for a container that supplies none, and
the focus card passes nothing.

Copying no longer dismisses the prompt. It called onInteract('none'), so the
band vanished under the cursor — taking the networks behind the chevron with
it, right when someone might want a second one.

Testing: shared 382 suites / 2680 tests, webapp 81 / 644, package lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Happening Now can be shared at three levels — the whole page, a topic and
a single highlight — and today none of them can. The page also has the
shortest shelf life we publish, which is why the image matters more than
the link: a URL sends someone to a page that has already moved on.

Adds the snapshot cards for both payloads and wires all three levels into
/highlights behind their own flags, plus /dev/snapshot-happening-now,
which stands the four placements side by side with working controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
daily-webapp Ready Ready Preview Sep 1, 2026 11:41am UTC
1 Skipped Deployment
Project Deployment Actions Updated
storybook Ignored Ignored Sep 1, 2026 11:41am UTC

Request Review

The row icon and the page-header control both go. The row put a button on
every line of a page people scan rather than read, and the page card was a
wall of five headlines nobody reads at thumbnail size.

What survives is the one placement with an intent signal behind it: the
expanded highlight, where there is room for a label and the reader has
already said which claim they care about. HighlightItem's header row goes
back to its original shape, since the nested button only existed for the
row icon.

Also lets a preview open its flags on the Vercel host. The daily.dev
preview domain is only assigned once a PR is marked ready for review,
while Vercel's own host is there from the first push, and nothing in
production is served from vercel.app.
…light

The expanded footer offered only Snapshot, while the review page paired it
with copy link — the pair the selection bar on the post page already ends
on. The two ship together as HighlightShareActions, so the review page
renders the production control rather than a copy of it.

Keeping the pair in its own component also keeps the copy hook out of
HighlightItem: useCopyText reaches for the toast, which needs a
QueryClient, and the flag is off for everyone by default.
@tomeredlich tomeredlich changed the title feat(snapshot): put the Happening Now placements on the real product feat(snapshot): snapshot an expanded highlight on Happening Now Sep 1, 2026
The same deployment is served from vercel.app, but api.daily.dev rejects
that origin on CORS: boot and every query fail, hydration dies, and the
page renders its SSR output inert. Opening the flags there advertises a
placement on a host that cannot reach the data behind it.

Leaves the note about why, so the next attempt does not repeat it.
@tomeredlich

Copy link
Copy Markdown
Contributor Author

Superseded by #6570, which sits directly on main rather than on snapshot-post-page-variations. This one carried 61 files and inherited that branch's conflicts; #6570 is 27 files and merges clean. Closing.

@tomeredlich tomeredlich closed this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant