fix(studio): audit batch — debug.echo install, copyable errors, fast Diagnose

From a live-test audit:
- Install via the flow-editor 'Fix' sent 'debug.echo@^0' (version
  constraint included) as the install source; the hub resolves by bare
  name so it missed ('no store entry for debug.echo@^0'). Strip the
  @<constraint> like the Store page does — debug.echo now installs.
- Errors the operator could not copy: route the daemon-start failure
  (its stderr!), the flow-editor install failure and the federation
  issue failure through showFaiErrorSnack / a new showFaiProcessError
  helper, so every error is selectable, one-tap copyable and logged.
- Diagnose page opened slowly because doctor() Future.wait-ed on a live
  manifest fetch (8s server timeout); cap that one check at 2s so the
  page no longer waits on the network.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-18 13:02:21 +02:00
parent ee4a0f6c62
commit 5f56abcf5f
5 changed files with 85 additions and 30 deletions

View file

@ -5,6 +5,7 @@ import '../data/hub.dart';
import '../l10n/app_localizations.dart';
import '../theme/theme.dart';
import '../theme/tokens.dart';
import '../data/error_presentation.dart';
import '../widgets/widgets.dart';
import 'welcome.dart' show showFaiDoc;
@ -48,9 +49,8 @@ class _FederationPageState extends State<FederationPage> {
_refresh();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l.federationIssueFailed(e.toString()))),
);
showFaiErrorSnack(context, 'federation.issue', e,
title: l.federationIssueFailed(''));
}
}

View file

@ -13,6 +13,7 @@
import 'package:chain_studio_flow_editor/chain_studio_flow_editor.dart';
import 'package:flutter/material.dart';
import '../data/error_presentation.dart';
import '../data/flow_run_driver.dart';
import '../data/hub.dart';
import '../l10n/app_localizations.dart';
@ -94,7 +95,13 @@ class _FlowsPageState extends State<FlowsPage> {
/// list so the editor can re-analyze without waiting for the
/// parent widget rebuild.
Future<List<String>?> _onInstallCapability(String capability) async {
return _runInstall(source: capability);
// The analyzer hands us the full `use:` value (e.g. `debug.echo@^0`).
// The hub resolves install sources by bare capability name; the
// `@<constraint>` suffix would make the store lookup miss ("no store
// entry for 'debug.echo@^0'"). Strip it — same as the Store page.
final at = capability.indexOf('@');
final bare = at < 0 ? capability : capability.substring(0, at);
return _runInstall(source: bare);
}
/// `Add source for <cap>` handler. Prompts the operator
@ -123,15 +130,13 @@ class _FlowsPageState extends State<FlowsPage> {
} catch (e) {
if (mounted) {
final l = AppLocalizations.of(context);
final msg = l != null
? l.installFailed(e.toString())
: 'Install failed: $e';
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(msg),
behavior: SnackBarBehavior.floating,
),
// Copyable error (was a plain SnackBar) install failures
// carry the real cause (network, signature, store lookup).
showFaiErrorSnack(
context,
'flows.install',
e,
title: l != null ? l.installFailed('') : 'Install failed',
);
}
return null;