Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
0c00263
[#49]: Add expo-audio and expo-crypto for draft recording
fel-cesar Jul 2, 2026
156485c
[#49]: Wire recordings table with take/is_latest invariants
fel-cesar Jul 2, 2026
16373c7
[#49]: Persist paused-take marker for recording recovery
fel-cesar Jul 2, 2026
0dee736
[#49]: Build Record tab with draft recording and playback
fel-cesar Jul 2, 2026
c7562a7
[#49]: Document drafting page and recording wiring
fel-cesar Jul 2, 2026
667c0a4
[#49]: Sharpen idle record button to circular icon-only affordance
fel-cesar Jul 2, 2026
9ccfe4d
[#49]: Polish Record tab live and review states
fel-cesar Jul 2, 2026
73509ad
[#49]: Lock verse chevrons while recording
fel-cesar Jul 2, 2026
7128bfd
[#49]: Track recording elapsed by wall clock and show centiseconds
fel-cesar Jul 2, 2026
8d5dd40
[#49]: Tidy verse-nav spacing and drop dead waveform styles
fel-cesar Jul 2, 2026
c21116a
[#49]: Extract draft playback into a dedicated useDraftPlayback hook
fel-cesar Jul 2, 2026
595896f
[#49]: Wire draft playback into the Record tab review controls
fel-cesar Jul 2, 2026
cc07aeb
[#49]: Add expo-file-system for durable recording storage
fel-cesar Jul 2, 2026
56581b5
[#49]: Add durable recording storage layout and file helpers
fel-cesar Jul 2, 2026
68d3b29
[#49]: Persist recording attribution and relative storage keys
fel-cesar Jul 2, 2026
bac7e4b
[#49]: Move committed takes into durable store in useRecorder
fel-cesar Jul 2, 2026
cbc180f
[#49]: Wire attribution context into the Record tab
fel-cesar Jul 2, 2026
bc87ec2
[#49]: Capture takes into durable dir and unlink discarded partials
fel-cesar Jul 2, 2026
2516d1a
[#49]: Extract a generic recorder hook behind a verse adapter
fel-cesar Jul 2, 2026
f2e3a6e
[#49]: Resolve playback uri through the adapter ref
fel-cesar Jul 2, 2026
e14e51e
[#49]: Defer Record tab paint to avoid an idle-to-review flash
fel-cesar Jul 2, 2026
544b789
[#49]: Reorganize Record tab into a record module
fel-cesar Jul 3, 2026
e92c11a
[#49]: Harden getPausedTake bibleTextId validation
fel-cesar Jul 3, 2026
6c85585
[#49]: Move RecordingWaveform bar styles out of inline JSX
fel-cesar Jul 3, 2026
1887bdf
[#49]: Add expo-asset peer dep and config plugin for expo-audio
fel-cesar Jul 3, 2026
123a11d
[#49]: Clear bibleTextId before verse lookup resolves
fel-cesar Jul 3, 2026
b9d5751
[#49]: Guard discard-then-continue handlers in record tab guards
fel-cesar Jul 3, 2026
ec1e31a
[#49]: Guard navigation leave while actively recording
fel-cesar Jul 3, 2026
82eab1a
[#49]: Unlink durable file when insertRecording fails after move
fel-cesar Jul 3, 2026
718ea65
[#49]: Handle pause, resume, and stop recorder failures in RecordTab
fel-cesar Jul 3, 2026
64efd0e
[#49]: Block resume on rehydrated paused takes without live recorder
fel-cesar Jul 3, 2026
5126f7f
[#49]: Harden useRecorder stop and delete error handling
fel-cesar Jul 3, 2026
fe0bb3c
[#49]: Reject reserved segments in recording storage paths
fel-cesar Jul 3, 2026
b6a07e4
Merge branch 'main' into 49-build-record-tab-with-draft-recording-and…
fel-cesar Jul 3, 2026
1c219e2
[#49]: Guard tab switches during recording and fix discard cleanup
fel-cesar Jul 6, 2026
9bb7fe5
[#49]: Toast when verse has no bible_text id on record
fel-cesar Jul 7, 2026
8f61958
[#49]: Keep recording paused across app backgrounding
fel-cesar Jul 7, 2026
24de04f
[#49]: Make partial recordings resilient to app kills
fel-cesar Jul 7, 2026
87773b8
[#49]: Document kill-resilient recording design
fel-cesar Jul 7, 2026
2dce913
[#49]: Persist recovery marker at recording start
fel-cesar Jul 7, 2026
d7a389e
[#49]: Resolve verse navigation from a chapter assignment
fel-cesar Jul 7, 2026
149de3f
[#49]: Prompt to recover a killed recording from home
fel-cesar Jul 7, 2026
200544b
[#49]: Document home recovery prompt and kill persistence
fel-cesar Jul 7, 2026
135b54b
[#49]: Derive recording duration from audio at commit time
fel-cesar Jul 7, 2026
3025c08
[#49]: Document audio-derived duration at commit time
fel-cesar Jul 7, 2026
700c95c
[#49]: Remove the documentation for kill-resilient partial recordings.
fel-cesar Jul 7, 2026
31e0c24
[#49]: Clean up partial merged AAC file on concat failure
fel-cesar Jul 7, 2026
17712e3
Merge branch 'main' into 49-build-record-tab-with-draft-recording-and…
fel-cesar Jul 7, 2026
8e1ae1a
[#49]: Bump Expo SDK 56 patch versions
fel-cesar Jul 7, 2026
357235a
Merge branch 'main' into 49-build-record-tab-with-draft-recording-and…
fel-cesar Jul 9, 2026
0f9ba9e
[#49]: Add draft review scrubbing via interactive waveform
fel-cesar Jul 9, 2026
609569c
[#49]: Generalize draft playback into useAudioPlayback
fel-cesar Jul 9, 2026
67b68ed
[#49]: Reuse UseAudioPlaybackApi for recorder playback
fel-cesar Jul 9, 2026
ee562e2
[#49]: Remove interactive scrub UI from review waveform
fel-cesar Jul 10, 2026
36ff481
Merge branch 'main' into 49-build-record-tab-with-draft-recording-and…
fel-cesar Jul 10, 2026
0b2c52e
[#49]: Propagate initialVerse changes in DraftingProvider
fel-cesar Jul 10, 2026
3ab266d
Merge branch 'main' into 49-build-record-tab-with-draft-recording-and…
fel-cesar Jul 10, 2026
a5cd1e0
[#96]: Capture recorder metering into a live level buffer
fel-cesar Jul 14, 2026
f8b2687
[#96]: Render live waveform bars from metering levels
fel-cesar Jul 14, 2026
ee8a99c
[#96]: Document the waveform rendering decision
fel-cesar Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/rules/project-overview.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Offline-first **Expo SDK 56** (React Native **0.85**, React **19.2.3**) companio

- Entry: `App.tsx` → `initializeDatabase()` → `syncAllData()` → `AppNavigator`
- App config: `app.config.ts` (EAS project ID, plugins, package id)
- Screens: `src/app/tabs/` (`ProjectList`, `ViewProject`, `ViewChapter`)
- Screens: `src/app/tabs/` (`ProjectList`, `ViewProject`, `drafting/DraftingPage` + `RecordTab`)
- API: `src/services/api.ts` (`FluentAPI`)
- Sync: `src/services/sync.ts`
- DB writes: `src/db/repository.ts` | reads: `src/db/queries.ts`
Expand Down
8 changes: 8 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ const config: ExpoConfig = {
],
'./plugins/withRNScreensFragmentFactory',
'expo-secure-store',
'expo-asset',
[
'expo-audio',
{
microphonePermission:
'Fluent needs access to your microphone so translators can record verse drafts.',
},
],
],
};

Expand Down
6 changes: 4 additions & 2 deletions docs/AGENT_ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Quick map for Cursor agents, other coding tools, and new contributors. Verified
| Path | Purpose |
|------|---------|
| [`App.tsx`](../App.tsx) | Root: DB init + initial `syncAllData`, then navigator |
| [`src/app/tabs/`](../src/app/tabs/) | Screens: `ProjectList`, `ViewProject`, `ViewChapter` |
| [`src/app/tabs/`](../src/app/tabs/) | Screens: `ProjectList`, `ViewProject`, `drafting/DraftingPage` (parent shell stub for #47) with `RecordTab` (#49) |
| [`src/navigation/`](../src/navigation/) | Stack navigator |
| [`src/services/api.ts`](../src/services/api.ts) | HTTP client (`FluentAPI`) — see [api-client-standard.md](guides/api-client-standard.md) |
| [`src/services/sync.ts`](../src/services/sync.ts) | Sync orchestration, retries, KV counts |
Expand Down Expand Up @@ -209,7 +209,9 @@ When adding features: mock `op-sqlite`, navigation, and sync in screen tests fol
- [x] Dependabot reviewers: `eten-tech-foundation/fluent-admin`
- [x] Mock or gate `fluent-api.test.ts` for offline CI
- [ ] Align `format:check` glob with `format` or document intentionally narrow check
- [ ] Wire `recordings` table to actual audio capture/upload
- [x] Wire `recordings` table to actual audio capture (issue #49 — upload still pending)
- [ ] Upload/sync draft recordings to the Fluent server (`sync_status='pending'` rows)
- [ ] Replace parent drafting shell stub with full #47 implementation (chapter audio player, tab persistence)

## Related docs

Expand Down
29 changes: 29 additions & 0 deletions docs/guides/waveform-decision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Waveform decision

How the record/review waveform is rendered in Fluent Mobile, and why. Refs GitHub [#96](https://github.com/eten-tech-foundation/fluent-mobile/issues/96).

## Decision

Render the waveform in React from `expo-audio` data. **Do not** add a third-party waveform native module.

- **Rejected:** [`@simform_solutions/react-native-audio-waveform`](https://github.com/SimformSolutionsPvtLtd/react-native-audio-waveform) (see [PR #82](https://github.com/eten-tech-foundation/fluent-mobile/pull/82)) — a bare-RN native module that complicates CNG prebuild / EAS builds and couples playback to a widget via imperative refs. Disproportionate for a level/progress visualization.
- Any future third-party waveform must be Expo config-plugin compatible.

## Approach

| State | Source | Component |
| ----- | ------ | --------- |
| Live recording | `expo-audio` recorder **metering** (`isMeteringEnabled`), sampled on the recorder tick and normalized to `0..1` | `RecordingWaveform` (recording/paused) |
| Review playback | position-driven progress fill from `positionMs` / `durationMs` | `RecordingWaveform` (review) — delivered in [#176](https://github.com/eten-tech-foundation/fluent-mobile/issues/176) |

This is a **component + hook** change, not a native module — unlike the seekable-remux work in #176, waveform rendering needs no platform APIs.

## Where it lives

- `src/hooks/useRecorder.ts` — `dbfsToLevel()` (dBFS -> `0..1`, floor `-60 dB`); enables `isMeteringEnabled` and samples `recorder.getStatus().metering` on the tick into a bounded `meteringLevels` buffer (newest last, reset on new take, frozen on pause).
- `src/app/tabs/drafting/record/components/RecordingWaveform.tsx` — renders live bars from the `levels` prop.

## Notes

- The live buffer length matches the bar count (`METERING_SAMPLE_CAP` = `LIVE_WAVEFORM_BARS`), so one sample maps to one bar and the row scrolls right as audio comes in.
- Metering is a single level per poll (not a full amplitude array); bars visualize input loudness over time, not an offline sample-accurate waveform.
Loading
Loading