Skip to content

feat(backup): add config export/import via .lockime files#18

Merged
BlackHole1 merged 2 commits into
mainfrom
add-import-export
Jun 15, 2026
Merged

feat(backup): add config export/import via .lockime files#18
BlackHole1 merged 2 commits into
mainfrom
add-import-export

Conversation

@BlackHole1

Copy link
Copy Markdown
Member

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

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>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf04b145-0319-4126-8286-fcb6cd53109d

📥 Commits

Reviewing files that changed from the base of the PR and between c0442bf and 3dbd692.

📒 Files selected for processing (4)
  • Sources/LockIME/Localizable.xcstrings
  • Sources/LockIME/UI/Settings/BackupSettingsPane.swift
  • Sources/LockIMEKit/Backup/ConfigBackup.swift
  • Tests/LockIMEKitTests/ConfigBackupTests.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • Sources/LockIMEKit/Backup/ConfigBackup.swift
  • Tests/LockIMEKitTests/ConfigBackupTests.swift
  • Sources/LockIME/UI/Settings/BackupSettingsPane.swift

Summary by CodeRabbit

  • New Features
    • Added a new Backup settings tab with export and import for configuration files in the .lockime format.
    • Added a Review Import screen that supports merge vs replace, shows conflicts, and guides handling of missing or inactive sources before applying.
    • Improved URL rules visibility with a hint when rules exist but enhanced mode is off.
  • Tests
    • Added/expanded test coverage for backup encoding/decoding and import planning, including conflict and summary/outcome logic.

Walkthrough

A complete configuration backup system is added to LockIME. LockIMEKit gains two new files: ConfigBackup.swift defining a versioned .lockime JSON envelope (BackupURLRule, BackupPayload, ConfigBackup) with make(), encoded(), and read() (including format/version gating and BackupReadError); and ImportPlan.swift defining the in-memory planning model (ImportMode, ImportItem, ImportSummary, ImportOutcome, ImportPlan) with resolvedConfiguration(), summary(), and outcome(). AppState gains makeBackup(), loadImportPlan(from:), and applyImport(_:). The Settings UI adds a backup tab case wired to a new BackupSettingsPane (export via NSSavePanel, import via NSOpenPanel leading to an ImportReviewSheet). URLRulesSettingsPane is updated to surface inactive-rule hints when enhanced mode is off. Localization strings for all new UI are added across eight languages. Full test coverage is provided in ConfigBackupTests and ImportPlanTests.

Sequence Diagram

sequenceDiagram
  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
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the required format with type and scope, and accurately describes the primary change: adding backup/export and import functionality via .lockime files.
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the backup/migration functionality, file format, conflict resolution, and deliberate exclusions of runtime state.
Linked Issues check ✅ Passed The PR fully addresses the core requirements from issue #17: export settings to file, import settings from file, and per-row conflict/missing-source handling for multi-device configuration transfer.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue's export/import feature; no out-of-scope modifications detected. Enhanced mode toggle hints and URL rules visibility are supportive context for imports.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch add-import-export

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between fd62310 and c0442bf.

📒 Files selected for processing (10)
  • Sources/LockIME/AppState.swift
  • Sources/LockIME/Localizable.xcstrings
  • Sources/LockIME/UI/Settings/BackupSettingsPane.swift
  • Sources/LockIME/UI/Settings/ImportReviewSheet.swift
  • Sources/LockIME/UI/Settings/URLRulesSettingsPane.swift
  • Sources/LockIME/UI/SettingsRootView.swift
  • Sources/LockIMEKit/Backup/ConfigBackup.swift
  • Sources/LockIMEKit/Backup/ImportPlan.swift
  • Tests/LockIMEKitTests/ConfigBackupTests.swift
  • Tests/LockIMEKitTests/ImportPlanTests.swift

Comment thread Sources/LockIME/UI/Settings/BackupSettingsPane.swift
Comment thread Sources/LockIME/UI/Settings/BackupSettingsPane.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>
@BlackHole1 BlackHole1 merged commit db5b696 into main Jun 15, 2026
3 checks passed
@BlackHole1 BlackHole1 deleted the add-import-export branch June 15, 2026 04:13
BlackHole1 added a commit that referenced this pull request Jun 15, 2026
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>
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.

Feature Request: Export/Import Configuration and iCloud Sync

1 participant