Visitar URL original
feat: add daily challenges system with XP progression and rewards by cwchanap · Pull Request #14 · cwchanap/cetus · GitHub
Skip to content

feat: add daily challenges system with XP progression and rewards - #14

Merged
cwchanap merged 17 commits into
mainfrom
feat/daily-challenges
Jan 1, 2026
Merged

cwchanap merged 17 commits into
mainfrom
feat/daily-challenges

Conversation

@cwchanap

@cwchanap cwchanap commented Dec 30, 2025 •

Copy link
Copy Markdown
Owner

Implement a comprehensive daily challenges feature to drive daily engagement:

Core Features:

  • 2-3 rotating daily challenges (UTC reset)
  • XP rewards and 10-level progression system
  • Challenge types: score targets, play count, variety, total score
  • Challenge streak tracking for consecutive daily completion
  • Deterministic challenge generation based on date

Database:

  • New daily_challenge_progress table for tracking user progress
  • Extended user_stats with xp, level, challenge_streak columns
  • Idempotent migrations for backward compatibility

API Endpoints:

  • GET /api/challenges - Fetch today's challenges with progress
  • GET /api/challenges/xp - Get user XP and level info
  • Updated POST /api/scores - Auto-update challenge progress

UI Components:

  • DailyMissionsPanel on dashboard with progress bars and countdown
  • /challenges page with stats cards and detailed mission view
  • ChallengeComplete modal for completion notifications
  • Integrated with existing achievement notification system

Tests:

  • Unit tests for challenge generation and level calculations
  • Service tests for progress tracking and XP awards

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Daily Challenges system with XP, level progression, streaks, and completion notifications.
    • New Daily Missions panel and Challenge Complete modal showing XP, rewards, and level-up.
    • Dashboard and Challenges pages now surface daily missions, progress bars, and reset timer.
  • Backend / API

    • New endpoints and server-side flows for challenges, XP/level tracking, and score-driven progress; persistent progress and XP/streak tracking added.
  • Documentation

    • Development docs switched to Bun for CLI commands.
  • Tests

    • Comprehensive unit tests for challenge logic and services.

✏️ Tip: You can customize this high-level summary in your review settings.

cwchanap and others added 15 commits December 23, 2025 16:40
Implement a comprehensive daily challenges feature to drive daily engagement:

**Core Features:**
- 2-3 rotating daily challenges (UTC reset)
- XP rewards and 10-level progression system
- Challenge types: score targets, play count, variety, total score
- Challenge streak tracking for consecutive daily completion
- Deterministic challenge generation based on date

**Database:**
- New daily_challenge_progress table for tracking user progress
- Extended user_stats with xp, level, challenge_streak columns
- Idempotent migrations for backward compatibility

**API Endpoints:**
- GET /api/challenges - Fetch today's challenges with progress
- GET /api/challenges/xp - Get user XP and level info
- Updated POST /api/scores - Auto-update challenge progress

**UI Components:**
- DailyMissionsPanel on dashboard with progress bars and countdown
- /challenges page with stats cards and detailed mission view
- ChallengeComplete modal for completion notifications
- Integrated with existing achievement notification system

**Tests:**
- Unit tests for challenge generation and level calculations
- Service tests for progress tracking and XP awards

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously, updateChallengeProgress and checkAndUpdateStreak used
local timezone (new Date()) for generating daily challenges while
progress records were keyed by getTodayUTC(). This caused a mismatch
around midnight in non-UTC timezones, preventing legitimate completions
from being recognized.

Now both functions use the same UTC date pattern as getUserDailyChallenges.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…correctly

Previously, updateChallengeStreak accepted an increment boolean and fetched
the current streak internally, which could lead to race conditions. Now it
accepts the new streak value directly, calculated by the caller.

Also moved streak expiration logic from updateChallengeStreak to
getUserXPAndLevel, so the returned challengeStreak is always accurate
(returns 0 if last completion was before yesterday).

Updated checkAndUpdateStreak to
Replace all npm commands with bun equivalents throughout the documentation.
Add explicit note that project uses Bun v1.3.1 as the package manager.
…sets

Add defensive checks for completedChallenges array in ChallengeComplete component
to prevent errors when data is undefined. Replace getElementById with querySelector
for better type safety and consistency.

Persist challenge streak resets to database when streak expires (last completion
was before yesterday) to keep stored value in sync with calculated value.

Update scoreService tests to use GameID enum and add test coverage
Copilot AI review requested due to automatic review settings December 30, 2025 23:41
@vercel

vercel Bot commented Dec 30, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
cetus Ready Ready Preview, Comment Jan 1, 2026 8:05am

@coderabbitai

coderabbitai Bot commented Dec 30, 2025 •

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a daily challenges system with challenge definitions, XP/level mechanics, persistence and DB schema changes, services to generate and update progress, UI components and a completion modal with global API, new API routes and dashboard integration, tests, Bun docs updates, and local MCP permission additions.

Changes

Cohort / File(s) Summary
Challenge Core Library
src/lib/challenges.ts, src/lib/challenges.test.ts
New challenge enums/types, CHALLENGE_POOL, LEVEL_THRESHOLDS, deterministic generateDailyChallenges, XP/level helpers (getLevelFromXP, getXPProgress), time utilities (getTodayUTC, getSecondsUntilMidnightUTC), and tests.
Database Schema & Types
scripts/init-db.sql, src/lib/server/db/types.ts, src/lib/server/db/queries.ts
Adds daily_challenge_progress table + index; extends user_stats with xp, level, challenge_streak, last_challenge_date, idempotent schema guards, and many DB functions for reading/upserting/completing progress and atomic streak updates.
Challenge Service
src/lib/services/challengeService.ts, src/lib/services/challengeService.test.ts
New service functions getUserDailyChallenges(userId) and updateChallengeProgress(userId, gameId, score) with per-challenge upserts, completion detection, XP awarding, level/streak updates, and comprehensive tests.
UI Components & Layout
src/components/ChallengeComplete.astro, src/components/DailyMissionsPanel.astro, src/layouts/AppLayout.astro
Adds ChallengeComplete modal with queueing, accessibility and window.showChallengeComplete; DailyMissionsPanel with progress, streak and reset timer; AppLayout renders modal.
Pages & API Routes
src/pages/challenges/index.astro, src/pages/api/challenges/index.ts, src/pages/api/challenges/xp.ts
New challenges dashboard page and API endpoints: /api/challenges (full daily payload) and /api/challenges/xp (XP/level progress) with auth and error handling.
Score Submission Integration
src/lib/services/scoreService.ts, src/lib/services/scoreService.test.ts, src/pages/api/scores.ts
Propagates challengeUpdates through score flows, extends ScoreSubmissionResult, triggers window.showChallengeComplete on client when present, and invokes updateChallengeProgress in /api/scores with resilient error handling; tests updated.
Dashboard Integration
src/pages/dashboard/index.astro
Dashboard loads daily challenges and XP/level data, computes progress/reset, renders DailyMissionsPanel, and adds Challenges navigation.
Config & Documentation
.claude/settings.local.json, CLAUDE.md
Adds MCP permissions (sequential-thinking and multiple chrome-devtools actions) and Bash(git add:*) to allowed list; documentation command examples switched from npm run to bun run and package manager reference set to Bun.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Dashboard
    participant API as API (/api/challenges)
    participant Service as Challenge Service
    participant DB as Database
    participant Clock as Time Util

    Client->>API: GET /api/challenges (auth)
    API->>Clock: getTodayUTC(), getSecondsUntilMidnightUTC()
    API->>Service: getUserDailyChallenges(userId)
    Service->>DB: ensure schema, upsert missing progress
    Service->>DB: getUserChallengeProgress(userId, today)
    DB-->>Service: progress records
    Service-->>API: merged UserChallengeProgress[]
    API->>DB: getUserXPAndLevel(userId)
    DB-->>API: xp, level, streak
    API->>Client: { challenges, xp, level, xpProgress, xpToNextLevel, streak, resetIn }
Loading
sequenceDiagram
    participant Game as Game Client
    participant ScoreAPI as /api/scores
    participant ScoreSvc as Score Service
    participant ChalSvc as Challenge Service
    participant DB as Database
    participant UI as Client UI
    participant Modal as ChallengeComplete Modal

    Game->>ScoreAPI: POST score {gameId, score}
    ScoreAPI->>ScoreSvc: saveGameScoreWithAchievements()
    ScoreSvc-->>ScoreAPI: { success, newAchievements }
    ScoreAPI->>ChalSvc: updateChallengeProgress(userId, gameId, score)
    ChalSvc->>DB: read/update daily stats & progress
    DB-->>ChalSvc: updated progress, xp awarded
    ChalSvc-->>ScoreAPI: ChallengeUpdateResult
    ScoreAPI-->>UI: { success, newAchievements, challengeUpdates }
    UI->>UI: if newAchievements -> showAchievementAward()
    UI->>UI: if challengeUpdates -> showChallengeComplete()
    UI->>Modal: render completion cards, animate, restore focus on close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hop-hop, new quests in view,

XP and streaks for me and you,
Cards that bounce with shiny light,
Timers tick and missions bite,
Bun runs fast — the bunny cheers tonight! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: introducing a daily challenges system with XP progression and rewards, which is the primary focus across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 97.06% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Dec 30, 2025 •

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (15)
src/pages/api/challenges/xp.ts (1)

28-30: Consider adding error logging for consistency.

Unlike the /api/challenges endpoint which logs errors with context, this endpoint silently swallows errors. While acceptable for a read-only endpoint, adding logging would help with debugging production issues.

Suggested improvement
-    } catch (_error) {
-        return jsonResponse({ error: 'Failed to fetch XP info' }, 500)
+    } catch (error) {
+        const safeMessage =
+            error instanceof Error ? error.message : 'Unknown error'
+        // eslint-disable-next-line no-console
+        console.error('[GET /api/challenges/xp] Failed to fetch XP info', {
+            path: request.url,
+            userId: session?.user?.id,
+            error: safeMessage,
+        })
+        return jsonResponse({ error: 'Failed to fetch XP info' }, 500)
     }
src/pages/api/scores.ts (1)

96-105: Consider including challengeUpdates even when no challenges are completed.

Currently, challengeUpdates is only included when there are completed challenges. The client might benefit from always receiving this field (even if empty) for consistent response structure, or to show progress updates without completion.

Alternative: Always include challengeUpdates
-            challengeUpdates:
-                challengeResult.completedChallenges.length > 0
-                    ? {
-                          completedChallenges:
-                              challengeResult.completedChallenges,
-                          xpEarned: challengeResult.xpEarned,
-                          levelUp: challengeResult.levelUp,
-                          newLevel: challengeResult.newLevel,
-                      }
-                    : undefined,
+            challengeUpdates: {
+                completedChallenges: challengeResult.completedChallenges,
+                xpEarned: challengeResult.xpEarned,
+                levelUp: challengeResult.levelUp,
+                newLevel: challengeResult.newLevel,
+            },
src/lib/challenges.test.ts (1)

103-114: Consider adding a boundary test for getSecondsUntilMidnightUTC.

The current test verifies the result is positive and less than 86400, but consider adding a test that mocks the current time to verify behavior near midnight boundaries.

src/pages/challenges/index.astro (3)

46-55: Unused variables appropriately prefixed.

The _totalXPToday and _difficultyColors are correctly prefixed with underscore to comply with ESLint rules for unused variables. Consider removing if not planned for near-term use.


106-106: Consider handling max level edge case.

When user reaches max level, displaying "Level X → X+1" could be misleading. Consider conditionally showing "MAX LEVEL" or similar.

🔎 Suggested enhancement
-          <span class="text-cyan-400">Level {level} → {level + 1}</span>
+          <span class="text-cyan-400">
+            {level >= 10 ? `Level ${level} (MAX)` : `Level ${level} → ${level + 1}`}
+          </span>

136-143: Progress calculation logic is duplicated.

This progress calculation is identical to the one in DailyMissionsPanel.astro. Consider extracting to a shared utility function to maintain DRY principles.

src/components/DailyMissionsPanel.astro (2)

56-64: Consider adding ARIA attributes to progress bars for accessibility.

The XP progress bar lacks accessibility attributes. Screen readers won't convey progress information.

🔎 Suggested enhancement
     <div
       class="w-full bg-gray-800/50 rounded-full h-2 overflow-hidden border border-gray-700/50"
+      role="progressbar"
+      aria-valuenow={xpProgress}
+      aria-valuemin={0}
+      aria-valuemax={100}
+      aria-label={`XP progress: ${xpProgress}%`}
     >

188-208: Timer logic duplicated from challenges page.

Both this component and src/pages/challenges/index.astro have nearly identical countdown timer implementations. Consider extracting to a shared utility script.

src/components/ChallengeComplete.astro (1)

135-147: Click handler on overlay persists across modal lifecycle.

The click listener added to this.overlay in the constructor is never removed. While this is generally fine since the overlay element persists, consider if cleanup is needed for SPA navigation scenarios.

src/lib/challenges.ts (1)

283-287: Consider documenting the PRNG limitations.

The Math.sin() based PRNG is adequate for daily challenge selection but has known distribution weaknesses. For this use case (selecting 2-3 challenges from a small pool), it's acceptable. Consider adding a brief comment noting this is a simple deterministic PRNG, not cryptographically secure.

src/lib/services/challengeService.ts (3)

47-55: Consider batching the upsert operations.

The loop calls upsertChallengeProgress sequentially for each challenge. While this works correctly for 2-3 challenges, consider batching these inserts for better performance if the challenge count increases.

🔎 Suggested batch approach
// Instead of sequential upserts, batch them:
await Promise.all(
    dailyChallenges.map(challenge =>
        upsertChallengeProgress(userId, today, challenge.id, challenge.targetValue)
    )
)

106-109: Parallelize independent database calls for better performance.

These three queries are independent and can be executed concurrently.

🔎 Suggested parallel execution
-    // Get current game stats for today
-    const gamesPlayedToday = await getGamesPlayedCountToday(userId)
-    const uniqueGamesToday = await getUniqueGamesPlayedToday(userId)
-    const totalScoreToday = await getTotalScoreToday(userId)
+    // Get current game stats for today (parallel)
+    const [gamesPlayedToday, uniqueGamesToday, totalScoreToday] = await Promise.all([
+        getGamesPlayedCountToday(userId),
+        getUniqueGamesPlayedToday(userId),
+        getTotalScoreToday(userId),
+    ])

200-216: The level synchronization logic works but could be clearer.

The code correctly syncs the level field after XP is awarded by completeChallengeAndAwardXP. The call updateUserXP(userId, 0, calculatedLevel) with xpToAdd=0 is intentional to only update the level. Consider adding a comment explaining this, or extracting a dedicated updateUserLevel function for clarity.

src/lib/server/db/queries.ts (2)

1347-1381: Side effect in getter function - consider documenting or extracting.

The getUserXPAndLevel function has a hidden side effect: it resets the streak in the database when it detects staleness. While this "lazy reset" approach is valid, it violates the principle of least surprise for a "get" function.

Consider either:

  1. Adding a JSDoc comment explicitly noting the side effect
  2. Extracting streak reset logic to a separate function called explicitly

1507-1556: Consider using upsert pattern for consistency.

This function uses an explicit existence check before insert/update, while updateUserXP uses the INSERT ... ON CONFLICT pattern. Consider aligning these for consistency, though the current implementation is functionally correct.

🔎 Suggested upsert approach
 export async function updateChallengeStreak(
     userId: string,
     newStreak: number,
     date: string
 ): Promise<number> {
     try {
         await ensureChallengeColumns()
-
-        // Check if user_stats exists
-        const existing = await db
-            .selectFrom('user_stats')
-            .select('id')
-            .where('user_id', '=', userId)
-            .executeTakeFirst()
-
-        if (existing) {
-            await db
-                .updateTable('user_stats')
-                .set({
-                    challenge_streak: newStreak,
-                    last_challenge_date: date,
-                    updated_at: new Date().toISOString(),
-                })
-                .where('user_id', '=', userId)
-                .execute()
-        } else {
-            await db
-                .insertInto('user_stats')
-                .values({
-                    user_id: userId,
-                    total_games_played: 0,
-                    total_score: 0,
-                    favorite_game: null,
-                    streak_days: 0,
-                    xp: 0,
-                    level: 1,
-                    challenge_streak: newStreak,
-                    last_challenge_date: date,
-                })
-                .execute()
-        }
+        const now = new Date().toISOString()
+        await db
+            .insertInto('user_stats')
+            .values({
+                user_id: userId,
+                total_games_played: 0,
+                total_score: 0,
+                favorite_game: null,
+                streak_days: 0,
+                xp: 0,
+                level: 1,
+                challenge_streak: newStreak,
+                last_challenge_date: date,
+            })
+            .onConflict(oc =>
+                oc.column('user_id').doUpdateSet({
+                    challenge_streak: newStreak,
+                    last_challenge_date: date,
+                    updated_at: now,
+                })
+            )
+            .execute()
         return newStreak
     } catch (error) {
         console.error('[updateChallengeStreak] Error:', sanitizeError(error))
         return 0
     }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d776b2e and 7b08f7a.

📒 Files selected for processing (19)
  • .claude/settings.local.json
  • CLAUDE.md
  • scripts/init-db.sql
  • src/components/ChallengeComplete.astro
  • src/components/DailyMissionsPanel.astro
  • src/layouts/AppLayout.astro
  • src/lib/challenges.test.ts
  • src/lib/challenges.ts
  • src/lib/server/db/queries.ts
  • src/lib/server/db/types.ts
  • src/lib/services/challengeService.test.ts
  • src/lib/services/challengeService.ts
  • src/lib/services/scoreService.test.ts
  • src/lib/services/scoreService.ts
  • src/pages/api/challenges/index.ts
  • src/pages/api/challenges/xp.ts
  • src/pages/api/scores.ts
  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
🧰 Additional context used
📓 Path-based instructions (18)
src/lib/server/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Kysely for type-safe database queries instead of raw SQL

Use Kysely for all database queries

Files:

  • src/lib/server/db/types.ts
  • src/lib/server/db/queries.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use single quotes for strings in JavaScript/TypeScript
Always include error handling in async operations

Files:

  • src/lib/server/db/types.ts
  • src/pages/api/challenges/index.ts
  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/pages/api/challenges/xp.ts
  • src/lib/challenges.test.ts
  • src/lib/services/scoreService.ts
  • src/pages/api/scores.ts
  • src/lib/challenges.ts
  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
**/*.{js,ts,jsx,tsx,py,java,go}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx,py,java,go}: Write clear variable names that describe their purpose
Document complex algorithms with comments

Files:

  • src/lib/server/db/types.ts
  • src/pages/api/challenges/index.ts
  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/pages/api/challenges/xp.ts
  • src/lib/challenges.test.ts
  • src/lib/services/scoreService.ts
  • src/pages/api/scores.ts
  • src/lib/challenges.ts
  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type annotations for function parameters in TypeScript

**/*.{ts,tsx}: Prefix unused variables with _ to comply with ESLint rules
Use strict TypeScript with explicit any warnings

Files:

  • src/lib/server/db/types.ts
  • src/pages/api/challenges/index.ts
  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/pages/api/challenges/xp.ts
  • src/lib/challenges.test.ts
  • src/lib/services/scoreService.ts
  • src/pages/api/scores.ts
  • src/lib/challenges.ts
  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
src/lib/server/db/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Handle database operations with proper TypeScript types for new entities

Files:

  • src/lib/server/db/types.ts
  • src/lib/server/db/queries.ts
src/lib/server/db/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Follow established patterns in src/lib/server/db/queries.ts when adding database queries

Files:

  • src/lib/server/db/types.ts
  • src/lib/server/db/queries.ts
src/{components,pages}/**/*.{ts,tsx,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/{components,pages}/**/*.{ts,tsx,astro}: Always use the established sci-fi theme classes for styling consistency
Apply mobile-first responsive design with responsive breakpoints
Use Button component with variants (primary, outline, destructive) and proper sizing (sm, md, lg)
Apply Card component with glass-morphism using variant="sci-fi" or variant="glass"
Use Badge component with color-coded variants (success, warning, error, outline)
Implement Avatar component with gradient backgrounds and proper sizing
Use text-holographic, bg-sci-fi-dark, border-neon-cyan, shadow-glow-purple, animate-float sci-fi theme classes
Ensure link styling with text-white no-underline class
Use CSS animations over JavaScript when possible for better performance
Maintain sci-fi aesthetic while ensuring accessibility with proper ARIA labels
Ensure sufficient color contrast for readability in sci-fi theme

Implement responsive design with mobile-first approach in UI components

Files:

  • src/pages/api/challenges/index.ts
  • src/components/ChallengeComplete.astro
  • src/pages/api/challenges/xp.ts
  • src/pages/challenges/index.astro
  • src/components/DailyMissionsPanel.astro
  • src/pages/api/scores.ts
  • src/pages/dashboard/index.astro
src/pages/api/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Validate sessions on all protected API endpoints

src/pages/api/**/*.ts: Use file-based routing in src/pages/api/ for API endpoints
Implement proper error handling with consistent error patterns across API routes

Files:

  • src/pages/api/challenges/index.ts
  • src/pages/api/challenges/xp.ts
  • src/pages/api/scores.ts
src/{lib/games,pages}/**/*.{ts,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Implement keyboard navigation for games

Files:

  • src/pages/api/challenges/index.ts
  • src/pages/api/challenges/xp.ts
  • src/pages/challenges/index.astro
  • src/pages/api/scores.ts
  • src/pages/dashboard/index.astro
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Verify responsive behavior of game interfaces and canvas elements in tests
Use Vitest 3.2.4 with jsdom environment for component and service testing

Files:

  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/lib/challenges.test.ts
src/lib/**/*.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Test achievement score thresholds and in-game event achievements

Files:

  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/lib/challenges.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use co-located *.test.ts files in the same directory as source code for unit tests

Files:

  • src/lib/services/challengeService.test.ts
  • src/lib/services/scoreService.test.ts
  • src/lib/challenges.test.ts
src/components/**/*.{ts,tsx,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/components/**/*.{ts,tsx,astro}: Use TypeScript interfaces for all component props
Utilize the cn() utility function for conditional classes
Implement consistent hover effects and animations using established sci-fi theme classes
Use class-variance-authority for component variants
Implement component props interface with variant, size, and class properties
Use conditional styling with cn() utility: base-classes, variant-classes conditionally applied, followed by className prop

src/components/**/*.{ts,tsx,astro}: Use TypeScript interface patterns for component props
Apply sci-fi theme classes consistently when styling components
Use cn() utility for conditional styling

Files:

  • src/components/ChallengeComplete.astro
  • src/components/DailyMissionsPanel.astro
src/components/**/*.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow Astro component syntax with frontmatter and JSX-like templates

Files:

  • src/components/ChallengeComplete.astro
  • src/components/DailyMissionsPanel.astro
src/{pages/api/scores,lib/services/scoreService}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Score submissions should include automatic achievement checking

Files:

  • src/lib/services/scoreService.ts
  • src/pages/api/scores.ts
src/pages/**/index.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/pages/**/index.astro: All game HTML structure should be in Astro components - TypeScript only manipulates dynamic content
Follow /game-name/index.astro file structure pattern for game pages
Game Container must include specific ID (id="[game]-container"), board div (id="[game]-board"), and GameOverlay component
Implement button state management: show 'End Game' when started, reset to 'Start Game' when finished
Use breadcrumb navigation for game context with 'Back to Menu' functionality
Provide real-time achievement award notifications using AchievementAward component
Implement proper loading states for games

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
src/pages/**/*.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Check Astro.locals.user for authentication on protected routes

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
src/lib/server/db/queries.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Integrate achievements automatically via saveGameScoreWithAchievements() function

Files:

  • src/lib/server/db/queries.ts
🧬 Code graph analysis (8)
src/pages/api/challenges/index.ts (4)
src/lib/server/api-utils.ts (2)
  • unauthorizedResponse (25-29)
  • jsonResponse (8-13)
src/lib/services/challengeService.ts (1)
  • getUserDailyChallenges (40-83)
src/lib/server/db/queries.ts (1)
  • getUserXPAndLevel (1330-1393)
src/lib/challenges.ts (3)
  • getXPProgress (251-270)
  • getSecondsUntilMidnightUTC (363-377)
  • getTodayUTC (380-382)
src/lib/services/challengeService.test.ts (2)
src/lib/challenges.ts (1)
  • generateDailyChallenges (317-360)
src/lib/services/challengeService.ts (2)
  • getUserDailyChallenges (40-83)
  • updateChallengeProgress (88-227)
src/lib/services/scoreService.test.ts (1)
src/lib/services/scoreService.ts (3)
  • submitScore (75-110)
  • saveGameScore (115-158)
  • formatGameName (205-220)
src/pages/api/challenges/xp.ts (5)
src/pages/api/challenges/index.ts (1)
  • GET (12-46)
src/lib/auth.ts (1)
  • auth (13-43)
src/lib/server/api-utils.ts (2)
  • unauthorizedResponse (25-29)
  • jsonResponse (8-13)
src/lib/server/db/queries.ts (1)
  • getUserXPAndLevel (1330-1393)
src/lib/challenges.ts (2)
  • getXPProgress (251-270)
  • LEVEL_THRESHOLDS (41-52)
src/lib/challenges.test.ts (1)
src/lib/challenges.ts (8)
  • CHALLENGE_POOL (55-233)
  • getChallengeById (236-238)
  • generateDailyChallenges (317-360)
  • getLevelFromXP (241-248)
  • getXPProgress (251-270)
  • LEVEL_THRESHOLDS (41-52)
  • getTodayUTC (380-382)
  • getSecondsUntilMidnightUTC (363-377)
src/pages/api/scores.ts (4)
src/lib/server/api-utils.ts (1)
  • badRequestResponse (34-36)
src/lib/games.ts (1)
  • getGameById (177-179)
src/lib/server/db/queries.ts (1)
  • saveGameScoreWithAchievements (459-504)
src/lib/services/challengeService.ts (1)
  • updateChallengeProgress (88-227)
src/lib/services/challengeService.ts (3)
src/lib/challenges.ts (3)
  • UserChallengeProgress (25-38)
  • getTodayUTC (380-382)
  • generateDailyChallenges (317-360)
src/lib/server/db/queries.ts (7)
  • upsertChallengeProgress (1181-1211)
  • getUserChallengeProgress (1160-1176)
  • updateChallengeProgressValue (1216-1238)
  • completeChallengeAndAwardXP (1243-1325)
  • getUserXPAndLevel (1330-1393)
  • updateUserXP (1398-1434)
  • updateChallengeStreak (1510-1556)
src/lib/server/db/types.ts (1)
  • GameType (66-66)
src/lib/server/db/queries.ts (3)
src/lib/server/db/client.ts (1)
  • db (22-22)
src/lib/server/db/index.ts (1)
  • db (2-2)
src/lib/server/db/types.ts (1)
  • DailyChallengeProgress (167-167)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: 🎭 End-to-End Tests
🔇 Additional comments (47)
CLAUDE.md (1)

11-61: LGTM!

The documentation updates are clear and consistent. The migration from npm to Bun is well-documented with the version pinned (bun@1.3.1), and all command examples correctly use bun run.

src/layouts/AppLayout.astro (1)

6-6: LGTM!

The ChallengeComplete component is correctly integrated following the same pattern as AchievementAward. Placing it at the layout level ensures the global window.showChallengeComplete API is available across all pages using this layout.

Also applies to: 93-95

src/lib/server/db/types.ts (2)

91-102: LGTM!

The DailyChallengeProgressTable interface is well-structured with appropriate column types. The comment documenting the uniqueness constraint (user_id, challenge_date, challenge_id) is helpful for maintainability. Using ColumnType<Date, string | Date, string | Date> | null for completed_at correctly handles nullable timestamps.


83-86: LGTM!

The UserStatsTable and UserStatsUpdate extensions for XP/level tracking are consistent with the existing schema patterns. Using string | null for last_challenge_date aligns with the YYYY-MM-DD format used throughout the challenge system.

Also applies to: 149-152

src/pages/api/challenges/index.ts (2)

12-34: LGTM!

The endpoint correctly validates the session, fetches challenge data, and computes XP progress. The response structure with resetIn and date enables client-side countdown displays. Using Math.max(0, ...) for xpToNextLevel handles the max-level edge case properly.


35-45: Good error handling pattern.

The error handling correctly extracts a safe message without leaking internal details to the client. Including path and userId in the log helps with debugging while returning a generic error to the user.

src/pages/api/challenges/xp.ts (1)

7-27: LGTM!

The endpoint follows the established authentication pattern and returns comprehensive XP/level data including maxLevel for client reference.

src/pages/api/scores.ts (2)

15-17: LGTM!

The type guard isGameId provides clean runtime validation with TypeScript type narrowing, ensuring gameId is properly validated before database operations.


62-85: Good resilience pattern for challenge updates.

The best-effort approach with try/catch ensures score submission succeeds even if challenge progress tracking fails. This prevents challenge system issues from impacting core game functionality.

src/lib/challenges.test.ts (2)

36-65: LGTM!

The daily challenge generation tests provide good coverage for determinism and variety. The test on lines 50-57 verifies different dates produce different challenges, which is essential for the rotating daily system.


67-101: Solid level system test coverage.

The tests cover boundary conditions well (XP at exact thresholds, high XP values beyond max level). The max-level progress cap test on lines 97-100 validates the edge case handling in getXPProgress.

src/pages/dashboard/index.astro (2)

40-44: LGTM!

Server-side data fetching for challenges and XP is correctly implemented. Using getSecondsUntilMidnightUTC() ensures accurate countdown timing.


157-165: LGTM!

The Challenges navigation button follows the established pattern with consistent styling and accessibility.

src/pages/challenges/index.astro (3)

13-16: LGTM! Auth check follows best practices.

Proper authentication guard with redirect preservation using query parameter.


26-43: Good error handling with user-facing feedback.

The try/catch with structured logging and user-visible error notice is well implemented. Sensitive details are kept server-side while providing helpful feedback to users.


204-223: Timer implementation is functional but may drift.

Using recursive setTimeout is acceptable, though slight drift may occur over long periods. The current approach is fine for countdown purposes. Consider using requestAnimationFrame with timestamp comparison for precision if needed in future.

scripts/init-db.sql (2)

52-65: Well-designed schema for daily challenge tracking.

The table structure properly handles the unique constraint per user/day/challenge, includes appropriate foreign key with cascade delete, and stores all necessary fields for progress tracking and XP awarding.


67-68: Good index choice for common query pattern.

The composite index on (user_id, challenge_date) efficiently supports the primary access pattern of fetching a user's daily progress.

src/lib/services/scoreService.test.ts (4)

16-30: Good mock setup for window hooks.

The mock window object properly includes both showAchievementAward and showChallengeComplete, with proper reset in beforeEach to ensure test isolation.


33-74: Comprehensive test for combined achievements and challenge updates.

Test properly validates the new challengeUpdates structure in the response including completedChallenges, xpEarned, and levelUp fields.


400-432: Good coverage for challenge completion flow.

This test validates that showChallengeComplete is called with the correct challengeUpdates payload when challenges are completed during score submission.


230-237: Proper fallback testing with type cast.

Using 'unknown_game' as any appropriately tests the default case in formatGameName while maintaining type safety elsewhere.

src/components/DailyMissionsPanel.astro (2)

6-14: Well-defined Props interface.

TypeScript interface clearly documents all required props with appropriate types. Follows coding guidelines for component props.


159-186: Toast notification has good accessibility and UX.

The refresh prompt toast includes:

  • role="alert" for screen reader announcement
  • Focus-visible ring styles for keyboard navigation
  • Clear action buttons with proper event handlers
  • Duplicate prevention check
src/lib/services/challengeService.test.ts (3)

6-34: Well-structured mock for deterministic testing.

The challenges module mock provides consistent test data with appropriate challenge types and properties. Using importOriginal preserves other exports while overriding generateDailyChallenges.


66-88: Good structural validation for getUserDailyChallenges.

Tests verify both the returned challenge count and the presence of all expected properties in the challenge structure.


198-271: Comprehensive streak increment test.

Test properly validates that streak increments from 5 to 6 when last completion was yesterday, with all challenges completed. Mock setup is thorough.

src/lib/services/scoreService.ts (4)

9-33: Good TypeScript global declaration for window hooks.

The optional function types on Window interface allow safe usage with optional chaining while providing full type information for the callback signatures.


44-56: ChallengeUpdates type matches component expectations.

The structure aligns with ChallengeCompleteData typedef in ChallengeComplete.astro, ensuring type consistency across the client-side integration.


141-149: Safe optional chaining for challenge updates.

The check result.challengeUpdates?.completedChallenges?.length properly guards against undefined before triggering the UI notification.


107-107: Consistent use of underscore prefix for unused error variables.

Using _error in catch blocks complies with ESLint rules for unused variables while maintaining proper error handling structure.

Also applies to: 155-155, 175-175, 197-197

src/components/ChallengeComplete.astro (5)

5-27: Excellent modal structure with proper ARIA attributes.

The overlay includes aria-hidden, the dialog has role="dialog", aria-modal="true", and aria-labelledby pointing to the challenge name. This follows accessibility best practices for modal dialogs.


29-83: Well-designed template with live regions.

Using aria-live="polite" for XP display and aria-live="assertive" for level-up badge ensures screen readers announce these dynamic updates appropriately.


283-297: Robust focus trap implementation.

The getFocusableElements method correctly filters for visible, enabled elements and excludes those with tabindex="-1" or aria-hidden="true".


362-381: Good initialization pattern with pending queue.

The pendingCalls array buffers any calls to showChallengeComplete before DOMContentLoaded, then flushes them once the manager is ready. This handles race conditions elegantly.


350-352: Auto-dismiss timer is 5 seconds.

The 5-second auto-dismiss provides reasonable viewing time. Consider if this should be configurable or if users reading with assistive technology might need more time.

.claude/settings.local.json (1)

14-25: Development tooling permissions.

These permissions enable Chrome DevTools automation for testing and git add for workflow automation. Appropriate for local development settings.

src/lib/challenges.ts (3)

1-52: LGTM! Well-designed XP and level system.

The level thresholds and type definitions are clear and well-documented. The max-level handling in getXPProgress (lines 260-263) correctly clamps values to avoid negative progress calculations.


54-233: LGTM! Comprehensive challenge pool with good variety.

The challenge definitions cover all challenge types with appropriate difficulty scaling and XP rewards. The weighting system ensures easier engagement challenges appear more frequently.


362-382: LGTM! Correct UTC time handling.

The time utilities consistently use UTC for date calculations, which is essential for the daily challenge reset system working correctly across time zones.

src/lib/services/challengeService.ts (2)

22-35: LGTM! Clean interface definitions.

The exported interfaces are well-defined with appropriate fields for communicating challenge completion results to the UI layer.


229-268: LGTM! Correct streak logic with proper idempotency.

The streak calculation correctly:

  1. Checks if all daily challenges are completed
  2. Only updates once per day (via lastChallengeDate !== today check)
  3. Increments streak if yesterday was the last completion, otherwise resets to 1

The date string comparison works correctly for the YYYY-MM-DD format.

src/lib/server/db/queries.ts (5)

61-63: Good defensive schema migration.

Calling ensureStreakColumn() and ensureChallengeColumns() before inserts ensures backward compatibility with existing databases that may not have the new columns. This follows the established pattern in the codebase.


1124-1155: LGTM! Well-designed table schema with proper indexing.

The daily_challenge_progress table has:

  • Appropriate unique constraint on (user_id, challenge_date, challenge_id)
  • Foreign key with cascade delete for data integrity
  • Composite index optimizing the common query pattern

The idempotent CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS ensure safe repeated execution.


1240-1325: LGTM! Proper transactional XP awarding with idempotency.

The implementation correctly:

  1. Uses a transaction for atomic updates across tables
  2. Checks xp_awarded > 0 to prevent double-awarding XP
  3. Creates user_stats record if user has no prior stats
  4. Catches and logs errors appropriately

1395-1434: LGTM! Safe upsert pattern for XP updates.

The INSERT ... ON CONFLICT DO UPDATE pattern correctly handles both new users and existing users. The Kysely sql template ensures parameterized queries, preventing SQL injection.


1436-1505: LGTM! Consistent UTC date handling for daily aggregates.

All three functions correctly:

  1. Use strftime('%Y-%m-%d', created_at, 'utc') for UTC date extraction
  2. Compare against today's UTC date
  3. Return safe defaults on error

This ensures consistent behavior regardless of server timezone.

Comment thread src/lib/services/challengeService.test.ts
Comment thread src/pages/dashboard/index.astro

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive daily challenges system to drive user engagement through XP progression, rotating challenges, and streak tracking. The feature integrates smoothly with the existing achievement system and game infrastructure.

Key Changes:

  • Daily challenge generation with 2-3 rotating missions per UTC day using deterministic seeding
  • 10-level XP progression system with challenge completion rewards (30-75 XP)
  • Automatic challenge progress tracking integrated into score submission flow
  • Challenge streak system for consecutive daily completion

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/lib/challenges.ts Core challenge logic: definitions, generation algorithm, XP/level calculations
src/lib/services/challengeService.ts Service layer for challenge progress tracking and XP awards
src/lib/server/db/queries.ts Database operations for challenges with idempotent migrations
src/lib/server/db/types.ts TypeScript schema definitions for challenge tables
src/pages/api/challenges/index.ts GET endpoint for fetching user challenges with progress
src/pages/api/challenges/xp.ts GET endpoint for user XP and level information
src/pages/api/scores.ts Enhanced to update challenge progress on score submission
src/components/DailyMissionsPanel.astro Dashboard widget displaying daily challenges with countdown timer
src/components/ChallengeComplete.astro Modal notification for challenge completion with level-up alerts
src/pages/challenges/index.astro Dedicated challenges page with stats and progress
src/pages/dashboard/index.astro Integration of challenges panel into dashboard
scripts/init-db.sql Schema for daily_challenge_progress table
src/lib/challenges.test.ts Unit tests for challenge generation and level calculations
src/lib/services/challengeService.test.ts Service tests for progress tracking and streak logic
CLAUDE.md Updated development commands (package manager documentation)

Comment thread src/components/DailyMissionsPanel.astro Outdated
Comment thread src/lib/services/challengeService.ts Outdated
Comment thread src/pages/dashboard/index.astro Outdated
Comment thread src/lib/services/challengeService.ts
Comment thread src/pages/challenges/index.astro Outdated
Comment thread scripts/init-db.sql
Comment thread src/lib/services/challengeService.ts Outdated
Comment thread src/lib/server/db/queries.ts Outdated
- Add xp, level, challenge_streak columns to user_stats schema
- Refactor streak update to use atomic transactions for thread safety
- Fix level-up calculation to compare before/after XP values
- Improve challenge completion detection and error handling
- Add error handling for dashboard daily missions loading

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/lib/server/db/queries.ts (1)

1398-1434: Function name and parameter are misleading.

The updateUserXPAndLevel function name and xpToAdd parameter suggest it adds XP, but the caller in challengeService.ts (line 216) passes 0 for xpToAdd when doing level-ups. The XP has already been added in completeChallengeAndAwardXP, so this call only updates the level. Consider renaming to updateUserLevel or clarifying the behavior.

src/pages/dashboard/index.astro (1)

206-225: Use Math.max to prevent negative XP values.

Line 222 calculates xpToNextLevel as xpProgress.nextLevelXP - xp, which can become negative at max level. The /api/challenges endpoint uses Math.max(0, ...) for consistency. Apply the same pattern here.

🔎 Proposed fix
-          xpToNextLevel={xpProgress.nextLevelXP - xp}
+          xpToNextLevel={Math.max(0, xpProgress.nextLevelXP - xp)}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b08f7a and 22f18d3.

📒 Files selected for processing (6)
  • scripts/init-db.sql
  • src/components/DailyMissionsPanel.astro
  • src/lib/server/db/queries.ts
  • src/lib/services/challengeService.ts
  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/init-db.sql
🧰 Additional context used
📓 Path-based instructions (13)
src/{components,pages}/**/*.{ts,tsx,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/{components,pages}/**/*.{ts,tsx,astro}: Always use the established sci-fi theme classes for styling consistency
Apply mobile-first responsive design with responsive breakpoints
Use Button component with variants (primary, outline, destructive) and proper sizing (sm, md, lg)
Apply Card component with glass-morphism using variant="sci-fi" or variant="glass"
Use Badge component with color-coded variants (success, warning, error, outline)
Implement Avatar component with gradient backgrounds and proper sizing
Use text-holographic, bg-sci-fi-dark, border-neon-cyan, shadow-glow-purple, animate-float sci-fi theme classes
Ensure link styling with text-white no-underline class
Use CSS animations over JavaScript when possible for better performance
Maintain sci-fi aesthetic while ensuring accessibility with proper ARIA labels
Ensure sufficient color contrast for readability in sci-fi theme

Implement responsive design with mobile-first approach in UI components

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
  • src/components/DailyMissionsPanel.astro
src/pages/**/index.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/pages/**/index.astro: All game HTML structure should be in Astro components - TypeScript only manipulates dynamic content
Follow /game-name/index.astro file structure pattern for game pages
Game Container must include specific ID (id="[game]-container"), board div (id="[game]-board"), and GameOverlay component
Implement button state management: show 'End Game' when started, reset to 'Start Game' when finished
Use breadcrumb navigation for game context with 'Back to Menu' functionality
Provide real-time achievement award notifications using AchievementAward component
Implement proper loading states for games

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
src/pages/**/*.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Check Astro.locals.user for authentication on protected routes

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
src/{lib/games,pages}/**/*.{ts,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Implement keyboard navigation for games

Files:

  • src/pages/challenges/index.astro
  • src/pages/dashboard/index.astro
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use single quotes for strings in JavaScript/TypeScript
Always include error handling in async operations

Files:

  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
**/*.{js,ts,jsx,tsx,py,java,go}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx,py,java,go}: Write clear variable names that describe their purpose
Document complex algorithms with comments

Files:

  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type annotations for function parameters in TypeScript

**/*.{ts,tsx}: Prefix unused variables with _ to comply with ESLint rules
Use strict TypeScript with explicit any warnings

Files:

  • src/lib/services/challengeService.ts
  • src/lib/server/db/queries.ts
src/lib/server/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Kysely for type-safe database queries instead of raw SQL

Use Kysely for all database queries

Files:

  • src/lib/server/db/queries.ts
src/lib/server/db/queries.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Integrate achievements automatically via saveGameScoreWithAchievements() function

Files:

  • src/lib/server/db/queries.ts
src/lib/server/db/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Handle database operations with proper TypeScript types for new entities

Files:

  • src/lib/server/db/queries.ts
src/lib/server/db/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Follow established patterns in src/lib/server/db/queries.ts when adding database queries

Files:

  • src/lib/server/db/queries.ts
src/components/**/*.{ts,tsx,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/components/**/*.{ts,tsx,astro}: Use TypeScript interfaces for all component props
Utilize the cn() utility function for conditional classes
Implement consistent hover effects and animations using established sci-fi theme classes
Use class-variance-authority for component variants
Implement component props interface with variant, size, and class properties
Use conditional styling with cn() utility: base-classes, variant-classes conditionally applied, followed by className prop

src/components/**/*.{ts,tsx,astro}: Use TypeScript interface patterns for component props
Apply sci-fi theme classes consistently when styling components
Use cn() utility for conditional styling

Files:

  • src/components/DailyMissionsPanel.astro
src/components/**/*.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow Astro component syntax with frontmatter and JSX-like templates

Files:

  • src/components/DailyMissionsPanel.astro
🧬 Code graph analysis (2)
src/lib/services/challengeService.ts (3)
src/lib/challenges.ts (3)
  • UserChallengeProgress (25-38)
  • getTodayUTC (380-382)
  • generateDailyChallenges (317-360)
src/lib/server/db/queries.ts (7)
  • upsertChallengeProgress (1181-1211)
  • getUserChallengeProgress (1160-1176)
  • updateChallengeProgressValue (1216-1238)
  • completeChallengeAndAwardXP (1243-1325)
  • getUserXPAndLevel (1330-1393)
  • updateUserXPAndLevel (1398-1434)
  • atomicCheckAndUpdateStreak (1510-1586)
src/lib/server/db/types.ts (1)
  • GameType (66-66)
src/lib/server/db/queries.ts (1)
src/lib/server/db/types.ts (1)
  • DailyChallengeProgress (167-167)
🪛 GitHub Actions: CI Pipeline
src/lib/services/challengeService.ts

[error] 259-259: [vitest] No "atomicCheckAndUpdateStreak" export is defined on the "../server/db/queries" mock. Did you forget to return it from "vi.mock"?

🪛 GitHub Check: 🧪 Test
src/lib/services/challengeService.ts

[failure] 259-259: src/lib/services/challengeService.test.ts > Challenge Service > updateChallengeProgress > should increment streak if last completion was yesterday
Error: [vitest] No "atomicCheckAndUpdateStreak" export is defined on the "../server/db/queries" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("../server/db/queries"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ checkAndUpdateStreak src/lib/services/challengeService.ts:259:15
❯ Module.updateChallengeProgress src/lib/services/challengeService.ts:225:5
❯ src/lib/services/challengeService.test.ts:264:13


[failure] 259-259: src/lib/services/challengeService.test.ts > Challenge Service > updateChallengeProgress > should reset streak to 1 if last completion was not yesterday
Error: [vitest] No "atomicCheckAndUpdateStreak" export is defined on the "../server/db/queries" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("../server/db/queries"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ checkAndUpdateStreak src/lib/services/challengeService.ts:259:15
❯ Module.updateChallengeProgress src/lib/services/challengeService.ts:225:5
❯ src/lib/services/challengeService.test.ts:189:13

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: 🎭 End-to-End Tests
🔇 Additional comments (16)
src/lib/server/db/queries.ts (5)

61-99: LGTM! Schema guards ensure backward compatibility.

The defensive schema checks before insert/update operations are a good practice for incremental migrations.


1086-1122: LGTM! Idempotent schema migration.

The function safely adds required columns with appropriate defaults and handles errors gracefully.


1124-1155: LGTM! Proper table schema and indexing.

The table structure supports the daily challenges system with appropriate constraints and indexes for efficient queries.


1510-1586: LGTM! Proper atomic streak update with locking.

The transaction uses forUpdate() to lock the row and prevent race conditions. The logic correctly handles first-time completion and consecutive day streaks.


1330-1392: Critical: Fix column name typo in getUserXPAndLevel.

Line 1345 has a typo: stats?.lastChallenge_date should be stats?.last_challenge_date (snake_case). This will cause lastChallengeDate to always be null, breaking streak detection logic throughout the challenge system.

🔎 Proposed fix
-        const lastChallengeDate = stats?.lastChallenge_date ?? null
+        const lastChallengeDate = stats?.last_challenge_date ?? null

Likely an incorrect or invalid review comment.

src/pages/challenges/index.astro (3)

13-43: LGTM! Proper authentication and error handling.

The page correctly checks authentication and handles loading errors gracefully with safe defaults, following the established pattern from the challenges page.


48-194: LGTM! Tailwind v4 compatible and responsive.

The template uses Tailwind v4 compatible syntax (slash notation for opacity, no deprecated utilities) and follows the established sci-fi theme classes with proper responsive design.


196-216: LGTM! Clean timer implementation.

The countdown timer is well-implemented with proper formatting and null safety checks.

src/components/DailyMissionsPanel.astro (3)

6-24: LGTM! Props interface is well-typed.

The component props are properly typed with TypeScript interfaces, following the coding guidelines.


27-147: LGTM! Component follows UI guidelines.

The component uses established sci-fi theme classes, proper Card/Badge variants, and Tailwind v4 compatible syntax. Responsive design and conditional styling are well-implemented.


149-209: LGTM! Past review feedback addressed.

The toast message correctly states "Daily missions have reset" (line 168), addressing the previous review comment. The script includes proper accessibility attributes and event handling.

src/pages/dashboard/index.astro (1)

43-72: LGTM! Past review feedback addressed.

Error handling has been properly implemented with try-catch and safe defaults, addressing the previous review comment about missing error handling.

src/lib/services/challengeService.ts (4)

40-83: LGTM! Clean data aggregation.

The function properly combines generated challenges with user progress, handling missing progress records gracefully.


88-199: LGTM! Idempotent completion handling.

The challenge completion logic correctly relies on database-level idempotency in completeChallengeAndAwardXP (lines 177-178 comment acknowledges this), which prevents duplicate XP awards even under concurrent updates.


201-233: LGTM! Level-up logic correctly derives levels from XP.

Lines 209-213 properly calculate level change by deriving levels purely from XP before/after the batch, avoiding dependence on potentially stale stored level values. This addresses the timing concern from the past review.


241-261: LGTM! Atomic streak updates prevent race conditions.

The function correctly uses atomicCheckAndUpdateStreak which employs database-level locking (forUpdate) to prevent concurrent completion issues mentioned in past reviews.

Comment thread src/lib/server/db/queries.ts Outdated
Comment thread src/lib/services/challengeService.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/lib/services/challengeService.test.ts (1)

124-131: Previous feedback addressed - getTodayUTC() is now used.

The test now correctly imports and uses getTodayUTC() for the today value (line 125), then derives twoDaysAgo from that UTC-anchored date. This addresses the earlier concern about duplicating date logic.

src/pages/dashboard/index.astro (2)

43-72: Good error handling implementation.

The try/catch pattern with safe defaults ensures the dashboard remains functional even if the challenge system encounters issues. The error is logged for debugging while users see a friendly message.


206-225: Previous feedback addressed - Math.max(0, ...) now used for xpToNextLevel.

Line 222 correctly uses Math.max(0, xpProgress.nextLevelXP - xp) to prevent negative values at max level, matching the pattern used in the /api/challenges endpoint.

src/lib/server/db/queries.ts (1)

457-462: Type guard is minimal but functional.

The guard correctly uses unknown type (addressing the previous any concern) and prevents null/undefined from passing through. However, it accepts any object without validating the expected game data structure.

For stricter runtime validation, consider checking for game-specific properties, though this may be overkill if the caller is trusted.

🧹 Nitpick comments (2)
src/lib/services/challengeService.test.ts (1)

67-89: Consider adding edge case tests for getUserDailyChallenges.

The current tests verify structure but don't test:

  • Behavior when getUserChallengeProgress returns existing progress
  • Merging of progress data with challenge definitions
  • Handling of completed_at date conversion

These would increase confidence in the mapping logic.

src/lib/server/db/queries.ts (1)

1359-1393: Read operation has side effect - consider documenting this behavior.

The function performs a write (resetting challenge_streak to 0) during a read operation when the streak is stale. While idempotent and error-tolerant, this pattern is unexpected. Consider adding a JSDoc note or renaming to getUserXPAndLevelWithStreakUpdate to make the behavior explicit.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22f18d3 and e8c980d.

📒 Files selected for processing (3)
  • src/lib/server/db/queries.ts
  • src/lib/services/challengeService.test.ts
  • src/pages/dashboard/index.astro
🧰 Additional context used
📓 Path-based instructions (14)
src/{components,pages}/**/*.{ts,tsx,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/{components,pages}/**/*.{ts,tsx,astro}: Always use the established sci-fi theme classes for styling consistency
Apply mobile-first responsive design with responsive breakpoints
Use Button component with variants (primary, outline, destructive) and proper sizing (sm, md, lg)
Apply Card component with glass-morphism using variant="sci-fi" or variant="glass"
Use Badge component with color-coded variants (success, warning, error, outline)
Implement Avatar component with gradient backgrounds and proper sizing
Use text-holographic, bg-sci-fi-dark, border-neon-cyan, shadow-glow-purple, animate-float sci-fi theme classes
Ensure link styling with text-white no-underline class
Use CSS animations over JavaScript when possible for better performance
Maintain sci-fi aesthetic while ensuring accessibility with proper ARIA labels
Ensure sufficient color contrast for readability in sci-fi theme

Implement responsive design with mobile-first approach in UI components

Files:

  • src/pages/dashboard/index.astro
src/pages/**/index.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/pages/**/index.astro: All game HTML structure should be in Astro components - TypeScript only manipulates dynamic content
Follow /game-name/index.astro file structure pattern for game pages
Game Container must include specific ID (id="[game]-container"), board div (id="[game]-board"), and GameOverlay component
Implement button state management: show 'End Game' when started, reset to 'Start Game' when finished
Use breadcrumb navigation for game context with 'Back to Menu' functionality
Provide real-time achievement award notifications using AchievementAward component
Implement proper loading states for games

Files:

  • src/pages/dashboard/index.astro
src/pages/**/*.astro

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Check Astro.locals.user for authentication on protected routes

Files:

  • src/pages/dashboard/index.astro
src/{lib/games,pages}/**/*.{ts,astro}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Implement keyboard navigation for games

Files:

  • src/pages/dashboard/index.astro
src/lib/server/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Kysely for type-safe database queries instead of raw SQL

Use Kysely for all database queries

Files:

  • src/lib/server/db/queries.ts
src/lib/server/db/queries.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Integrate achievements automatically via saveGameScoreWithAchievements() function

Files:

  • src/lib/server/db/queries.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use single quotes for strings in JavaScript/TypeScript
Always include error handling in async operations

Files:

  • src/lib/server/db/queries.ts
  • src/lib/services/challengeService.test.ts
**/*.{js,ts,jsx,tsx,py,java,go}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx,py,java,go}: Write clear variable names that describe their purpose
Document complex algorithms with comments

Files:

  • src/lib/server/db/queries.ts
  • src/lib/services/challengeService.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type annotations for function parameters in TypeScript

**/*.{ts,tsx}: Prefix unused variables with _ to comply with ESLint rules
Use strict TypeScript with explicit any warnings

Files:

  • src/lib/server/db/queries.ts
  • src/lib/services/challengeService.test.ts
src/lib/server/db/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Handle database operations with proper TypeScript types for new entities

Files:

  • src/lib/server/db/queries.ts
src/lib/server/db/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Follow established patterns in src/lib/server/db/queries.ts when adding database queries

Files:

  • src/lib/server/db/queries.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Verify responsive behavior of game interfaces and canvas elements in tests
Use Vitest 3.2.4 with jsdom environment for component and service testing

Files:

  • src/lib/services/challengeService.test.ts
src/lib/**/*.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Test achievement score thresholds and in-game event achievements

Files:

  • src/lib/services/challengeService.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use co-located *.test.ts files in the same directory as source code for unit tests

Files:

  • src/lib/services/challengeService.test.ts
🧬 Code graph analysis (2)
src/lib/server/db/queries.ts (4)
src/lib/achievements.ts (1)
  • AchievementCheckData (22-29)
src/lib/server/db/client.ts (1)
  • db (22-22)
src/lib/server/db/index.ts (1)
  • db (2-2)
src/lib/server/db/types.ts (1)
  • DailyChallengeProgress (167-167)
src/lib/services/challengeService.test.ts (2)
src/lib/challenges.ts (2)
  • generateDailyChallenges (317-360)
  • getTodayUTC (380-382)
src/lib/services/challengeService.ts (2)
  • getUserDailyChallenges (40-83)
  • updateChallengeProgress (88-233)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: 🎭 End-to-End Tests
🔇 Additional comments (17)
src/lib/services/challengeService.test.ts (4)

1-5: LGTM!

Imports are well-organized, importing both the namespace (* as challenges) for mocking and the specific function (getTodayUTC) for direct usage in tests.


6-60: LGTM!

Mock setup is comprehensive and follows vitest best practices. The mocks are correctly placed before the imports of the modules being tested, and the default return values provide sensible baseline behavior.


91-122: LGTM!

Tests verify the expected result structure and confirm that the necessary database functions are called with correct user IDs.


202-279: LGTM!

Streak increment and reset tests are well-structured with proper mock setup. The tests correctly verify that atomicCheckAndUpdateStreak is called with the expected parameters when all challenges are completed.

src/pages/dashboard/index.astro (2)

10-21: LGTM!

Imports are well-organized, bringing in the necessary challenge-related functions and types. The UserChallengeProgress type import ensures type safety for the challenges array.


185-193: LGTM!

The Challenges navigation button follows the established pattern and styling of adjacent buttons, maintaining UI consistency.

src/lib/server/db/queries.ts (11)

3-4: LGTM!

Imports are correctly added for the new types needed by the challenge system.

Also applies to: 14-14


62-91: LGTM!

Schema guards ensure columns exist before operations, and the new challenge-related fields are correctly initialized with sensible defaults.


1098-1134: LGTM!

Idempotent column additions follow the established ensureStreakColumn pattern. Error handling appropriately logs warnings without breaking primary flows.


1136-1167: LGTM!

Table creation is idempotent with appropriate schema: UNIQUE constraint prevents duplicate progress entries, index optimizes user+date queries, and CASCADE delete maintains referential integrity.


1169-1188: LGTM!

Follows established patterns with proper error handling and type-safe Kysely queries.


1190-1223: LGTM!

Idempotent upsert with onConflict().doNothing() correctly initializes progress records without overwriting existing data.


1252-1337: Well-designed transactional XP award.

The transaction ensures atomicity, and the idempotency check at line 1278 prevents double-awarding XP. The fallback to create user_stats handles first-time users correctly.


1407-1446: Function correctly handles level-only updates.

When called with xpToAdd=0 (as in challengeService.ts line 210 for level-ups), the function correctly updates only the level while preserving existing XP. The sqluser_stats.xp + ${xpToAdd}`` pattern with xpToAdd=0 is a valid no-op for the XP field.


1448-1517: LGTM!

Daily stats functions use consistent UTC-based date filtering and return safe defaults on error. The strftime approach correctly groups by UTC day.


1561-1590: LGTM!

Streak calculation logic is correct: increments if last completion was yesterday, resets to 1 otherwise. The idempotency check (last_challenge_date !== today) prevents duplicate updates.


1539-1539: No action needed—the code already documents this appropriately.

The inline comment .forUpdate() // Lock the row for update if supported by the driver already explains that forUpdate() is conditional on database support. Since the project uses LibSQL (SQLite-compatible), which doesn't support SELECT ... FOR UPDATE, this is the correct approach and is already well-documented in the code.

@cwchanap
cwchanap merged commit d40d645 into main Jan 1, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 18, 2026

This branch was successfully deployed

1 active deployment
Preview — e8c980dc Deployed Jan 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants