Keep watching the job through transient fetch failures; don't freeze the screen

reprocessInPlace's poll loop swallowed fetchJobs failures as an empty
list and broke on the resulting missing status, so ONE dropped poll
during a 10-minute summarize ended the watch early: chips stayed
disabled, the screen showed stale content, and only leaving to the
library and back (fresh openDetail fetch) displayed the finished
summary — the exact reported symptom.

Now only a successful fetch reporting a terminal state ends the loop;
transient failures keep polling for ~30s, and a genuinely-lost engine
says so in plain words instead of silently stranding the screen. A
missing job row on healthy fetches gets 3 grace cycles before giving
up (engine rewrites rows mid-requeue).
This commit is contained in:
avi 2026-09-18 21:11:58 -05:00
commit b5836d0fb6

View file

@ -1632,11 +1632,30 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
return@launch return@launch
} }
} }
// Poll the target job to a terminal state. // Poll the target job to a terminal state. A job this long
// (10+ min = hundreds of polls) WILL hit a transient fetch
// failure; treating one as terminal (the old `orEmpty()` →
// st == null → break) froze the screen on stale content until
// the user left and returned — exactly the reported symptom.
// So: only a SUCCESSFUL fetch reporting a terminal state
// breaks; failures keep polling up to ~30s of contact loss.
var miss = 0
var gone = 0
while (true) { while (true) {
delay(2000) delay(2000)
val jobs = runCatching { parseJobs(provider.fetchJobs(remoteId)) }.getOrNull() val fetched = runCatching { parseJobs(provider.fetchJobs(remoteId)) }
.orEmpty() if (fetched.isFailure) {
if (++miss >= 15) {
d0.error = "Lost contact with the engine while " +
"processing — reopen this recording to check."
d0.busy = null
_detail.value = d0.copy()
return@launch
}
continue
}
miss = 0
val jobs = fetched.getOrDefault(emptyList())
d0.jobs = jobs d0.jobs = jobs
jobLabel(jobs)?.let { d0.busy = it } jobLabel(jobs)?.let { d0.busy = it }
// Mirror live progress onto the library row too — otherwise // Mirror live progress onto the library row too — otherwise
@ -1646,7 +1665,10 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
rescanStatuses() rescanStatuses()
_detail.value = d0.copy() _detail.value = d0.copy()
val st = jobs.firstOrNull { it.jobType == job }?.status val st = jobs.firstOrNull { it.jobType == job }?.status
if (st == null || st in TERMINAL) break if (st in TERMINAL) break
// Job row missing from a healthy fetch: give it a few
// cycles (the engine rewrites rows mid-requeue) then stop.
if (st == null && ++gone >= 3) break
} }
setLive(d0.file.name, null) setLive(d0.file.name, null)
rescanStatuses() rescanStatuses()