feat(approvals,runs): explain approvals in place + one-click hub update (0.81.0)

Approvals: the pending card now shows its full origin — flow, step,
run id (previously dropped at the Dart mapping layer), project, and
requested-at — under an ORIGIN heading, led by a one-line intro strip
that says what the inbox is and what Approve/Reject do. Approve/Reject
buttons carry tooltips; the history dialog gains project + run id.
Fixes the approvals doc drift (title/details/reviewer ->
prompt/show/timeout_seconds). Guard: approvals_origin_test renders the
card via the hermetic fake hub and pins every origin fact.

Runs: the "hub too old" state now leads with an in-place update button
(same `chain update apply` path as the Diagnose page), the Diagnose
deeplink demoted to secondary, with a CLI-absent fallback. Guard: two
new RunsLoadErrorView widget tests.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-26 15:50:41 +02:00
parent 14f824b8ef
commit 28f6fe1a9a
16 changed files with 613 additions and 115 deletions

View file

@ -65,6 +65,11 @@ class FakeHubService extends Fake implements HubService {
bool detachedEnabled = false;
bool probeServing = true;
/// Approvals returned by [listApprovalsRecords], filtered by the
/// requested statuses so the pending / history tabs each see the
/// right slice. Empty by default (healthy-empty inbox).
List<ApprovalRecord> approvals = const [];
void failWith(Object error, {Set<String>? only}) {
if (only == null) {
failAll = error;
@ -209,7 +214,12 @@ class FakeHubService extends Fake implements HubService {
List<String> statuses = const [],
int limit = 200,
String project = '',
}) => _async('listApprovalsRecords', () => const []);
}) => _async(
'listApprovalsRecords',
() => statuses.isEmpty
? approvals
: approvals.where((a) => statuses.contains(a.status)).toList(),
);
@override
Future<({List<DetachedRun> runs, bool enabled})> listDetachedRuns({