diff --git a/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs b/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs
new file mode 100644
index 0000000000..4008e954e8
--- /dev/null
+++ b/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs
@@ -0,0 +1,48 @@
+using NUnit.Framework;
+
+using io.github.hatayama.UnityCliLoop.Presentation;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Characterization tests for Tool Settings list layout signatures.
+ ///
+ public sealed class ToolSettingsSectionLayoutSignatureTests
+ {
+ [Test]
+ public void Create_WhenGroupsHaveTools_IncludesGroupMarkersAndToolFields()
+ {
+ // Pins the rebuild signature format used to decide whether the ListView must rebuild.
+ ToolSettingsSectionData data = new(
+ showToolSettings: true,
+ builtInTools: new[]
+ {
+ new ToolToggleItem("compile", true, false, "Compile the project")
+ },
+ thirdPartyTools: new[]
+ {
+ new ToolToggleItem("vendor.tool", false, true, "Vendor tool")
+ },
+ isRegistryAvailable: true);
+
+ string signature = ToolSettingsSectionLayoutSignature.Create(data);
+
+ Assert.That(signature, Is.EqualTo("B:compile|Compile the project|;T:vendor.tool|Vendor tool|;"));
+ }
+
+ [Test]
+ public void Create_WhenGroupsAreEmpty_KeepsEmptyGroupMarkers()
+ {
+ // Pins empty-group signatures so collapsed/empty catalogs stay stable across refreshes.
+ ToolSettingsSectionData data = new(
+ showToolSettings: true,
+ builtInTools: System.Array.Empty(),
+ thirdPartyTools: System.Array.Empty(),
+ isRegistryAvailable: true);
+
+ string signature = ToolSettingsSectionLayoutSignature.Create(data);
+
+ Assert.That(signature, Is.EqualTo("B:;T:;"));
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs.meta b/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs.meta
new file mode 100644
index 0000000000..fb0ceae4dc
--- /dev/null
+++ b/Assets/Tests/Editor/ToolSettingsSectionLayoutSignatureTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c25e44ffdb1e4c2f928333fdfd7079cd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs
new file mode 100644
index 0000000000..d46677f1d1
--- /dev/null
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+using io.github.hatayama.UnityCliLoop.Application;
+using io.github.hatayama.UnityCliLoop.Domain;
+using io.github.hatayama.UnityCliLoop.ToolContracts;
+
+namespace io.github.hatayama.UnityCliLoop.Presentation
+{
+ ///
+ /// Owns Setup Wizard CLI-step workflow state and async install/repair operations.
+ ///
+ internal sealed class SetupWizardCliWorkflowController
+ {
+ private readonly SetupWizardCliStepPresenter _cliStepPresenter;
+ private readonly CliSetupApplicationService _cliSetupApplicationService;
+ private readonly Action _refreshUi;
+
+ private bool _isInstallingCli;
+ private bool _needsCliPathSetup;
+
+ internal SetupWizardCliWorkflowController(
+ VisualElement cliStatusIcon,
+ Label cliStatusLabel,
+ Button installCliButton,
+ CliSetupApplicationService cliSetupApplicationService,
+ Action refreshUi)
+ {
+ Debug.Assert(cliSetupApplicationService != null, "cliSetupApplicationService must not be null");
+ Debug.Assert(refreshUi != null, "refreshUi must not be null");
+
+ _cliSetupApplicationService = cliSetupApplicationService
+ ?? throw new ArgumentNullException(nameof(cliSetupApplicationService));
+ _refreshUi = refreshUi
+ ?? throw new ArgumentNullException(nameof(refreshUi));
+
+ _cliStepPresenter = new SetupWizardCliStepPresenter(
+ cliStatusIcon,
+ cliStatusLabel,
+ installCliButton,
+ HandleInstallCli);
+ }
+
+ internal void ShowChecking()
+ {
+ _cliStepPresenter.ShowChecking();
+ }
+
+ internal async Task RefreshAndUpdateAsync(CancellationToken ct)
+ {
+ await _cliSetupApplicationService.ForceRefreshCliVersionAsync(ct);
+ _needsCliPathSetup = await ShouldRepairCliPathSetupAsync(ct);
+ string cliVersion = _cliSetupApplicationService.GetCachedCliVersion();
+ UpdateFromCachedState(cliVersion);
+ return IsCliInstalled(cliVersion);
+ }
+
+ private void UpdateFromCachedState(string cliVersion = null)
+ {
+ if (cliVersion == null)
+ {
+ cliVersion = _cliSetupApplicationService.GetCachedCliVersion();
+ }
+
+ bool cliIsDispatcher = _cliSetupApplicationService.GetCachedCliIsDispatcher();
+ string requiredCliVersion = GetMinimumRequiredCliVersion();
+ bool cliInstalled = IsCliInstalled(cliVersion);
+ _cliStepPresenter.Update(
+ cliInstalled,
+ cliVersion,
+ cliIsDispatcher,
+ requiredCliVersion,
+ _isInstallingCli,
+ _needsCliPathSetup);
+ }
+
+ private void HandleInstallCli()
+ {
+ HandleInstallCliAsync(CancellationToken.None).Forget();
+ }
+
+ private async Task HandleInstallCliAsync(CancellationToken ct)
+ {
+ await RefreshCliPrimaryActionStateAsync(ct);
+
+ string cliVersion = _cliSetupApplicationService.GetCachedCliVersion();
+ bool cliIsDispatcher = _cliSetupApplicationService.GetCachedCliIsDispatcher();
+ CliSetupCompatibilityState state = EvaluateCliSetupCompatibilityForSetupWizard(
+ cliVersion,
+ cliIsDispatcher,
+ GetMinimumRequiredCliVersion());
+ if (SetupWizardWindow.ShouldRepairCliPathFromPrimaryButton(_needsCliPathSetup, state.NeedsUpdate))
+ {
+ await HandleRepairCliPathSetup(ct);
+ return;
+ }
+
+ bool wasCliInstalledBeforeInstall = _cliSetupApplicationService.IsCliInstalled();
+ _needsCliPathSetup = false;
+ _isInstallingCli = true;
+ _cliStepPresenter.Update(
+ cliInstalled: false,
+ cliVersion: null,
+ cliIsDispatcher: false,
+ requiredCliVersion: GetMinimumRequiredCliVersion(),
+ isInstallingCli: _isInstallingCli,
+ needsCliPathSetup: _needsCliPathSetup);
+
+ try
+ {
+ CliInstallResult result = await _cliSetupApplicationService.InstallGlobalCliAsync(
+ UnityEngine.Application.platform,
+ ct);
+
+ if (!result.Success)
+ {
+ NativeCliInstallCommand command = _cliSetupApplicationService.GetGlobalCliInstallCommand(
+ UnityEngine.Application.platform,
+ true);
+ EditorUtility.DisplayDialog(
+ "Installation Failed",
+ $"Failed to install uloop CLI.\n\n{result.ErrorOutput}\n\n"
+ + $"You can install manually:\n {command.ManualCommand}",
+ "OK");
+ return;
+ }
+
+ await CliPathSetupPrompt.EnsureVisibleAndShowResultAsync(
+ UnityEngine.Application.platform,
+ _cliSetupApplicationService,
+ ct);
+ _needsCliPathSetup = await ShouldRepairCliPathSetupAsync(ct);
+ }
+ finally
+ {
+ _isInstallingCli = false;
+ _refreshUi(CliInstallRefreshPolicy.ShouldRefreshSkillsAfterCliInstall(
+ wasCliInstalledBeforeInstall));
+ }
+ }
+
+ private async Task RefreshCliPrimaryActionStateAsync(CancellationToken ct)
+ {
+ _cliStepPresenter.ShowRefreshingPrimaryAction();
+
+ try
+ {
+ await _cliSetupApplicationService.ForceRefreshCliVersionAsync(ct);
+ _needsCliPathSetup = await ShouldRepairCliPathSetupAsync(ct);
+ }
+ finally
+ {
+ RefreshCliStepFromCachedState();
+ }
+ }
+
+ private void RefreshCliStepFromCachedState()
+ {
+ UpdateFromCachedState();
+ }
+
+ private async Task HandleRepairCliPathSetup(CancellationToken ct)
+ {
+ _isInstallingCli = true;
+ _cliStepPresenter.Update(
+ cliInstalled: true,
+ cliVersion: _cliSetupApplicationService.GetCachedCliVersion(),
+ cliIsDispatcher: _cliSetupApplicationService.GetCachedCliIsDispatcher(),
+ requiredCliVersion: GetMinimumRequiredCliVersion(),
+ isInstallingCli: _isInstallingCli,
+ needsCliPathSetup: _needsCliPathSetup);
+
+ try
+ {
+ await CliPathSetupPrompt.EnsureVisibleAndShowResultAsync(
+ UnityEngine.Application.platform,
+ _cliSetupApplicationService,
+ ct);
+ _needsCliPathSetup = await ShouldRepairCliPathSetupAsync(ct);
+ }
+ finally
+ {
+ _isInstallingCli = false;
+ _refreshUi(true);
+ }
+ }
+
+ private async Task ShouldRepairCliPathSetupAsync(CancellationToken ct)
+ {
+ bool hasPackageOwnedCurrentUserInstall =
+ _cliSetupApplicationService.HasPackageOwnedCurrentUserInstall(UnityEngine.Application.platform);
+ if (!SetupWizardWindow.ShouldCheckCliPathSetupForSetupWizard(
+ UnityEngine.Application.platform,
+ hasPackageOwnedCurrentUserInstall))
+ {
+ return false;
+ }
+
+ bool isCliVisibleFromShell = await _cliSetupApplicationService.IsCliVisibleFromShellAsync(
+ UnityEngine.Application.platform,
+ ct);
+ return !isCliVisibleFromShell;
+ }
+
+ private static CliSetupCompatibilityState EvaluateCliSetupCompatibilityForSetupWizard(
+ string cliVersion,
+ bool cliIsDispatcher,
+ string requiredCliVersion)
+ {
+ return CliSetupCompatibility.Evaluate(
+ cliVersion,
+ cliIsDispatcher,
+ requiredCliVersion);
+ }
+
+ private string GetMinimumRequiredCliVersion()
+ {
+ return _cliSetupApplicationService.GetMinimumRequiredCliVersion();
+ }
+
+ private static bool IsCliInstalled(string cliVersion)
+ {
+ return !string.IsNullOrEmpty(cliVersion);
+ }
+ }
+}
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs.meta b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs.meta
new file mode 100644
index 0000000000..0a5cb3feb4
--- /dev/null
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7a13fc2d47454255863d9f9f35f90719
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs
new file mode 100644
index 0000000000..20b32bbc2b
--- /dev/null
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs
@@ -0,0 +1,290 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+using io.github.hatayama.UnityCliLoop.Application;
+using io.github.hatayama.UnityCliLoop.Domain;
+using io.github.hatayama.UnityCliLoop.ToolContracts;
+
+namespace io.github.hatayama.UnityCliLoop.Presentation
+{
+ ///
+ /// Owns Setup Wizard skills-step workflow state and async install/refresh operations.
+ ///
+ internal sealed class SetupWizardSkillsWorkflowController
+ {
+ private readonly VisualElement _groupSkillsRow;
+ private readonly EnumField _skillsTargetField;
+ private readonly Toggle _groupSkillsToggle;
+ private readonly Label _groupSkillsLabel;
+ private readonly SetupWizardSkillsStepPresenter _skillsStepPresenter;
+ private readonly SkillSetupUseCase _skillSetupUseCase;
+ private readonly IUnityCliLoopEditorSettingsPort _editorSettingsPort;
+ private readonly CliSetupApplicationService _cliSetupApplicationService;
+ private readonly Action _scheduleResizeToContent;
+
+ private bool _isInstallingSkills;
+ private bool _isSkillsTargetFieldInitialized;
+ private bool _shouldUseFirstInstallSkillsUi;
+ private bool _installSkillsFlat;
+ private CancellationTokenSource _skillInstallStateRefreshCts;
+ private SkillsTarget _skillsTarget = SkillsTarget.Claude;
+
+ internal SetupWizardSkillsWorkflowController(
+ VisualElement groupSkillsRow,
+ EnumField skillsTargetField,
+ Toggle groupSkillsToggle,
+ Label groupSkillsLabel,
+ VisualElement skillsTargetRow,
+ VisualElement skillsTargetList,
+ VisualElement skillsStatusDivider,
+ Label skillsStatusLabel,
+ Button installSkillsButton,
+ SkillSetupUseCase skillSetupUseCase,
+ IUnityCliLoopEditorSettingsPort editorSettingsPort,
+ CliSetupApplicationService cliSetupApplicationService,
+ Action scheduleResizeToContent,
+ string lastSeenSetupWizardVersionBeforeOpen)
+ {
+ Debug.Assert(groupSkillsRow != null, "groupSkillsRow must not be null");
+ Debug.Assert(skillsTargetField != null, "skillsTargetField must not be null");
+ Debug.Assert(groupSkillsToggle != null, "groupSkillsToggle must not be null");
+ Debug.Assert(groupSkillsLabel != null, "groupSkillsLabel must not be null");
+ Debug.Assert(skillSetupUseCase != null, "skillSetupUseCase must not be null");
+ Debug.Assert(editorSettingsPort != null, "editorSettingsPort must not be null");
+ Debug.Assert(cliSetupApplicationService != null, "cliSetupApplicationService must not be null");
+ Debug.Assert(scheduleResizeToContent != null, "scheduleResizeToContent must not be null");
+
+ _groupSkillsRow = groupSkillsRow ?? throw new ArgumentNullException(nameof(groupSkillsRow));
+ _skillsTargetField = skillsTargetField
+ ?? throw new ArgumentNullException(nameof(skillsTargetField));
+ _groupSkillsToggle = groupSkillsToggle
+ ?? throw new ArgumentNullException(nameof(groupSkillsToggle));
+ _groupSkillsLabel = groupSkillsLabel
+ ?? throw new ArgumentNullException(nameof(groupSkillsLabel));
+ _skillSetupUseCase = skillSetupUseCase
+ ?? throw new ArgumentNullException(nameof(skillSetupUseCase));
+ _editorSettingsPort = editorSettingsPort
+ ?? throw new ArgumentNullException(nameof(editorSettingsPort));
+ _cliSetupApplicationService = cliSetupApplicationService
+ ?? throw new ArgumentNullException(nameof(cliSetupApplicationService));
+ _scheduleResizeToContent = scheduleResizeToContent
+ ?? throw new ArgumentNullException(nameof(scheduleResizeToContent));
+
+ _skillsStepPresenter = new SetupWizardSkillsStepPresenter(
+ skillsTargetRow,
+ skillsTargetList,
+ skillsStatusDivider,
+ skillsStatusLabel,
+ installSkillsButton,
+ HandleInstallSkills);
+
+ InitializeFirstInstallSkillsUiState(lastSeenSetupWizardVersionBeforeOpen);
+ }
+
+ internal void InitializeSkillsTargetField()
+ {
+ if (_isSkillsTargetFieldInitialized) return;
+
+ _skillsTargetField.Init(_skillsTarget);
+ _skillsTargetField.RegisterValueChangedCallback(evt =>
+ {
+ if (evt.newValue is SkillsTarget newTarget)
+ {
+ _skillsTarget = newTarget;
+ RefreshSkillsSection();
+ }
+ });
+ _isSkillsTargetFieldInitialized = true;
+ }
+
+ internal void InitializeGroupSkillsToggle()
+ {
+ ApplyFlatSkillInstallPreference();
+ ViewDataBinder.SetVisible(_groupSkillsRow, false);
+ _groupSkillsToggle.SetValueWithoutNotify(!_installSkillsFlat);
+ _groupSkillsToggle.RegisterValueChangedCallback(evt =>
+ {
+ evt.StopPropagation();
+ ApplyFlatSkillInstallPreference();
+ RefreshSkillsSection();
+ });
+ _groupSkillsLabel.RegisterCallback(HandleGroupSkillsRowClicked);
+ }
+
+ internal void ShowChecking()
+ {
+ ViewDataBinder.SetVisible(_groupSkillsRow, false);
+ _groupSkillsToggle.SetEnabled(false);
+ _skillsStepPresenter.ShowChecking(_shouldUseFirstInstallSkillsUi);
+ }
+
+ internal void RefreshSkillsSection()
+ {
+ string cachedCliVersion = _cliSetupApplicationService.GetCachedCliVersion();
+ string projectRoot = UnityCliLoopPathResolver.GetProjectRoot();
+ bool cliInstalled = IsCliInstalled(cachedCliVersion);
+ List targets = DetectDisplayedSkillTargetsFast(projectRoot);
+ bool canManageSkills = SetupWizardWindow.CanManageSkills(cliInstalled);
+ UpdateSkillsStep(canManageSkills, targets);
+ BeginRefreshDisplayedSkillTargets(canManageSkills);
+ _scheduleResizeToContent();
+ }
+
+ internal void ApplyFastSkillsState(bool cliInstalled)
+ {
+ string projectRoot = UnityCliLoopPathResolver.GetProjectRoot();
+ List targets = DetectDisplayedSkillTargetsFast(projectRoot);
+ bool canManageSkills = SetupWizardWindow.CanManageSkills(cliInstalled);
+ UpdateSkillsStep(canManageSkills, targets);
+ BeginRefreshDisplayedSkillTargets(canManageSkills);
+ }
+
+ internal void CancelSkillInstallStateRefresh()
+ {
+ if (_skillInstallStateRefreshCts == null)
+ {
+ return;
+ }
+
+ _skillInstallStateRefreshCts.Cancel();
+ _skillInstallStateRefreshCts.Dispose();
+ _skillInstallStateRefreshCts = null;
+ }
+
+ private void InitializeFirstInstallSkillsUiState(string lastSeenSetupWizardVersionBeforeOpen)
+ {
+ _shouldUseFirstInstallSkillsUi = SetupWizardWindow.ShouldUseFirstInstallSkillsUi(
+ lastSeenSetupWizardVersionBeforeOpen);
+ }
+
+ private List DetectDisplayedSkillTargets(string projectRoot)
+ {
+ return _skillSetupUseCase.DetectSkillTargetsForLayoutAtProjectRoot(projectRoot, !_installSkillsFlat);
+ }
+
+ private List DetectDisplayedSkillTargetsFast(string projectRoot)
+ {
+ return _skillSetupUseCase.DetectSkillTargetsForLayoutFastAtProjectRoot(projectRoot, !_installSkillsFlat);
+ }
+
+ private void BeginRefreshDisplayedSkillTargets(bool canManageSkills)
+ {
+ CancelSkillInstallStateRefresh();
+ if (!canManageSkills || _isInstallingSkills)
+ {
+ return;
+ }
+
+ CancellationTokenSource cts = new();
+ _skillInstallStateRefreshCts = cts;
+ RefreshDisplayedSkillTargetsAsync(cts.Token).Forget();
+ }
+
+ private async Task RefreshDisplayedSkillTargetsAsync(CancellationToken ct)
+ {
+ string projectRoot = UnityCliLoopPathResolver.GetProjectRoot();
+ List targets =
+ await Task.Run(() => DetectDisplayedSkillTargets(projectRoot));
+ if (ct.IsCancellationRequested)
+ {
+ return;
+ }
+
+ UpdateSkillsStep(canManageSkills: true, targets);
+ _scheduleResizeToContent();
+ }
+
+ private void UpdateSkillsStep(
+ bool canManageSkills,
+ List targets)
+ {
+ _groupSkillsToggle.SetEnabled(canManageSkills && !_isInstallingSkills);
+ _skillsStepPresenter.Update(
+ canManageSkills,
+ targets,
+ _shouldUseFirstInstallSkillsUi,
+ _skillsTarget,
+ !_installSkillsFlat,
+ _isInstallingSkills);
+ }
+
+ private void HandleInstallSkills()
+ {
+ HandleInstallSkillsAsync(CancellationToken.None).Forget();
+ }
+
+ private async Task HandleInstallSkillsAsync(CancellationToken ct)
+ {
+ CancelSkillInstallStateRefresh();
+ string projectRoot = UnityCliLoopPathResolver.GetProjectRoot();
+ List targets = DetectDisplayedSkillTargets(projectRoot);
+ List installableTargets = _shouldUseFirstInstallSkillsUi
+ ? SetupWizardSkillsStepPresenter.GetFirstInstallableSkillTargets(
+ targets,
+ _skillsTarget,
+ !_installSkillsFlat)
+ : SetupWizardSkillsStepPresenter.FilterInstallableSkillTargets(targets);
+ if (installableTargets.Count == 0) return;
+
+ bool shouldShowSkillsInstalledDialog =
+ SkillInstallDialogPolicy.ShouldShowForInstallableTargets(installableTargets);
+ _isInstallingSkills = true;
+ UpdateSkillsStep(true, targets);
+
+ try
+ {
+ await _skillSetupUseCase.InstallSkillFilesAsync(
+ installableTargets,
+ !_installSkillsFlat,
+ ct);
+ if (shouldShowSkillsInstalledDialog)
+ {
+ EditorDialogHelper.ShowSkillsInstalledDialog();
+ }
+ }
+ finally
+ {
+ _isInstallingSkills = false;
+ RefreshSkillsSection();
+ }
+ }
+
+ private void HandleGroupSkillsRowClicked(ClickEvent evt)
+ {
+ evt.StopPropagation();
+ if (!_groupSkillsToggle.enabledSelf)
+ {
+ return;
+ }
+
+ if (evt.target is VisualElement targetElement && _groupSkillsToggle.Contains(targetElement))
+ {
+ return;
+ }
+
+ bool newValue = !_groupSkillsToggle.value;
+ _groupSkillsToggle.SetValueWithoutNotify(newValue);
+ ApplyFlatSkillInstallPreference();
+ RefreshSkillsSection();
+ }
+
+ private void ApplyFlatSkillInstallPreference()
+ {
+ // Claude Code does not resolve nested skill folders, so setup keeps every editor target on the flat layout.
+ _installSkillsFlat = SetupWizardWindow.ForceFlatSkillInstall;
+ _editorSettingsPort.SetInstallSkillsFlat(_installSkillsFlat);
+ }
+
+ private static bool IsCliInstalled(string cliVersion)
+ {
+ return !string.IsNullOrEmpty(cliVersion);
+ }
+ }
+}
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs.meta b/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs.meta
new file mode 100644
index 0000000000..68d4ea40ad
--- /dev/null
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardSkillsWorkflowController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f941f9579e2c4ca0a3090b762b65261c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs
index b3dd582718..0bf2ee4ab6 100644
--- a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs
@@ -1,7 +1,4 @@
using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.UIElements;
@@ -192,18 +189,7 @@ private static SkillSetupUseCase GetSkillSetupUseCase()
return RegisteredSkillSetupUseCase;
}
- // Prerequisite
- private VisualElement _nodejsWarning;
- private VisualElement _nodejsOk;
private Button _refreshButton;
-
- // Step 2
- private VisualElement _groupSkillsRow;
- private EnumField _skillsTargetField;
- private Toggle _groupSkillsToggle;
- private Label _groupSkillsLabel;
-
- // Footer
private Toggle _suppressAutoShowToggle;
private Button _openSettingsButton;
private Button _closeButton;
@@ -211,23 +197,12 @@ private static SkillSetupUseCase GetSkillSetupUseCase()
private Label _githubLinkLabel;
private Image _githubLinkIcon;
private ScrollView _mainScrollView;
- private SetupWizardCliStepPresenter _cliStepPresenter;
- private SetupWizardSkillsStepPresenter _skillsStepPresenter;
-
- // State
- private bool _isInstallingCli;
- private bool _isInstallingSkills;
- private bool _needsCliPathSetup;
- private bool _isSkillsTargetFieldInitialized;
- private bool _shouldUseFirstInstallSkillsUi;
- private bool _installSkillsFlat;
+ private SetupWizardWorkflowController _controller;
+
[SerializeField]
private string _lastSeenSetupWizardVersionBeforeOpen = string.Empty;
[SerializeField]
private bool _shouldRecordLastSeenVersionAfterCreateGui;
- private IVisualElementScheduledItem _initialRefreshScheduledItem;
- private CancellationTokenSource _skillInstallStateRefreshCts;
- private SkillsTarget _skillsTarget = SkillsTarget.Claude;
private SkillSetupUseCase _skillSetupUseCase;
private IUnityCliLoopEditorSettingsPort _editorSettingsPort;
private CliSetupApplicationService _cliSetupApplicationService;
@@ -236,14 +211,13 @@ private static SkillSetupUseCase GetSkillSetupUseCase()
private void CreateGUI()
{
InitializeApplicationServices();
- InitializeFirstInstallSkillsUiState();
LoadLayout();
BindElements();
_resizer = new SetupWizardWindowResizer(this, _mainScrollView);
BindEvents();
BindSizeUpdates();
- ApplyInitialCheckingState();
- ScheduleInitialRefresh();
+ _controller.ApplyInitialCheckingState();
+ _controller.ScheduleInitialRefresh();
ScheduleResizeToContent();
RecordLastSeenSetupWizardStateAfterSuccessfulCreateGui();
}
@@ -255,12 +229,6 @@ private void InitializeApplicationServices()
_cliSetupApplicationService = GetCliSetupApplicationService();
}
- private void InitializeFirstInstallSkillsUiState()
- {
- _shouldUseFirstInstallSkillsUi = ShouldUseFirstInstallSkillsUi(
- _lastSeenSetupWizardVersionBeforeOpen);
- }
-
private void RecordLastSeenSetupWizardStateAfterSuccessfulCreateGui()
{
SetupWizardStartupFlow.MaybeRecordLastSeenSetupWizardState(
@@ -273,9 +241,9 @@ private void RecordLastSeenSetupWizardStateAfterSuccessfulCreateGui()
private void OnDisable()
{
- _initialRefreshScheduledItem?.Pause();
+ _controller?.PauseInitialRefresh();
_resizer?.Pause();
- CancelSkillInstallStateRefresh();
+ _controller?.CancelSkillInstallStateRefresh();
}
private void LoadLayout()
@@ -293,35 +261,23 @@ private void LoadLayout()
private void BindElements()
{
- _nodejsWarning = rootVisualElement.Q("nodejs-warning");
- _nodejsOk = rootVisualElement.Q("nodejs-ok");
+ VisualElement nodejsWarning = rootVisualElement.Q("nodejs-warning");
+ VisualElement nodejsOk = rootVisualElement.Q("nodejs-ok");
_refreshButton = rootVisualElement.Q