feat(snapshot): snapshot an expanded highlight on Happening Now - #6570
Open
tomeredlich wants to merge 15 commits into
Open
feat(snapshot): snapshot an expanded highlight on Happening Now#6570tomeredlich wants to merge 15 commits into
tomeredlich wants to merge 15 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tomeredlich
force-pushed
the
snapshot-happening-now
branch
from
September 2, 2026 15:07
e11ab2f to
220744a
Compare
tomeredlich
force-pushed
the
snapshot-happening-now
branch
from
September 3, 2026 09:30
220744a to
bbe1546
Compare
tomeredlich
force-pushed
the
snapshot-happening-now
branch
from
September 3, 2026 13:09
e0216a8 to
cc74e39
Compare
tomeredlich
force-pushed
the
snapshot-happening-now
branch
from
September 3, 2026 13:52
cc74e39 to
1269d52
Compare
tomeredlich
marked this pull request as ready for review
September 3, 2026 13:57
tomeredlich
added a commit
that referenced
this pull request
Sep 3, 2026
Replace the spinning avocado arrow with CopyStateIcon, taken verbatim from snapshot-happening-now so the two branches cannot drift: the link glyph and a green check share one grid cell and cross-fade over 200ms on easeOutExpo, collapsing to an instant swap under prefers-reduced-motion. Nothing resizes mid-swap, which the spin could not promise. Drop the copy-confirm keyframes with it, now that nothing references them. On the squad directory cards the control loses its fill and stroke, and takes the Medium size SquadActionButton leaves Button on, so it lines up with Join rather than sitting a step smaller beside it.
tomeredlich
added a commit
that referenced
this pull request
Sep 3, 2026
#6570 extends CopyStateIcon for the same reason — a copy control that rests on a link glyph — and lands on a green VIcon that fades in, with the resting glyph passed as `icon`. This branch had an avocado UpvoteIcon spinning 360° through a `copy-confirm` keyframe, under a prop called `idle`. Both PRs touch the same file, so this takes #6570's component and spec verbatim and renames the call sites. The keyframe goes with the spin: nothing references it once the check lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 3, 2026
Every highlight 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 beats the link: a URL sends someone to a page that has already moved on. Snapshot and copy link sit opposite Read more once a highlight is open. Expansion is the intent signal, so a scan of the feed stays a scan. Behind snapshot_highlight_expanded, off. The card takes its timestamp from the formatter the row already renders, so the two cannot disagree about how old the claim is, and it no longer prints the channel, which is a slug rather than the tab's display name. /dev/snapshot-happening-now renders the production row with the flag pinned through the harness context, so the review surface exercises the shipped component instead of restating its markup. Mockup-to-eng-pass: 1
Snapshot on an expanded highlight now matches the copy link beside it — tertiary and icon-only, so the footer reads as two peers rather than one button and one afterthought. Marking text inside a highlight's TLDR raises the same selection bar the post page uses: snapshot the quote, copy it, or copy the link. The bar, the card and the selection hook come over from the post page branch; whichever lands second drops its copy. Copy link joins three more places, each pointing at what the reader is looking at: the feed card header on hover, a headline row on hover (carrying that highlight's id), and the page header. The flag becomes happening_now_share, since it no longer gates only the expanded snapshot. Still off by default. Mockup-to-eng-pass: 2
Every copy control on Happening Now swapped its glyph for nothing: the toast was the only acknowledgement, and it lands in a corner away from the press. The icon now crossfades to a green check and back after a second, so the confirmation arrives where the eye already is. CopyStateIcon comes from the split share button work, widened to take the resting glyph so a link button can use it too. It ships there as well; whichever lands second drops its copy. Mockup-to-eng-pass: 3
The square held the copy to whatever fitted it: type stepped down as a claim grew, so a long TLDR and a short one shipped as visibly different cards, and a short one padded the rest out with dead gradient. The frame now grows to fit and stops at 9:16, the card widens to 1008 with tighter padding, and the copy sets at one size throughout. The Happening Now wordmark moves onto the logo row as an eyebrow, and the headline comes off the card — the TLDR already says what it says, at more length. The selection card loses its display quote marks and takes the same treatment. Capture height is measured at the press rather than assumed, since a frame that grows only has one once it is mounted. Ported from #6544. Its SnapshotButton rewrite is left there: this branch keeps its own trigger. Mockup-to-eng-pass: 4
tomeredlich
force-pushed
the
snapshot-happening-now
branch
from
September 8, 2026 09:44
0d68d3d to
d15341b
Compare
Take the merged captureShareImage from #6426 with the width, height and capture timeout the snapshot cards need. The designed cards carry their own logo, so the capture options opt out of the frame's logo bar. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main now owns the snapshot foundation this branch vendored (#6556 and #6544), so every add/add conflict takes main's version: SnapshotButton, SnapshotFrame, HighlightTextSnapshotCard, snapshotGradient, snapshotText, useTextSelection, captureShareImage, copyShareImage and its spec. CopyStateIcon, SnapshotCredit, SnapshotEyebrow, snapshotCapture, the icon and the shutter CSS merged cleanly because they already matched main. The highlight placement is ported onto main's APIs in the commits that follow.
…hot parts The branch carried its own copies of what #6556 and #6544 shipped: a second selection bar, a second highlight card and a card-mounting button. The placement now runs on main's versions. - The expanded row's Snapshot mounts its card through useArmedCard, on hover, touch or focus, instead of one off-screen 1080px card per expanded highlight at render. The card is HighlightTextSnapshotCard with the "Happening now" eyebrow on its logo row, and the wordmark gradient lives next to it in shared (Storybook keeps its own copy). - The quote bar over the TLDR is main's selection bar. Its body is SelectionShareBar now, taking the link, seed, source and a share callback, and SelectionSnapshotBar wraps it for posts, so the post page keeps its API and events unchanged. - The clipboard gets the image only, as #6556 decided; the old button wrote the link beside it. - Copy link on the expanded row goes through useCopyPostLink with `shorten`, like the post page's copy placements, so the write stays in the gesture and signed-in shares carry the referral campaign. - Every action now logs `SharePost` on the highlight's post with `provider`, a distinct `origin` and the highlight id (plus `result` for a snapshot). Nothing was logged before. New origins: `happening now highlight` for the row and `happening now selection` for the quote bar. - The flag is read with useConditionalFeature, evaluated only once a row is expanded.
…copy links - With the flag off, the copy link rendered nothing but still owned the `ml-auto` that the feed card's options menu used to carry, so the menu slid left against the title for everyone. The menu keeps `ml-auto` unless the copy link is there to take it. - The page title took `flex-1` to push the copy link right. Its wordmark gradient is sized to the element's box, so a full-width h1 showed only the blue end of it, again for everyone. The title keeps its width and the copy link takes `ml-auto`. - The card's hover-revealed copy links were only transparent, so on touch screens they were invisible buttons that swallowed taps on the header and on each row's timestamp. They take pointer events only while the card or row is hovered; focus still reveals them for keyboard users. - The flag is read once per card and once per page with useConditionalFeature, and a disabled placement renders nothing, so the card no longer needs a query client when the flag is off. - Each copy now logs. A row's link is a share of that highlight's post (`SharePost` with the highlight id); the page link has no post, so it logs `ShareHighlights`. Origins: `highlights card` for the feed card, `happening now` for the page header. - getHighlightsUrl moved to lib/links for the copy link; the card module no longer re-exports it and its one other caller imports it directly.
… forcing #6556 took these out of the snapshot foundation and they came back with this branch: - useSharePlacement and isPreviewHost turned every share placement on for anyone on a `.preview.app.daily.dev` host, bypassing GrowthBook. The placements read `happening_now_share` with useConditionalFeature, and a preview is reviewed by opening the flag like any other. - shutterSound.ts and public/sounds/shutter.mp3 had no caller left once SnapshotButton came from main. - pages/dev/snapshot-happening-now was a review harness with stubbed auth, logging and flags; `/dev/*` pages do not ship. The flag comment now lists every control it gates.
…hips The surface story drew a fake Happening Now page to argue for the expanded-row placement. The placement is real now, so the live page is the reference, the same way #6556 retired the post page mockup. The overview row points at this PR.
Product decided the sharing placements go out to everyone, so happening_now_share is gone along with every read of it. The page header's copy link, the feed card's copy links, and an expanded highlight's copy link, snapshot and quote bar now always render. The feed card's copy link carries ml-auto, so the options menu stays pinned to the right with no conditional class, and the page h1 keeps its intrinsic width so the wordmark gradient does not stretch. The flag-off test is dropped. The card and row specs render inside TestBootProvider, since the controls need the query client and log context wherever the card or an expanded row mounts.
The page header link on /highlights, the feed card's header link and its per-row links all built their URL from `webappUrl`, which is a bare `/` on the webapp, so the clipboard got `/highlights` or `/highlights?highlight=<id>`: a path that means nothing once pasted anywhere else. A row now copies the highlight's post permalink, the same link the expanded row on /highlights already copied and the one both of them log as `share post`. The deep link was the weaker target: /highlights is statically generated, so a crawler gets the generic page title, the default image and `og:url` pointing at /highlights whatever the query says, and the `highlight` param only expands a row that is still in the loaded list. The post page previews with the headline, the TLDR and the post's own image. The page links resolve `/highlights` against the current origin the way `agentShareLink` does, since the share pipeline runs `new URL(link)` and threw on the bare path. Both go through `useCopyLink`'s `shorten` path with a campaign, as PostMenuOptions does: the long link is written inside the click, then the short link carrying `userid` and `cid` replaces it. The page gets its own `share_highlights` campaign, added to the join page's campaign map, which has to list every key.
The expanded-highlight card carried the "Happening now" eyebrow but no
credit, and the quote card over the TLDR carried neither, where the briefing
and post-page cards name the source under a rule. An image passed around
without a byline reads as daily.dev's own words.
The highlights feed fragment now asks for the post's `source` and `domain`
(and the shared post's, which is where a share's TLDR comes from), and both
cards take `snapshotSource` of the same post the TLDR is read from.
`snapshotSource` accepts any `{ name, image }` source rather than the full
`Source`, which is all it ever read. The quote bar passes the eyebrow through
to its card so both images carry the same label.
The expanded row's copy link is now the same control as the feed card's
rows, so one highlight is copied, tracked and logged the same way on every
surface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Share controls for Happening Now, built on the snapshot foundation from #6556 and the cards from #6544. There is no feature flag: every control below ships to everyone on merge.
What ships
HighlightItem): copy link and a Snapshot button sit opposite "Read more". Snapshot captures the TLDR as a 1080-wideHighlightTextSnapshotCardwith a "Happening now" eyebrow on its logo row and the post's source credited under a rule, and puts the image on the clipboard (download as the fallback). The card mounts throughuseArmedCardon hover, touch or focus, never one per row at render. Collapsed rows are unchanged.HighlightGrid/HighlightList): a copy link in the header for the page, and one per row for that highlight. Both appear on hover or keyboard focus, and take no taps while hidden. On touch they stay hidden; the page header and the expanded row cover touch.Links
Every copy link puts an absolute URL on the clipboard and goes through
useCopyLink'sshortenpath with a campaign, asPostMenuOptionsdoes: the long link is written inside the click (Safari refuses a write after an awaited shortener), then the short link carryinguseridandcidreplaces it.cidshare_post/highlightsresolved against the current originshare_highlights(new)One highlight, one link: the post. Before, a feed card row copied the highlights deep link (
/highlights?highlight=<id>) while the expanded row copied the post, both logged asshare post. The deep link is the weaker destination for whoever receives it:/highlightsis statically generated, so a crawler gets the same page for everyhighlightvalue: title "Highlights | daily.dev", the generic page description, the default daily.dev image, andog:urlpointing athttps://daily.dev/highlights. Checked against production with a Slackbot user agent.highlightparam only expands a row that is still in the page's loaded list, so an older highlight lands on the plain page.og:title, the TLDR asog:descriptionand the post's ownog:image, and it is what theshare postevent already names as the target.The page links used
webappUrl, which is a bare/on the webapp, so the clipboard got/highlights. They now resolve the path againstlocation.originthe wayagentShareLinkdoes (in the extensionwebappUrlis already absolute and passes through unchanged), which also stops the tracked-link step from throwing onnew URL('/highlights').Where to look first
features/snapshot/SelectionSnapshotBar.tsx: the bar's body is nowSelectionShareBar(link, seed, source, label, share callback).SelectionSnapshotBarwraps it for posts with the same props and events as before, and the quote bar here uses the generic one. WithsnapshotSource's wider parameter (item 5), these are the only changes to feat(snapshot): share placements for the post page #6556 files.components/highlights/CopyHighlightsLink.tsx: the one copy control every surface uses, and the link and campaign choice above.features/snapshot/HighlightShareActions.tsx: the expanded row's controls and quote bar, thehappening noweyebrow gradient, and the credit passed to both cards.features/snapshot/useLogHighlightShare.ts: the event shape below.graphql/highlights.ts: the feed fragment now asks for the post'ssource { name image }anddomain, and the shared post's, since a share's TLDR comes from the shared post.snapshotSourcetakes any{ name, image }source instead of the fullSource, which is all it ever read.components/cards/highlight/common.tsx: the feed card header, where the copy link carriesml-autoso the options menu stays on the right.Events
A highlight points at a post, so its shares are
SharePoston that post (target_id= post id,target_type=post) withhighlight_idinextra, the same shape as the post page's placements, and every one of them copies that post's link. The page link has no post, so it logs a newshare highlightsevent and copies the page.provideroriginshare postcopy linkhappening now highlighthighlight_idshare postsnapshothappening now highlighthighlight_id,resultshare postsnapshothappening now selectionhighlight_id,resultshare postcopy linkhappening now selectionhighlight_idshare postcopy texthappening now selectionhighlight_idshare highlightscopy linkhappening nowshare highlightscopy linkhighlights cardshare postcopy linkhighlights cardhighlight_idresultisclipboard,downloadorerror. The post page's quote bar still logsorigin: text selection, unchanged. Visits through a copied link carrycid=share_postorcid=share_highlightswith the sharer'suserid.Experiment
None. The controls ship to everyone with no flag; the events above are how their use is read.
Testing
node ./scripts/typecheck-strict-changed.js origin/main: passed.tsc --noEmit: no errors outside the known__tests__backlog.pnpm --filter shared test: 408 suites, 2960 tests passed (the script exits 1 afterwards on the known log-after-teardown warning;NODE_ENV=test npx jest --runInBand --silentexits 0 with the same counts).pnpm --filter webapp test: 86 suites, 689 tests passed.http://localhost/highlightswithwebappUrlmocked to/(the webapp's value), and a row copies the post permalink without opening the highlight.cid(share_highlightsfor the page,share_postfor a row) and the sharer'suserid.SharePostwith the highlight's post and origin.share highlights. The existingSelectionSnapshotBartests pass unchanged against the refactor.To try it on the preview, visit
/highlights, copy the header link and a row's link, and expand a headline to snapshot its TLDR or a quote from it.Preview domain
https://snapshot-happening-now.preview.app.daily.dev