Data Persistence

Two mechanisms: tauri-plugin-store for app-level config, and the file system (via read_file / write_file IPC) for project content. No localStorage except for a one-time migration of legacy keys on first launch.

The map

Data Mechanism Location
App settings tauri-plugin-store settings.json in app data dir
Project files File system IPC (read_file / write_file) projects/{projectId}/ under app data
Assets File system + sidecar JSON projects/{projectId}/assets/
Bonsai config tauri-plugin-store bonsai_config.json in app data dir
Workflows save_workflow / load_workflow Rust commands Per-project workflow directory
Model presets save_model_presets / load_model_presets App data dir
Pane sizes useAllotmentLayout hook Tauri Store

App-level config: Tauri Store

Two stores live in the app data dir:

  • settings.json — theme, accent, model picker, AI keys, layout
  • bonsai_config.json — Bonsai image server config

Frontend access goes through useSettings:

import { useSettings } from '@/hooks/useSettings';

const { settings, updateSetting } = useSettings();
await updateSetting('theme', 'dark');

useSettings is a thin re-export over tauri-plugin-store’s API. There is no fallback to localStorage — keys persist in the store file across launches.

Project files: filesystem IPC

Project content lives under projects/{projectId}/ in the app data dir. The frontend never reads or writes this directly; it goes through read_file / write_file:

// src/lib/ipc.ts
export async function readFile(path: string): Promise<string> {
  return invoke('read_file', { path });
}

export async function writeFile(path: string, contents: string): Promise<void> {
  return invoke('write_file', { path, contents });
}

React Query wraps these for caching and invalidation (useProjectFiles). Project mutations invalidate the affected query keys so the UI re-fetches.

Assets: filesystem + sidecar JSON

Generated images (Bonsai) are stored under projects/{projectId}/assets/. Each image has a sidecar .json file with metadata (prompt, model, seed, dimensions, creation time).

projects/{projectId}/assets/
  a1b2c3d4.png
  a1b2c3d4.json    # sidecar metadata
  e5f6g7h8.png
  e5f6g7h8.json

The sidecar is the source of truth for asset metadata — never infer it from the image filename.

Workflows: per-project dir

Workflows are persisted via save_workflow / load_workflow Rust commands. Each workflow lives in its own subdirectory under the project’s workflow dir, with the React Flow graph JSON as the canonical artifact.

Model presets

Saved via save_model_presets / load_model_presets (file system, app data dir). Each preset is { provider, model, host?, apiKey? }. Cloud API keys live in presets — not in settings.json.

Pane sizes

useAllotmentLayout persists Allotment pane sizes via the Tauri Store, not a separate file. On mount, defaultSizes is restored from the stored value. On drag end, onDragEnd writes the new sizes.

What is NOT persisted

  • Chat history — lives in chatStore (Zustand) and is lost on app restart. This is intentional.
  • Per-panel UI state (open/closed sections, selected tabs) — resets each launch.
  • Wizard annotations — tied to the live preview, ephemeral by design.

What next

  • Backend — the file system commands (read_file, write_file, create_dir, …)
  • AI Streaming — channels for transient data