Skip to content

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:

FieldTypeWhat it meansValues
currentLevelInt!The user's actual XP level1–10
currentVolumeInt!A 3-bucket journey era grouping1, 2, or 3

currentVolume collapses ten XP levels into three narrative chapters ("volumes") used for era-based progression storytelling and AI Coach context:

ruby
# 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
end

So 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:

ts
// 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:

ts
// 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:

js
// ionic_frontend/src/constants/graphql/user.js
achievementStats {
  currentLevel   # actual XP level 110 — use for display
  currentVolume  # journey era 13 — 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.

  • rails_api/app/services/achievements/compute_user_rank_service.rbeffective_level and current_volume derivation
  • rails_api/app/graphql/types/achievement_stats_type.rb — field definitions with descriptions
  • ionic_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 achievementStats gotcha on the same field: partial-selection writes (e.g. a streak repair) can wholesale-replace the cached object and drop currentLevel along with it

Last updated: 2026-08-19 (OBJ-2707: cross-linked the Apollo cache merge-policy gotcha on the same field)

Loading…