Render the escapes in an error, and drop the manifest field nobody read

Two loose ends from the per-component work.

The card's error is the tail of what the build actually printed, and a
compiler marks its own errors in colour -- so it arrived as `[1;31merror`,
with punctuation welded onto the one line somebody is trying to read. It now
goes through the same `ansiAnnotated` the log dialog uses: the colour is
drawn, and the sequences with no meaning on a phone are consumed rather than
printed, so a cursor movement cannot arrive looking like corruption either.
One composable covers it because every failure already went through
OutputText. Selection copies AnnotatedString.text, which is the message with
every escape already gone, so what lands on the clipboard is what was on
screen rather than what was on the wire.

And `/manifest` carried a `build` object that nothing on the phone ever
parsed. It was going to be how a running build survived leaving the app;
that is not built, so it is gone rather than left looking finished.
`RunningBuild` went with it -- it was split out of `BuildStatus` for that
one reader and had become an indirection with one user, so `status()` fills
one flat struct again. `/status` is byte-identical either way, since the
split was flattened on the wire; checked against the running server rather
than assumed.

Looked at on the emulator against a failing build: bold red `error`, blue
`-->`, no escape text anywhere, and long-pressing it still raises the
handles and the Copy toolbar.
This commit is contained in:
iris committed 2026-09-01 03:48:23 -04:00
1 parent 3685ab107d
commit aa8e2b97a5
4 files changed
+44 -70

No files matched your search

-18
View File
@@ -493,23 +493,6 @@ struct ManifestApp {
/// one flag for the list, so a card says whether *it* is the one still
/// being worked out.
check_pending: bool,
/// The build running for this project right now, if one is, in the
/// same shape `/status` answers.
///
/// Here so that a build survives leaving the app. The run itself never
/// stops -- it owns its own `Arc<BuildState>` and outlives the request
/// that started it -- but the phone's record of it lives only in the
/// composition, so backgrounding tears down the polling loop and the
/// card state together. Without this the list it comes back to cannot
/// say a build is still going, and the card reads as one that was
/// killed: it offers Update again, and pressing it does nothing,
/// because there is already a run in this project's one build slot.
///
/// Read with [`crate::build_state::BuildState::running`] rather than
/// `status`, which also answers `stale` and walks every component's
/// directory to do it -- this is on the manifest path.
#[serde(skip_serializing_if = "Option::is_none")]
build: Option<crate::build_state::RunningBuild>,
/// What this project produces, in the order it is built. One entry is
/// the ordinary case and the card shows it inline; more than one is
/// what the phone draws as a nested list.
@@ -673,7 +656,6 @@ async fn describe(state: &Arc<AppState>, entry: &AppEntry) -> Result<ManifestApp
.build
.as_ref()
.is_some_and(|build| build.has_command()),
build: entry.build.as_ref().and_then(|build| build.running()),
git_ipv4: entry.git_ipv4,
built_in: entry.built_in,
pending_declaration: pending