feat(snapshot): square share images for every daily.dev surface - #6544
Open
tomeredlich wants to merge 18 commits into
Open
feat(snapshot): square share images for every daily.dev surface#6544tomeredlich wants to merge 18 commits into
tomeredlich wants to merge 18 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Sep 2, 2026
Storybook-only. Every surface in the sharing initiative gets a review page, and the snapshot cards those pages render come with them. - Fourteen card components under features/snapshot, drawn as twenty variations. Each is a real React component rasterized off-screen by snapdom, so the pages show what would ship. The background gradient is seeded from the subject's id, so a given post or profile always produces the same image. - A page per surface under Features/Snapshot/Surfaces, plus Share images (every card), Button placements (where the control sits), the sharing map (which surfaces lead with a link and which with an image), the spec, and the edge cases each card degrades through. - SnapshotButton and the imageShare utils, which the pages need to render and export a card. No app surface is touched: nothing under components/cards, components/post, components/profile, features/profile or webapp changes, and the review pages draw the surrounding UI themselves rather than mounting the real components. Placement is argued in Storybook here; wiring it into the app belongs to the per-surface PRs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CEO's note on surfaces 1, 1b, 2, 4 and 12 was that the export loses to a plain screenshot, because a screenshot carries more text than we do. Two causes. The square was one: every card was locked to 1080x1080, so a title and a summary had to fit a fixed box and the clamps cut them. Frames now take a `grow` flag that lets the height follow the content, floored at the square and capped at 9:16, and the capture measures the mounted card instead of assuming it. The five text surfaces drop their line clamps. The other was the highlight card: it exported only the marked run, which threw away the point the reader was making by marking it. It now takes the passage plus the selection, windows the passage around the mark, and sets the mark apart the way the page does.
Surface 1 exported a summary of a post; the reference the CEO shared was a screenshot of the page itself. So the card now carries what the page shows — the source with its Follow affordance, the headline beside its thumbnail, the date/read time/domain line, the whole TLDR, the tags and the engagement counts. The page's own controls are dropped: a still image has nothing to press, so "Read post" and the overflow menu are noise. At 780 the card could not hold that without setting the headline four lines deep next to a stamp-sized thumbnail. Frames take a `wide` flag that trades the gradient stage for a gradient border — 964 wide, tighter padding — which is the room the article needed. Only the post card uses it for now.
Widening the card bought room and then left it as padding and gaps. The reclaimed space goes to the article instead: the inner padding drops from 40 to 32, the block gaps tighten, and the headline, TLDR, meta and counts all step up a size. The frame grows to absorb it, so nothing is cut.
The summary is the part worth sending, so it goes first and in white at headline scale, and the rest of the card credits it: title, source, date, under a rule. The page furniture comes off — thumbnail, follow, tags, upvote/comment/impression counts, read time and domain. It competed with the copy for the room we had just freed up, and a still image has nothing to press.
Bold at that size read as a headline, which put it in competition with the actual headline below it.
1008 instead of 964, so the purple reads as a border on the black box rather than a stage it sits on. The frame pads by the same gutter, so the card gains on all four sides.
Three things, all about the same card. The square was still a floor: a growing frame could get taller but never shorter, so a short TLDR paid for dead gradient underneath it. The floor comes off in both the frame and the capture, which now measures the card in both directions. The TLDR was still styled as the display copy it used to be — balanced wrapping and negative tracking, at 1.35 leading. All three fight a normal weight paragraph, so it reads as body text now: plain wrapping, no tracking, 1.55 leading. Source and date share a line, dot-separated. Two stacked lines of grey read as two separate facts; the credit is one.
The headline goes, and the rule under the copy with it. The TLDR already says what the headline says, at more length and in the reader's interest; printing both made the card argue with itself. With one block of copy and one line of credit there is nothing left for a divider to separate — the space does that on its own.
Bold, under the TLDR, above the credit. No rule between them — the gap reads as the break now that the copy is one block.
The TLDR gets 44px of air before the rule and the credit block 32 after, so the break belongs to the copy above it rather than sitting flush.
Headline, source and date come off, and the rule with them. What is left is the summary and the daily.dev mark, which is attribution enough for an image whose only job is to carry the text.
Source and date come back under the rule, so the image says where the text came from. The headline stays off: the TLDR already says what it says.
They sit next to each other everywhere this feature gets reviewed, and a highlight is a post's own text, so looking like two different products was the wrong signal. The copy scale moves into snapshotText as one function both cards call, the passage drops to body weight and 1.55 leading, and the credit is the post card's line — round avatar, one row, dot-separated. The post title comes off the highlight card with it: the passage is the subject, and the title was a second headline competing for the same job.
Surfaces 1, 1b and 2 were three cards that happened to carry text. They are one card now: white copy at a single size over the wide frame, credited to its source, and nothing else. What came off along the way — the highlight card's surrounding passage, quote glyph and URL, the post card's date, the Happening now eyebrow, headline and timestamp. Each was a second thing competing with the copy for the same glance. The size is a constant rather than a scale. These frames grow and shrink to fit now, so type no longer has to shrink to reach the bottom of a fixed square, and a shared image that changes size with its length reads as two different cards. The credit block moves into SnapshotCredit so the two cards cannot drift apart.
An optional eyebrow on the post card, painted with the surface's own wordmark gradient. It says which part of the product the copy came from, which the TLDR alone cannot — and unlike the headline it does not repeat what the copy already says.
The label naming the surface — Hot take, Reading overview, Squad, Level up and the rest — sat above the card's content, where it read as a headline for the copy or the stats under it. It is a sibling of the mark, not a title, so it rides the logo row on the far right. One SnapshotEyebrow now draws all of them, fed through the frame's new logoAside slot. Three separate implementations go away with it: the inline spans in the entity and list cards, and SnapshotIdentity's trailing label, which no card passes any more. Surfaces 4 and 12 also become the post card's treatment — a watercooler post is a person's own words and a comment is too, so both are copy at one size credited to whoever wrote it. The discussion card drops the "From the discussion" label, the post title and the upvote/reply counts with it. The hot take stops splitting one sentence across two type styles.
A hot take is one short statement, so it and its upvote count sit centred under the mark rather than ranged left in a column they do not fill. The invite card's link had a rule above it that separated nothing: the whole card is one invitation, and the space already sets the URL apart.
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.
Turns any daily.dev surface into a branded 1080² share image. It is easier to review in Storybook than in the diff — links at the bottom.
The one idea
An image is one action, not a mode. A link needs a destination — that is why the share sheet has twenty. An image does not: the OS share sheet and the clipboard are the destination, and no web share intent accepts an image anyway. So the image owns its own button and everything else keeps sharing the link. That is what keeps this on one screen instead of a two-step wizard.
What is here
useSnapshotCaptureso the dropdown and the modal section render from one implementation. ItsisActivegate does two jobs: a feed never carries a 1080px card per item until someone asks to share, and because the modal pre-renders on open, the click reachesclipboard.writewith noawaitin front of it — still inside the user gesture the clipboard requires.packages/storybook/vercel.json— build config for the storybook package's Vercel project.What a reviewer should weigh first
The control is wired into real surfaces, with no feature flag. It is not fixture-only:
ProfileHeader, the three profile widgets,AchievementCard,HotAndColdModal,HighlightItem,LeaderboardListItem,FreeformListandPostFocusCardall mount it, reading their own data. Merging puts a Share control on those surfaces for everyone. If that should go out behind a flag, this is the PR to say so on.Storybook fixtures are only what the review pages feed the cards; the surfaces themselves are live.
Events
None.
Experiment
None — see above.
Testing
typecheck-strict-changedclean; eslint clean on every touched file.pnpm --filter storybook buildverified.Review links
Storybook is the review surface — start at the Overview:
Per-surface pages live under
Features/Snapshot/Surfacesin the same build; the sibling PRs in this initiative link their own.In the product, on the preview:
Preview domain
https://snapshot-share-images.preview.app.daily.dev