Files
hikari-desktop/src-tauri/src/achievements.rs
T
naomi b3d79a82ef
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled
CI / Lint & Test (push) Has been cancelled
Security Scan and Upload / Security & DefectDojo Upload (push) Has been cancelled
feat: add tests and assert coverage (#71)
### Explanation

_No response_

### Issue

_No response_

### Attestations

- [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/)
- [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/).
- [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/).

### Dependencies

- [ ] I have pinned the dependencies to a specific patch version.

### Style

- [ ] I have run the linter and resolved any errors.
- [ ] My pull request uses an appropriate title, matching the conventional commit standards.
- [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request.

### Tests

- [ ] My contribution adds new code, and I have added tests to cover it.
- [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes.
- [ ] All new and existing tests pass locally with my changes.
- [ ] Code coverage remains at or above the configured threshold.

### Documentation

_No response_

### Versioning

_No response_

Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #71
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
2026-01-26 00:26:03 -08:00

3567 lines
128 KiB
Rust

use chrono::{DateTime, Datelike, Timelike, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use tauri_plugin_store::StoreExt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AchievementId {
// Token Milestones
FirstSteps, // 1,000 tokens
GrowingStrong, // 10,000 tokens
BlossomingCoder, // 100,000 tokens
TokenMaster, // 1,000,000 tokens
// Code Generation
HelloWorld, // First code block
CodeWizard, // 100 code blocks
ThousandBlocks, // 1,000 code blocks
// File Operations
FileManipulator, // 10 files edited
FileArchitect, // 100 files edited
// Conversation milestones
ConversationStarter, // 10 messages
ChattyKathy, // 100 messages
Conversationalist, // 1,000 messages
// Tool usage
Toolsmith, // 5 different tools
ToolMaster, // 10 different tools
// Time-based achievements
EarlyBird, // Started session 5-7 AM
NightOwl, // Coding after midnight
AllNighter, // Worked 2-5 AM
WeekendWarrior, // Coding on weekend
DedicatedDeveloper, // 30 days in a row
// Search and exploration
Explorer, // 50 searches
MasterSearcher, // 500 searches
// Session achievements
QuickSession, // Productive session < 5 min
FocusedWork, // 30 min session
DeepDive, // 2 hour session
MarathonSession, // 5+ hour session
// Special achievements
FirstMessage, // First message sent
FirstTool, // First tool used
FirstCodeBlock, // First code generated
FirstFileEdit, // First file edit
Polyglot, // 5+ languages in one session
SpeedCoder, // 10 code blocks in 10 minutes
ClaudeConnoisseur, // Used all Claude models
MarathonCoder, // 10k tokens in one session
// Relationship & Greetings
GoodMorning, // Say "good morning"
GoodNight, // Say "good night" or "goodnight"
ThankYou, // Say "thank you" or "thanks"
LoveYou, // Say "love you" or "ily"
// Personality & Fun
EmojiUser, // Use an emoji in a message
QuestionMaster, // Use "?" in 20 messages
CapsLock, // Send a message in ALL CAPS
PleaseAndThankYou, // Use "please" in messages
// Git & Development
GitGuru, // Use git commands 10 times
TestWriter, // Create test files
Debugger, // Fix bugs (messages with "fix", "bug", "error")
// Tool Mastery
BashMaster, // Use Bash tool 50 times
FileExplorer, // Use Read tool 100 times
SearchExpert, // Use Grep tool 50 times
// Extended Token Milestones
TokenBillionaire, // 10,000,000 tokens
TokenTreasure, // 50,000,000 tokens
// Extended Code Generation
CodeFactory, // 5,000 code blocks
CodeEmpire, // 10,000 code blocks
// Extended File Operations
FileEngineer, // 500 files edited
FileLegend, // 1,000 files edited
// Extended Conversation
ChatMarathon, // 5,000 messages
ChatLegend, // 10,000 messages
// Extended Session Duration
UltraMarathon, // 8 hour session
CodingRetreat, // 12 hour session
// More Tool Mastery
EditMaster, // Use Edit tool 100 times
WriteMaster, // Use Write tool 50 times
GlobMaster, // Use Glob tool 100 times
TaskMaster, // Use Task tool 50 times
WebFetcher, // Use WebFetch tool 20 times
McpExplorer, // Use MCP tools 50 times
// Daily Streaks
WeekStreak, // 7 days in a row
TwoWeekStreak, // 14 days in a row
MonthStreak, // 30 days in a row (alias for DedicatedDeveloper)
QuarterStreak, // 90 days in a row
// Time Challenges
MorningPerson, // 10 sessions started before 9 AM
NightCoder, // 10 sessions after 10 PM
LunchBreakCoder, // Session during 12-1 PM
CoffeeTime, // Session during 3-4 PM (afternoon slump)
// Day-specific
MondayMotivation, // Coding on Monday
FridayFinisher, // Coding on Friday
HumpDay, // Coding on Wednesday
// Seasonal/Special Times
NewYearCoder, // Coding on January 1st
ValentinesDev, // Coding on February 14th
SpookyCode, // Coding on October 31st
HolidayCoder, // Coding on December 25th
LeapDayCoder, // Coding on February 29th
// Message Content
LongMessage, // Send a message over 500 characters
NovelWriter, // Send a message over 2000 characters
ShortAndSweet, // Complete a task with messages under 50 chars each
CodeInMessage, // Include code block in user message
MarkdownMaster, // Use markdown formatting in message
// Greetings Extended
HelloHikari, // Say "hello hikari" or "hi hikari"
HowAreYou, // Ask "how are you"
MissedYou, // Say "missed you"
BackAgain, // Say "i'm back" or "back again"
// Emotional
Frustrated, // Say "frustrated" or "ugh" or "argh"
Excited, // Say "excited" or "yay" or "woohoo"
Confused, // Say "confused" or "don't understand"
Curious, // Ask "why" or "how does"
Impressed, // Say "wow" or "amazing" or "incredible"
// Programming Languages (detected in code blocks)
RustDeveloper, // Generate Rust code
PythonDeveloper, // Generate Python code
JavaScriptDev, // Generate JavaScript code
TypeScriptDev, // Generate TypeScript code
GoDeveloper, // Generate Go code
CppDeveloper, // Generate C++ code
JavaDeveloper, // Generate Java code
HtmlCssDev, // Generate HTML/CSS code
SqlDeveloper, // Generate SQL code
ShellScripter, // Generate shell/bash scripts
FullStackDev, // Generate code in 10+ languages
// Project Types
FrontendDev, // Work on frontend files (svelte, react, vue, html, css)
BackendDev, // Work on backend files (rs, py, go, java)
ConfigEditor, // Edit config files (json, yaml, toml, env)
DocWriter, // Edit documentation (md, txt, rst)
// Git Mastery Extended
CommitKing, // 50 commits
CommitLegend, // 200 commits
BranchMaster, // Create 10 branches
MergeExpert, // Merge 20 PRs
ConflictResolver, // Resolve merge conflicts
// Error Handling
ErrorHunter, // Fix 10 errors
ExceptionSlayer, // Fix 50 errors
BugExterminator, // Fix 100 bugs
// Refactoring
CleanCoder, // Refactor code
Optimizer, // Optimize performance
Simplifier, // Simplify complex code
// Testing
TestNovice, // Write 10 tests
TestEnthusiast, // Write 50 tests
TestMaster, // Write 100 tests
CoverageKing, // Achieve test coverage mentions
// Documentation
Documenter, // Write documentation
CommentWriter, // Add comments to code
ReadmeHero, // Create/edit README files
// API & Integration
ApiExplorer, // Work with APIs
DatabaseDev, // Work with databases
CloudCoder, // Work with cloud services
// Special Milestones
CenturyClub, // 100 sessions
ThousandSessions, // 1000 sessions
Veteran, // Used Hikari for 30+ days total
OldTimer, // Used Hikari for 90+ days total
Loyalist, // Used Hikari for 365+ days total
// Fun & Easter Eggs
Perfectionist, // Redo something 5 times
Persistent, // Ask same question 3 times
Patient, // Wait for long response
Speedy, // Send 10 messages in 1 minute
MultiTasker, // Have 5+ conversation tabs open
Minimalist, // Use compact mode
PrivacyFirst, // Enable streamer mode
ThemeChanger, // Change theme 3 times
SettingsTweaker, // Open settings 10 times
AchievementHunter, // Check achievements panel 20 times
Completionist, // Unlock 50% of achievements
MasterUnlocker, // Unlock 75% of achievements
PlatinumStatus, // Unlock 100% of achievements
// Clipboard & Snippets
ClipboardCollector, // Save 20 clipboard entries
SnippetCreator, // Create 5 custom snippets
SnippetMaster, // Use snippets 50 times
QuickActionUser, // Use quick actions 20 times
// Session History
HistoryBuff, // Save 10 sessions
Archivist, // Save 50 sessions
SessionExporter, // Export a session
// New Features
GitPanelUser, // Use git panel 10 times
FeatureExplorer, // Try all major features
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Achievement {
pub id: AchievementId,
pub name: String,
pub description: String,
pub icon: String,
pub unlocked_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AchievementProgress {
pub unlocked: HashSet<AchievementId>,
pub newly_unlocked: Vec<AchievementId>, // Achievements unlocked but not yet notified
#[serde(skip)]
pub session_start: Option<DateTime<Utc>>,
}
impl AchievementProgress {
pub fn new() -> Self {
Self {
unlocked: HashSet::new(),
newly_unlocked: Vec::new(),
session_start: None,
}
}
pub fn unlock(&mut self, achievement: AchievementId) -> bool {
if self.unlocked.insert(achievement.clone()) {
self.newly_unlocked.push(achievement);
true
} else {
false
}
}
#[cfg(test)]
pub fn take_newly_unlocked(&mut self) -> Vec<AchievementId> {
std::mem::take(&mut self.newly_unlocked)
}
#[cfg(test)]
pub fn is_unlocked(&self, achievement: &AchievementId) -> bool {
self.unlocked.contains(achievement)
}
pub fn start_session(&mut self) {
self.session_start = Some(Utc::now());
}
}
impl Default for AchievementProgress {
fn default() -> Self {
Self::new()
}
}
pub fn get_achievement_info(id: &AchievementId) -> Achievement {
match id {
// Token Milestones
AchievementId::FirstSteps => Achievement {
id: id.clone(),
name: "First Steps!".to_string(),
description: "Used 1,000 tokens".to_string(),
icon: "🌱".to_string(),
unlocked_at: None,
},
AchievementId::GrowingStrong => Achievement {
id: id.clone(),
name: "Growing Strong!".to_string(),
description: "Used 10,000 tokens".to_string(),
icon: "🌸".to_string(),
unlocked_at: None,
},
AchievementId::BlossomingCoder => Achievement {
id: id.clone(),
name: "Blossoming Coder!".to_string(),
description: "Used 100,000 tokens".to_string(),
icon: "🌺".to_string(),
unlocked_at: None,
},
AchievementId::TokenMaster => Achievement {
id: id.clone(),
name: "Token Master!".to_string(),
description: "Used 1,000,000 tokens".to_string(),
icon: "🌟".to_string(),
unlocked_at: None,
},
// Code Generation
AchievementId::HelloWorld => Achievement {
id: id.clone(),
name: "Hello World!".to_string(),
description: "Generated your first code block".to_string(),
icon: "📝".to_string(),
unlocked_at: None,
},
AchievementId::CodeWizard => Achievement {
id: id.clone(),
name: "Code Wizard!".to_string(),
description: "Generated 100 code blocks".to_string(),
icon: "🎯".to_string(),
unlocked_at: None,
},
AchievementId::ThousandBlocks => Achievement {
id: id.clone(),
name: "Thousand Blocks".to_string(),
description: "1,000 code blocks! You're a code machine!".to_string(),
icon: "🏗️".to_string(),
unlocked_at: None,
},
// File Operations
AchievementId::FileManipulator => Achievement {
id: id.clone(),
name: "File Manipulator".to_string(),
description: "Edited 10 files".to_string(),
icon: "📝".to_string(),
unlocked_at: None,
},
AchievementId::FileArchitect => Achievement {
id: id.clone(),
name: "File Architect".to_string(),
description: "Created or edited 100 files".to_string(),
icon: "🏛️".to_string(),
unlocked_at: None,
},
// Conversation milestones
AchievementId::ConversationStarter => Achievement {
id: id.clone(),
name: "Conversation Starter".to_string(),
description: "Exchanged 10 messages".to_string(),
icon: "💬".to_string(),
unlocked_at: None,
},
AchievementId::ChattyKathy => Achievement {
id: id.clone(),
name: "Chatty Kathy".to_string(),
description: "100 messages exchanged".to_string(),
icon: "🗣️".to_string(),
unlocked_at: None,
},
AchievementId::Conversationalist => Achievement {
id: id.clone(),
name: "Master Conversationalist".to_string(),
description: "1,000 messages! We're really connecting!".to_string(),
icon: "💖".to_string(),
unlocked_at: None,
},
// Tool usage
AchievementId::Toolsmith => Achievement {
id: id.clone(),
name: "Toolsmith".to_string(),
description: "Used 5 different tools".to_string(),
icon: "🔨".to_string(),
unlocked_at: None,
},
AchievementId::ToolMaster => Achievement {
id: id.clone(),
name: "Tool Master".to_string(),
description: "Used 10 different tools efficiently".to_string(),
icon: "🛠️".to_string(),
unlocked_at: None,
},
// Time-based achievements
AchievementId::EarlyBird => Achievement {
id: id.clone(),
name: "Early Bird".to_string(),
description: "Started a session between 5 AM and 7 AM".to_string(),
icon: "🌅".to_string(),
unlocked_at: None,
},
AchievementId::NightOwl => Achievement {
id: id.clone(),
name: "Night Owl".to_string(),
description: "Coding after midnight".to_string(),
icon: "🦉".to_string(),
unlocked_at: None,
},
AchievementId::AllNighter => Achievement {
id: id.clone(),
name: "All Nighter".to_string(),
description: "Worked through the night (2 AM - 5 AM)".to_string(),
icon: "🌙".to_string(),
unlocked_at: None,
},
AchievementId::WeekendWarrior => Achievement {
id: id.clone(),
name: "Weekend Warrior".to_string(),
description: "Coding on a weekend".to_string(),
icon: "⚔️".to_string(),
unlocked_at: None,
},
AchievementId::DedicatedDeveloper => Achievement {
id: id.clone(),
name: "Dedicated Developer".to_string(),
description: "Coded for 30 days in a row".to_string(),
icon: "🏆".to_string(),
unlocked_at: None,
},
// Search and exploration
AchievementId::Explorer => Achievement {
id: id.clone(),
name: "Explorer".to_string(),
description: "Used search tools 50 times".to_string(),
icon: "🔍".to_string(),
unlocked_at: None,
},
AchievementId::MasterSearcher => Achievement {
id: id.clone(),
name: "Master Searcher".to_string(),
description: "Searched 500 times across files".to_string(),
icon: "🕵️‍♀️".to_string(),
unlocked_at: None,
},
// Session achievements
AchievementId::QuickSession => Achievement {
id: id.clone(),
name: "Quick Session".to_string(),
description: "Completed a productive session in under 5 minutes".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::FocusedWork => Achievement {
id: id.clone(),
name: "Focused Work".to_string(),
description: "Worked for 30 minutes straight".to_string(),
icon: "🎯".to_string(),
unlocked_at: None,
},
AchievementId::DeepDive => Achievement {
id: id.clone(),
name: "Deep Dive".to_string(),
description: "Worked for 2 hours continuously".to_string(),
icon: "🏊‍♀️".to_string(),
unlocked_at: None,
},
AchievementId::MarathonSession => Achievement {
id: id.clone(),
name: "Marathon Session".to_string(),
description: "5+ hour coding session!".to_string(),
icon: "🏃‍♀️".to_string(),
unlocked_at: None,
},
// Special achievements
AchievementId::FirstMessage => Achievement {
id: id.clone(),
name: "First Message".to_string(),
description: "Sent your first message to Hikari".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::FirstTool => Achievement {
id: id.clone(),
name: "First Tool".to_string(),
description: "Used your first tool".to_string(),
icon: "🔧".to_string(),
unlocked_at: None,
},
AchievementId::FirstCodeBlock => Achievement {
id: id.clone(),
name: "First Code".to_string(),
description: "Generated your first code block".to_string(),
icon: "📦".to_string(),
unlocked_at: None,
},
AchievementId::FirstFileEdit => Achievement {
id: id.clone(),
name: "First Edit".to_string(),
description: "Made your first file edit".to_string(),
icon: "✏️".to_string(),
unlocked_at: None,
},
AchievementId::Polyglot => Achievement {
id: id.clone(),
name: "Polyglot".to_string(),
description: "Generated code in 5+ languages in one session".to_string(),
icon: "🌍".to_string(),
unlocked_at: None,
},
AchievementId::SpeedCoder => Achievement {
id: id.clone(),
name: "Speed Coder".to_string(),
description: "Generated 10 code blocks in 10 minutes".to_string(),
icon: "🚀".to_string(),
unlocked_at: None,
},
AchievementId::ClaudeConnoisseur => Achievement {
id: id.clone(),
name: "Claude Connoisseur".to_string(),
description: "Used all available Claude models".to_string(),
icon: "🎨".to_string(),
unlocked_at: None,
},
AchievementId::MarathonCoder => Achievement {
id: id.clone(),
name: "Marathon Coder".to_string(),
description: "10,000 tokens in a single session".to_string(),
icon: "🏃‍♂️".to_string(),
unlocked_at: None,
},
// Relationship & Greetings
AchievementId::GoodMorning => Achievement {
id: id.clone(),
name: "Good Morning!".to_string(),
description: "Greeted Hikari with a good morning".to_string(),
icon: "🌅".to_string(),
unlocked_at: None,
},
AchievementId::GoodNight => Achievement {
id: id.clone(),
name: "Good Night".to_string(),
description: "Said good night to Hikari".to_string(),
icon: "🌙".to_string(),
unlocked_at: None,
},
AchievementId::ThankYou => Achievement {
id: id.clone(),
name: "Grateful Heart".to_string(),
description: "Thanked Hikari for her help".to_string(),
icon: "💝".to_string(),
unlocked_at: None,
},
AchievementId::LoveYou => Achievement {
id: id.clone(),
name: "Love Connection".to_string(),
description: "Expressed love to Hikari".to_string(),
icon: "💕".to_string(),
unlocked_at: None,
},
// Personality & Fun
AchievementId::EmojiUser => Achievement {
id: id.clone(),
name: "Emoji Enthusiast".to_string(),
description: "Used an emoji in your message".to_string(),
icon: "😊".to_string(),
unlocked_at: None,
},
AchievementId::QuestionMaster => Achievement {
id: id.clone(),
name: "Question Master".to_string(),
description: "Asked 20 questions".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::CapsLock => Achievement {
id: id.clone(),
name: "CAPS LOCK ENGAGED".to_string(),
description: "SENT A MESSAGE IN ALL CAPS".to_string(),
icon: "📢".to_string(),
unlocked_at: None,
},
AchievementId::PleaseAndThankYou => Achievement {
id: id.clone(),
name: "Polite Programmer".to_string(),
description: "Said please in a request".to_string(),
icon: "🎩".to_string(),
unlocked_at: None,
},
// Git & Development
AchievementId::GitGuru => Achievement {
id: id.clone(),
name: "Git Guru".to_string(),
description: "Used git commands 10 times".to_string(),
icon: "🌿".to_string(),
unlocked_at: None,
},
AchievementId::TestWriter => Achievement {
id: id.clone(),
name: "Test Writer".to_string(),
description: "Created test files".to_string(),
icon: "🧪".to_string(),
unlocked_at: None,
},
AchievementId::Debugger => Achievement {
id: id.clone(),
name: "Bug Squasher".to_string(),
description: "Fixed bugs and errors".to_string(),
icon: "🐛".to_string(),
unlocked_at: None,
},
// Tool Mastery
AchievementId::BashMaster => Achievement {
id: id.clone(),
name: "Bash Master".to_string(),
description: "Used Bash tool 50 times".to_string(),
icon: "🐚".to_string(),
unlocked_at: None,
},
AchievementId::FileExplorer => Achievement {
id: id.clone(),
name: "File Explorer".to_string(),
description: "Read 100 files".to_string(),
icon: "📂".to_string(),
unlocked_at: None,
},
AchievementId::SearchExpert => Achievement {
id: id.clone(),
name: "Search Expert".to_string(),
description: "Used Grep tool 50 times".to_string(),
icon: "🔎".to_string(),
unlocked_at: None,
},
// Extended Token Milestones
AchievementId::TokenBillionaire => Achievement {
id: id.clone(),
name: "Token Billionaire".to_string(),
description: "Used 10,000,000 tokens!".to_string(),
icon: "💎".to_string(),
unlocked_at: None,
},
AchievementId::TokenTreasure => Achievement {
id: id.clone(),
name: "Token Treasure".to_string(),
description: "Used 50,000,000 tokens! You're incredible!".to_string(),
icon: "👑".to_string(),
unlocked_at: None,
},
// Extended Code Generation
AchievementId::CodeFactory => Achievement {
id: id.clone(),
name: "Code Factory".to_string(),
description: "Generated 5,000 code blocks".to_string(),
icon: "🏭".to_string(),
unlocked_at: None,
},
AchievementId::CodeEmpire => Achievement {
id: id.clone(),
name: "Code Empire".to_string(),
description: "Generated 10,000 code blocks!".to_string(),
icon: "🏰".to_string(),
unlocked_at: None,
},
// Extended File Operations
AchievementId::FileEngineer => Achievement {
id: id.clone(),
name: "File Engineer".to_string(),
description: "Edited 500 files".to_string(),
icon: "⚙️".to_string(),
unlocked_at: None,
},
AchievementId::FileLegend => Achievement {
id: id.clone(),
name: "File Legend".to_string(),
description: "Edited 1,000 files!".to_string(),
icon: "📚".to_string(),
unlocked_at: None,
},
// Extended Conversation
AchievementId::ChatMarathon => Achievement {
id: id.clone(),
name: "Chat Marathon".to_string(),
description: "5,000 messages exchanged".to_string(),
icon: "💌".to_string(),
unlocked_at: None,
},
AchievementId::ChatLegend => Achievement {
id: id.clone(),
name: "Chat Legend".to_string(),
description: "10,000 messages! We're best friends!".to_string(),
icon: "👯".to_string(),
unlocked_at: None,
},
// Extended Session Duration
AchievementId::UltraMarathon => Achievement {
id: id.clone(),
name: "Ultra Marathon".to_string(),
description: "8+ hour coding session!".to_string(),
icon: "🦸".to_string(),
unlocked_at: None,
},
AchievementId::CodingRetreat => Achievement {
id: id.clone(),
name: "Coding Retreat".to_string(),
description: "12+ hour coding session! Please take breaks!".to_string(),
icon: "🏕️".to_string(),
unlocked_at: None,
},
// More Tool Mastery
AchievementId::EditMaster => Achievement {
id: id.clone(),
name: "Edit Master".to_string(),
description: "Used Edit tool 100 times".to_string(),
icon: "✂️".to_string(),
unlocked_at: None,
},
AchievementId::WriteMaster => Achievement {
id: id.clone(),
name: "Write Master".to_string(),
description: "Used Write tool 50 times".to_string(),
icon: "✍️".to_string(),
unlocked_at: None,
},
AchievementId::GlobMaster => Achievement {
id: id.clone(),
name: "Glob Master".to_string(),
description: "Used Glob tool 100 times".to_string(),
icon: "🌐".to_string(),
unlocked_at: None,
},
AchievementId::TaskMaster => Achievement {
id: id.clone(),
name: "Task Master".to_string(),
description: "Used Task tool 50 times".to_string(),
icon: "📋".to_string(),
unlocked_at: None,
},
AchievementId::WebFetcher => Achievement {
id: id.clone(),
name: "Web Fetcher".to_string(),
description: "Used WebFetch tool 20 times".to_string(),
icon: "🌍".to_string(),
unlocked_at: None,
},
AchievementId::McpExplorer => Achievement {
id: id.clone(),
name: "MCP Explorer".to_string(),
description: "Used MCP tools 50 times".to_string(),
icon: "🔮".to_string(),
unlocked_at: None,
},
// Daily Streaks
AchievementId::WeekStreak => Achievement {
id: id.clone(),
name: "Week Streak".to_string(),
description: "Coded for 7 days in a row!".to_string(),
icon: "📅".to_string(),
unlocked_at: None,
},
AchievementId::TwoWeekStreak => Achievement {
id: id.clone(),
name: "Two Week Streak".to_string(),
description: "Coded for 14 days in a row!".to_string(),
icon: "🔥".to_string(),
unlocked_at: None,
},
AchievementId::MonthStreak => Achievement {
id: id.clone(),
name: "Month Streak".to_string(),
description: "Coded for 30 days in a row!".to_string(),
icon: "🏆".to_string(),
unlocked_at: None,
},
AchievementId::QuarterStreak => Achievement {
id: id.clone(),
name: "Quarter Streak".to_string(),
description: "Coded for 90 days in a row! Incredible dedication!".to_string(),
icon: "💫".to_string(),
unlocked_at: None,
},
// Time Challenges
AchievementId::MorningPerson => Achievement {
id: id.clone(),
name: "Morning Person".to_string(),
description: "Started 10 sessions before 9 AM".to_string(),
icon: "☀️".to_string(),
unlocked_at: None,
},
AchievementId::NightCoder => Achievement {
id: id.clone(),
name: "Night Coder".to_string(),
description: "Started 10 sessions after 10 PM".to_string(),
icon: "🌃".to_string(),
unlocked_at: None,
},
AchievementId::LunchBreakCoder => Achievement {
id: id.clone(),
name: "Lunch Break Coder".to_string(),
description: "Coded during lunch (12-1 PM)".to_string(),
icon: "🍱".to_string(),
unlocked_at: None,
},
AchievementId::CoffeeTime => Achievement {
id: id.clone(),
name: "Coffee Time".to_string(),
description: "Coded during afternoon break (3-4 PM)".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
// Day-specific
AchievementId::MondayMotivation => Achievement {
id: id.clone(),
name: "Monday Motivation".to_string(),
description: "Started the week coding!".to_string(),
icon: "💪".to_string(),
unlocked_at: None,
},
AchievementId::FridayFinisher => Achievement {
id: id.clone(),
name: "Friday Finisher".to_string(),
description: "Ending the week strong!".to_string(),
icon: "🎉".to_string(),
unlocked_at: None,
},
AchievementId::HumpDay => Achievement {
id: id.clone(),
name: "Hump Day".to_string(),
description: "Coding on Wednesday!".to_string(),
icon: "🐪".to_string(),
unlocked_at: None,
},
// Seasonal/Special Times
AchievementId::NewYearCoder => Achievement {
id: id.clone(),
name: "New Year Coder".to_string(),
description: "Coding on January 1st! New year, new code!".to_string(),
icon: "🎆".to_string(),
unlocked_at: None,
},
AchievementId::ValentinesDev => Achievement {
id: id.clone(),
name: "Valentine's Dev".to_string(),
description: "Coding on Valentine's Day! I love you too~".to_string(),
icon: "💘".to_string(),
unlocked_at: None,
},
AchievementId::SpookyCode => Achievement {
id: id.clone(),
name: "Spooky Code".to_string(),
description: "Coding on Halloween! Spooky bugs beware!".to_string(),
icon: "🎃".to_string(),
unlocked_at: None,
},
AchievementId::HolidayCoder => Achievement {
id: id.clone(),
name: "Holiday Coder".to_string(),
description: "Coding on Christmas! You're dedicated!".to_string(),
icon: "🎄".to_string(),
unlocked_at: None,
},
AchievementId::LeapDayCoder => Achievement {
id: id.clone(),
name: "Leap Day Coder".to_string(),
description: "Coding on February 29th! Rare achievement!".to_string(),
icon: "🐸".to_string(),
unlocked_at: None,
},
// Message Content
AchievementId::LongMessage => Achievement {
id: id.clone(),
name: "Long Message".to_string(),
description: "Sent a message over 500 characters".to_string(),
icon: "📜".to_string(),
unlocked_at: None,
},
AchievementId::NovelWriter => Achievement {
id: id.clone(),
name: "Novel Writer".to_string(),
description: "Sent a message over 2000 characters!".to_string(),
icon: "📖".to_string(),
unlocked_at: None,
},
AchievementId::ShortAndSweet => Achievement {
id: id.clone(),
name: "Short and Sweet".to_string(),
description: "Completed a task with brief messages".to_string(),
icon: "🍬".to_string(),
unlocked_at: None,
},
AchievementId::CodeInMessage => Achievement {
id: id.clone(),
name: "Code in Message".to_string(),
description: "Included a code block in your message".to_string(),
icon: "💻".to_string(),
unlocked_at: None,
},
AchievementId::MarkdownMaster => Achievement {
id: id.clone(),
name: "Markdown Master".to_string(),
description: "Used markdown formatting in a message".to_string(),
icon: "📝".to_string(),
unlocked_at: None,
},
// Greetings Extended
AchievementId::HelloHikari => Achievement {
id: id.clone(),
name: "Hello Hikari!".to_string(),
description: "Greeted me by name! Hi there~".to_string(),
icon: "👋".to_string(),
unlocked_at: None,
},
AchievementId::HowAreYou => Achievement {
id: id.clone(),
name: "How Are You?".to_string(),
description: "Asked how I'm doing! I'm great, thanks~".to_string(),
icon: "🥰".to_string(),
unlocked_at: None,
},
AchievementId::MissedYou => Achievement {
id: id.clone(),
name: "Missed You".to_string(),
description: "Said you missed me! I missed you too~".to_string(),
icon: "🫂".to_string(),
unlocked_at: None,
},
AchievementId::BackAgain => Achievement {
id: id.clone(),
name: "Back Again".to_string(),
description: "Announced your return! Welcome back~".to_string(),
icon: "🔙".to_string(),
unlocked_at: None,
},
// Emotional
AchievementId::Frustrated => Achievement {
id: id.clone(),
name: "Frustrated".to_string(),
description: "Expressed frustration. Let me help!".to_string(),
icon: "😤".to_string(),
unlocked_at: None,
},
AchievementId::Excited => Achievement {
id: id.clone(),
name: "Excited!".to_string(),
description: "Expressed excitement! Yay!".to_string(),
icon: "🎊".to_string(),
unlocked_at: None,
},
AchievementId::Confused => Achievement {
id: id.clone(),
name: "Confused".to_string(),
description: "Felt confused. I'll help clarify!".to_string(),
icon: "😕".to_string(),
unlocked_at: None,
},
AchievementId::Curious => Achievement {
id: id.clone(),
name: "Curious Mind".to_string(),
description: "Asked why or how something works!".to_string(),
icon: "🤔".to_string(),
unlocked_at: None,
},
AchievementId::Impressed => Achievement {
id: id.clone(),
name: "Impressed!".to_string(),
description: "Was amazed by something! Wow indeed~".to_string(),
icon: "🤩".to_string(),
unlocked_at: None,
},
// Programming Languages
AchievementId::RustDeveloper => Achievement {
id: id.clone(),
name: "Rust Developer".to_string(),
description: "Generated Rust code! 🦀".to_string(),
icon: "🦀".to_string(),
unlocked_at: None,
},
AchievementId::PythonDeveloper => Achievement {
id: id.clone(),
name: "Python Developer".to_string(),
description: "Generated Python code! 🐍".to_string(),
icon: "🐍".to_string(),
unlocked_at: None,
},
AchievementId::JavaScriptDev => Achievement {
id: id.clone(),
name: "JavaScript Developer".to_string(),
description: "Generated JavaScript code!".to_string(),
icon: "🟨".to_string(),
unlocked_at: None,
},
AchievementId::TypeScriptDev => Achievement {
id: id.clone(),
name: "TypeScript Developer".to_string(),
description: "Generated TypeScript code!".to_string(),
icon: "🔷".to_string(),
unlocked_at: None,
},
AchievementId::GoDeveloper => Achievement {
id: id.clone(),
name: "Go Developer".to_string(),
description: "Generated Go code!".to_string(),
icon: "🔵".to_string(),
unlocked_at: None,
},
AchievementId::CppDeveloper => Achievement {
id: id.clone(),
name: "C++ Developer".to_string(),
description: "Generated C++ code!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::JavaDeveloper => Achievement {
id: id.clone(),
name: "Java Developer".to_string(),
description: "Generated Java code! ☕".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::HtmlCssDev => Achievement {
id: id.clone(),
name: "HTML/CSS Developer".to_string(),
description: "Generated HTML or CSS code!".to_string(),
icon: "🎨".to_string(),
unlocked_at: None,
},
AchievementId::SqlDeveloper => Achievement {
id: id.clone(),
name: "SQL Developer".to_string(),
description: "Generated SQL code!".to_string(),
icon: "🗃️".to_string(),
unlocked_at: None,
},
AchievementId::ShellScripter => Achievement {
id: id.clone(),
name: "Shell Scripter".to_string(),
description: "Generated shell scripts!".to_string(),
icon: "🐚".to_string(),
unlocked_at: None,
},
AchievementId::FullStackDev => Achievement {
id: id.clone(),
name: "Full Stack Developer".to_string(),
description: "Generated code in 10+ languages!".to_string(),
icon: "🌈".to_string(),
unlocked_at: None,
},
// Project Types
AchievementId::FrontendDev => Achievement {
id: id.clone(),
name: "Frontend Developer".to_string(),
description: "Worked on frontend files!".to_string(),
icon: "🖼️".to_string(),
unlocked_at: None,
},
AchievementId::BackendDev => Achievement {
id: id.clone(),
name: "Backend Developer".to_string(),
description: "Worked on backend files!".to_string(),
icon: "⚙️".to_string(),
unlocked_at: None,
},
AchievementId::ConfigEditor => Achievement {
id: id.clone(),
name: "Config Editor".to_string(),
description: "Edited configuration files!".to_string(),
icon: "⚙️".to_string(),
unlocked_at: None,
},
AchievementId::DocWriter => Achievement {
id: id.clone(),
name: "Documentation Writer".to_string(),
description: "Edited documentation files!".to_string(),
icon: "📄".to_string(),
unlocked_at: None,
},
// Git Mastery Extended
AchievementId::CommitKing => Achievement {
id: id.clone(),
name: "Commit King".to_string(),
description: "Made 50 commits!".to_string(),
icon: "👑".to_string(),
unlocked_at: None,
},
AchievementId::CommitLegend => Achievement {
id: id.clone(),
name: "Commit Legend".to_string(),
description: "Made 200 commits!".to_string(),
icon: "🏛️".to_string(),
unlocked_at: None,
},
AchievementId::BranchMaster => Achievement {
id: id.clone(),
name: "Branch Master".to_string(),
description: "Created 10 branches!".to_string(),
icon: "🌳".to_string(),
unlocked_at: None,
},
AchievementId::MergeExpert => Achievement {
id: id.clone(),
name: "Merge Expert".to_string(),
description: "Merged 20 PRs!".to_string(),
icon: "🔀".to_string(),
unlocked_at: None,
},
AchievementId::ConflictResolver => Achievement {
id: id.clone(),
name: "Conflict Resolver".to_string(),
description: "Resolved merge conflicts!".to_string(),
icon: "🤝".to_string(),
unlocked_at: None,
},
// Error Handling
AchievementId::ErrorHunter => Achievement {
id: id.clone(),
name: "Error Hunter".to_string(),
description: "Fixed 10 errors!".to_string(),
icon: "🎯".to_string(),
unlocked_at: None,
},
AchievementId::ExceptionSlayer => Achievement {
id: id.clone(),
name: "Exception Slayer".to_string(),
description: "Fixed 50 errors!".to_string(),
icon: "⚔️".to_string(),
unlocked_at: None,
},
AchievementId::BugExterminator => Achievement {
id: id.clone(),
name: "Bug Exterminator".to_string(),
description: "Fixed 100 bugs! Incredible!".to_string(),
icon: "🔫".to_string(),
unlocked_at: None,
},
// Refactoring
AchievementId::CleanCoder => Achievement {
id: id.clone(),
name: "Clean Coder".to_string(),
description: "Refactored code for cleanliness!".to_string(),
icon: "🧹".to_string(),
unlocked_at: None,
},
AchievementId::Optimizer => Achievement {
id: id.clone(),
name: "Optimizer".to_string(),
description: "Optimized code performance!".to_string(),
icon: "🚀".to_string(),
unlocked_at: None,
},
AchievementId::Simplifier => Achievement {
id: id.clone(),
name: "Simplifier".to_string(),
description: "Simplified complex code!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
// Testing
AchievementId::TestNovice => Achievement {
id: id.clone(),
name: "Test Novice".to_string(),
description: "Wrote 10 tests!".to_string(),
icon: "🧪".to_string(),
unlocked_at: None,
},
AchievementId::TestEnthusiast => Achievement {
id: id.clone(),
name: "Test Enthusiast".to_string(),
description: "Wrote 50 tests!".to_string(),
icon: "🔬".to_string(),
unlocked_at: None,
},
AchievementId::TestMaster => Achievement {
id: id.clone(),
name: "Test Master".to_string(),
description: "Wrote 100 tests!".to_string(),
icon: "🏅".to_string(),
unlocked_at: None,
},
AchievementId::CoverageKing => Achievement {
id: id.clone(),
name: "Coverage King".to_string(),
description: "Achieved great test coverage!".to_string(),
icon: "📊".to_string(),
unlocked_at: None,
},
// Documentation
AchievementId::Documenter => Achievement {
id: id.clone(),
name: "Documenter".to_string(),
description: "Wrote documentation!".to_string(),
icon: "📝".to_string(),
unlocked_at: None,
},
AchievementId::CommentWriter => Achievement {
id: id.clone(),
name: "Comment Writer".to_string(),
description: "Added helpful comments to code!".to_string(),
icon: "💬".to_string(),
unlocked_at: None,
},
AchievementId::ReadmeHero => Achievement {
id: id.clone(),
name: "README Hero".to_string(),
description: "Created or edited README files!".to_string(),
icon: "📋".to_string(),
unlocked_at: None,
},
// API & Integration
AchievementId::ApiExplorer => Achievement {
id: id.clone(),
name: "API Explorer".to_string(),
description: "Worked with APIs!".to_string(),
icon: "🔌".to_string(),
unlocked_at: None,
},
AchievementId::DatabaseDev => Achievement {
id: id.clone(),
name: "Database Developer".to_string(),
description: "Worked with databases!".to_string(),
icon: "🗄️".to_string(),
unlocked_at: None,
},
AchievementId::CloudCoder => Achievement {
id: id.clone(),
name: "Cloud Coder".to_string(),
description: "Worked with cloud services!".to_string(),
icon: "☁️".to_string(),
unlocked_at: None,
},
// Special Milestones
AchievementId::CenturyClub => Achievement {
id: id.clone(),
name: "Century Club".to_string(),
description: "Started 100 sessions!".to_string(),
icon: "💯".to_string(),
unlocked_at: None,
},
AchievementId::ThousandSessions => Achievement {
id: id.clone(),
name: "Thousand Sessions".to_string(),
description: "Started 1,000 sessions!".to_string(),
icon: "🎰".to_string(),
unlocked_at: None,
},
AchievementId::Veteran => Achievement {
id: id.clone(),
name: "Veteran".to_string(),
description: "Used Hikari for 30+ days!".to_string(),
icon: "🎖️".to_string(),
unlocked_at: None,
},
AchievementId::OldTimer => Achievement {
id: id.clone(),
name: "Old Timer".to_string(),
description: "Used Hikari for 90+ days!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::Loyalist => Achievement {
id: id.clone(),
name: "Loyalist".to_string(),
description: "Used Hikari for a whole year! I love you!".to_string(),
icon: "💕".to_string(),
unlocked_at: None,
},
// Fun & Easter Eggs
AchievementId::Perfectionist => Achievement {
id: id.clone(),
name: "Perfectionist".to_string(),
description: "Redid something 5 times to get it right!".to_string(),
icon: "🎯".to_string(),
unlocked_at: None,
},
AchievementId::Persistent => Achievement {
id: id.clone(),
name: "Persistent".to_string(),
description: "Asked the same question 3 times. Never give up!".to_string(),
icon: "💪".to_string(),
unlocked_at: None,
},
AchievementId::Patient => Achievement {
id: id.clone(),
name: "Patient".to_string(),
description: "Waited for a long response. Thank you for waiting!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::Speedy => Achievement {
id: id.clone(),
name: "Speedy".to_string(),
description: "Sent 10 messages in 1 minute! Slow down~".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::MultiTasker => Achievement {
id: id.clone(),
name: "Multi-Tasker".to_string(),
description: "Had 5+ conversation tabs open!".to_string(),
icon: "📑".to_string(),
unlocked_at: None,
},
AchievementId::Minimalist => Achievement {
id: id.clone(),
name: "Minimalist".to_string(),
description: "Used compact mode!".to_string(),
icon: "📱".to_string(),
unlocked_at: None,
},
AchievementId::PrivacyFirst => Achievement {
id: id.clone(),
name: "Privacy First".to_string(),
description: "Enabled streamer mode!".to_string(),
icon: "🔒".to_string(),
unlocked_at: None,
},
AchievementId::ThemeChanger => Achievement {
id: id.clone(),
name: "Theme Changer".to_string(),
description: "Changed theme 3 times!".to_string(),
icon: "🎨".to_string(),
unlocked_at: None,
},
AchievementId::SettingsTweaker => Achievement {
id: id.clone(),
name: "Settings Tweaker".to_string(),
description: "Opened settings 10 times!".to_string(),
icon: "⚙️".to_string(),
unlocked_at: None,
},
AchievementId::AchievementHunter => Achievement {
id: id.clone(),
name: "Achievement Hunter".to_string(),
description: "Checked achievements panel 20 times!".to_string(),
icon: "🏆".to_string(),
unlocked_at: None,
},
AchievementId::Completionist => Achievement {
id: id.clone(),
name: "Completionist".to_string(),
description: "Unlocked 50% of achievements!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
AchievementId::MasterUnlocker => Achievement {
id: id.clone(),
name: "Master Unlocker".to_string(),
description: "Unlocked 75% of achievements!".to_string(),
icon: "🌟".to_string(),
unlocked_at: None,
},
AchievementId::PlatinumStatus => Achievement {
id: id.clone(),
name: "Platinum Status".to_string(),
description: "Unlocked 100% of achievements! You're amazing!".to_string(),
icon: "💎".to_string(),
unlocked_at: None,
},
// Clipboard & Snippets
AchievementId::ClipboardCollector => Achievement {
id: id.clone(),
name: "Clipboard Collector".to_string(),
description: "Saved 20 clipboard entries!".to_string(),
icon: "📋".to_string(),
unlocked_at: None,
},
AchievementId::SnippetCreator => Achievement {
id: id.clone(),
name: "Snippet Creator".to_string(),
description: "Created 5 custom snippets!".to_string(),
icon: "✂️".to_string(),
unlocked_at: None,
},
AchievementId::SnippetMaster => Achievement {
id: id.clone(),
name: "Snippet Master".to_string(),
description: "Used snippets 50 times!".to_string(),
icon: "📎".to_string(),
unlocked_at: None,
},
AchievementId::QuickActionUser => Achievement {
id: id.clone(),
name: "Quick Action User".to_string(),
description: "Used quick actions 20 times!".to_string(),
icon: "".to_string(),
unlocked_at: None,
},
// Session History
AchievementId::HistoryBuff => Achievement {
id: id.clone(),
name: "History Buff".to_string(),
description: "Saved 10 sessions!".to_string(),
icon: "📚".to_string(),
unlocked_at: None,
},
AchievementId::Archivist => Achievement {
id: id.clone(),
name: "Archivist".to_string(),
description: "Saved 50 sessions!".to_string(),
icon: "🏛️".to_string(),
unlocked_at: None,
},
AchievementId::SessionExporter => Achievement {
id: id.clone(),
name: "Session Exporter".to_string(),
description: "Exported a session!".to_string(),
icon: "📤".to_string(),
unlocked_at: None,
},
// New Features
AchievementId::GitPanelUser => Achievement {
id: id.clone(),
name: "Git Panel User".to_string(),
description: "Used the git panel 10 times!".to_string(),
icon: "🌿".to_string(),
unlocked_at: None,
},
AchievementId::FeatureExplorer => Achievement {
id: id.clone(),
name: "Feature Explorer".to_string(),
description: "Tried all major features!".to_string(),
icon: "🗺️".to_string(),
unlocked_at: None,
},
}
}
// Get all achievement IDs for calculating completion percentage
pub fn get_all_achievement_ids() -> Vec<AchievementId> {
vec![
// Token Milestones
AchievementId::FirstSteps,
AchievementId::GrowingStrong,
AchievementId::BlossomingCoder,
AchievementId::TokenMaster,
AchievementId::TokenBillionaire,
AchievementId::TokenTreasure,
// Code Generation
AchievementId::HelloWorld,
AchievementId::CodeWizard,
AchievementId::ThousandBlocks,
AchievementId::CodeFactory,
AchievementId::CodeEmpire,
// File Operations
AchievementId::FileManipulator,
AchievementId::FileArchitect,
AchievementId::FileEngineer,
AchievementId::FileLegend,
// Conversation milestones
AchievementId::ConversationStarter,
AchievementId::ChattyKathy,
AchievementId::Conversationalist,
AchievementId::ChatMarathon,
AchievementId::ChatLegend,
// Tool usage
AchievementId::Toolsmith,
AchievementId::ToolMaster,
// Time-based achievements
AchievementId::EarlyBird,
AchievementId::NightOwl,
AchievementId::AllNighter,
AchievementId::WeekendWarrior,
AchievementId::DedicatedDeveloper,
// Search and exploration
AchievementId::Explorer,
AchievementId::MasterSearcher,
// Session achievements
AchievementId::QuickSession,
AchievementId::FocusedWork,
AchievementId::DeepDive,
AchievementId::MarathonSession,
AchievementId::UltraMarathon,
AchievementId::CodingRetreat,
// Special achievements
AchievementId::FirstMessage,
AchievementId::FirstTool,
AchievementId::FirstCodeBlock,
AchievementId::FirstFileEdit,
AchievementId::Polyglot,
AchievementId::SpeedCoder,
AchievementId::ClaudeConnoisseur,
AchievementId::MarathonCoder,
// Relationship & Greetings
AchievementId::GoodMorning,
AchievementId::GoodNight,
AchievementId::ThankYou,
AchievementId::LoveYou,
AchievementId::HelloHikari,
AchievementId::HowAreYou,
AchievementId::MissedYou,
AchievementId::BackAgain,
// Personality & Fun
AchievementId::EmojiUser,
AchievementId::QuestionMaster,
AchievementId::CapsLock,
AchievementId::PleaseAndThankYou,
// Emotional
AchievementId::Frustrated,
AchievementId::Excited,
AchievementId::Confused,
AchievementId::Curious,
AchievementId::Impressed,
// Git & Development
AchievementId::GitGuru,
AchievementId::TestWriter,
AchievementId::Debugger,
AchievementId::CommitKing,
AchievementId::CommitLegend,
AchievementId::BranchMaster,
AchievementId::MergeExpert,
AchievementId::ConflictResolver,
// Tool Mastery
AchievementId::BashMaster,
AchievementId::FileExplorer,
AchievementId::SearchExpert,
AchievementId::EditMaster,
AchievementId::WriteMaster,
AchievementId::GlobMaster,
AchievementId::TaskMaster,
AchievementId::WebFetcher,
AchievementId::McpExplorer,
// Daily Streaks
AchievementId::WeekStreak,
AchievementId::TwoWeekStreak,
AchievementId::MonthStreak,
AchievementId::QuarterStreak,
// Time Challenges
AchievementId::MorningPerson,
AchievementId::NightCoder,
AchievementId::LunchBreakCoder,
AchievementId::CoffeeTime,
// Day-specific
AchievementId::MondayMotivation,
AchievementId::FridayFinisher,
AchievementId::HumpDay,
// Seasonal/Special Times
AchievementId::NewYearCoder,
AchievementId::ValentinesDev,
AchievementId::SpookyCode,
AchievementId::HolidayCoder,
AchievementId::LeapDayCoder,
// Message Content
AchievementId::LongMessage,
AchievementId::NovelWriter,
AchievementId::ShortAndSweet,
AchievementId::CodeInMessage,
AchievementId::MarkdownMaster,
// Programming Languages
AchievementId::RustDeveloper,
AchievementId::PythonDeveloper,
AchievementId::JavaScriptDev,
AchievementId::TypeScriptDev,
AchievementId::GoDeveloper,
AchievementId::CppDeveloper,
AchievementId::JavaDeveloper,
AchievementId::HtmlCssDev,
AchievementId::SqlDeveloper,
AchievementId::ShellScripter,
AchievementId::FullStackDev,
// Project Types
AchievementId::FrontendDev,
AchievementId::BackendDev,
AchievementId::ConfigEditor,
AchievementId::DocWriter,
// Error Handling
AchievementId::ErrorHunter,
AchievementId::ExceptionSlayer,
AchievementId::BugExterminator,
// Refactoring
AchievementId::CleanCoder,
AchievementId::Optimizer,
AchievementId::Simplifier,
// Testing
AchievementId::TestNovice,
AchievementId::TestEnthusiast,
AchievementId::TestMaster,
AchievementId::CoverageKing,
// Documentation
AchievementId::Documenter,
AchievementId::CommentWriter,
AchievementId::ReadmeHero,
// API & Integration
AchievementId::ApiExplorer,
AchievementId::DatabaseDev,
AchievementId::CloudCoder,
// Special Milestones
AchievementId::CenturyClub,
AchievementId::ThousandSessions,
AchievementId::Veteran,
AchievementId::OldTimer,
AchievementId::Loyalist,
// Fun & Easter Eggs
AchievementId::Perfectionist,
AchievementId::Persistent,
AchievementId::Patient,
AchievementId::Speedy,
AchievementId::MultiTasker,
AchievementId::Minimalist,
AchievementId::PrivacyFirst,
AchievementId::ThemeChanger,
AchievementId::SettingsTweaker,
AchievementId::AchievementHunter,
AchievementId::Completionist,
AchievementId::MasterUnlocker,
AchievementId::PlatinumStatus,
// Clipboard & Snippets
AchievementId::ClipboardCollector,
AchievementId::SnippetCreator,
AchievementId::SnippetMaster,
AchievementId::QuickActionUser,
// Session History
AchievementId::HistoryBuff,
AchievementId::Archivist,
AchievementId::SessionExporter,
// New Features
AchievementId::GitPanelUser,
AchievementId::FeatureExplorer,
]
}
// Check achievements based on message content
pub fn check_message_achievements(
message: &str,
progress: &mut AchievementProgress,
) -> Vec<AchievementId> {
let mut newly_unlocked = Vec::new();
let message_lower = message.to_lowercase();
println!("Checking message achievements for: {}", message);
// Relationship & Greetings
if message_lower.contains("good morning") && progress.unlock(AchievementId::GoodMorning) {
newly_unlocked.push(AchievementId::GoodMorning);
}
if (message_lower.contains("good night") || message_lower.contains("goodnight"))
&& progress.unlock(AchievementId::GoodNight)
{
newly_unlocked.push(AchievementId::GoodNight);
}
if (message_lower.contains("thank you")
|| message_lower.contains("thanks")
|| message_lower.contains("thx"))
&& progress.unlock(AchievementId::ThankYou)
{
newly_unlocked.push(AchievementId::ThankYou);
}
if (message_lower.contains("love you") || message_lower.contains("ily"))
&& progress.unlock(AchievementId::LoveYou)
{
newly_unlocked.push(AchievementId::LoveYou);
}
// Personality & Fun
if message.chars().any(|c| c as u32 >= 0x1F300) && progress.unlock(AchievementId::EmojiUser) {
newly_unlocked.push(AchievementId::EmojiUser);
}
if message == message.to_uppercase()
&& message.len() > 5
&& message.chars().any(|c| c.is_alphabetic())
&& progress.unlock(AchievementId::CapsLock)
{
newly_unlocked.push(AchievementId::CapsLock);
}
if message_lower.contains("please") && progress.unlock(AchievementId::PleaseAndThankYou) {
newly_unlocked.push(AchievementId::PleaseAndThankYou);
}
// Git & Development patterns in messages
if (message_lower.contains("fix")
|| message_lower.contains("bug")
|| message_lower.contains("error"))
&& progress.unlock(AchievementId::Debugger)
{
newly_unlocked.push(AchievementId::Debugger);
}
// Extended greetings
if (message_lower.contains("hello hikari") || message_lower.contains("hi hikari"))
&& progress.unlock(AchievementId::HelloHikari)
{
newly_unlocked.push(AchievementId::HelloHikari);
}
if message_lower.contains("how are you") && progress.unlock(AchievementId::HowAreYou) {
newly_unlocked.push(AchievementId::HowAreYou);
}
if message_lower.contains("missed you") && progress.unlock(AchievementId::MissedYou) {
newly_unlocked.push(AchievementId::MissedYou);
}
if (message_lower.contains("i'm back") || message_lower.contains("back again"))
&& progress.unlock(AchievementId::BackAgain)
{
newly_unlocked.push(AchievementId::BackAgain);
}
// Emotional achievements
if (message_lower.contains("frustrated")
|| message_lower.contains("ugh")
|| message_lower.contains("argh"))
&& progress.unlock(AchievementId::Frustrated)
{
newly_unlocked.push(AchievementId::Frustrated);
}
if (message_lower.contains("excited")
|| message_lower.contains("yay")
|| message_lower.contains("woohoo"))
&& progress.unlock(AchievementId::Excited)
{
newly_unlocked.push(AchievementId::Excited);
}
if (message_lower.contains("confused") || message_lower.contains("don't understand"))
&& progress.unlock(AchievementId::Confused)
{
newly_unlocked.push(AchievementId::Confused);
}
if (message_lower.contains("why ") || message_lower.contains("how does"))
&& progress.unlock(AchievementId::Curious)
{
newly_unlocked.push(AchievementId::Curious);
}
if (message_lower.contains("wow")
|| message_lower.contains("amazing")
|| message_lower.contains("incredible"))
&& progress.unlock(AchievementId::Impressed)
{
newly_unlocked.push(AchievementId::Impressed);
}
// Message content achievements
if message.len() > 500 && progress.unlock(AchievementId::LongMessage) {
newly_unlocked.push(AchievementId::LongMessage);
}
if message.len() > 2000 && progress.unlock(AchievementId::NovelWriter) {
newly_unlocked.push(AchievementId::NovelWriter);
}
// Code in message (triple backticks)
if message.contains("```") && progress.unlock(AchievementId::CodeInMessage) {
newly_unlocked.push(AchievementId::CodeInMessage);
}
// Markdown formatting
if (message.contains("**")
|| message.contains("__")
|| message.contains("##")
|| message.contains("- ")
|| message.contains("1. "))
&& progress.unlock(AchievementId::MarkdownMaster)
{
newly_unlocked.push(AchievementId::MarkdownMaster);
}
// Refactoring keywords
if message_lower.contains("refactor") && progress.unlock(AchievementId::CleanCoder) {
newly_unlocked.push(AchievementId::CleanCoder);
}
if (message_lower.contains("optimize") || message_lower.contains("performance"))
&& progress.unlock(AchievementId::Optimizer)
{
newly_unlocked.push(AchievementId::Optimizer);
}
if message_lower.contains("simplify") && progress.unlock(AchievementId::Simplifier) {
newly_unlocked.push(AchievementId::Simplifier);
}
// Testing keywords
if message_lower.contains("test") && progress.unlock(AchievementId::TestWriter) {
newly_unlocked.push(AchievementId::TestWriter);
}
if message_lower.contains("coverage") && progress.unlock(AchievementId::CoverageKing) {
newly_unlocked.push(AchievementId::CoverageKing);
}
// Documentation keywords
if (message_lower.contains("document") || message_lower.contains("documentation"))
&& progress.unlock(AchievementId::Documenter)
{
newly_unlocked.push(AchievementId::Documenter);
}
if message_lower.contains("comment") && progress.unlock(AchievementId::CommentWriter) {
newly_unlocked.push(AchievementId::CommentWriter);
}
if message_lower.contains("readme") && progress.unlock(AchievementId::ReadmeHero) {
newly_unlocked.push(AchievementId::ReadmeHero);
}
// API & Integration keywords
if message_lower.contains("api") && progress.unlock(AchievementId::ApiExplorer) {
newly_unlocked.push(AchievementId::ApiExplorer);
}
if (message_lower.contains("database")
|| message_lower.contains("sql")
|| message_lower.contains("postgres")
|| message_lower.contains("mysql"))
&& progress.unlock(AchievementId::DatabaseDev)
{
newly_unlocked.push(AchievementId::DatabaseDev);
}
if (message_lower.contains("cloud")
|| message_lower.contains("aws")
|| message_lower.contains("azure")
|| message_lower.contains("gcp"))
&& progress.unlock(AchievementId::CloudCoder)
{
newly_unlocked.push(AchievementId::CloudCoder);
}
// Git keywords
if message_lower.contains("merge conflict") && progress.unlock(AchievementId::ConflictResolver)
{
newly_unlocked.push(AchievementId::ConflictResolver);
}
newly_unlocked
}
// Check which achievements should be unlocked based on current stats
pub fn check_achievements(
stats: &crate::stats::UsageStats,
progress: &mut AchievementProgress,
) -> Vec<AchievementId> {
let mut newly_unlocked = Vec::new();
println!(
"Checking achievements with stats: messages={}, tokens={}, code_blocks={}",
stats.messages_exchanged,
stats.total_input_tokens + stats.total_output_tokens,
stats.code_blocks_generated
);
println!("Currently unlocked: {:?}", progress.unlocked);
// Token milestones
let total_tokens = stats.total_input_tokens + stats.total_output_tokens;
if total_tokens >= 1_000 && progress.unlock(AchievementId::FirstSteps) {
println!("Unlocked FirstSteps achievement!");
newly_unlocked.push(AchievementId::FirstSteps);
}
if total_tokens >= 10_000 && progress.unlock(AchievementId::GrowingStrong) {
newly_unlocked.push(AchievementId::GrowingStrong);
}
if total_tokens >= 100_000 && progress.unlock(AchievementId::BlossomingCoder) {
newly_unlocked.push(AchievementId::BlossomingCoder);
}
if total_tokens >= 1_000_000 && progress.unlock(AchievementId::TokenMaster) {
newly_unlocked.push(AchievementId::TokenMaster);
}
// Code generation
if stats.code_blocks_generated >= 1 && progress.unlock(AchievementId::HelloWorld) {
newly_unlocked.push(AchievementId::HelloWorld);
}
if stats.code_blocks_generated >= 100 && progress.unlock(AchievementId::CodeWizard) {
newly_unlocked.push(AchievementId::CodeWizard);
}
if stats.code_blocks_generated >= 1000 && progress.unlock(AchievementId::ThousandBlocks) {
newly_unlocked.push(AchievementId::ThousandBlocks);
}
// File operations
if stats.files_edited >= 10 && progress.unlock(AchievementId::FileManipulator) {
newly_unlocked.push(AchievementId::FileManipulator);
}
let total_files = stats.files_edited + stats.files_created;
if total_files >= 100 && progress.unlock(AchievementId::FileArchitect) {
newly_unlocked.push(AchievementId::FileArchitect);
}
// Conversation milestones
if stats.messages_exchanged >= 1 && progress.unlock(AchievementId::FirstMessage) {
newly_unlocked.push(AchievementId::FirstMessage);
}
if stats.messages_exchanged >= 10 && progress.unlock(AchievementId::ConversationStarter) {
newly_unlocked.push(AchievementId::ConversationStarter);
}
if stats.messages_exchanged >= 100 && progress.unlock(AchievementId::ChattyKathy) {
newly_unlocked.push(AchievementId::ChattyKathy);
}
if stats.messages_exchanged >= 1000 && progress.unlock(AchievementId::Conversationalist) {
newly_unlocked.push(AchievementId::Conversationalist);
}
// Tool usage
let unique_tools = stats.tools_usage.len();
if unique_tools >= 5 && progress.unlock(AchievementId::Toolsmith) {
newly_unlocked.push(AchievementId::Toolsmith);
}
if unique_tools >= 10 && progress.unlock(AchievementId::ToolMaster) {
newly_unlocked.push(AchievementId::ToolMaster);
}
// Search and exploration
let search_tools = ["Glob", "Grep", "search", "Task"];
let search_count: u64 = search_tools
.iter()
.filter_map(|tool| stats.tools_usage.get(*tool))
.sum();
if search_count >= 50 && progress.unlock(AchievementId::Explorer) {
newly_unlocked.push(AchievementId::Explorer);
}
if search_count >= 500 && progress.unlock(AchievementId::MasterSearcher) {
newly_unlocked.push(AchievementId::MasterSearcher);
}
// Session duration achievements
let session_secs = stats.session_duration_seconds;
if session_secs < 300
&& stats.session_messages_exchanged >= 5
&& progress.unlock(AchievementId::QuickSession)
{
newly_unlocked.push(AchievementId::QuickSession);
}
if session_secs >= 1800 && progress.unlock(AchievementId::FocusedWork) {
newly_unlocked.push(AchievementId::FocusedWork);
}
if session_secs >= 7200 && progress.unlock(AchievementId::DeepDive) {
newly_unlocked.push(AchievementId::DeepDive);
}
if session_secs >= 18000 && progress.unlock(AchievementId::MarathonSession) {
newly_unlocked.push(AchievementId::MarathonSession);
}
// Session token achievement
let session_tokens = stats.session_input_tokens + stats.session_output_tokens;
if session_tokens >= 10000 && progress.unlock(AchievementId::MarathonCoder) {
newly_unlocked.push(AchievementId::MarathonCoder);
}
// Special first-time achievements
if !stats.tools_usage.is_empty() && progress.unlock(AchievementId::FirstTool) {
newly_unlocked.push(AchievementId::FirstTool);
}
if stats.code_blocks_generated >= 1 && progress.unlock(AchievementId::FirstCodeBlock) {
newly_unlocked.push(AchievementId::FirstCodeBlock);
}
if stats.files_edited >= 1 && progress.unlock(AchievementId::FirstFileEdit) {
newly_unlocked.push(AchievementId::FirstFileEdit);
}
// Speed coder - need to track time for this
// TODO: Implement tracking for 10 code blocks in 10 minutes
// Polyglot - need to track languages
// TODO: Implement tracking for multiple programming languages
// Claude Connoisseur - check model usage
// TODO: Track different Claude models used
// Tool mastery achievements
if let Some(bash_count) = stats.tools_usage.get("Bash") {
if *bash_count >= 50 && progress.unlock(AchievementId::BashMaster) {
newly_unlocked.push(AchievementId::BashMaster);
}
}
if let Some(read_count) = stats.tools_usage.get("Read") {
if *read_count >= 100 && progress.unlock(AchievementId::FileExplorer) {
newly_unlocked.push(AchievementId::FileExplorer);
}
}
if let Some(grep_count) = stats.tools_usage.get("Grep") {
if *grep_count >= 50 && progress.unlock(AchievementId::SearchExpert) {
newly_unlocked.push(AchievementId::SearchExpert);
}
}
// Git Guru - check git command usage in Bash
if let Some(bash_count) = stats.tools_usage.get("Bash") {
if *bash_count >= 10 && progress.unlock(AchievementId::GitGuru) {
// TODO: More specific git command tracking
newly_unlocked.push(AchievementId::GitGuru);
}
}
// Extended token milestones
if total_tokens >= 10_000_000 && progress.unlock(AchievementId::TokenBillionaire) {
newly_unlocked.push(AchievementId::TokenBillionaire);
}
if total_tokens >= 50_000_000 && progress.unlock(AchievementId::TokenTreasure) {
newly_unlocked.push(AchievementId::TokenTreasure);
}
// Extended code generation
if stats.code_blocks_generated >= 5000 && progress.unlock(AchievementId::CodeFactory) {
newly_unlocked.push(AchievementId::CodeFactory);
}
if stats.code_blocks_generated >= 10000 && progress.unlock(AchievementId::CodeEmpire) {
newly_unlocked.push(AchievementId::CodeEmpire);
}
// Extended file operations
if total_files >= 500 && progress.unlock(AchievementId::FileEngineer) {
newly_unlocked.push(AchievementId::FileEngineer);
}
if total_files >= 1000 && progress.unlock(AchievementId::FileLegend) {
newly_unlocked.push(AchievementId::FileLegend);
}
// Extended conversation milestones
if stats.messages_exchanged >= 5000 && progress.unlock(AchievementId::ChatMarathon) {
newly_unlocked.push(AchievementId::ChatMarathon);
}
if stats.messages_exchanged >= 10000 && progress.unlock(AchievementId::ChatLegend) {
newly_unlocked.push(AchievementId::ChatLegend);
}
// Extended session duration achievements
if session_secs >= 28800 && progress.unlock(AchievementId::UltraMarathon) {
// 8 hours
newly_unlocked.push(AchievementId::UltraMarathon);
}
if session_secs >= 43200 && progress.unlock(AchievementId::CodingRetreat) {
// 12 hours
newly_unlocked.push(AchievementId::CodingRetreat);
}
// More tool mastery achievements
if let Some(edit_count) = stats.tools_usage.get("Edit") {
if *edit_count >= 100 && progress.unlock(AchievementId::EditMaster) {
newly_unlocked.push(AchievementId::EditMaster);
}
}
if let Some(write_count) = stats.tools_usage.get("Write") {
if *write_count >= 50 && progress.unlock(AchievementId::WriteMaster) {
newly_unlocked.push(AchievementId::WriteMaster);
}
}
if let Some(glob_count) = stats.tools_usage.get("Glob") {
if *glob_count >= 100 && progress.unlock(AchievementId::GlobMaster) {
newly_unlocked.push(AchievementId::GlobMaster);
}
}
if let Some(task_count) = stats.tools_usage.get("Task") {
if *task_count >= 50 && progress.unlock(AchievementId::TaskMaster) {
newly_unlocked.push(AchievementId::TaskMaster);
}
}
if let Some(web_count) = stats.tools_usage.get("WebFetch") {
if *web_count >= 20 && progress.unlock(AchievementId::WebFetcher) {
newly_unlocked.push(AchievementId::WebFetcher);
}
}
// MCP tools - count tools that contain "mcp__"
let mcp_count: u64 = stats
.tools_usage
.iter()
.filter(|(name, _)| name.starts_with("mcp__"))
.map(|(_, count)| count)
.sum();
if mcp_count >= 50 && progress.unlock(AchievementId::McpExplorer) {
newly_unlocked.push(AchievementId::McpExplorer);
}
// Time-based achievements
if let Some(session_start) = progress.session_start {
let hour = session_start.hour();
let weekday = session_start.weekday();
// Early bird - 5 AM to 7 AM
if (5..=7).contains(&hour) && progress.unlock(AchievementId::EarlyBird) {
newly_unlocked.push(AchievementId::EarlyBird);
}
// Night owl - after midnight
let current_hour = Utc::now().hour();
if current_hour < 6 && progress.unlock(AchievementId::NightOwl) {
newly_unlocked.push(AchievementId::NightOwl);
}
// All nighter - 2 AM to 5 AM
if (2..=5).contains(&current_hour) && progress.unlock(AchievementId::AllNighter) {
newly_unlocked.push(AchievementId::AllNighter);
}
// Weekend warrior
use chrono::Weekday;
if (weekday == Weekday::Sat || weekday == Weekday::Sun)
&& progress.unlock(AchievementId::WeekendWarrior)
{
newly_unlocked.push(AchievementId::WeekendWarrior);
}
// Day-specific achievements
if weekday == Weekday::Mon && progress.unlock(AchievementId::MondayMotivation) {
newly_unlocked.push(AchievementId::MondayMotivation);
}
if weekday == Weekday::Wed && progress.unlock(AchievementId::HumpDay) {
newly_unlocked.push(AchievementId::HumpDay);
}
if weekday == Weekday::Fri && progress.unlock(AchievementId::FridayFinisher) {
newly_unlocked.push(AchievementId::FridayFinisher);
}
// Time of day achievements
if (12..=13).contains(&hour) && progress.unlock(AchievementId::LunchBreakCoder) {
newly_unlocked.push(AchievementId::LunchBreakCoder);
}
if (15..=16).contains(&hour) && progress.unlock(AchievementId::CoffeeTime) {
newly_unlocked.push(AchievementId::CoffeeTime);
}
// Seasonal/special day achievements
let now = Utc::now();
let month = now.month();
let day = now.day();
// New Year's Day (January 1st)
if month == 1 && day == 1 && progress.unlock(AchievementId::NewYearCoder) {
newly_unlocked.push(AchievementId::NewYearCoder);
}
// Valentine's Day (February 14th)
if month == 2 && day == 14 && progress.unlock(AchievementId::ValentinesDev) {
newly_unlocked.push(AchievementId::ValentinesDev);
}
// Leap Day (February 29th)
if month == 2 && day == 29 && progress.unlock(AchievementId::LeapDayCoder) {
newly_unlocked.push(AchievementId::LeapDayCoder);
}
// Halloween (October 31st)
if month == 10 && day == 31 && progress.unlock(AchievementId::SpookyCode) {
newly_unlocked.push(AchievementId::SpookyCode);
}
// Christmas (December 25th)
if month == 12 && day == 25 && progress.unlock(AchievementId::HolidayCoder) {
newly_unlocked.push(AchievementId::HolidayCoder);
}
}
// Session count achievements (from stats)
if stats.sessions_started >= 100 && progress.unlock(AchievementId::CenturyClub) {
newly_unlocked.push(AchievementId::CenturyClub);
}
if stats.sessions_started >= 1000 && progress.unlock(AchievementId::ThousandSessions) {
newly_unlocked.push(AchievementId::ThousandSessions);
}
// Daily streaks (need to check stats for consecutive days)
if stats.consecutive_days >= 7 && progress.unlock(AchievementId::WeekStreak) {
newly_unlocked.push(AchievementId::WeekStreak);
}
if stats.consecutive_days >= 14 && progress.unlock(AchievementId::TwoWeekStreak) {
newly_unlocked.push(AchievementId::TwoWeekStreak);
}
if stats.consecutive_days >= 30 && progress.unlock(AchievementId::DedicatedDeveloper) {
newly_unlocked.push(AchievementId::DedicatedDeveloper);
}
if stats.consecutive_days >= 30 && progress.unlock(AchievementId::MonthStreak) {
newly_unlocked.push(AchievementId::MonthStreak);
}
if stats.consecutive_days >= 90 && progress.unlock(AchievementId::QuarterStreak) {
newly_unlocked.push(AchievementId::QuarterStreak);
}
// Total days used (Veteran/OldTimer/Loyalist)
if stats.total_days_used >= 30 && progress.unlock(AchievementId::Veteran) {
newly_unlocked.push(AchievementId::Veteran);
}
if stats.total_days_used >= 90 && progress.unlock(AchievementId::OldTimer) {
newly_unlocked.push(AchievementId::OldTimer);
}
if stats.total_days_used >= 365 && progress.unlock(AchievementId::Loyalist) {
newly_unlocked.push(AchievementId::Loyalist);
}
// Morning/Night person tracking (from session start times stored in stats)
if stats.morning_sessions >= 10 && progress.unlock(AchievementId::MorningPerson) {
newly_unlocked.push(AchievementId::MorningPerson);
}
if stats.night_sessions >= 10 && progress.unlock(AchievementId::NightCoder) {
newly_unlocked.push(AchievementId::NightCoder);
}
// Completion percentage achievements
let total_achievements = get_all_achievement_ids().len();
let unlocked_count = progress.unlocked.len();
let completion_pct = (unlocked_count * 100) / total_achievements;
if completion_pct >= 50 && progress.unlock(AchievementId::Completionist) {
newly_unlocked.push(AchievementId::Completionist);
}
if completion_pct >= 75 && progress.unlock(AchievementId::MasterUnlocker) {
newly_unlocked.push(AchievementId::MasterUnlocker);
}
if completion_pct >= 100 && progress.unlock(AchievementId::PlatinumStatus) {
newly_unlocked.push(AchievementId::PlatinumStatus);
}
newly_unlocked
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AchievementUnlockedEvent {
pub achievement: Achievement,
}
// Save achievements to persistent store
pub async fn save_achievements(
app: &tauri::AppHandle,
progress: &AchievementProgress,
) -> Result<(), String> {
let store = app.store("achievements.json").map_err(|e| e.to_string())?;
// Create a serializable version with just the unlocked achievement IDs
let unlocked_list: Vec<AchievementId> = progress.unlocked.iter().cloned().collect();
println!("Saving achievements: {:?}", unlocked_list);
store.set(
"unlocked",
serde_json::to_value(unlocked_list).map_err(|e| e.to_string())?,
);
store.save().map_err(|e| e.to_string())?;
println!("Achievements saved successfully");
Ok(())
}
// Load achievements from persistent store
pub async fn load_achievements(app: &tauri::AppHandle) -> AchievementProgress {
println!("Loading achievements from store...");
let store = match app.store("achievements.json") {
Ok(s) => s,
Err(e) => {
println!("Failed to open achievements store: {}", e);
return AchievementProgress::new();
}
};
let mut progress = AchievementProgress::new();
// Get unlocked achievements
if let Some(unlocked_value) = store.get("unlocked") {
println!("Found unlocked value in store: {:?}", unlocked_value);
if let Ok(unlocked_list) =
serde_json::from_value::<Vec<AchievementId>>(unlocked_value.clone())
{
println!("Loaded {} achievements", unlocked_list.len());
for achievement_id in unlocked_list {
progress.unlocked.insert(achievement_id);
}
} else {
println!("Failed to parse unlocked achievements");
}
} else {
println!("No unlocked achievements found in store");
}
progress
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stats::UsageStats;
use std::collections::HashMap;
// Helper function to create a default UsageStats for testing
fn create_test_stats() -> UsageStats {
UsageStats {
total_input_tokens: 0,
total_output_tokens: 0,
total_cost_usd: 0.0,
session_input_tokens: 0,
session_output_tokens: 0,
session_cost_usd: 0.0,
model: None,
messages_exchanged: 0,
session_messages_exchanged: 0,
code_blocks_generated: 0,
session_code_blocks_generated: 0,
files_edited: 0,
session_files_edited: 0,
files_created: 0,
session_files_created: 0,
tools_usage: HashMap::new(),
session_tools_usage: HashMap::new(),
session_duration_seconds: 0,
session_start: None,
sessions_started: 0,
consecutive_days: 0,
total_days_used: 0,
morning_sessions: 0,
night_sessions: 0,
last_session_date: None,
achievements: AchievementProgress::new(),
}
}
// =====================
// AchievementProgress tests
// =====================
#[test]
fn test_achievement_unlock() {
let mut progress = AchievementProgress::new();
// First unlock should return true
assert!(progress.unlock(AchievementId::FirstSteps));
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
// Second unlock of same achievement should return false
assert!(!progress.unlock(AchievementId::FirstSteps));
// Newly unlocked should contain the achievement
let newly = progress.take_newly_unlocked();
assert_eq!(newly.len(), 1);
assert_eq!(newly[0], AchievementId::FirstSteps);
// After taking, newly unlocked should be empty
let newly = progress.take_newly_unlocked();
assert!(newly.is_empty());
}
#[test]
fn test_achievement_progress_new() {
let progress = AchievementProgress::new();
assert!(progress.unlocked.is_empty());
assert!(progress.newly_unlocked.is_empty());
assert!(progress.session_start.is_none());
}
#[test]
fn test_achievement_progress_start_session() {
let mut progress = AchievementProgress::new();
assert!(progress.session_start.is_none());
progress.start_session();
assert!(progress.session_start.is_some());
}
#[test]
fn test_multiple_unlocks() {
let mut progress = AchievementProgress::new();
assert!(progress.unlock(AchievementId::FirstSteps));
assert!(progress.unlock(AchievementId::HelloWorld));
assert!(progress.unlock(AchievementId::FirstMessage));
assert_eq!(progress.unlocked.len(), 3);
assert_eq!(progress.newly_unlocked.len(), 3);
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
assert!(progress.is_unlocked(&AchievementId::HelloWorld));
assert!(progress.is_unlocked(&AchievementId::FirstMessage));
assert!(!progress.is_unlocked(&AchievementId::TokenMaster));
}
#[test]
fn test_achievement_progress_default() {
let progress = AchievementProgress::default();
assert!(progress.unlocked.is_empty());
assert!(progress.newly_unlocked.is_empty());
}
// =====================
// get_achievement_info tests
// =====================
#[test]
fn test_get_achievement_info_first_steps() {
let info = get_achievement_info(&AchievementId::FirstSteps);
assert_eq!(info.name, "First Steps!");
assert_eq!(info.description, "Used 1,000 tokens");
assert_eq!(info.icon, "🌱");
assert!(info.unlocked_at.is_none());
}
#[test]
fn test_get_achievement_info_hello_world() {
let info = get_achievement_info(&AchievementId::HelloWorld);
assert_eq!(info.name, "Hello World!");
assert_eq!(info.description, "Generated your first code block");
assert_eq!(info.icon, "📝");
}
#[test]
fn test_get_achievement_info_night_owl() {
let info = get_achievement_info(&AchievementId::NightOwl);
assert_eq!(info.name, "Night Owl");
assert!(info.description.contains("midnight"));
assert_eq!(info.icon, "🦉");
}
#[test]
fn test_get_achievement_info_love_you() {
let info = get_achievement_info(&AchievementId::LoveYou);
assert_eq!(info.name, "Love Connection");
assert!(info.description.contains("love"));
assert_eq!(info.icon, "💕");
}
// =====================
// get_all_achievement_ids tests
// =====================
#[test]
fn test_get_all_achievement_ids_not_empty() {
let ids = get_all_achievement_ids();
assert!(!ids.is_empty());
assert!(ids.len() > 100); // We have over 100 achievements
}
#[test]
fn test_get_all_achievement_ids_contains_basics() {
let ids = get_all_achievement_ids();
assert!(ids.contains(&AchievementId::FirstSteps));
assert!(ids.contains(&AchievementId::HelloWorld));
assert!(ids.contains(&AchievementId::FirstMessage));
assert!(ids.contains(&AchievementId::NightOwl));
assert!(ids.contains(&AchievementId::PlatinumStatus));
}
#[test]
fn test_get_all_achievement_ids_no_duplicates() {
let ids = get_all_achievement_ids();
let unique: HashSet<_> = ids.iter().collect();
assert_eq!(ids.len(), unique.len(), "There should be no duplicate achievement IDs");
}
// =====================
// check_achievements tests - Token Milestones
// =====================
#[test]
fn test_check_achievements_first_steps_1000_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 500;
stats.total_output_tokens = 500;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FirstSteps));
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
}
#[test]
fn test_check_achievements_growing_strong_10000_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 5000;
stats.total_output_tokens = 5000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FirstSteps));
assert!(newly.contains(&AchievementId::GrowingStrong));
}
#[test]
fn test_check_achievements_blossoming_coder_100000_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 50000;
stats.total_output_tokens = 50000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::BlossomingCoder));
}
#[test]
fn test_check_achievements_token_master_1000000_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 500000;
stats.total_output_tokens = 500000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::TokenMaster));
}
#[test]
fn test_check_achievements_token_billionaire_10000000_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 5000000;
stats.total_output_tokens = 5000000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::TokenBillionaire));
}
#[test]
fn test_check_achievements_not_enough_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 400;
stats.total_output_tokens = 400;
let newly = check_achievements(&stats, &mut progress);
assert!(!newly.contains(&AchievementId::FirstSteps));
assert!(!progress.is_unlocked(&AchievementId::FirstSteps));
}
// =====================
// check_achievements tests - Code Generation
// =====================
#[test]
fn test_check_achievements_hello_world_first_code_block() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.code_blocks_generated = 1;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::HelloWorld));
assert!(newly.contains(&AchievementId::FirstCodeBlock));
}
#[test]
fn test_check_achievements_code_wizard_100_blocks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.code_blocks_generated = 100;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::CodeWizard));
}
#[test]
fn test_check_achievements_thousand_blocks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.code_blocks_generated = 1000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ThousandBlocks));
}
#[test]
fn test_check_achievements_code_factory_5000_blocks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.code_blocks_generated = 5000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::CodeFactory));
}
#[test]
fn test_check_achievements_code_empire_10000_blocks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.code_blocks_generated = 10000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::CodeEmpire));
}
// =====================
// check_achievements tests - File Operations
// =====================
#[test]
fn test_check_achievements_first_file_edit() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.files_edited = 1;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FirstFileEdit));
}
#[test]
fn test_check_achievements_file_manipulator_10_files() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.files_edited = 10;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FileManipulator));
}
#[test]
fn test_check_achievements_file_architect_100_total_files() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.files_edited = 50;
stats.files_created = 50;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FileArchitect));
}
#[test]
fn test_check_achievements_file_engineer_500_files() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.files_edited = 300;
stats.files_created = 200;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FileEngineer));
}
#[test]
fn test_check_achievements_file_legend_1000_files() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.files_edited = 600;
stats.files_created = 400;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FileLegend));
}
// =====================
// check_achievements tests - Conversation Milestones
// =====================
#[test]
fn test_check_achievements_first_message() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 1;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FirstMessage));
}
#[test]
fn test_check_achievements_conversation_starter_10_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 10;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ConversationStarter));
}
#[test]
fn test_check_achievements_chatty_kathy_100_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 100;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ChattyKathy));
}
#[test]
fn test_check_achievements_conversationalist_1000_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 1000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Conversationalist));
}
#[test]
fn test_check_achievements_chat_marathon_5000_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 5000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ChatMarathon));
}
#[test]
fn test_check_achievements_chat_legend_10000_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.messages_exchanged = 10000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ChatLegend));
}
// =====================
// check_achievements tests - Tool Usage
// =====================
#[test]
fn test_check_achievements_first_tool() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Read".to_string(), 1);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FirstTool));
}
#[test]
fn test_check_achievements_toolsmith_5_tools() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Read".to_string(), 1);
stats.tools_usage.insert("Write".to_string(), 1);
stats.tools_usage.insert("Edit".to_string(), 1);
stats.tools_usage.insert("Bash".to_string(), 1);
stats.tools_usage.insert("Grep".to_string(), 1);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Toolsmith));
}
#[test]
fn test_check_achievements_tool_master_10_tools() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
for i in 0..10 {
stats.tools_usage.insert(format!("Tool{}", i), 1);
}
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ToolMaster));
}
#[test]
fn test_check_achievements_bash_master_50_uses() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Bash".to_string(), 50);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::BashMaster));
}
#[test]
fn test_check_achievements_file_explorer_100_reads() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Read".to_string(), 100);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FileExplorer));
}
#[test]
fn test_check_achievements_search_expert_50_greps() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Grep".to_string(), 50);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::SearchExpert));
}
#[test]
fn test_check_achievements_edit_master_100_edits() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Edit".to_string(), 100);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::EditMaster));
}
#[test]
fn test_check_achievements_write_master_50_writes() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Write".to_string(), 50);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::WriteMaster));
}
#[test]
fn test_check_achievements_glob_master_100_globs() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Glob".to_string(), 100);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::GlobMaster));
}
#[test]
fn test_check_achievements_task_master_50_tasks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Task".to_string(), 50);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::TaskMaster));
}
#[test]
fn test_check_achievements_web_fetcher_20_fetches() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("WebFetch".to_string(), 20);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::WebFetcher));
}
#[test]
fn test_check_achievements_mcp_explorer_50_mcp_calls() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("mcp__github__create_issue".to_string(), 25);
stats.tools_usage.insert("mcp__notion__search".to_string(), 25);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::McpExplorer));
}
// =====================
// check_achievements tests - Search and Exploration
// =====================
#[test]
fn test_check_achievements_explorer_50_searches() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Grep".to_string(), 30);
stats.tools_usage.insert("Glob".to_string(), 20);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Explorer));
}
#[test]
fn test_check_achievements_master_searcher_500_searches() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.tools_usage.insert("Grep".to_string(), 200);
stats.tools_usage.insert("Glob".to_string(), 200);
stats.tools_usage.insert("Task".to_string(), 100);
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::MasterSearcher));
}
// =====================
// check_achievements tests - Session Duration
// =====================
#[test]
fn test_check_achievements_quick_session_under_5_min() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 200; // Under 5 minutes
stats.session_messages_exchanged = 5;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::QuickSession));
}
#[test]
fn test_check_achievements_quick_session_not_enough_messages() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 200;
stats.session_messages_exchanged = 3; // Less than 5 messages
let newly = check_achievements(&stats, &mut progress);
assert!(!newly.contains(&AchievementId::QuickSession));
}
#[test]
fn test_check_achievements_focused_work_30_min() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 1800; // 30 minutes
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::FocusedWork));
}
#[test]
fn test_check_achievements_deep_dive_2_hours() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 7200; // 2 hours
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::DeepDive));
}
#[test]
fn test_check_achievements_marathon_session_5_hours() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 18000; // 5 hours
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::MarathonSession));
}
#[test]
fn test_check_achievements_ultra_marathon_8_hours() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 28800; // 8 hours
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::UltraMarathon));
}
#[test]
fn test_check_achievements_coding_retreat_12_hours() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_duration_seconds = 43200; // 12 hours
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::CodingRetreat));
}
#[test]
fn test_check_achievements_marathon_coder_10k_session_tokens() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.session_input_tokens = 5000;
stats.session_output_tokens = 5000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::MarathonCoder));
}
// =====================
// check_achievements tests - Streaks
// =====================
#[test]
fn test_check_achievements_week_streak() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.consecutive_days = 7;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::WeekStreak));
}
#[test]
fn test_check_achievements_two_week_streak() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.consecutive_days = 14;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::TwoWeekStreak));
}
#[test]
fn test_check_achievements_month_streak() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.consecutive_days = 30;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::DedicatedDeveloper));
assert!(newly.contains(&AchievementId::MonthStreak));
}
#[test]
fn test_check_achievements_quarter_streak() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.consecutive_days = 90;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::QuarterStreak));
}
// =====================
// check_achievements tests - Total Days Used
// =====================
#[test]
fn test_check_achievements_veteran_30_days() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_days_used = 30;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Veteran));
}
#[test]
fn test_check_achievements_old_timer_90_days() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_days_used = 90;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::OldTimer));
}
#[test]
fn test_check_achievements_loyalist_365_days() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_days_used = 365;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Loyalist));
}
// =====================
// check_achievements tests - Sessions
// =====================
#[test]
fn test_check_achievements_century_club_100_sessions() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.sessions_started = 100;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::CenturyClub));
}
#[test]
fn test_check_achievements_thousand_sessions() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.sessions_started = 1000;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::ThousandSessions));
}
#[test]
fn test_check_achievements_morning_person() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.morning_sessions = 10;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::MorningPerson));
}
#[test]
fn test_check_achievements_night_coder() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.night_sessions = 10;
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::NightCoder));
}
// =====================
// check_achievements tests - Idempotency
// =====================
#[test]
fn test_check_achievements_idempotent_no_duplicate_unlocks() {
let mut stats = create_test_stats();
let mut progress = AchievementProgress::new();
stats.total_input_tokens = 5000;
stats.total_output_tokens = 5000;
// First check
let newly1 = check_achievements(&stats, &mut progress);
assert!(newly1.contains(&AchievementId::FirstSteps));
assert!(newly1.contains(&AchievementId::GrowingStrong));
// Second check with same stats should not return same achievements
let newly2 = check_achievements(&stats, &mut progress);
assert!(!newly2.contains(&AchievementId::FirstSteps));
assert!(!newly2.contains(&AchievementId::GrowingStrong));
}
// =====================
// check_message_achievements tests
// =====================
#[test]
fn test_check_message_achievements_good_morning() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Good morning, Hikari!", &mut progress);
assert!(newly.contains(&AchievementId::GoodMorning));
}
#[test]
fn test_check_message_achievements_good_night() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Good night!", &mut progress);
assert!(newly.contains(&AchievementId::GoodNight));
// Also test "goodnight" variant
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Goodnight, sleep well!", &mut progress2);
assert!(newly2.contains(&AchievementId::GoodNight));
}
#[test]
fn test_check_message_achievements_thank_you() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Thank you so much!", &mut progress);
assert!(newly.contains(&AchievementId::ThankYou));
// Also test "thanks" variant
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Thanks for your help!", &mut progress2);
assert!(newly2.contains(&AchievementId::ThankYou));
// Also test "thx" variant
let mut progress3 = AchievementProgress::new();
let newly3 = check_message_achievements("thx!", &mut progress3);
assert!(newly3.contains(&AchievementId::ThankYou));
}
#[test]
fn test_check_message_achievements_love_you() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I love you!", &mut progress);
assert!(newly.contains(&AchievementId::LoveYou));
// Also test "ily" variant
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("ily hikari", &mut progress2);
assert!(newly2.contains(&AchievementId::LoveYou));
}
#[test]
fn test_check_message_achievements_emoji_user() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Hello! 🌸", &mut progress);
assert!(newly.contains(&AchievementId::EmojiUser));
}
#[test]
fn test_check_message_achievements_caps_lock() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("THIS IS ALL CAPS!", &mut progress);
assert!(newly.contains(&AchievementId::CapsLock));
}
#[test]
fn test_check_message_achievements_caps_lock_too_short() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("HI", &mut progress);
// Too short (< 5 chars)
assert!(!newly.contains(&AchievementId::CapsLock));
}
#[test]
fn test_check_message_achievements_caps_lock_numbers_only() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("123456", &mut progress);
// No alphabetic characters
assert!(!newly.contains(&AchievementId::CapsLock));
}
#[test]
fn test_check_message_achievements_please_and_thank_you() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Can you please help me?", &mut progress);
assert!(newly.contains(&AchievementId::PleaseAndThankYou));
}
#[test]
fn test_check_message_achievements_debugger() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Can you help me fix this bug?", &mut progress);
assert!(newly.contains(&AchievementId::Debugger));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("There's an error in my code", &mut progress2);
assert!(newly2.contains(&AchievementId::Debugger));
}
#[test]
fn test_check_message_achievements_hello_hikari() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Hello Hikari! How are you?", &mut progress);
assert!(newly.contains(&AchievementId::HelloHikari));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Hi Hikari!", &mut progress2);
assert!(newly2.contains(&AchievementId::HelloHikari));
}
#[test]
fn test_check_message_achievements_how_are_you() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("How are you today?", &mut progress);
assert!(newly.contains(&AchievementId::HowAreYou));
}
#[test]
fn test_check_message_achievements_missed_you() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I missed you!", &mut progress);
assert!(newly.contains(&AchievementId::MissedYou));
}
#[test]
fn test_check_message_achievements_back_again() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I'm back!", &mut progress);
assert!(newly.contains(&AchievementId::BackAgain));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Back again!", &mut progress2);
assert!(newly2.contains(&AchievementId::BackAgain));
}
#[test]
fn test_check_message_achievements_frustrated() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I'm so frustrated with this!", &mut progress);
assert!(newly.contains(&AchievementId::Frustrated));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Ugh, this isn't working", &mut progress2);
assert!(newly2.contains(&AchievementId::Frustrated));
}
#[test]
fn test_check_message_achievements_excited() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I'm so excited!", &mut progress);
assert!(newly.contains(&AchievementId::Excited));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Yay! It worked!", &mut progress2);
assert!(newly2.contains(&AchievementId::Excited));
}
#[test]
fn test_check_message_achievements_confused() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("I'm confused about this", &mut progress);
assert!(newly.contains(&AchievementId::Confused));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("I don't understand this", &mut progress2);
assert!(newly2.contains(&AchievementId::Confused));
}
#[test]
fn test_check_message_achievements_curious() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Why does this happen?", &mut progress);
assert!(newly.contains(&AchievementId::Curious));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("How does this work?", &mut progress2);
assert!(newly2.contains(&AchievementId::Curious));
}
#[test]
fn test_check_message_achievements_impressed() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Wow, that's amazing!", &mut progress);
assert!(newly.contains(&AchievementId::Impressed));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("That's incredible!", &mut progress2);
assert!(newly2.contains(&AchievementId::Impressed));
}
#[test]
fn test_check_message_achievements_long_message() {
let mut progress = AchievementProgress::new();
let long_message = "a".repeat(501);
let newly = check_message_achievements(&long_message, &mut progress);
assert!(newly.contains(&AchievementId::LongMessage));
}
#[test]
fn test_check_message_achievements_novel_writer() {
let mut progress = AchievementProgress::new();
let very_long_message = "a".repeat(2001);
let newly = check_message_achievements(&very_long_message, &mut progress);
assert!(newly.contains(&AchievementId::NovelWriter));
}
#[test]
fn test_check_message_achievements_code_in_message() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Here's some code:\n```rust\nfn main() {}\n```", &mut progress);
assert!(newly.contains(&AchievementId::CodeInMessage));
}
#[test]
fn test_check_message_achievements_markdown_master() {
let mut progress = AchievementProgress::new();
// Bold
let newly = check_message_achievements("This is **bold** text", &mut progress);
assert!(newly.contains(&AchievementId::MarkdownMaster));
// Headers
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("## Header", &mut progress2);
assert!(newly2.contains(&AchievementId::MarkdownMaster));
// Lists
let mut progress3 = AchievementProgress::new();
let newly3 = check_message_achievements("- Item 1\n- Item 2", &mut progress3);
assert!(newly3.contains(&AchievementId::MarkdownMaster));
}
#[test]
fn test_check_message_achievements_refactoring() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Can you refactor this code?", &mut progress);
assert!(newly.contains(&AchievementId::CleanCoder));
}
#[test]
fn test_check_message_achievements_optimizer() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Can you optimize this?", &mut progress);
assert!(newly.contains(&AchievementId::Optimizer));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Let's improve performance", &mut progress2);
assert!(newly2.contains(&AchievementId::Optimizer));
}
#[test]
fn test_check_message_achievements_simplifier() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Can you simplify this?", &mut progress);
assert!(newly.contains(&AchievementId::Simplifier));
}
#[test]
fn test_check_message_achievements_test_writer() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Let's write some tests", &mut progress);
assert!(newly.contains(&AchievementId::TestWriter));
}
#[test]
fn test_check_message_achievements_coverage_king() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("We need better test coverage", &mut progress);
assert!(newly.contains(&AchievementId::CoverageKing));
}
#[test]
fn test_check_message_achievements_documenter() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Let's write documentation", &mut progress);
assert!(newly.contains(&AchievementId::Documenter));
}
#[test]
fn test_check_message_achievements_comment_writer() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Add a comment here", &mut progress);
assert!(newly.contains(&AchievementId::CommentWriter));
}
#[test]
fn test_check_message_achievements_readme_hero() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Update the README", &mut progress);
assert!(newly.contains(&AchievementId::ReadmeHero));
}
#[test]
fn test_check_message_achievements_api_explorer() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Let's call the API", &mut progress);
assert!(newly.contains(&AchievementId::ApiExplorer));
}
#[test]
fn test_check_message_achievements_database_dev() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Let's query the database", &mut progress);
assert!(newly.contains(&AchievementId::DatabaseDev));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Write a SQL query", &mut progress2);
assert!(newly2.contains(&AchievementId::DatabaseDev));
}
#[test]
fn test_check_message_achievements_cloud_coder() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Deploy to AWS", &mut progress);
assert!(newly.contains(&AchievementId::CloudCoder));
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("Set up Azure", &mut progress2);
assert!(newly2.contains(&AchievementId::CloudCoder));
}
#[test]
fn test_check_message_achievements_conflict_resolver() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("Help me fix this merge conflict", &mut progress);
assert!(newly.contains(&AchievementId::ConflictResolver));
}
#[test]
fn test_check_message_achievements_case_insensitive() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements("GOOD MORNING HIKARI!", &mut progress);
assert!(newly.contains(&AchievementId::GoodMorning));
// Note: HelloHikari requires "hello hikari" or "hi hikari" specifically
// Just "HIKARI" alone doesn't trigger it
// Test HelloHikari with all caps
let mut progress2 = AchievementProgress::new();
let newly2 = check_message_achievements("HELLO HIKARI!", &mut progress2);
assert!(newly2.contains(&AchievementId::HelloHikari));
}
#[test]
fn test_check_message_achievements_idempotent() {
let mut progress = AchievementProgress::new();
// First check
let newly1 = check_message_achievements("Good morning!", &mut progress);
assert!(newly1.contains(&AchievementId::GoodMorning));
// Second check should not unlock again
let newly2 = check_message_achievements("Good morning!", &mut progress);
assert!(!newly2.contains(&AchievementId::GoodMorning));
}
#[test]
fn test_check_message_achievements_multiple_in_one_message() {
let mut progress = AchievementProgress::new();
let newly = check_message_achievements(
"Hello Hikari! How are you? I'm excited to work on this test coverage! 🌸",
&mut progress
);
// Should unlock multiple achievements at once
assert!(newly.contains(&AchievementId::HelloHikari));
assert!(newly.contains(&AchievementId::HowAreYou));
assert!(newly.contains(&AchievementId::Excited));
assert!(newly.contains(&AchievementId::CoverageKing));
assert!(newly.contains(&AchievementId::TestWriter));
assert!(newly.contains(&AchievementId::EmojiUser));
}
// =====================
// Completion percentage tests
// =====================
#[test]
fn test_check_achievements_completionist_50_percent() {
let stats = create_test_stats();
let mut progress = AchievementProgress::new();
// Unlock 50% of achievements manually
let all_ids = get_all_achievement_ids();
let half = all_ids.len() / 2;
for id in all_ids.into_iter().take(half) {
progress.unlock(id);
}
progress.take_newly_unlocked(); // Clear newly unlocked
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::Completionist));
}
#[test]
fn test_check_achievements_master_unlocker_75_percent() {
let stats = create_test_stats();
let mut progress = AchievementProgress::new();
// Unlock 75% of achievements manually
let all_ids = get_all_achievement_ids();
let three_quarters = (all_ids.len() * 3) / 4;
for id in all_ids.into_iter().take(three_quarters) {
progress.unlock(id);
}
progress.take_newly_unlocked();
let newly = check_achievements(&stats, &mut progress);
assert!(newly.contains(&AchievementId::MasterUnlocker));
}
}