Skip to content

feat(briefing): share controls on the briefing surfaces - #6563

Open
tomeredlich wants to merge 41 commits into
mainfrom
claude/snapshot-briefing-preview
Open

feat(briefing): share controls on the briefing surfaces#6563
tomeredlich wants to merge 41 commits into
mainfrom
claude/snapshot-briefing-preview

Conversation

@tomeredlich

@tomeredlich tomeredlich commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What ships

Share controls on the two briefing surfaces, on for everyone (no feature flag).

Surface Control Where
/briefing list Copy link and a share arrow on every row BriefListItem
Briefing header A share arrow at every width, next to the laptop-only copy link and settings gear BriefPostHeaderActions
Briefing body The post page's selection bar (Snapshot, copy link, copy text) BriefPostContent via SelectionSnapshotBar
Briefing body A Snapshot at the end of every bullet and paragraph BriefPostContent via ParagraphSnapshotButtons
Briefing body A Snapshot on the Must know heading that captures that section's bullets as one list card BriefMustKnowSnapshotButton
End of the briefing The share band (ShareBand) with the briefing's copy BriefShareBand

A brief is a normal post at /posts/{slug}, so a copied link resolves for the recipient and every link control goes through the tracked, shortened share URL. Snapshots put the image alone on the clipboard, as on the post page.

Everything is built on what #6556 and #6544 put on main: SnapshotButton with a target, useArmedCard so no 1080px card is mounted until the reader heads for a button, useLogSnapshot, ShareBand, CopyStateIcon and the designed ListSnapshotCard.

What a reviewer should look at first

  • The briefing header now has a share control at every width. The share arrow shows on phones too. The copy link and the settings gear stay hidden laptop:flex, because below laptop the post page's sticky header (GoBackHeaderMobile) and the modal's navigation strip already show a copy link and the menu with Share via and Settings. Showing them in the briefing header as well would put two copy links a few rows apart on a phone. The digest page, which has no share button, keeps its laptop-only gear as before.
  • The header buttons take ButtonVariant.Tertiary. They had no variant before. Tertiary matches the copy link and menu in the page header, and the copy link, share arrow and gear read at one weight. This also applies to the gear on the digest page, which shares the component.
  • The kicker truncates. The kicker row in BriefPostHeader holds the brief's title and the header buttons. Now that a button shows at every width, a long title would push it off the row on a phone, so the kicker truncates and the buttons keep their width (shrink-0).
  • ParagraphSnapshotButtons and SelectionSnapshotBar gained optional props (selector, origin) so the briefing can reuse them. Their defaults keep the post page's behaviour and origins unchanged.
  • The Must know lookup matches heading text. The backend sends no ids, so if a brief ships without a "Must know" heading the button does not appear. The block snapshots do not depend on it. A backend anchor would be the durable fix.
  • The body is found after Markdown renders it. Markdown sanitizes a render after mounting, so BriefMustKnowSnapshotButton watches the body with a MutationObserver, the same way ParagraphSnapshotButtons does.
  • useSharePost now returns isCopying, so a control can confirm a copy on itself.
  • BriefPostHeaderActions no longer spreads contextMenuId onto its container div (React warned about an unknown DOM attribute).

Changes made while bringing this onto main

  • Merged main. Every conflict was a vendored copy of a feat(snapshot): share placements for the post page #6556 or feat(snapshot): designed share cards for the sharing initiative #6544 file and takes main's version. useSharePlacement (and the *.preview.app.daily.dev override it carried), the post page flags, CopySummaryButton, ParagraphCopyButtons, the shutter sound and pages/dev/snapshot went with it.
  • The per-block copy of text plus link is now a snapshot of the block, matching feat(snapshot): share placements for the post page #6556's move from paragraph copies to paragraph snapshots on the post page. That also removed a second copy of the portal machinery main already has.
  • Fixed: the in-body controls looked for the blocks once, before Markdown had rendered them, so on a first render they never appeared.
  • Fixed: the Must know card was mounted with every briefing view. It is armed now.
  • Fixed: none of the in-body controls logged a share, and the block copy wrote the untracked permalink.
  • The closing band logs under its own origin instead of the header's, and says "Share this briefing", since a recipient of the link sees it too.
  • Removed the Features/Snapshot/Surfaces/Briefing Storybook mockup; the live briefing is the reference now.
  • Removed the briefing_share_controls flag. The controls ship to everyone, and the flag-off code paths and their tests are gone.

Events

Every action logs SharePost with provider and origin in extra.

Action provider origin Also
Copy link on a /briefing row copy link brief page
Share arrow on a /briefing row, then a network per network brief page
Copy link in the briefing header copy link the host's origin (brief modal, article page) as before this PR
Share arrow in the briefing header, then a network per network the host's origin
Snapshot from the selection bar snapshot brief text selection result
Copy link / copy text in the selection bar copy link / copy text brief text selection
Snapshot of a bullet or paragraph snapshot brief paragraph result
Snapshot of Must know snapshot brief must know result
End-of-briefing band per network end of briefing

result is clipboard, download or error.

Experiment

None.

Testing

  • shared: 411 suites / 2965 tests pass (jest --runInBand).
  • webapp: 86 suites / 685 tests pass.
  • New and updated specs: BriefMustKnowSnapshotButton (renders the body after mount, arms the card on hover, logs brief must know), BriefShareBand (logs end of briefing), briefBodyBlocks (the block selector and the section lookup), BriefListItem (copy and share without opening the brief) and BriefPostHeaderActions (share at every width with a laptop-only copy link, no share controls on the digest, both actions fire).
  • Lint clean on the changed files; typecheck-strict-changed against origin/main passes.
  • Full webapp tsc: no errors outside the known __tests__ backlog.

Preview domain

https://claude-snapshot-briefing-preview.preview.app.daily.dev

tomeredlich and others added 16 commits August 31, 2026 15:19
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>
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>
"Tsahi's button" is SplitShareButton from #6369 — copy link and a chevron
holding the networks, meeting at one hairline — wrapped by ShareBand, which
pairs it with a title and a line of copy. #6369 built it for exactly these two
surfaces: the band below an active discussion and the prompt after an upvote.

Neither PR landed. #6369 is closed and #6378 merged into it, so the components
exist on no branch that ships. They are carried here — ShareBand, ShareActions,
SplitShareButton, CopyStateIcon and the Origin they log against — and both
prompts now render through ShareBand rather than the pair of hand-rolled rows
I had built. If that stack revives, these two callers should collapse into its
EndOfConversationShare and the copies here should go.

The copy is theirs too, so the two bands read as one pair.

Testing: shared 382 suites / 2680 tests, webapp 81 / 644, extension 6 / 52,
package lint clean for shared and webapp. The ported ShareActions spec runs
green here as it does there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The band sat 40px below the action bar and 16px above the comment box. The
focus card's column is already gap-4, so the mt-6 the band carried for the
classic layout — where PostContainer has no gap and every child hand-rolls its
margins — stacked on top of a gap that was doing the job. Spacing now belongs
to the host: the band defaults to my-4 for a container that supplies none, and
the focus card passes nothing.

Copying no longer dismisses the prompt. It called onInteract('none'), so the
band vanished under the cursor — taking the networks behind the chevron with
it, right when someone might want a second one.

Testing: shared 382 suites / 2680 tests, webapp 81 / 644, package lint clean.

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

Audit against production: every copy on a post resolves a short link carrying
the share campaign at press time — usePostContent and DiscussionShareRow both
do `getShortUrl(commentsPermalink, SharePost)` in the handler. Three of mine
put the raw permalink on the clipboard instead, so a paste from them was
attributed to nobody and logged nothing.

- The selection bar's copy link now shortens, tracks and logs SharePost,
  matching DiscussionShareRow line for line.
- Copy summary embeds the tracked link rather than the permalink.
- Both snapshots carry the tracked link as the text half of the clipboard.

The link is resolved as a promise handed to ClipboardItem rather than awaited
first: awaiting ends the task that handled the gesture, and Safari refuses a
clipboard write after that. The capture and the shortener resolve in parallel.

Testing: shared 382 suites / 2681 tests, webapp 81 / 644, package lint clean.
New spec covers a link that is still resolving when the write starts.

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

vercel Bot commented Sep 1, 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 1:15pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
storybook Ignored Ignored Sep 10, 2026 1:15pm UTC

Request Review

"Share via…" opened the native sheet titled "null". The band was handed
`shareLink` from PostContentShare's eager short-URL query, and that query is
disabled without a signed-in user and yields nothing when the shortener fails
— so an empty value reached `navigator.share` as the text it renders.

The band now takes the permalink and the campaign, like the end-of-thread band
does, and lets the control shorten at press time: getShortUrl falls back to the
tracked URL, so there is always a link to share.

The same query also gated the whole component. A disabled query stays pending
forever in query-core, so that check could hide the prompt outright; it now
guards only the widget that needs the link up front to fill its input.

Testing: shared 382 suites / 2681 tests, webapp 81 / 644, package lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o what ships

The copy confirmation was a green check of its own invention. It is now
the upvote button's glyph, colour and spin, so the two gestures that mean
"that worked" look alike.

The Storybook page also carried the pre-change design of every surface
and the placements we decided against — the menu-and-sheet route, the
seven-post-type audit, the poll before snapshot, the modal and sticky nav
as they ship. A review page for a change should show the change, so only
the five flagged placements remain, each labelled with its flag. The
scaffolding those sections needed goes with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 2 commits September 6, 2026 08:39
…s one

The link went into PostHeaderActions, and the redesigned post page does not
render that component — PostFocusCard builds its own header and reaches for
PostMenuOptions directly, as do CollectionPostHeaderActions, FocusCardActionBar
and EngagementRail. So the button existed and was invisible on the layout
most readers get.

PostMenuOptions is the one control every header has in common, which is what
"next to the three dots" actually means. The link now renders there, just
before the menu trigger, and every header inherits it.

Testing: shared 384 suites / 2693 tests, webapp 81 / 644, package lint clean,
typecheck against origin/main as CI runs it. The spec moves with the code and
still walks all seven post types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A collection has no summary — CollectionPostContent renders contentHtml
through Markdown — so the copy that trails a TLDR everywhere else had nothing
to attach to and the body ended with no way to lift a passage out.

Each paragraph now ends with its own copy. The body is sanitized HTML written
straight into the DOM, so there is no JSX to hang a button off: every
paragraph gets one empty span appended and the control is portalled into it,
which leaves the markup the renderer's and the button React's. A MutationObserver
covers the sanitize finishing after the first render, and a signature of the
paragraph text stops the spans it appends from feeding it back to itself.

Paragraphs under 80 characters are left alone; a caption is not worth a
control. Behind post_copy_summary, the flag already covering "copy the text of
this post".

Testing: shared 385 suites / 2696 tests, webapp 81 / 644, package lint clean,
typecheck against origin/main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 3 commits September 6, 2026 08:55
The briefing was not missing a share control, it was hiding one:
BriefPostHeaderActions wraps the cluster in `hidden laptop:block` and
BriefPostContent is its only caller passing `showShareButton`, so a
working copy button existed and was invisible on the widths where most
briefings are read. Dropping the wrapper is the whole change there, with
a share arrow beside it for the popover on desktop and the native sheet
on mobile, and the settings gear moved to Tertiary so the three controls
read at one weight.

On the /briefing rows, #6353's pairing: copy and the arrow in one
container after the CardLink with `relative z-1`, or the full-bleed
overlay swallows the clicks. The text column gives way to them —
`w-full` claimed the whole card and pushed the controls past its border —
and rows without controls keep their original DOM.

The header's kicker row never had to survive a narrow width while the
actions were desktop-only, so it gains a shrink guard.

`briefing_share_controls` gates all of it, default false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three controls inside the brief itself:

- The post page's highlight bar (#6556), scoped to a body container so a
  selection in the header or the preferences card never raises it.
- A copy icon per bullet and paragraph, carrying the text and the brief
  link, after #6350's copy-summary icon. Each one is labelled with what
  it copies, since a brief carries a column of them.
- A Snapshot on the Must know heading that captures its bullets as one
  card, with the claim before the colon as each line.

None of these can be declared in JSX: the body is a single `<Markdown
content={contentHtml} />`, so briefBodyBlocks reads the blocks back out
of the rendered DOM — which is also the most faithful source, being what
the reader is looking at — and the controls are portalled into them.
Hosts are marked and swept before each run, because Markdown re-renders
on its own for hover cards and the image modal.

The closing band is #6369's ShareBand with the briefing's copy, so it
cannot drift from the end-of-conversation band it was asked to match.

All of it sits behind `briefing_share_controls`, default false.

Mockup-to-eng-pass: 1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The briefing has four copy controls — the header, the list row, and one
per body block — and all four relied on the toast alone. useSharePost
was discarding useCopyPostLink's flag, so two of them had no way to know
a copy had landed; it now returns isCopying, which any consumer can use.

CopyStateIcon takes the glyph it rests on, so a link control keeps its
link icon and still lands on the upvote arrow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ListSnapshotCard arrived from another branch with `subtitle` and
`footer` props, and the divider colour only the footer used; the one
caller passes neither.

The rest is commentary the code already says, or narration of how the
code got here, which belongs in the commit that changed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card's `meta` line is sized for a short label — "Frontend · 3m read"
in #6544's own examples — and is not clamped. It was being fed the
sentence behind each claim, which wrapped for as many lines as it needed
and pushed the rest of the card out of frame.

Items carry the claim alone now, and the subtitle carries what a brief
actually knows about itself: how many posts and sources it read.

ListSnapshotCard goes back to #6544's file byte for byte. The `subtitle`
and `footer` props read as dead here, but they belong to the shared card,
and the examples use both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eyebrow moves off the copy and onto the logo row, where it reads as a
sibling of the mark rather than a headline for the list under it, and the
title takes the space it left.

SnapshotFrame, SnapshotEyebrow, the gradient module and ListSnapshotCard
are #6544's files byte for byte, so the card cannot drift from the
library it belongs to. The frame's new props are additive and the gutter
maths resolves to the old 780/58 values, so the cards already drawing
through it — the quote card, the poll card — render exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tomeredlich and others added 4 commits September 8, 2026 12:39
The quote card was a size ladder inside a fixed square: short highlights set
at 72px, long ones stepped down to 40 and the frame padded whatever was left
with gradient. #6544 replaced that with one copy size in a frame that grows
with the passage, so a shared image no longer changes character with its
length, and pulls the attribution back to just the source.

Carries over SnapshotFrame's grow/wide modes, SnapshotCredit, and the
measured capture options — a growing card cannot be captured at an assumed
1080 square without letterboxing a long passage or padding a short one. The
poll card keeps its square, since bar charts need it, and picks up the same
credit block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A link the backend could not attribute is given a real source record named
"unknown", handle "unknown", over a squad placeholder avatar. In the post
header that passes as chrome; printed as the sole credit on an image someone
sends out it reads as a bug — and the credit became the only attribution when
the card dropped the domain line.

Credit the domain instead, which is the one thing an unattributed link still
carries, and nobody at all when there is not even that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Take the merged captureShareImage from #6426 with the width, height and
capture timeout the snapshot cards need. Keep main's removal of the
community_sentiment flag and its postBody refactor of the focus card,
threading the paragraph copy and copy summary placements through it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…efing-preview

Pick up the base branch's merge with main, keeping its removal of the
community_sentiment flag next to the briefing share controls flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Base automatically changed from snapshot-post-page-variations to main September 10, 2026 11:34
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.
…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.
…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.
Features/Snapshot/Surfaces/Briefing drew a fake briefing page to argue for
where the controls should go. The controls now ship on the real briefing,
so the Overview row points at this PR and says the live page is the
reference, as it already does for the post page.
BriefPostHeaderActions spread every prop it did not name onto its
container, so the contextMenuId both callers pass landed on a div and
React warned about an unknown DOM attribute. It is dropped the way
PostHeaderActions already drops it.
The share controls on the briefing list, header and body are permanent
now, so briefing_share_controls is gone along with the flag-off paths
and the tests that only covered them.

The header's Share button shows at every width. The copy link and the
settings gear stay laptop-only: below laptop the post page's sticky
header and the modal's navigation strip already carry a copy link and
the menu with Share via and Settings, so repeating them in the briefing
header would put two copy links a few rows apart on phones. The digest
page, which has no share button, keeps its laptop-only gear as before.
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