feat(studio): contextual "Add registry token" CTA on install auth wall (v0.46.0)
Some checks are pending
Security / Security check (push) Waiting to run

The hub's v0.10.91 install error message already tells the
operator exactly what to do — set FAI_REGISTRY_TOKEN or save
to ~/.fai/registry-token via Settings. But that text lives in
a FaiErrorBox inside the install dialog. Reading the buried
recovery instruction, finding Settings, scrolling to the
Registry credentials panel, pasting, then re-clicking the
install pill is five hops for what should be one.

When at least one item in the install dialog fails with the
"registry returned an HTML page" / "no registry token
configured" signature, the dialog now surfaces a prominent
filled "Add registry token" button next to Close. Click closes
the install dialog and opens the Settings dialog directly so
the operator pastes + saves without losing context. After
Settings dismisses, the operator is back at the flow card and
re-clicks the same pill to retry.

Detection is signature-based (substring match on the hub's
error text) so it activates whether the install was a single-
pill click or an "Install all" sequence. Other failure modes
(network down, signature invalid, sha mismatch) still show
just the Close button — the CTA is specific to the auth-wall
case it can actually unblock.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-09 19:32:13 +02:00
parent 3db7db7330
commit 7416c34d7c
7 changed files with 43 additions and 1 deletions

View file

@ -428,6 +428,7 @@
"failed": { "type": "int" } "failed": { "type": "int" }
} }
}, },
"flowsInstallAuthWallButton": "Registry-Token einfügen",
"flowsOpenInEditorTooltip": "YAML-Datei im Standard-Editor öffnen", "flowsOpenInEditorTooltip": "YAML-Datei im Standard-Editor öffnen",
"flowsOpenInEditorFailed": "Konnte Datei nicht öffnen: {error}", "flowsOpenInEditorFailed": "Konnte Datei nicht öffnen: {error}",
"@flowsOpenInEditorFailed": { "@flowsOpenInEditorFailed": {

View file

@ -429,6 +429,7 @@
"failed": { "type": "int" } "failed": { "type": "int" }
} }
}, },
"flowsInstallAuthWallButton": "Add registry token",
"flowsOpenInEditorTooltip": "Open the YAML file in your default editor", "flowsOpenInEditorTooltip": "Open the YAML file in your default editor",
"flowsOpenInEditorFailed": "Could not open file: {error}", "flowsOpenInEditorFailed": "Could not open file: {error}",
"@flowsOpenInEditorFailed": { "@flowsOpenInEditorFailed": {

View file

@ -1958,6 +1958,12 @@ abstract class AppLocalizations {
/// **'{ok} installed, {failed} failed.'** /// **'{ok} installed, {failed} failed.'**
String flowsInstallDepsAnyFailed(int ok, int failed); String flowsInstallDepsAnyFailed(int ok, int failed);
/// No description provided for @flowsInstallAuthWallButton.
///
/// In en, this message translates to:
/// **'Add registry token'**
String get flowsInstallAuthWallButton;
/// No description provided for @flowsOpenInEditorTooltip. /// No description provided for @flowsOpenInEditorTooltip.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -1109,6 +1109,9 @@ class AppLocalizationsDe extends AppLocalizations {
return '$ok installiert, $failed fehlgeschlagen.'; return '$ok installiert, $failed fehlgeschlagen.';
} }
@override
String get flowsInstallAuthWallButton => 'Registry-Token einfügen';
@override @override
String get flowsOpenInEditorTooltip => 'YAML-Datei im Standard-Editor öffnen'; String get flowsOpenInEditorTooltip => 'YAML-Datei im Standard-Editor öffnen';

View file

@ -1122,6 +1122,9 @@ class AppLocalizationsEn extends AppLocalizations {
return '$ok installed, $failed failed.'; return '$ok installed, $failed failed.';
} }
@override
String get flowsInstallAuthWallButton => 'Add registry token';
@override @override
String get flowsOpenInEditorTooltip => String get flowsOpenInEditorTooltip =>
'Open the YAML file in your default editor'; 'Open the YAML file in your default editor';

View file

@ -940,6 +940,22 @@ class _InstallDependenciesDialogState
int get _failedCount => int get _failedCount =>
_items.where((i) => i.status == _InstallStatus.failed).length; _items.where((i) => i.status == _InstallStatus.failed).length;
/// True iff at least one failure carries the v0.10.91+ HTML-instead-
/// of-bundle signature emitted by `download_to_temp` when the
/// registry redirects an unauthenticated request to a signin page.
/// Used to surface a contextual "Open Settings" button so the
/// operator does not have to read the wall of error text first.
bool get _anyAuthWallFailure => _items.any(
(i) =>
i.status == _InstallStatus.failed && _isAuthWallError(i.error),
);
static bool _isAuthWallError(String? msg) {
if (msg == null) return false;
return msg.contains('registry returned an HTML page') ||
msg.contains('no registry token configured');
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -986,6 +1002,18 @@ class _InstallDependenciesDialogState
), ),
), ),
actions: [ actions: [
if (!_running && _anyAuthWallFailure)
FilledButton.icon(
icon: const Icon(Icons.vpn_key_outlined, size: 16),
label: Text(l.flowsInstallAuthWallButton),
onPressed: () async {
// Close install dialog first so the operator returns
// to the flow card on Settings dismiss then they
// can re-click the pill to retry the install.
Navigator.pop(context, _anySucceeded);
await FaiSettingsDialog.show(context);
},
),
TextButton( TextButton(
onPressed: _running onPressed: _running
? null ? null

View file

@ -1,7 +1,7 @@
name: fai_studio name: fai_studio
description: "F∆I Studio — desktop GUI for the F∆I hub" description: "F∆I Studio — desktop GUI for the F∆I hub"
publish_to: 'none' publish_to: 'none'
version: 0.45.0 version: 0.46.0
environment: environment:
sdk: ^3.11.0-200.1.beta sdk: ^3.11.0-200.1.beta