You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ViewChapter.tsx keeps per-verse UI state in a plain object (verseStates) toggled by a button (:108). No persistence, no engine.
No hook coordinates record → stop → persist → play.
Problems / why (anti-patterns to avoid)
PR #82src/hooks/useVerseAudio.ts (316 lines) is the anti-pattern:
Fat hook: imperative IWaveformRef refs, react-native-fs file moves, Alert.alert dialogs, permission prompts, and DB writes (upsertRecording/deleteRecording) all inline in startRecord/stopRecord.
State is split across useState + multiple useRefs (recordingStarted, tempRecordingPath, finalRecordingPath) with ad-hoc transitions → untestable, race-prone.
Tightly coupled to the Simform library types (PlayerState, RecorderState, FinishMode).
Target design
A small, deterministic state machine driven by typed events, with side effects injected (so it is unit-testable without native modules):
Current state on main
ViewChapter.tsxkeeps per-verse UI state in a plain object (verseStates) toggled by a button (:108). No persistence, no engine.Problems / why (anti-patterns to avoid)
PR #82
src/hooks/useVerseAudio.ts(316 lines) is the anti-pattern:IWaveformRefrefs,react-native-fsfile moves,Alert.alertdialogs, permission prompts, and DB writes (upsertRecording/deleteRecording) all inline instartRecord/stopRecord.useState+ multipleuseRefs (recordingStarted,tempRecordingPath,finalRecordingPath) with ad-hoc transitions → untestable, race-prone.PlayerState,RecorderState,FinishMode).Target design
A small, deterministic state machine driven by typed events, with side effects injected (so it is unit-testable without native modules):
useVerseAudiohook wires the reducer to the recorder (Recording engine on expo-audio #95) and player (Playback engine on expo-audio + waveform decision #96) and persists via repository functions (Multi-take support (take_number / is_latest semantics) #98) andaudioStorage(Audio storage on expo-file-system (net-new) #94). Permission/Alertdecisions live in the screen, not the hook.bible_text_id), not from in-memory refs.Implementation steps
src/hooks/verseAudioReducer.ts(pure) + tests covering each transition (includingERRORandDELETE).src/hooks/useVerseAudio.tsthat composes the reducer + recorder (Recording engine on expo-audio #95) + player (Playback engine on expo-audio + waveform decision #96) + persistence (Multi-take support (take_number / is_latest semantics) #98), with injectable dependencies.STOP: move temp file torecordingPath(id)(Audio storage on expo-file-system (net-new) #94) and insert a take (Multi-take support (take_number / is_latest semantics) #98); set staterecorded.ViewChapter.tsx, replacing the mock toggle.react-native-fs, no Simform types, no DB writes inside reducer.Acceptance criteria
react-native-fsand no@simform_solutions/*imports anywhere in the audio path.ViewChapter.tsxuses the hook instead of the mock toggle.Test plan / verification
EXPO_PUBLIC_API_BASE_URL=http://localhost:9999viajest.env.cjs; no live API required.expo-audio/expo-file-system(Expo test infra (jest-expo mocks, gate fluent-api.test.ts) #111).FLUENT_USER_EMAIL.Out of scope
Links
src/hooks/useVerseAudio.ts