Community Challenges — Feature Guide
Challenges are time-boxed goals that unite your community around shared achievement. Admins create challenges with a target goal count and a custom badge. Members join with one tap and compete together.
For Admins: Creating a Challenge
Community creators can spark challenges right from the Community page to rally their members around a shared goal.
Where to Find It
Open any community you own. At the top of the page, below the hero and above the feed tabs, you'll see one of two states:
- If a challenge is active or upcoming: The challenge card is already pinned. Tap the card to jump straight to the leaderboard — to start a new challenge instead, use the About tab → Admin Settings → "+ Create challenge" button described below.
- If no challenge is running: A banner appears: "No challenge running. Start one →" Tap "Start one" to open the admin modal.
You can also open the creation modal from the About tab → Admin Settings → "+ Create challenge" button.
The Creation Flow
Tapping "Start one →" opens a modal where you fill in:
| Field | Required | Constraints | Purpose |
|---|---|---|---|
| Challenge name | Yes | ≤100 chars | What members will see and join (e.g., "30-Day Fitness Blitz") |
| Description | No | ≤1000 chars | Context and win conditions (e.g., "Complete a goal daily for 30 days. Any goal counts!") |
| Start & End dates | Yes | End ≥ Start | Time window for the challenge. Use the presets (7 days / 30 days) or set custom dates. |
| Goal count | Yes | ≥1, default 7 | How many goal completions equal "finished" for a member (e.g., 7 goals = 7 days of the week) |
| Goal type (optional) | No | Dropdown from system | Narrow the challenge to one goal type (e.g., "Habit" only), or leave blank for "Any goal type" |
| Badge name | Yes | ≤50 chars | What the achievement is called (e.g., "Movement Maven", "Reading Ranger") |
| Badge icon | Yes | 24-emoji picker | Pick one emoji from the grid to represent the badge visually (e.g., 🏆, 🚀, 💪) |
Example
Let's say you're an admin in the "Fitness Collective" community:
- Tap "Start one →" on the community page
- Fill in:
- Name: "July Fitness Challenge"
- Description: "Complete 10 workouts by July 31. Count gym sessions, runs, yoga, anything that counts for your goals."
- Dates: Tap "30 days" preset → auto-fills today through July 31
- Goal count: 10
- Goal type: "Habit"
- Badge name: "Fitness Finisher"
- Badge icon: 💪 (tap the flexed bicep emoji)
- Tap "Launch challenge"
- The card is now pinned at the top of the Community page
- Every member receives a push notification (once backend fan-out is enabled)
For Members: Discovering & Joining Challenges
When admins create challenges, they appear pinned at the top of the Community page so members see them immediately.
The Challenge Card
The pinned card displays:
🏆 July Fitness Challenge [Join]
Ends Jul 31 · 247 joinedWhat you see depends on the challenge status and your participation:
| Scenario | Card Display | CTA |
|---|---|---|
| Challenge is upcoming | Badge, name, countdown ("Starts in 3 days"), count of members | (No button; just info) |
| Challenge is active, you haven't joined | Badge, name, end date, participant count | [Join] button |
| Challenge is active, you've joined | Badge, name, end date, participant count, Joined ✓ chip, thin progress bar | (No button; shows progress) |
| You completed the challenge | Badge, name, end date, participant count | Done ✓ chip (gold) |
| You're not a community member | Badge, name, end date, participant count | [Join] button (disabled; "Join the community first") |
| No challenge is running | (Nothing appears for you) | — |
Joining a Challenge
Tap [Join] on an active challenge. Your card updates immediately to show Joined ✓ and a thin progress bar. The progress bar fills as you complete goals that match the challenge's target type (or any goals, if no type is set).
Progress: The card shows "4 / 7 days" (for example), meaning you've completed 4 out of the 7 goals needed to finish.
Viewing the Leaderboard
Tap the challenge card's name (or the card itself) to navigate to the Leaderboard view, where you can:
- See all members' progress on the challenge
- View a celebration page if you've completed it
- Compare your pace with others in the community (future phase)
Push Notifications
When an admin creates a challenge, every active community member receives a push notification:
💬 "Fitness Collective just started a challenge: July Fitness Challenge"
Tapping the notification brings you straight to the community page with the challenge card visible.
Leaderboard
The leaderboard shows all participants' progress on the challenge in real-time, ranked by completion and progress.
How to Access
Tap the challenge card's "View leaderboard" button or the card title/name to navigate to the leaderboard view at /communities/:id/challenges/:challengeId.
What You See
The leaderboard displays:
- Rank pills — Top 3 ranked participants show gold/silver/amber background colors; remaining ranks use neutral styling
- Participant avatars — Initials in a gradient circle
- Name — First name + last initial; your own row is highlighted with a ★ You chip and a light background
- Progress bar — Visual fill from 0 to 100% completion, color-coded: blue for in-progress, green for completed
- Progress count — "X / Y" (e.g., "5 / 7") showing goals completed toward the target
- Completion date — "Completed [date]" for participants who finished (e.g., "Completed May 5, 2026")
Pagination
The leaderboard loads the first 50 participants on initial page load. Tap "Load more" at the bottom to fetch the next batch (offset-based pagination).
Completion Celebration
When you reach the challenge's goal count, a celebration modal appears immediately to acknowledge your achievement.
When It Triggers
The modal appears once when your currentUserParticipant.completed flag becomes true (i.e., when you log your final goal event to hit the target count).
Celebration Features
- Headline — "You did it!"
- Badge display — The challenge badge icon, centered with a pop-in animation
- Badge chip — "Community Badge" label above the headline
- Achievement text — "{badge_name} earned." (e.g., "Fitness Finisher earned.")
- Confetti animation — Full-screen canvas confetti burst with brand colors (blue, cyan, orange, gold) and configurable particle count
- Reduced-motion fallback — Users with
prefers-reduced-motion: reduceenabled see no confetti; the modal still appears with full content - Primary CTA — "View leaderboard →" button navigates to the leaderboard to see your rank
- Secondary CTA — "Done" button closes the modal
localStorage Gate
The modal is fired at most once per challenge per user. Dismissal state is stored in localStorage at key challenge-completion-celebrated:{publicId} to prevent re-showing on subsequent visits or page reloads.
Examples
Example 1: Weekly Wellness Challenge
Admin (Creator's POV):
- Community: "Wellbeing Warriors"
- Opens the challenge modal
- Name: "Weekly Water Habit"
- Description: "Drink 8 glasses of water daily. Log a water-intake goal each day this week."
- Dates: 7 days (preset), auto-filled to today through next Sunday
- Goal count: 7 (one per day)
- Goal type: (leaves blank → "Any goal type")
- Badge name: "Hydration Hero"
- Badge icon: 💧 (water drop)
- Launches challenge
- Community members get a push: "Wellbeing Warriors just started a challenge: Weekly Water Habit"
Member (Participant's POV):
- Opens the community, sees the card pinned
- Taps [Join]
- As they complete water-intake goals over the week, the progress bar advances: 1/7 → 2/7 → ... → 7/7
- When complete, the card shows "Done ✓" and they can optionally view their leaderboard rank
Example 2: Reading Sprint
Admin's Setup:
- Name: "Books Before Summer"
- Goal count: 5 books (set as 5, not days)
- Goal type: "Reading" (filtered to one goal type)
- Badge: "Bookworm Badge" + 📚
- Dates: 60 days (custom date range)
What members see:
- Card shows badge, dates, and participant count
- Joining works the same way (one tap)
- Progress bar fills as they log book-reading goals
- Once they've logged 5 books, the card switches to "Done ✓"
Lifecycle
Community challenges follow a predictable four-stage lifecycle:
1. Creation (Admin)
- An admin fills the creation modal and taps "Launch challenge"
- A new
CommunityChallengerecord is created withstart_date,end_date,target_goal_count, and badge metadata - All community members receive a push notification announcing the challenge
- The challenge appears pinned on the community page
2. Join (Member)
- During the upcoming and active phases, members can tap [Join] on the challenge card
- A
ChallengeParticipantrecord is created, starting withprogress_count = 0andcompleted_at = nil - The member's card updates to show Joined ✓ with an empty progress bar
3. Live (Tracking)
- Once a member joins, the backend automatically tracks their progress
- Every time a member completes a goal, the
AddGoalEventinteraction callsSocial::IncrementChallengeProgressto check if the event matches the challenge criteria (goal type, if specified) and increment the participant'sprogress_count - If it matches, the corresponding
ChallengeParticipant.progress_countis incremented - The progress bar fills in real-time as the participant logs qualifying events
- Participants can view the leaderboard at any time to see their rank relative to other members
4. Finalization (Badges)
- When a challenge's
end_datepasses, the dailyProcessChallengeCompletionsJobruns at 00:30 UTC - The job finds all
CommunityChallengerecords whereend_date < todayandcompletion_processed_at = nil - For each challenge, it calls the
FinalizeCommunityChallengeinteraction, which:- Queries all
ChallengeParticipantrecords whereprogress_count >= target_goal_count - Sets
completed_at = nowfor qualifying participants - Marks the challenge as processed by setting
completion_processed_at = now
- Queries all
- Participants who completed see their card update to Done ✓ (gold styling)
- A celebration modal appears when they next view the community (if not yet dismissed)
- The badge-completion push (
UserNotification.generate) fires only after the DB transaction above commits, and is idempotent across retries: each qualifyingChallengeParticipantis pushed at most once, gated by theirnotified_atmarker. Since the push itself can't be rolled back like a DB write, moving it outside the transaction means a rolled-back finalize attempt (e.g. a mid-loop DB error) pushes nothing, and the retry that follows pushes each qualifier exactly once — no duplicate badge notifications.
Progress Tracking
How Progress is Counted
When a member joins a challenge and logs a goal, the backend uses a data hook to automatically increment the participant's progress:
- Goal creation: A member creates and completes a goal via the
AddGoalEventinteraction, which callsSocial::IncrementChallengeProgressas a data hook - Hook check:
Social::IncrementChallengeProgresschecks all activeChallengeParticipantrecords for this user - Criteria match: For each challenge, the hook compares:
- If the challenge has a
target_goal_typeset: Does the goal's type match? - If the challenge has no
target_goal_typeset: All goal types count
- If the challenge has a
- Increment: If the goal matches criteria,
progress_countis incremented by 1 (one event = one point) - Progress bar: The frontend calculates
progress_percent = (progress_count / target_goal_count) * 100, clamped to 0–100
Supported Criteria
- Goal type filtering: Admins can restrict challenges to a specific goal type (e.g., "Habit" only). If no type is specified, all goal events count.
- Date filtering: Only goal events completed between the challenge's
start_dateandend_datecount (enforced server-side during finalization).
Limitations
- Habit check-ins are not counted: Only full goal completions trigger progress updates. Habit check-ins (daily streaks) are not yet integrated with challenges.
- No retroactive progress: Members who joined after the challenge started only count events logged after they joined, not pre-existing goals.
Completion & Badge Award
When Completion Happens
A participant is marked completed when:
- Their
progress_count >= target_goal_count, AND - The
ProcessChallengeCompletionsJobhas run after the challenge'send_date
The job runs automatically at 00:30 UTC every day. Challenges are finalized the morning after their end_date passes.
Badge Assignment
Once finalized, the participant receives:
- Badge name and icon as defined by the admin at challenge creation
- A new entry in the participant's profile badge collection
- A celebration modal on next visit (via the
challenge-completion-celebrated:{challengeId}localStorage key)
Celebration Modal
When a participant returns to the community after being marked completed, they see:
You did it!
[Badge Icon]
{badge_name} earned.
[View leaderboard →] [Done]
The modal is shown at most once per challenge per user, controlled by localStorage. Users can dismiss it and come back later to view their leaderboard rank.
Known Limitations
The following features are not yet implemented and are tracked separately:
Phase 4 Limitations
| Limitation | Issue | Impact |
|---|---|---|
| Habit check-in counting | OBJ-259 (future) | Participants can't earn challenge progress from daily habit check-ins; only full goal completions count. |
| Badge model | OBJ-** (deferred) | Badge data is stored denormalized on CommunityChallenge columns (badge_name, badge_icon), not as a separate Badge model. The PRD anticipated a badge_id FK; implementation uses denormalized columns per Orion's guidance. |
| FeedKind seed | OBJ-256 | FeedKind.challenge_completed seed is pending; challenge completion posts to the community feed are queued for implementation. |
| Push notification fan-out | OBJ-257 | Challenge creation fans out a push notification to all community members; implementation is pending. |
Related Features
For more on communities and goals, see:
- Communities — community overview, discovery, and membership
- Seasonal Events — the platform-wide counterpart to this model, deliberately not scoped to a community
- Goals — creating, tracking, and completing personal goals
- GraphQL API Reference — full API documentation for challenge queries and mutations
- PRD § 5d — requirements and acceptance criteria
Last updated: 2026-09-19 (footer currency fix, OBJ-4028 atlas sweep — content last changed 2026-09-19, PR #3347, adding the Seasonal Events cross-reference above)