This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run dev # Start development server
npm run build # Build
npm run generate # Generate static files (for GitHub Pages deployment)
npm run lint # Biome lint check
npm run lint:fix # Biome lint auto-fix
npm run typecheck # Type check with vue-tsc
npm run test # Playwright E2E testsAlways run lint and type check together: npm run lint -- --fix && npx nuxi typecheck
- Nuxt 4 (SSR disabled, SPA mode) + Vue 3 + TypeScript
- Deployment target: GitHub Pages (static files generated with
npm run generate) - CSS framework: Bulma
- Linter: Biome (not ESLint). Single quotes, tab indentation
1. Firebase Firestore (app/lib/firebase.ts, app/lib/db.ts)
- Firestore project:
tsg-slackbot - Stored data: achievement data (
achievement_data), unlock records (achievements), user counters (users), dream fortune messages, slow quiz, 20-questions game
2. External REST API (https://slackbot-api.tsg.ne.jp)
- Slack user profiles (name, icon, etc.):
/slack/users - Topic messages:
/topic/topics - Auth: Uses cookies (
credentials: 'include'). Returns 401 when unauthenticated, settingisUnauthorizedstate totrue
Uses Vuex 4 instead of Nuxt's default Pinia. Manually registered and typed in app/plugins/vuex.ts.
Typing mechanism: Getter types are defined in the StoreGetters interface, and useStore() returns a TypedStore. When adding new getters, update StoreGetters as well.
Store modules (app/store/):
| Module | Data Source | Role |
|---|---|---|
slackInfos |
REST API | Slack user profiles, topic messages |
achievements |
Firestore | Achievement unlock records (all or filtered by user/name) |
achievementData |
Firestore | Achievement definition data (title, conditions, difficulty, etc.) |
users |
Firestore | User counters (numeric achievement progress) |
achievementStatsByCategory/Difficulty/Month |
Firestore | Aggregated statistics |
oneiromancies |
Firestore | Dream fortune messages |
slowQuizGames / twentyQuestionsGames |
Firestore | Game records |
Duplicate fetch prevention pattern: Each store has an isInitX flag and returns early at the start of actions with if (state.isInitX) return. Most Firestore stores subscribe in real time using onSnapshot.
The SlackUser type comes from two different stores:
slackInfos/getUser(id)→ returns profile information (name, icon image URL)users/getById(id)→ returns counter values (numeric progress per achievement) from the Firestoreuserscollection
Counter access uses dynamic keys user[counterName], so SlackUser has an index signature [counterName: string]: unknown. When retrieving counter values, use a type guard typeof val === 'number' (see getCounterValue in app/lib/utils.ts).
getCategoryColor(category)— returns the color corresponding to a category namegetUserName(user)— generates a display name fromSlackUser | undefinedgetUserIcon(user)/getUserIcon2x(user)/getUserIcon3x(user)— returns icon image URLs (falls back to default image when not loaded)
- Counter display for unlocked achievements always shows the MAX value regardless of the stored data value (due to past migrations or bugs, unlocked achievements may have counter values below the threshold)
OneiromancyMessage.originalMessageis an optional field because it may not exist in Firestore documentsachievementData/getByIdreturnsundefinedwhen not yet loaded. Use?.for safe access in templates