Skip to content

Add change-storage-location (move / adopt / warn + restart)#30

Merged
StuartMeeks merged 5 commits into
mainfrom
feat/storage-path-change
May 30, 2026
Merged

Add change-storage-location (move / adopt / warn + restart)#30
StuartMeeks merged 5 commits into
mainfrom
feat/storage-path-change

Conversation

@StuartMeeks

Copy link
Copy Markdown
Owner

Implements the "Storage path: move / adopt / warn-on-conflict" carried-over item (task #8). Settings → Storage location becomes editable via a "Change…" button.

Behaviour (the three flows)

Picking a folder is classified by StorageRelocationService.Inspect:

  • Adopt — the folder already contains a Snipdeck store → use it from now on; your current snips are left where they are (this is the warn/conflict case — the confirm dialog says the folder already has a store and your current snips won't be moved).
  • Move — folder is empty and you have a store → your store.json + icons/ are moved there.
  • Set — neither has a store → just adopt the empty folder.

Each is confirmed first; cancelling (or cancelling the folder picker) is a clean no-op.

⚠️ Design decision for your review: apply-and-restart

The storage path is only read at startup (Bootstrap resolves it once; ISnipStore/BackupService/IconAssetStorage are immutable singletons). Rather than a risky live-repoint refactor I couldn't runtime-verify, a confirmed change persists the new path and restarts the app (IAppRestartService → WinAppSDK AppInstance.Restart).

This is a correctness choice, not just convenience: if the app kept running against the old store after the switch, edits made before a manual restart would silently land in the old location and appear lost. Restarting eliminates that window. Trade-offs / alternatives I considered are in the commit message; happy to switch to live-reload if you'd prefer (bigger refactor).

What's where

  • Core (UI-free, tested): IStorageRelocationService + StorageRelocationService (classify + move), IFolderPickerService, IAppRestartService; SettingsViewModel.ChangeStoragePathCommand + observable StorageDirectory.
  • App: WindowsFolderPickerService (FolderPicker), WindowsAppRestartService (AppInstance.Restart), DI registration, the "Change…" button.

Tests

  • StorageRelocationServiceTests (5: all Inspect outcomes + a real move of store+icons against temp dirs) and SettingsViewModelTests storage cases (4: move, adopt-leaves-current, cancel, picker-cancel). Full suite: 157 passing.
  • WinUI head compiles clean on the Windows build agent (0/0).

Needs your manual verification (can't be done from Linux)

  • The AppInstance.Restart call actually restarts the unpackaged app, and that after a real Move/Adopt the relaunched app loads from the new folder. If Restart misbehaves, the config is already persisted, so a manual relaunch still picks up the new path correctly.

🤖 Generated with Claude Code

StuartMeeks and others added 5 commits May 30, 2026 10:20
Settings → Storage location gains a "Change…" button. Picking a folder
classifies the change (Core StorageRelocationService):
- target already has a store -> adopt it (current store left in place; this
  is the conflict/warn case, surfaced in the confirm copy)
- target empty, current has a store -> move store + icons there
- neither -> just adopt the empty target

On confirm, the new path is persisted and the app restarts (IAppRestartService
-> AppInstance.Restart). The storage path is only read at startup, so
apply-and-restart keeps the immutable startup singletons consistent and avoids
the running app writing snips to the old location after the switch (a
live-repoint would need a store-indirection refactor + Windows runtime
verification — deferred deliberately).

New Core: IStorageRelocationService/StorageRelocationService,
IFolderPickerService, IAppRestartService; SettingsViewModel gains
ChangeStoragePathCommand and an observable StorageDirectory. App:
WindowsFolderPickerService, WindowsAppRestartService, DI + XAML wiring.
20 new Core tests (relocation outcomes + move; VM move/adopt/cancel/picker-cancel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picking a folder inside the current storage directory (e.g. its icons/
subfolder) would make MoveStore copy a directory into itself and could
delete the just-copied store. Inspect now returns a new Invalid outcome for
either-way nesting, and the change command notifies and aborts.

Found by codex review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the move case, copy the store/icons, persist the new StoragePath, and only
then remove the originals. Previously the old store was deleted before the
config was saved, so a failed PersistAsync (locked settings file, full disk)
could leave the app pointing at an emptied old location — snips appearing lost.
Split MoveStore into CopyStore + RemoveStore to allow the safe ordering.

Found by codex review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AppInstance.Restart returns a failure reason rather than guaranteeing
termination, so IAppRestartService.Restart now returns bool. The change-path
flow never deletes the old store from the running process (left as a backup);
on a failed restart it notifies the user to restart manually, so the app
stays consistent against the still-intact old store. Removed the now-unused
RemoveStore.

Found by codex review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
If AppInstance.Restart can't restart, the running app stays bound to the old
store while the persisted setting would point future launches at the new one —
so edits before a manual restart would be lost. On restart failure, revert the
persisted StoragePath to the previous value so this session and future launches
stay consistent on the old store, and notify the user it didn't take. The
copied target files remain as a harmless backup.

Found by codex review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@StuartMeeks StuartMeeks merged commit fb32691 into main May 30, 2026
4 checks passed
@StuartMeeks StuartMeeks deleted the feat/storage-path-change branch May 30, 2026 12:01
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