diff --git a/docs/onboarding-redesign/00-welcome.png b/docs/onboarding-redesign/00-welcome.png new file mode 100644 index 000000000..bb7dc198b Binary files /dev/null and b/docs/onboarding-redesign/00-welcome.png differ diff --git a/docs/onboarding-redesign/01-capabilities-step1.png b/docs/onboarding-redesign/01-capabilities-step1.png new file mode 100644 index 000000000..08526b667 Binary files /dev/null and b/docs/onboarding-redesign/01-capabilities-step1.png differ diff --git a/docs/onboarding-redesign/02-capabilities-step2.png b/docs/onboarding-redesign/02-capabilities-step2.png new file mode 100644 index 000000000..e6d59f523 Binary files /dev/null and b/docs/onboarding-redesign/02-capabilities-step2.png differ diff --git a/docs/onboarding-redesign/03-capabilities-step3.png b/docs/onboarding-redesign/03-capabilities-step3.png new file mode 100644 index 000000000..684d42500 Binary files /dev/null and b/docs/onboarding-redesign/03-capabilities-step3.png differ diff --git a/docs/onboarding-redesign/04-gateway-onboard.png b/docs/onboarding-redesign/04-gateway-onboard.png new file mode 100644 index 000000000..25a49c64b Binary files /dev/null and b/docs/onboarding-redesign/04-gateway-onboard.png differ diff --git a/docs/onboarding-redesign/05-progress.png b/docs/onboarding-redesign/05-progress.png new file mode 100644 index 000000000..bbef21832 Binary files /dev/null and b/docs/onboarding-redesign/05-progress.png differ diff --git a/docs/onboarding-redesign/06-complete.png b/docs/onboarding-redesign/06-complete.png new file mode 100644 index 000000000..4ae2ff2f6 Binary files /dev/null and b/docs/onboarding-redesign/06-complete.png differ diff --git a/docs/onboarding-redesign/07-onboard-1-start.png b/docs/onboarding-redesign/07-onboard-1-start.png new file mode 100644 index 000000000..9a3fbca2a Binary files /dev/null and b/docs/onboarding-redesign/07-onboard-1-start.png differ diff --git a/docs/onboarding-redesign/08-onboard-2-setup-mode.png b/docs/onboarding-redesign/08-onboard-2-setup-mode.png new file mode 100644 index 000000000..7d35799ab Binary files /dev/null and b/docs/onboarding-redesign/08-onboard-2-setup-mode.png differ diff --git a/docs/onboarding-redesign/09-onboard-3-auth-method.png b/docs/onboarding-redesign/09-onboard-3-auth-method.png new file mode 100644 index 000000000..d64f40a1f Binary files /dev/null and b/docs/onboarding-redesign/09-onboard-3-auth-method.png differ diff --git a/docs/onboarding-redesign/10-onboard-4-web-search.png b/docs/onboarding-redesign/10-onboard-4-web-search.png new file mode 100644 index 000000000..56f02b408 Binary files /dev/null and b/docs/onboarding-redesign/10-onboard-4-web-search.png differ diff --git a/src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml b/src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml index 806fd3cad..9a853fd31 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml +++ b/src/OpenClaw.SetupEngine.UI/Pages/CapabilitiesPage.xaml @@ -5,46 +5,157 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" NavigationCacheMode="Disabled"> - + - + - - + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HorizontalAlignment="Stretch" Height="44" Margin="0,16,0,0" + Style="{StaticResource AccentButtonStyle}" + Click="LaunchButton_Click" /> diff --git a/src/OpenClaw.SetupEngine.UI/Pages/CompletePage.xaml.cs b/src/OpenClaw.SetupEngine.UI/Pages/CompletePage.xaml.cs index fb53c77eb..f1be74dfe 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/CompletePage.xaml.cs +++ b/src/OpenClaw.SetupEngine.UI/Pages/CompletePage.xaml.cs @@ -34,6 +34,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) SubtitleText.Text = "OpenClaw is ready to go"; ErrorCard.Visibility = Visibility.Collapsed; HelpLink.Visibility = Visibility.Collapsed; + SummaryPanel.Visibility = Visibility.Visible; } else { @@ -48,6 +49,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) : "Follow the steps below to resolve the setup issue and retry."; NodeModeBanner.Visibility = Visibility.Collapsed; StartupRow.Visibility = Visibility.Collapsed; + SummaryPanel.Visibility = Visibility.Collapsed; LaunchButton.Content = "Close"; // Show error card with details and log link diff --git a/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml b/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml index c153f3738..f66920a6f 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml +++ b/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml @@ -10,10 +10,11 @@ - @@ -33,24 +34,15 @@ - + HorizontalAlignment="Stretch" Height="44" + Style="{StaticResource AccentButtonStyle}" + Click="Next_Click" /> diff --git a/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml.cs b/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml.cs index 2e152a7de..aea89f5aa 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml.cs +++ b/src/OpenClaw.SetupEngine.UI/Pages/PermissionsPage.xaml.cs @@ -1,31 +1,17 @@ -using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using OpenClaw.SetupEngine.UI; -using Microsoft.Win32; -using Windows.Devices.Enumeration; -using Windows.Graphics.Capture; -using Windows.UI; namespace OpenClaw.SetupEngine.UI.Pages; +// Legacy standalone permissions step. Its content now lives inline on the merged +// CapabilitiesPage ("Windows permissions" section), so the main setup flow no longer +// navigates here — it is retained for the dev preview route and as a fallback. public sealed partial class PermissionsPage : Page { private SetupConfig? _config; - private record PermDef(string Name, string Glyph, string SettingsUri, Func> Check); - - private static readonly PermDef[] Permissions = - [ - new("Notifications", "\uEA8F", "ms-settings:notifications", CheckNotificationsAsync), - new("Camera", "\uE722", "ms-settings:privacy-webcam", CheckCameraAsync), - new("Microphone", "\uE720", "ms-settings:privacy-microphone", CheckMicrophoneAsync), - new("Location (optional)", "\uE81D", "ms-settings:privacy-location", CheckLocationAsync), - new("Screen Capture", "\uE7F4", "", CheckScreenCaptureAsync), - ]; - public PermissionsPage() { InitializeComponent(); @@ -40,106 +26,11 @@ protected override void OnNavigatedTo(NavigationEventArgs e) private async Task RefreshPermissions() { PermRows.Children.Clear(); - var isDark = ActualTheme == ElementTheme.Dark; - var cardBg = new SolidColorBrush(isDark - ? Color.FromArgb(255, 0x2C, 0x2C, 0x2C) - : Color.FromArgb(255, 0xF5, 0xF5, 0xF5)); - - foreach (var perm in Permissions) + foreach (var perm in SetupPermissionHelper.All) { var (status, granted) = await perm.Check(); - PermRows.Children.Add(BuildRow(perm, status, granted, cardBg, isDark)); - } - } - - private static FrameworkElement BuildRow(PermDef perm, string status, bool granted, Brush cardBg, bool isDark) - { - var statusColor = granted - ? Color.FromArgb(255, 0x2B, 0xC3, 0x6F) // green - : Color.FromArgb(255, 0xF4, 0xA6, 0xB0); // pink - - // Icon badge - var iconBadge = new Border - { - Width = 40, Height = 40, - CornerRadius = new CornerRadius(20), - Background = isDark - ? new SolidColorBrush(Microsoft.UI.Colors.Transparent) - : new SolidColorBrush(Color.FromArgb(255, 0x33, 0x33, 0x33)), - Child = new TextBlock - { - Text = perm.Glyph, - FontFamily = IconFonts.SymbolThemeFontFamily, - FontSize = 20, - Foreground = new SolidColorBrush(isDark ? Microsoft.UI.Colors.White : Microsoft.UI.Colors.White), - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - }, - VerticalAlignment = VerticalAlignment.Center, - Margin = new Thickness(0, 0, 16, 0), - }; - - // Title + status - var textStack = new StackPanel { Spacing = 2, VerticalAlignment = VerticalAlignment.Center }; - textStack.Children.Add(new TextBlock { Text = perm.Name, FontSize = 15, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold }); - textStack.Children.Add(new TextBlock { Text = status, FontSize = 13, Foreground = new SolidColorBrush(statusColor) }); - - // Open Settings button (only if URI exists) - FrameworkElement actionCol; - if (!string.IsNullOrEmpty(perm.SettingsUri)) - { - var btn = new Button - { - Padding = new Thickness(8, 6, 8, 6), - Background = new SolidColorBrush(Microsoft.UI.Colors.Transparent), - BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.Transparent), - }; - var btnContent = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 6 }; - btnContent.Children.Add(new TextBlock - { - Text = "\uE8A7", FontFamily = IconFonts.SymbolThemeFontFamily, - FontSize = 14, VerticalAlignment = VerticalAlignment.Center - }); - btnContent.Children.Add(new TextBlock { Text = "Open Settings", FontSize = 13, VerticalAlignment = VerticalAlignment.Center }); - btn.Content = btnContent; - var uri = perm.SettingsUri; - btn.Click += async (_, _) => - { - try { await Windows.System.Launcher.LaunchUriAsync(new Uri(uri)); } - // slopwatch-ignore: SW003 UI helper action is best-effort and failure should not break the owning UI flow. - catch { /* best effort */ } - }; - actionCol = btn; + PermRows.Children.Add(SetupPermissionHelper.BuildRow(perm, status, granted)); } - else - { - actionCol = new Border { Width = 1 }; - } - - var grid = new Grid - { - ColumnDefinitions = - { - new ColumnDefinition { Width = GridLength.Auto }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = GridLength.Auto }, - }, - VerticalAlignment = VerticalAlignment.Center, - }; - Grid.SetColumn(iconBadge, 0); - Grid.SetColumn(textStack, 1); - Grid.SetColumn(actionCol, 2); - grid.Children.Add(iconBadge); - grid.Children.Add(textStack); - grid.Children.Add(actionCol); - - return new Border - { - Child = grid, - Background = cardBg, - CornerRadius = new CornerRadius(8), - Padding = new Thickness(20, 18, 20, 18), - }; } private void Refresh_Click(object sender, RoutedEventArgs e) => _ = RefreshPermissions(); @@ -149,81 +40,4 @@ private void BackToWizard_Click(object sender, RoutedEventArgs e) private void Next_Click(object sender, RoutedEventArgs e) => SetupWindow.Active?.NavigateToComplete(true, TimeSpan.Zero, null); - - // ── Permission checks (passive, no OS consent dialogs) ── - - private static Task<(string, bool)> CheckNotificationsAsync() - { - try - { - using var key = Registry.CurrentUser.OpenSubKey( - @"Software\Microsoft\Windows\CurrentVersion\PushNotifications"); - if (key?.GetValue("ToastEnabled") is int val && val == 0) - return Task.FromResult(("Disabled", false)); - return Task.FromResult(("Enabled", true)); - } - catch - { - return Task.FromResult(("Unable to check", false)); - } - } - - private static async Task<(string, bool)> CheckCameraAsync() - { - try - { - var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture); - if (devices.Count == 0) return ("No camera detected", false); - var access = DeviceAccessInformation.CreateFromDeviceClass(DeviceClass.VideoCapture); - return access.CurrentStatus == DeviceAccessStatus.Allowed - ? ("Allowed", true) - : ("Denied — open Settings to allow", false); - } - catch { return ("Unable to check", false); } - } - - private static async Task<(string, bool)> CheckMicrophoneAsync() - { - try - { - var devices = await DeviceInformation.FindAllAsync(DeviceClass.AudioCapture); - if (devices.Count == 0) return ("No microphone detected", false); - var access = DeviceAccessInformation.CreateFromDeviceClass(DeviceClass.AudioCapture); - return access.CurrentStatus == DeviceAccessStatus.Allowed - ? ("Allowed", true) - : ("Denied — open Settings to allow", false); - } - catch { return ("Unable to check", false); } - } - - private static Task<(string, bool)> CheckLocationAsync() - { - try - { - using var sysKey = Registry.LocalMachine.OpenSubKey( - @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\location"); - if (sysKey?.GetValue("Value") is string sv && sv.Equals("Deny", StringComparison.OrdinalIgnoreCase)) - return Task.FromResult(("Location services disabled", false)); - - using var userKey = Registry.CurrentUser.OpenSubKey( - @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\location"); - var uv = userKey?.GetValue("Value") as string; - if (uv != null && uv.Equals("Deny", StringComparison.OrdinalIgnoreCase)) - return Task.FromResult(("Disabled for this user", false)); - - return Task.FromResult(("Location services enabled", true)); - } - catch { return Task.FromResult(("Unable to check", false)); } - } - - private static Task<(string, bool)> CheckScreenCaptureAsync() - { - try - { - return Task.FromResult(GraphicsCaptureSession.IsSupported() - ? ("Available — uses picker per capture", true) - : ("Not supported on this device", false)); - } - catch { return Task.FromResult(("Unable to check", false)); } - } } diff --git a/src/OpenClaw.SetupEngine.UI/Pages/ProgressPage.xaml b/src/OpenClaw.SetupEngine.UI/Pages/ProgressPage.xaml index cff2dd1fb..963a6be9f 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/ProgressPage.xaml +++ b/src/OpenClaw.SetupEngine.UI/Pages/ProgressPage.xaml @@ -5,65 +5,56 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" NavigationCacheMode="Disabled"> - + - + - - + - - - - + - - - - - - + + - - - - - + + + + + + + + + - - + - diff --git a/src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs b/src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs index 3762b4ead..eb488d61d 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs +++ b/src/OpenClaw.SetupEngine.UI/Pages/WelcomePage.xaml.cs @@ -2,13 +2,11 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Hosting; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using OpenClaw.SetupEngine; using OpenClaw.SetupEngine.UI; using OpenClaw.Shared; using System.Numerics; -using Windows.UI; namespace OpenClaw.SetupEngine.UI.Pages; @@ -29,14 +27,6 @@ protected override void OnNavigatedTo(NavigationEventArgs e) private void OnLoaded(object sender, RoutedEventArgs e) { - var isDark = ActualTheme == ElementTheme.Dark; - InfoCard.Background = new SolidColorBrush(isDark - ? Color.FromArgb(255, 0x2C, 0x2C, 0x2C) - : Color.FromArgb(255, 0xF0, 0xF0, 0xF0)); - - InfoText.Text = "This local setup installs a small WSL Linux instance dedicated to OpenClaw. " - + "If you'd rather connect to an existing or remote gateway, choose Advanced setup."; - StartLobsterBreatheAnimation(); } diff --git a/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml b/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml index 1097c29bf..0e287c19e 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml +++ b/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml @@ -5,35 +5,43 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" NavigationCacheMode="Disabled"> - + - - - + + + - + + + - - + + @@ -42,9 +50,11 @@ - + + @@ -74,16 +84,8 @@ Click="Secondary_Click" IsEnabled="False" /> + Style="{StaticResource AccentButtonStyle}" + Click="Primary_Click" IsEnabled="False" /> diff --git a/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs b/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs index bc2a32a47..869c8ac5c 100644 --- a/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs +++ b/src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs @@ -41,9 +41,44 @@ public WizardPage() protected override void OnNavigatedTo(NavigationEventArgs e) { _config = e.Parameter as SetupConfig ?? new SetupConfig(); + if (SetupPreview.IsActive) + { + RenderWizardPreview(); + return; + } _ = StartWizardAsync(); } + private void RenderWizardPreview() + { + BusyRing.Visibility = Visibility.Collapsed; + BusyRing.IsActive = false; + StatusText.Text = "Answer the gateway setup question"; + ShowRecoveryActions(); + AppendTranscriptTurn("Welcome — let's connect your agent", null); + AppendTranscriptTurn("Choose your AI provider", "Anthropic — Claude"); + AppendTranscriptTurn("Paste your API key", "••••••"); + + _stepType = "select"; + _stepId = "model"; + TitleText.Text = "Default model"; + SelectOptions.Visibility = Visibility.Visible; + foreach (var (val, lbl) in new[] { ("opus", "claude-opus-4.8"), ("sonnet", "claude-sonnet-4.6"), ("haiku", "claude-haiku-4.5") }) + { + SelectOptions.Children.Add(new RadioButton + { + Content = lbl, + Tag = val, + GroupName = "preview", + Padding = new Thickness(8, 6, 8, 6), + }); + } + ((RadioButton)SelectOptions.Children[0]).IsChecked = true; + PrimaryButton.Content = "Continue"; + PrimaryButton.IsEnabled = true; + SecondaryButton.Visibility = Visibility.Collapsed; + } + protected override void OnNavigatedFrom(NavigationEventArgs e) { _ = DisconnectAsync(); @@ -64,6 +99,7 @@ private async Task StartWizardAsync() _sessionId = ""; _wizardStepCount = 0; _stepVisits.Clear(); + TranscriptPanel.Children.Clear(); SetBusy("Connecting to gateway..."); _client = await ConnectClientAsync(); _client.StatusChanged += OnWizardClientStatusChanged; @@ -173,10 +209,8 @@ private async Task ApplyPayloadAsync(JsonElement payload) } await DisconnectAsync(); - if (_config!.SkipPermissions) - SetupWindow.Active?.NavigateToComplete(true, TimeSpan.Zero, _config.LogPath); - else - SetupWindow.Active?.NavigateToPermissions(); + // Permissions now live inline on the capabilities screen; skip the standalone step. + SetupWindow.Active?.NavigateToComplete(true, TimeSpan.Zero, _config!.LogPath); return; } @@ -441,6 +475,9 @@ private async Task SendCurrentAnswerAsync(bool skip) return; } + var answeredQuestion = TitleText.Text; + var answeredLabel = CurrentAnswerLabel(skip); + SetBusy(skip ? "Skipping..." : "Submitting..."); // The console banner shows output that arrived between the last payload // render and the user's current click. Once they answer, those messages @@ -462,7 +499,9 @@ private async Task SendCurrentAnswerAsync(bool skip) if (generation != _operationGeneration) return; + AppendTranscriptTurn(answeredQuestion, answeredLabel); await ApplyPayloadAsync(payload); + ScrollActiveIntoView(); } catch (Exception ex) { @@ -548,6 +587,107 @@ private int TimeoutForCurrentStep() : 30_000; } + // ── Transcript (vertical accreting list). Presentation only: each answered + // step is frozen into a compact ✓ row above the active step card. No protocol change. + private void AppendTranscriptTurn(string question, string? answer) + { + if (string.IsNullOrWhiteSpace(question)) + return; + + var grid = new Grid { Padding = new Thickness(2, 6, 2, 6) }; + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + + var dot = new Border + { + Width = 22, + Height = 22, + CornerRadius = new CornerRadius(11), + Background = ResourceBrush("SystemFillColorSuccessBrush"), + Margin = new Thickness(0, 1, 12, 0), + VerticalAlignment = VerticalAlignment.Top, + Child = new FontIcon + { + Glyph = "\uE73E", + FontSize = 11, + Foreground = new SolidColorBrush(Microsoft.UI.Colors.White), + }, + }; + + var stack = new StackPanel { Spacing = 2, VerticalAlignment = VerticalAlignment.Center }; + stack.Children.Add(new TextBlock + { + Text = question, + FontSize = 14, + Foreground = ResourceBrush("TextFillColorSecondaryBrush"), + TextWrapping = TextWrapping.Wrap, + }); + if (!string.IsNullOrWhiteSpace(answer)) + { + stack.Children.Add(new TextBlock + { + Text = answer, + FontSize = 13, + Foreground = ResourceBrush("TextFillColorPrimaryBrush"), + TextWrapping = TextWrapping.Wrap, + }); + } + + Grid.SetColumn(dot, 0); + Grid.SetColumn(stack, 1); + grid.Children.Add(dot); + grid.Children.Add(stack); + TranscriptPanel.Children.Add(grid); + } + + private string? CurrentAnswerLabel(bool skip) + { + if (skip) + return _stepType == "confirm" ? "No" : "Skipped"; + + return _stepType switch + { + "confirm" => "Yes", + "text" => _sensitive ? "••••••" : (string.IsNullOrEmpty(TextInput.Text) ? null : TextInput.Text), + "select" or "multiselect" => LabelForValues(GetSelectedOptionValues()), + _ => null, + }; + } + + private string? LabelForValues(string[] values) + { + if (values.Length == 0) + return null; + var labels = values.Select(v => _options.FirstOrDefault(o => o.Value == v)?.Label ?? v); + return string.Join(", ", labels); + } + + private void ScrollActiveIntoView() + { + MainScroller.UpdateLayout(); + // Bring the active step card's TITLE into view (just below the last answered + // step) rather than jumping to the very bottom — scrolling to the bottom hid + // the step's introduction/question when it had many options (e.g. web search). + if (MainScroller.Content is FrameworkElement content) + { + try + { + var cardTop = StepCard.TransformToVisual(content) + .TransformPoint(new Windows.Foundation.Point(0, 0)).Y; + // Leave a little room above so the most recent answered step stays + // visible for continuity, but keep the active title at the top. + var target = Math.Max(0, cardTop - 44); + MainScroller.ChangeView(null, target, null); + return; + } + catch + { + // Fall back to the previous behaviour if the transform fails. + } + } + MainScroller.ChangeView(null, MainScroller.ScrollableHeight, null); + } + private void ResetInputs() { SelectOptions.Children.Clear(); @@ -764,10 +904,8 @@ private async Task SkipWizardAsync() HideRecoveryActions(); SetBusy("Skipping wizard..."); await CancelCurrentSessionAsync(); - if (_config!.SkipPermissions) - SetupWindow.Active?.NavigateToComplete(true, TimeSpan.Zero, _config.LogPath); - else - SetupWindow.Active?.NavigateToPermissions(); + // Permissions now live inline on the capabilities screen; skip the standalone step. + SetupWindow.Active?.NavigateToComplete(true, TimeSpan.Zero, _config!.LogPath); } private async Task CancelCurrentSessionAsync() diff --git a/src/OpenClaw.SetupEngine.UI/SetupPreview.cs b/src/OpenClaw.SetupEngine.UI/SetupPreview.cs new file mode 100644 index 000000000..bfd4d41d6 --- /dev/null +++ b/src/OpenClaw.SetupEngine.UI/SetupPreview.cs @@ -0,0 +1,38 @@ +using System; + +namespace OpenClaw.SetupEngine.UI; + +/// +/// Dev-only preview routing for the setup window. Lets OPENCLAW_SETUP_PREVIEW_PAGE +/// open a single page directly with sample content (no pipeline, no gateway) for visual +/// iteration during development. +/// +/// In Release builds this is fully inert: the environment variable is never read, +/// so the preview route can never bypass the setup run lock or the real install pipeline +/// in production. The gating lives here so call sites stay simple and there is exactly one +/// place that reads the variable. +/// +internal static class SetupPreview +{ + private const string EnvVar = "OPENCLAW_SETUP_PREVIEW_PAGE"; + + /// + /// The requested preview page (lower-cased, trimmed), or null when preview mode + /// is off. Always null in Release builds. + /// + public static string? RequestedPage + { +#if DEBUG + get + { + var page = Environment.GetEnvironmentVariable(EnvVar); + return string.IsNullOrWhiteSpace(page) ? null : page.Trim().ToLowerInvariant(); + } +#else + get => null; +#endif + } + + /// True when a preview page is active. Always false in Release builds. + public static bool IsActive => RequestedPage is not null; +} diff --git a/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml b/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml index 3d9f4b147..f2361bced 100644 --- a/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml +++ b/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml @@ -6,6 +6,83 @@ Title="OpenClaw Setup"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml.cs b/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml.cs index 17872724a..f7c2b3028 100644 --- a/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml.cs +++ b/src/OpenClaw.SetupEngine.UI/SetupWindow.xaml.cs @@ -82,6 +82,13 @@ public SetupWindow(string? configPath = null) Active = null; }; + var previewPage = SetupPreview.RequestedPage; + if (previewPage != null) + { + NavigatePreview(previewPage); + return; + } + if (!SetupRunLock.TryAcquire(SetupContext.ResolveDataDir(), out _setupLock, out var lockMessage)) { RootFrame.Navigate(typeof(CompletePage), new CompletePageArgs(false, TimeSpan.Zero, null, lockMessage ?? "Another setup run is active.")); @@ -111,6 +118,20 @@ public void NavigateToWizard() public void NavigateToComplete(bool success, TimeSpan elapsed, string? logPath, string? errorMessage = null) => RootFrame.Navigate(typeof(CompletePage), new CompletePageArgs(success, elapsed, logPath, errorMessage)); + // Dev-only: OPENCLAW_SETUP_PREVIEW_PAGE= + // opens a page directly with sample content (no pipeline, no gateway). Used for visual iteration only. + private void NavigatePreview(string page) => RootFrame.Navigate( + page switch + { + "capabilities" => typeof(CapabilitiesPage), + "progress" => typeof(ProgressPage), + "wizard" => typeof(WizardPage), + "permissions" => typeof(PermissionsPage), + "complete" => typeof(CompletePage), + _ => typeof(WelcomePage), + }, + page == "complete" ? new CompletePageArgs(true, TimeSpan.FromMinutes(3), null) : _config); + public void RequestAdvancedSetup() { AdvancedSetupRequested?.Invoke(this, EventArgs.Empty);