From a multi-agent bug-hunt + adversarial verification pass (0 critical/
high; 15 mediums). Fixed 13; 2 deferred as bigger refactors.
Correctness:
- srs: "Hard" no longer overshoots "Good" for reviewed cards (reps>=2). It
compounded ease AND x1.2; now grows x1.2 off the previous interval only.
+ regression test.
- enrich/dates: stop reading the modal verb "may" as the month May (phantom
calendar events). "may" needs an ordinal/year/date-preposition now.
- enrich/bib: disambiguate colliding BibTeX keys (smith2020, smith2020a, …)
— duplicates corrupted reference-manager imports.
- learn/glossary: junk-term guard used && (dead); now || so all-stopword
terms like "there" are actually skipped.
- transcription/engineImpl.web: don't collapse Whisper's null end-timestamp
to a zero-length [t,t] segment; estimate from the next chunk or window
duration (fixes citation/seam anchors).
- transcription/pipeline: re-check the abort signal AFTER each chunk so
Cancel works on single-chunk audio (it previously still saved).
- db/repo.native: use withExclusiveTransactionAsync for reassign /
deleteCourse / upsertVectors / createFlashcards (withTransactionAsync is
not isolated on a shared connection → interleaved/half-applied writes).
- db/repo.native: don't cache a rejected open/migrate promise — a transient
first-open failure no longer bricks storage for the whole session.
- stores/transcriptsStore: sequence-guard refresh() so overlapping
focus/typing/filter refreshes can't resolve out of order and show stale
results.
- audio/wav: decode WAVE_FORMAT_EXTENSIBLE (0xFFFE) via its SubFormat GUID
+ add 24-bit PCM — common ffmpeg/Windows WAVs no longer hard-fail native
import. + tests.
Performance / footprint:
- audio/decode.native: decode straight to 16kHz (decodeAudioData sampleRate
hint) so the JS side never holds a full-rate buffer or runs the resample
loop — big memory/OOM win on long lectures.
- models/catalog display: Settings model sizes are now backend-aware (the
no-GPU WASM path pulls ~2x fp32 weights; was advertising ~half).
Feature gap:
- download: native exports were silent no-ops. New download.native.ts writes
to cache + opens the share sheet (expo-sharing); transcript/ICS/Anki-CSV/
BibTeX/RIS exports now work on device.
Deferred (bigger): lexical-search recall ceiling (needs a full-corpus rank
path on both repos); triple in-memory copy of the encoded file during
transcribe (media is keyed by a not-yet-existing transcript id).
Validated: tsc clean, 282 tests pass, web export clean (native deps not
bundled), arm64 APK compiles with expo-sharing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On native, importing anything but WAV failed with "Only WAV is supported
on native" — ffmpeg-kit-react-native was retired in 2025 and a real
decoder was left as a follow-up. Since in-app recording is web-only, the
native app's only way to add audio is importing a phone recording — which
is m4a/aac — so transcription was effectively broken on the APK.
Add react-native-audio-api (Software Mansion, maintained) and route
non-WAV files through its standalone decodeAudioData(ArrayBuffer), which
uses platform codecs + bundled FFmpeg. WAV keeps its pure-JS fast path.
The decoded channels go through the existing toMono16k, mirroring the web
AudioContext.decodeAudioData path exactly. Bytes are read locally and
passed as an ArrayBuffer, so it doesn't matter whether the picker yields
file:// or content://. WAV is now sniffed by RIFF/WAVE content, not just
the extension.
The lib's config plugin is intentionally NOT enabled: it only sets up
playback (background audio mode, a media-playback foreground service, mic
permission), none of which decoding needs — and adding those would
contradict the app's on-device/no-extra-permissions stance.
Validated locally (arm64 Docker build): react-native-audio-api compiles
and autolinks against RN 0.85 / new arch; the arm64-v8a APK ships
libreact-native-audio-api.so + libav{codec,format,util}/libswresample.so
+ liboboe.so (48MB -> 58MB). tsc clean, 279 tests pass, web export does
NOT bundle the native dep. Runtime decode still needs an on-device check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strictly opt-in, gated, with the deterministic features as the always-present floor:
- GenerationEngine: WebLLM (Qwen2.5-1.5B, WebGPU, CDN-loaded) + BYO-key cloud
(OpenAI-compatible); native stub. Pure grounding prompt builder (4 tests).
- rag.askLectures: retrieve Phase-1 hits -> grounded prompt -> answer with
citations; refuses when nothing relevant; falls back to search-only when no
engine is available. Never sends raw audio/transcripts — only question + snippets.
- aiStore (BYO key persisted in localStorage on web), Ask screen (answer +
tappable citation chips that jump to the audio + honest "verify" disclaimer),
Settings AI section (engine status + bring-your-own-key form).
279 tests green, 0 tsc errors, web export builds. ROADMAP phases 0-5 complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docker/android.Dockerfile: install nodejs (image ships Bun; ci-android-sign.sh
patches build.gradle with a Node script).
- buffer polyfill: whisper.rn -> safe-buffer requires Node's `buffer`, absent in
Metro/RN. Add `buffer` dep, alias it in metro.config.js, set the global in
_layout. Web export unaffected (verified).
Validated locally end-to-end: assembleRelease BUILD SUCCESSFUL, 117MB APK across
all 4 ABIs with librnwhisper.so, signed (CN=Wisp, O=briggen.dev).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>