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>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AchievementProgress { pub unlocked: HashSet, pub newly_unlocked: Vec, // Achievements unlocked but not yet notified #[serde(skip)] pub session_start: Option>, } 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 { 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 { 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 { let mut newly_unlocked = Vec::new(); let message_lower = message.to_lowercase(); tracing::info!("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 { let mut newly_unlocked = Vec::new(); tracing::info!( "Checking achievements with stats: messages={}, tokens={}, code_blocks={}", stats.messages_exchanged, stats.total_input_tokens + stats.total_output_tokens, stats.code_blocks_generated ); tracing::info!("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) { tracing::info!("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)) .map(|t| t.call_count) .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_stats) = stats.tools_usage.get("Bash") { if bash_stats.call_count >= 50 && progress.unlock(AchievementId::BashMaster) { newly_unlocked.push(AchievementId::BashMaster); } } if let Some(read_stats) = stats.tools_usage.get("Read") { if read_stats.call_count >= 100 && progress.unlock(AchievementId::FileExplorer) { newly_unlocked.push(AchievementId::FileExplorer); } } if let Some(grep_stats) = stats.tools_usage.get("Grep") { if grep_stats.call_count >= 50 && progress.unlock(AchievementId::SearchExpert) { newly_unlocked.push(AchievementId::SearchExpert); } } // Git Guru - check git command usage in Bash if let Some(bash_stats) = stats.tools_usage.get("Bash") { if bash_stats.call_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_stats) = stats.tools_usage.get("Edit") { if edit_stats.call_count >= 100 && progress.unlock(AchievementId::EditMaster) { newly_unlocked.push(AchievementId::EditMaster); } } if let Some(write_stats) = stats.tools_usage.get("Write") { if write_stats.call_count >= 50 && progress.unlock(AchievementId::WriteMaster) { newly_unlocked.push(AchievementId::WriteMaster); } } if let Some(glob_stats) = stats.tools_usage.get("Glob") { if glob_stats.call_count >= 100 && progress.unlock(AchievementId::GlobMaster) { newly_unlocked.push(AchievementId::GlobMaster); } } if let Some(task_stats) = stats.tools_usage.get("Task") { if task_stats.call_count >= 50 && progress.unlock(AchievementId::TaskMaster) { newly_unlocked.push(AchievementId::TaskMaster); } } if let Some(web_stats) = stats.tools_usage.get("WebFetch") { if web_stats.call_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(|(_, tool_stats)| tool_stats.call_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(¤t_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 = progress.unlocked.iter().cloned().collect(); tracing::info!("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())?; tracing::info!("Achievements saved successfully"); Ok(()) } // Load achievements from persistent store pub async fn load_achievements(app: &tauri::AppHandle) -> AchievementProgress { tracing::info!("Loading achievements from store..."); let store = match app.store("achievements.json") { Ok(s) => s, Err(e) => { tracing::error!("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") { tracing::info!("Found unlocked value in store: {:?}", unlocked_value); if let Ok(unlocked_list) = serde_json::from_value::>(unlocked_value.clone()) { tracing::info!("Loaded {} achievements", unlocked_list.len()); for achievement_id in unlocked_list { progress.unlocked.insert(achievement_id); } } else { tracing::error!("Failed to parse unlocked achievements"); } } else { tracing::info!("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, context_tokens_used: 0, context_window_limit: 200_000, context_utilisation_percent: 0.0, potential_cache_hits: 0, potential_cache_savings_tokens: 0, current_request_input: None, current_request_output_chars: 0, current_request_thinking_chars: 0, current_request_tools: Vec::new(), 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 // ===================== // Helper function to create a ToolTokenStats with just call count for tests fn tool_stats(call_count: u64) -> crate::stats::ToolTokenStats { crate::stats::ToolTokenStats { call_count, estimated_input_tokens: 0, estimated_output_tokens: 0, } } #[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(), tool_stats(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(), tool_stats(1)); stats.tools_usage.insert("Write".to_string(), tool_stats(1)); stats.tools_usage.insert("Edit".to_string(), tool_stats(1)); stats.tools_usage.insert("Bash".to_string(), tool_stats(1)); stats.tools_usage.insert("Grep".to_string(), tool_stats(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), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(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(), tool_stats(25)); stats.tools_usage.insert("mcp__notion__search".to_string(), tool_stats(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(), tool_stats(30)); stats.tools_usage.insert("Glob".to_string(), tool_stats(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(), tool_stats(200)); stats.tools_usage.insert("Glob".to_string(), tool_stats(200)); stats.tools_usage.insert("Task".to_string(), tool_stats(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)); } }