feat(launcher): LU4 — first-run setup ends with "Setup complete" and an OK button

Setup used to finish by leaving a full progress bar and a status line on
screen, with the same Validate / Cancel bake / Close / Build and install row
underneath. Nothing said "you are done" and nothing said what to press.

The wizard now swaps its whole form for a plain completion panel: "Setup
complete", one sentence saying the content was built and verified, and a single
OK that closes the dialog and returns to the launcher.

Raised at exactly one point — after _onInstalled publishes the record — so the
launcher behind the dialog is already in its launch-enabled state when OK is
pressed, and the "Client setup required" banner is gone the moment the user
gets back. The cancelled and failed branches deliberately never reach it and
keep their existing status/error reporting.

Tests: FirstRunSetupEndsWithACompletionPanelThatOkReturnsFrom (form hidden,
panel shown, record published before OK, wizard reopens as an ordinary form
afterwards) and AFailedFirstRunSetupNeverShowsTheCompletionPanel.
Launcher 61 passed.

Campaign LU slice LU4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-19 18:51:08 +02:00
parent a01ff42640
commit 0a2defb618
3 changed files with 138 additions and 0 deletions

View file

@ -350,6 +350,25 @@
VerticalAlignment="Center">
<ScrollViewer>
<StackPanel Spacing="12">
<!-- LU4: the completion panel replaces the form outright, so the
user is not left staring at a finished progress bar wondering
whether they are allowed to close the window. -->
<StackPanel Spacing="16" IsVisible="{Binding FirstRunWizardShell.IsCompleted}">
<TextBlock Text="{Binding FirstRunWizardShell.CompletedTitle}"
FontSize="24"
FontWeight="Bold" />
<TextBlock Text="{Binding FirstRunWizardShell.CompletedBody}"
TextWrapping="Wrap" />
<Button Content="OK"
Classes="primary"
IsDefault="True"
HorizontalAlignment="Right"
MinWidth="96"
AutomationProperties.Name="Finish first-run setup"
Command="{Binding FirstRunWizardShell.AcknowledgeCompletionCommand}" />
</StackPanel>
<StackPanel Spacing="12" IsVisible="{Binding FirstRunWizardShell.ShowSetupForm}">
<TextBlock Text="{Binding FirstRunWizardShell.Title}" FontSize="24" FontWeight="Bold" />
<TextBlock Text="{Binding FirstRunWizardShell.Body}" TextWrapping="Wrap" />
@ -423,6 +442,7 @@
AutomationProperties.Name="Build and install client content"
Command="{Binding FirstRunWizardShell.StartCommand}" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Border>

View file

@ -25,6 +25,7 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
private string? _error;
private LauncherInstallPhase _phase = LauncherInstallPhase.Idle;
private double _progressPercent;
private bool _isCompleted;
private bool _isOpen;
private bool _isRunning;
private bool _isDatDirectoryValid;
@ -44,6 +45,9 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
_canStart = canStart ?? (() => true);
OpenCommand = new RelayCommand(Open, () => !_disposed && _canOpen());
AcknowledgeCompletionCommand = new RelayCommand(
AcknowledgeCompletion,
() => IsCompleted && !IsRunning);
CloseCommand = new RelayCommand(Close, () => !IsRunning);
ValidateCommand = new RelayCommand(Validate, () => !IsRunning);
StartCommand = new AsyncRelayCommand(StartAsync, CanBeginInstall);
@ -183,6 +187,9 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
public RelayCommand OpenCommand { get; }
/// <summary>LU4: OK on the "Setup complete" panel.</summary>
public RelayCommand AcknowledgeCompletionCommand { get; }
public RelayCommand CloseCommand { get; }
public RelayCommand ValidateCommand { get; }
@ -200,6 +207,34 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
: message;
}
/// <summary>
/// LU4: true once first-run setup has actually succeeded — the pak was
/// built, verified, and its record published. The wizard replaces its form
/// with a plain "Setup complete" panel whose single OK button returns to
/// the launcher, instead of leaving the user looking at a finished
/// progress bar wondering whether they may close the window.
/// </summary>
public bool IsCompleted
{
get => _isCompleted;
private set
{
if (SetProperty(ref _isCompleted, value))
{
OnPropertyChanged(nameof(ShowSetupForm));
NotifyCommandStates();
}
}
}
/// <summary>The form and the completion panel are mutually exclusive.</summary>
public bool ShowSetupForm => !IsCompleted;
public string CompletedTitle => "Setup complete";
public string CompletedBody =>
"acdream built and verified your game content. You can play now.";
public void NotifyCommandStates()
{
OpenCommand.NotifyCanExecuteChanged();
@ -207,6 +242,7 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
ValidateCommand.NotifyCanExecuteChanged();
StartCommand.NotifyCanExecuteChanged();
CancelCommand.NotifyCanExecuteChanged();
AcknowledgeCompletionCommand.NotifyCanExecuteChanged();
}
public void Close()
@ -231,6 +267,15 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
NotifyCommandStates();
}
/// <summary>LU4: OK on the completion panel. Closes the wizard and leaves
/// it ready to be reopened as an ordinary form (a user may reinstall
/// content later against a different DAT folder).</summary>
private void AcknowledgeCompletion()
{
IsCompleted = false;
IsOpen = false;
}
private void Open()
{
if (string.IsNullOrWhiteSpace(DatDirectory))
@ -306,6 +351,11 @@ public sealed class FirstRunInstallerViewModel : ObservableObject, IDisposable
Phase = LauncherInstallPhase.Completed;
ProgressPercent = 100;
Status = "Client content installed and verified. Launch is enabled.";
// LU4: raised only here, AFTER the record is published, so the
// launcher behind the dialog is already in its launch-enabled
// state when the user presses OK. The cancelled and failed
// branches below deliberately never reach this.
IsCompleted = true;
}
catch (OperationCanceledException)
{