Skip to content

Add per-vault AI history storage#301

Open
jsgrrchg wants to merge 19 commits into
mainfrom
ai-history-local-per-vault
Open

Add per-vault AI history storage#301
jsgrrchg wants to merge 19 commits into
mainfrom
ai-history-local-per-vault

Conversation

@jsgrrchg

@jsgrrchg jsgrrchg commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds per-vault control over where AI chat history is stored, and moves the default private storage path to local app data instead of writing chat history and pasted AI attachments into the vault by default.

Motivation

AI chat history should not be written into the vault by default because vaults may be synced, shared, backed up, or committed. This keeps private AI history device-local unless the user explicitly opts into vault storage.

The new model has two explicit storage scopes:

  • Device storage: histories live under NeverWrite app data, keyed by the normalized vault path. Pasted AI attachments are also stored under app data in a vault-specific attachment namespace.
  • Vault storage: histories continue to live inside the vault state directory, and AI-owned pasted images can be migrated into the vault's assets/chat folder when the user opts into vault-backed chat history.

User-facing behavior

  • Adds a per-vault setting, Store AI chats inside this vault, in AI settings.
  • The setting defaults to device-local storage unless a vault already has existing vault-backed AI history.
  • The storage-scope preference is stored locally per normalized vault path; it is not written into the vault.
  • Switching the setting changes where new chat history is saved.
  • If the previous storage location has existing histories, the UI prompts the user to move them instead of silently leaving or deleting them.
  • Chat history views, content search, forking, deletion, retention pruning, and reset flows now pass the selected storage scope through the AI history API.
  • Device-local image attachments can still render in chat messages through the neverwrite-file://.../ai-attachment/... preview route, without exposing arbitrary local files.

Backend/storage changes

  • Adds a shared native app-data resolver so both native AI runtime state and AI history storage can resolve the same app-data root.
  • Extends the persistence layer with explicit-storage-root variants for save, load, paging, search, fork, delete, delete-all, and prune operations.
  • Adds native backend commands for:
    • checking whether vault-scoped histories exist,
    • migrating histories between device and vault,
    • saving/deleting pasted AI attachments in the app-data namespace.
  • Routes all AI history commands through an AiSessionsStorage resolver so the selected scope owns the storage root decision in one place.

Migration and cleanup details

Migration copies readable histories from the source scope into the destination scope and skips sessions already present in the destination. AI-owned pasted attachments are handled separately from arbitrary user files:

  • device -> vault: copies AI-owned pasted-image-* attachments from app data into the vault's assets/chat folder and rewrites persisted filePath references.
  • vault -> device: copies AI-owned pasted-image-* attachments from the vault into the app-data attachment namespace and rewrites persisted filePath references.
  • Non-AI-owned or missing attachments are left as-is and reported as skipped rather than being deleted or guessed.
  • Source histories are only deleted after a successful copy when requested by the caller.
  • Device attachment cleanup now runs when deleting individual histories, deleting all histories, and retention-pruning histories. It keeps attachments that are still referenced by remaining histories.
  • The Electron fallback backend now follows the same attachment rewrite behavior during migration instead of copying history directories blindly.

Safety notes

  • Device storage is keyed by a hash of the normalized vault path, so histories for different vaults do not share a directory.
  • Attachment preview only serves files inside the expected app-data attachment namespace for the active vault.
  • Attachment deletion rejects paths outside the vault-specific app-data attachment root.
  • Vault retention pruning does not delete arbitrary files from assets/chat; cleanup of device-local attachments is limited to the app-data namespace.

Review focus

  • Native backend storage resolver and migration behavior.
  • Attachment path rewrite and preview safety.
  • Chat store scope propagation through load, save, search, fork, delete, and prune.
  • Settings and legacy-history prompt UX.

Manual QA focus

  • Fresh vault: AI chats default to device storage.
  • Existing vault-backed history: app detects it and shows the migration/keep prompt.
  • Toggle from device -> vault and vault -> device with existing histories and pasted images.
  • Delete, reset, retention prune, search, fork, and restore still operate on the selected scope.

Tests / validation

  • cargo test -p neverwrite-ai persistence --lib
  • cargo test -p neverwrite-native-backend ai_
  • pnpm --dir apps/desktop exec tsc -p tsconfig.json --noEmit
  • pnpm --dir apps/desktop exec vitest run src/features/settings/SettingsPanel.test.tsx src/features/ai/components/AIChatMessageItem.test.tsx

@jsgrrchg

jsgrrchg commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Non-blocking: this only affects the Electron fallback path. Packaged builds should route this command through the Rust sidecar, which validates actual persisted history content. Still, for parity/hardening, the fallback should eventually mirror that behavior instead of treating any non-empty .neverwrite/sessions directory as valid AI history.

@jsgrrchg jsgrrchg marked this pull request as ready for review July 9, 2026 00:42
@jsgrrchg

jsgrrchg commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

@spamsch This is ready for a review, I thought it was going to be simpler, but i was wrong, the code is not very clean, i made a lot of mistakes during implementation. The lifecycle is working fine, but I'm considering rewriting this PR. I will give it another review tomorrow with fresh eyes.

I don't want to rush this until we are confident enough to merge, we do not want to break the chat history for existing users or break the migration.

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.

1 participant