feat(gamification): tappable streak badge → streak page (#108) - #129
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
Clean, focused PR. Both badges correctly swapped to interactive elements (<button> on web, <Pressable> on mobile) with proper router navigation and no regressions. The mobile stopPropagation pattern mirrors the existing NotificationBell and habit-emoji-selector precedents, and the doc comment explains the non-obvious propagation invariant. Cross-platform parity is fully satisfied, tests cover the new behavior on both platforms, and production code is free of any, console.log, and hardcoded strings.
There was a problem hiding this comment.
Second commit (auto-freeze UI redesign) is clean and well-structured — both platforms updated, i18n parity maintained, tests rewritten to match the new model, and the manual activation flow deleted end-to-end. One stale-code issue blocks approval.
Dead export in packages/shared/src/types/gamification.ts (lines 81–88)
This PR deletes every caller of StreakFreezeResponse:
useActivateStreakFreezeremoved from bothapps/web/hooks/use-gamification.tsandapps/mobile/hooks/use-gamification.ts- The
streakFreezeendpoint constant removed frompackages/shared/src/api/endpoints.ts
But streakFreezeResponseSchema and the inferred StreakFreezeResponse type were not removed. A full-repo grep confirms zero references to either symbol anywhere in the monorepo outside their own definition. Delete both:
// packages/shared/src/types/gamification.ts — remove lines 81-88:
export const streakFreezeResponseSchema = z.object({
freezesRemainingThisMonth: z.number(),
frozenDate: z.string(),
currentStreak: z.number(),
streakFreezesAccumulated: z.number().default(0),
})
export type StreakFreezeResponse = z.infer<typeof streakFreezeResponseSchema>Everything else — the isFrozenToday ref-based celebration trigger (identical pattern on both platforms), the ChargeGauge pip animations (CSS + Animated.timing respectively), the Pro gate, and the nextFreezeDays arithmetic — is correct.
There was a problem hiding this comment.
The changes-requested item from the prior review — dead StreakFreezeResponse / streakFreezeResponseSchema in packages/shared/src/types/gamification.ts — is fully addressed by the third commit. A full-repo grep confirms zero references remain. The rest of the PR (tappable badge on both platforms, auto-freeze UI redesign with Pro gate, deleted manual-activation flow end-to-end, i18n parity maintained, badge tests on both platforms) was already approved in the earlier pass. No new issues introduced. Approving.
There was a problem hiding this comment.
Fourth commit is a clean polish pass. The cryptic AUTO chip (which read as a technical flag rather than user communication) is removed from both platforms in perfect parity, the now-dead auto.chip i18n key is deleted from both locales, the explainer is tightened to a single plain sentence, and the tests are updated to match. No new issues introduced.
The full PR is solid: tappable StreakBadge on both platforms, complete manual-activation removal end-to-end, auto-freeze UI with charge gauge + Pro gate, celebration wired to the isFrozenToday transition, dead StreakFreezeResponse type and endpoint constant cleaned up, i18n parity throughout.
There was a problem hiding this comment.
Fifth commit is a clean unification pass. The two tier threshold ladders had genuinely drifted (web mapped 7–29 as 'strong', mobile as 'steady'; web had no 365-day tier branch, mobile reused a motivational-text key as a tier label) — extracting getStreakTierLabelKey into packages/shared fixes all of that in one shot. Both platforms now render the tier in a matching SettingsGroupRow + primary dot, the dead reserved CSS hooks are gone per the delete-unused-code standard, the useMemo wrapper is correctly dropped per React 19 compiler rules, all four tier keys are present in both locales, and the shared test covers every boundary value. The full PR is solid end to end.
Make the Today-header StreakBadge interactive on both platforms so a tap
routes to the existing /streak page.
- Web: render the badge as a <button> (implicit button role) that calls
router.push('/streak') via next/navigation, preserving the aria-label,
frozen/active stroke logic, and hairline pill styling.
- Mobile: wrap the badge in a Pressable with accessibilityRole="button"
and onPress → router.push('/streak') (expo-router). The press stops
propagation so the Today header's outer go-to-today Pressable does not
also fire — matching the sibling NotificationBell behavior.
Badge tests on both platforms assert the button role and that activating
the badge navigates to /streak; the mobile test also asserts propagation
is stopped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…108) Remove the manual streak-freeze activation flow across web and mobile and redesign the freeze section for the auto-activation model. - Delete useActivateStreakFreeze (web + mobile), the activate-freeze Server Action, and the streakFreeze endpoint constant. Keep gamificationKeys.streak() for refetching. - Redesign the freeze section (Linear-tactical): SectionLabel + AUTO chip, auto explainer, a banked charge gauge (frozen pips, mount fade/scale), Used-this-month and Next-freeze rows, and an auto-protected days list. - Gate the section behind Pro (profile.hasProAccess); free users get a quiet hairline Pro line linking to /upgrade. - Rewire StreakFreezeCelebration to fire on the isFrozenToday false->true transition (auto-applied freeze) instead of manual-activation success. - Replace manual i18n keys under streakDisplay.freeze with the auto-model copy in both en and pt-BR; update streak page + hook tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The manual streak-freeze activation hook and endpoint were removed in this PR, leaving streakFreezeResponseSchema and StreakFreezeResponse with zero references. Deletes both per the delete-unused-code standard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The floating AUTO eyebrow chip had no referent and read as a technical flag instead of communication. Removed it from the web and mobile streak freeze sections and deleted the now-dead auto.chip i18n key. Rewrote the explainer to a single sentence carrying the automatic behavior, dropping the confusing "Nothing to tap" line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Web rendered the streak tier only as an invisible hero-tint CSS class (overridden by inline styles), while mobile showed it as an explicit Stats row -- and the two threshold ladders had drifted apart. Add a shared getStreakTierLabelKey as the single source of truth, render the tier as an explicit Stats row on both platforms, and remove the dead web tint code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
No new commits since the fifth-round approval. Full PR is solid end to end: tappable StreakBadge on both platforms, manual freeze activation removed end-to-end (hook, Server Action, endpoint constant, i18n keys all deleted), auto-freeze UI with charge-gauge + Pro gate at parity, isFrozenToday-transition celebration, dead StreakFreezeResponse type and endpoint constant cleaned up, AUTO chip removed, and getStreakTierLabelKey extracted into packages/shared as the single source of truth for tier thresholds (all four tier keys present in both locales, boundary values covered by the shared test). Production code is free of any, console.log, and hardcoded user-facing strings throughout.
|



Closes #108
What
Make the Today-header
StreakBadgeinteractive on both web and mobile so a tap routes to the existing/streakpage.How
apps/web/components/gamification/streak-badge.tsx): render the badge as a<button>(implicit button role) callingrouter.push('/streak')vianext/navigation, preserving thearia-label, frozen/active stroke logic, and hairline pill styling. Mirrors the existing/streaknavigation on the profile page.apps/mobile/components/gamification/streak-badge.tsx): wrap the badge in aPressablewithaccessibilityRole="button"andonPress → router.push('/streak')(expo-router). The press callsevent.stopPropagation()so the Today header's outer<Pressable onPress={onGoToToday}>does not also fire — same pattern as the siblingNotificationBell.today-shell.tsx(web + mobile) and the page/index containers are intentionally untouched — the interaction is self-contained in the badge.Tests / validation
/streak; the mobile test also asserts propagation is stopped.npm run type-checkGREEN (all 3 workspaces),npm run lintGREEN.npm test: web 1449 passed (149 files), mobile 366 passed (82 files).Backend
Paired API PR (auto-activate streak freeze on a missed day): thomasluizon/orbit-api#179