feat(share): celebrations, achievements & game center share (PR 13)#6360
Draft
tsahimatsliah wants to merge 3 commits into
Draft
feat(share): celebrations, achievements & game center share (PR 13)#6360tsahimatsliah wants to merge 3 commits into
tsahimatsliah wants to merge 3 commits into
Conversation
Adds per-achievement share/download, a peak-moment share on the two celebration modals, and a page-level share on the profile achievements tab — all behind `share_celebrations` + the `sharing_visibility` master kill-switch. Control renders every surface exactly as it does today. - add the achievement image-generator page (screenshot_wrapper + ISR) and the AchievementShareCard it renders - add AchievementShareActions (badge download + ShareActions) and wire it into AchievementCard (opt-in prop), the unlock modal and the showcase picker - add a share row to the streak milestone modal - add a page-level share to ProfileAchievements - add share_celebrations flag, useShareCelebrations gate, log events 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. Page-level achievement shares reuse ShareAchievement (target_type/target_id already distinguish page vs card); the streak milestone share reuses the generic ShareLog with target_type StreaksMilestone carrying the milestone context. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Without it, target_id silently switched meaning (achievement id vs user id) under one event name. Matches the ShareProfile event shape. 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.Adds share/download affordances to the celebration + achievement surfaces, all behind one flag. Control is pixel-identical to the PR 1 branch.
Flag
share_celebrations(defaultfalse, appended at the end offeatureManagement.ts).Every surface gates on
useShareCelebrations(), which requires both the initiative's master kill-switch (sharing_visibility) andshare_celebrations, evaluated throughuseConditionalFeaturewithshouldEvaluateso GrowthBook is only hit when the surface would actually render.useShareCelebrations.spec.tsxasserts the full truth table (off/off, off/on, on/off, on/on, opted-out).Per-item status
#13 — per-achievement image + Download/Share (headline) — shipped, with one backend dependency
packages/webapp/pages/image-generator/achievement/[...params].tsx(/image-generator/achievement/<userId>/<achievementId>), following the existing screenshot pattern exactly:getStaticPathswithfallback: 'blocking',revalidate: 60, and a singleid="screenshot_wrapper"wrapper — same shape asdevcards/[userId].tsxandbadges/[badgeId].tsx. No OG-image library was added; there is none in this repo.getProfile) and achievements (getUserAchievements) server-side and 404s for a locked achievement, so the page can never render a card claiming an achievement the user does not have.AchievementShareCard— a static, self-contained render (badge art, name, description, points, rarity tier glow, owner, unlock date, daily.dev logo) with no viewport/hover/client-only dependencies.achievement.image) — which works today with no backend change. Once the service learns/image-generator/achievement/<userId>/<achievementId>and exposes the resulting URL on the achievement payload, Download can point at the richer card with a one-line change. The frontend renders thescreenshot_wrapperpage regardless.AchievementShareActionscomponent into:AchievementCard.tsx— opt-in via a newshareUserprop. OnlyAchievementsList(the achievements tab) passes it, so the card's other consumers (Game Center,AchievementsWidget,ProfileAchievementShowcasehover card,AchievementTrackerButton×2) keep their current DOM and get no share control. Only unlocked achievements get the row.AchievementShowcaseModal.tsx— the share control is rendered as a sibling of the row toggle, never inside it (a<button>nested in a<button>is invalid and would swallow row selection). With the flag off the row renders inside aReact.Fragment, i.e. byte-identical DOM.#12 — share on celebration completion — partially shipped, quests deferred
Covered, each with a deliberate copy choice:
AchievementCompletionModal, celebrate phase)+N pointschip — the visual peak, before "Choose next goal"NewStreakModal)daily.devDeferred: quest / task claim and level-up. Reasons, stated plainly rather than shipped half-done:
QuestButton.tsx(~1250 lines) via a flying-reward particle layer plus a level-up firework overlay, rendered from two places (the header button and thepanelOnlydaily-quests page) — a share row bolted in there would double-render or fight the animation, exactly the composition trap this batch was warned about./daily-questsisProtectedPage+noindex, so anyone who followed the link would hit a login wall.Doing it properly means a real completion surface, which is a product decision, not a wiring change. Flagged rather than faked.
#17 — page-level share on profile → achievements — shipped
ProfileAchievements.tsxgets one share control next to the points/count line ("Share achievements"). It only evaluates the flag once achievements have loaded, are non-empty, and the profile has a username. Per-card controls use a distinct label ("Share this achievement"), so the page never presents two identically-labelled affordances.#16 — page-level share on Game Center — skipped: the page is not public
Verified before building anything:
packages/webapp/pages/game-center/index.tsxwraps its whole tree in<ProtectedPage>and its SEO setsnoindex: true/nofollow. Anyone receiving a Game Center link would hit an auth wall, so a share button there would ship a broken promise. Skipped deliberately, per the plan's instruction to verify first. If Game Center is ever made public, the sameShareActions+Origin.GameCenter(already added in PR 1) drop in as a two-line change.Composition-trap audit
Every render site of every touched component was read. Exactly one share control per surface:
/[userId]/achievements: one page-level control (ProfileAchievements) + one per unlocked card (AchievementsList→AchievementCard). No nesting, distinct labels./[userId]:ProfileAchievementShowcaserendersAchievementCardwithoutshareUser→ no control; the showcase edit modal (owner-only) has one per row.AchievementsWidget,AchievementTrackerButton: noshareUser→ unchanged.AchievementShowcaseModaldoes not composeAchievementCard(it builds its own rows), so there is no double render.Verification
node ./scripts/typecheck-strict-changed.js— passpnpm --filter shared lint— passpnpm --filter webapp lint— passNODE_ENV=test pnpm --filter shared test— 298 suites / 2004 tests passNODE_ENV=test pnpm --filter webapp test— 44 suites / 297 tests passtsc --noEmiton the storybook package reports no errors in the new story (the package has a pre-existing strict backlog elsewhere)buildnot run, per the batch instructionsNew tests assert:
navigator.clipboard.writeTextand the "Copied link to clipboard" toastnavigator.share(native path), and that clipboard is not used in that caseuseShareCelebrations.spec.tsxStorybook
packages/storybook/stories/components/AchievementShare.stories.tsxcovers the unlocked card with share, the same card with the flag off, a locked card, the labelled celebration pair, and the generated share image in both a normal and a rarest (emerald) tier.Note for reviewers: the repo-wide Storybook GrowthBook mock (
mock/gb.ts) doesreturn value || 'control', so a flag defaulting tofalseresolves to the truthy string'control'and always reads as ON. The flag-off story therefore holdsFeaturesReadyContextas not ready, which is the only honest way to render control in Storybook. Jest is unaffected and is the real flag-off guarantee.🤖 Generated with Claude Code
Preview domain
https://claude-share-celebrations.preview.app.daily.dev