Skip to content

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 tabAdmin Settings"+ Create challenge" button.

The Creation Flow

Tapping "Start one →" opens a modal where you fill in:

FieldRequiredConstraintsPurpose
Challenge nameYes≤100 charsWhat members will see and join (e.g., "30-Day Fitness Blitz")
DescriptionNo≤1000 charsContext and win conditions (e.g., "Complete a goal daily for 30 days. Any goal counts!")
Start & End datesYesEnd ≥ StartTime window for the challenge. Use the presets (7 days / 30 days) or set custom dates.
Goal countYes≥1, default 7How many goal completions equal "finished" for a member (e.g., 7 goals = 7 days of the week)
Goal type (optional)NoDropdown from systemNarrow the challenge to one goal type (e.g., "Habit" only), or leave blank for "Any goal type"
Badge nameYes≤50 charsWhat the achievement is called (e.g., "Movement Maven", "Reading Ranger")
Badge iconYes24-emoji pickerPick 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:

  1. Tap "Start one →" on the community page
  2. 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)
  3. Tap "Launch challenge"
  4. The card is now pinned at the top of the Community page
  5. 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 joined

What you see depends on the challenge status and your participation:

ScenarioCard DisplayCTA
Challenge is upcomingBadge, name, countdown ("Starts in 3 days"), count of members(No button; just info)
Challenge is active, you haven't joinedBadge, name, end date, participant count[Join] button
Challenge is active, you've joinedBadge, name, end date, participant count, Joined ✓ chip, thin progress bar(No button; shows progress)
You completed the challengeBadge, name, end date, participant countDone ✓ chip (gold)
You're not a community memberBadge, 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: reduce enabled 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):

  1. Community: "Wellbeing Warriors"
  2. Opens the challenge modal
  3. Name: "Weekly Water Habit"
  4. Description: "Drink 8 glasses of water daily. Log a water-intake goal each day this week."
  5. Dates: 7 days (preset), auto-filled to today through next Sunday
  6. Goal count: 7 (one per day)
  7. Goal type: (leaves blank → "Any goal type")
  8. Badge name: "Hydration Hero"
  9. Badge icon: 💧 (water drop)
  10. Launches challenge
  11. Community members get a push: "Wellbeing Warriors just started a challenge: Weekly Water Habit"

Member (Participant's POV):

  1. Opens the community, sees the card pinned
  2. Taps [Join]
  3. As they complete water-intake goals over the week, the progress bar advances: 1/7 → 2/7 → ... → 7/7
  4. 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 CommunityChallenge record is created with start_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 ChallengeParticipant record is created, starting with progress_count = 0 and completed_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 AddGoalEvent interaction calls Social::IncrementChallengeProgress to check if the event matches the challenge criteria (goal type, if specified) and increment the participant's progress_count
  • If it matches, the corresponding ChallengeParticipant.progress_count is 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_date passes, the daily ProcessChallengeCompletionsJob runs at 00:30 UTC
  • The job finds all CommunityChallenge records where end_date < today and completion_processed_at = nil
  • For each challenge, it calls the FinalizeCommunityChallenge interaction, which:
    • Queries all ChallengeParticipant records where progress_count >= target_goal_count
    • Sets completed_at = now for qualifying participants
    • Marks the challenge as processed by setting completion_processed_at = now
  • 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 qualifying ChallengeParticipant is pushed at most once, gated by their notified_at marker. 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:

  1. Goal creation: A member creates and completes a goal via the AddGoalEvent interaction, which calls Social::IncrementChallengeProgress as a data hook
  2. Hook check: Social::IncrementChallengeProgress checks all active ChallengeParticipant records for this user
  3. Criteria match: For each challenge, the hook compares:
    • If the challenge has a target_goal_type set: Does the goal's type match?
    • If the challenge has no target_goal_type set: All goal types count
  4. Increment: If the goal matches criteria, progress_count is incremented by 1 (one event = one point)
  5. 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_date and end_date count (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:

  1. Their progress_count >= target_goal_count, AND
  2. The ProcessChallengeCompletionsJob has run after the challenge's end_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

LimitationIssueImpact
Habit check-in countingOBJ-259 (future)Participants can't earn challenge progress from daily habit check-ins; only full goal completions count.
Badge modelOBJ-** (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 seedOBJ-256FeedKind.challenge_completed seed is pending; challenge completion posts to the community feed are queued for implementation.
Push notification fan-outOBJ-257Challenge creation fans out a push notification to all community members; implementation is pending.

For more on communities and goals, see:

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)

Loading…