Skip to content

feat(snapshot): share placements for the post page - #6556

Merged
idoshamun merged 39 commits into
mainfrom
snapshot-post-page-variations
Sep 10, 2026
Merged

feat(snapshot): share placements for the post page#6556
idoshamun merged 39 commits into
mainfrom
snapshot-post-page-variations

Conversation

@tomeredlich

@tomeredlich tomeredlich commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What ships

Six share placements on the post page, all on for everyone. No flags.

Placement Where
Floating bar on selected text, with Snapshot PostContent, PostFocusCard
Snapshot of the TLDR at the end of it PostContent, PostFocusCard
Snapshot after each paragraph of a markdown body CollectionPostContent, PostFocusCard
Share strip under the composer, end-of-thread band (3+ comments) PostEngagements, PostDiscussionPanel
Snapshot under the poll bars and on the post-vote prompt PollPostContent
Copy link beside the ⋯ PostMenuOptions

Selecting a line raises a bar whose Snapshot rasterizes the quote into a 1080² PNG. Polls get the same treatment for their result, and so does the TLDR. A snapshot puts the image on the clipboard and nothing else: a link written beside it arrives as a stray line of text in the composer, and the card already names its source. Everything that copies a link still resolves the short, campaign-carrying URL, and confirms with the green check from #6570.

Three placements carry a condition of their own rather than a flag: the poll snapshot only appears once a poll has votes, since an unvoted poll has nothing to put in an image; the TLDR snapshot is post-page only; and the end-of-thread band waits for three comments, below which a thread is a couple of remarks rather than a discussion.

Snapshot cards are armed, not always mounted. A capture reads the live DOM, so the off-screen 1080px card has to exist before the press. Mounting them with the page would put a second copy of the post's own prose in every post's document — one card per paragraph on a long body — so useArmedCard mounts a card on the first sign the reader is heading for its button: hover, touch or focus, each of which lands a render before the click.

What stays a copy. Only links: beside the ⋯, in the selection bar, and in the two share bands. The one remaining text copy is Copy text in the selection bar, which is the deliberate plain-text fallback sitting beside that bar's own Snapshot.

What a reviewer should look at first

Vendored from a closed PR. ShareBand, ShareActions, SplitShareButton and CopyStateIcon come from #6369, which is closed, and #6378, which merged into it. They exist on no branch that ships, so they are carried here. If that stack revives, EndOfThreadShare and PostContentShare should collapse into its EndOfConversationShare and these copies should go.

useCopy is shared. The clipboard fixes below touch a hook every copy button in the app goes through, not just the new ones. They are all in the direction of reporting a failure that was previously silent, but they are the widest-reaching change in the PR.

Fixes found while reviewing this PR

These are bugs the placements exposed, not new behaviour:

  • The engagement bar v2 never raised the upvote interaction, so the share band after an upvote could not appear for anyone in that experiment. Only the v1 bar did.
  • That interaction lives in the query cache keyed by post id, with gcTime: Infinity, and is shared with the feed. An upvote in the post modal left the feed card behind it covered by its own share prompt, with nothing to clear it. It is now released with the surface that raised it.
  • PostMenuOptions logged a hardcoded Origin.PostContent while receiving the real origin as a prop, so copy link from the collection modal and the reader rail reported as post content.
  • Copy link awaited the shortener before writing to the clipboard, which puts the write outside the gesture Safari requires. Both call sites now hand the pending link to useCopyLink's existing shorten path, which is already built for this.
  • A rejected clipboard write was an unhandled promise rejection with no feedback at all, so a blocked clipboard read as a dead button. useCopy now reports it. It also no longer pastes the string "undefined" when handed nothing to copy, and an error toast can no longer be replaced by the caller's own success message.
  • The capture timeout left its timer queued for the full 15 seconds after every snapshot.

Also removed

  • /dev/snapshot, and the rest of pages/dev/* with it (buttons, checkbox-radio) plus the components that existed only to serve them.
  • The shutter sound. The visual sweep on the button stays.
  • The Storybook snapshot mockups, which drew a fake post page to argue for the design rather than exercising the components.
  • isPreviewHost, which forced every placement on for branch previews so the flags could be reviewed without GrowthBook.

tailwind.config.ts is untouched. The only design-system addition is two scoped classes in utilities.css, snapshot-shutter-sweep and snapshot-copy, both used only by this feature. No dependency or lockfile changes.

Not in this PR

The mobile single-tap share path still awaits the shortener before navigator.share; that lives in useShareOrCopyLink and is fixed standalone in #6564.

Events

Every copy, share and snapshot logs SharePost, with provider and origin in extra. No new event name, so snapshots land in the existing share reporting.

Action provider origin Also
Snapshot from the selection bar snapshot text selection result
Snapshot of the TLDR snapshot post summary result
Snapshot of a paragraph snapshot post paragraph result
Snapshot under the poll bars snapshot poll results result
Snapshot on the post-vote prompt snapshot poll vote prompt result
Copy text / copy link in the selection bar copy text / copy link text selection
Copy link beside the ⋯ copy link the host page's origin
End-of-thread band per network end of conversation

result is clipboard, download or error, so one event gives adoption, per-placement usage, the clipboard-versus-download split and the failure rate.

Known gap: on the focus card, the copy link beside the ⋯ logs exactly what the action bar's existing copy link logs, so that one placement can't yet be told apart.

Testing

  • shared: 403 suites / 2916 tests. webapp: 86 / 685. extension: 6 / 52.
  • Lint clean for shared, webapp and extension; typecheck-strict-changed against origin/main.
  • Full webapp tsc: no errors outside the known __tests__ backlog.

Preview

https://snapshot-post-page-variations.preview.app.daily.dev

Preview domain

https://snapshot-post-page-variations.preview.app.daily.dev

Adds a Storybook page that draws the post page from its production
components — PostSourceInfo, PostActions, PostContentWidget and
MobilePostFloatingBar — at desktop, tablet and mobile, showing what
ships today and the one snapshot placement we want.

Today the post menu opens with 'Share via' and the engagement bar ends
with a labeled 'Copy'; below laptop PostHeaderActions is hidden, so on
tablet the only share action on the screen is that Copy. The post has a
real per-post OG image, so snapshot is limited to the text-selection
bar, where a highlighted line has no OG of its own.

Carries the Snapshot icon it needs; no production surface changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 31, 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 10, 2026 11:33am UTC
storybook Building Building Preview Sep 10, 2026 11:33am UTC

Request Review

The page argued for one snapshot placement — the text-selection bar — without
showing what pressing it produces, so the trade-off against Copy link had to be
taken on trust. The selection category now ends with the 1080x1080 card the
button exports, drawn from the real HighlightTextSnapshotCard (#6544) at a
third of its size and seeded with the highlighted line on the page above it.

Carries the four card files that page needs from #6544 — the frame, the
seeded gradient, the quote truncation and the highlight card itself — plus the
.snapshot-copy utility they rely on. No production surface renders them yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#6556 argued the case for one snapshot placement and drew what it exports;
this makes it real. Selecting a line of a post now raises a floating bar with
copy link, copy text and Snapshot, and Snapshot hands back the 1080x1080 PNG
of the quote — clipboard first, download where the clipboard refuses images.

Behind `snapshot_selection_share`, which defaults to off, and only on the post
page: the decision on the modal was to leave it alone. `useConditionalFeature`
evaluates on post pages only, so nothing else logs an exposure.

The capture pipeline and the button come from #6544 (snapdom, captureShareImage,
SnapshotButton). New here: `useTextSelection`, which reports a selection only
while it lives inside the post body, and the bar itself. The selection commits
on the trailing edge of the drag rather than behind a drag flag — a pointerup
lost to a release outside the window would otherwise strand the toolbar.

/dev/snapshot carries the review surface: the live bar, the card at every quote
length, and the real capture output. Unflagged there, so it can be reviewed
without GrowthBook, and blocked on the canonical production hosts.

Testing: shared 376 suites / 2652 tests, webapp 81 / 644, extension 6 / 52.
Drag-selected a quote in a browser and captured from the bar: clipboard on a
real press, download plus an "Image saved" toast on a scripted one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The version on this branch was a trimmed copy: two categories out of six. The
page on #6544 carries the whole post-page argument, and the missing four are
the load-bearing ones — the share strip, end-of-thread band and post-upvote
prompt; the audit showing seven post types breaking seven different ways; the
poll recommendation, which is the one type where snapshot beats a link
outright; and the modal and sticky nav the audit had missed.

Brings surfaceChrome along, since the page is drawn with its ShareSheet and
its three real viewport widths rather than the older device rail, and keeps
the exported-image variant next to the selection bar it belongs to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Storybook page argues for nine placements; only the selection bar could
actually be pressed. /dev/snapshot now carries all of them with working
controls rather than pictures of them: copy summary, the selection bar, the
share strip, the end-of-thread band, the post-upvote prompt, both poll
snapshots and the sticky nav copy-link. Every copy button copies for real and
every Snapshot rasterizes the card it would share.

Two new payloads this needed:

- PollSnapshotCard — the poll recommendation had no card. The winner is drawn
  in the accent and the rest stay quiet, so the answer reads before the bars.
- CopySummaryButton (#6350) — headline, TLDR and link as three paragraphs, so
  a paste into a thread is a usable message rather than a bare URL.

Both copy call sites now catch a rejected clipboard write. `navigator.clipboard`
refuses outright when the document is not focused, and the shared hook awaits
it without a catch, so the press produced no toast, no fallback and an
unhandled rejection. It now says the clipboard was blocked.

Only the selection bar is wired to the real post page; the rest are placement
mocks with real controls, which is what the review needs before any of them is
committed to a surface.

Testing: shared 377 suites / 2654 tests, webapp 81 / 644. Pressed every control
in a browser — real clicks copy (OS clipboard changes), blocked clipboard shows
the error toast, both poll Snapshots and both capture buttons return 1080²
PNGs (quote 423 KB, poll 378 KB).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every placement the Storybook page argues for is now production code on the
surface it belongs to, each behind its own flag defaulting to off:

- post_copy_summary — CopySummaryButton runs into the end of the TLDR in
  PostContent, evaluated only where a summary exists.
- post_share_prompts — DiscussionShareRow under the comment composer in
  PostEngagements, already shipping in the discussion panel but never on the
  post page, and EndOfThreadShare after the thread.
- poll_snapshot — PollSnapshotButton under the poll bars and on the post-vote
  prompt in PollPostContent, evaluated only on polls that have votes.
- post_nav_copy_link — a copy-link icon in PostNavigation, gated on
  isFixedNavigation so the inline navigation is untouched.

The post-upvote prompt needed nothing: PostContentShare already renders
"Should anyone else see this post?" with a copy-link input the moment you
upvote. The page proposed a placement production had already built.

pollSnapshotFromPost derives each bar from the options' own counts rather than
numPollVotes, which counts voters and would leave the bars adding up to
something other than the poll.

/dev/snapshot now mounts the real components instead of imitations. `/dev/*`
short-circuits to a QueryClient-only tree in _app, so the page stands in for
the auth and log contexts the share components reach for — signed out, no
squads, logging swallowed — and names the flag behind each placement.

Testing: shared 379 suites / 2661 tests, webapp 81 / 644, extension 6 / 52.
New specs cover the poll payload maths, the band's empty-thread case, the copy
payload and both flag states for copy summary. Pressed every control in a
browser: both poll snapshots capture, the real share row and band render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lint_shared runs eslint over the whole package with --max-warnings 0, and this
file was written after the last per-file fix pass, so a prettier break reached
CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The placements were production code on the real surfaces but invisible where
they most needed reviewing. A preview deployment runs NODE_ENV=production
against the production API, so `isDevelopment` is false and GrowthBook's dev
tools are off: there is no way to open a flag from the browser, and every
placement defaulted to off.

useSharePlacement wraps the flag: on, when the flag says so, or unconditionally
on a *.preview.app.daily.dev host. The host is read after mount rather than
during render, because the server cannot know what it will be served as and
disagreeing would break hydration.

The committed defaults stay false, so merging still changes nothing for anyone
on app.daily.dev and the rollout remains a GrowthBook decision. Only the
branch preview opens itself, and a surface that opted out with shouldEvaluate
stays out there too.

Testing: shared 380 suites / 2665 tests, webapp 81 / 644. New spec covers the
production host with the flag on and off, the preview host with no flag, and
the opted-out surface on a preview. Package lint clean for shared and webapp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found on the preview: the icon was missing on a real post that plainly had a
summary. Anonymous visitors get in-content AdSense, and then the post page
renders the summary itself through `renderSummarySegments` — ad slots between
the parts — so PostContent's own paragraph, the only branch carrying the
button, never runs. Every signed-out visitor saw nothing.

The segmented branch cannot take the icon inline the way the plain one does,
since the parts belong to the page's render prop, so it trails the block
instead.

Testing: shared 380 suites / 2667 tests, webapp 81 / 644, package lint clean.
Two new specs cover the segmented summary with the flag on and off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four placements were invisible, and they had one cause: they were wired to
PostContent and PostEngagements, which the redesigned post page does not
render. It draws PostFocusCard and PostDiscussionPanel instead, and the
post_redesign flag decides which layout a reader gets — so the work landed on
the layout half the audience does not see. The two that did work were the ones
that did not depend on it: the share strip, which the discussion panel already
shipped, and the poll, which is not redesign-eligible and is always classic.

- PostFocusCard now carries the selection bar, scoped to the card so a quote
  can only come from the post's own body, and copy summary on both summary
  variants — the plain paragraph and the truncated video one.
- PostDiscussionPanel carries the end-of-thread band, and PostContentShare,
  which is where the classic page's post-upvote prompt comes from.

The prompt also never fired from the post page at all: PostContentShare waits
on usePostActions, and only feed card buttons raised 'upvote'. Both post-page
bars — PostActions and FocusCardActionBar — now raise it when they upvote,
behind post_share_prompts so the prompt's existing behaviour is unchanged
wherever the flag is off.

Testing: shared 380 suites / 2667 tests, webapp 81 / 644, extension 6 / 52,
package lint clean for shared and webapp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two reasons it was missing from the modal. The gate was `isPostPage`, so the
classic modal never mounted it — I had read the page's "no snapshot on the post
page or modal" as covering selected text too, when selected text was the
exception it carved out. And the bar is portaled to the body at z-popup while
the modal overlay is z-modal, so even once mounted it rendered behind the modal
the quote was selected in.

The bar is now page and modal both, at z-max: a reader highlights a line
wherever they happen to be reading it, and on desktop that is mostly the modal.

Copy summary is still page-only — say the word and it can follow.

Also covers the redesigned layout with specs: PostFocusCard is what the
post_redesign flag serves, and the placements were wired to the classic layout
first.

Testing: shared 380 suites / 2673 tests, webapp 81 / 644, package lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nk with it

Design pass on the three prompts:

- The floating bar leads with Snapshot, labelled and solid, with copy link and
  copy text as the quiet pair beside it. It is the reason the bar exists and it
  was the last, smallest thing in the row.
- "Should anyone else see this post?" drops the bordered widget and the link in
  an input for a copy-link button with the icon trailing. There is one thing to
  do with that link, and an input asks to be read first. Behind
  post_share_prompts, so the shipping prompt is unchanged until rollout.
- The end-of-thread band drops its box for the same reason, reads "Send it to
  someone who'd have opinions" instead of counting comments, and offers a solid
  copy-link button. It still only appears where a thread exists.

A copied snapshot now carries the post link as text/plain beside the PNG, so a
rich composer takes the image and a plain one takes the link. Browsers that
refuse two representations in one item still get the image.

Testing: shared 381 suites / 2676 tests, webapp 81 / 644, extension 6 / 52,
package lint clean. New spec covers the clipboard payload, its single-item
fallback and the no-link case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The poll card carried its status and count as a footer, while the product
  puts that line under the question and above the options. It now reads the
  same way, and says what the product says: "Voting open · 1.3K votes · Today",
  or "Voting ended · N total votes" once it closes. The source keeps the
  bottom line as attribution.
- The upvote prompt moves out of the discussion panel and under the action bar
  that raises it — the classic page has always had it there, and on the focus
  layout it had ended up below the comment composer.
- Both prompts now read as a pair: title, subtext, and a solid Copy link with
  the icon trailing.
- Both primary buttons are ButtonV2.

Testing: shared 381 suites / 2677 tests, webapp 81 / 644, package lint clean.
The poll spec now freezes the clock rather than reading it: postDateFormat
answers "Now" inside the first minute, so a real clock decided the assertion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things in how the highlight card finds the marked run inside its
paragraph.

A markdown soft break reaches the DOM as a literal `\n` inside the `p`,
which the block's `textContent` keeps and the browser's selection string
renders as the space it displays as. Any selection spanning one found no
match, and the card quietly dropped back to the bare quote. Freeform
posts are written in a composer where pressing Enter inside a paragraph
is routine. Both sides are now whitespace-folded before matching; the
card sets its copy with `white-space: normal`, so folding the passage
changes nothing drawn.

A phrase the paragraph repeats was always marked at its first
occurrence. The text leading up to the selection now says roughly where
it starts, and the nearest occurrence wins.

windowAroundHighlight gave a run near the end of a long paragraph only
half its budget: the leading side was capped at half the slack and never
took back what the trailing side could not use. Slack now flows to
whichever side has room, in both directions.
No snapshot press logged anything: every link copy on the page reported
a SharePost, but the placements this branch is built around were
invisible to analytics, and so was the selection bar's Copy text.

A snapshot now logs the same SharePost event, with provider `snapshot`,
the placement as its origin, and how the press ended (`clipboard`,
`download` or `error`). One event gives adoption, per-placement usage,
the clipboard-versus-download split and the failure rate, and it joins
the share reporting that already exists instead of starting a new
family. SnapshotButton reports the outcome through an onResult callback
so it stays generic; useLogSnapshot does the logging.

Placements get their own origins: text selection, post summary, post
paragraph, poll results and poll vote prompt. The selection bar's copy
link moves from `post content` to `text selection` and Copy text now
logs as provider `copy text`, so the bar's three actions compare
directly under one origin.
@idoshamun
idoshamun enabled auto-merge (squash) September 10, 2026 11:32
@idoshamun
idoshamun merged commit 366c845 into main Sep 10, 2026
11 checks passed
@idoshamun
idoshamun deleted the snapshot-post-page-variations branch September 10, 2026 11:34
idoshamun added a commit that referenced this pull request Sep 10, 2026
#6556 landed the post-page placements first, carrying newer copies of the
snapshot primitives this branch introduced. Main's versions are kept for all
six conflicts: SnapshotButton is the one the post page already mounts, and
the capture, clipboard, text windowing and CSS changes on main are supersets
of this branch's.
idoshamun added a commit that referenced this pull request Sep 10, 2026
#6556 shipped SnapshotButton as a target-based button with its own
off-screen staging (useArmedCard). This branch's card-based button went
with the merge, which left useSnapshotCapture as a second capture
pipeline with no caller, so it is removed.

- PostSnapshotCard and DiscussionSnapshotCard were copies of
  HighlightTextSnapshotCard's passage-plus-credit layout. They now render
  it, with a new optional label on the logo row for the surface eyebrow.
  The post card also credits through snapshotSource, so an unattributed
  link names its domain instead of the API's literal "unknown".
- The boxed stat tile was pasted into three cards and had already drifted;
  it is SnapshotTile in SnapshotStats now.
- Top reader badges keyed on the keyword, which repeats when the same tag
  is earned in different months.
- The profile cover bleed hardcoded the frame's padding; it reads the
  exported constant.
- Imports go to lib/numberFormat rather than the lib barrel.
- SnapshotContent only ever rendered in Storybook, so it moves there.
idoshamun added a commit that referenced this pull request Sep 10, 2026
- The Sharing map carried internal traffic and share counts; this repo is
  public, so the table and the per-network numbers are gone.
- Its advice to put the link on the clipboard with every snapshot is
  replaced with what shipped: the clipboard is image-only.
- The post page surface mockup is dropped, as it was from #6556: those
  placements are live now, so the real page is the reference.
- The post edge cases rendered a title, meta row and seven-line TLDR the
  post card no longer has; they now render PostSnapshotCard, including an
  unattributed source.
- PostSnapshot.stories used the card prop of the removed button and
  duplicated the post placement in Share images.
idoshamun added a commit that referenced this pull request Sep 10, 2026
The page lived at /dev/status-moments in the webapp. #6556 removed the
pages/dev review surfaces it sat next to, and #6544 shipped this same
section as the Features/Snapshot/Surfaces/Status moments story, so the
branch was a second, newer copy of that story served from production
code.

The designer's later iterations now replace the story instead: the
streak screens on the milestone-rewards design, one placement per
moment, the real ProgressBar, ProfilePicture and TopRankBadge, a single
control on post analytics, and the header control on awards. The
celebration panel reuses FlameBadge, TierName, DayStrip, EmberPanel and
NoThanks from the milestone-rewards stories rather than copying their
gradients, and the Inferno milestone comes from its ladder. The story
gets a QueryClientProvider like its siblings, since ProfilePicture
needs one outside the app shell.

The page, its host gate and the webapp copy of inferno.png are deleted;
Storybook already serves the same PNG from its public folder. Copy drops
the em dashes and a claim about the audience's device split.
idoshamun added a commit that referenced this pull request Sep 10, 2026
main now owns the snapshot capture path (#6556) and the designed share
cards (#6544), which this branch had vendored copies of. Every conflict
took main's version:

- SnapshotButton, CopyStateIcon, copyShareImage (+ spec),
  captureShareImage: #6556's target-based button, image-only clipboard
  and cleared capture timeout.
- SnapshotFrame, snapshotText: #6544's cards.
- storybook surfaceChrome: main's copy.

The hot take and reading history placements still call the vendored
APIs; they are ported onto main's in the commits that follow.
idoshamun added a commit that referenced this pull request Sep 10, 2026
The hot take placements used the vendored card-prop SnapshotButton, which
mounted a 1080px card beside every button on render: one per take in a
profile list, plus the modal's top card. They now go through
HotTakeSnapshotButton, which follows TextSnapshotButton and
PollSnapshotButton: useArmedCard mounts the card on hover, touch or
focus, and it is portalled to the body because the swipe card it sits on
is transformed while it moves.

HotTakeSnapshotCard is rebuilt on main's SnapshotFrame and
SnapshotEyebrow instead of the vendored SnapshotContent (which main keeps
in Storybook), with HOT_TAKE_EYEBROW_GRADIENT defined beside it. The
image is unchanged.

A hot take is not a post, so the press logs a new ShareHotTake event with
the SharePost extra scheme: provider snapshot, the placement as origin
(hot and cold for the modal, hot take list for the profile), and the
result.

Removed with the vendored path: SnapshotContent, the shutter sound and
its mp3 (#6556 dropped both), and the spec for the old SnapshotButton
API, whose clipboard, download and error cases main already covers in
useLogSnapshot.spec. The specs no longer need to skip aria-hidden text,
since no card is mounted on load.
idoshamun added a commit that referenced this pull request Sep 10, 2026
…ces-topic-directory-3b3fd5

Brings in #6556 (post page share placements) and #6544 (designed share
cards).

Conflicts: surfaceChrome.tsx and surfaces/Directories.stories.tsx were
added on both sides. Took main's version of both, since main's
surfaceChrome is the shared helper the other surface stories import.
CopyStateIcon and its spec were identical on both sides and merged
cleanly.
idoshamun added a commit that referenced this pull request Sep 10, 2026
The copy link placements logged ShareTag/ShareSource with only a
target, so a press on the new button could not be told apart from the
overflow menu's Share item on the same page, and native share could not
be told apart from a copy.

CopyLinkButton now takes a required origin and adds
extra: { provider, origin } to the caller's event, the same extra shape
#6556 uses for SharePost. Tag, source and squad pages reuse their page
origins; the source directory lists and the best-of archive get new
SourceDirectory and ArchiveIndex values.

The global best-of archive has no tag or source, but fell through to
ShareSource with no target and the share_source campaign. It now logs
a new ShareArchive event with the generic campaign.

Also imports LinkIcon from its own file rather than the icons barrel.
idoshamun added a commit that referenced this pull request Sep 10, 2026
The copy path awaited the URL shortener before writing to the
clipboard. Safari refuses a clipboard write once the task that handled
the click has ended, and useCopy now reports that refusal, so every
signed-in copy from CopyLinkButton (and the other callers of this hook,
including ShareActions) would end in a "blocked the clipboard" toast.

It now writes the tracked link straight away and hands the shortening
to useCopyLink's shorten path, which swaps the short link in through a
pending ClipboardItem, the same approach #6556 took for the post page.
The native share path still resolves the short link first, since the
text it shares has to carry it.

The CopyLinkButton spec pins the synchronous write and the logged
provider and origin. The two squad card tests that only asserted the
button exists are dropped: they catch nothing the new spec does not.
idoshamun added a commit that referenced this pull request Sep 10, 2026
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.
idoshamun added a commit that referenced this pull request Sep 10, 2026
…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.
idoshamun added a commit that referenced this pull request Sep 10, 2026
… 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.
idoshamun added a commit that referenced this pull request Sep 10, 2026
…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.
idoshamun added a commit that referenced this pull request Sep 10, 2026
Main now owns the snapshot foundation this branch had vendored (#6556
share placements, #6544 designed share cards). Every conflict takes
main's side: SnapshotButton, the Achievements, Badges, Profile and
ReadingOverview cards, SnapshotFrame, SnapshotStats, useCopy,
captureShareImage, copyShareImage and utilities.css.

The vendored files main deleted or never kept are removed here:
useSnapshotCapture, shutterSound and public/sounds/shutter.mp3, and the
spec for the old card-prop SnapshotButton, which no longer exists.

The profile placements still call the old card-prop SnapshotButton;
they are ported onto main's target API in the next commit.
idoshamun added a commit that referenced this pull request Sep 10, 2026
main now carries #6556 (post page share placements) and #6544 (designed
share cards), which this branch had vendored from their unmerged heads.

Conflicts: every conflicted file is one of those vendored copies
(SnapshotButton, ShareBand, SelectionSnapshotBar, EndOfThreadShare,
PollSnapshotButton, HighlightTextSnapshotCard, SnapshotFrame, snapshotText,
useTextSelection, captureShareImage, copyShareImage, the post page hosts,
log.ts and the Storybook surface chrome). All take main's version.

Also dropped, since main removed or never shipped them: useSharePlacement
and its isPreviewHost helper, CopySummaryButton, ParagraphCopyButtons, the
shutter sound, the post page flags (snapshot_selection_share,
post_copy_summary, post_share_prompts, poll_snapshot, post_copy_link), the
empty tailwind keyframes comment, pages/dev/snapshot and the PostPage
Storybook mockup.

What remains against main is the briefing work alone. It still calls
useSharePlacement and the old SnapshotButton props; the next commit ports it
onto main's APIs.
idoshamun added a commit that referenced this pull request Sep 10, 2026
…eature

useSharePlacement went with #6556's cleanup: besides reading the flag it
forced every placement on for *.preview.app.daily.dev. The list rows and
the briefing header now read briefing_share_controls through
useConditionalFeature, the header only when it is asked to show the share
button, so the digest page never evaluates it. The flag default stays
false.

The share arrow and link glyphs import from their own icon files rather
than the icons barrel.

Tests: drop the two that pinned class names (the text column's flex
classes and the btn-tertiary weight) and fold the copy and share presses
on a row into one test.
idoshamun added a commit that referenced this pull request Sep 10, 2026
…ents

The briefing body carried its own portal machinery, BriefBodyShareControls,
which main's ParagraphSnapshotButtons already does, and it had three bugs
of its own:

- It looked for the blocks once, in an effect keyed on contentHtml. Markdown
  sanitizes the body a render after mounting, so with the flag already on
  at first render the effect found an empty body and never ran again; only
  the preview-host override flipping on a render late hid this. The Must
  know button now watches the body with a MutationObserver, like
  ParagraphSnapshotButtons, and a test renders the body late.
- The Must know card was mounted with the page, one 1080px card per
  briefing view. It is armed with useArmedCard now.
- None of the in-body controls logged anything. The per-block copy wrote
  the untracked permalink and wrapped useCopyText in a try/catch that
  could never fire, since useCopyText reports a blocked clipboard itself.

The per-block copy of text and link becomes a snapshot of the block, as
#6556 did for post paragraphs: ParagraphSnapshotButtons takes a selector
(bullets, and paragraphs that are not the body of one) and an origin, and
BriefBlockCopyButton and getBriefBlocks go. The card list also no longer
repeats a claim that appears twice, which would have collided as a React
key in ListSnapshotCard.

Every placement logs SharePost under its own origin: SelectionSnapshotBar
takes an origin prop (brief text selection), then brief paragraph, brief
must know, and end of briefing for the closing band, which no longer shared
the header's host origin.

The flag is evaluated once in BriefPostContent, which renders the selection
bar, block snapshots, Must know snapshot and band together, so the
components themselves no longer read it. The band says "Share this
briefing": a recipient of the link reads it too, and it is not theirs.
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.

2 participants