Skip to content

feat(snapshot): square share images for every daily.dev surface - #6544

Draft
tomeredlich wants to merge 138 commits into
dev-take-picfrom
snapshot-share-images
Draft

feat(snapshot): square share images for every daily.dev surface#6544
tomeredlich wants to merge 138 commits into
dev-take-picfrom
snapshot-share-images

Conversation

@tomeredlich

@tomeredlich tomeredlich commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Stacked on #6426 — base retargets to main once that merges. The diff here is one commit.

Adds a Snapshot control that exports a surface as a branded 1080×1080 PNG, and the twenty card designs it captures. Easier to review in Storybook than in the diff: Features/SnapshotShare images (every image), Spec (dimensions and content contract), Edge cases (how each card degrades).

Changes

  • Capture builds on feat: add captureShareImage util for element-to-OG-image capture #6426's util: each card is a real React component rendered off-screen, rasterized by snapdom, composed onto one canvas. The background gradient is seeded from the subject's id, so a post or profile always produces the same image.
  • Twenty cards across the sharing-initiative surfaces — post, highlighted text, Happening Now, leaderboard rank, watercooler, hot take, profile, reading overview, badges, achievements, the collectible achievement slab, invite, streak, tag/source/squad, discussion, briefing, best-of, level up. Tag/source/squad share one component, as do briefing/best-of: each pair is the same object with a different label.
  • The button plays a shutter and sweeps a highlight across its face on press. Beside the bookmark on watercooler cards, on hover in leaderboard rows and achievement cards, inline elsewhere.
  • Content rules — quotes cap at 280 characters and truncate at the last word rather than being refused; headlines balance line lengths; long URLs and type names wrap inside the card; missing copy collapses instead of leaving an empty band; cards cannot outgrow the square, so a capture never clips the logo.

Not in this PR

Design review only. No data wiring — every card reads Storybook fixtures. No feature flag. No share flow: the button downloads a PNG, and onCapture is the seam for whatever replaces that. RTL is not handled yet.

Events

None.

Experiment

None.

Testing

  • shared: 365 suites / 2,579 tests. webapp: 74 / 587.
  • typecheck-strict-changed clean; eslint clean on every touched file.
  • pnpm --filter storybook build verified, and the static bundle re-checked in a browser — cards render and capture from the production build.
  • Two overflow bugs found and fixed by the edge-case page: cards could grow past the square and clip the logo, and the streak card overflowed its own card at typical values.

🤖 Generated with Claude Code

The surface pages

The ten pages under Features/Snapshot/Surfaces each used to draw the surface as it ships, the placement we want, and a louder alternative or two. They now show only what the sibling PRs implement, labelled with the flag that gates it where there is one — the before halves and the options we did not take are gone, along with the props and page furniture that existed only to switch between them.

Review links

rebelchris and others added 30 commits August 6, 2026 14:00
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ass card (#6462)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…rfaced (#6463)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#6461)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
I had only drawn the profile list row. The surface people actually meet a
hot take on is HotAndColdModal: an emoji tile, centred Title3, the quote
in Body-tertiary, an upvote pill and a bordered author footer, with the
three reaction buttons and 'Add your own hot take' beneath.

The snapshot already ships there, on the top card only and at Float,
beside the upvote pill — so the profile-list placement is a placement of
an existing card, not a new feature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each section is now Today plus the chosen option, with the alternatives
removed:

- Swipe modal: snapshot promoted from Float to labeled and filled.
- Profile list: snapshot beside the upvote at XSmall.
- Reading history: an icon-only copy link before the menu, always
  visible rather than hover-gated.

Dead 'lead' branches dropped from the list and history rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both snapshot variants removed, along with the now-unreachable snapshot
target inside InviteScreen. The section keeps its Today state as a
reference point and states the conclusion outright: no change proposed,
because an image of a referral cannot be clicked and the page already
carries the most complete share UI in the product.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The settings page is complete and unreachable: a labelled TextField, a
Primary Copy link and the seven-target row, buried three levels into
settings, long after the moment a new account is most willing to bring
someone with them. So the invite is not a payload problem, it is a
placement one — which makes it a surface of its own rather than a
suggestion attached to the feed export.

The new page draws #6366's step on the real funnel chrome:
FunnelStepCtaWrapper's glass branch, the 32rem rail, StepHeadline, three
invite slots, the glass bar CTA and step dots. Snapshot is deliberately
absent throughout — a referral image cannot be clicked.

Invite & feed export becomes Copy my feed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scoped to custom feeds and drawn where it belongs: FeedSettingsGeneralSection
already gates feed name, the emoji picker, the default-feed toggle and
delete behind isCustomFeed, so an export is a property of a feed you
built rather than a control on the feed header. The 'Share this feed'
block sits between the default toggle and Happening Now placement.

Replaces the invented feed-header mock with the real modal: seven tabs,
Cancel and a disabled Save, and every General block in its shipped order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 4 commits September 2, 2026 09:28
Pick emoji and the snapshot control were filling the panel width because
the block is a flex column. Both now size themselves, matching Make
default and Delete feed at w-40. 'Choose an icon' also has no description
in production, so the empty line is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reframed on the product call: a custom feed is something you built, so
the thing worth sending is the feed, not a picture of its posts. Copy
link leads, the top-20 text list becomes the fallback, and snapshot is
out entirely — which revises #6362's sharing-map row.

Adds the recipient side, which is what makes the share worth anything:
open the link, see whose feed it is and what adding it will do, get a new
feed named after the sharer. FeedSettingsCreate's
/feeds/new?entityId=&entityType= flow is the precedent — same thing with
a set instead of a single tag. Also drawn: the added state, and the feed
limit, which FeedSettingsCreate already treats as a real error.

Four blockers named, three of them not frontend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Copy on add, not a live subscription.
- Sign-in required: a feed lives in an account, so a session-less visitor
  gets a sign-in state and opens the link again afterwards. No logged-out
  preview, which is also why the sample posts are dimmed and not links.
- 'Just show me the posts' removed, and the feed-limit copy now names the
  only two ways out — delete a feed or upgrade.
- The remaining open question restated plainly: whether an added feed
  consumes a feed slot.

The story was titled 'Copy my feed' while its heading read 'Share my
feed', which made the two halves look like separate pages; renamed so
the sharer and recipient sections are visibly one page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add stays live and the failure comes back as a real toast — NotifContainer
with the Error variant's AlertIcon — naming both ways out. Nothing is
disabled up front, because we cannot know the recipient is at their cap
until they press, and a dead button explains nothing.

That also settles the last open question: an added feed uses a slot like
any other, so there is one kind of feed, one limit and one message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 2 commits September 2, 2026 11:12
Three more celebrations already ship with no share route, drawn before
and after: TopReaderBadgeModal, which already generates an image for its
Download button and shares none of it; the ten-tier streak milestone
system in streakTiers.ts, which is far richer than the day count we had
been drawing; and ReputationPrivilegesModal.

Six more listed rather than drawn, because each needs a call on whether
the moment is worth interrupting: the achievement unlock modal, levelling
up, curating the profile showcase, being awarded, a post that did well
via analytics or a boost, and going Plus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d Plus

Being awarded and a post that did well move from the listed set into
drawn before/after frames, joining the top reader badge and the streak
tiers. Reputation privileges and going Plus removed.

The awards moment is drawn from the recipient's side — ListAwardsModal's
cores total and giver list — and analytics gets two placements, an icon
in the header and a labeled control under the stats where the numbers
have just made the case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 3 commits September 3, 2026 10:11
…o choose from

The Snapshot button becomes a Share button: the icon and label are the ones
people already recognise, and pressing it opens a menu that previews the
designed 1080² card before offering it.

The menu opens immediately with a skeleton rather than waiting on the render.
Rasterizing takes a second or two, and a control that does nothing visible for
that long reads as broken — so the capture is deferred a tick, letting the menu
paint first, and Copy link stays usable while the card is still rendering.

Seven layouts are up for review in Features/Snapshot/Menu styles, all live
against the same card: rows, rows-centred, an overlay pill on the card, a
compact no-preview variant, Tsahi's split button from #6369, and the two ways
the image can sit in the share modal — its own section or a single tile. The
`menuVariant` prop exists to serve that page and comes out with the losers.

The image is one action, not a mode over the sheet's other targets. A link needs
a destination, which is why there are twenty of them; an image does not, because
the OS share sheet and the clipboard are the destinations, and no web share
intent takes an image anyway. So the image owns its own action and the squads
and external tiles keep sharing the link — which is what keeps this on one
screen instead of two.

Pressing it hands the PNG to the native sheet where the platform takes files,
copies it to the clipboard otherwise (with a toast, since the clipboard has no
UI of its own), and downloads it only as a last resort.

Capture moves into `useSnapshotCapture` so the dropdown and the modal section
render from one implementation. Its `isActive` gate matters twice: a feed never
carries a 1080px card per item until someone asks to share, and because the
modal pre-renders on open, the click handler reaches `clipboard.write` with no
await in front of it — still inside the user gesture the clipboard requires.

Two surfaces had no card at all, from the #6579 and #6581 reviews:

- `FeedSnapshotCard` carries a shared feed's recipe — tags and source count —
  rather than its posts, which are a standing filter and would go stale.
- `AwardSnapshotCard` names the sender. It is the only status moment that comes
  from someone else, and a gift with no giver reads as self-congratulation.

Tsahi's `SplitShareButton` and `CopyStateIcon` come across from #6369 unchanged
apart from four additive props — `onOpenChange`, `header`, `menuLayout` and a
width override — so his grid is untouched when nobody passes them.

`SocialShareList`, `SocialShareButton` and `SocialShareContainer` gain optional
size and compact props, defaulting to today's values, so the dropdown can
tighten without moving anything else in the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fixture pinned `endDate` to a literal 2026-09-01, which passed, so the
running-sale test started failing on its own. Main already replaced it with a
relative date and a comment saying why; this branch forked before that landed.

Kept as its own commit so it can be dropped when the branch rebases onto main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two corrections after catching up with the branch.

FeedSnapshotCard goes: ac9dacd already settled that a custom feed is
shared as the feed itself, not a picture of its posts, and took snapshot
off that surface entirely. The card was written from the #6579 review
page without checking that decision first.

The clipboard write now goes through copyShareImage instead of calling
`clipboard.write` inline. The util hands ClipboardItem a promise rather
than a resolved blob, which is what keeps Safari from dropping the write
for losing the gesture — and there is no reason for two of these.

The toast moves to ToastType.Success rather than a ✅ in the string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ten surface pages each drew the surface as it ships, the placement
we want, and one or two louder alternatives. That is the shape of a
decision being made, not one already taken — and the pages are read now
to review the placements the sibling PRs implement.

Each page keeps only what those PRs ship, labelled with the flag that
gates it where there is one. Gone: the ⋯-menu and share-sheet route on
the post page, the seven-post-type audit, the four directory surfaces
before their copy link, the hot-take and history rows before theirs, the
labeled-and-filled pushes, the profile's visitor menu, and the three
status moments we listed without drawing. The props and furniture that
existed only to switch between the two halves go with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

7 participants