State Management
Overview
Section titled “Overview”ElyOS uses Svelte 5 runes for state management. Stores are class-based with $state properties and follow the singleton pattern.
Global Stores
Section titled “Global Stores”Global stores are located in the src/lib/stores/ folder:
| File | Store Class | Description |
|---|---|---|
windowStore.svelte.ts | WindowManager | Window management, app loading |
themeStore.svelte.ts | ThemeManager | Theme, dark/light mode, colors |
desktopStore.svelte.ts | DesktopStore | Desktop icons, layout |
notificationStore.svelte.ts | NotificationStore | Notifications |
permissionStore.svelte.ts | PermissionStore | Permissions |
connectionStore.svelte.ts | ConnectionStore | Server connection status |
Store Pattern
Section titled “Store Pattern”Every store follows the same pattern:
import { getContext, setContext } from 'svelte';
export class MyManager { // Reactive state – $state rune data = $state<MyData>({ value: '' }); isLoading = $state(false);
// Derived value – $derived rune isEmpty = $derived(this.data.value === '');
// Method async update(newValue: string) { this.isLoading = true; // ... async operation this.data.value = newValue; this.isLoading = false; }}
// Context keyconst MY_MANAGER_KEY = Symbol('myManager');
// Global singletonlet globalMyManager: MyManager | null = null;
// Creation (once, at app initialization)export function createMyManager() { if (!globalMyManager) { globalMyManager = new MyManager(); } return globalMyManager;}
// Place in context (in layout component)export function setMyManager(manager: MyManager) { globalMyManager = manager; setContext(MY_MANAGER_KEY, manager);}
// Retrieve (from components)export function getMyManager(): MyManager { try { return getContext(MY_MANAGER_KEY); } catch { if (!globalMyManager) { globalMyManager = new MyManager(); } return globalMyManager; }}Importing
Section titled “Importing”Stores can be imported from the central $lib/stores barrel export:
import { getWindowManager, getThemeManager } from '$lib/stores';Or directly:
import { getWindowManager } from '$lib/stores/windowStore.svelte';Using in Components
Section titled “Using in Components”<script lang="ts"> import { getWindowManager } from '$lib/stores';
const windowManager = getWindowManager();</script>
<!-- The $state is reactive – automatically updates -->{#each windowManager.windows as window} <div>{window.title}</div>{/each}
<button onclick={() => windowManager.openWindow('settings', 'Settings')}> Open</button>WindowManager
Section titled “WindowManager”The WindowManager handles all open windows and application loading.
const wm = getWindowManager();
// Open windowwm.openWindow( 'settings', // app name (src/apps/ folder name) 'Settings', // window title { // metadata (optional) defaultSize: { width: 800, height: 600 }, maximizable: true, resizable: true }, { tab: 'security' } // parameters for the app (optional));
// Close windowwm.closeWindow(windowId);
// Activate windowwm.activateWindow(windowId);
// Minimize/restorewm.minimizeWindow(windowId);
// Maximize/restorewm.maximizeWindow(windowId);
// Update titlewm.updateWindowTitle(windowId, 'New Title');
// Get parametersconst params = wm.getWindowParameters(windowId);WindowState Type
Section titled “WindowState Type”type WindowState = { id: string; appName: string; title: string; isActive: boolean; isMinimized: boolean; isMaximized: boolean; zIndex: number; position: { x: number; y: number }; size: { width: number; height: number }; parameters?: AppParameters; isLoading?: boolean; screenshot?: string;};ThemeManager
Section titled “ThemeManager”const theme = getThemeManager();
// Set modeawait theme.setMode('dark'); // 'light' | 'dark' | 'auto'
// Set color (HSL hue value)await theme.setColor('160');
// Font sizeawait theme.setFontSize('medium'); // 'small' | 'medium' | 'large'
// Readconsole.log(theme.effectiveMode); // 'light' | 'dark'console.log(theme.isDark); // booleanconsole.log(theme.cssClasses); // e.g. 'dark font-medium'console.log(theme.cssVariables); // CSS variables objectApp-Specific Stores
Section titled “App-Specific Stores”App-specific stores live in the app’s stores/ folder and follow the same pattern:
export class MyAppStore { items = $state<Item[]>([]); selectedId = $state<string | null>(null);
selected = $derived( this.items.find(i => i.id === this.selectedId) ?? null );
setSelected(id: string) { this.selectedId = id; }}
let instance: MyAppStore | null = null;
export function getMyAppStore() { if (!instance) instance = new MyAppStore(); return instance;}Usage in the app component:
<script lang="ts"> import { getMyAppStore } from './stores/myAppStore.svelte';
const store = getMyAppStore();</script>
{#if store.selected} <p>{store.selected.name}</p>{/if}Using $effect
Section titled “Using $effect”The $effect rune is for handling side effects (e.g., loading data when state changes):
<script lang="ts"> import { getMyAppStore } from './stores/myAppStore.svelte';
const store = getMyAppStore();
$effect(() => { // Runs when store.selectedId changes if (store.selectedId) { loadDetails(store.selectedId); } });
async function loadDetails(id: string) { // ... }</script>Triggering Reactivity with Arrays
Section titled “Triggering Reactivity with Arrays”In Svelte 5, array mutations (e.g., push, splice) don’t always trigger reactivity. Instead:
// ❌ Not reactivethis.windows.push(newWindow);
// ✅ Reactivethis.windows = [...this.windows, newWindow];
// ✅ Modify elementthis.windows = this.windows.map(w => w.id === id ? { ...w, isActive: true } : w);