Withhold a component's freshness while it is being built

Nothing re-reads the manifest during a run, so the "out of date" note
beside a running progress bar is the answer from before the button was
pressed -- shown for the length of a build, next to the work that is
making it wrong.

The card now reads a busy component's freshness as unknown, in the one
place the list of components is built for the cards, so the row's note
and the sibling-mismatch warning both go quiet. Same condition as the
one that disables the Update button.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-01 10:09:29 -04:00
1 parent aa8e2b97a5
commit c35f84a373
2 files changed
+116 -81

No files matched your search

@@ -1580,87 +1580,107 @@ private fun AppCard(
// order is the *build* order -- dev-updater builds its
// server before its APK on purpose -- and that stays as
// it is; this sort is stable, so anything else keeps it.
entry.components
.sortedBy { it.isServer }
.forEach { component ->
val installed = installedTimes[component.name]
val installedSize = installedSizes[component.name]
val chosenVariantPath = chosenVariants[component.name]
// This component's own, so nothing below can
// reach for a sibling's by accident.
val componentState = componentStates[component.name]
val upToDate =
component.apk?.let {
isUpToDate(it, installed, chosenVariantPath)
} == true
ComponentCard(
entryKey = entry.key,
component = component,
packageName = component.apk?.packageName,
// What the download would cost, and what it
// replaces. An APK's size sits where a
// server's state does: the one thing worth
// knowing about it besides its name.
sizeText =
component.apk
?.takeIf { it.built }
?.let { apk ->
when {
!upToDate && installedSize != null ->
"${formatSize(installedSize)} \u2192 " +
formatSize(apk.size)
else -> formatSize(apk.size)
}
},
chosenVariantPath = chosenVariantPath,
onSelectVariant = { onSelectVariant(component.name, it) },
// This app reaches the server through this server.
// Stopping or uninstalling it is the one action
// here that cannot be undone from the phone.
isOwnServer = entry.builtIn,
build =
componentBuild(
projectState,
componentState,
component.name,
),
// Being worked on right now, which the
// component's own slice of the build says
// directly rather than being inferred from a
// project-wide phase name.
working =
componentBuild(projectState, componentState, component.name)
?.running == true,
busy = serviceBusy == component.name,
state = componentState,
onAction = { action, purge ->
onServiceAction(component.name, action, purge)
},
controls = {
if (!component.isServer) {
// Said before the button, because it
// qualifies what pressing it gets you.
MismatchedPairNote(
self = component,
others = entry.components,
)
// The button that asked for it, then
// how far along it is: a bar reports on
// the control above it.
UpdateButton(
built = component.apk?.built == true,
needsBuild = entry.needsBuild,
installed = installed != null,
upToDate = upToDate,
state = componentState,
projectState = projectState,
onUpdate = { onUpdate(entry, component.name) },
)
ApkProgress(componentState)
}
},
)
}
//
// Freshness is withheld from a component being worked
// on, because it is the one thing on the card that the
// work is *about* and nothing re-reads it while the
// work runs: the entry was fetched before the button
// was pressed and is read again only once the run is
// over. So "out of date" beside the bar that is making
// it current is not a measurement, it is the answer
// from before the press -- and unknown is what this
// card already says when it has no reading, so both
// readers of it (the row's own note, and the sibling
// warning) go quiet without either having to know why.
// Deliberately the same pair of conditions that
// disables the Update button: what cannot be acted on
// is exactly what cannot be measured just now.
val components =
entry.components
.sortedBy { it.isServer }
.map { component ->
if (projectState.busy || componentStates[component.name].busy)
component.copy(freshness = "unknown")
else component
}
components.forEach { component ->
val installed = installedTimes[component.name]
val installedSize = installedSizes[component.name]
val chosenVariantPath = chosenVariants[component.name]
// This component's own, so nothing below can
// reach for a sibling's by accident.
val componentState = componentStates[component.name]
val upToDate =
component.apk?.let { isUpToDate(it, installed, chosenVariantPath) } ==
true
ComponentCard(
entryKey = entry.key,
component = component,
packageName = component.apk?.packageName,
// What the download would cost, and what it
// replaces. An APK's size sits where a
// server's state does: the one thing worth
// knowing about it besides its name.
sizeText =
component.apk
?.takeIf { it.built }
?.let { apk ->
when {
!upToDate && installedSize != null ->
"${formatSize(installedSize)} \u2192 " +
formatSize(apk.size)
else -> formatSize(apk.size)
}
},
chosenVariantPath = chosenVariantPath,
onSelectVariant = { onSelectVariant(component.name, it) },
// This app reaches the server through this server.
// Stopping or uninstalling it is the one action
// here that cannot be undone from the phone.
isOwnServer = entry.builtIn,
build =
componentBuild(
projectState,
componentState,
component.name,
),
// Being worked on right now, which the
// component's own slice of the build says
// directly rather than being inferred from a
// project-wide phase name.
working =
componentBuild(projectState, componentState, component.name)
?.running == true,
busy = serviceBusy == component.name,
state = componentState,
onAction = { action, purge ->
onServiceAction(component.name, action, purge)
},
controls = {
if (!component.isServer) {
// Said before the button, because it
// qualifies what pressing it gets you.
MismatchedPairNote(
self = component,
others = components,
)
// The button that asked for it, then
// how far along it is: a bar reports on
// the control above it.
UpdateButton(
built = component.apk?.built == true,
needsBuild = entry.needsBuild,
installed = installed != null,
upToDate = upToDate,
state = componentState,
projectState = projectState,
onUpdate = { onUpdate(entry, component.name) },
)
ApkProgress(componentState)
}
},
)
}
}
}
// What there is to read before pressing anything, directly