Straw full-audit fix batch (2026-07-04). Confirmed HIGH/MED findings:
- Updater self-brick: drop the fdroid.sulkta.com leaf+E7 SPKI pins (LE
rotates the leaf every ~90d + intermediates from a pool → a routine
renewal was guaranteed to miss both pins and silently kill the only
in-app update path). System-CA validation + the install-time APK
signature gate remain. Also distinguish "index unreachable" from "up to
date", add a 30s callTimeout + bounded index read, guard the API-26
NotificationChannel, and setOnlyAlertOnce.
- Request POST_NOTIFICATIONS at runtime so A13+ update/media notifications
actually post (was declared but never requested → silent no-op).
- isDebuggable=false on the shipped debug variant (closes ADB run-as dump
of watch/search history + subs; no package-id cutover).
- Crash fixes: distinctBy(url) on moreFromChannel + related (duplicate
LazyColumn key); back-handling driven by live nav depth so it survives
moveTaskToBack on A12+; PlaylistsStore per-store caps + off-main
hydration (hostile import ANR); SponsorBlock staleness fence via
NowPlaying.refreshIfCurrent.
- CacheCap.nearest() snaps up to the nearest finite cap (defaults no longer
resolve to Unlimited/100k on fresh installs).
- RYD/SB FFI shims wrap the uniffi call (swallow a core panic per contract).
- Rust wrapper: release panic="unwind" (was "abort", which defeated UniFFI
catch_unwind → any core panic = whole-app SIGABRT) + a 60s wall-clock
timeout on every blocking extractor call (unblocks the caller, bounds
thread pileup). Pairs with the strawcore-core reliability fix.
stream_info(url) UniFFI suspend fn replaces NewPipeExtractor's
StreamInfo.getInfo() for both VideoDetailViewModel and PlayerViewModel.
One Rust round-trip drives the detail screen render AND the player's
resolve(). The VideoDetailUiState.info field cached on detail load is
reused by the Download dialog so we don't refetch.
Deferred to U-3.5:
- like_count (rustypipe's player() doesn't surface engagement data;
a separate query is needed)
- related (player() doesn't include 'up next'; comes from a separate
endpoint). Kotlin gets empty list for now — RelatedRow handles it.
Type quirks vs my initial guesses (caught by cargo check):
- details.duration is u32, not Option<u32>
- channel is split into channel_id + channel_name, not a struct
- like_count doesn't exist at this query depth
- VideoFormat::Webm (lowercase mb), VideoCodec::Avc1 (not H264)
- video_only is a separate vec (video_only_streams), not a bool flag