vc=42: loop round 4/5 — surgical cleanup, round 7 hit diminishing returns

Round-7 Opus audits explicitly flagged the audit loop as past
diminishing returns. Landing the few real items + one race-order
fix; leaving the comment-hygiene tail for the next time someone
reads the files.

MED
  R7-1  SearchViewModel cache-preview race: vc=41 wrote the cached
        preview to UI before cancelling the prior inFlight, so the
        prior coroutine (already past its ensureActive() gate)
        could reach its terminal _ui.update and clobber the cache
        preview. Moved inFlight?.cancel() above the preview write.
  R7-2  StrawActivity.YT_HOSTS duplicated util.YtUrl.ALLOWED_YT_HOSTS
        — drift risk where one gets a new host and the other
        doesn't. Collapsed StrawActivity.looksLikeYouTube to call
        util.isAllowedYtUrl, picking up the scheme + trailing-dot
        defenses for free.
  R7-3  Dropped dead once_cell dep from rust/strawcore/Cargo.toml.
        Round-4's runtime rewrite uses AtomicBool + Mutex; nothing
        consumes once_cell anymore.

Audit explicitly called out (and we agree) these as past the value
threshold for further rounds:
  - Verified-clean: try_lock no deadlock, ensureActive fence, bad-URL
    early-return cancel, duplicate-zip-entry rejection, LIMIT clauses,
    SponsorBlock 50ms exclusion drop, applied++ count.
  - False positive: H1 "Related videos always empty" — the section
    already gates on `d.related.isNotEmpty()`; UI doesn't paint when
    extractor stub returns empty.
  - Deferred: R8 enable, Nav rememberSaveable, LazyColumn keys,
    collectAsStateWithLifecycle, DASH/HLS max-resolution cap,
    Gradle cargo-build input/output declarations (CI cost, not
    runtime), legacy :app module trim, stale comment cleanup.
This commit is contained in:
Kayos 2026-05-25 15:36:00 -07:00
parent ecc54aaf38
commit 10154c380b
4 changed files with 15 additions and 14 deletions

View file

@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app"
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
// NewPipeExtractor in the runtime path.
const val STRAW_VERSION_CODE = 41
const val STRAW_VERSION_NAME = "0.1.0-BA"
const val STRAW_VERSION_CODE = 42
const val STRAW_VERSION_NAME = "0.1.0-BB"
const val STRAW_APPLICATION_ID = "com.sulkta.straw"

View file

@ -34,8 +34,6 @@ strawcore-core = { path = "../../../strawcore" }
rquickjs-sys = { version = "0.11", default-features = false, features = ["bindgen"] }
# Error glue.
thiserror = "1"
# Single-threaded init for the runtime + extractor singletons.
once_cell = "1"
# Android log integration — `log::info!()` ends up in `adb logcat -s strawcore`.
log = "0.4"
android_logger = { version = "0.14", default-features = false }

View file

@ -42,11 +42,9 @@ import com.sulkta.straw.feature.search.SearchScreen
import com.sulkta.straw.feature.settings.SettingsScreen
import kotlinx.coroutines.flow.MutableStateFlow
private val YT_HOSTS = setOf(
"youtube.com", "www.youtube.com", "m.youtube.com",
"music.youtube.com", "youtube-nocookie.com", "www.youtube-nocookie.com",
"youtu.be",
)
// Allowlist now lives in util/YtUrl.kt with extra hardening (scheme
// requirement, trailing-dot strip). Round-7 audit MED-4: prior shape
// duplicated the host set here and would drift away from the util.
private val YT_URL_RE = Regex(
"https?://(?:www\\.|m\\.|music\\.)?(?:youtube(?:-nocookie)?\\.com/[A-Za-z0-9_/?=&\\-.%]+|youtu\\.be/[A-Za-z0-9_\\-]+)",
)
@ -231,8 +229,6 @@ class StrawActivity : ComponentActivity() {
}
}
private fun looksLikeYouTube(url: String): Boolean {
val host = runCatching { java.net.URI(url).host }.getOrNull() ?: return false
return host.lowercase() in YT_HOSTS
}
private fun looksLikeYouTube(url: String): Boolean =
com.sulkta.straw.util.isAllowedYtUrl(url)
}

View file

@ -144,6 +144,14 @@ class SearchViewModel : ViewModel() {
.firstOrNull { it.query.equals(q, ignoreCase = true) }
?.items
} else null
// Cancel any prior in-flight submit BEFORE writing the cached
// preview to the UI — round-7 audit MED-1: previously a fresh
// submit that hit the cache could be clobbered seconds later
// by the prior submit's late terminal write, because the
// prior coroutine had already advanced past its `ensureActive`
// gate by the time the new submit got around to cancelling.
inFlight?.cancel()
if (cached != null && cached.isNotEmpty()) {
_ui.update {
it.copy(
@ -164,7 +172,6 @@ class SearchViewModel : ViewModel() {
}
}
inFlight?.cancel()
inFlight = viewModelScope.launch {
try {
// strawcore.search() is suspend on the tokio runtime baked