Skip to main content

State Management

Meelio uses Zustand for state management, providing a lightweight, hook-based approach to global state.

Store Location

All stores are located in:
packages/shared/src/stores/

Available Stores

Meelio includes 18 specialized stores for different features:

Timer Store

File: timer.store.tsManages Pomodoro timer state, sessions, and statistics.

State Shape

interface TimerState {
  stage: TimerStage;              // 'Focus' | 'Break'
  isRunning: boolean;             // Timer active state
  endTimestamp: number | null;    // When current session ends
  durations: {
    [TimerStage.Focus]: number;   // Focus duration in seconds
    [TimerStage.Break]: number;   // Break duration in seconds
  };
  settings: {
    notifications: boolean;        // Enable notifications
    sounds: boolean;               // Enable completion sounds
    soundscapes: boolean;          // Enable soundscapes
    autoStartBreaks: boolean;      // Auto-start breaks
  };
  stats: {
    focusSec: number;              // Daily focus time
    breakSec: number;              // Daily break time
  };
  unsyncedFocusSec: number;        // Unsynced focus time
  prevRemaining: number | null;    // Previous remaining time
}

Key Actions

start()
function
Start the timer for the current stage
const start = () => void
pause()
function
Pause the running timer
const pause = () => void
reset()
function
Reset timer to initial focus state
const reset = () => void
skipToStage(stage)
function
Skip to a specific timer stage
const skipToStage = (stage: TimerStage) => void
updateDurations(d)
function
Update focus and/or break durations
const updateDurations = (d: Partial<{ focus: number; break: number }>) => void
toggleNotifications()
function
Toggle notification settings
toggleSounds()
function
Toggle completion sound settings
toggleSoundscapes()
function
Toggle soundscapes integration
completeStage()
function
Mark current stage as complete and transition to next

Usage Example

import { useTimerStore } from '@meelio/shared';

function TimerControls() {
  const timerStore = useTimerStore();
  const { stage, isRunning, start, pause, reset } = timerStore();

  return (
    <div>
      <p>Current: {stage === TimerStage.Focus ? 'Focus' : 'Break'}</p>
      <button onClick={isRunning ? pause : start}>
        {isRunning ? 'Pause' : 'Start'}
      </button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
The timer store includes platform-specific implementations for web and extension environments, managed through a store registry pattern.
File: task.store.tsManages tasks, lists, and task categories.

State Shape

interface TaskState {
  lists: TaskListMeta[];      // All task lists
  tasks: Task[];              // All tasks
  activeListId: string | null; // Currently selected list
  isLoading: boolean;
  error: string | null;
}

interface Task {
  id: string;
  userId: string;
  title: string;
  completed: boolean;
  pinned: boolean;
  dueDate?: string;
  categoryId?: string;
  providerId?: string;
  createdAt: number;
  updatedAt: number;
  deletedAt: number | null;
}

Key Actions

addTask(task)
async function
Create a new task
const addTask = async (task: {
  title: string;
  dueDate?: string;
  pinned?: boolean;
  categoryId?: string;
}) => Promise<void>
toggleTask(taskId)
async function
Toggle task completion status
const toggleTask = async (taskId: string) => Promise<void>
togglePinTask(taskId)
async function
Pin/unpin a task (only one task can be pinned at a time)
deleteTask(taskId)
async function
Soft delete a task
getNextPinnedTask()
function
Get the currently pinned incomplete task
const getNextPinnedTask = () => Task | undefined

Usage Example

import { useTaskStore } from '@meelio/shared';

function TaskManager() {
  const { tasks, addTask, toggleTask } = useTaskStore();

  const handleAddTask = async () => {
    await addTask({
      title: 'Complete documentation',
      pinned: true
    });
  };

  return (
    <div>
      {tasks.map(task => (
        <div key={task.id}>
          <input
            type="checkbox"
            checked={task.completed}
            onChange={() => toggleTask(task.id)}
          />
          {task.title}
        </div>
      ))}
    </div>
  );
}
File: note.store.tsManages user notes with local storage persistence.

State Shape

interface NoteState {
  notes: Note[];
  isLoading: boolean;
  error: string | null;
  enableTypingSound: boolean;
}

interface Note {
  id: string;
  userId: string;
  title: string;
  content: string | null;
  pinned: boolean;
  categoryId: string | null;
  providerId: string | null;
  createdAt: number;
  updatedAt: number;
  deletedAt: number | null;
}

Key Actions

addNote(payload)
async function
Create a new note (max 500 notes, 10,000 chars per note)
const addNote = async (payload: {
  title: string;
  content?: string | null;
  pinned?: boolean;
  categoryId?: string | null;
}) => Promise<Note | undefined>
updateNote(id, payload)
async function
Update note properties
const updateNote = async (
  id: string,
  payload: Partial<Pick<Note, "title" | "content" | "pinned" | "categoryId">>
) => Promise<void>
togglePinNote(noteId)
async function
Pin/unpin a note (only one note can be pinned)
deleteNote(id)
async function
Soft delete a note
setEnableTypingSound(enabled)
function
Toggle typing sound effects

Usage Example

import { useNoteStore } from '@meelio/shared';

function NoteEditor() {
  const { notes, addNote, updateNote } = useNoteStore();
  const [content, setContent] = useState('');

  const handleSave = async () => {
    const note = await addNote({
      title: 'Meeting Notes',
      content: content,
      pinned: false
    });
  };

  return (
    <textarea
      value={content}
      onChange={(e) => setContent(e.target.value)}
      onBlur={handleSave}
    />
  );
}
File: soundscapes.store.tsManages ambient soundscapes, combos, and audio playback.

State Shape

interface SoundscapesState {
  sounds: Sound[];              // All available sounds
  combos: Combo[];              // Saved sound combinations
  globalVolume: number;         // Master volume (0-1)
  pausedSounds: number[];       // IDs of paused sounds
  isOscillating: boolean;       // Volume oscillation state
  activeCategoryId: Category | null;
  isShuffling: boolean;         // Shuffle mode active
  sharedSoundState: SoundState[];
  editorTypingSoundEnabled: boolean;
}

interface Sound {
  id: number;
  name: string;
  playing: boolean;
  volume: number;
  loading?: boolean;
}

Key Actions

playSound(id)
function
Start playing a sound
pauseSound(id)
function
Pause a playing sound
toggleSoundState(id)
function
Toggle sound play/pause state
setVolumeForSound(id, volume, comboId?)
function
Set volume for a specific sound
const setVolumeForSound = (
  id: number,
  volume: number,
  comboId?: string
) => void
setGlobalVolume(volume)
function
Set master volume for all sounds
toggleOscillation()
function
Toggle volume oscillation effect
playCategory(category)
function
Play all sounds in a category
playRandom()
function
Play 2-4 random sounds
addCombo(combo)
function
Save a sound combination
playCombo(id)
function
Play a saved combo

Usage Example

import { useSoundscapesStore } from '@meelio/shared';

function SoundControls() {
  const {
    sounds,
    globalVolume,
    playSound,
    setGlobalVolume,
    toggleOscillation
  } = useSoundscapesStore();

  return (
    <div>
      <input
        type="range"
        min="0"
        max="1"
        step="0.1"
        value={globalVolume}
        onChange={(e) => setGlobalVolume(parseFloat(e.target.value))}
      />
      <button onClick={toggleOscillation}>Toggle Oscillation</button>
    </div>
  );
}
File: background.store.tsManages wallpapers and background images/videos.

State Shape

interface BackgroundState {
  wallpapers: Wallpaper[];
  currentWallpaper: Wallpaper | null;
  _hasHydrated: boolean;
}

type Wallpaper = StaticWallpaper | LiveWallpaper;

interface BaseWallpaper {
  id: string;
  type: 'static' | 'live';
  title: string;
  author: string;
  thumbnail: string;
  blurhash: string;
  source: 'unsplash' | 'custom' | 'local';
}

interface StaticWallpaper extends BaseWallpaper {
  type: 'static';
  url: string;
}

interface LiveWallpaper extends BaseWallpaper {
  type: 'live';
  url: string;
  video: {
    fallbackImage: string;
  };
}

Key Actions

setCurrentWallpaper(wallpaper)
function
Set the active wallpaper
removeWallpaper(id)
function
Remove a custom wallpaper
resetToDefault()
function
Reset to default wallpaper (date-based selection)
getWallpaper()
function
Get current wallpaper (handles hydration state)
File: app.store.tsGlobal application settings and configuration.

State Shape

interface AppState {
  version: string;
  platform: 'extension' | 'web';
  mantraRotationCount: number;
  mantraRotationEnabled: boolean;
  wallpaperRotationEnabled: boolean;
  twelveHourClock: boolean;
  confettiOnComplete: boolean;
}

Key Actions

setPlatform(platform)
function
Set the current platform
setMantraRotation(enabled)
function
Enable/disable daily mantra rotation
setWallpaperRotationEnabled(enabled)
function
Enable/disable daily wallpaper rotation
setTwelveHourClock(enabled)
function
Toggle 12/24 hour clock format
setConfettiOnComplete(enabled)
function
Toggle confetti on task completion

Other Stores

Auth Store

auth.store.ts - User authentication and session management

Bookmarks Store

bookmarks.store.ts - Bookmark management

Breathing Store

breathing.store.ts - Breathing exercise state

Calendar Store

calendar.store.ts - Calendar integration

Category Store

category.store.ts - Task/note categories

Dock Store

dock.store.ts - Dock UI state

Greetings Store

greetings.store.ts - Personalized greetings

Onboarding Store

onboarding.store.ts - First-time user experience

Quotes Store

quotes.store.ts - Inspirational quotes

Search Store

search.store.ts - Search functionality

Settings Store

settings.store.ts - Application settings

Site Blocker Store

site-blocker.store.ts - Website blocking

Tab Stash Store

tab-stash.store.ts - Browser tab management

Store Patterns

Persistence

Most stores use Zustand’s persist middleware for localStorage persistence:
export const useNoteStore = create<NoteState>()(
  persist(
    (set, get) => ({ /* state and actions */ }),
    {
      name: 'meelio:local:notes',
      version: 2,
      storage: createJSONStorage(() => localStorage),
      partialize: (s) => ({ enableTypingSound: s.enableTypingSound })
    }
  )
);

Selectors with subscribeWithSelector

Some stores use subscribeWithSelector middleware for fine-grained subscriptions:
export const useTaskStore = create<TaskState>()(
  subscribeWithSelector((set, get) => ({
    // state and actions
  }))
);

Database Integration

Stores integrate with Dexie.js for IndexedDB storage:
import { db } from '../lib/db/meelio.dexie';

const addTask = async (task) => {
  const newTask = { /* ... */ };
  await db.tasks.add(newTask);
  set((state) => ({ tasks: [...state.tasks, newTask] }));
};
All stores follow a consistent pattern:
  1. State interface definition
  2. Store creation with Zustand
  3. Actions that update state
  4. Optional persistence configuration
  5. Database synchronization where needed

Next Steps

Component Overview

Learn about component architecture

Custom Hooks

Explore reusable hooks

Build docs developers (and LLMs) love