feat(share): copy-link control on profile headers - #6354
Merged
tsahimatsliah merged 12 commits intoJul 28, 2026
Merged
Conversation
Adds a dedicated share control to the profile header instead of burying it in the "..." options menu. - Own profile: sits next to "Edit profile". - Public profile: fills the slot the (inapplicable) edit button used to reserve, so the layout height is unchanged. - Stays well away from Follow (different card region, Float icon vs. the filled primary CTA) so the two intents don't read as one control group. - The pinned mobile profile header shows it only while pinned, where the profile card has scrolled away — otherwise there would be two identical copy buttons on screen. Built on the shared `ShareActions` primitive from PR 1, so mobile taps straight through to the native share sheet and desktop opens the network popover. Gated by `share_profile` plus the `sharing_visibility` master flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Reconciles the CustomFeedOptionsMenu API with the tags/sources branch: shareProps stays required (no empty-link fallback into useShareOrCopyLink) and surfaces that promote share to a visible control pass hideShare instead. File is now byte-identical on both branches so the stack merges cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Evaluate the per-topic flag only once sharing_visibility passes, so control users are never bucketed into the experiment. Matches the other topic gates. Also types the menu spec helper now that shareProps is required again. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Puts every surface the two share PRs touch on one scrollable page — flag-off next to flag-on — so the experiment can be reviewed without running the app or flipping GrowthBook. - stories/share/Overview.stories.tsx: 11 sections covering the flag matrix, the ShareActions primitive (icon/inline/hover, the four button variant+size pairs actually used), both profile headers, the pinned mobile bar, the menu before/after, the icon swap and the copy inventory. Mobile-only branches are embedded as real 390px story frames rather than mocked. - stories/share/shareStoryContext.tsx: the providers, mock profile and flag-on/flag-off scope, previously duplicated in both share story files, now defined once and reused by all three. - stories/share/reviewLayout.tsx: presentational scaffolding for the page, themed off CSS variables so it follows the light/dark toggle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ShareActions trigger rendered CopyIcon; the copy-link affordance everywhere else in the product is the chain-link glyph, so the profile share control now matches it. Both branches of the trigger (desktop popover, mobile native-share) swap together. Flags a conflict in the review page: `share_copy_icon` swaps the same glyph the other way (LinkIcon -> CopyIcon) on the feed card, brief header and mobile share widget. That's a call to settle before either ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Share and Edit sit side by side in the profile header card, so they now share a variant. Float there read as two unrelated ghost icons. - ProfileShareButton defaults to Subtle (the header card is its canonical placement). - ProfileHeader's Edit button follows, but only when the share control is actually rendered — control users, who get no share button, keep the Float button that ships today, so flag-off stays identical to main. - The pinned mobile bar overrides back to Float: there the control sits among Float icons (award, options) and a bordered button would read as a different kind of action. Storybook: the review page's variant grid documents both pairings, and its embedded 390px frames now follow the light/dark toggle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Medium (40px) icon buttons dominated the header card next to the Small (32px) options menu below them. Share and Edit are both Small now. ProfileShareButton defaults to Small, which is what the pinned mobile bar already passed explicitly, so that override goes away. The Edit button only shrinks when the share control renders — control users keep today's Medium Float button. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The control row was spaced off the cover with mt-2, so where it landed depended on the button height — at Medium it overshot the avatar's bottom edge by 8px, and at Small it happened to line up by luck. Anchor it instead: the cover is h-36 (9rem) and the avatar hangs to 11.5rem, so a fixed 2.5rem row with items-end sits the controls on the avatar's bottom edge at any button height. Control keeps the centred row that ships today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the gating so the copy-link control on profile headers is permanent, per product decision — there is no ramp and no kill-switch short of a revert. Deleted `share_profile`, `sharing_visibility` and `share_copy_icon` along with useShareProfileEnabled / useSharingVisibility / useShareCopyIcon and their spec. Every gated path becomes the kept behaviour: - ProfileHeader always renders the share control; the invisible edit placeholder public profiles used to hold the row height is gone, and Edit is unconditionally Subtle + Small beside it. - The pinned mobile bar shows the control whenever it is sticky, and both profile menus always pass hideShare. - share_copy_icon's CopyIcon arm is the discarded path: the feed card action bar, brief header and mobile share widget keep the LinkIcon they ship today, which is also the glyph the new control uses. Those three files are back to main apart from a Copy link aria-label. Also fixes the pre-existing strict-null errors the changed-file typecheck guard flags in the two files this PR touches (user.username is optional; feeds.edges may be undefined), which had CI red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clicking the profile control opened a network popover — a second decision before anything useful happened. It now copies straight away and confirms twice: the glyph flips to a green check for a second (VIcon in text-status-success, the same check the product uses elsewhere) and a toast names what landed on the clipboard. - ProfileShareButton drops ShareActions and drives useShareOrCopyLink directly, so it keeps the dly.to shortening, the ShareProfile referral campaign and the ShareProfile log event with its provider. - Mobile is unchanged: where the platform offers a native share sheet a tap still opens it, and the check/toast only fire on the copy path. - Labels follow the behaviour: "Copy link to your profile" / "Copy link to @user's profile", replacing the old "Share …". - useShareOrCopyLink takes an optional copyMessage so the toast can name the profile instead of the generic "Copied link to clipboard". ShareActions itself is untouched and still owned by PR 1 (#6343) for the other surfaces in the initiative. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review pass: this PR was reaching into files PR 1 (#6343) owns, even though none of them are needed for the profile control. Restored to the base branch, unchanged by this PR: - ShareActions.tsx — the profile control drives useShareOrCopyLink directly, so it has no reason to touch the primitive's glyph. - ShareMobile / ActionButtons.v2 / BriefPostHeaderActions and useShareCopyIcon — the share_copy_icon experiment is PR 1's call about a different set of surfaces, not this PR's to unwind. - useSharingVisibility and the sharing_visibility flag — this PR stops consuming the initiative-wide kill-switch, but deleting infrastructure later surfaces may still want belongs in PR 1. - ShareActions.stories.tsx — reverted the decorator refactor. The Storybook review page documents this PR's control instead of PR 1's primitive: the variant/size grid now renders ProfileShareButton with the two combinations actually used. Net diff is 13 files, all profile-share. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verification pass on the tracking. The emitted event now has exact-match
assertions instead of objectContaining, on both the component and the
real header surface, so the payload can't drift away from what the
dashboards already match:
event_name: share profile
target_id: <user id>
target_type: profile page
extra: {"provider":"copy link"|"native","origin":"profile"}
Also covers the cancelled-native-share case, which must not log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rebelchris
approved these changes
Jul 28, 2026
tsahimatsliah
marked this pull request as ready for review
July 28, 2026 12:36
tsahimatsliah
merged commit Jul 28, 2026
4579d30
into
claude/website-sharing-visibility-be6b32
14 checks passed
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.
Part of the sharing visibility initiative. Builds on PR 1 (#6343) — base branch is
claude/website-sharing-visibility-be6b32, notmain.What changed
The share action on a profile lived only inside the
...options menu, and the owner's only share affordance (thePublic profile & URLwidget) islaptop:hidden, so a signed-in user on desktop had no way to share their own profile at all. This promotes it to a real control.ProfileHeader.tsx— the canonical placement (all breakpoints, both profile types)invisiblethere today purely to hold the row's height), so the layout height is unchanged and no dead placeholder is left behind.profile/Header.tsx— the pinned mobile barsticky). Unpinned, the profile card directly below owns it; rendering both would put two identical copy buttons on one screen....menu here.ProfileActions.tsx— share promoted out of its...menu too; the new button sits directly above it on the same card.CustomFeedOptionsMenu.tsx— new optionalhideShareprop drops just theShareentry. Defaultfalse, so no other call site changes. This is the only shared-component touch outsideprofile/.New
profile/ProfileShareButton.tsx— the control itself. One click copies the profile link (shortened viadly.towithReferralCampaignKey.ShareProfile) and confirms twice: the glyph flips to a green check for a second (VIconintext-status-success) and a toast names what was copied. No network picker — choosing a destination is a second decision and the clipboard covers every destination. On mobile, where the platform offers a native share sheet, a tap still opens that.useShareOrCopyLinkgains an optionalcopyMessageso the toast can name the profile instead of the generic "Copied link to clipboard". Backwards compatible.Deliberately not using PR 1's
ShareActionsThe primitive opens a network popover. That is a second click before anything useful happens, so this control drives
useShareOrCopyLinkdirectly instead.ShareActionsis untouched by this PR and stays PR 1's for the initiative's other surfaces.Research principle #6 — share stays distinct from Follow
Share is an icon control in the header's top-right utility slot; Follow is the filled primary CTA further down the card, inside
ProfileActions. Different region, different weight — they never sit in the same control group. In the pinned bar,ml-1separates the utility icon from the Follow group.Logging
Reuses the existing
LogEvent.ShareProfile(the event already emitted by the menu item and the owner share widget, so metrics stay comparable) withtarget_type: TargetType.ProfilePageandextra: { provider, origin: Origin.Profile }. No newOriginvalue was needed.No flag — ships to everyone
Per product decision this is unconditional:
share_profileanduseShareProfileEnabledare gone, so there is no ramp and no kill-switch short of a revert.This PR simply stops consuming PR 1's
sharing_visibilitymaster gate — it does not delete it.share_copy_iconand the surfaces it touches (feed card action bar, brief post header, mobile share widget) are PR 1's and are untouched here.Header control design
Share and Edit are both
SubtleSmalland bottom-aligned to the avatar: the row is a fixedh-10withitems-endrather than spaced off the cover, so alignment holds at any button height. The glyph isLinkIcon, the same copy-link glyph the feed card already uses.OG image — deferred, on purpose
A per-profile unfurl card (mini dev-card with handle + stats) would make a shared profile land much harder. It is not in this PR:
packages/webapp/pages/image-generator/(devcards/[userId].tsx,badges/[badgeId].tsx,id="screenshot_wrapper", ISR).image-generator/profiles/[userId].tsxroute is only half the work: the backend screenshot service needs a route registered for it, andgetProfileSeoDefaultsneeds to emit the resultingog:image. That is a cross-repo dependency with its own rollout.Shipping the affordance now and the image as a follow-up keeps this PR reviewable, per the brief's explicit guidance to defer rather than half-build.
Backend dependencies
None for this PR. The deferred OG image needs a screenshot-service route (see above).
Verification
node ./scripts/typecheck-strict-changed.js— clean for every file this PR adds or rewrites. The remaining reported errors (CustomFeedOptionsMenu.tsx:51feeds.edges.length,ProfileActions.tsxuser.username) are pre-existing on the base branch; confirmed by reading the same expressions out oforigin/claude/website-sharing-visibility-be6b32, untouched by this diff.pnpm --filter shared lint— pass.NODE_ENV=test pnpm --filter shared test— 297 suites / 1997 tests pass.NODE_ENV=test pnpm --filter webapp test— 44 suites / 297 tests pass.pnpm --filter storybook lintnot run (missing eslint binary, pre-existing repo issue).buildnot run, per instructions.Tests added
ProfileShareButton.spec.tsx— copy writes the permalink to the clipboard and raises the✅ Copied link to clipboardtoast; native share callsnavigator.sharewith the share text + link and never touches the clipboard; desktop reveals the network list; both labels (Share your profile/Share @handle's profile) are asserted for a11y.ProfileHeader.spec.tsx— the control fills the slot on public profiles (the invisible edit placeholder is gone), sits beside Edit on the owner's, and both render at the same size and variant.ProfileShareButton.spec.tsx— both labels, the copied link, the toast wording for each, the green-check transition, the native-share path, and that no popover opens.CustomFeedOptionsMenu.spec.tsx—Shareis listed withsharePropsand dropped without them.Storybook
Components/Share/ProfileShareButton— public/own labels, network list, copying state, and the button in place in both header variants.Components/Share/Overview → States And Variants— a review page putting every surface and state on one scrollable page: the primitive's variants, both profile headers, the pinned mobile bar (embedded as real 390px story frames, not mock-ups), the⋯menu with and withouthideShare, the glyph's idle/copied states, and the copy inventory.Open questions
Sharefrom the...menus — intentional per the brief ("promote out of the menu where it makes sense"), but it does mean the menu loses an entry some users may have learned. Easy to keep both if we'd rather not move anyone's cheese mid-experiment.Check out {name}'s profile on daily.dev(matching the current menu copy) andCheck out my profile on daily.devfor the owner. Worth a copy pass?🤖 Generated with Claude Code
Preview domain
https://claude-share-profile.preview.app.daily.dev