CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix: stop Rust overwriting camelCase settings — root cause of app being unusable #4736

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/inspiring-curie-xd4n0bmainopened Jun 21, 20260/4 tasks
ccantynzcommented Jun 21, 2026

Originally written by @ccantynz-alt on GitHub.
Imported from https://github.com/ccantynz-alt/voxlen/pull/65


Summary

This PR fixes the root cause of settings being lost on every restart, which made the app non-functional (every launch requires re-entering API keys, language, etc. before anything works).

Root cause: camelCase vs snake_case mismatch in persistence layer

The frontend (TypeScript/Zustand) saves settings as camelCase JSON to the Tauri store ({ "sttEngine": "deepgram", "sttApiKey": "..." }). The Rust backend uses snake_case structs (stt_engine, stt_api_key). Every time Rust wrote to the store it silently corrupted the file with snake_case — which the frontend then couldn't read, causing an all-defaults reset.

There were two places Rust was writing snake_case into the frontend-owned store:

  1. update_settings called persist_settings (fixed in a prior commit)
  2. load_settings_from_disk tried to parse the camelCase JSON as snake_case, failed, then overwrote the store with snake_case defaults — destroying all saved settings on every launch

Changes

src-tauri/src/commands/settings.rsload_settings_from_disk

  • No longer reads or writes the Tauri store JSON (that belongs to the frontend)
  • Just opens the store to initialise the app data directory
  • Hydrates API keys from the OS keychain so STT/grammar engines work before the frontend calls update_settings

src/App.tsx

  • Removed the useSettingsStore.subscribe(...) effect that was saving the entire Zustand state — including sttApiKey, grammarApiKey, voxlenApiKey — to plain JSON on every settings change. This was a privacy violation for a product used by lawyers. schedulePersist() in stores/settings.ts already handles all persistence correctly (keychain for API keys, Tauri store for everything else).
  • Removed redundant settings loading from checkFirstLaunch (now reads legalAcceptedVersion directly from the raw store JSON to avoid racing with usePersistedSettings)

src/hooks/useGlobalShortcuts.ts

  • When start_dictation fails in Tauri context (no API key configured, mic permission denied, engine error), the UI was left permanently stuck showing "Listening" with no feedback. Now resets status to idle and shows an error toast.

Test plan

  • Launch app fresh — settings (language, grammar config, theme, etc.) should persist across restarts without re-entering
  • Enter a Deepgram API key in settings — it should survive restart and dictation should work on first press of the shortcut
  • Press dictation shortcut with no API key configured — should show an error toast, not hang in "Listening"
  • Complete onboarding — should not write API keys to plain JSON (settings.json should not contain sttApiKey etc.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ggw2m7nwT2HDNaQJqSX6Sp


Generated by Claude Code

Cross-repo impact

See what breaks downstream if this PR merges.

Analyze →
⮌ Merged

This pull request was merged into main.

c comment · e edit title · m merge · a approve · r request changes · ? shortcuts