feat(backup): add config export/import via .lockime files#18
Conversation
Adds file-level backup and migration: export the portable configuration (global default, app rules, URL rules) to a versioned .lockime JSON, and import it on another Mac through a single Review Import screen — Merge/Replace, per-row conflict resolution, and keep/remove for input sources missing on the target — committed only on Apply, with zero side effects on cancel. Per-device runtime state (master lock, enhanced mode, language, login item) is deliberately never exported or imported. The envelope carries a format id, schema version, and minReader so newer files are gated cleanly. Pure diff/merge/version logic lives in LockIMEKit with unit tests; all new UI strings are localized for every supported language. Signed-off-by: Kevin Cui <bh@bugs.cc>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
Summary by CodeRabbit
WalkthroughA complete configuration backup system is added to LockIME. Sequence DiagramsequenceDiagram
actor User
participant BackupSettingsPane
participant AppState
participant ConfigBackup
participant ImportPlan
participant ImportReviewSheet
rect rgba(100, 149, 237, 0.5)
Note over User,ConfigBackup: Export flow
User->>BackupSettingsPane: Click "Export Configuration…"
BackupSettingsPane->>AppState: makeBackup()
AppState->>ConfigBackup: make(from: config, appVersion:, sourceNames:)
ConfigBackup-->>AppState: ConfigBackup
AppState-->>BackupSettingsPane: ConfigBackup
BackupSettingsPane->>ConfigBackup: encoded()
ConfigBackup-->>BackupSettingsPane: JSON Data
BackupSettingsPane->>BackupSettingsPane: write atomically to chosen URL
end
rect rgba(144, 238, 144, 0.5)
Note over User,ImportReviewSheet: Import flow
User->>BackupSettingsPane: Click "Import Configuration…"
BackupSettingsPane->>AppState: loadImportPlan(from: url)
AppState->>ConfigBackup: read(Data)
ConfigBackup-->>AppState: Result<ConfigBackup, BackupReadError>
AppState->>ImportPlan: init(config:, payload:, installedSources:)
AppState-->>BackupSettingsPane: Result<ImportPlan, BackupReadError>
BackupSettingsPane->>ImportReviewSheet: present(ImportReviewModel)
User->>ImportReviewSheet: Select mode/resolutions, click Apply
ImportReviewSheet->>ImportPlan: resolvedConfiguration()
ImportPlan-->>ImportReviewSheet: LockConfiguration
ImportReviewSheet->>AppState: applyImport(plan)
AppState->>AppState: commit(reason: .configChanged)
AppState-->>ImportReviewSheet: ImportOutcome
ImportReviewSheet-->>BackupSettingsPane: receipt (imported/inactive counts)
end
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/LockIME/UI/Settings/BackupSettingsPane.swift`:
- Around line 89-92: The Self.log.error call in the backup export error handling
is logging the raw error with .public privacy, which can expose user file paths
in diagnostics. Change the privacy parameter from .public to .private when
logging the error in the String(describing: error) expression to prevent
sensitive local path information from being included in shared diagnostics.
- Around line 126-127: The error message for the `.unreadable` case in
BackupSettingsPane.swift is too specific, stating the file "isn't a LockIME
backup" when `.unreadable` actually covers broader I/O and permission read
failures. Replace the current message with a broader, more semantic message that
accurately reflects that the file could not be read due to various reasons (not
just an invalid backup format). Use a catalog key for the message as per coding
guidelines, and ensure the original error is logged separately rather than being
hidden by an overly specific message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c15fa59f-7822-4f98-a48b-7784c673532a
📒 Files selected for processing (10)
Sources/LockIME/AppState.swiftSources/LockIME/Localizable.xcstringsSources/LockIME/UI/Settings/BackupSettingsPane.swiftSources/LockIME/UI/Settings/ImportReviewSheet.swiftSources/LockIME/UI/Settings/URLRulesSettingsPane.swiftSources/LockIME/UI/SettingsRootView.swiftSources/LockIMEKit/Backup/ConfigBackup.swiftSources/LockIMEKit/Backup/ImportPlan.swiftTests/LockIMEKitTests/ConfigBackupTests.swiftTests/LockIMEKitTests/ImportPlanTests.swift
Two CodeRabbit findings on the new backup flow. The export failure log interpolated the raw error with .public, which can embed the user's chosen file path into shared diagnostics; log it .private(mask: .hash) so identical failures still correlate without leaking the path. BackupReadError.unreadable conflated "couldn't read the bytes" with "read fine but isn't our format", so the inline note claimed a file "isn't a LockIME backup" even on an I/O/permission read failure. Split it into .unreadable (raised by the loader on a read failure) and .notABackup (raised by read() for non-JSON / wrong format), each with an accurate, localized message. Signed-off-by: Kevin Cui <bh@bugs.cc>
The README never mentioned the config import/export feature shipped in #18, nor that the app now downloads as a sub-3 MB .dmg. Add both to the Features list and drop the stale MarkdownUI mention from the Architecture section (removed in #21). All nine language variants are updated together to keep the translations in sync. Signed-off-by: Kevin Cui <bh@bugs.cc>
Adds file-level backup and migration: export the portable configuration (global default, app rules, URL rules) to a versioned .lockime JSON, and import it on another Mac through a single Review Import screen — Merge/Replace, per-row conflict resolution, and keep/remove for input sources missing on the target — committed only on Apply, with zero side effects on cancel.
Per-device runtime state (master lock, enhanced mode, language, login item) is deliberately never exported or imported. The envelope carries a format id, schema version, and minReader so newer files are gated cleanly. Pure diff/merge/version logic lives in LockIMEKit with unit tests; all new UI strings are localized for every supported language.
Closed: #17