Data Persistence
Relevant source files
The following files were used as context for generating this wiki page:The Character Sheet Maker is a "Local-First" application, meaning all user-generated data is stored directly in the user's browser without needing a backend server. This provides a fast, offline-capable experience. Persistence is primarily managed through the browser's localStorage API, automatically saving the character sheet's state, including its layout, content, and styling.
The core of this system is built upon the zustand state management library and its persist middleware. A secondary persistence mechanism is also in place for the user interface's visual theme (light/dark mode), handled by a custom React hook. This ensures that both the user's creative work and their UI preferences are preserved between sessions.
Sources: README.md:8, src/store/useSheetStore.ts:55-121
Core Persistence Mechanism
The application's main state, which includes all character sheet blocks, their positions, and global styles, is managed by a Zustand store. Data persistence is achieved by wrapping the store's definition with the persist middleware from zustand/middleware.
Sources: src/store/useSheetStore.ts:3, package.json:28
Zustand persist Middleware
The useSheetStore is configured to automatically save its state to localStorage. This is enabled by passing a configuration object to the persist function with a name property. This name becomes the key for the data in localStorage.
// src/store/useSheetStore.ts:55-121
export const useSheetStore = create<SheetState>()(
persist(
(set) => ({
// ... state and actions
}),
{ name: "rpg-sheet-storage" } // AutoSave in localStorage
)
);
Sources: src/store/useSheetStore.ts:55-121
Any update to the state via the store's actions (addBlock, updateLayout, setTheme, etc.) will trigger the middleware, which then serializes the entire state and saves it to localStorage under the key rpg-sheet-storage.
This diagram illustrates the automatic persistence flow when a user action modifies the state.
Persisted Data Structure
The data saved to localStorage is a JSON representation of the SheetState interface. This object contains everything needed to reconstruct the character sheet.
SheetState Interface
The root object of the persisted state.
| Property | Type | Description |
|---|---|---|
blocks | Block[] | An array of all block objects that constitute the sheet. |
globalStyle | SheetStyle | An object containing the global theme and font settings for the entire sheet. |
isCompact | boolean | A flag that controls the grid layout's compaction behavior ("physics"). |
Sources: src/store/useSheetStore.ts:40-53
Block and SheetStyle Interfaces
The following diagram shows the relationship between the main data interfaces that are persisted.
This diagram shows that the SheetState is the main container, holding an array of Block objects and a single SheetStyle object.
Sources: src/store/useSheetStore.ts:8-39
Data Flow and State Updates
User interactions within the UI components trigger actions defined in useSheetStore. These actions update the state, and the persist middleware handles saving the new state.
The following sequence diagram shows the process of adding a new block to the sheet from the ToolsSidebar.
Key actions that result in persisted state changes include:
addBlock: Adds a new block. Called fromToolsSidebar.removeBlock: Removes a block. Called fromBlockRenderer.updateBlockData: Updates the content of a block (e.g., text). Called fromBlockRenderer.updateLayout: Updates the position and size of blocks. Called fromCanvas.setTheme: Changes the sheet's visual theme. Called fromToolsSidebar.
Sources: src/store/useSheetStore.ts:76-119, src/components/layout/ToolsSidebar.tsx, src/components/sheet/BlockRenderer.tsx, src/components/sheet/Canvas.tsx:14
Ancillary Persistence: UI Theme
Separate from the sheet data, the application's UI theme (light or dark mode) is also persisted in localStorage. This is managed by the useTheme custom hook.
useTheme Hook
This hook is responsible for:
- Reading the initial theme from
localStorage(key:"theme") or the user's system preference on load. - Applying the theme class (
lightordark) to the root<html>element. - Providing a
toggleThemefunction to switch between themes. - Writing the new theme choice to
localStoragewhenever it changes.
// src/hooks/useTheme.ts:16-20
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(theme);
localStorage.setItem("theme", theme);
}, [theme]);
This useEffect hook ensures that any change to the theme state variable is immediately reflected in the DOM and persisted in localStorage. This functionality is invoked from the TopBar component.
Sources: src/hooks/useTheme.ts:3-26, src/components/layout/TopBar.tsx:14
Summary
The application employs a robust, "Local-First" data persistence strategy using browser localStorage. The primary mechanism relies on Zustand's persist middleware to automatically save all character sheet data, including blocks, styles, and layout. A secondary, independent mechanism uses a custom hook to persist the user's UI theme preference. This dual approach ensures that all aspects of the user's session are preserved seamlessly, providing a reliable and user-friendly experience without any server-side dependency.