Skip to content

feat: store all user settings in local files instead of browser localStorage#7821

Merged
louis-jan merged 9 commits intojanhq:mainfrom
bittoby:feat/settings-local-files
Apr 4, 2026
Merged

feat: store all user settings in local files instead of browser localStorage#7821
louis-jan merged 9 commits intojanhq:mainfrom
bittoby:feat/settings-local-files

Conversation

@bittoby
Copy link
Copy Markdown
Contributor

@bittoby bittoby commented Mar 26, 2026

Describe Your Changes

Replace browser localStorage with file-based storage for all user settings. Settings now persist to a local settings.json file via @tauri-apps/plugin-store (already a project dependency).

What changed:

  • New fileStorage adapter (web-app/src/lib/fileStorage.ts) that implements zustand's StateStorage interface backed by @tauri-apps/plugin-store
  • All 17 zustand persisted stores switched from createJSONStorage(() => localStorage) to createJSONStorage(() => fileStorage)
  • i18n/setup.ts updated to read language from the zustand store instead of raw localStorage
  • Automatic one-time migration: existing localStorage data is copied to the file store on first access, then cleaned up
  • Falls back to localStorage in non-Tauri environments (browser dev mode)

Settings file location: ~/.local/share/jan.ai.app/settings.json (Linux)

Why this matters:

  • Settings are now human-readable and editable with any text editor
  • Portable across machines (copy the file)
  • Not tied to browser/webview state - clearing browser data won't wipe user preferences

Fixes Issues

Self Checklist

  • Added relevant comments, esp in complex areas
  • Updated docs (for bug fixes / features)
  • Created issues for follow-up changes or refactoring needed

…to local JSON file via @tauri-apps/plugin-store
@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Mar 27, 2026

@louis-jan @Vanalite Please review this PR. welcome to any feedbacks

@louis-jan
Copy link
Copy Markdown
Contributor

@bittoby We need to do the migration so that users who update the app will not lose all of their data.

@louis-jan louis-jan added the needs: comms Major issue - we should inform users label Mar 30, 2026
@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Mar 30, 2026

@louis-jan Thanks for your feedback. I've added migration function. It is safe now. No data loss. Please review again.

@louis-jan
Copy link
Copy Markdown
Contributor

The migration works great.

  1. I still see some of the data still left on local storage, especially extensions data.
  2. I could not locate the store.json file in my computer.
Screenshot 2026-03-31 at 10 27 32

@louis-jan louis-jan requested a review from qnixsynapse March 31, 2026 03:37
@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Mar 31, 2026

The migration works great.

  1. I still see some of the data still left on local storage, especially extensions data.
  2. I could not locate the store.json file in my computer.

Thanks for reviewing again. Actually these are not bug.

  1. The leftover localStorage keys are written after migration by extension code (registerSettings()) and migration flags. It is not a migration bug
  2. The file is settings.json not store.json at ~/jan.ai.app/settings.json

@tokamak-pm
Copy link
Copy Markdown

tokamak-pm Bot commented Mar 31, 2026

Code Review

Summary

Introduces a StateStorage-compatible adapter backed by @tauri-apps/plugin-store to persist all user settings in local files instead of browser localStorage. Includes lazy per-key migration from localStorage. Falls back to localStorage in non-Tauri environments. 281-line test suite included.

Key Findings

  • Startup i18n flash — During cold start, the store hasn't hydrated yet (async), so getStoredLanguage() returns 'en'. Non-English users will see a brief flash of English text.
  • Single-file corruption risk — All settings in one settings.json file means corruption could wipe all preferences at once. No atomic writes or backup strategy documented.
  • needs: comms label — Must be resolved before merge (user communication about storage location change).
  • PR ordering conflict with feat: add options to preserve user data during factory reset #7858 — If this merges first, feat: add options to preserve user data during factory reset #7858's localStorage snapshot/restore will silently fail for migrated settings.
  • Migration logic is careful and correct (only migrates if file store doesn't have the key; doesn't delete from localStorage if write fails).
  • Test suite is high quality, covering migration, concurrency, and failure cases.

Recommendation: fix needed

Resolve needs: comms label, address startup i18n flash, document corruption mitigation, and coordinate with PR #7858.

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Mar 31, 2026

@louis-jan @qnixsynapse what else should I update more? please let me know. thanks

@louis-jan
Copy link
Copy Markdown
Contributor

I see, this makes very sense

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Apr 1, 2026

@louis-jan would you please merge this PR?
thank you

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Apr 2, 2026

@Vanalite would you please review this PR?

@tokamak-pm
Copy link
Copy Markdown

tokamak-pm Bot commented Apr 3, 2026

Follow-up Review (new discussion since last review)

Discussion Summary

  • louis-jan tested migration and confirmed it "works great"
  • Raised two concerns: (1) leftover localStorage keys, (2) could not find store.json
  • bittoby explained: (1) leftover keys are written post-migration by extension code — by design; (2) file is settings.json, not store.json
  • louis-jan accepted both explanations
  • bittoby has pinged for merge twice (Apr 1, Apr 2) — no response yet

Previous Concerns Status

Concern Status
Migration data-loss risk Resolved — louis-jan confirmed working
needs: comms label Still present — team process gate
Startup i18n flash Unaddressed — async hydration may show English briefly for non-English users
Single-file corruption risk Unaddressed

Assessment

Code quality and migration are solid. Two items remain: (1) the needs: comms label requires team action (changelog/release note), and (2) the i18n startup flash should be acknowledged or addressed.

Final recommendation: fix needed

@tokamak-pm
Copy link
Copy Markdown

tokamak-pm Bot commented Apr 4, 2026

Follow-up Review (new commits since last review)

New Commits

  • fa19cff0 — Merge branch 'main'
  • 5f56aa14 — fix conflicts (integrates toast placement feature from main)

Assessment

Both commits are merge/conflict-resolution only. The conflict resolution integrates the recently-merged configurable toast placement feature (#7886) — adding new files (NotificationPositionSwitcher.tsx, toastPlacement.ts, etc.) and updating useInterfaceSettings.ts and vitest.config.ts. No changes to the core settings-migration logic introduced by this PR.

Previous Findings Status

Concern Status
Migration data-loss risk Resolved — confirmed working by louis-jan
needs: comms label Still present — team process gate
Startup i18n flash Unaddressed — async hydration may flash English
Single-file corruption risk Unaddressed
Coordination with #7858 Still relevant

Recommendation

No new functional changes — the merge commits just keep the branch up to date. Previous concerns about needs: comms label and i18n startup flash still apply. The PR is technically sound and ready once the team process gate is cleared.

Final recommendation: fix needed (pending needs: comms resolution)

@louis-jan
Copy link
Copy Markdown
Contributor

@bittoby it should be under the application data folder (see attached)
Screenshot 2026-04-04 at 19 17 08

@louis-jan
Copy link
Copy Markdown
Contributor

@bittoby can you help correct the setting file to the correct location. Then it's good to go

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Apr 4, 2026

@louis-jan Thanks. I updated. please check again

Copy link
Copy Markdown
Contributor

@louis-jan louis-jan left a comment

Choose a reason for hiding this comment

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

LGTM

@louis-jan louis-jan merged commit f672680 into janhq:main Apr 4, 2026
17 checks passed
@github-project-automation github-project-automation Bot moved this to QA in Jan Apr 4, 2026
@markmehere
Copy link
Copy Markdown
Contributor

I'm having some difficulties with this PR. Specifically the "last-used-assistant" is disappearing when I type "make dev" as is the "llamacpp_env" (the setting for the llama.cpp engine). It's like all settings are constantly being reverted. I am also having trouble quitting Jan - it just hangs forever. Not really sure what to make of it all. Reverting the PR both the quitting and saved settings work again.

Not quite sure what's going on. 😕 I haven't run any updates just "make dev". I'm on macOS 26.3.1. Sorry to be the bearer of bad news.

@markmehere
Copy link
Copy Markdown
Contributor

Deleting the settings.json has fixed the hanging on quitting but the "last-used-assistant" still isn't working. It keeps going back to Jan. 😞

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Apr 7, 2026

@markmehere If possible, could you share recording video or screenshot here? Then I will try to fix. Thanks for reporting error

for (const key of keys) {
// Skip keys already being migrated by a concurrent getItem call
if (migrationPromises.has(key)) continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you add if (key === localStorageKey.lastUsedAssistant) continue here (and the relevant import), it seems to restore the "last-used-assistant" to working order. Alternatively I'll write a comment on where it is set in case you want to fix it by using fileStorage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bittoby see this comment and my comment on the conversation page - hopefully those help 🙂

@markmehere
Copy link
Copy Markdown
Contributor

I think there are still a few uses of localStorage that are causing issues.

Fortunately the assistant ones are all in this file:

https://github.com/janhq/jan/blob/main/web-app/src/hooks/useAssistant.ts

On line 24, line 68, line 89, line 100 and line 103.

One big gotcha is that some of these are called really early on.

If you search for "localStorage." you may find a few more in the code base.

Hope this helps!

@markmehere
Copy link
Copy Markdown
Contributor

image

This setting is also not sticking for me. That area is a bit of a terror - so I can't provide the same guidance.

@bittoby
Copy link
Copy Markdown
Contributor Author

bittoby commented Apr 7, 2026

@markmehere Thanks for providing guidance. I created new pr for missing migration parts.
#7919

Pls test with this and let me know. thanks

qnixsynapse added a commit that referenced this pull request Apr 24, 2026
After #7821, migrateAllLocalStorageKeys() moves every localStorage key
(including each extension's settings bag) into the Tauri plugin-store
file on startup — but BaseExtension was still reading and writing
localStorage directly. Consequences:

- On upgrade, extension settings disappear: the migration evicts the
  key from localStorage, and the next registerSettings() sees no
  oldSettings and writes defaults back.
- Within a session the extension writes to localStorage, but on the
  next boot migrateAllLocalStorageKeys finds the key already in the
  file store and deletes the localStorage copy without updating the
  file — silently discarding whatever the user changed (e.g. the
  llamacpp auto_update_engine toggle).

Introduce an ExtensionStorage abstraction in @janhq/core with a
localStorage default, and inject the app's fileStorage adapter from
main.tsx so BaseExtension and the Zustand stores share one source of
truth. Also narrow the provider settings UI to write only the key the
user actually changed, so a stale in-memory snapshot can't overwrite
unrelated keys via BaseExtension.updateSettings' full-array merge.
qnixsynapse added a commit that referenced this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs: comms Major issue - we should inform users

Projects

Status: QA

Development

Successfully merging this pull request may close these issues.

idea: All settings should be controlled via local files not browser localStorage

3 participants