Skip to content

Commit 90c4a72

Browse files
committed
feat(reset): add remove-account mode and bump version to 2.6.1
- Add Reset mode selector in Reset tab: Full Reset or Remove Account Only.\n- Implement backend API ResetAccountOnly(appKey) with mandatory app close flow.\n- Remove account files directly via known targets:\n User/globalStorage/state.vscdb\n User/globalStorage/state.vscdb.backup\n User/globalStorage/storage.json\n- Do not run auto-backup in Remove Account Only flow.\n- Make Restore Addon Only non-disruptive: no close/kill process step.\n- Simplify Sessions addon-restore UI flow by removing failed-close override branch.\n- Update Wails bindings for new backend API.\n- Bump version to 2.6.1 in frontend version/package files.\n- Update CHANGELOG and README with 2.6.1 release notes.
1 parent 5adeb78 commit 90c4a72

File tree

11 files changed

+221
-38
lines changed

11 files changed

+221
-38
lines changed

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,32 @@
22

33
All notable changes to SurfManager will be documented in this file.
44

5+
## [2.6.1] - 2026-02-08
6+
7+
### Summary
8+
9+
Reset and restore behavior update focused on account management flow and safer addon restore UX.
10+
11+
### Added
12+
13+
- Reset tab now shows a mode selector modal with:
14+
- Full Reset
15+
- Remove Account Only
16+
- Added backend API: `ResetAccountOnly(appKey)`
17+
18+
### Changed
19+
20+
- `RestoreAddonOnly` no longer closes/kills the target app.
21+
- Remove Account Only now removes known account files directly (no dependency on app `backup_items` config):
22+
- `User/globalStorage/state.vscdb`
23+
- `User/globalStorage/state.vscdb.backup`
24+
- `User/globalStorage/storage.json`
25+
- Remove Account Only does not create auto-backup.
26+
27+
### Fixed
28+
29+
- Fixed failure case: `no account-only backup items configured for <app>` during Remove Account Only.
30+
531
## [2.5.0] - 2026-02-07
632

733
### Summary

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# SurfManager v2.5.0
1+
# SurfManager v2.6.1
22

33
> **Advanced Session & Data Manager for Development Tools**
44
5-
[![Version](https://img.shields.io/badge/version-2.5.0-brightgreen.svg)](https://github.com/risunCode/SurfManager)
5+
[![Version](https://img.shields.io/badge/version-2.6.1-brightgreen.svg)](https://github.com/risunCode/SurfManager)
66
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-blue.svg)](https://github.com/risunCode/SurfManager)
77
[![Go](https://img.shields.io/badge/go-1.22+-00ADD8.svg)](https://golang.org/)
88
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
@@ -55,6 +55,13 @@ Perfect for developers who need to:
5555
| **📝 Custom App Config** | VSCode preset or fully custom backup items |
5656
| **✏️ Edit App Config** | Edit existing app configurations via UI |
5757

58+
### 🚀 What's New in v2.6.1
59+
60+
- **Reset Mode Selector** - Reset button now offers Full Reset or Remove Account Only
61+
- **Remove Account Only** - Deletes only `User/globalStorage/state.vscdb`, `state.vscdb.backup`, and `storage.json`
62+
- **No Auto-Backup for Remove Account** - Account-only removal skips auto-backup by design
63+
- **Addon Restore No-Close** - Restore Addon Only no longer kills/closes running apps
64+
5865
### 🚀 What's New in v2.5.0
5966

6067
- **3-5x Faster Backup/Restore** - Parallel file copying using all CPU cores
@@ -398,7 +405,7 @@ SurfManager is open-source under the MIT License.
398405

399406
<div align="center">
400407

401-
**SurfManager v2.5.0**
408+
**SurfManager v2.6.1**
402409

403410
*Making development workflows smoother, one session at a time*
404411

app.go

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,88 @@ func (a *App) ResetApp(appKey string, autoBackup bool, skipClose bool) error {
320320
return nil
321321
}
322322

323+
// ResetAccountOnly removes only known account-related files.
324+
// This operation always requires the target app to be closed first.
325+
func (a *App) ResetAccountOnly(appKey string) error {
326+
cfg := a.GetApp(appKey)
327+
if cfg == nil {
328+
return fmt.Errorf("app not found: %s", appKey)
329+
}
330+
331+
dataPath := a.GetAppDataPath(appKey)
332+
if dataPath == "" {
333+
return fmt.Errorf("no data folder found for %s", cfg.DisplayName)
334+
}
335+
336+
// Validate and clean the data path
337+
dataPath = filepath.Clean(dataPath)
338+
absDataPath, err := filepath.Abs(dataPath)
339+
if err != nil {
340+
return fmt.Errorf("invalid data path: %w", err)
341+
}
342+
343+
processNames := a.collectProcessNames(cfg)
344+
345+
// Remove account-only always requires app to be closed
346+
if len(processNames) > 0 {
347+
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
348+
"percent": 10,
349+
"message": fmt.Sprintf("Closing %s...", cfg.DisplayName),
350+
})
351+
if err := a.process.SmartClose(cfg.DisplayName, processNames); err != nil {
352+
return fmt.Errorf("failed to close %s: %w", cfg.DisplayName, err)
353+
}
354+
}
355+
356+
accountOnlyPaths := []string{
357+
"User/globalStorage/state.vscdb",
358+
"User/globalStorage/state.vscdb.backup",
359+
"User/globalStorage/storage.json",
360+
}
361+
362+
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
363+
"percent": 30,
364+
"message": "Removing account files...",
365+
})
366+
367+
deletedCount := 0
368+
for i, relPath := range accountOnlyPaths {
369+
cleanRelPath := filepath.Clean(filepath.FromSlash(relPath))
370+
targetPath := filepath.Clean(filepath.Join(absDataPath, cleanRelPath))
371+
372+
// Ensure resolved path stays inside app data directory
373+
if targetPath != absDataPath && !strings.HasPrefix(targetPath, absDataPath+string(os.PathSeparator)) {
374+
wailsRuntime.EventsEmit(a.ctx, "log", fmt.Sprintf("[RemoveAccountOnly] Skipped unsafe path: %s", relPath))
375+
continue
376+
}
377+
378+
if _, statErr := os.Stat(targetPath); statErr == nil {
379+
if rmErr := os.RemoveAll(targetPath); rmErr != nil {
380+
wailsRuntime.EventsEmit(a.ctx, "log", fmt.Sprintf("[RemoveAccountOnly] Failed to delete %s: %v", relPath, rmErr))
381+
} else {
382+
deletedCount++
383+
}
384+
}
385+
386+
progress := 30 + int(float64(i+1)/float64(len(accountOnlyPaths))*70)
387+
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
388+
"percent": progress,
389+
"message": fmt.Sprintf("Processed: %s", relPath),
390+
})
391+
}
392+
393+
if deletedCount == 0 {
394+
wailsRuntime.EventsEmit(a.ctx, "log", "[RemoveAccountOnly] No known account files found to delete")
395+
}
396+
397+
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
398+
"percent": 100,
399+
"message": fmt.Sprintf("Remove account complete! Deleted %d file(s)", deletedCount),
400+
})
401+
402+
return nil
403+
}
404+
323405
// GenerateNewID generates new machine IDs for an app
324406
func (a *App) GenerateNewID(appKey string) (int, error) {
325407
cfg := a.GetApp(appKey)
@@ -1241,18 +1323,7 @@ func (a *App) RestoreAddonOnly(appKey, sessionName string, skipClose bool) error
12411323
return fmt.Errorf("session '%s' does not have addon backups", sessionName)
12421324
}
12431325

1244-
processNames := a.collectProcessNames(cfg)
1245-
1246-
// Smart close the app (unless skipClose is true)
1247-
if !skipClose && len(processNames) > 0 {
1248-
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
1249-
"percent": 5,
1250-
"message": fmt.Sprintf("Closing %s...", cfg.DisplayName),
1251-
})
1252-
if err := a.process.SmartClose(cfg.DisplayName, processNames); err != nil {
1253-
return fmt.Errorf("failed to close %s: %w", cfg.DisplayName, err)
1254-
}
1255-
}
1326+
_ = skipClose // Addon-only restore does not close/kill the app.
12561327

12571328
wailsRuntime.EventsEmit(a.ctx, "progress", map[string]interface{}{
12581329
"percent": 20,

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "frontend",
33
"private": true,
4-
"version": "2.5.0",
4+
"version": "2.6.1",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

frontend/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0880540a82c9d48ae6d1b07e74b7df9b
1+
644b84176574c62f19a01d6c52deeb0e

frontend/src/lib/ResetTab.svelte

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script>
22
import { onMount, createEventDispatcher } from 'svelte';
33
import { FolderOpen, RotateCcw, Fingerprint, Play, RefreshCw, Plus, Trash2, XCircle, Copy, Database, HardDrive, Download, AlertTriangle, Loader2 } from 'lucide-svelte';
4-
import { GetActiveApps, CheckAppInstalled, GetAppDataPath, ResetApp, GenerateNewID, LaunchApp, OpenAppFolder, GetSessions, KillApp, ResetAddonData, GetApp, GetPlatformInfo, GetLogs } from '../../wailsjs/go/main/App.js';
4+
import { GetActiveApps, CheckAppInstalled, GetAppDataPath, ResetApp, ResetAccountOnly, GenerateNewID, LaunchApp, OpenAppFolder, GetSessions, KillApp, ResetAddonData, GetApp, GetPlatformInfo, GetLogs } from '../../wailsjs/go/main/App.js';
55
import { confirm } from './ConfirmModal.svelte';
66
import { toast } from './Toast.svelte';
77
import { settings } from './stores/settings.js';
@@ -12,6 +12,7 @@
1212
let loadingKill = false;
1313
let loadingAddon = false;
1414
let loadingLaunch = false;
15+
let showResetModeModal = false;
1516
1617
export let logs = [];
1718
export let globalRunningAppsStatus = {};
@@ -216,9 +217,45 @@
216217
}
217218
218219
async function handleReset() {
220+
if (!selectedApp || loadingReset || !selectedApp.dataPath) return;
221+
showResetModeModal = true;
222+
}
223+
224+
async function handleFullReset() {
225+
showResetModeModal = false;
219226
await handleResetWithOverride(false);
220227
}
221228
229+
async function handleRemoveAccountOnly() {
230+
showResetModeModal = false;
231+
if (!selectedApp || loadingReset) return;
232+
233+
if ($settings.confirmBeforeReset) {
234+
const confirmed = await confirm({
235+
title: 'Remove Account Only',
236+
message: `Remove account files for ${selectedApp.display_name}?\n\nThis will remove:\n- User/globalStorage/state.vscdb\n- User/globalStorage/state.vscdb.backup\n- User/globalStorage/storage.json\n\nAuto-backup will NOT be created.`,
237+
confirmText: 'Remove Account',
238+
cancelText: 'Cancel',
239+
danger: true
240+
});
241+
if (!confirmed) return;
242+
}
243+
244+
loadingReset = true;
245+
log(`[RemoveAccount] Starting ${selectedApp.display_name}...`);
246+
try {
247+
await ResetAccountOnly(selectedApp.app_name);
248+
log(`[RemoveAccount] ${selectedApp.display_name} complete!`);
249+
toast.success('Account data removed successfully', 3000);
250+
await loadApps();
251+
} catch (e) {
252+
log(`[RemoveAccount] Error: ${e}`);
253+
toast.error(`Remove account failed: ${e}`, 5000);
254+
} finally {
255+
loadingReset = false;
256+
}
257+
}
258+
222259
async function handleNewID() {
223260
if (!selectedApp || loadingNewID) return;
224261
@@ -638,3 +675,56 @@
638675
</div>
639676
</div>
640677
</div>
678+
679+
{#if showResetModeModal}
680+
<div
681+
class="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-[210]"
682+
role="dialog"
683+
aria-modal="true"
684+
on:click|self={() => showResetModeModal = false}
685+
>
686+
<div class="bg-[var(--bg-card)] rounded-xl border border-[var(--border)] w-full max-w-lg p-6 shadow-2xl">
687+
<h3 class="text-lg font-semibold text-[var(--text-primary)] mb-2">Choose Reset Mode</h3>
688+
<p class="text-sm text-[var(--text-secondary)] mb-5">
689+
Select which reset operation to run for <span class="text-[var(--text-primary)] font-medium">{selectedApp?.display_name}</span>.
690+
</p>
691+
692+
<div class="space-y-3">
693+
<button
694+
class="w-full p-4 rounded-lg border border-[var(--border)] bg-[var(--bg-elevated)] hover:border-[var(--danger)]/60 hover:bg-[var(--danger)]/10 transition-colors text-left"
695+
on:click={handleFullReset}
696+
>
697+
<div class="flex items-center gap-2 mb-1">
698+
<RotateCcw size={16} class="text-[var(--danger)]" />
699+
<span class="font-semibold text-[var(--text-primary)]">Full Reset</span>
700+
</div>
701+
<p class="text-xs text-[var(--text-secondary)]">
702+
Delete app data folder and addon folders. Auto-backup follows current setting.
703+
</p>
704+
</button>
705+
706+
<button
707+
class="w-full p-4 rounded-lg border border-[var(--border)] bg-[var(--bg-elevated)] hover:border-[var(--warning)]/60 hover:bg-[var(--warning)]/10 transition-colors text-left"
708+
on:click={handleRemoveAccountOnly}
709+
>
710+
<div class="flex items-center gap-2 mb-1">
711+
<Trash2 size={16} class="text-[var(--warning)]" />
712+
<span class="font-semibold text-[var(--text-primary)]">Remove Account Only</span>
713+
</div>
714+
<p class="text-xs text-[var(--text-secondary)]">
715+
Remove only known account files: <code>User/globalStorage/state.vscdb</code>, <code>state.vscdb.backup</code>, and <code>storage.json</code>.
716+
</p>
717+
</button>
718+
</div>
719+
720+
<div class="flex justify-end mt-6">
721+
<button
722+
class="px-4 py-2 rounded-lg font-medium bg-[var(--bg-hover)] hover:bg-[var(--border)] border border-[var(--border)] text-[var(--text-secondary)] transition-all"
723+
on:click={() => showResetModeModal = false}
724+
>
725+
Cancel
726+
</button>
727+
</div>
728+
</div>
729+
</div>
730+
{/if}

frontend/src/lib/SessionsTab.svelte

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -292,23 +292,6 @@
292292
toast.success('Addon folders restored successfully');
293293
await loadData();
294294
} catch (e) {
295-
const msg = e?.toString?.() || '';
296-
if (msg.toLowerCase().includes('failed to close')) {
297-
const appConfig = await GetApp(session.app);
298-
const override = await confirm({
299-
title: `${appConfig.display_name} still running`,
300-
message: 'We could not close the app automatically. If you already closed it, continue without closing step.',
301-
confirmText: "I've closed the app",
302-
cancelText: 'Cancel',
303-
danger: true
304-
});
305-
if (override) {
306-
await RestoreAddonOnly(session.app, session.name, true);
307-
toast.success('Addon folders restored successfully');
308-
await loadData();
309-
return;
310-
}
311-
}
312295
log(`Error: ${e}`);
313296
toast.error(`Addon restore failed: ${e}`);
314297
}

frontend/src/lib/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// Single source of truth for app version
2-
export const APP_VERSION = '2.5.0';
2+
export const APP_VERSION = '2.6.1';

frontend/wailsjs/go/main/App.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export function ReloadApps():Promise<void>;
7676

7777
export function RenameSession(arg1:string,arg2:string,arg3:string):Promise<void>;
7878

79+
export function ResetAccountOnly(arg1:string):Promise<void>;
80+
7981
export function ResetAddonData(arg1:string,arg2:boolean):Promise<void>;
8082

8183
export function ResetApp(arg1:string,arg2:boolean,arg3:boolean):Promise<void>;

0 commit comments

Comments
 (0)