feat(share): leaderboard share affordances + share-my-rank card#6359
Draft
tsahimatsliah wants to merge 2 commits into
Draft
feat(share): leaderboard share affordances + share-my-rank card#6359tsahimatsliah wants to merge 2 commits into
tsahimatsliah wants to merge 2 commits into
Conversation
Adds sharing to the leaderboard surfaces behind `share_leaderboard`, gated together with the `sharing_visibility` master switch. - Personal rank share on a leaderboard page: "You're #12 on the longest streak leaderboard" with the pre-filled share text spelled out, sourced from the existing viewer-scoped `leaderboardPosition(type)` query. - Per-leaderboard share on every directory card, via a new optional `titleAction` slot on `LeaderboardListContainer`. - Page-level share on a leaderboard page (v2 header + breadcrumb row). `useLeaderboardPosition` extracts the query that `CurrentUserPositionRow` already ran, so both surfaces share one cache entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Initiative-wide normalization: one event per entity, surfaces distinguished by payload. Rank shares already carry rank in extra, so the dedicated ShareLeaderboardRank event was redundant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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. Stacked on PR 1 (#6343) — base is
claude/website-sharing-visibility-be6b32, notmain.What changed
1. Share your own rank (the headline)
A rank card above the list on a leaderboard page (
/users/[type]), for signed-in viewers who actually have a rank:TopRankBadgemedal and a different line ("Somehow I'm feat: profile modal #2 on the daily.dev longest streak leaderboard. Come take the spot.").tabular-nums; the share control is anaria-label'd, keyboard-operableShareActionstrigger (mobile → native share sheet, desktop → share popover).2. Directory share (
/users)Each leaderboard card gets its own share control next to the card title, sharing that board's permalink. Delivered through a new optional
titleActionslot onLeaderboardListContainer.3. Page-level share (
/users/[type])In the v2
PageHeaderactions slot and in the legacy breadcrumb row.Composition check: the detail page deliberately does not pass
titleAction, so the page-level share and the card share never double-render. The directory page has no page-level share for the same reason.How rank is sourced
The existing
leaderboardPosition(type)query (packages/shared/src/graphql/leaderboard.ts) →{ rank: number | null, score, cappedAt }. No new field, no invented name.userIdargument; the API resolves the position of the authenticated caller.highestReputation,longestStreak,mostReadingDays(isLeaderboardPositionSupported). Other boards render no rank card.rankisnullwhen the viewer sits pastcappedAt. That renders nothing — there is no honest number, so no share affordance. Same for loading, no data, signed-out, and unsupported boards. There is no code path that can produce "I'm #undefined".useLeaderboardPositionextracts the queryCurrentUserPositionRowalready ran inline; both now share one cache entry (identical query key).OG image — deferred, with a backend dependency
Not shipped. Building it now would be half-building it:
leaderboardPositiontakes nouserId, so an ISR image-generator page (thescreenshot_wrapperpattern inpages/image-generator/) cannot resolve someone else's rank at build time. Deriving it from the top-100 board query would silently break for anyone outside the top 100. Backend dependency: a publicleaderboardPosition(type, userId)(or equivalent) field.og:image; it cannot vary per viewer. A per-rank image needs a per-rank shareable URL, which does not exist yet.Shipping the rank-share affordance now; the image is a clean follow-up once (1) lands.
Flag
share_leaderboard, defaultfalse, appended at the end offeatureManagement.ts.Gated via
useLeaderboardShareEnabled():useSharingVisibility()(the initiative kill-switch) is evaluated first, and the per-topic flag is only evaluated withshouldEvaluate: isSharingEnabled— control users are never bucketed into the experiment.Flag-off is pixel-identical to the PR 1 branch. Both pages compute the flag and pass the slot as
undefinedwhen off, so no empty wrapper is introduced: the card heading keeps its original<h3 class="mb-2 …">directly above the<ol>, andPageHeaderrenders no actions container. Asserted in tests.New log events (end of the
LogEventenum):share leaderboard,share leaderboard rank. Reuses the existingOrigin.Leaderboard.Storybook
Components/Share/LeaderboardShare— own rank, top-3, deep rank (4 figures), all three supported boards, the renders-nothing state, and the directory card on/off side by side.packages/storybook/mock/gb.tsdoesreturn value || 'control', so afalsedefault flag reads back as the truthy string'control'and every flag looks ON in Storybook. These stories therefore never assert a flag — ON states render the presentational components (LeaderboardRankCard,LeaderboardShareControl) directly, and the control state holdsFeaturesReadyContextas not-ready. Jest owns the real flag-off guarantee.Verification
node ./scripts/typecheck-strict-changed.js— passpnpm --filter shared lint— passpnpm --filter webapp lint— passNODE_ENV=test pnpm --filter shared test— 297 suites / 2003 tests passNODE_ENV=test pnpm --filter webapp test— 44 suites / 297 tests passtsc --noEmitonpackages/storybookreports no errors in the new story (package has pre-existing errors elsewhere;pnpm --filter storybook lintcannot run)Tests cover: copy → clipboard + toast; mobile tap →
navigator.sharecarrying the rank text and tracked link; loading /rank: null/ no data / unsupported board / signed-out all render nothing; flag-off renders nothing and leaves the container DOM untouched; and the flag chaining (per-topic flag not evaluated while the kill-switch is off).🤖 Generated with Claude Code
Preview domain
https://claude-share-leaderboard.preview.app.daily.dev