Application State
The application's logic and user experience are driven by a centralized state management system. This system tracks financial progress, gamification metrics, and the evolution of the user's "FinMon" pet.
UserState Interface
The UserState object is the primary data structure that governs the application. It acts as the "single source of truth" for the dashboard, AI services, and gamification engine.
Core Properties
| Property | Type | Description |
| :--- | :--- | :--- |
| trainerName | string | The display name of the user. |
| points | number | Total experience points (XP) earned by the user. |
| level | number | The user's current progression level. |
| monthlyIncome | number | The base "Power" level used for budget health calculations. |
| transactions | Transaction[] | An array of all logged expenses and income items. |
| finMon | FinMonState | Data regarding the user's pet (name, species, stage, mood). |
| dailyStats | DailyStats | Tracks daily activity for quest completion. |
| storyFlags | StoryFlags | Boolean flags to track which UI tutorials or story beats have been seen. |
FinMon State
The finMon object within the state determines the visual representation and personality of the AI companion:
- Species: Ranges from
Coinlet(Egg) toLedgerazard(Master). - Stage: 1 (Egg) through 4 (Master).
- Mood:
happy,neutral, orsad(derived from budget health).
export interface FinMonState {
name: string;
species: 'Coinlet' | 'Cashmander' | 'Wealthasaur' | 'Ledgerazard' | 'SQUIRREL' | 'HAWK' | 'TORTOISE' | 'PHOENIX';
stage: 1 | 2 | 3 | 4;
mood: 'happy' | 'neutral' | 'sad';
}
State Persistence
The application uses localStorage to ensure user progress is saved across sessions. The primary state is serialized and stored under the key finmon_state.
Hydration and Initialization
When the application mounts, it attempts to hydrate the state from local storage. If no existing state is found, it initializes the application using the INITIAL_STATE constant defined in App.tsx.
const [userState, setUserState] = useState<UserState>(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('finmon_state');
if (saved) {
const parsed = JSON.parse(saved);
// Logic for providing defaults for missing fields (migration)
return parsed;
}
}
return INITIAL_STATE;
});
Data Migration Strategy
As new features are added to the application, the structure of UserState may evolve. The application implements a "soft migration" strategy during the state initialization phase:
- Schema Checks: The initialization hook checks for the existence of new keys (e.g.,
storyFlagsordailyStats). - Default Injection: If a user has an older version of the state missing these keys, the application injects the default values from
INITIAL_STATEwithout wiping the user's existing transaction history or points. - Version Tracking: The
useAppUpdatehook compares the local version string against the latestAPP_VERSION. If a mismatch is detected, it triggers the Changelog modal to inform the user of updates.
Extended State & Services
Beyond the main UserState, the application persists specific data for AI context and learning:
Long-Term Memory
Stored under finmon_long_term_memory, this contains a collection of Memory objects. These are short snippets of context (e.g., "The user mentioned they want to buy a house") that the Gemini AI uses to personalize responses.
Learning Rules
Stored under finmon_learning_rules, these capture user corrections for transaction categorization.
- If a user re-categorizes "Starbucks" from "Shopping" to "Food", a rule is saved.
- Future transactions with matching descriptions will automatically use the user-taught category via the
predictCategoryhelper.
Usage Example
To update the global state from a child component, use the onUpdateUser prop, which accepts a partial state object:
// Adding a new transaction and rewarding points
onUpdateUser({
transactions: [...userState.transactions, newTransaction],
points: userState.points + 10
});