6 Commits

Author SHA1 Message Date
NilsBriggen ed1df8986f Bug-hunt sweep: fix 13 verified issues (correctness + perf)
CI / build-apk (push) Successful in 5m59s
CI / test (push) Successful in 17s
CI / deploy-web (push) Successful in 32s
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>
2026-06-15 11:05:17 +02:00
NilsBriggen 7aa8b05971 Native audio: decode m4a/mp3/etc. (not just WAV)
CI / test (push) Successful in 23s
CI / deploy-web (push) Successful in 42s
CI / build-apk (push) Successful in 8m15s
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>
2026-06-15 09:03:31 +02:00
NilsBriggen 7611ada001 feat(phase5): optional generative RAG — ask your lectures, with citations
CI / test (push) Successful in 20s
CI / build-apk (push) Has been skipped
CI / deploy-web (push) Successful in 33s
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>
2026-06-14 15:56:57 +02:00
NilsBriggen 7a1e6b06c1 fix(android): working signed release APK
CI / test (push) Successful in 22s
CI / deploy-web (push) Successful in 34s
CI / build-apk (push) Failing after 3h1m42s
- 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>
2026-06-13 23:59:08 +02:00
NilsBriggen 9f42ee2460 Build Wisp: on-device transcription studio (web + native, one codebase)
CI / test (push) Has been cancelled
CI / deploy-web (push) Has been cancelled
CI / build-apk (push) Has been cancelled
Private, offline speech-to-text that runs Whisper on the user's own device —
free, no account, no per-minute fees. Replaces Otter.ai / Rev.

- Pure, tested engine: chunking, overlap timestamp-stitching, exports
  (SRT/VTT/TXT/MD/JSON), WAV codec, resampler, job queue, model catalog (142 tests).
- Platform-abstracted TranscriptionEngine: transformers.js on web (loaded from
  CDN at runtime to dodge Metro's onnxruntime-web bundling limits), whisper.rn
  on native. Shared pipeline orchestrates decode -> chunk -> transcribe -> stitch.
- Cross-platform StorageRepo (Dexie web / expo-sqlite native), Zod-validated.
- UI: library + search, import, live-progress transcription, synced click-to-seek
  editor, multi-format export; model picker + privacy in settings.
- Web ships as a single-page PWA with COOP/COEP isolation for threaded WASM;
  Docker (nginx) image + Traefik compose for wisp.briggen.dev.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 17:54:21 +02:00
NilsBriggen 97996c9846 Initial commit
Generated by create-expo-app 4.0.0.
2026-06-13 17:05:52 +02:00