feat(update): surface backend update receipt in action popup (#958) (#964)

Adopt GET /api/hermes/update/receipt and append its summary to the
end of the hermes-update action progress popup after it finishes.
Additive only: existing checkHermesUpdate + connection-retry polling
is untouched. 404 (old backend) is handled gracefully.

- add UpdateReceiptResponse data model matching backend schema
- add getUpdateReceipt() to HermesApiService
- ActionProgressController gains trailingLines + pushTrailingLines
- ActionProgressDialog renders trailingLines after the log
- SystemViewModel fetches receipt on action finish and formats it
- extract pure formatUpdateReceiptLines for mockk-free testing
This commit is contained in:
M57 authored and GitHub committed 2026-08-24 01:35:59 +03:00
1 parent 5fd732be9b
commit ece0288ca8
8 files changed
+342 -1

No files matched your search

@@ -40,6 +40,11 @@ data class ActionProgressState(
val lines: List<String> = emptyList(),
val exitCode: Int? = null,
val error: String? = null,
/**
* Host-appended lines shown after the log tail once the action finishes
* (e.g. a structured update receipt). Empty unless the host pushes them.
*/
val trailingLines: List<String> = emptyList(),
)
/**
@@ -150,6 +155,16 @@ class ActionProgressController(
_state.update { it.copy(phase = ActionProgressPhase.FAILED, error = error) }
}
/**
* Append host-supplied lines after the log tail (e.g. an update receipt
* summary). Safe to call after the action has finished; only affects
* [ActionProgressState.trailingLines], never the poll loop.
*/
fun pushTrailingLines(lines: List<String>) {
if (lines.isEmpty()) return
_state.update { it.copy(trailingLines = it.trailingLines + lines) }
}
/** Stop tracking and hide the dialog. Safe mid-run (action keeps going). */
fun dismiss() {
pollJob?.cancel()
@@ -16,6 +16,7 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -169,6 +170,26 @@ fun ActionProgressDialog(
color = statusColors.error,
)
}
// Host-appended trailing block (e.g. update receipt summary),
// shown after the log tail once the action has finished.
if (state.trailingLines.isNotEmpty()) {
HorizontalDivider(
modifier = Modifier.padding(vertical = spacing.sm),
color = MaterialTheme.colorScheme.outlineVariant,
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
state.trailingLines.forEach { line ->
Text(
text = line,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
}
},
confirmButton = {