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