feat(storage): add custom data path and safe migration - #1294
Merged
Merged
Conversation
Move desktop and Chromium state through a cold, verified migration so host and browser data stay consistent across drives. Keep the original roots as backups until explicit cleanup, and limit cache clearing to rebuildable paths.
Keep the architecture decision discoverable and satisfy the docs index check.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Failed copies can become untracked, destination permissions can expose credentials, and several user-facing errors remain unlocalized.
Review effort: Balanced
Findings: 1
Open (5)
Validate destination privacy before copying sensitive data · New Clean up or track orphaned profiles after failed migration · New Localize startup and preference-parse error details · New Localize filesystem errors through stable storage error codes · New Place Korean storage highlight under version 0.16.0 · New
What changed in this PR
Adds configurable storage relocation for issue #1213, including cold migration, cache maintenance, backup cleanup, localized Settings UI, and startup integration.
Changes:
- Adds verified file copying and Rust-owned persisted-path relocation.
- Adds storage IPC, Settings controls, localization, and release notes.
- Adds unit, integration, and Electron E2E coverage plus specifications.
| File | Description |
|---|---|
scripts/e2e/storage-settings.jsx |
Exercises storage Settings interactions. |
scripts/e2e-storage-settings.mjs |
Runs isolated Settings Electron E2E. |
scripts/e2e-storage-migration.mjs |
Tests end-to-end host migration. |
scripts/e2e-storage-bootstrap.mjs |
Tests cold Electron bootstrap. |
packages/shared/src/storage.ts |
Defines storage IPC data types. |
packages/shared/src/protocol.ts |
Adds storage IPC channels. |
packages/shared/src/index.ts |
Exports storage types. |
packages/shared/src/changelog-zh-TW.ts |
Adds Traditional Chinese release note. |
packages/shared/src/changelog-zh-CN.ts |
Adds Simplified Chinese release note. |
packages/shared/src/changelog-tr.ts |
Adds Turkish release note. |
packages/shared/src/changelog-pt-BR.ts |
Adds Portuguese release note. |
packages/shared/src/changelog-ko.ts |
Adds Korean release note. |
packages/shared/src/changelog-fr.ts |
Adds French release note. |
packages/shared/src/changelog-es.ts |
Adds Spanish release note. |
packages/shared/src/changelog-en.ts |
Adds English release note. |
packages/shared/src/changelog-de.ts |
Adds German release note. |
packages/i18n/test/catalogs.test.mjs |
Validates maintenance translations. |
packages/i18n/src/locales/zh-TW/index.ts |
Adds Traditional Chinese storage copy. |
packages/i18n/src/locales/zh-CN/index.ts |
Adds Simplified Chinese storage copy. |
packages/i18n/src/locales/tr/index.ts |
Adds Turkish storage copy. |
packages/i18n/src/locales/pt-BR/index.ts |
Adds Portuguese storage copy. |
packages/i18n/src/locales/ko/index.ts |
Adds Korean storage copy. |
packages/i18n/src/locales/fr/index.ts |
Adds French storage copy. |
packages/i18n/src/locales/es/index.ts |
Adds Spanish storage copy. |
packages/i18n/src/locales/en/index.ts |
Adds English storage copy. |
packages/i18n/src/locales/de/index.ts |
Adds German storage copy. |
package.json |
Registers storage E2E suite. |
docs/spec/06-delivery/04-e2e-test-plan.md |
Documents storage E2E coverage. |
docs/spec/04-ux/06-settings-ia.md |
Specifies storage Settings behavior. |
docs/spec/03-runtime/04-data-storage.md |
Specifies migration and cleanup semantics. |
docs/adr/custom-storage-location.md |
Records the storage architecture decision. |
docs/adr/0094-single-instance-per-data-directory.md |
Amends locking architecture. |
crates/host-core/src/main.rs |
Adds offline relocation CLI dispatch. |
crates/host-core/src/data_relocation/tests.rs |
Tests persisted-path relocation. |
crates/host-core/src/data_relocation.rs |
Implements offline Rust relocation. |
apps/desktop/test/storage-maintenance.test.mjs |
Tests migration and cleanup safety. |
apps/desktop/test/single-instance.test.mjs |
Tests revised startup locking. |
apps/desktop/test/development-profile.test.mjs |
Updates profile bootstrap contracts. |
apps/desktop/src/styles/settings.css |
Styles storage controls and confirmation. |
apps/desktop/src/lib/settings-search.ts |
Adds storage search terms. |
apps/desktop/src/lib/api.ts |
Exposes storage renderer APIs. |
apps/desktop/src/features/settings/StorageSettingsSection.tsx |
Implements storage Settings UI. |
apps/desktop/src/features/settings/SettingsPage.tsx |
Mounts the storage section. |
apps/desktop/electron/main/storage/preferences.ts |
Persists storage bootstrap state. |
apps/desktop/electron/main/storage/ipc.ts |
Implements storage IPC handlers. |
apps/desktop/electron/main/storage/files.ts |
Implements copying and cleanup. |
apps/desktop/electron/main/storage/bootstrap.ts |
Runs cold maintenance at startup. |
apps/desktop/electron/main/ipc/register.ts |
Registers storage IPC. |
apps/desktop/electron/main/installation.ts |
Extracts installation identity and locking. |
apps/desktop/electron/main/index.ts |
Uses prepared storage and restart workflow. |
apps/desktop/electron/main/host-process.ts |
Exports host binary resolution. |
apps/desktop/electron/main/entry.ts |
Adds pre-runtime storage bootstrap entry. |
apps/desktop/electron.vite.config.ts |
Switches Electron’s main entrypoint. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+23
to
+29
| export async function validateTarget(path: string, source: StorageRoots, anchor: string, retryId?: string): Promise<string> { | ||
| if (!isAbsolute(path) || path.includes("\0")) throw new Error("Select an absolute directory."); | ||
| const target = await realpath(path); | ||
| if (!(await lstat(path)).isDirectory() || (await lstat(path)).isSymbolicLink()) throw new Error("Select a real directory, not a symbolic link."); | ||
| for (const sourcePath of [source.data, source.browser, anchor]) { | ||
| const canonical = await canonicalPath(sourcePath); | ||
| if (contains(canonical, target) || contains(target, canonical)) throw new Error("The destination must be separate from the current storage directories."); |
Comment on lines
+81
to
+85
| writeStoragePreferences(file, { version: 1, roots: next, | ||
| backups: [...preferences.backups, { | ||
| data: existsSync(preferences.roots.data) ? await realpath(preferences.roots.data) : preferences.roots.data, | ||
| browser: await realpath(preferences.roots.browser), | ||
| }] }); |
Comment on lines
+38
to
+39
| await dialog.showMessageBox({ type: "error", title: copy.failedTitle, | ||
| message: copy.unavailableHint, detail: error instanceof Error ? error.message : String(error) }); |
| <Button disabled={disabled} onClick={() => void confirm()}>{busy ? t("settings.storage.restarting") : t(confirmation === "migrate" ? "settings.storage.migrateRestart" : confirmation === "cache" ? "settings.storage.cacheRestart" : "settings.storage.backupRestart")}</Button> | ||
| </div> | ||
| </div> : null} | ||
| {error ? <p className="settings-storage-message error" role="alert">{t("settings.storage.operationError", { error })}</p> : null} |
| version: "0.15.9", | ||
| date: "2026-09-27", | ||
| highlights: [ | ||
| "설정에서 데이터 저장 위치를 지정하고 이전 진행률을 확인하며 재생성 가능한 캐시를 안전하게 정리하세요.", |
Place the storage migration highlight in the current release so every locale has the same versioned highlight count.
Assert application identity and early error handlers where the startup split installs them, while preserving the logger wiring check in the composition root.
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #1213
Adds a configurable storage location in Settings and a cold-start migration that copies and verifies desktop and Chromium data before switching the active roots. The migration window reports stages and file/byte progress; the original data remains available as a backup until the user explicitly removes it. Settings also provides safe cache cleanup for rebuildable cache paths.
Validation on the task candidate: