feat(gamification): Phase 2 — taxonomy + grouped UI for achievements
Achievements gain four new columns: group_slug, track, tier, sort_order.
Existing 36 are backfilled into 5 groups (onboarding/volume/mastery/
consistency/exploration) by migration 030; 'social' stays empty until
Phase 3 adds class/leaderboard/live-quiz tracks.
Tracks bundle escalating thresholds into one progression (tests_10/50/
100 → track='tests', tiers 1-3), so the UI can show '★★★' on the top
tier and the user understands the relationship. sort_order is reserved
in blocks of 10 inside groups of 100, leaving room for inserts without
renumbering.
Backend:
• migration 030 adds the columns + index + backfill UPDATEs
• _shared.ACHIEVEMENT_DEFS gains group/track/tier/sort_order per row
• _shared exports new ACHIEVEMENT_GROUPS metadata for the UI
• service.seedAchievements writes the new fields on insert AND
backfills them via UPDATE on existing rows (fresh installs +
pre-migration installs both end up consistent)
• _shared.stmts.getAllAchs SELECT updated, ORDER BY sort_order
• gamification/api.getAchievements forwards the new fields
Frontend:
• profile.html groups achievements by group_slug with a per-section
header (icon + title + 'unlocked / total' chip) and a tier-star
badge (★★ etc.) on tier ≥ 2 items
• Hard-coded ACH_GROUPS mirror of the backend list (small, stable)
• New CSS for .ach-group / .ach-group-head / .ach-tier
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -81,17 +81,33 @@ function updateStreak(userId) {
|
||||
|
||||
/* ── Achievements ──────────────────────────────────────────────────── */
|
||||
function seedAchievements() {
|
||||
// INSERT for missing rows — supplies legacy + new taxonomy fields.
|
||||
const ins = db.prepare(`
|
||||
INSERT OR IGNORE INTO achievements (slug, title, icon, category, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT OR IGNORE INTO achievements
|
||||
(slug, title, icon, category, description, group_slug, track, tier, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
// UPDATE for existing rows — keep title/icon/desc/category fresh AND
|
||||
// backfill the taxonomy columns added by migration 030 (so installs
|
||||
// that ran the SEED before the migration get fixed on next boot).
|
||||
const upd = db.prepare(`
|
||||
UPDATE achievements SET icon = ?, category = ?, title = ?, description = ?
|
||||
WHERE slug = ? AND (icon IS NULL OR icon = '' OR icon != ?)
|
||||
UPDATE achievements SET
|
||||
icon = ?,
|
||||
category = ?,
|
||||
title = ?,
|
||||
description= ?,
|
||||
group_slug = COALESCE(?, group_slug),
|
||||
track = COALESCE(?, track),
|
||||
tier = COALESCE(?, tier),
|
||||
sort_order = ?
|
||||
WHERE slug = ?
|
||||
`);
|
||||
for (const a of ACHIEVEMENT_DEFS) {
|
||||
ins.run(a.slug, a.title, a.icon, a.cat, a.desc);
|
||||
upd.run(a.icon, a.cat, a.title, a.desc, a.slug, a.icon);
|
||||
ins.run(a.slug, a.title, a.icon, a.cat, a.desc,
|
||||
a.group ?? null, a.track ?? null, a.tier ?? null, a.sort_order ?? 0);
|
||||
upd.run(a.icon, a.cat, a.title, a.desc,
|
||||
a.group ?? null, a.track ?? null, a.tier ?? null, a.sort_order ?? 0,
|
||||
a.slug);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user