How TUUI Integrates Pinia State Persistence with LocalStorage for Configuration Management

TUUI uses the pinia-plugin-state-persistence plugin to automatically synchronize selected Pinia stores with the browser's localStorage, ensuring configuration data survives page refreshes and application restarts without manual serialization logic.

The ai-ql/tuui repository implements a declarative persistence layer for its Electron-based Vue.js application by integrating Pinia with a dedicated state persistence plugin. This approach allows individual stores to opt-in to localStorage synchronization, providing seamless configuration management for UI preferences, chat history, and runtime settings across the entire application lifecycle.

Bootstrapping Pinia with the Persistence Plugin

The integration begins during application initialization in src/renderer/main.ts. Here, the Pinia instance is created and enhanced with the persistence plugin before being mounted to the Vue application.

// src/renderer/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createStatePersistence } from 'pinia-plugin-state-persistence'

const app = createApp(App)
const pinia = createPinia()
pinia.use(createStatePersistence())   // activates localStorage synchronization
app.use(pinia).mount('#app')

The createStatePersistence() function wraps each store's state, automatically writing JSON-serialized data to window.localStorage on every mutation and restoring values when the application reloads.

Store-Level Persistence Configuration

Individual stores opt-in to persistence by declaring a persist option in their definition passed to defineStore. TUUI supports both full-state and selective property persistence, allowing granular control over which configuration data persists across sessions.

Full State Persistence

The stdio store demonstrates complete state persistence for command-line configuration data. In src/renderer/store/stdio.ts, the persist: true option ensures the entire store state is serialized to localStorage.

// src/renderer/store/stdio.ts
export const useStdioStore = defineStore('stdioStore', () => {
  const configValues = ref<Record<string, CustomStdioServerParameters>>({})
  // ... store logic and actions
  
  return { configValues }
}, {
  persist: true               // entire state saved to localStorage
})

Selective Property Persistence

The locale store in src/renderer/store/locale.ts uses selective persistence to store only the user's language preference while keeping other properties ephemeral.

// src/renderer/store/locale.ts
export const useLocaleStore = defineStore('localeStore', {
  state: () => ({
    selected: undefined,
    list: [],
    fallback: {}
  }),
  persist: {
    include: ['selected']      // only persists the chosen language
  }
})

This pattern appears across multiple configuration stores in TUUI, including chatbotStore, agentStore, historyStore, and dxtStore, each specifying exactly which runtime settings must survive application restarts.

Underlying Storage Mechanism

The plugin writes data to window.localStorage using a deterministic key format that includes versioning for safe migrations:


persist:<storeId>-<process.env.NODE_ENV>-<schemaVersion>

The schemaVersion constant is defined in src/main/utils/Constants.ts and appended to each storage key. This versioning strategy prevents conflicts when the persisted data structure changes between application updates.

When a store initializes, the plugin automatically checks for existing data at its namespaced key (e.g., persist:localeStore-development-1), parses the JSON, and hydrates the store state before the first component access. Subsequent mutations trigger immediate re-serialization to localStorage.

Accessing Persisted Configuration

Application code interacts with persisted stores exactly like standard Pinia stores. The restoration happens transparently during store initialization:

const localeStore = useLocaleStore()
console.log(localeStore.selected)   // returns value from localStorage if present

This declarative approach eliminates the need for manual localStorage API calls within components or store actions, centralizing persistence logic within the Pinia plugin ecosystem.

Summary

  • TUUI integrates pinia-plugin-state-persistence during bootstrap in src/renderer/main.ts to enable automatic localStorage synchronization across all Pinia stores.
  • Stores opt-in via the persist option, supporting both full-state persistence (persist: true) and selective property filtering (persist: { include: [...] }).
  • Configuration stores like localeStore, stdioStore, chatbotStore, agentStore, historyStore, and dxtStore leverage this system to preserve user preferences and runtime settings across Electron app restarts.
  • Versioned storage keys using schemaVersion from src/main/utils/Constants.ts ensure safe data migration when configuration schemas evolve.

Frequently Asked Questions

How does TUUI persist Pinia state to localStorage?

TUUI registers the pinia-plugin-state-persistence plugin during application initialization in src/renderer/main.ts. This plugin wraps each Pinia store and automatically serializes state mutations to window.localStorage as JSON strings, then restores them when the application reloads.

Can I persist only specific properties of a Pinia store in TUUI?

Yes. Instead of persist: true, pass an options object with an include array listing the specific state properties to persist. The localeStore in src/renderer/store/locale.ts demonstrates this by persisting only the selected language property while keeping other state ephemeral.

Where is the persisted data stored in localStorage?

Data is stored under keys following the format persist:<storeId>-<process.env.NODE_ENV>-<schemaVersion>. For example, the locale store might use persist:localeStore-development-1, where the schema version is imported from src/main/utils/Constants.ts.

How does TUUI handle schema changes in persisted configuration?

TUUI uses a schemaVersion constant defined in src/main/utils/Constants.ts that is appended to every localStorage key. When the data structure changes, incrementing this version forces the application to use a fresh storage key, preventing deserialization errors from outdated cached data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →