feat(squads): social streak groups, Kudos, leaderboard (#220)#223
feat(squads): social streak groups, Kudos, leaderboard (#220)#223Eliaaazzz wants to merge 2 commits into
Conversation
…220) Implements feature 1 of 3 from issue #220 — adapts the Strava Clubs + Duolingo group-streak retention pattern to nutrition logging. Backend (Spring Boot, Java 21): - V52 migration: squads, squad_members (composite PK), meal_log_kudos - 8 new ErrorCodes (2010–2017) wired into GlobalExceptionHandler - SquadService: create / join / leave / list / detail / removeMember, shared-streak evaluation, 7-day leaderboard with Warming Up tier - KudosService: toggle with self-/expiry-/cross-squad validation - SquadStreakScheduler: hourly per-squad-timezone evaluation, idempotent - InviteCodeGenerator: 6-char codes from a 32-char unambiguous alphabet - 18 Mockito unit tests covering AC-1..AC-5 Frontend (React Native + Expo): - Squads list screen with Create / Join CTAs and pull-to-refresh - Squad detail screen with streak header, share-via-system, leaderboard - KudosButton component (optimistic UI, in-flight debounce, revert on error) - SquadCreateModal (emoji palette + name) and JoinSquadModal (segmented code) - 4 Jest tests for KudosButton (toggle, revert, debounce, disabled) Routes registered in AppNavigator; entry-point wiring from Profile is a small follow-up. No design-system tokens were introduced — all theming goes through BRAND_COLORS / spacing per CLAUDE.md. Closes #220 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
aurafit | 51bff4a | May 07 2026, 07:09 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
aurafitness | 51bff4a | May 07 2026, 07:10 AM |
Deploying aurafitness-2 with
|
| Latest commit: |
51bff4a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f043682a.aurafitness-2.pages.dev |
| Branch Preview URL: | https://feat-squads.aurafitness-2.pages.dev |
Deploying aurafitness with
|
| Latest commit: |
51bff4a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b5bdb585.aurafitness.pages.dev |
| Branch Preview URL: | https://feat-squads.aurafitness.pages.dev |
Deploying aurafitness-1 with
|
| Latest commit: |
51bff4a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ae175f66.aurafitness-1.pages.dev |
| Branch Preview URL: | https://feat-squads.aurafitness-1.pages.dev |
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'Squads' feature, enabling users to form groups, track shared streaks, and interact via kudos on meal logs. The implementation includes comprehensive backend support with new entities, services for squad management and streak evaluation, database migrations, and a robust test suite. On the frontend, the PR adds necessary UI components and screens for squad creation, joining, and detailed viewing. A review comment suggests adding a dedicated test case for the ownership transfer logic in the leave method of SquadService to ensure business logic robustness.
|
|
||
| assertThatThrownBy(() -> service.leave(userB, squadId)) | ||
| .isInstanceOf(SquadException.class) | ||
| .extracting(e -> ((SquadException) e).getErrorCode()) | ||
| .isEqualTo(ErrorCode.SQUAD_ACCESS_DENIED); | ||
| } |
There was a problem hiding this comment.
The leave method in SquadService includes logic for transferring ownership to the earliest joiner if the current owner leaves and other members remain. This is a critical piece of business logic that should have a dedicated test case to ensure its correctness. Adding a test here would improve the robustness of the test suite.
Addresses gemini-code-assist's review on PR #223: SquadService.leave() contains the only place where ownership transfers to the earliest joiner when the current owner leaves with members remaining. That branch was previously uncovered. Adds two tests in SquadServiceTest: - leave_ownerWithRemainingMembers_transfersOwnershipToEarliestJoiner — three members; owner leaves; the earliest-joinedAt member is promoted (verified against a deliberately out-of-order findAllBySquadId list so the test catches a regression where ordering is taken from the list instead of from joinedAt). Verifies the new owner is persisted, the promoted member's role flips to "owner", and the squad is not dissolved. - leave_nonOwnerWithRemainingMembers_doesNotTransferOwnership — guard test: when a non-owner leaves, no ownership transfer occurs and squadRepository.save() is never called. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed in 51bff4a. Added two tests to
Local test run: Thanks for the review! |
Summary
Implements Feature 1 of 3 from issue #220 — Squads. Adapts two of the strongest published retention mechanics in consumer fitness (Strava Clubs + Duolingo group streak) to the nutrition-logging lane.
Closes #220.
What's in this PR
Backend (Spring Boot, Java 21):
V52__create_squads.sql—squads,squad_members(composite PK),meal_log_kudosErrorCodeentries (2010–2017) plumbed throughGlobalExceptionHandlervia a newSquadExceptionSquadService— create / join / leave / list / detail / removeMember, owner-transfer on leave, dissolve on last-member-leave, shared-streak evaluation, 7-day leaderboard with "Warming Up" tier (members with <3 distinct logged days are unranked)KudosService— toggle with self-forbidden, 7-day window, and cross-squad-membership checksSquadStreakScheduler—@EnableScheduling+ hourly cron; per-squad timezone idempotent evaluationInviteCodeGenerator— 6-char codes from a 32-char unambiguous alphabet (no0/O/1/I); collision-checkedSquadServiceTest,KudosServiceTest,InviteCodeGeneratorTestFrontend (React Native + Expo, TypeScript):
SquadsScreen— list with pull-to-refresh, Create / Join with code CTAs, empty stateSquadDetailScreen— streak hero header, share-via-system invite, 7-day leaderboard (auto-refreshes every 5min while focused), members list, leave with confirmSquadCreateModal— 16-emoji palette + 30-char name inputJoinSquadModal— Apple-style 6-cell segmented code entry, regex-validated to the same 32-char alphabetKudosButton— optimistic UI, in-flight debounce against rapid double-tap, revert on API error, hapticsSquadCard,SquadStreakHeader(reuses personalStreakBadgetier system)KudosButtonAcceptance Criteria coverage
MealLogCardlastActiveDaySQUAD_ACCESS_DENIED); screens wrapped withwithErrorBoundary; no hardcoded hex (theme tokens only)What's intentionally out of scope (follow-ups)
KudosButton— component is built and unit-tested; wiring it into the existing meal-log card is a small UI patch, kept separate to keep this PR reviewable.navigation.navigate('Squads')works); a one-line link fromProfileScreenwill follow.feature.squads.enabled) — to be added when rolling out; not gated in this PR since it's behind nav routes only reachable via explicit nav.Design philosophy compliance (per CLAUDE.md)
BentoCardfor all cards; no flat opaque cards.BRAND_COLORStokens — no hardcoded hex.KudosButton(damping 12–14, stiffness 150–220).withErrorBoundary.Test plan
SquadServiceTestcovers create / join (full / dup / invalid-code / limit), leave (last-member dissolve, non-member reject), streak (increment / reset preserves longest / idempotent), leaderboard (Warming Up trailing, non-member reject), shareSquad (overlap / disjoint).KudosServiceTestcovers toggle add / toggle remove / self-forbidden / expired-window / non-shared-squad / unknown-meal.InviteCodeGeneratorTestcovers shape (32-char alphabet, no confusables), retry on collision, give-up afterMAX_ATTEMPTS.KudosButton.test.tsxcovers optimistic update + reconcile, revert on error, in-flight debounce, disabled-state suppression.tsc --noEmitpasses clean for all new/modified frontend files../gradlew testlocally and verify Flyway applies V52 against a fresh Postgres.🤖 Generated with Claude Code