Conversation
WalkthroughThe pull request expands frontend app functionality with audio management (background music and sound effects), restructures navigation routes with expanded screen definitions, adjusts UI layout spacing globally with consistent padding adjustments, updates API score integration with enhanced logging, and bumps dependency versions for expo packages. Changes
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly Related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
frontend/app/solo.tsx (1)
1-1: Critical: Update the lockfile to fix pipeline failure.The pipeline is failing because
pnpm-lock.yamlis out of sync withpackage.json. One dependency was added (expo-av@~16.0.8) and three were updated (expo,expo-constants,expo-router).Run this command to update the lockfile:
cd frontend && pnpm installThen commit the updated
pnpm-lock.yamlfile.frontend/app/battle-game.tsx (1)
47-73: Add error handling for audio operations.Similar to
index.tsx, the audio initialization and BGM management lack error handling. Audio failures shouldn't crash the game screen.Apply this diff:
useEffect(() => { const init = async () => { - // 戦闘BGM再生 - await audioManager.playBGM(require('@/assets/sounds/bgm/fight.mp3')) - - const user = await getCurrentUser() - setUserLevel(user.level) - const maxHp = 100 + user.level * 10 - setUserMaxHp(maxHp) - setUserHp(maxHp) - - // 敵データを取得 - const enemies = await getEnemies() - const enemy = enemies.find((e) => e.id === enemyId) - if (enemy) { - setEnemyMaxHp(enemy.hp) - setEnemyHp(enemy.hp) - } - - addLog('戦闘開始!') + try { + // 戦闘BGM再生 + await audioManager.playBGM(require('@/assets/sounds/bgm/fight.mp3')) + } catch (error) { + console.error('Failed to play battle BGM:', error) + } + + try { + const user = await getCurrentUser() + setUserLevel(user.level) + const maxHp = 100 + user.level * 10 + setUserMaxHp(maxHp) + setUserHp(maxHp) + + // 敵データを取得 + const enemies = await getEnemies() + const enemy = enemies.find((e) => e.id === enemyId) + if (enemy) { + setEnemyMaxHp(enemy.hp) + setEnemyHp(enemy.hp) + } + + addLog('戦闘開始!') + } catch (error) { + console.error('Failed to initialize battle:', error) + addLog('初期化エラーが発生しました') + } } init() return () => { - // 画面離脱時にBGM停止する - audioManager.stopBGM() + try { + // 画面離脱時にBGM停止する + audioManager.stopBGM() + } catch (error) { + console.error('Failed to stop BGM:', error) + } } }, [enemyId])
🧹 Nitpick comments (6)
frontend/utils/audio.ts (1)
4-165: Consider adding a cleanup method for app lifecycle management.Add a
dispose()orcleanup()method to unload all sounds when the app closes or the audio manager is no longer needed.async dispose() { try { await this.stopBGM() // Unload all cached SE sounds for (const [key, sound] of this.seCache.entries()) { await sound.unloadAsync() this.seCache.delete(key) } this.isInitialized = false console.log('🎵 AudioManager disposed') } catch (error) { console.error('❌ AudioManager disposal failed:', error) } }frontend/app/solo.tsx (2)
182-195: Consider distinguishing between zero scores and missing scores.Displaying
'0'whenmax_score,avg_score, orlast_scoreisnullmight mislead users into thinking they scored zero, when in reality the score failed to compute or is unavailable.Consider either:
- Keep
'N/A'to clearly indicate missing data- Add additional context like
'0 (failed)'or show an error message when scores are null- Use a different visual indicator (e.g.,
'--'or'?')Apply this diff if you prefer the first option:
- {scoreResult.max_score?.toFixed(1) ?? '0'} + {scoreResult.max_score?.toFixed(1) ?? 'N/A'} ... - {scoreResult.avg_score?.toFixed(1) ?? '0'} + {scoreResult.avg_score?.toFixed(1) ?? 'N/A'} ... - {scoreResult.last_score?.toFixed(1) ?? '0'} + {scoreResult.last_score?.toFixed(1) ?? 'N/A'}
61-62: Consider removing or reducing console logs for production.The file contains multiple
console.logstatements for debugging. While helpful during development, excessive logging in production can impact performance and expose internal details.Consider:
- Using a logging utility that respects environment (e.g., only log in
__DEV__)- Reducing log verbosity for production builds
- Using a structured logging library
Example:
if (__DEV__) { console.log('📹 Using video.path directly:', videoUri) }frontend/app/battle-game.tsx (1)
176-177: Consider adding defeat audio feedback.Currently, victory plays a special BGM but defeat only stops the music. For better UX symmetry and player feedback, consider adding a defeat sound effect or BGM.
Example:
if (newUserHp <= 0) { setIsGameOver(true) setWinner('enemy') addLog('やられた...') audioManager.stopBGM() + await audioManager.playSE(require('@/assets/sounds/se/se_defeat.wav')) return }frontend/utils/score.ts (2)
42-48: Reduce console logging for production.The file contains extensive console logging with emojis and multiple log statements per operation. While useful for debugging, this is excessive for production and may impact performance.
Consider:
- Use environment-based logging (only log in development)
- Reduce the number of log statements
- Use a structured logging library
Example refactor:
export const uploadVideoForScore = async ( videoUri: string, mode: Mode ): Promise<string> => { if (__DEV__) { console.log('Uploading video:', { videoUri, mode }) } const formData = new FormData() const filename = `video_${Date.now()}.mp4` formData.append('file', { uri: videoUri, type: 'video/mp4', name: filename, } as any) formData.append('mode', mode as string) try { const response = await fetch(SCORE_API_URL, { method: 'POST', body: formData, }) if (__DEV__) { console.log('API response:', { status: response.status, statusText: response.statusText }) } if (!response.ok) { const errorText = await response.text() console.error('API Error:', errorText) throw new Error(`HTTP ${response.status}: ${errorText}`) } const responseText = await response.text() if (__DEV__) { try { const parsed = JSON.parse(responseText) console.log('Parsed response:', parsed) } catch (e) { console.warn('Response is not valid JSON') } } return responseText } catch (error) { console.error('Upload failed:', error) throw error } }Also applies to: 79-119
70-74: Document the FormData type cast.The
as anycast bypasses TypeScript's type checking for the file blob. While this is common in React Native due to platform differences, adding a comment explaining why it's necessary would improve code maintainability.formData.append('file', { uri: videoUri, type: 'video/mp4', name: filename, -} as any) +} as any) // React Native FormData accepts uri-based file objectsAlternatively, if you want stronger typing, create a type definition:
interface ReactNativeFile { uri: string type: string name: string } formData.append('file', { uri: videoUri, type: 'video/mp4', name: filename, } as unknown as ReactNativeFile)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
frontend/assets/images/dumbbell.pngis excluded by!**/*.pngfrontend/assets/images/icon.pngis excluded by!**/*.pngfrontend/assets/sounds/bgm/bgm.wavis excluded by!**/*.wavfrontend/assets/sounds/bgm/fight.mp3is excluded by!**/*.mp3frontend/assets/sounds/bgm/victory.mp3is excluded by!**/*.mp3frontend/assets/sounds/se/se_hit_001.wavis excluded by!**/*.wavfrontend/assets/sounds/se/se_select_002.wavis excluded by!**/*.wavfrontend/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
frontend/app.json(2 hunks)frontend/app/_layout.tsx(1 hunks)frontend/app/battle-game.tsx(9 hunks)frontend/app/battle-result.tsx(1 hunks)frontend/app/battle.tsx(3 hunks)frontend/app/create-user.tsx(1 hunks)frontend/app/index.tsx(7 hunks)frontend/app/login-screen.tsx(1 hunks)frontend/app/solo.tsx(5 hunks)frontend/components/battle/EnemyCard.tsx(1 hunks)frontend/package.json(1 hunks)frontend/utils/audio.ts(1 hunks)frontend/utils/score.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/**/*.{ts,tsx}: Frontend Expo Router screens live underfrontend/app/(e.g.,frontend/app/(tabs)/_layout.tsx), shared UI infrontend/components/andfrontend/components/ui/, assets infrontend/assets/images/, and utilities/hooks infrontend/hooks/andfrontend/constants/
Expo SDK 54 is in use; match the documented Node/Java versions for native tooling
Files:
frontend/app/login-screen.tsxfrontend/app/create-user.tsxfrontend/utils/audio.tsfrontend/app/battle-game.tsxfrontend/app/battle-result.tsxfrontend/app/_layout.tsxfrontend/app/solo.tsxfrontend/components/battle/EnemyCard.tsxfrontend/app/index.tsxfrontend/app/battle.tsxfrontend/utils/score.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript; prefer explicit return types on exported functions and components
Files:
frontend/app/login-screen.tsxfrontend/app/create-user.tsxfrontend/utils/audio.tsfrontend/app/battle-game.tsxfrontend/app/battle-result.tsxfrontend/app/_layout.tsxfrontend/app/solo.tsxfrontend/components/battle/EnemyCard.tsxfrontend/app/index.tsxfrontend/app/battle.tsxfrontend/utils/score.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indentation, single quotes, and trailing commas per ESLint/Prettier defaults
Files:
frontend/app/login-screen.tsxfrontend/app/create-user.tsxfrontend/utils/audio.tsfrontend/app/battle-game.tsxfrontend/app/battle-result.tsxfrontend/app/_layout.tsxfrontend/app/solo.tsxfrontend/components/battle/EnemyCard.tsxfrontend/app/index.tsxfrontend/app/battle.tsxfrontend/utils/score.ts
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
React components: Use PascalCase filenames (e.g.,
HelloWave.tsx)
Files:
frontend/app/login-screen.tsxfrontend/app/create-user.tsxfrontend/app/battle-game.tsxfrontend/app/battle-result.tsxfrontend/app/_layout.tsxfrontend/app/solo.tsxfrontend/components/battle/EnemyCard.tsxfrontend/app/index.tsxfrontend/app/battle.tsx
frontend/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Route files must follow Expo Router patterns (e.g.,
app/modal.tsx)
Files:
frontend/app/login-screen.tsxfrontend/app/create-user.tsxfrontend/app/battle-game.tsxfrontend/app/battle-result.tsxfrontend/app/_layout.tsxfrontend/app/solo.tsxfrontend/app/index.tsxfrontend/app/battle.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend Expo Router screens live under `frontend/app/` (e.g., `frontend/app/(tabs)/_layout.tsx`), shared UI in `frontend/components/` and `frontend/components/ui/`, assets in `frontend/assets/images/`, and utilities/hooks in `frontend/hooks/` and `frontend/constants/`
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: PRs should include: concise summary, linked issue/ticket, test or lint command results, and screenshots/video for UI changes (mobile + web when relevant)
📚 Learning: 2025-12-08T15:54:59.341Z
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: Applies to frontend/**/*.{ts,tsx} : Frontend Expo Router screens live under `frontend/app/` (e.g., `frontend/app/(tabs)/_layout.tsx`), shared UI in `frontend/components/` and `frontend/components/ui/`, assets in `frontend/assets/images/`, and utilities/hooks in `frontend/hooks/` and `frontend/constants/`
Applied to files:
frontend/app/_layout.tsxfrontend/app.jsonfrontend/package.jsonfrontend/app/index.tsxfrontend/app/battle.tsx
📚 Learning: 2025-12-08T15:54:59.341Z
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: Applies to frontend/app/**/*.{ts,tsx} : Route files must follow Expo Router patterns (e.g., `app/modal.tsx`)
Applied to files:
frontend/app/_layout.tsx
📚 Learning: 2025-12-08T15:54:59.341Z
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: Applies to frontend/**/*.{ts,tsx} : Expo SDK 54 is in use; match the documented Node/Java versions for native tooling
Applied to files:
frontend/app.jsonfrontend/package.json
📚 Learning: 2025-12-08T15:54:59.341Z
Learnt from: CR
Repo: befoafsuger/rizap2025 PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T15:54:59.341Z
Learning: Frontend: from `frontend/`, run `npm install` then `npm run start` (Expo dev server), or platform targets via `npm run ios` / `npm run android` / `npm run web`. `npm run lint` uses Expo's ESLint preset. `npm run reset-project` wipes starter code—use cautiously.
Applied to files:
frontend/package.json
🧬 Code graph analysis (5)
frontend/app/battle-game.tsx (1)
frontend/utils/audio.ts (1)
audioManager(165-165)
frontend/app/_layout.tsx (2)
frontend/app/settings.tsx (1)
SettingScreen(5-22)frontend/components/common/ScreenHeader.tsx (1)
ScreenHeader(8-14)
frontend/components/battle/EnemyCard.tsx (3)
frontend/components/themed/Text.tsx (1)
Text(4-11)frontend/components/themed/TextInput.tsx (1)
TextInput(4-11)frontend/components/common/ScreenHeader.tsx (1)
ScreenHeader(8-14)
frontend/app/index.tsx (1)
frontend/utils/audio.ts (1)
audioManager(165-165)
frontend/app/battle.tsx (2)
frontend/components/themed/Text.tsx (1)
Text(4-11)frontend/components/common/ScreenHeader.tsx (1)
ScreenHeader(8-14)
🪛 GitHub Actions: Lint and Format
frontend/app/login-screen.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/create-user.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/utils/audio.ts
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/battle-game.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/battle-result.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/_layout.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app.json
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/package.json
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/solo.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/components/battle/EnemyCard.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/index.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/app/battle.tsx
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
frontend/utils/score.ts
[error] 1-1: pnpm install --frozen-lockfile failed: PNPM_OUTDATED_LOCKFILE. The lockfile is not up to date with package.json. Specifiers in the lockfile don't match: 1 dependencies added (expo-av@~16.0.8); 3 dependencies mismatched (expo ~54.0.27 vs ~54.0.29, expo-constants ~18.0.11 vs ~18.0.12, expo-router ~6.0.17 vs ~6.0.19). Run 'pnpm install' without --frozen-lockfile or update the lockfile and commit changes.
🔇 Additional comments (18)
frontend/app/create-user.tsx (1)
67-67: LGTM—consistent layout adjustment.The paddingTop addition aligns with similar UI spacing updates across multiple screens in this PR (login-screen, battle-result, solo).
frontend/app/login-screen.tsx (1)
76-76: LGTM—consistent layout adjustment.Matches the paddingTop pattern applied across other screens.
frontend/components/battle/EnemyCard.tsx (1)
1-3: LGTM—aligns with themed UI pattern.Switching to the themed Text component ensures the 'DotGothic16-Regular' font is applied consistently across the battle UI.
frontend/utils/audio.ts (3)
13-28: LGTM—solid initialization with proper guards.The initialization method correctly sets audio mode, loads persisted settings, guards against double-initialization, and handles errors gracefully.
30-80: LGTM—BGM control methods are well-implemented.The playBGM, stopBGM, pauseBGM, and resumeBGM methods properly manage the BGM lifecycle with good error handling and logging.
102-162: LGTM—settings management is robust.Volume clamping, toggle methods, and AsyncStorage persistence are all correctly implemented with appropriate error handling.
frontend/app/_layout.tsx (1)
36-48: LGTM—explicit route declarations improve clarity.The explicit Stack.Screen declarations with global
headerShown: falseand the modal override are properly configured.frontend/app/battle-result.tsx (1)
44-44: LGTM—consistent layout adjustment.The paddingTop addition matches the UI spacing pattern applied across other screens in this PR.
frontend/app.json (1)
35-35: Asset file exists. The./assets/images/dumbbell.pngfile is present in the repository at the correct path and can be safely referenced in app.json.frontend/package.json (1)
23-23: expo-av@16.0.8 is compatible with Expo SDK 54 and the Audio API usage infrontend/utils/audio.tsmatches documented patterns.However, note that
expo-avis deprecated and scheduled for removal in a future SDK version; plan a migration toexpo-audioandexpo-videowhen feasible.frontend/app/solo.tsx (1)
87-92: Verify that 3 seconds is sufficient for exercise detection.The recording duration has been reduced from 10 seconds to 3 seconds. This is quite brief for capturing exercise movements, especially for exercises like squats or push-ups where users need time to perform the motion.
Confirm that:
- The scoring algorithm can accurately detect exercises within 3 seconds
- Users have adequate time to start the exercise after pressing the button
- This duration works across all supported modes (AUTO, RUNNING, SQUAT, PUSHUP)
You may want to add user feedback or testing data to validate this change doesn't negatively impact the scoring experience.
frontend/app/battle.tsx (2)
6-6: Verify the intentional replacement of ScreenHeader with Text.The code replaces
ScreenHeaderwith a themedTextcomponent and inline styles. WhileScreenHeaderstill exists in the codebase (as shown infrontend/components/common/ScreenHeader.tsx), this screen now uses a custom title implementation.Confirm whether:
- This is part of a broader refactoring to remove or deprecate
ScreenHeader- The title styling should remain inline or be extracted to the
ScreenHeadercomponent for consistency- Other screens should follow this same pattern
If this is intentional, the changes look correct. However, if
ScreenHeaderis meant to be the standard header component, consider using it for consistency:-import { Text } from '@/components/themed' +import { ScreenHeader } from '@/components/common/ScreenHeader' ... -<Text style={styles.title}>対戦相手を選ぶ</Text> +<ScreenHeader title="対戦相手を選ぶ" />Also applies to: 41-41, 63-69
60-60: LGTM: Consistent padding adjustment.The
paddingTop: 80change aligns with similar adjustments across multiple screens in this PR (solo.tsx, index.tsx, battle-game.tsx), maintaining visual consistency.frontend/app/index.tsx (2)
65-68: Consider the timing of sound effects relative to navigation.The sound effects are played without
await, meaning navigation happens immediately without waiting for the SE to complete. This might cause the SE to be cut off or not play at all if navigation is fast.Verify that:
- The
audioManager.playSE()implementation handles fire-and-forget correctly- Sound effects actually play before navigation completes
- This timing provides the desired user experience
If SEs are being cut off, consider adding a small delay:
onPress={async () => { audioManager.playSE(require('@/assets/sounds/se/se_select_002.wav')) // Small delay to ensure SE plays await new Promise(resolve => setTimeout(resolve, 100)) router.push('/settings') }}Also applies to: 86-89, 98-101
186-205: LGTM: Well-structured layout styles.The dumbbell container and updated placeholder button styles create a balanced visual layout with consistent sizing (all elements at 60px height, centered alignment).
frontend/app/battle-game.tsx (2)
118-119: Verify the damage calculation change from max_score to avg_score.The damage calculation has been changed from using
max_scoretoavg_score. Sinceavg_scoreis typically lower thanmax_score, this effectively reduces player damage output and could significantly impact game balance and difficulty.Confirm that:
- This change is intentional and has been tested for game balance
- The difficulty adjustment aligns with the desired player experience
- This won't make battles too difficult or frustrating
If this was unintentional and you want to revert to the previous behavior:
-const scoreDamage = score?.avg_score ? Math.floor(score.avg_score) : 0 +const scoreDamage = score?.max_score ? Math.floor(score.max_score) : 0
133-135: LGTM: Appropriate victory BGM sequence.The victory sequence correctly stops the battle BGM before playing a non-looping victory BGM, providing a satisfying audio transition for the player's win.
frontend/utils/score.ts (1)
15-17: LGTM: Well-typed score response fields.The addition of
max_score,avg_score, andlast_scorefields with proper nullable types aligns with the API response structure and is used correctly in gameplay logic (e.g.,battle-game.tsxusesavg_scorefor damage calculation).
| <StatusBar style="auto" /> | ||
| <Stack> | ||
| <Stack.Screen name="index" options={{ headerShown: false }} /> | ||
| <Stack screenOptions={{ headerShown: false }}> | ||
| <Stack.Screen name="index" /> | ||
| <Stack.Screen name="battle" /> | ||
| <Stack.Screen name="battle-game" /> | ||
| <Stack.Screen name="battle-result" /> | ||
| <Stack.Screen name="solo" /> | ||
| <Stack.Screen name="settings" /> | ||
| <Stack.Screen name="create-user" /> | ||
| <Stack.Screen name="login-screen" /> | ||
| <Stack.Screen | ||
| name="modal" | ||
| options={{ presentation: 'modal', title: 'Modal' }} | ||
| options={{ presentation: 'modal', headerShown: true, title: 'Modal' }} | ||
| /> | ||
| </Stack> | ||
| <StatusBar style="light" /> |
There was a problem hiding this comment.
Remove duplicate StatusBar declaration.
StatusBar is declared twice with conflicting styles ("auto" on line 35 and "light" on line 50). The second declaration overrides the first.
Remove the first declaration:
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
- <StatusBar style="auto" />
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
...
</Stack>
<StatusBar style="light" />
</ThemeProvider>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <StatusBar style="auto" /> | |
| <Stack> | |
| <Stack.Screen name="index" options={{ headerShown: false }} /> | |
| <Stack screenOptions={{ headerShown: false }}> | |
| <Stack.Screen name="index" /> | |
| <Stack.Screen name="battle" /> | |
| <Stack.Screen name="battle-game" /> | |
| <Stack.Screen name="battle-result" /> | |
| <Stack.Screen name="solo" /> | |
| <Stack.Screen name="settings" /> | |
| <Stack.Screen name="create-user" /> | |
| <Stack.Screen name="login-screen" /> | |
| <Stack.Screen | |
| name="modal" | |
| options={{ presentation: 'modal', title: 'Modal' }} | |
| options={{ presentation: 'modal', headerShown: true, title: 'Modal' }} | |
| /> | |
| </Stack> | |
| <StatusBar style="light" /> | |
| <Stack screenOptions={{ headerShown: false }}> | |
| <Stack.Screen name="index" /> | |
| <Stack.Screen name="battle" /> | |
| <Stack.Screen name="battle-game" /> | |
| <Stack.Screen name="battle-result" /> | |
| <Stack.Screen name="solo" /> | |
| <Stack.Screen name="settings" /> | |
| <Stack.Screen name="create-user" /> | |
| <Stack.Screen name="login-screen" /> | |
| <Stack.Screen | |
| name="modal" | |
| options={{ presentation: 'modal', headerShown: true, title: 'Modal' }} | |
| /> | |
| </Stack> | |
| <StatusBar style="light" /> |
🤖 Prompt for AI Agents
In frontend/app/_layout.tsx around lines 35 to 50 there are two StatusBar
declarations (style="auto" and style="light"), causing a duplicate/conflicting
declaration; remove the first StatusBar (style="auto") at line ~35 so only the
intended StatusBar (style="light") remains and ensure imports/spacing remain
correct.
| useEffect(() => { | ||
| const init = async () => { | ||
| // AudioManager初期化 | ||
| await audioManager.initialize() | ||
| // ホームBGM再生 | ||
| await audioManager.playBGM(require('@/assets/sounds/bgm/bgm.wav')) | ||
| } | ||
| init() | ||
|
|
||
| return () => { | ||
| // 画面離脱時にBGM停止 | ||
| audioManager.stopBGM() | ||
| } | ||
| }, []) |
There was a problem hiding this comment.
Add error handling for audio initialization.
The audio initialization is async and could fail (e.g., if audio files are missing or audio permissions are denied), but there's no error handling. This could cause silent failures or unhandled promise rejections.
Apply this diff to add error handling:
useEffect(() => {
const init = async () => {
- // AudioManager初期化
- await audioManager.initialize()
- // ホームBGM再生
- await audioManager.playBGM(require('@/assets/sounds/bgm/bgm.wav'))
+ try {
+ // AudioManager初期化
+ await audioManager.initialize()
+ // ホームBGM再生
+ await audioManager.playBGM(require('@/assets/sounds/bgm/bgm.wav'))
+ } catch (error) {
+ console.error('Failed to initialize audio:', error)
+ // Audio failure should not crash the app
+ }
}
init()
return () => {
- // 画面離脱時にBGM停止
- audioManager.stopBGM()
+ try {
+ // 画面離脱時にBGM停止
+ audioManager.stopBGM()
+ } catch (error) {
+ console.error('Failed to stop BGM:', error)
+ }
}
}, [])🤖 Prompt for AI Agents
In frontend/app/index.tsx around lines 12 to 25, the async audio initialization
and play calls lack error handling which can lead to unhandled promise
rejections; wrap the init sequence in a try/catch, log or report the caught
error (e.g., console.error or app logger) and optionally show a user-friendly
fallback/notification, and ensure the cleanup return function defensively checks
that audioManager was initialized before calling stopBGM so stopBGM is safe even
if initialization failed.
| "expo": "~54.0.29", | ||
| "expo-av": "~16.0.8", | ||
| "expo-camera": "^17.0.10", | ||
| "expo-constants": "~18.0.11", | ||
| "expo-constants": "~18.0.12", | ||
| "expo-font": "~14.0.10", | ||
| "expo-haptics": "~15.0.8", | ||
| "expo-image": "~3.0.11", | ||
| "expo-linking": "~8.0.10", | ||
| "expo-router": "~6.0.17", | ||
| "expo-router": "~6.0.19", |
There was a problem hiding this comment.
Critical: Update the lockfile to match package.json changes.
The CI pipeline is failing because the lockfile is outdated after adding expo-av@~16.0.8 and updating expo, expo-constants, and expo-router.
Run the following to fix:
cd frontend
pnpm install
git add pnpm-lock.yaml
git commit -m "chore: update lockfile"🤖 Prompt for AI Agents
In frontend/package.json around lines 22 to 30 the dependencies were changed but
the lockfile was not updated, causing CI failures; run pnpm install in the
frontend folder to regenerate pnpm-lock.yaml, stage the updated pnpm-lock.yaml,
and commit it (e.g., git add frontend/pnpm-lock.yaml && git commit -m "chore:
update lockfile") so the lockfile matches package.json.
|
|
||
| class AudioManager { | ||
| private bgmSound: Audio.Sound | null = null | ||
| private seCache: Map<string, Audio.Sound> = new Map() |
There was a problem hiding this comment.
Remove unused seCache field or implement SE caching.
The seCache Map is declared but never used. The playSE method creates a new Sound instance each time without caching.
Either implement SE caching:
async playSE(source: any) {
if (!this.isSeEnabled) return
try {
+ const cacheKey = JSON.stringify(source)
+ let sound = this.seCache.get(cacheKey)
+
+ if (!sound) {
- const { sound } = await Audio.Sound.createAsync(source, {
+ const { sound: newSound } = await Audio.Sound.createAsync(source, {
volume: this.seVolume,
})
+ sound = newSound
+ this.seCache.set(cacheKey, sound)
+ }
+
+ await sound.replayAsync()
- await sound.playAsync()
- sound.setOnPlaybackStatusUpdate((status) => {
- if (status.isLoaded && status.didJustFinish) {
- sound.unloadAsync()
- }
- })
console.log('🔊 SE played')
} catch (error) {
console.error('❌ SE play failed:', error)
}
}Or remove the unused field:
- private seCache: Map<string, Audio.Sound> = new Map()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private seCache: Map<string, Audio.Sound> = new Map() | |
| // Remove the unused seCache declaration if SE caching is not implemented | |
| // Original line removed: | |
| // private seCache: Map<string, Audio.Sound> = new Map() |
🤖 Prompt for AI Agents
In frontend/utils/audio.ts around line 6, the seCache Map is declared but never
used; either remove the unused seCache field or implement SE caching in playSE:
update playSE to check seCache for the given key and reuse a loaded Audio.Sound
if present, otherwise create and load the Sound, store it in seCache, and handle
unload/cleanup on failure; if you choose removal, delete the seCache declaration
and any related imports/usages to keep the module tidy.
|
|
||
| const SCORE_API_URL = | ||
| 'https://sponsored-media-compatibility-captured.trycloudflare.com/api/debug' | ||
| 'https://rizap.snowing.moe/api/score' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's understand the context of this file and the API endpoint
cat -n frontend/utils/score.tsRepository: befoafsuger/rizap2025
Length of output: 4352
🏁 Script executed:
# Search for references to this API endpoint or similar patterns
rg "rizap\.snowing\.moe" --type ts --type tsxRepository: befoafsuger/rizap2025
Length of output: 92
🏁 Script executed:
# Check for any environment configuration files that might define API endpoints
fd -e env -e config -e json | head -20Repository: befoafsuger/rizap2025
Length of output: 227
🏁 Script executed:
# Look for other API endpoint definitions in the codebase
rg "https://" frontend/ --type ts --type tsx -A 2 -B 2Repository: befoafsuger/rizap2025
Length of output: 92
🏁 Script executed:
# Check if there are environment or configuration files that might define API URLs
rg "SCORE_API_URL|snowing\.moe" . --type-listRepository: befoafsuger/rizap2025
Length of output: 454
🏁 Script executed:
# Search more carefully for any config or environment references
rg "SCORE_API_URL|snowing" . -g "*.ts" -g "*.tsx" -g "*.js" -g "*.json"Repository: befoafsuger/rizap2025
Length of output: 401
🏁 Script executed:
# Check if there's any documentation about this API endpoint
fd -e md | xargs rg "rizap|snowing|api/score" 2>/dev/nullRepository: befoafsuger/rizap2025
Length of output: 49
🏁 Script executed:
# Look at package.json to understand project setup and any API configuration
cat frontend/package.jsonRepository: befoafsuger/rizap2025
Length of output: 1826
Move the API endpoint to environment-based configuration instead of hardcoding it.
The API URL is currently hardcoded as 'https://rizap.snowing.moe/api/score' on line 4. This prevents switching between development, staging, and production endpoints without modifying the source code. Define SCORE_API_URL using an environment variable or configuration file (e.g., via .env or Expo's app config) to support different deployment environments.
🤖 Prompt for AI Agents
In frontend/utils/score.ts around line 4 the API URL is hardcoded as
'https://rizap.snowing.moe/api/score'; replace this with a configurable constant
that reads from an environment/config source (e.g., process.env.SCORE_API_URL or
Expo/React Native config) and fall back to a sensible default if not set, update
any imports/usage to reference the new SCORE_API_URL constant, and ensure the
project .env or app config is updated with the appropriate values for
dev/staging/production and that the build/start scripts load these env
variables.
| // // ダミーデータ生成 | ||
| // const generateMockScore = (mode: Mode): ScoreResponse => { | ||
| // const baseScore = Math.random() * 100 // 0-100のランダムスコア | ||
| // const variation = Math.random() * 20 - 10 // ±10の変動 | ||
|
|
||
| // return { | ||
| // frames_processed: 90, | ||
| // input_fps: 30.0, | ||
| // duration_seconds: 3.0, | ||
| // mode: mode, | ||
| // max_score: Math.min(100, Math.max(0, baseScore + variation)), | ||
| // avg_score: Math.min(100, Math.max(0, baseScore)), | ||
| // last_score: Math.min(100, Math.max(0, baseScore - variation)), | ||
| // processing_time_seconds: 0.5 + Math.random() * 0.5, | ||
| // } | ||
| // } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove commented-out code.
The mock data generation logic is fully commented out. Since USE_MOCK is set to false and the real API is now in use, this code should be removed entirely to improve maintainability.
Apply this diff to remove the dead code:
-// // ダミーデータ生成
-// const generateMockScore = (mode: Mode): ScoreResponse => {
-// const baseScore = Math.random() * 100 // 0-100のランダムスコア
-// const variation = Math.random() * 20 - 10 // ±10の変動
-
-// return {
-// frames_processed: 90,
-// input_fps: 30.0,
-// duration_seconds: 3.0,
-// mode: mode,
-// max_score: Math.min(100, Math.max(0, baseScore + variation)),
-// avg_score: Math.min(100, Math.max(0, baseScore)),
-// last_score: Math.min(100, Math.max(0, baseScore - variation)),
-// processing_time_seconds: 0.5 + Math.random() * 0.5,
-// }
-// }
...
- // // ダミーモード
- // if (USE_MOCK) {
- // // 処理時間をシミュレート(1-2秒)
- // await new Promise((resolve) =>
- // setTimeout(resolve, 1000 + Math.random() * 1000)
- // )
-
- // const mockData = generateMockScore(mode)
- // console.log('Mock score generated:', mockData)
-
- // return JSON.stringify(mockData)
- // }Alternatively, if you want to keep the mock functionality for future development:
- Move it to a separate file (e.g.,
score.mock.ts) - Use environment variables to conditionally import it
- Remove the
USE_MOCKconstant entirely
Also applies to: 50-61
🤖 Prompt for AI Agents
frontend/utils/score.ts around lines 21 to 36 (and also 50 to 61): the file
contains commented-out mock-data generation code that is dead since USE_MOCK is
false and the real API is used; remove these commented blocks to clean up the
file, or if you want to keep the mocks, move the function to a new file (e.g.,
frontend/utils/score.mock.ts), export it there, and conditionally import/use it
via an env flag (remove the USE_MOCK constant from this file), then update any
imports or usage sites to reference the new module.
Summary by CodeRabbit
Release Notes
New Features
UI/UX Improvements
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.