Skip to content

feat(share): surface share next to Follow on tag and source pages#6357

Draft
tsahimatsliah wants to merge 1 commit into
claude/website-sharing-visibility-be6b32from
claude/share-tags-sources
Draft

feat(share): surface share next to Follow on tag and source pages#6357
tsahimatsliah wants to merge 1 commit into
claude/website-sharing-visibility-be6b32from
claude/share-tags-sources

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Jul 22, 2026

Copy link
Copy Markdown
Member

Part of the sharing visibility initiative (PR 9 / #20). Stacked on PR 1 — base is claude/website-sharing-visibility-be6b32, not main.

What changed

Share is promoted out of the buried CustomFeedOptionsMenu "…" menu into a visible control in the header action row on both entity pages:

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

New shared pieces:

  • components/share/EntityShareAction.tsx — thin wrapper over PR 1's ShareActions (variant="icon", ButtonVariant.Tertiary, aria-label/tooltip "Share"). Renders as a fragment so the host row's own gap spaces it symmetrically, and leads with a vertical rule.
  • hooks/useShareTagsSources.ts — gate: sharing_visibility master switch first, then share_tags_sources, both via useConditionalFeature with shouldEvaluate.

Wiring details:

  • Research principle feat: cookie banner #6 (distinctness): the share control is a ghost icon button behind a vertical rule, never adjacent-and-matching to the filled Follow/Following button — the two intents don't read as one segmented control.
  • No duplication: CustomFeedOptionsMenu gains an optional hideShare prop; when the visible control renders, the in-menu "Share" entry is dropped. Default false, so every other consumer's menu is unchanged.
  • No composition trap: SourceActions is also rendered by PostUsersHighlights (post page). The new control is behind an opt-in showShare prop that only /sources/[source] passes, so that consumer's DOM is untouched. TagTopicPage and SourceActions are independent surfaces — nothing double-renders.
  • Link/text reuse: both surfaces derive one shareProps object that feeds the visible control and the existing CustomFeedOptionsMenu shareProps — no recomputed URLs, both hand out the same link.

Flag

share_tags_sources, default false, appended at the end of featureManagement.ts. Gated together with useSharingVisibility(). lib/log.ts is not touched — Origin.TagPage and Origin.SourcePage already exist.

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 ("<Tag> posts | daily.dev") ✅ per-tag (flags.description, else a generated per-tag sentence) ✅ per-route, from _app's DefaultSeo (canonicalFromRouter) ⚠️ generic daily.dev card
/sources/[source] ✅ per-source source.description, else the site default ✅ per-route, same mechanism ⚠️ generic daily.dev card

So a shared tag/source link already unfurls naming the specific entity — no code change was needed, and next-seo falls back to top-level description for og:description, which both routes set per entity. Instead of a no-op edit I added __tests__/TagSourceSeo.spec.ts to lock 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. For sources, source.image is a small square logo and the site's Twitter card is summary_large_image, so substituting it would likely make unfurls worse than the branded 1200×630 default. Getting real per-entity images needs a backend/OG-service change, out of scope here.

Storybook

stories/components/EntityShareAction.stories.tsx — tag/source × following/not-following, mobile, and a Control story.

The repo-wide Storybook GrowthBook mock coerces falsy defaults to the truthy string 'control', so a "flag off" story via the mock would be lying. Control is rendered by holding FeaturesReadyContext as not-ready — the exact path useConditionalFeature takes to fall back to the default. Jest is the real flag-off guarantee.

Verification

  • node ./scripts/typecheck-strict-changed.js — no new errors. 3 remaining are pre-existing on untouched lines (CustomFeedOptionsMenu.tsx:50, SourceActions/index.tsx:110,118).
  • pnpm --filter shared lint ✅ · pnpm --filter webapp lint
  • NODE_ENV=test pnpm --filter shared test — 296 suites / 1994 tests ✅
  • NODE_ENV=test pnpm --filter webapp test — 45 suites / 302 tests ✅
  • build not run, per instructions.

Tests assert: copy → clipboard.writeText + the "✅ Copied link to clipboard" toast; mobile single tap → navigator.share; the entity log event carries provider + origin; the in-menu Share entry disappears only when the visible control shows; and flag-off renders markup byte-identical to the untouched consumer (SourceActions.spec.tsx), plus a flag-off assertion on the tag page in TagPage.tsx.

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. Switching both to a canonical ${webappUrl}tags/${tag} would be cleaner for a promoted share control — deliberately left alone to keep flag-off parity, easy follow-up.
  3. Per-entity OG images need an OG-image service for tags/sources (see table).

🤖 Generated with Claude Code

Preview domain

https://claude-share-tags-sources.preview.app.daily.dev

Promotes copy-link/share out of the buried "…" options menu into the tag
and source header action rows, behind `share_tags_sources` plus the
`sharing_visibility` master switch.

A shared `EntityShareAction` wraps the PR-1 `ShareActions` primitive: a
vertical rule plus a ghost icon button keep "share this" spatially and
visually distinct from the filled Follow/Following button, and the
in-menu Share entry is dropped while the visible control is shown so the
action isn't duplicated. Link/text derivation is shared with the existing
`CustomFeedOptionsMenu` `shareProps` rather than recomputed.

Flag off is pixel-identical to the base branch, asserted by a DOM-equality
test against the untouched consumer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@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 22, 2026 2:59pm
storybook Building Building Preview Jul 22, 2026 2:59pm

Request Review

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