Introduction

July 9, 2026 · View on GitHub

optimizerDuck Banner

English | 日本語 | Türkçe

IntroductionGetting StartedArchitecture OverviewWays to ContributeCreating an OptimizationCreating a Customize SettingThe Refresh Scope SystemBuilding New FeaturesRevert SystemTestingCoding StandardsLocalizationPull Request ProcessIssue GuidelinesFAQ & TroubleshootingLicense


Introduction

Thanks for contributing to optimizerDuck — a free, open-source Windows optimization tool built with WPF on .NET 10.

You can help in many ways:

  • Reporting bugs with clear reproduction steps
  • Suggesting new optimizations or features (open an issue first)
  • Improving documentation and guides
  • Adding or fixing translations
  • Contributing code: optimizations, customize settings, services, UI improvements

Getting Started

1. Environment Setup

RequirementNotes
Windows 10/11 x64The app runs as admin and makes system changes — Windows-only
.NET 10 SDKDownload from dotnet.microsoft.com
IDEVisual Studio 2026 (.NET desktop development workload), JetBrains Rider, or VS Code + C# Dev Kit
GitVersion control

Verify your setup:

dotnet --version
# Should output 10.x

2. Fork and Clone

# Fork on GitHub first, then clone your fork
git clone https://github.com/<your-username>/optimizerDuck.git
cd optimizerDuck

# Add upstream remote to sync with the main repo
git remote add upstream https://github.com/itsfatduck/optimizerDuck.git

# Create a branch for your work (never work on master)
git checkout -b feature/your-feature-name

3. Restore, Build, Test

The solution uses the .slnx format (XML-based solution file, not .sln).

# Restore dependencies
dotnet restore optimizerDuck.slnx

# Build (CI uses Release, Debug works too)
dotnet build optimizerDuck.slnx --configuration Release --no-restore

# Run tests
dotnet test optimizerDuck.Test/optimizerDuck.Test.csproj --configuration Release --no-build

# Run the app
dotnet run --project optimizerDuck/optimizerDuck.csproj

# Format code with CSharpier
dotnet csharpier .

If you add new NuGet dependencies, run dotnet restore again (then --no-restore for subsequent builds).

4. Publishing

publish.bat portable              # Portable folder (recommended for testing)
publish.bat single                # Single-file executable
publish.bat single --skip-tests   # Skip tests for quick iteration
publish.bat portable --no-pause   # Don't pause at the end (CI-friendly)

Publishing uses built-in dotnet publish profiles. The publish.bat script handles the workflow: it runs tests first (unless --skip-tests), then calls dotnet publish with the chosen profile.

5. Quick Start Checklist

Before your first contribution:

  • Fork + clone the repo
  • dotnet build succeeds (0 errors)
  • dotnet test passes (all tests green)
  • dotnet csharpier . formats without errors
  • Read the Architecture Overview below

Architecture Overview

Solution Structure

optimizerDuck.slnx                          # Solution file (.slnx format)
├── optimizerDuck/                          # Main WPF app (net10.0-windows)
│   ├── App.xaml.cs                         # DI registration, startup, theme, logging
│   ├── optimizerDuck.csproj                # TFM: net10.0-windows10.0.17763.0, UseWPF=true
│   ├── app.manifest                        # requireAdministrator UAC level
│   │
│   ├── Domain/                             # Pure models, interfaces, attributes (no WPF deps)
│   │   ├── Abstractions/                   # IOptimization, ICustomizeSetting, IRevertStep, IWindow, ICustomizeCategory, IOptimizationCategory
│   │   ├── Attributes/                     # [Optimization], [CustomizeSetting], [OptimizationCategory], [CustomizeCategory]
│   │   ├── Configuration/                  # AppSettings model
│   │   ├── Execution/                      # ExecutionScope — ambient step tracking via AsyncLocal
│   │   ├── Customize/                      # Customize settings (Desktop, Gaming, Preferences, System)
│   │   │   ├── Categories/                 # Category classes with nested setting classes
│   │   │   └── Models/                     # BaseCustomizeSetting, RegistryToggle, RefreshScope, SettingOption, CustomizeControlType, RecommendationState
│   │   ├── Optimizations/                  # Optimizations (Performance, Privacy, GPU, etc.)
│   │   │   ├── Categories/                 # Category classes with nested optimization classes
│   │   │   └── Models/                     # BaseOptimization, ApplyResult, OptimizationContext, ServiceItem, RegistryItem
│   │   │       ├── Bloatware/              # AppXPackage model for preinstalled apps
│   │   │       ├── Cleanup/                # CleanupItem for disk cleanup
│   │   │       ├── ScheduledTask/          # ScheduledTaskModel
│   │   │       ├── Services/               # RegistryItem, ServiceItem, ShellResult, ServiceStartupType
│   │   │       └── StartupManager/         # StartupApp, StartupTask models
│   │   ├── Revert/                         # RevertData, RevertResult, revert step types
│   │   │   └── Steps/                      # RegistryRevertStep, ServiceRevertStep, ScheduledTaskRevertStep, ShellRevertStep, UsbPowerRevertStep
│   │   └── UI/                             # Enums: OptimizationRisk, OptimizationTags, OptimizationCategoryOrder, CustomizeOrder, LanguageOption, OptimizationState, RiskVisual, ProcessingProgress
│   │
│   ├── Common/                             # Shared helpers, extensions, converters
│   │   ├── Converters/                     # 20+ WPF value converters (BooleanToVisibility, InverseBoolean, MBToGB, etc.)
│   │   ├── Extensions/                     # StringExtensions, CustomizePageRegistryExtensions, OptimizationPageRegistryExtensions, LanguageExtensions
│   │   ├── Helpers/                        # Shared.cs, ReflectionHelper.cs, SystemRefreshService.cs, EmbeddedResourceHelper.cs, GitHubSourceHelper.cs, WmiHelper.cs, ThemeResource.cs
│   │   └── Native/                         # Native interop helpers (currently available for future use)
│   │
│   ├── Services/                           # Business logic layer
│   │   ├── Configuration/                  # ConfigManager, LanguageManager
│   │   ├── Customize/                      # CustomizeRegistry (reflection-based discovery of customize settings)
│   │   ├── Optimization/                   # OptimizationRegistry, OptimizationService
│   │   │   └── Providers/                  # Static: RegistryService, ShellService, ScheduledTaskService, ServiceProcessService
│   │   ├── Revert/                         # RevertManager (atomic write/read of revert JSON files)
│   │   ├── System/                         # RegistryWatcher, StreamService, SystemInfoService, UpdaterService
│   │   └── UI/                             # BloatwareService, DiskCleanupService, StartupManagerService
│   │
│   ├── UI/                                 # WPF pages, ViewModels, controls, styles
│   │   ├── Behaviors/                      # SmoothScrollBehavior
│   │   ├── Controls/                       # FilledNavigationViewItem (custom nav control)
│   │   ├── Dialogs/                        # ProcessingDialog, OptimizationDetailsDialog, OptimizationResultDialog, RestorePointDialog, LegalDialog, BloatwareConfirmationDialog, ScheduledTaskCreateDialog, ScheduledTaskDetailsDialog, StartupTaskDetailsPanel
│   │   ├── Pages/                          # App pages + sub-folders
│   │   │   ├── Customize/                  # CustomizePage + Categories/ (auto-registered category pages)
│   │   │   ├── Optimize/                   # OptimizePage + Categories/ (auto-registered category pages)
│   │   │   ├── DashboardPage
│   │   │   ├── SettingsPage
│   │   │   ├── BloatwarePage
│   │   │   ├── DiskCleanupPage
│   │   │   ├── StartupManagerPage
│   │   │   └── ScheduledTasksPage
│   │   ├── Styles/                         # FluentDesign.xaml, NavigationViewOverride.xaml, ToolTipOverride.xaml
│   │   ├── ViewModels/                     # Page and dialog ViewModels
│   │   │   ├── Customize/                  # CustomizeItemViewModel, CustomizeCategoryViewModel
│   │   │   ├── Dialogs/                    # ProcessingViewModel, OptimizationDetailsViewModel, OptimizationResultDialogViewModel, BloatwareConfirmationDialogViewModel
│   │   │   ├── Optimizer/                  # OptimizationCategoryViewModel
│   │   │   ├── Pages/                      # DashboardViewModel, OptimizeViewModel, CustomizeViewModel, SettingsViewModel, BloatwareViewModel, DiskCleanupViewModel, StartupManagerViewModel, ScheduledTasksViewModel
│   │   │   └── Windows/                    # MainWindowViewModel
│   │   └── Windows/                        # MainWindow
│   │
│   └── Resources/                          # Images, embedded assets, localization
│       ├── Embedded/                       # Power plans (optimizerDuck.pow), Icons (blank.ico)
│       ├── Images/                         # Duck.png, GitHubLogo, DiscordLogo
│       └── Languages/                      # Translations.resx + 12 locale variants

└── optimizerDuck.Test/                     # xUnit v3 test project
    ├── Common/Helpers/
    ├── Domain/
    │   ├── Customize/
    │   ├── Exceptions/
    │   ├── Optimizations/
    │   └── Revert/Steps/
    └── Services/
        ├── Managers/
        ├── OptimizationServices/
        └── ApplyRevertComprehensiveTests.cs

Key Design Decisions

DecisionRationale
Reflection-based discoveryNo DI registration arrays to update. ReflectionHelper.FindImplementationsInLoadedAssemblies<T>() scans optimizerDuck.* assemblies at startup. New optimizations/settings are auto-discovered.
Static provider servicesRegistryService, ShellService, ScheduledTaskService, ServiceProcessService are static classes. They capture revert steps into the ambient ExecutionScope — no need to inject or pass context.
File-based revert trackingApplied state = file exists on disk (%localappdata%\optimizerDuck\Revert\{id}.json). No database. Atomic writes via File.Replace().
Integration-style testsReal filesystem, real registry (under HKCU\Software\TestOptimizerDuck*), real process execution. No mocking libraries — hand-written test doubles only.
Async service methodsProvider methods that run external processes are async (*Async suffix). Optimization ApplyAsync methods should use async/await to keep the UI responsive.
Static WMI helperWmiHelper.Initialize() runs at startup to register WMI event cleanup handlers for abnormal process termination.
Pending changes trackingApp.HasPendingChanges property tracks whether applied optimizations haven't been reverted. The app warns on close with options to restart PC/Explorer or exit.

Ways to Contribute

Contribution TypeDescriptionWhere to Start
New OptimizationsRegistry tweaks, service changes, system tweaksDomain/Optimizations/Categories/*.cs
New Customize SettingsUI toggles for Windows settings (Game Mode, Mouse Acceleration, Taskbar, etc.)Domain/Customize/Categories/*.cs
New App FeaturesNew pages, tools, or functionalityOpen an issue first
Bug FixesCrash fixes, logic errors, UI issuesAnywhere
TranslationsNew languages or fixing existing translationsResources/Languages/Translations.*.resx
DocumentationREADME, CONTRIBUTING, etc.*.md files
TestingAdding/reviewing tests for existing or new optimizationsoptimizerDuck.Test/

Creating an Optimization

How Discovery Works

At startup:

  1. ReflectionHelper.FindImplementationsInLoadedAssemblies<IOptimizationCategory>() scans all optimizerDuck.* assemblies
  2. It finds every class implementing IOptimizationCategory
  3. For each category, it scans nested public classes implementing IOptimization
  4. All discovered optimizations are instantiated and OwnerType is assigned automatically
  5. OptimizationService.UpdateOptimizationStateAsync scans revert files on disk to mark each optimization as Applied or not
  6. OptimizationRegistry.StartPreload() runs this on a background thread at startup for non-blocking loading

Your job: Create a nested class inside a category, extend BaseOptimization, decorate with [Optimization]. That's it.

Optimization Categories

Current categories (in Domain/Optimizations/Categories/):

FileAttributeFocus
Performance.cs[OptimizationCategory(typeof(PerformanceOptimizerPage))]RAM tuning, process priority, keyboard latency, multimedia scheduler, accessibility keyboard hotkeys
SecurityAndPrivacy.cs[OptimizationCategory(typeof(SecurityAndPrivacyOptimizerPage))]Telemetry, error reporting, advertising ID, location, Cortana, Copilot, content delivery manager, activity history, auto-logger
Gpu.cs[OptimizationCategory(typeof(GpuOptimizerPage))]AMD/NVIDIA/Intel registry tweaks, power states, clock gating, ASPM, async flips
PowerManagement.cs[OptimizationCategory(typeof(PowerManagementOptimizerPage))]Hibernation, fast startup, USB selective suspend, custom power plan installation, power saving disable
BloatwareAndServices.cs[OptimizationCategory(typeof(BloatwareAndServicesOptimizerPage))]OEM preinstalled app blocking, 170+ Windows service startup type optimization
UserExperience.cs[OptimizationCategory(typeof(UserExperienceOptimizerPage))]Menu delays, visual effects, taskbar animations, transparency, Start Menu web search

Step-by-Step: Add to an Existing Category

Pick the best-matching category file and add a nested class:

[OptimizationCategory(typeof(PerformanceOptimizerPage))]
public class Performance : IOptimizationCategory
{
    public string Name => Loc.Instance[$"Optimizer.{nameof(Performance)}"];
    public OptimizationCategoryOrder Order { get; init; } = OptimizationCategoryOrder.Performance;
    public ObservableCollection<IOptimization> Optimizations { get; init; } = [];

    [Optimization(
        Id = "a1b2c3d4-...",                          // Generate a NEW GUID
        Risk = OptimizationRisk.Safe,                   // Safe / Moderate / Risky
        Tags = OptimizationTags.Performance             // Flags — combine with |
    )]
    public class MyNewTweak : BaseOptimization
    {
        public override async Task<ApplyResult> ApplyAsync(
            IProgress<ProcessingProgress> progress,
            OptimizationContext context)
        {
            // 1. Use static providers to make system changes
            RegistryService.Write(new RegistryItem(
                @"HKLM\SOFTWARE\Something", "ValueName", 1));

            // 2. Await async operations — this yields the UI thread
            await ServiceProcessService.ChangeServiceStartupTypeAsync(
                new ServiceItem("SomeService", ServiceStartupType.Disabled));

            // 3. Return result from the ambient ExecutionScope
            return CompleteFromScope();
        }
    }
}

Key Rules

RuleDetail
Id must be a new GUIDUsed for revert file naming and applied-state tracking. Generate with [guid]::NewGuid() in PowerShell.
Extend BaseOptimizationProvides Name, ShortDescription, Prefix, RiskVisual, TagDisplays from attribute + localization keys
Use async Task<ApplyResult>Not Task.FromResult(). Service providers are async — await them to keep the UI responsive.
Return CompleteFromScope()Derives ApplyResult from steps recorded in the ambient ExecutionScope
Report progressUse progress.Report(new ProcessingProgress { ... }) to update the UI dialog
Don't catch all exceptionsLet them bubble up. ExecutionScope tracks success/failure. The OptimizationService layer handles exceptions.
Don't manually create revert stepsStatic provider services do this automatically via ExecutionScope.RecordStep()
Use context.LoggerThe optimization context provides a logger — use it for important diagnostic info
Check context.SnapshotOptimizationContext.Snapshot (a SystemSnapshot) gives you system info: RAM, GPU info, CPU. Use it for conditional logic (e.g., skip if insufficient RAM).

Available Service Providers

These static classes handle logging, error handling, and automatic revert step recording.

ServiceKey MethodsWhy It's Used
RegistryServiceWrite(), Read<T>(), DeleteValue(), CreateSubKey(), DeleteSubKeyTree(), KeyExists()Read/write/delete registry keys. Backs up original values for revert. Supports writing multiple RegistryItem entries in one call via params array.
ShellServiceCMDAsync(), PowerShellAsync(), CMD() (sync), PowerShell() (sync)Run CMD or PowerShell commands. Prefer async variants. Both sync and async methods accept an optional revertCommand parameter.
ScheduledTaskServiceDisableTask(), EnableTask(), IsTaskEnabled(), DeleteTask()Manage Windows Scheduled Tasks.
ServiceProcessServiceChangeServiceStartupTypeAsync(), GetStartupTypeAsync()Manage Windows Services. Always use async variants. Supports changing multiple services at once via params array.

Methods accepting multiple items via params: Most write/change methods accept a params array of items (e.g., RegistryService.Write(item1, item2, item3)). This is more efficient than multiple individual calls.

Example usage:

// Sync registry writes — multiple items at once
RegistryService.Write(
    new RegistryItem(@"HKLM\...", "Value1", 1),
    new RegistryItem(@"HKLM\...", "Value2", 0)
);
RegistryService.DeleteValue(new RegistryItem(@"HKCU\...", "OldValue"));

// Async service changes — multiple services at once
await ServiceProcessService.ChangeServiceStartupTypeAsync(
    new ServiceItem("DiagTrack", ServiceStartupType.Disabled),
    new ServiceItem("dmwappushservice", ServiceStartupType.Disabled)
);

// Async shell commands with revert command
var result = await ShellService.CMDAsync(
    "powercfg /h off",
    "powercfg /h on"     // revert command stored for undo
);

// Async PowerShell
var usbStates = await ShellService.PowerShellAsync(
    "Get-CimInstance -Namespace root\\wmi -ClassName MSPower_DeviceEnable"
);

Handling Asynchronous Operations

Not all optimizations need async/await. If your optimization only does synchronous registry writes (no async calls), you can return Task.FromResult():

public override Task<ApplyResult> ApplyAsync(
    IProgress<ProcessingProgress> progress,
    OptimizationContext context)
{
    RegistryService.Write(new RegistryItem(@"HKLM\...", "Value", 1));
    context.Logger.LogInformation("Applied tweak");
    return Task.FromResult(CompleteFromScope());
}

But if you use any async provider (service, shell, task), always await them:

public override async Task<ApplyResult> ApplyAsync(...)
{
    await ServiceProcessService.ChangeServiceStartupTypeAsync(...);
    return CompleteFromScope();
}

Create a New Category

Only if your optimizations don't fit any existing category. Avoid hyper-specific categories.

  1. Create Domain/Optimizations/Categories/YourCategory.cs
  2. Implement IOptimizationCategory
  3. Apply [OptimizationCategory(PageType = typeof(YourPage))] — you'll also need a XAML page
  4. Add a member to OptimizationCategoryOrder enum in Domain/UI/OptimizationCategoryOrder.cs
  5. The XAML page auto-registers via services.AddAllOptimizationPages() in App.xaml.cs

Create an Optimization Helper Base Class

If you have multiple optimizations that share the same structure (like GPU tweaks that all iterate over detected GPUs), create an abstract intermediate class:

public abstract class GpuRegistryOptimization : BaseOptimization
{
    protected abstract GpuVendor Vendor { get; }
    protected abstract IReadOnlyList<RegistryItem> CreateItems(string registryPath);

    public override Task<ApplyResult> ApplyAsync(...)
    {
        foreach (var gpu in context.Snapshot.Gpus.Where(g => g.Vendor == Vendor))
        {
            var path = $@"HKLM\...\{index:D4}";
            RegistryService.Write(CreateItems(path).ToArray());
        }
        return Task.FromResult(CompleteFromScope());
    }
}

See Gpu.cs for a real example with AMD, NVIDIA, and Intel subclasses.

Localization Keys

Every optimization needs entries in Translations.resx. The keys follow a strict convention:

Optimizer.{CategoryName}.{OptimizationKey}.Name
Optimizer.{CategoryName}.{OptimizationKey}.ShortDescription
Optimizer.{CategoryName}.{OptimizationKey}.Progress.{CustomKey}
Optimizer.{CategoryName}.{OptimizationKey}.Error.{CustomKey}

Where CategoryName = category class name (e.g., Performance) and OptimizationKey = nested class name.

Important

Translations required. If you skip adding these keys, the app displays raw key strings like "Optimizer.Performance.MyNewTweak.Name". Always add entries in Translations.resx (English) at minimum.


Creating a Customize Setting

Customize settings are UI controls (toggle switches, dropdowns, number inputs) that flip Windows settings ON or OFF. They live in Domain/Customize/Categories/.

Customize Categories

FileAttributeFocus
Desktop.cs[CustomizeCategory(PageType = typeof(DesktopFeatureCategory))]Desktop icons (This PC, Recycle Bin, Network, User Files, Control Panel), global show/hide icons, shortcut arrow visibility
Preferences.cs[CustomizeCategory(PageType = typeof(PreferencesFeatureCategory))]Taskbar alignment, widgets, task view button, end task, dark mode, file extensions, hidden files, clipboard history, search mode, seconds in clock, Bing search, classic context menu
Gaming.cs[CustomizeCategory(PageType = typeof(GamingFeatureCategory))]Game Mode, Game Bar, background recording, mouse acceleration, fullscreen optimizations, GPU scheduling
SystemFeatures.cs[CustomizeCategory(PageType = typeof(SystemFeatureCategory))]Num Lock on boot, Developer Mode, allow all trusted apps, long paths enabled, battery percentage

Step-by-Step: Simple Registry Toggle

For a simple on/off registry toggle, the base class does all the work:

private enum Sections { Taskbar, Widgets, Advanced }

[CustomizeSetting(
    Section = nameof(Sections.Taskbar),        // Groups settings in the UI
    Icon = SymbolRegular.AlignCenter24,         // From Wpf.Ui.Controls.SymbolRegular
    Recommendation = RecommendationState.On     // On / Off / Depends / Experimental / None
)]
public class TaskbarAlignment : BaseCustomizeSetting
{
    protected override IEnumerable<RegistryToggle> RegistryToggles =>
        [
            new()
            {
                Path = @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
                Name = "TaskbarAl",
                OnValue = 0,            // value when toggle is ON
                OffValue = 1,           // value when toggle is OFF
                DefaultValue = 1,       // value = default state (used when key missing)
            },
        ];

    // Declare what needs refreshing after this setting changes
    protected override CustomizeRefreshScope RefreshScope =>
        CustomizeRefreshScope.TaskbarSettings;
}

RegistryToggle Properties

PropertyTypeDefaultDescription
PathstringrequiredFull registry key path (e.g., @"HKCU\Software\...")
NamestringrequiredRegistry value name
OnValueobject?1Value representing "on" state
OffValueobject?0Value representing "off" state
DefaultValueobject?0Fallback when registry value is missing
IsOptionalboolfalseIf true, not required for state detection
TreatMissingAsDefaultboolfalseIf true, missing key uses DefaultValue instead of treating as "off"
ValueKindRegistryValueKindDWordRegistry value type (DWord, String, etc.)

State detection logic: GetState() (in BaseCustomizeSetting) collects all non-optional RegistryToggles and returns true only when every required toggle matches its OnValue.

Control Types

TypeRendered AsUsed For
ToggleOn/off switchMost settings (default)
DropdownComboBoxMultiple choice (e.g., power plan, search box mode, taskbar alignment)
OptionRadio button groupMutually exclusive visual options (e.g., left/center alignment)
NumberIntInteger text inputNumeric values (e.g., seconds)
NumberFloatDecimal text inputPrecision values
StringText inputFree-form text

Override ControlType to change the UI control:

public override CustomizeControlType ControlType => CustomizeControlType.Dropdown;

For settings with multiple choices:

public override CustomizeControlType ControlType => CustomizeControlType.Dropdown;

public override IReadOnlyList<SettingOption>? Options =>
    [
        Option("Never", 0),      // Option() helper reads from Translations.resx:
        Option("Battery", 1),    //   Customize.{Category}.{Feature}.Options.Never
        Option("Always", 2),     //   Customize.{Category}.{Feature}.Options.Always
    ];

public override async Task ApplyAsync(object? value)
{
    var intValue = value is int i ? i : 0;
    RegistryService.Write(new RegistryItem(Path, "ValueName", intValue));
    await ExecutePostActionAsync();  // MUST call when overriding ApplyAsync
}

Dynamic Options (Platform-Aware)

You can conditionally show options based on the Windows version:

public override IReadOnlyList<SettingOption>? Options
{
    get
    {
        if (Shared.IsWindows11OrGreater)
            return [Option("Hidden", 0), Option("Icon", 1), Option("IconAndLabel", 2), Option("SearchBox", 3)];
        return [Option("Hidden", 0), Option("Icon", 1), Option("SearchBox", 2)];
    }
}

CurrentValue for Dropdowns

For dropdowns and option-based controls, implement CurrentValue so the UI shows the current system state:

public override object? CurrentValue =>
    RegistryService.Read<object>(new RegistryItem(RegPath, RegName));

Custom Logic (Override GetStateAsync / ApplyAsync)

For settings that aren't simple registry toggles (e.g., mouse acceleration combines 3 registry values):

[CustomizeSetting(
    Section = nameof(Sections.Input),
    Icon = SymbolRegular.Cursor24,
    Recommendation = RecommendationState.Off
)]
public class MouseAcceleration : BaseCustomizeSetting
{
    private const string Path = @"HKCU\Control Panel\Mouse";

    // Watched paths let the UI auto-refresh when external changes occur
    protected override IReadOnlyList<string> GetWatchedRegistryPaths() => [Path];

    public override Task<bool> GetStateAsync()
    {
        return Task.Run(() =>
        {
            var speed = RegistryService.Read<string>(new RegistryItem(Path, "MouseSpeed"));
            var t1 = RegistryService.Read<string>(new RegistryItem(Path, "MouseThreshold1"));
            var t2 = RegistryService.Read<string>(new RegistryItem(Path, "MouseThreshold2"));
            return (int.TryParse(speed, out var s) && s != 0)
                || (int.TryParse(t1, out var a) && a != 0)
                || (int.TryParse(t2, out var b) && b != 0);
        });
    }

    public override async Task ApplyAsync(object? value)
    {
        var isOn = value is bool b && b;
        RegistryService.Write(new RegistryItem(Path, "MouseSpeed", isOn ? "1" : "0"));
        RegistryService.Write(new RegistryItem(Path, "MouseThreshold1", isOn ? "6" : "0"));
        RegistryService.Write(new RegistryItem(Path, "MouseThreshold2", isOn ? "10" : "0"));
        await ExecutePostActionAsync();  // MUST call when overriding ApplyAsync
    }

    protected override CustomizeRefreshScope RefreshScope => CustomizeRefreshScope.Default;
}

State Detection with Retry

After applying a value, the UI calls GetStateWithRetryAsync() (not GetStateAsync()). This method:

  1. Reads state up to maxRetries (default 3) times with delayMs (default 80ms) between attempts
  2. Returns when two consecutive reads agree on the same value (convergence check)
  3. Falls back to the last read value after exhausting retries

This prevents the UI from showing stale values while the registry settles after a write.

Custom Logic with Non-Registry Dependencies

For settings that involve embedded resource extraction (like replacing shortcut arrows with a blank icon):

public override async Task ApplyAsync(object? value)
{
    var isOn = value is bool b && b;
    if (isOn)
    {
        RegistryService.DeleteValue(new RegistryItem(Path, "29"));
    }
    else
    {
        var outputPath = Path.Combine(Shared.AssetsDirectory, nameof(Desktop), "blank.ico");
        EmbeddedResourceHelper.TryExtract("Icons.blank.ico", outputPath);
        RegistryService.Write(new RegistryItem(Path, "29", outputPath));
    }
    await ExecutePostActionAsync();
}

Use EmbeddedResourceHelper.TryExtract(resourceName, outputPath) to extract embedded resources from the assembly to disk.

The Recommendation System

Each customize setting can declare a recommendation:

[CustomizeSetting(
    ...,
    Recommendation = RecommendationState.On    // or Off, Depends, Experimental, None
)]

Available states:

  • On: Recommended to turn ON — improves system
  • Off: Recommended to turn OFF — improves system
  • Depends: Depends on user's specific needs/configuration
  • Experimental: May be unstable, use with caution
  • None (default): No recommendation displayed

You can also add a recommendation reason via localization key: Customize.{Category}.{Feature}.Recommendation.Reason.

What to Override per Pattern

ScenarioOverride
Simple registry toggleRegistryToggles + RefreshScope
Multiple registry toggles (e.g., Game Mode: 2 values)RegistryToggles (list them all)
Dropdown/OptionsControlTypeDropdown, Options, custom ApplyAsync, CurrentValue
Multi-value logic (e.g., mouse accel: 3 registry values)GetStateAsync() + ApplyAsync() + GetWatchedRegistryPaths()
Setting with no registry interactionGetStateAsync() + ApplyAsync() (full custom)
Custom refresh behaviorRefreshScope (if only changing flags) or ExecutePostActionAsync() (full override)
State detection with convergenceUse GetStateWithRetryAsync() (built-in — don't override)
Dynamic options per Windows versionOverride Options getter with conditional logic
Embedded resource extractionEmbeddedResourceHelper.TryExtract() in custom ApplyAsync

Create a New Category

  1. Create Domain/Customize/Categories/YourCategory.cs
  2. Implement ICustomizeCategory with [CustomizeCategory(PageType = typeof(YourPage))]
  3. Add a member to CustomizeOrder enum in Domain/UI/CustomizeOrder.cs
  4. Create the XAML page (a new class in UI/Pages/Customize/Categories/)
  5. The page auto-registers via services.AddAllCustomizeCategoryPages() in App.xaml.cs

Localization Keys for Customize Settings

Customize.{CategoryName}.{SettingKey}.Name
Customize.{CategoryName}.{SettingKey}.Description
Customize.{CategoryName}.{SettingKey}.Options.{OptionKey}    (if using SettingOption)
Customize.{CategoryName}.{SettingKey}.Recommendation.Reason   (if Recommendation != None)
Customize.{CategoryName}.Section.{SectionName}                (for section headers)

The Refresh Scope System

When a customize setting changes state, different Windows surfaces need different refresh strategies. The CustomizeRefreshScope [Flags] enum controls this granularly.

Available Flags

MemberValueEffectP/Invoke
None0No refresh
Settings1 << 0Broadcast WM_SETTINGCHANGE so apps re-read registrySendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE)
Associations1 << 1Notify shell that file associations or icon cache changedSHChangeNotify(SHCNE_ASSOCCHANGED)
Desktop1 << 2Force desktop icon list (SysListView32) to repaintLVM_REFRESH + LVM_UPDATE
Taskbar1 << 3Broadcast taskbar-targeted WM_SETTINGCHANGE ("TraySettings")SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, "TraySettings")
PolicyUpdate1 << 4Push SystemParametersInfo with SPIF_SENDCHANGE for per-user paramsSystemParametersInfo(SPI_SETDESKWALLPAPER)
Theme1 << 5Broadcast WM_THEMECHANGED for theme/visual tweaksSendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED)
DesktopIconCache1 << 6Toggle HideIcons registry + send WM_COMMAND 0x7402 to desktopRegistry read + SendMessage(Progman, WM_COMMAND)

Named Composites

NameCompositionUse Case
DefaultSettings | AssociationsGeneral explorer-level settings
DesktopIconsSettings | DesktopShow/hide individual desktop icons (This PC, Recycle Bin)
HideDesktopIconsSettings | DesktopIconCacheGlobal "Hide all desktop icons" toggle
TaskbarSettingsSettings | TaskbarTaskbar alignment, widgets, task view, end task
ExplorerViewSettings | Associations | PolicyUpdateFile extensions, hidden files, compact view

How Refresh Flows

Setting toggle → BaseCustomizeSetting.ApplyAsync(value)
  ├─ Writes RegistryToggles (if any)
  ├─ Checks NeedsPostAction (true if RefreshScope != None)
  └─ Task.Run → ExecutePostActionAsync()
       ├─ Checks each CustomizeRefreshScope flag
       ├─ Calls SystemRefreshService methods (P/Invoke)
       └─ Win32 notifications sent to Windows

If you override ApplyAsync, you must call await ExecutePostActionAsync() yourself to trigger the refresh. The base class only does this automatically when using the default RegistryToggles-based apply.


Building New Features

If you want to add a new page or tool (e.g., a "Network Monitor"):

  1. Open a GitHub Issue first — describe the feature, use case, and design. Wait for maintainer feedback.
  2. Implementation order:
// 1. Service layer in Services/UI or Services/System/YourService.cs
public class YourService(ILogger<YourService> logger) { ... }

// 2. ViewModel in UI/ViewModels/Pages/YourViewModel.cs
//    Extends ViewModel (which extends ObservableValidator + INavigationAware)

// 3. XAML Page in UI/Pages/YourPage.xaml (+ code-behind)

// 4. Register as singletons in App.xaml.cs
services.AddSingleton<YourViewModel>();
services.AddSingleton<YourPage>();
  • ViewModels and Pages must be registered as singletons in App.xaml.cs
  • Navigation is handled by WPF UI (INavigationService)
  • Follow the existing patterns — check DashboardPage, OptimizePage, BloatwarePage, DiskCleanupPage, ScheduledTasksPage, StartupManagerPage, etc.

DI Registration Pattern (from App.xaml.cs)

// Pages + ViewModels — one pair per feature
services.AddSingleton<DashboardViewModel>();
services.AddSingleton<DashboardPage>();

services.AddSingleton<OptimizeViewModel>();
services.AddSingleton<OptimizePage>();

services.AddSingleton<SettingsViewModel>();
services.AddSingleton<SettingsPage>();

services.AddSingleton<BloatwareViewModel>();
services.AddSingleton<BloatwarePage>();

services.AddSingleton<DiskCleanupViewModel>();
services.AddSingleton<DiskCleanupPage>();

services.AddSingleton<StartupManagerViewModel>();
services.AddSingleton<StartupManagerPage>();

services.AddSingleton<ScheduledTasksViewModel>();
services.AddSingleton<ScheduledTasksPage>();

// Customize
services.AddSingleton<CustomizeViewModel>();
services.AddSingleton<CustomizePage>();

// Automatic page registration (category pages using reflection)
services.AddAllCustomizeCategoryPages();   // scans [CustomizeCategory] attributes
services.AddAllOptimizationPages();        // scans [OptimizationCategory] attributes

// Managers
services.AddSingleton<ConfigManager>();
services.AddSingleton<RevertManager>();

// Services
services.AddSingleton<OptimizationRegistry>();
services.AddSingleton<CustomizeRegistry>();
services.AddSingleton<OptimizationService>();
services.AddSingleton<BloatwareService>();
services.AddSingleton<DiskCleanupService>();
services.AddSingleton<StartupManagerService>();
services.AddSingleton<SystemInfoService>();
services.AddSingleton<StreamService>();
services.AddSingleton<UpdaterService>();
services.AddSingleton<IRegistryWatcher, RegistryWatcher>();

System Services Reference

Additional services available for feature development:

ServicePurpose
SystemInfoServiceProvides Snapshot property (SystemSnapshot) with CPU, RAM, GPU info via WMI. Used in optimization context.
StreamServiceDownloads remote resources (e.g., updated power plan files). Used via OptimizationContext.StreamService.
UpdaterServiceChecks GitHub releases for updates. Shows update prompt on Dashboard.
RegistryWatcherMonitors registry keys for external changes and notifies the UI to refresh. Implements IRegistryWatcher.
BloatwareServiceLists preinstalled AppX packages, categorizes them as Safe/Caution/Dangerous.
DiskCleanupServiceScans disks for cleanup opportunities (temp files, caches, logs).
StartupManagerServiceLists and manages startup applications and scheduled tasks.

Revert System

Every applied optimization creates a JSON file at %localappdata%\optimizerDuck\Revert\{optimizationId}.json.

How It Works

ApplyAsync()

  ├─ ExecutionScope.Begin(optimization, logger)    ← creates ambient AsyncLocal scope

  ├─ RegistryService.Write(...)                     ← auto-records RegistryRevertStep
  ├─ ServiceProcessService.ChangeServiceStartupTypeAsync(...)  ← auto-records ServiceRevertStep
  ├─ ShellService.CMDAsync(...)                     ← auto-records ShellRevertStep

  ├─ CompleteFromScope() → ApplyResult              ← derived from recorded steps

  └─ ExecutionScope disposes → RevertManager.SaveRevertDataAsync()

Step Types

Step TypeRecordsAutomatically Created By
RegistryRevertStepOriginal registry value before changeRegistryService.Write(), RegistryService.DeleteValue(), RegistryService.CreateSubKey(), RegistryService.DeleteSubKeyTree()
ServiceRevertStepOriginal service startup typeServiceProcessService.ChangeServiceStartupTypeAsync()
ScheduledTaskRevertStepOriginal task state (enabled/disabled)ScheduledTaskService.DisableTask(), ScheduledTaskService.EnableTask()
ShellRevertStepShell command to reverse the changeShellService.CMDAsync(), ShellService.PowerShellAsync() — pass a revertCommand parameter
UsbPowerRevertStepUSB power settings (per-device)USB-related optimizations (manual via ExecutionScope.RecordStep())

Adding a Revert Command to Shell Calls

When calling CMDAsync or PowerShellAsync, you can optionally pass a revertCommand parameter that gets saved for undo:

// The revert command "powercfg /h on" will be stored to reverse this change
await ShellService.CMDAsync("powercfg /h off", "powercfg /h on");

Revert Data Format

{
  "SchemaVersion": 1,
  "OptimizationId": "guid",
  "OptimizationName": "DisableTelemetry",
  "AppliedAt": "2026-06-02T12:00:00Z",
  "Steps": [
    { "Index": 0, "Type": "Registry", "Data": { ... } },
    null,                    // null gap = failed step at this index
    { "Index": 2, "Type": "Service", "Data": { ... } }
  ]
}

Key Details

  • Applied state is inferred from file presence on disk (RevertManager.IsAppliedAsync(id))
  • Atomic writes: writes to .tmp then File.Replace() — crash-safe
  • Concurrent access: per-file SemaphoreSlim locks prevent race conditions; 30-second timeout
  • File locking: _fileLocks dictionary (ConcurrentDictionary<Guid, SemaphoreSlim>) protects each revert file
  • ExecutionScope uses AsyncLocal<ExecutionScope?> for ambient step tracking. No need to pass context through parameters
  • Revert executes steps in reverse order (last applied = first reverted)
  • Partial success: revert continues even if some steps fail. Failed steps get retry actions recorded
  • Retry: OptimizationService.RetryFailedStepsAsync() can retry individual failed steps
  • Upsert: RevertManager.UpsertRevertStepAtIndexAsync() can add/replace revert steps at specific indices (used during retry)
  • Step registry: Revert step deserialization uses reflection-based _stepRegistry — new step types auto-register by implementing IRevertStep with a static FromData(JObject) method

Important: When you call provider services (RegistryService.Write, ShellService.CMDAsync, etc.), revert steps are recorded automatically. Do NOT manually create revert steps unless you're implementing a custom provider (like UsbPowerRevertStep).


Testing

Tests use xUnit v3 and follow an integration-style approach with real I/O.

Test Patterns

PatternDetail
No mocking librariesAll test doubles are hand-written classes implementing interfaces
Real I/OReal filesystem (revert JSON files), real registry (HKCU\Software\TestOptimizerDuck*), real process execution (CMD, PowerShell)
CleanupUse try/finally or IDisposable for test artifact cleanup
Naming{Method}_{Scenario}_{ExpectedResult} — e.g., ApplyAsync_Success_PersistsRevertDataFile
LoggingUse NullLogger<T>.Instance / NullLoggerFactory.Instance for DI logging parameters
STA threadTests involving ContentDialogService or WPF components must use RunInStaThreadAsync helper

Test Structure

optimizerDuck.Test/
├── Common/Helpers/
│   └── SystemRefreshServiceTests.cs
├── Domain/
│   ├── Customize/
│   │   └── BaseCustomizeSettingTests.cs
│   ├── Exceptions/
│   │   └── StepExecutionExceptionTests.cs
│   ├── Optimizations/
│   │   ├── Models/
│   │   │   └── Services/RegistryItemKindDetectionTests.cs
│   │   └── PowerManagementTests.cs
│   └── Revert/Steps/
│       ├── RevertStepSerializationTests.cs
│       └── ScheduledTaskRevertStepTests.cs
└── Services/
    ├── ApplyRevertComprehensiveTests.cs
    ├── OptimizationServiceTests.cs
    ├── OptimizationServiceIntegrationTests.cs
    ├── OptimizationExecutionContextTests.cs
    ├── OptimizationServices/
    │   ├── RegistryServiceTests.cs
    │   ├── ServiceProcessServiceTests.cs
    │   ├── ShellServiceTests.cs
    │   └── ShellPolicyTests.cs
    ├── Managers/
    │   └── RevertManagerTests.cs
    ├── RegistryWatcherTests.cs
    └── SystemInfoServiceTests.cs

Running Tests

# After building
dotnet test optimizerDuck.Test/optimizerDuck.Test.csproj --configuration Release --no-build

# Build + test in one step
dotnet test optimizerDuck.Test/optimizerDuck.Test.csproj --configuration Release

CI Integration

The CI pipeline (ci.yml) runs:

dotnet restore optimizerDuck.slnx
dotnet build optimizerDuck.slnx --configuration Release --no-restore
dotnet test optimizerDuck.Test/optimizerDuck.Test.csproj --configuration Release --no-build --blame-hang --blame-hang-timeout 30s

The --blame-hang --blame-hang-timeout 30s flags ensure tests don't hang longer than 30 seconds, which is critical for integration-style tests that interact with real Windows services.

Writing Tests for Provider Services

public class MyOptimizationTests
{
    [Fact]
    public async Task ApplyAsync_Success_PersistsRevertDataFile()
    {
        var optimization = new TestOptimization
        {
            ApplyImpl = _ =>
            {
                ExecutionScope.RecordStep("Test", "Step 1", true, ...);
                return Task.FromResult(ApplyResult.True());
            },
        };

        var service = CreateService();
        var result = await service.ApplyAsync(optimization, new Progress<ProcessingProgress>());

        Assert.Equal(OptimizationSuccessResult.Success, result.Status);
    }

    private static OptimizationService CreateService()
    {
        return new OptimizationService(
            new RevertManager(NullLogger<RevertManager>.Instance, NullLoggerFactory.Instance),
            NullLoggerFactory.Instance,
            new SystemInfoService(NullLogger<SystemInfoService>.Instance),
            new StreamService(NullLogger<StreamService>.Instance),
            null!,
            NullLogger<OptimizationService>.Instance
        );
    }
}

Registering New Tests

When adding tests for a new optimization or service, follow the existing directory structure. Tests for OptimizationServices/ go in Services/OptimizationServices/, and tests for domain models go in the matching Domain/ subdirectory.


Coding Standards

Language Features

FeatureUsed?Notes
File-scoped namespacesYesnamespace X.Y;
Collection expressionsYes[] for empty, [item1, item2] for lists
Primary constructorsSomeUsed in simple types
Implicit usingsYesEnabled in .csproj
Nullable reference typesYes<Nullable>enable</Nullable> — handle nulls properly
Extension methods (extension(T type))YesC# 13 feature, used in OptimizationTagsToDisplay

Naming Conventions

ElementConventionExample
Classes, enums, interfaces, methods, propertiesPascalCaseRegistryService, ApplyAsync
Private fields_camelCase_lastError, registryService
Local variables, parameterscamelCaseprogress, serviceName
Async methods*Async suffixChangeServiceStartupTypeAsync, CMDAsync
Public constantsPascalCaseMaxRetries
Private constants_PascalCase_defaultTimeout

Formatting

SettingValue
Indentation4 spaces (no tabs)
End of lineLF
EncodingUTF-8
Max line length100 characters
Trailing whitespaceTrimmed
Final newlineRequired
FormatterCSharpier — run dotnet csharpier . before committing
CA1416Silenced via .editorconfig — all code is Windows-only

Code Style

  • No hardcoded strings — always use Translations.KeyName or Loc.Instance["Key"]
  • Keep comments sparse — existing code has almost none. Don't add unnecessary comments.
  • No type error suppression — handle types properly.
  • Prefer existing libraries over new dependencies.
  • Prefer small, focused changes over large refactors.
  • Use @formatter:off / @formatter:on comments around large, structured registry write blocks when needed to suppress automatic reformatting.
  • Never commit machine-specific paths or secrets.

Dependency Injection

  • Services, ViewModels, and Pages are registered as singletons in App.xaml.cs
  • Use constructor injection: public class Foo(Bar bar, Baz baz) or public class Foo(ILogger<Foo> logger)
  • Static provider services (RegistryService, ShellService, ScheduledTaskService, ServiceProcessService) are NOT injected — access them directly
  • Test doubles are hand-written (no mocking libraries like Moq)

Error Handling

LayerPractice
OptimizationsReturn ApplyResult.False("reason") instead of throwing. Let ExecutionScope handle step-level failure tracking.
Provider servicesUse try/catch around system calls, log errors via ExecutionScope.LogError. Record failed steps with retry actions.
ViewModelsCatch exceptions in command handlers, show user-friendly snackbars.
Don'tCatch exceptions you can't handle. Don't silently swallow all exceptions.

Global Error Handling

The App.xaml.cs registers three global exception handlers:

  • AppDomain.CurrentDomain.UnhandledException — catches fatal exceptions
  • TaskScheduler.UnobservedTaskException — catches unobserved task exceptions
  • DispatcherUnhandledException — catches unhandled UI thread exceptions

All crash details are logged to %localappdata%\optimizerDuck\Crashes\crash_*.log.


Localization

RESX Files

All user-facing strings live in Resources/Languages/Translations.resx. Use the strongly-typed Translations class in C#, or Loc.Instance["Key"] for dynamic lookup.

  • Do not edit Translations.Designer.cs directly — it's auto-generated
  • Use ResXManager (VS) or Rider's built-in resource editor
  • Preserve format parameters like {0}, {1} exactly
  • Keep strings concise — some UI cards have width limits

Available Locales

LanguageFile
EnglishTranslations.resx (default)
GermanTranslations.de-DE.resx
VietnameseTranslations.vi-VN.resx
SpanishTranslations.es-ES.resx
FrenchTranslations.fr-FR.resx
Traditional ChineseTranslations.zh-TW.resx
Simplified ChineseTranslations.zh-CN.resx
RussianTranslations.ru-RU.resx
KoreanTranslations.ko-KR.resx
JapaneseTranslations.ja-JP.resx
PolishTranslations.pl-PL.resx
TurkishTranslations.tr-TR.resx
Portuguese (Brazil)Translations.pt-BR.resx
HebrewTranslations.he-IL.resx

Note: 12 locale variants total, plus English as the default fallback.

Adding a New Language

  1. Create Translations.{locale}.resx (e.g., Translations.de-DE.resx) with all the same keys as Translations.resx
  2. Register the language in UI/ViewModels/Pages/SettingsViewModel.cs:
new() { DisplayName = "Deutsch", Culture = new CultureInfo("de-DE") },

Hardcoded String Rule

Never hardcode strings. Always use:

// Strongly typed (recommended)
string title = Translations.Features_Desktop_Name;

// With format args
string msg = string.Format(Translations.Dashboard_SystemInfo_Storage_DiskInfo, used, total, percent);

// Dynamic key lookup (for convention-based keys)
string title = Loc.Instance[$"Optimizer.{category}.{key}.Name"];

In XAML:

<!-- Without args -->
<ui:TextBlock Text="{ext:Loc Dashboard.Header.Title}" />

<!-- With bound args -->
<ui:TextBlock Text="{ext:Loc Dashboard.UpdateInfoBar.Message, {Binding ViewModel.LatestVersion}}" />

Pull Request Process

  1. Branch from master — never work directly on master:

    git checkout -b feature/your-feature-name
    # or
    git checkout -b fix/issue-number
    
  2. Commit with Conventional Commits:

    PrefixWhen to Use
    feat:New optimizations or features
    fix:Bug fixes
    refactor:Code restructuring without behavior change
    docs:Documentation updates
    test:Adding or fixing tests
    i18n:Translation updates
    chore:Maintenance, build config, dependencies
  3. Before pushing, verify:

    # 1. Build
    dotnet build optimizerDuck.slnx --configuration Release
    
    # 2. Test
    dotnet test optimizerDuck.Test/optimizerDuck.Test.csproj --configuration Release --no-build
    
    # 3. Format
    dotnet csharpier .
    
    # 4. Check git status — make sure only intended files are staged
    git status
    git diff --cached
    
  4. Open the PR:

    • Describe what changed and why
    • If your PR has UI changes, include a screenshot
    • Link related issues: Closes #42
    • Mark as draft if still a work in progress
  5. Review: A maintainer will review. Be open to feedback and respond promptly.

PR Checklist

  • Code follows existing patterns (discovery, attributes, async naming)
  • Localization keys added to Translations.resx at minimum
  • dotnet build succeeds (0 errors)
  • dotnet test passes (all tests green)
  • dotnet csharpier . has been run
  • No hardcoded strings
  • Revert steps are properly recorded (if applicable)
  • UI changes include a screenshot

Issue Guidelines

  • Bug reports: Use the Bug Report template. Include steps to reproduce, expected vs actual behavior, and logs from %localappdata%\optimizerDuck\optimizerDuck.log + system specs.
  • Feature requests: Describe the use case, the problem it solves, and how it should work.
  • Optimization suggestions: Include registry paths, service names, or CLI commands. Link to documentation or credible sources.
  • Questions: Use GitHub Discussions or join the Discord server.

FAQ & Troubleshooting

Build fails with "CA1416" errors

The .editorconfig silences CA1416. If you're still seeing it, ensure you have the latest .editorconfig from master. This project is Windows-only — don't add SupportedOSPlatform guards.

My optimization isn't showing up in the UI

Checklist:

  • Is it a nested public class inside a category class?
  • Does the category class implement IOptimizationCategory?
  • Does the optimization class extend BaseOptimization?
  • Does it have [Optimization(Id = "...", ...)] attribute?
  • Are the localization keys added to Translations.resx?
  • Has the optimization category been preloaded? (Check OptimizationRegistry.IsPreloaded)

My customize setting isn't showing up

Same checks as above but for ICustomizeCategory / BaseCustomizeSetting.

  • Does it have [CustomizeSetting(Section = ..., Icon = ...)]?
  • Is the Section enum value correctly spelled?
  • Does the category class use the correct [CustomizeCategory(PageType = ...)] attribute?

No revert data file after testing

Tests that check revert data expect files in %localappdata%\optimizerDuck\Revert\. Test cleanup runs in finally blocks — make sure assertions run before cleanup.

UI freezes when applying an optimization

Ensure your ApplyAsync uses async/await for any provider calls that are async (ChangeServiceStartupTypeAsync, CMDAsync, PowerShellAsync). If you're using Task.FromResult or blocking with .Result / .Wait(), the UI thread will freeze.

How do I generate a GUID?

# PowerShell
[guid]::NewGuid()
# Command line (if uuidgen is available)
uuidgen

Translations showing as key names in the UI

You missed adding localization keys to Translations.resx. Check the Localization section for the expected key patterns.

"No revert data" error when reverting

Check that the optimization's Id GUID hasn't changed. Revert files are keyed by Id. If you regenerate the GUID, previously applied optimizations won't have matching revert files.

How do I add a new revert step type?

  1. Create a new class in Domain/Revert/Steps/ that implements IRevertStep
  2. Add a static FromData(JObject data) method for deserialization
  3. The RevertManager's reflection-based _stepRegistry will auto-discover it
  4. Record it via ExecutionScope.RecordStep() with your step as the revertStep parameter

How does the app handle crash safety?

  • Revert files use atomic writes (.tmp + File.Replace)
  • Crash logging writes to %localappdata%\optimizerDuck\Crashes\crash_*.log
  • WmiHelper.Initialize() at startup registers WMI cleanup for abnormal termination
  • App.xaml.cs registers 3 global exception handlers

Credits

Contributors with merged PRs are listed in release notes. If you contribute significantly to a module, you can add an author tag at the top of the file header.


License

By contributing to optimizerDuck, you agree that your contributions will be licensed under the project's GPL v3 License.


Thanks for making optimizerDuck better.

Contributors