diff --git a/.trae/specs/launcher-oobe-elevation-hardening/checklist.md b/.trae/specs/launcher-oobe-elevation-hardening/checklist.md
index 1afaff9..b9003ac 100644
--- a/.trae/specs/launcher-oobe-elevation-hardening/checklist.md
+++ b/.trae/specs/launcher-oobe-elevation-hardening/checklist.md
@@ -6,3 +6,4 @@
- [ ] `apply-update` and `plugin-install` do not auto-enter OOBE.
- [ ] Default plugin install does not request UAC.
- [ ] Logs include OOBE status, suppression reason, and launch source.
+- [ ] Startup presentation step inside `OobeWindow` (after data location) writes host `settings.json` and syncs Windows Run when autostart is chosen (Launcher executable).
diff --git a/LanMountainDesktop.Launcher/Services/HostAppSettingsOobeMerger.cs b/LanMountainDesktop.Launcher/Services/HostAppSettingsOobeMerger.cs
new file mode 100644
index 0000000..04a4b9b
--- /dev/null
+++ b/LanMountainDesktop.Launcher/Services/HostAppSettingsOobeMerger.cs
@@ -0,0 +1,134 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using LanMountainDesktop.Shared.Contracts.Launcher;
+
+namespace LanMountainDesktop.Launcher.Services;
+
+///
+/// 在 OOBE 中向 Host 的 settings.json 写入启动与展示相关字段,属性名与 Host
+/// AppSettingsSnapshot 的 JSON 序列化一致(PascalCase)。
+///
+public static class HostAppSettingsOobeMerger
+{
+ public const string ShowInTaskbarKey = "ShowInTaskbar";
+ public const string EnableFadeTransitionKey = "EnableFadeTransition";
+ public const string EnableSlideTransitionKey = "EnableSlideTransition";
+ public const string EnableFusedDesktopKey = "EnableFusedDesktop";
+ public const string EnableThreeFingerSwipeKey = "EnableThreeFingerSwipe";
+ public const string AutoStartWithWindowsKey = "AutoStartWithWindows";
+
+ public static string GetSettingsFilePath(string dataRoot) =>
+ Path.Combine(Path.GetFullPath(dataRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), "settings.json");
+
+ public static HostAppSettingsStartupDefaults LoadStartupDefaults(string settingsPath)
+ {
+ if (!File.Exists(settingsPath))
+ {
+ return HostAppSettingsStartupDefaults.Fallback;
+ }
+
+ try
+ {
+ var root = JsonNode.Parse(File.ReadAllText(settingsPath))?.AsObject();
+ if (root is null)
+ {
+ return HostAppSettingsStartupDefaults.Fallback;
+ }
+
+ var fade = ReadBool(root, EnableFadeTransitionKey, defaultValue: true);
+ var slide = ReadBool(root, EnableSlideTransitionKey, defaultValue: false);
+ var normalized = StartupVisualPreferencesResolver.FromFlags(fade, slide);
+
+ return new HostAppSettingsStartupDefaults(
+ ShowInTaskbar: ReadBool(root, ShowInTaskbarKey, defaultValue: false),
+ EnableFadeTransition: normalized.EnableFadeTransition,
+ EnableSlideTransition: normalized.EnableSlideTransition,
+ FusedPopupExperience: ReadBool(root, EnableFusedDesktopKey, defaultValue: false) &&
+ ReadBool(root, EnableThreeFingerSwipeKey, defaultValue: false),
+ AutoStartWithWindows: ReadBool(root, AutoStartWithWindowsKey, defaultValue: false));
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn($"HostAppSettingsOobeMerger: failed to read '{settingsPath}'. {ex.Message}");
+ return HostAppSettingsStartupDefaults.Fallback;
+ }
+ }
+
+ public static void MergeStartupPresentation(string settingsPath, HostAppSettingsStartupChoices choices)
+ {
+ var directory = Path.GetDirectoryName(settingsPath);
+ if (!string.IsNullOrWhiteSpace(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ JsonObject root;
+ if (File.Exists(settingsPath))
+ {
+ try
+ {
+ root = JsonNode.Parse(File.ReadAllText(settingsPath))?.AsObject() ?? new JsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn($"HostAppSettingsOobeMerger: replacing invalid JSON at '{settingsPath}'. {ex.Message}");
+ root = new JsonObject();
+ }
+ }
+ else
+ {
+ root = new JsonObject();
+ }
+
+ var normalized = StartupVisualPreferencesResolver.FromFlags(
+ choices.EnableFadeTransition,
+ choices.EnableSlideTransition);
+
+ root[ShowInTaskbarKey] = choices.ShowInTaskbar;
+ root[EnableFadeTransitionKey] = normalized.EnableFadeTransition;
+ root[EnableSlideTransitionKey] = normalized.EnableSlideTransition;
+ root[EnableFusedDesktopKey] = choices.FusedPopupExperience;
+ root[EnableThreeFingerSwipeKey] = choices.FusedPopupExperience;
+ root[AutoStartWithWindowsKey] = choices.AutoStartWithWindows;
+
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ File.WriteAllText(settingsPath, root.ToJsonString(options));
+ }
+
+ private static bool ReadBool(JsonObject root, string key, bool defaultValue)
+ {
+ if (!root.TryGetPropertyValue(key, out var node) || node is null)
+ {
+ return defaultValue;
+ }
+
+ return node switch
+ {
+ JsonValue v when v.TryGetValue(out var b) => b,
+ JsonValue v when v.TryGetValue(out var s) => bool.TryParse(s, out var p) && p,
+ _ => defaultValue
+ };
+ }
+}
+
+public readonly record struct HostAppSettingsStartupDefaults(
+ bool ShowInTaskbar,
+ bool EnableFadeTransition,
+ bool EnableSlideTransition,
+ bool FusedPopupExperience,
+ bool AutoStartWithWindows)
+{
+ public static HostAppSettingsStartupDefaults Fallback { get; } = new(
+ ShowInTaskbar: false,
+ EnableFadeTransition: true,
+ EnableSlideTransition: false,
+ FusedPopupExperience: false,
+ AutoStartWithWindows: false);
+}
+
+public readonly record struct HostAppSettingsStartupChoices(
+ bool ShowInTaskbar,
+ bool EnableFadeTransition,
+ bool EnableSlideTransition,
+ bool FusedPopupExperience,
+ bool AutoStartWithWindows);
diff --git a/LanMountainDesktop.Launcher/Services/LauncherWindowsStartupService.cs b/LanMountainDesktop.Launcher/Services/LauncherWindowsStartupService.cs
new file mode 100644
index 0000000..d94a912
--- /dev/null
+++ b/LanMountainDesktop.Launcher/Services/LauncherWindowsStartupService.cs
@@ -0,0 +1,82 @@
+using System;
+using Microsoft.Win32;
+
+namespace LanMountainDesktop.Launcher.Services;
+
+///
+/// 将当前 Windows 用户登录时自启动项指向本 Launcher 进程(与正式入口一致)。
+/// Host 内 WindowsStartupService 使用 Host 进程路径;
+/// OOBE 在 Launcher 内执行时应使用本类型,以便开机后仍走更新/版本协调流程。
+///
+public sealed class LauncherWindowsStartupService
+{
+ private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
+ private const string ValueName = "LanMountainDesktop";
+ private readonly string _startupCommand;
+
+ public LauncherWindowsStartupService()
+ {
+ var processPath = Environment.ProcessPath;
+ _startupCommand = string.IsNullOrWhiteSpace(processPath)
+ ? string.Empty
+ : $"\"{processPath}\"";
+ }
+
+ public bool IsEnabled()
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return false;
+ }
+
+ try
+ {
+ using var runKey = Registry.CurrentUser.OpenSubKey(RunKeyPath, writable: false);
+ return runKey?.GetValue(ValueName) is string value &&
+ !string.IsNullOrWhiteSpace(value);
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn($"LauncherWindowsStartup: failed to read Run key. {ex.Message}");
+ return false;
+ }
+ }
+
+ public bool SetEnabled(bool enabled)
+ {
+ if (!OperatingSystem.IsWindows())
+ {
+ return false;
+ }
+
+ if (enabled && string.IsNullOrWhiteSpace(_startupCommand))
+ {
+ return false;
+ }
+
+ try
+ {
+ using var runKey = Registry.CurrentUser.CreateSubKey(RunKeyPath);
+ if (runKey is null)
+ {
+ return false;
+ }
+
+ if (enabled)
+ {
+ runKey.SetValue(ValueName, _startupCommand, RegistryValueKind.String);
+ }
+ else
+ {
+ runKey.DeleteValue(ValueName, throwOnMissingValue: false);
+ }
+
+ return IsEnabled() == enabled;
+ }
+ catch (Exception ex)
+ {
+ Logger.Warn($"LauncherWindowsStartup: failed to set Run key. Enabled={enabled}. {ex.Message}");
+ return false;
+ }
+ }
+}
diff --git a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml
index eb55cfa..e098665 100644
--- a/LanMountainDesktop.Launcher/Views/OobeWindow.axaml
+++ b/LanMountainDesktop.Launcher/Views/OobeWindow.axaml
@@ -596,7 +596,142 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
("StartupPresentationBackButton") is { } startupPresentationBack)
+ {
+ startupPresentationBack.Click += OnStartupPresentationBackClick;
+ }
+
+ if (this.FindControl