Skip to content

feat(share): surface a copy-link control on tag and source pages - #6357

Draft
tsahimatsliah wants to merge 2 commits into
claude/share-split-copy-buttonfrom
claude/share-tags-sources
Draft

feat(share): surface a copy-link control on tag and source pages#6357
tsahimatsliah wants to merge 2 commits into
claude/share-split-copy-buttonfrom
claude/share-tags-sources

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Jul 22, 2026

Copy link
Copy Markdown
Member

Part of the sharing visibility initiative. Now stacked on #6369 (claude/share-split-copy-button) — not main — because both PRs share the copy-link control. Review #6343#6349#6369 → this one, or read this diff alone: it only touches the two entity headers.

What changed

Share moves out of the buried CustomFeedOptionsMenu "…" menu into the header action row on both entity pages, as #6369's SplitShareButton: the left half copies the link (the glyph cross-fades to a green check), the chevron drops the standard social list.

  • TagsTagTopicPage header (the real tag surface; PageInfoHeader named in the brief is a classed div used only by the source page).
  • SourcesSourceActions, rendered inside PageInfoHeader on /sources/[source].

The row

State Row "…" menu
Not followed Follow · Copy link ⌄ · Add to custom feed · Block
Following Following · Copy link ⌄ · bell · Add to custom feed
Blocked Unblock · Copy link ⌄ · Add to custom feed

Block leaves the row for the menu, so the row is one filled Follow button plus secondary controls that all look and behave the same. Blocked is the deliberate exception: Unblock is the only way back out of a state a user may not have meant to enter, so it stays visible — in the Follow slot, which is empty while blocked — and leaves the menu, so it never appears twice.

The notification bell takes ButtonVariant.Float, matching the copy control and the "…" button. Its state still reads from the icon.

No feature flag

This ships to everyone. share_tags_sources and useShareTagsSources are gone — nothing else read them. sharing_visibility stays in the codebase; it still gates the initiative's other surfaces, but these two rows no longer consult it.

That means no runtime off-switch for these rows: turning them off after merge takes a revert. Flagged deliberately, per the product call to show it to everyone.

Structure

  • components/share/EntityShareAction.tsx — wraps feat(share): flat end-of-conversation band + split copy-link button #6369's ShareActions with the entity's link/text/cid and the per-surface log event. display defaults to split; icon keeps the original icon-only trigger (behind a vertical rule) for any surface that wants it.
  • CustomFeedOptionsMenu gains hideShare, so the in-menu Share entry is dropped exactly where the visible control renders — never both. Default false, so every other consumer is unchanged.
  • SourceActionsNotify gains an optional variant override; without it the state-derived variant is unchanged.
  • SourceActions gains showShare. Only /sources/[source] passes it, so PostUsersHighlights on the post page keeps its existing row: Block as a button, Share inside the menu.
  • Both surfaces derive one shareProps object feeding the visible control and the existing CustomFeedOptionsMenu shareProps, so both hand out the same link.

The individual social share links

The copy path tags its link with the referral campaign through useShareOrCopyLink, but the social targets went out untagged — SocialShareList called getShortUrl(link) with no cid. The post share modal was unaffected because it pre-tags the link before handing it down, so this only hit ShareActions consumers, i.e. the control this PR surfaces. SocialShareList now takes an optional cid and ShareActions passes its own, so a share to X carries the same userid/cid a copied link does.

Two link builders also interpolated their text raw, so any &, #, ? or % in a title silently truncated or corrupted the shared text — and post titles carry those constantly:

Builder Before After
getRedditShareLink &title=${text} encodeURIComponent(text)
getTelegramShareLink &text=${text} encodeURIComponent(text)
getWhatsappShareLink bare URL, text dropped text\nlink, matching the other targets

Facebook and LinkedIn are left alone: both ignore the text/quote params they used to accept.

Note that getShortUrl returns the raw link for logged-out visitors, so their shares stay unattributed on every path, copy included. That is existing behaviour, unchanged here.

Logging

ShareProvider + per-surface Origin on the existing entity events — LogEvent.ShareTag / LogEvent.ShareSource with extra: {provider, origin}.

Deliberate deviation from the brief's LogEvent.SharePost: that event is post-shaped (always emitted via postLogEvent, carrying post_* fields and target_type: 'post'). Reusing the entity events keeps the promoted control measurable against the same event the "…" menu already fires, so the promotion is a clean before/after. Happy to switch if analytics prefers one event name.

OG meta status, per route

Checked against next-seo's actual buildTags behaviour rather than assuming.

Route og:title og:description og:url / canonical og:image
/tags/[tag] ✅ per-tag ✅ per-tag (flags.description, else a generated sentence) ✅ per-route, from _app's DefaultSeo ⚠️ generic daily.dev card
/sources/[source] ✅ per-source source.description, else the site default ✅ per-route, same mechanism ⚠️ generic daily.dev card

A shared tag/source link already unfurls naming the specific entity — no code change needed. __tests__/TagSourceSeo.spec.ts locks that behaviour (including the nullable fallbacks) so it can't silently regress now that these links are promoted.

Explicitly not faked: per-entity og:image. There is no OG-image renderer for tags or sources — og.daily.dev only serves /api/posts/:id. Needs a backend/OG-service change, out of scope here.

Storybook

Components/Share/EntityShareAction — one All States page mapping every surface, state and opt-in combination, a before/after comparison against the old row, the three copy-control displays, and an interactive playground. Plus per-state stories for isolated review.

Verification

  • NODE_ENV=test pnpm --filter shared test — 298 suites / 2009 tests ✅
  • NODE_ENV=test pnpm --filter webapp test — 45 suites / 302 tests ✅ (cross-package check per AGENTS.md; SourcePage.tsx and TagPage.tsx reworked to reach Block through the menu)
  • pnpm --filter shared lint ✅ · pnpm --filter webapp lint
  • node ./scripts/typecheck-strict-changed.js ✅ — clean. The 3 pre-existing violations in the files this PR touches are fixed rather than skip-listed: feeds?.edges?.length > 0feeds?.edges?.length, and source.id hoisted to a sourceId fallback (source.id ?? source.handle) for the follow/unfollow entity id, since Source['id'] is optional on the model.
  • build not run, per instructions.

On the share links specifically: a regression test builds Reddit/Telegram/WhatsApp links from Rust & Go: what #1 teams ask? 100% real and reads the params back through URL, and the attribution test seeds the query cache under the tagged url's key so it only passes if the cid actually reaches the social path.

Tests assert: the label copies straight to the clipboard with the "✅ Copied link to clipboard" toast; the chevron opens the list without copying; mobile single tap → navigator.share; the entity log event carries provider + origin; the in-menu Share entry disappears only where the visible control shows; Block moves to the menu and Unblock returns to the row; and the embedded consumer keeps its original row.

Open questions

  1. Log event name — entity events (ShareTag/ShareSource) vs the brief's SharePost. See above.
  2. Tag share link is globalThis?.location?.href, inherited verbatim from the existing menu wiring so both controls agree. It carries whatever query string the visitor arrived with. A canonical ${webappUrl}tags/${tag} would be cleaner for a promoted control — easy follow-up.
  3. Block sits under "Add to custom feed" in the menu (additionalOptions appends). Say the word if it should sit first.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Jul 28, 2026 11:47am
storybook Building Building Preview Jul 28, 2026 11:47am

Request Review

@tsahimatsliah
tsahimatsliah force-pushed the claude/share-tags-sources branch from e168f1e to 88b557e Compare July 27, 2026 12:02
@tsahimatsliah
tsahimatsliah changed the base branch from claude/website-sharing-visibility-be6b32 to claude/share-split-copy-button July 27, 2026 12:03
@tsahimatsliah tsahimatsliah changed the title feat(share): surface share next to Follow on tag and source pages feat(share): surface a copy-link control on tag and source pages Jul 27, 2026
@tsahimatsliah
tsahimatsliah force-pushed the claude/share-tags-sources branch from 88b557e to b9571d5 Compare July 27, 2026 12:14
Share moves out of the buried "..." options menu into the header action row on
both entity pages, as PR #6369's SplitShareButton: the label copies the link and
the chevron drops the standard social list.

- New `EntityShareAction` wraps `ShareActions` with the entity's link/text/cid
  and logs `ShareTag`/`ShareSource` with the provider and origin. `display`
  defaults to the split control; `icon` keeps the icon-only trigger.
- Block leaves the row for the "..." menu, so the row is one Follow button plus
  identical secondary controls. Blocked is the exception: Unblock stays in the
  row, in the Follow slot that is empty then, and leaves the menu.
- The notification bell takes ButtonVariant.Float so the bell, the copy control
  and the "..." button read as one treatment.
- `CustomFeedOptionsMenu` gains `hideShare` so the in-menu Share entry is
  dropped where the visible control renders, never both.
- `SourceActions` gains `showShare`; only /sources/[source] passes it, so the
  post page highlights widget keeps its existing row.

No feature flag: this ships to everyone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The copy path tags its link with the referral campaign through
`useShareOrCopyLink`, but the social targets went out untagged: `SocialShareList`
called `getShortUrl(link)` with no cid. The post share modal was unaffected — it
pre-tags the link before handing it down — so this only hit `ShareActions`
consumers, which is the control this PR surfaces. `SocialShareList` now takes an
optional `cid` and `ShareActions` passes its own.

Two link builders also interpolated their text raw, so any `&`, `#`, `?` or `%`
in a title silently truncated or corrupted the shared text:

- `getRedditShareLink` — `&title=` now encoded
- `getTelegramShareLink` — `&text=` now encoded

And `getWhatsappShareLink` shared a bare URL while every other target carried the
description; it now sends the text with the link.

Facebook and LinkedIn are left alone — both ignore text/quote params.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant