currentVolume vs currentLevel — Journey Volumes Are Not XP Levels
Applies to: Frontend work querying AchievementStatsType, gamification feature development, any surface that displays a user's level number.
The trap
AchievementStatsType exposes two integer fields that look similar but measure different things:
| Field | Type | What it means | Values |
|---|---|---|---|
currentLevel | Int! | The user's actual XP level | 1–10 |
currentVolume | Int! | A 3-bucket journey era grouping | 1, 2, or 3 |
currentVolume collapses ten XP levels into three narrative chapters ("volumes") used for era-based progression storytelling and AI Coach context:
# rails_api/app/services/achievements/compute_user_rank_service.rb
def current_volume
return 3 if effective_level >= 7
return 2 if effective_level >= 4
1
endSo a user at level 6 has currentLevel: 6 but currentVolume: 2. A user at level 8 has currentLevel: 8 but currentVolume: 3.
What went wrong (OBJ-933)
AchievementsHero.vue read currentVolume as the displayed level badge:
// WRONG — was reading the 3-bucket era index, not the XP level
const level = computed(() => props.achievementStats?.currentVolume ?? 1)This rendered "LV 2" for a user at XP level 6 (Level 6 → volume 2), while the push notification (which reads user.level directly) correctly said "You've reached Level 6!" — a confusing discrepancy.
Correct usage
Always use currentLevel when displaying a numeric level to the user:
// CORRECT — actual XP level (1–10)
const level = computed(() => props.achievementStats?.currentLevel ?? 1)Use currentVolume only for era-based features (AI Coach context, journey chapter unlocks, volume-gated copy). It is intentionally preserved in the schema for those purposes.
GraphQL query
Both fields are available in the achievementStats selection set. Add currentLevel to any query that renders a level number:
// ionic_frontend/src/constants/graphql/user.js
achievementStats {
currentLevel # actual XP level 1–10 — use for display
currentVolume # journey era 1–3 — use for era-based features only
currentRankName
# ...
}Regression test
spec/services/achievements/compute_user_rank_service_spec.rb includes an explicit context verifying that 5,800 XP produces current_level: 6 and current_volume: 2. Run this spec before touching ComputeUserRankService.
Related
rails_api/app/services/achievements/compute_user_rank_service.rb—effective_levelandcurrent_volumederivationrails_api/app/graphql/types/achievement_stats_type.rb— field definitions with descriptionsionic_frontend/src/components/achievements/AchievementsHero.vue— reference implementation (post-fix)rails_api/spec/services/achievements/compute_user_rank_service_spec.rb— regression test- Apollo Cache Merge Policies — Non-Normalized Nested Fields — a different, unrelated
achievementStatsgotcha on the same field: partial-selection writes (e.g. a streak repair) can wholesale-replace the cached object and dropcurrentLevelalong with it
Last updated: 2026-08-19 (OBJ-2707: cross-linked the Apollo cache merge-policy gotcha on the same field)