From 10154c380b52c7cceaf9278905f1d3b4548024f6 Mon Sep 17 00:00:00 2001 From: Kayos Date: Mon, 25 May 2026 15:36:00 -0700 Subject: [PATCH] =?UTF-8?q?vc=3D42:=20loop=20round=204/5=20=E2=80=94=20sur?= =?UTF-8?q?gical=20cleanup,=20round=207=20hit=20diminishing=20returns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- buildSrc/src/main/kotlin/ProjectConfig.kt | 4 ++-- rust/strawcore/Cargo.toml | 2 -- .../main/kotlin/com/sulkta/straw/StrawActivity.kt | 14 +++++--------- .../sulkta/straw/feature/search/SearchViewModel.kt | 9 ++++++++- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index 8386cec82..0c5a8b5b1 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -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" diff --git a/rust/strawcore/Cargo.toml b/rust/strawcore/Cargo.toml index f693973ee..1cd5ce697 100644 --- a/rust/strawcore/Cargo.toml +++ b/rust/strawcore/Cargo.toml @@ -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 } diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt b/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt index ead83af3f..9bcb0664e 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/StrawActivity.kt @@ -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) } diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt index 2e64950fe..249327e12 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/search/SearchViewModel.kt @@ -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