A component's build progress reads under its buttons, and an unrelated history can be overridden

Two things, both about a card being able to act on what it says.

The progress bar, its counts and the last line a component printed now
sit below that component's buttons rather than above them. A bar reports
on the press that started it, so it reads in the order it happened -- and
above, it pushed the buttons down the moment a build began, moving the row
somebody had just pressed out from under their finger.

A pull that cannot fast-forward because the checkout shares no history
with its upstream now offers a way past it. There is no fast-forward
between two unrelated histories and there never will be, so the card was
one that could never be pulled again, with the only remedy on the build
machine -- exactly where the person holding the phone isn't. The card
reports the failure as before and a dialog offers a forced pull, naming
the branch and the upstream it is about to overwrite; confirming sends
?force=true, which resets onto the upstream instead of merging.

Whether it *is* that failure is decided structurally, by `git merge-base`
finding no common ancestor, rather than by matching what git printed:
those messages are translated, and a button that appeared only on an
English build machine would be worse than no button. It travels to the
phone as its own field for the same reason. The dirty-tree refusal stays
in front of it, so a forced pull can only ever discard something that was
committed, and a merely diverged history is not offered it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-31 22:03:44 -04:00
1 parent b0e83059a3
commit db47972a25
6 files changed
+306 -29

No files matched your search

+16
View File
@@ -12,6 +12,7 @@
//! PUT /roots {roots} set the directories /suggestions scans
//! GET /apps/{key}/apk[?variant=] the payload itself (ranged)
//! POST /apps/{key}/pull fetch, fast-forward, and build
//! ?force=true resets onto the upstream
//! POST /apps/{key}/prepare run the on-demand build step, if any
//! POST /apps/{key}/build run it whether or not it looks stale
//! POST /apps/{key}/approve accept the build step it asks for
@@ -1024,6 +1025,19 @@ struct PurgeQuery {
config: bool,
}
/// Whether a pull may throw this checkout's own history away.
///
/// Off unless the phone says otherwise, and it only says so after being
/// told the checkout and its upstream are unrelated -- there is no
/// fast-forward for that ever, so the alternative to this is a card that
/// can never be pulled again. Destructive, and confirmed on the phone
/// against the branch it names before it is ever sent.
#[derive(Deserialize)]
struct PullQuery {
#[serde(default)]
force: bool,
}
/// Which build a download wants. Absent means the newest, which is what
/// every phone gets until it says otherwise.
#[derive(Deserialize)]
@@ -1180,6 +1194,7 @@ async fn build_now(
async fn build_pull(
State(state): State<Arc<AppState>>,
key: UrlPath<String>,
Query(query): Query<PullQuery>,
) -> Result<Json<BuildStatus>, ApiError> {
let entry = lookup(&state, Some(key))?;
let build = entry
@@ -1191,6 +1206,7 @@ async fn build_pull(
// here is the answer for the commit being replaced.
let gate = Arc::clone(&entry);
build.pull_and_build(
query.force,
move || gate.pending_declaration().is_none(),
records_builds(&state, &entry.key),
);