Home updating-veil rework: change-triggered, settle-driven, with refresh feedback #114

Merged
bvandeusen merged 4 commits from dev into main 2026-07-31 23:32:06 -04:00
2 changed files with 20 additions and 9 deletions
Showing only changes of commit 4f99b42844 - Show all commits
+14 -1
View File
@@ -210,4 +210,17 @@ dependencies {
debugImplementation(libs.compose.ui.test.manifest)
}
tasks.withType<Test> { useJUnitPlatform() }
tasks.withType<Test> {
useJUnitPlatform()
// Print the assertion message + full stack trace for failures. The
// default console output gives only "AssertionError at Foo.kt:12", and
// for a failure inside a `runTest { }` lambda even that line collapses
// to the test function's own line (the assertion frames live in the
// suspend-lambda class, which Gradle filters out) — leaving nothing to
// debug from when the HTML report isn't reachable, as in CI.
testLogging {
events("failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
showStackTraces = true
}
}
@@ -3,7 +3,6 @@ package com.fabledsword.minstrel.shared
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -14,7 +13,6 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
// Once raised, the veil stays up at least this long. Without a floor a
@@ -166,15 +164,15 @@ class UpdateVeilController(
// here before the raiser has been dispatched, tear the session
// down, and leave the churn uncovered.
withTimeoutOrNull(timings.maxHoldMs) { awaitSettled() }
// Honour the no-flash minimum before lowering. Deliberately in
// the try and not the finally: on cancellation the scope is
// going away and nothing will render the veil, so the floor is
// pointless there — and a finally that suspends is a finally
// that can resist teardown.
if (raised.isCompleted) floor.join()
} finally {
raiser.cancel()
ceiling.cancel()
if (raised.isCompleted) {
// NonCancellable so the floor is honoured (and the veil
// always cleared) even while the scope is torn down; the
// floor job dies with the scope, so this cannot hang.
withContext(NonCancellable) { floor.join() }
}
floor.cancel()
visibleInternal.value = false
}