feat(snapshot): rasterize designed cards instead of the live DOM - #6614
Closed
tomeredlich wants to merge 6 commits into
Closed
feat(snapshot): rasterize designed cards instead of the live DOM#6614tomeredlich wants to merge 6 commits into
tomeredlich wants to merge 6 commits into
Conversation
A snapshot was a screenshot: the live element fitted into a 1200x630 frame with a logo bar drawn under it. It looked nothing like the designed square cards the surface pages compare against. Brings the card system over from snapshot-share-images verbatim — the 1080px SnapshotFrame with its lit edge and seeded gradient, the identity and stat rows, and the five profile cards — along with useSnapshotCapture, which mounts a card off-screen and rasterizes it, and that branch's SnapshotButton. The button renders on hover or focus rather than on press, so the press itself still owns the gesture the clipboard needs; a press that lands first falls back to a download. That replaces the shutter sound, the sweep animation and the Snapshot icon, which go with it. The achievement card is wired to AchievementSnapshotCard, and only where the achievement is unlocked: a locked one has no completion date to show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ards Every profile placement now rasterizes a designed card instead of the live element, so no snapshot is a screenshot any more. The values come from whatever the page itself renders, not a parallel derivation, because a share image that disagrees with the page is worse than no share image. The rarest-unlocked sort moves into sortAchievements so the widget's five and the card's ten come from one comparator. CalendarHeatmap exports its bins, so the card's cells are bucketed exactly as the profile heatmap buckets them. Badge counts read topReaders[0].total and tag labels read tagTitles, matching the widgets beside them. ProfileHeader needed posts read, which only the widgets column had. That query moves into useProfileReadingHistory: the window was never part of the key, so the header and the column share one cache entry and one request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopting snapshot-share-images' button wholesale brought its identity with it: the control became "Share as image" with a share-or-download glyph, and the Snapshot icon, the shutter and the sweep went in the bin. The ask was for the captured image to match the designed cards, not for the button to become something else. The card mechanism stays — render on hover or focus so the press still owns the gesture the clipboard needs — under the Snapshot icon, the Snapshot label, and the shutter and sweep on press. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopting the other branch's share action changed what the press does. It led with the native sheet where the platform had one, worded the copy toast differently, and said nothing at all when it fell back to a download. The press copies again: clipboard first, download as the fallback, "Image copied" or "Image saved" to say which. A rasterization that fails now says so too, instead of going quiet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cards were copied a few days ago and that branch has redesigned them since. Refreshes every file this branch carries to its tip, verbatim again. The surface label moves off the identity row and onto the logo row as SnapshotEyebrow, a sibling of the mark rather than a headline for the copy under it. SnapshotFrame gains the grow and wide treatments, and the capture now measures a mounted card instead of assuming the square, so a card that grows is neither letterboxed nor padded out with dead gradient. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card draws one cell per value it is given and stops at its grid, so handing it a day per entry showed the oldest eighty-eight days and dropped every month since. On a profile whose reading picked up recently that is a field of empty cells: seventy-one of eighty-eight rendered at level zero. The window is compressed into as many buckets as the grid has cells instead, summed per bucket and binned by CalendarHeatmap's own thresholds, so the strip carries the same shape the profile heatmap shows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
Folded into #6580 — the cards now live on that branch, so there is one PR and one preview URL. Landing them separately meant the familiar preview kept producing screenshots, which cost several review rounds. |
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.
Stacked on #6580. A snapshot was a screenshot — the live element fitted into a 1200×630 frame with a logo bar drawn under it — so nothing it produced looked like the designed cards on #6544's surface pages. This swaps the capture subject to those cards.
Changes
snapshot-share-images: the 1080pxSnapshotFramewith its lit hairline edge and seeded gradient,SnapshotIdentity/SnapshotStats, and the five profile cards. Verbatim so the two branches don't ship rival components — whichever lands second gets an identical-file conflict rather than a divergent one.useSnapshotCapture, which mounts a card off-screen atleft-[-200vw]and rasterizes it square withpadding: 0, branded: false, since the card carries its own logo.SnapshotButton, which renders ononPointerEnter/onFocusrather than on press. This is the point: the clipboard needs the press's own user activation, and a card cannot be rasterized inside the press because it has to be mounted first. Hovering buys the copy; a press that lands before the render finishes falls back to a download.AchievementSnapshotCard, and only where the achievement is unlocked — a locked one has no completion date for the card to show.Removed
The shutter sound, the
snapshot-shutter-sweepanimation, theSnapshoticon andshutter.mp3all came in with #6580's button and have no caller now that this branch adopts #6544's.Every placement, and where its values come from
No placement screenshots any more. Each card reads what the page itself renders rather than a parallel derivation, because a share image that disagrees with the page is worse than no share image.
ProfileHeaderProfileSnapshotCardJoinedDate's ownMMMM y; posts read from the shared reading queryReadingOverviewReadingOverviewSnapshotCardstreak.max/streak.totalas the widget's own cards show them; tag labels fromtagTitlesand percentages rounded the wayReadingTagProgressrounds them; heatmap bucketed byCalendarHeatmap's exportedgetBins/getBinBadgesAndAwardsBadgesSnapshotCardtopReaders[0].total, not the array length, matching the summary card; keywords asflags.title || value; dates onTimeFormatType.TopReaderBadgeAchievementsWidgetAchievementsSnapshotCardunlockedCount/totalCount/totalPointsfromuseProfileAchievements; the rarest unlocked from the shared comparatorAchievementCardAchievementSnapshotCardTwo extractions this forced
sortRarestUnlockedAchievementsjoinssortLockedAchievements. The widget shows five and the card shows ten; with the comparator inline in the widget they could have disagreed about which five. Covered by three new cases.useProfileReadingHistorytakes over the queryProfileWidgetsowned inline, becauseProfileHeaderneeds posts read and had no way to reach it. The date window was never part of the query key, so both share one cache entry and one request — no second fetch, and the date maths lives in one place.CalendarHeatmap'sgetBins/getBinwent from module-private to exported for the same reason: the card's cells had to be bucketed by the page's own thresholds, not a second guess at them.Events
None.
Experiment
None.
Manual Testing
ReadingOverview.spec.tsxupdated for the new requireduserprop.typecheck-strict-changedclean; lint clean for shared and webapp.SnapshotButton.spec.tsxrewritten for the adopted button: no rasterize until intent, rasterize on hover and on focus, copy when ready, download when the clipboard cannot take an image, and the throw when given neither a card nor a target.Preview
https://claude-profile-snapshot-cards-pr.preview.app.daily.dev/idoshamun
Not #6580's preview — that one still screenshots the live DOM and always will, because the cards live on this branch. If the profile page you are on produces a screenshot rather than a card, check the URL.
/idoshamun/idoshamun/achievementsisOwner-gatedHover the Snapshot button before clicking it. The hover starts the rasterization so the press still owns the gesture the clipboard needs; you get "Image copied" and can paste straight into Slack. Clicking cold renders first and then falls back to a download with "Image saved" — that is the design, not a fault.
Preview domain
https://claude-profile-snapshot-cards-pr.preview.app.daily.dev