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:
1 parent
aa8e2b97a5
commit
c35f84a373
2 files changed
+116
-81
No files matched your search
@@ -494,6 +494,21 @@ mutable at runtime from the phone.
|
||||
the Retry that acts on it, in the one place that reports that
|
||||
component's failures whether they came from a build, a download or a
|
||||
service action.
|
||||
- **A card being worked on has no freshness, rather than the one from
|
||||
before the press.** Nothing re-reads the manifest during a run -- the
|
||||
entry was fetched before the button was pressed and read again only
|
||||
once the run is over -- so "out of date" drawn beside the bar that is
|
||||
making it current is last minute's answer wearing this minute's
|
||||
clothes. `UpdaterScreen` therefore maps a busy component's `freshness`
|
||||
to `unknown` in the one place the component list is built for the
|
||||
cards, which is what makes both readers of it -- the row's own note and
|
||||
`MismatchedPairNote` -- go quiet without either having to know why. The
|
||||
condition is deliberately the same pair that disables the Update button
|
||||
(`projectState.busy || componentState.busy`): what cannot be acted on
|
||||
is exactly what cannot be measured just now. It comes back the instant
|
||||
the run ends, still saying "out of date" if the build failed, because
|
||||
by then the entry has been read again -- withheld is not the same as
|
||||
cleared.
|
||||
- **A project's own `.dev-updater.ron` is a request, never an
|
||||
instruction.** It only runs once accepted from the phone, which copies
|
||||
it into `config.ron`; `AppEntry::pending_declaration` is the whole gate.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user