Skip to content

feat: rebuild Industry UI - #4

Merged
nicolasestrem merged 16 commits into
mainfrom
codex/winrestorekit-visual-exploration
Aug 3, 2026
Merged

nicolasestrem merged 16 commits into
mainfrom
codex/winrestorekit-visual-exploration

Conversation

@nicolasestrem

Copy link
Copy Markdown
Owner

Summary

  • Rebuild the WinForms shell and primary views with the Industry design system, bundled typography, blueprint controls, icon rail navigation, and persisted Voltage, Flux, and Follow system palettes.
  • Add snapshot display names, destination selection, archive compression, archive-backed restore discovery, live registry drift detection, and a dedicated progress experience with real metrics and run controls.
  • Preserve frozen backup folder naming and existing manifest compatibility.
  • Prevent a second backup or restore request while a run is active.

Verification

  • dotnet build src/WinRestoreKit.sln --no-restore
  • dotnet test src/WinRestoreKit.sln --no-restore
  • Result: 841 passed, 0 failed.
  • Desktop smoke: Home and Back up rendered with repaired title-bar labels and compression selector painting.

Notes

  • Includes focused regressions for compressed-payload drift detection and single-run admission.
  • Preserves unrelated local work: .gitignore, existing changelog exploration notes, .github/workflows/opencode.yml, and .superpowers/ are not in this PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af43fe7014

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

cardEntries.Clear();
cards.Controls.Clear();

BackupFolders folders = BackupFolders.Read();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include custom destinations in restore discovery

When a user selects a destination other than Data.DataRootDir, the backup is successfully written there, but this picker still obtains every candidate from BackupFolders.Read(), which only scans Data.DataRootDir. The repository has no browse/import path in the restore wizard, so backups created through the new destination selector disappear from Home, History, and Restore and cannot be restored through the application.

Useful? React with 👍 / 👎.

{
try
{
Directory.Delete(item.Folder.Path, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block pruning the active restore source

During an active restore the History rail remains available, and pausing at a module boundary makes it straightforward to invoke this action. If the source is an uncompressed recognized user backup older than 90 days, it is included in candidates and deleted here; resuming then makes subsequent modules read a missing source after earlier modules have already changed the machine. Exclude the current restore source or disable pruning while RunCoordinator.IsRunning.

Useful? React with 👍 / 👎.

Comment on lines +417 to +423
if (!summaryShown)
{
((IRunUi)this).ShowSummary(
RunSummary.For(new List<ModuleOutcome>(), false, verb,
"the " + caption.ToLowerInvariant() + " runner returned without a result"),
caption,
new List<ModuleOutcome>());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat consent cancellation as cancellation

When the user presses Cancel in the restore consent dialog, RunRestoreCore intentionally returns without calling ShowSummary; this fallback therefore renders the normal user cancellation as a problem stating that the runner returned without a result. Since the dedicated progress page is already visible by then, this path should show a canceled/no-changes result or return to the wizard rather than reporting an execution failure.

Useful? React with 👍 / 👎.

@nicolasestrem

Copy link
Copy Markdown
Owner Author

/oc review this pr

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 040367560c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +72 to +73
foreach (SourceFile source in sourceFiles)
File.Delete(source.FullPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the app export visible to its restore dialog

When the default Fast compression is used and AppStoreApps succeeds, this loop deletes its root-level Remember installed apps.json after adding it to the payload. Unlike other modules, AppStoreApps.RestoreAsync ignores the extracted restore path and opens RestAppsForm, which enumerates Data.DataRootDir and reads AppStoreApps.ExportPathIn directly from the selected backup folder. Consequently, even a backup in the default destination appears in the restore wizard but the app-reinstall dialog cannot read its package list; pass the extracted source into that dialog or retain this artifact where its reader expects it.

Useful? React with 👍 / 👎.

Comment thread src/WinRestoreKit/Helpers/FontLoader.cs Outdated
Comment on lines +46 to +48
finally
{
handle.Free();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain embedded font buffers for the collection lifetime

PrivateFontCollection.AddMemoryFont requires the supplied memory to remain valid while the collection uses the font, but this frees the only pinned handle immediately and retains no reference to fontBytes. After EnsureLoaded returns, the GC may move or reclaim those arrays while controls throughout the application continue creating and drawing Barlow/IBM Plex fonts from Collection, leading to invalid font data or native drawing failures. Keep each pinned buffer alive until the private collection is disposed.

Useful? React with 👍 / 👎.

Comment thread src/WinRestoreKit/Views/HomePageView.cs Outdated
Comment on lines +110 to +112
BackupFolder latest = folders.Backups[0];
IReadOnlyList<DriftItem> drifted = DetectDrift(latest, registrations, out string driftUnavailableReason);
string driftCount = drifted == null ? "Unavailable" : drifted.Count.ToString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid extracting the full payload during Home refresh

With the new default Fast compression, this synchronous Home-page call reaches BackupPayload.TryPrepareForRead, which extracts every payload entry to a temporary directory before drift detection examines only the registry-capable modules. Since Home is rendered during startup and on every return to it, a backup containing large font or folder artifacts can make the UI appear hung and require temporary space equal to the full uncompressed snapshot merely to display the dashboard. Probe only the entries needed for drift, cache the prepared payload, or move extraction off the UI path.

Useful? React with 👍 / 👎.

Comment on lines +78 to +81
catch (Exception ex)
{
error = ex.Message;
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Delete partial payload files when archiving fails

If ZIP creation throws—for example because the destination fills up or a source file becomes unreadable—the catch returns without deleting the .payload-<guid>.tmp created above. These partial archives can be nearly as large as the backup, are explicitly excluded from later archive inputs, and InvalidateBackupManifest removes only payload.zip, so repeated attempts accumulate orphaned data in the backup folder and may exhaust the destination. Hoist the temporary path outside the try and remove it on every failure path.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

I read the key changed files and built the solution. The build succeeds with EnableWindowsTargeting=true after a restore. The xUnit suite cannot execute on this Linux agent because the Microsoft.WindowsDesktop.App 8.0 runtime is missing, so I cannot independently verify the 841 passing tests.

Review

1. Custom-destination backups are not discoverable for restore

src/WinRestoreKit/Views/BackupPageView.cs lets the user pick a destination folder other than Data.DataRootDir, but src/WinRestoreKit/Views/RestoreWizardStep1View.cs:148 only calls BackupFolders.Read(), which enumerates Data.DataRootDir. Backups written to a custom location therefore disappear from Home, History, and Restore and cannot be restored through the app. This contradicts the "destination selection" feature in the PR summary.

Fix: either scan the manifest registry for all known destination paths, or remove custom destinations until the restore wizard can browse to them.

2. Pruning can delete the active restore source

src/WinRestoreKit/Views/HistoryPageView.cs:510 deletes candidate backups older than 90 days without checking RunCoordinator.IsRunning. If the user pauses a restore at a module boundary, opens History, and prunes, the uncompressed source backup can be deleted while the restore is still in progress.

Fix: guard the prune action with RunCoordinator.IsRunning and disable the button while any run is active.

3. Restore consent cancellation is reported as a runner failure

src/WinRestoreKit/Views/ProgressPageView.cs:423 falls through to the !summaryShown branch and shows "the runner returned without a result" when the user cancels the restore confirmation dialog. The orchestrator intentionally returns without calling ShowSummary, but the progress view misinterprets that as an execution failure.

Fix: in BackupRestoreOrchestrator.RunRestoreCore, call ui.ShowSummary(RunSummary.For(..., ran: false, verb: Restore, because: "the confirmation dialog was cancelled"), ...) before returning on consent cancellation, so the progress view renders the correct "did not run" state.

4. Minor: ListEmptyPayloadDirectories leaks an enumerator

src/WinRestoreKit.Core/Results/BackupPayload.cs calls Directory.EnumerateFileSystemEntries(path).GetEnumerator().MoveNext() without disposing the enumerator. BackupBase.HasFolderOrKeyArtifactIn uses a foreach to avoid the same issue. Consider matching that pattern.

5. Minor: BackupPageView still uses absolute Location for some child controls

src/WinRestoreKit/Views/BackupPageView.cs has Location = new Point(...) for heading labels and scope-row labels, while CLAUDE.md says the views are built with TableLayoutPanel/Dock/AutoSize and absolute positioning is gone. The smoke test apparently covered the visible result, but this is a design inconsistency.

6. Build/test note

The PR verification commands (dotnet build ... --no-restore and dotnet test ... --no-restore) require a prior restore and the Windows desktop runtime. I built successfully with dotnet build src/WinRestoreKit.sln -p:EnableWindowsTargeting=true, but dotnet test aborted because Microsoft.WindowsDesktop.App 8.0.0 is not installed on this agent. The claimed 841 passing tests are not independently verified here.

Recommendation

Request changes for items 1–3. Item 1 makes a new feature effectively unreachable; item 2 is a destructive safety issue during active restores; item 3 misreports user cancellation as a failure. Items 4–5 can be follow-up cleanups.

New%20session%20-%202026-08-03T05%3A49%3A17.801Z
opencode session  |  github run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d26758005

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +459 to +462
return scopeGroups
.Where(group => scopeToggles[group].Checked)
.SelectMany(group => group.Modules)
.Distinct()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve opt-in for full environment exports

When the user selects the App settings group, this expansion runs every module in that group, including both EEnvironmentFiltered and the unrestricted EEnvironment. The latter explicitly exports environment variables such as API tokens and access keys in plaintext, but the rebuilt page provides neither a module-level opt-out nor the module's warning, so choosing the apparently generic group can expose credentials even when the user would have chosen the filtered variant in the previous module tree. Keep these alternatives independently selectable and surface the unrestricted export warning.

Useful? React with 👍 / 👎.

Comment on lines +88 to +95
foreach (string path in paths)
{
BackupFolder folder = new BackupFolder(path);

if (IsSnapshot(folder.Name))
snapshots.Add(folder);
else
backups.Add(folder);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter unrelated directories from custom backup roots

When a user chooses an existing general-purpose destination such as Documents or a drive root, every immediate child directory is converted into a BackupFolder, not just the timestamp folder created by WinRestoreKit. Because folders without manifests are intentionally accepted as legacy backups, unrelated Projects/Photos/etc. then appear in Home, History, and Restore; Home may recursively measure their entire contents, and Restore may offer them as unknown legacy sources. Record the exact created backup path or require recognizable backup artifacts when enumerating custom roots.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08d777a0c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +83 to +85
catch (Exception ex)
{
return new BackupFolders(backups, snapshots, ex.Message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Continue past an unavailable custom backup root

When a remembered USB drive, disconnected network share, or other custom root throws an IOException rather than DirectoryNotFoundException, this returns a non-null UnreadableReason even if the default root was already enumerated successfully. RestoreWizardStep1View.RefreshView treats any such reason as fatal and returns before adding the accumulated folders, so one offline custom destination makes every valid local backup disappear from Restore, Home, and History until that destination becomes available again. Handle failures per custom root while preserving readable roots, reserving the fatal result for the default root or for a state where nothing could be inspected.

Useful? React with 👍 / 👎.

incompleteOutcomes);
return;
}
bool archived = BackupPayload.TryArchive(backupPath, compression, out string archiveError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move payload archiving off the UI thread

With the new default Fast compression, this synchronous call runs after RunModulesBackup resumes on the WinForms synchronization context, so compressing and verifying a large fonts/AppData snapshot blocks painting and input for the entire archive operation. The module loop has already reported 100% at this point, leaving the application apparently frozen at completion and making Pause/Cancel unusable during what may be a substantial part of the run; perform this work asynchronously and represent it as a progress phase.

Useful? React with 👍 / 👎.

Comment on lines +198 to +203
private void UpdateProgressButton(bool running)
{
btnProgress.Enabled = railEnabled && running;

private void btnHome_Click(object sender, EventArgs e)
=> navigation.Show(homePage);
if (!btnProgress.Enabled)
btnProgress.IsSelected = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep completed run results reachable

If the user navigates from the progress page to Home, Back up, Restore, or History while a run is active, those rail handlers call navigation.Show and clear the back stack. When the run then finishes, this disables the only rail control that can reopen progressPage, so the success/failure summary and per-module outcomes become inaccessible even though the page is retained in memory. Keep the progress entry enabled until the result is dismissed, or prevent navigation away during the run.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c35a291b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

detail = "Cancellation was requested. No further group was started. Completed output " +
"remains without a trusted manifest.";
}
else if (TryRemoveIncompleteBackupFolder(backupPath))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid deleting a backup folder owned by another process

When two app instances start a backup to the same destination during the same minute, both derive the same Data.NowShort path and can observe it as absent before either creates it. If the other instance creates or completes the folder first, this instance still has folderExistedBeforeRun == false, so canceling recursively deletes the shared folder here, including the other run's artifacts. Establish folder ownership atomically or use a unique path before removing canceled output.

Useful? React with 👍 / 👎.

Comment on lines +29 to +30
{
if (module.HasDriftedFrom(backupPath) == true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve unknown drift results

When the latest backup contains only modules that cannot compare drift, or registry probing/export fails, every HasDriftedFrom call returns null; this loop silently drops those results and returns an empty non-null list. HomePageView consequently renders the run as measured with 0 drift and “No tracked changes found” rather than unavailable, even though no comparison succeeded. Propagate comparison coverage or an unknown result instead of collapsing it into zero drift.

Useful? React with 👍 / 👎.

Comment on lines +51 to +52
if (BackupPayload.TryPrepareForRead(restoreSourcePath, out payload, out string ignoredError))
probePath = payload.Path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid extracting the payload twice during restore

For a compressed backup containing large Fonts or AppData artifacts, opening restore step 2 synchronously extracts every archive entry here on the WinForms thread, and the finally immediately deletes that extraction. Starting the restore then calls BackupPayload.TryPrepareForRead again and extracts the same archive a second time, blocking navigation and doubling temporary I/O and space churn before any module runs. Probe the manifest/archive entries directly or retain and reuse a prepared payload scope.

Useful? React with 👍 / 👎.

@nicolasestrem

Copy link
Copy Markdown
Owner Author

/oc check

Repository owner deleted a comment from github-actions Bot Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Based on the PR review findings, here's the assessment:

Critical Issues (Must Fix)

  1. Custom-destination backups unreachable (BackupPageView → RestoreWizardStep1View): Backups written to custom locations disappear from restore/Home/History since only DataRootDir is scanned.

  2. Pruning can delete active restore source (HistoryPageView:510): No RunCoordinator.IsRunning check before pruning older backups during an active restore.

  3. Consent cancellation misreported (ProgressPageView:423): User cancellation shows "runner returned without result" instead of a proper cancellation state.

  4. Race condition on backup folder creation (BackupRestoreOrchestrator:182): Two app instances could target the same folder path and one could delete the other's artifacts.

  5. Font buffer lifetime (FontLoader.cs): Pinned font handles freed immediately while PrivateFontCollection still needs them - can cause native drawing failures.

High Priority

  • Home payload extraction (HomePageView:112): Full archive extracted synchronously on startup for drift detection - UI hangs possible.
  • Custom root availability (BackupFolders.cs): One offline USB/network drive makes all backups disappear.
  • App export visibility (BackupPayload.cs:74): Fast compression deletes app export before restore dialog can read it.
  • Environment module opt-in (BackupPageView:486): Unrestricted environment export (with credentials) included without warning when selecting group.

Medium Priority

  • Partial payload cleanup (BackupPayload.cs:82): Failed archive attempts leave temp files.
  • Double extraction during restore (RestoreContents.cs:52): Compressed backups extracted twice.
  • Run results accessibility (MainForm.cs:213): Progress page unreachable after navigating away during run.
  • Unknown drift results (DriftDetector.cs:30): Null comparisons collapsed to "0 drift" instead of "unavailable".
  • Enumerator leak (BackupPayload.cs:74): GetEnumerator() not disposed.

Recommendation

Request changes for items 1-5. The custom-destination discoverability and prune-safety issues are design flaws that break core features; the font buffer issue can cause crashes on some systems.

New%20session%20-%202026-08-03T07%3A25%3A43.412Z
opencode session  |  github run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4c33b1b5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/WinRestoreKit/Views/HomePageView.cs Outdated
return;
}

BackupFolder latest = folders.Backups[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Select the latest snapshot for the current account

When a remembered shared/custom root contains a newer backup created by another machine or Windows account, this global newest entry becomes the dashboard baseline without checking its manifest provenance. The page then compares that foreign snapshot against the current registry and claims it was taken from the current machine, potentially telling users this PC is protected while reporting meaningless drift; select by manifest machine/user or clearly treat foreign backups as non-current.

Useful? React with 👍 / 👎.

Comment thread src/WinRestoreKit/Views/HomePageView.cs Outdated
+ watchedGroups.Count + " watched groups are under watch; "
+ (drifted == null ? "drift could not be measured" : drifted.Count + " have drifted since the snapshot") + ".";

BuildHeader("SYSTEM IS CAPTURED", summary);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check backup outcomes before declaring the system captured

When the newest backup manifest records failed, skipped, or unknown modules—even every selected module failing—the dashboard still unconditionally renders SYSTEM IS CAPTURED. History already derives a Failed/Partial result from these same manifest states, so the Home status can falsely reassure users after an unsuccessful backup; gate this headline and protection summary on the latest manifest outcomes.

Useful? React with 👍 / 👎.

Comment on lines +225 to +226
if (destinationRoot != null)
BackupRootRegistry.Remember(destinationRoot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remember custom roots before running the backup

For the first backup to a new custom destination, a crash or power loss during module execution or archiving occurs before this call, so the root is never persisted and the partial backup cannot be rediscovered after restart. The new BackupRootRegistry discovery is fresh evidence beyond the earlier complete-backup issue: completed roots are now found, but interrupted roots still disappear despite custom-folder recognition explicitly admitting interrupted timestamp folders; remember the root once its destination folder is created.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff267b5e53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 463 to 468
if (!TryRemove(path) && File.Exists(path))
{
logger.LogMessage(
"The previous backup manifest at " + path + " could not be removed or emptied. "
"The previous backup metadata or payload at " + path + " could not be removed or emptied. "
+ "If this run does not finish, that file still describes the PREVIOUS run.");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Abort when the previous payload cannot be invalidated

When a second backup reuses the process-scoped timestamp folder while its previous payload.zip is locked against deletion and truncation, this path only logs the failure and continues. The new modules write loose artifacts, but TryArchive excludes the existing payload and cannot replace the locked file; the run then publishes a new uncompressed manifest, while TryPrepareForRead later sees the stale archive and restores the previous run's data instead. Abort the run or otherwise prevent a stale payload from surviving alongside the new manifest.

Useful? React with 👍 / 👎.

Comment on lines +117 to +120
if (!File.Exists(payloadPath))
{
payload = new ReadScope(backupPath, null);
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail when a manifest-declared payload is absent

If a compressed backup's payload.zip was omitted during copying, deleted, or quarantined, this branch treats the metadata-only directory as a valid loose legacy backup even though its manifest declares that the module artifacts are archived. The wizard trusts the manifest's succeeded rows, so starting the restore can take snapshots and close applications before modules fail or do nothing against the empty root. Check the manifest's payload declaration and return a preparation error when the declared archive is missing.

Useful? React with 👍 / 👎.

Comment on lines +114 to +119
foreach (string path in paths)
{
if (!isDefaultRoot && !IsRecognizableCustomFolder(path))
continue;

BackupFolder folder = new BackupFolder(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude nested custom-root containers from default discovery

When the user chooses a destination beneath Data.DataRootDir, such as DataRootDir\Archive, the default-root pass admits Archive itself as a legacy backup because this filter is bypassed, and the remembered custom-root pass separately admits the timestamp folder inside it. The container consequently appears as a bogus restore source and Home/History count its contents again through the real child backup. Detect nested remembered roots or exclude their container directories during the default-root pass.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71b7effb81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

SnapshotCompression = compression;
string backupPath = Path.Combine(destinationPath, Data.NowShort);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject destinations nested inside a selected source

When the chosen destination lies inside a folder being backed up—for example, selecting the VS Code snippets directory while capturing app settings—this makes the timestamped backup a descendant of that source. WindowsHelper.CopyFolderInto creates its destination before enumerating the source's subdirectories, so it then discovers the new backup directory and recursively copies the backup into itself until path-length or disk-space exhaustion. Validate the destination against the selected modules' source folders before starting the run.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05bbffc2a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +786 to +789
void IRunUi.SetExplorerRestartVisible(bool visible)
{
if (visible)
UpdateUi(() => AppendLog("Explorer restart is required to apply restored settings."));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the Explorer restart action

When a restore successfully writes a module such as WTaskbar, this implementation only appends a log line and offers no way to call Utils.RestartExplorer(). The removed RunResultsPanel previously exposed that action, and WTaskbar depends on the post-import forced restart to prevent Explorer's in-memory pin list from overwriting the restored Taskband data on sign-out. Add the restart control and preserve its retry/result handling rather than merely logging the requirement.

Useful? React with 👍 / 👎.


results.Add(outcome);

bool hasByteMeasurement = TryMeasureBackupArtifactBytes(folder, out long bytesWritten);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move artifact-size scans off the UI thread

When a selected module writes a large folder tree, this call recursively re-enumerates every accumulated backup file after every module, and the await module.BackupAsync(...) continuation resumes on the WinForms synchronization context. A large Fonts/AppData snapshot therefore causes repeated O(modules × accumulated files) UI-thread stalls between groups, during which Pause and Cancel cannot respond. Measure incrementally or perform the recursive scan off the UI thread.

Useful? React with 👍 / 👎.

@nicolasestrem
nicolasestrem merged commit f4c33ba into main Aug 3, 2026
1 check passed
@nicolasestrem
nicolasestrem deleted the codex/winrestorekit-visual-exploration branch August 3, 2026 08:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d54055f15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +66 to +67
if (BackupPayload.TryPrepareForRead(restoreSourcePath, out payload, out string ignoredError))
probePath = payload.Path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep stale modules out of the new payload

When an uncompressed backup is followed by a Fast/Max backup with a different selection in the same process, both runs reuse the frozen Data.NowShort folder, so artifacts from modules omitted by the second run remain and are swept into its new archive. Extracting that archive here lets HoldsSomethingFor find those stale artifacts despite their absence from the new manifest, causing the wizard to enable and preselect them and a restore to apply data from the earlier run. Either remove artifacts not produced by the current run before archiving or treat a valid manifest's omission as authoritative.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant