Skip to content

Persist desktop settings to settings.json across releases#1191

Open
jamesx0416 wants to merge 1 commit intopingdotgg:mainfrom
jamesx0416:t3code/settings-persistence-across-versions
Open

Persist desktop settings to settings.json across releases#1191
jamesx0416 wants to merge 1 commit intopingdotgg:mainfrom
jamesx0416:t3code/settings-persistence-across-versions

Conversation

@jamesx0416
Copy link
Contributor

@jamesx0416 jamesx0416 commented Mar 18, 2026

What Changed

  • added shared desktop settings schemas and IPC types for a persisted desktop settings payload
  • added Electron read/write support for settings.json in the desktop state directory
  • exposed a synchronous initialSettings snapshot from preload so the renderer can read desktop settings before routes and hooks initialize
  • added desktop settings bootstrap/migration logic in the web app:
    • read settings.json first on desktop
    • if it does not exist, fall back to legacy localStorage
    • if legacy values exist, write a new settings.json
  • updated desktop theme and app settings persistence so UI changes write back to settings.json
  • kept browser behavior unchanged

Why

Packaged desktop builds were still effectively depending on Chromium localStorage in the Electron profile, which made preferences unreliable across release builds and caused startup-order issues where settings were only reflected after more of the app mounted.

This change gives desktop a stable, versioned settings.json source of truth and makes startup deterministic:

  • desktop reads settings.json first
  • legacy localStorage is only used as a one-time migration source when the file is missing
  • subsequent desktop settings changes update the file directly

That fixes the desktop persistence bug without changing browser-only behavior.

UI Changes

None.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Persist desktop settings to settings.json across app releases

  • Adds a new settings.json file to store desktop settings (theme and app settings) across releases, using atomic writes with a temp file and rename.
  • On startup in Electron, main.tsx awaits hydrateDesktopSettings before rendering, loading persisted settings from disk and mirroring them to localStorage with storage change events.
  • A new desktopSettings.ts module handles hydration, migration of legacy localStorage values to disk, and de-duplicated persistence via window.desktopBridge.setSettings.
  • Settings schemas and types (DesktopSettings, DesktopSettingsInput, AppSettings) are defined in a new packages/contracts/src/settings.ts module using Effect Schema, and DesktopBridge is extended with initialSettings and setSettings.
  • Behavioral Change: In Electron, app settings and theme are now bootstrapped from disk rather than localStorage, and writes are validated and persisted to disk on every change.
📊 Macroscope summarized 4eae069. 10 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5b3a7220-b639-4539-a488-297bd46b48df

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions bot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 18, 2026
appSettings: input.appSettings,
};
const settingsPath = getSettingsPath();
const tempPath = `${settingsPath}.tmp`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low src/main.ts:151

Using a fixed temp file name ${settingsPath}.tmp creates a race condition when concurrent calls to writeSettingsFile occur. If two IPC SET_SETTINGS_CHANNEL calls overlap, the second write can overwrite the temp file before the first rename, causing incorrect data to be persisted. The second call's rename may also fail with ENOENT if the first call already moved the file. Consider using a unique temp file name per call (e.g., with crypto.randomUUID() or Date.now()) to ensure atomic writes.

-  const tempPath = `${settingsPath}.tmp`;
+  const tempPath = `${settingsPath}.tmp-${Date.now()}-${Crypto.randomBytes(4).toString('hex')}`;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/desktop/src/main.ts around line 151:

Using a fixed temp file name `${settingsPath}.tmp` creates a race condition when concurrent calls to `writeSettingsFile` occur. If two IPC `SET_SETTINGS_CHANNEL` calls overlap, the second write can overwrite the temp file before the first rename, causing incorrect data to be persisted. The second call's rename may also fail with ENOENT if the first call already moved the file. Consider using a unique temp file name per call (e.g., with `crypto.randomUUID()` or `Date.now()`) to ensure atomic writes.

Evidence trail:
apps/desktop/src/main.ts lines 141-156 at REVIEWED_COMMIT:
- Line 151: `const tempPath = `${settingsPath}.tmp`;` confirms fixed temp file name
- Line 153: `await FS.promises.writeFile(tempPath, ...)` writes to temp
- Line 154: `await FS.promises.rename(tempPath, settingsPath);` renames temp to final
- No locking mechanism present in the async function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant