fix(installer): make user-data deletion on uninstall opt-in

Add bool removeAppData parameter (default false) to UninstallRunner.RunAsync,
gate ~/.todo-app deletion on it, surface a checkbox in SettingsWindow, and
update the confirmation message to reflect whether data will be removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-17 14:29:35 +02:00
parent 7d48f34b15
commit 5f3d41e1f6
3 changed files with 27 additions and 13 deletions

View File

@@ -17,7 +17,7 @@ public sealed class UninstallRunner
_stopService = stopService;
}
public async Task<StepResult> RunAsync(IProgress<string> progress, CancellationToken ct)
public async Task<StepResult> RunAsync(bool removeAppData, IProgress<string> progress, CancellationToken ct)
{
// 1) Validate install dir up front — refuse obviously unsafe paths.
// Prevents Directory.Delete(recursive:true) from wiping C:\ or C:\Program Files\.
@@ -67,7 +67,9 @@ public sealed class UninstallRunner
failures.Add($"install dir ({_context.InstallDirectory}): {err}");
}
// 6) Delete ~/.todo-app (config + DB + logs) — user opted into full removal.
// 6) Delete ~/.todo-app (config + DB + logs) — only if user opted in.
if (removeAppData)
{
var appData = Paths.AppDataRoot();
if (Directory.Exists(appData))
{
@@ -75,6 +77,7 @@ public sealed class UninstallRunner
if (!TryDeleteDir(appData, out var err))
failures.Add($"app data ({appData}): {err}");
}
}
// 7) If we were launched from inside the install dir (Apps & Features case),
// our own exe is still locked — schedule a cmd.exe trampoline to finish

View File

@@ -29,6 +29,9 @@ public partial class SettingsViewModel : ObservableObject
[ObservableProperty]
private string _versionLabel = "";
[ObservableProperty]
private bool _removeAppData;
public SettingsViewModel(
PageResolver resolver,
InstallContext context,
@@ -133,8 +136,12 @@ public partial class SettingsViewModel : ObservableObject
[RelayCommand]
private async Task Uninstall()
{
var dataNote = RemoveAppData
? "This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?"
: "This will remove ClaudeDo. Your tasks, configuration, and database in ~/.todo-app will be kept.\n\nContinue?";
var confirm = MessageBox.Show(
"This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?",
dataNote,
"Uninstall ClaudeDo",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
@@ -142,7 +149,7 @@ public partial class SettingsViewModel : ObservableObject
if (confirm != MessageBoxResult.Yes) return;
var progress = new Progress<string>(msg => StatusMessage = msg);
var r = await _uninstallRunner.RunAsync(progress, CancellationToken.None);
var r = await _uninstallRunner.RunAsync(RemoveAppData, progress, CancellationToken.None);
if (!r.Success)
{

View File

@@ -69,6 +69,7 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Status message / version label -->
@@ -88,14 +89,17 @@
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Content="Uninstall" Margin="0,0,8,0"
<CheckBox Grid.Column="1" IsChecked="{Binding RemoveAppData}"
Content="Remove user data (tasks, logs, configs in ~/.todo-app)"
Margin="0,0,12,0" VerticalAlignment="Center"/>
<Button Grid.Column="2" Content="Uninstall" Margin="0,0,8,0"
Command="{Binding UninstallCommand}"/>
<Button Grid.Column="2" Content="Repair" Margin="0,0,8,0"
<Button Grid.Column="3" Content="Repair" Margin="0,0,8,0"
Command="{Binding RepairCommand}"/>
<Button Grid.Column="3" Content="Save" Margin="0,0,8,0"
<Button Grid.Column="4" Content="Save" Margin="0,0,8,0"
Command="{Binding SaveCommand}"
Style="{StaticResource AccentButton}"/>
<Button Grid.Column="4" Content="Close"
<Button Grid.Column="5" Content="Close"
Command="{Binding CloseCommand}"/>
</Grid>
</Border>