Skip to content

feat(snapshot): share a hot take as an image, and copy a link from reading history - #6610

Open
tomeredlich wants to merge 8 commits into
mainfrom
claude/snapshot-hot-take-placements
Open

feat(snapshot): share a hot take as an image, and copy a link from reading history#6610
tomeredlich wants to merge 8 commits into
mainfrom
claude/snapshot-hot-take-placements

Conversation

@tomeredlich

@tomeredlich tomeredlich commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Standalone — base is main. Carries the snapshot capture path, the hot-take placements, the reading-history copy link, and the Storybook page that specs them.

What changes for the reader

Snapshot a hot take. A hot take is a self-contained opinion with nowhere to link to, so the card is the whole payload and an image is the only share that carries it. The image is the square card #6544 designs — 1080×1080, a gradient seeded from the take's id, the eyebrow, the take, the emoji as a watermark, and the count read as agreement rather than a score. It is rendered off-screen and rasterized; it is not a photograph of the row on screen.

  • Swipe modal (HotAndColdModal.tsx) — beside the upvote pill, labeled and filled, on the top card only. The cards stacked behind are mounted too, so an unguarded control would offer to capture a take the reader has not reached.
  • Profile list (HotTakeItem.tsx + .v2) — icon-only at XSmall to match the upvote counter, placed before it so the count stays at the edge. Both variants carry it: engagement_bar_v2 decides which renders, not whether a take can be shared.

Copy a link from reading history. A history row is a pointer back to a post, so copying its link is the share that fits it. Icon-only before the ⋯ menu, always visible rather than hover-gated so it survives touch. CopyStateIcon confirms on the button itself — the copy glyph and a green check share one grid cell, so nothing beside them moves during the swap, and it collapses to an instant swap under prefers-reduced-motion.

Gated on showCopyLink, which only the history list passes, so the reading-history modal keeps the DOM it has.

The capture path

captureShareImage rasterizes through @zumer/snapdom; copyShareImage puts the PNG on the clipboard with the link beside it as text, so a rich composer takes the image and a plain one takes the link; downloadShareImage is the fallback when the clipboard refuses; shutterSound plays once per press without ever failing the capture.

SnapshotButton mounts its card off-screen for as long as the button is mounted, so the capture starts inside the press — Safari only honours a clipboard write in the task that handled the gesture.

SnapshotFrame, SnapshotContent, snapshotGradient and snapshotText are copied from #6544 so this PR stands alone. They overlap with that PR; whoever merges second resolves it.

Storybook

Features/Snapshot/Surfaces/Hot takes & history draws all three placements at desktop, tablet and mobile. Controls there are inert — the page compares placement, not behaviour.

Testing

  • SnapshotButton.spec.tsx: copies and reports, falls back to download when the clipboard refuses, reports a failed capture rather than throwing, names both halves when a link is passed.
  • HotAndColdModal.spec.tsx: one control, top card only, with a second take stacked behind.
  • ProfileUserHotTakes.spec.tsx: one control per take, visitors included.
  • ReadingHistory.spec.tsx: copies the right link and swaps to the copied state; absent by default.
  • Two existing modal specs needed a QueryClientProvider (SnapshotButton reports through useToastNotification), and three text queries now skip the aria-hidden capture card. Both handled in this pass.
  • shared 381 suites / 2721 tests, webapp 86 / 683, extension 6 / 52. typecheck-strict-changed clean, shared lint clean.
  • Lockfile adds @zumer/snapdom only; verified --frozen-lockfile accepts it unchanged.

Decisions

  • No feature flag, by design. Both controls ship to everyone on merge.
  • The copy link keeps its toast. The inline green check and the "Copied link to clipboard" toast both fire, deliberately: the check confirms at the button, the toast confirms away from it.

Review links

Preview domain

https://claude-snapshot-hot-take-placeme.preview.app.daily.dev

tomeredlich and others added 5 commits September 2, 2026 17:47
Adds one Storybook page — Features / Snapshot / Surfaces / Hot takes &
history — recording where a share control belongs on the two hot-take
frames (the swipe modal, the profile list) and on reading history. Each
surface is drawn at desktop, tablet and mobile, with what ships today
beside the placement the page argues for.

No production surface changes. The controls are inert: the page compares
placement, not behaviour. SnapshotIcon comes with it because the page
draws it and it is not on main yet.

Mockup-to-eng-pass: 1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each of the three surfaces was drawn twice, before and after. A review
page for a change should show the change, so the before halves go: the
modal's Float snapshot, the profile list with no share route, and the
history row's ⋯ menu. The placement prop that switched between them, and
the menu furniture only the history row needed, go with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reading-history row is a pointer back to a post, so copying its link
is the share that fits it. The control sits before the ⋯ menu, icon-only
because the row already drops its vote buttons below laptop, and always
visible rather than hover-gated so it survives touch.

CopyStateIcon confirms on the button itself: the copy glyph and a green
check share one grid cell, so nothing beside them moves during the swap.

Gated on showCopyLink, which only the history list passes, so the
reading-history modal keeps the DOM it has.

ReadingHistoryList came into the strict-typecheck guard's scope with this
change, which surfaced an untyped reduce accumulator and an unguarded
Date built from an optional field. Both are typed now, with no change to
what runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Puts the snapshot capture path on main so the surfaces that want a
snapshot can add one without carrying the machinery themselves.

captureShareImage rasterizes a DOM node through snapdom at a fixed
1200x630 and brands it. SnapshotButton is the control on top: it plays
the shutter, captures, and writes the PNG to the clipboard with the
link beside it as text, so a rich composer takes the image and a plain
one takes the link. A browser that refuses the clipboard write gets the
download instead, and a failed capture reports rather than throws.

Nothing renders SnapshotButton yet. The surfaces that will are drawn in
Features / Snapshot / Surfaces in Storybook.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A hot take is a self-contained opinion with nowhere to link to, so the
card is the whole payload and an image is the only share that carries it.

In the swipe modal the control sits beside the upvote pill, labeled and
filled, on the top card only: the cards stacked behind are rendered too,
and a control on those would capture a take the reader has not reached.
On the profile list it is icon-only at XSmall to match the upvote counter
it sits next to, and placed before it so the count stays at the edge.
Both HotTakeItem variants carry it, so engagement_bar_v2 does not change
whether a hot take can be shared.

Stacked on the capture path.
@vercel

vercel Bot commented Sep 6, 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 8, 2026 9:33am UTC
1 Skipped Deployment
Project Deployment Actions Updated
storybook Ignored Ignored Sep 8, 2026 9:33am UTC

Request Review

tomeredlich and others added 2 commits September 6, 2026 09:45
The image was a photograph of the card as it sits on screen, at 1200x630
with a logo bar bolted on. It should be the square card #6544 designs:
1080x1080, a gradient seeded from the take's id, the eyebrow, the take,
the emoji as a watermark and the count read as agreement.

SnapshotButton now takes that card and mounts it off-screen for as long
as the button is mounted, so the capture still starts inside the press:
Safari only honours a clipboard write in the task that handled it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…otdev/apps into claude/snapshot-hot-take-placements
@tomeredlich tomeredlich changed the title feat(hot-takes): snapshot a hot take from the modal and the profile list feat(snapshot): share a hot take as an image, and copy a link from reading history Sep 6, 2026
@tomeredlich
tomeredlich marked this pull request as ready for review September 6, 2026 07:45
Picks up the treatment #6544 settled on since the card was copied across.
The surface label rides the logo row instead of heading the copy, the
take and its subtitle are set as one statement rather than split across
two type styles that read as two voices, and the copy is centred.

The frame grows to the card rather than holding 1:1, so the capture is
measured through getSnapshotCaptureOptions instead of assuming the
square: a short take gives a short image rather than one padded out with
dead gradient.

Carries the .snapshot-copy rule the cards have always referenced, which
the first port missed — without it a long take wrapped ragged and a long
unbroken word could leave the card.

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.

1 participant