# Meetily NotificationManagerState Pattern for User Preferences: Implementation Deep Dive

> Explore Meetily's NotificationManagerState pattern. Learn how Arc RwLock provides thread-safe, persistent user notification preferences and deterministic decision gates. Unlock efficient state management.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: deep-dive
- Published: 2026-07-30

---

**Meetily implements a centralized state-management pattern using `Arc<RwLock<T>>` to provide thread-safe, persistent notification preferences with deterministic decision gates.**

Meetily, an open-source meeting assistant from Zackriya-Solutions, centralizes user notification preferences through a robust NotificationManagerState pattern. This Rust-based implementation ensures consistent behavior across async threads while maintaining persistent storage. The pattern combines immutable state containers with a decision-gate architecture to guarantee that every notification respects user consent and system permissions.

## Architectural Foundations of the NotificationManagerState Pattern

The NotificationManagerState pattern rests on three pillars: thread-safe state containers, persistent consent management, and a mandatory decision gate. These components work together in [`frontend/src-tauri/src/notifications/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/manager.rs) to create a single source of truth for notification preferences.

### Immutable Thread-Safe State Containers

All mutable data within the notification system is wrapped in `Arc<RwLock<T>>` to enable concurrent read access with exclusive write capabilities. The `NotificationManager` struct holds `settings: Arc<RwLock<NotificationSettings>>` and `initialized: Arc<RwLock<bool>>`, allowing async tasks to read current preferences without blocking while writers obtain exclusive locks only during persistence operations.

This approach eliminates race conditions when the frontend queries state while the backend updates preferences, ensuring the React UI receives consistent snapshots of the `NotificationSettings` struct.

### Persistent Consent and Preferences Storage

The `ConsentManager` abstraction handles disk I/O for JSON storage at `$XDG_CONFIG/meetily/notifications.json`. During initialization, the manager invokes `get_settings_with_migration` to load existing preferences, while `update_settings` triggers `ConsentManager::save_settings` to persist changes immediately.

This guarantees that user preferences survive application restarts and remain synchronized between the in-memory state and the filesystem. The persistence layer validates settings through `validate_settings` before committing data, preventing corruption of the notification configuration.

### Multi-Layer Decision Gate

Before displaying any notification, the `should_show_notification` method applies three sequential checks:

1. **Consent and system permission validation** – Verifies OS-level notification permissions and user consent
2. **Do-Not-Disturb evaluation** – Checks both manual DND state and system DND status
3. **Per-type preference verification** – Validates specific flags like `show_recording_started`

Only when all three checks pass does the manager forward the request to `SystemNotificationHandler` in [`frontend/src-tauri/src/notifications/system.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/system.rs). This gate pattern ensures zero notifications bypass user preferences, even when triggered by background recording events.

## NotificationManager Lifecycle and State Operations

The NotificationManagerState pattern follows a strict lifecycle that maintains consistency between the Tauri backend and React frontend.

### Construction and Lazy Initialization

Construction occurs through `NotificationManager::new(app_handle).await`, which instantiates `SystemNotificationHandler` and `ConsentManager`, then loads stored settings into the `Arc<RwLock<T>>` container. The `initialize()` method operates lazily, deferring OS permission requests until the first notification requirement, optimizing startup performance.

### Reading State Snapshots

The `get_settings()` method returns a cloned `NotificationSettings` snapshot using `Arc<RwLock<NotificationSettings>>.read().await.clone()`. This immutable snapshot pattern prevents the frontend from holding references to internal state while allowing the UI to render current preferences without blocking the manager's async operations.

### Writing and Validating Updates

When the UI modifies preferences, `update_settings(new)` validates the payload, writes to disk via `ConsentManager`, and atomically swaps the in-memory value through `Arc<RwLock<NotificationSettings>>.write().await`. This transactional approach ensures that disk state and memory state remain synchronized even if the operation encounters errors mid-write.

## Implementation Examples

The following code demonstrates practical usage of the NotificationManagerState pattern within Meetily's Tauri architecture.

### Initializing the Notification Manager

```rust
// frontend/src-tauri/src/notifications/commands.rs
#[tauri::command]
pub async fn init_notifications<R: Runtime>(app: AppHandle<R>) -> Result<()> {
    let manager = NotificationManager::new(app.clone()).await?;
    manager.initialize().await?;
    // Store the manager in AppState for subsequent commands
    Ok(())
}

```

### Displaying Notifications with Preference Checks

```rust
async fn on_recording_start<R: Runtime>(mgr: &NotificationManager<R>, meeting: Option<String>) {
    // Internally checks consent, DND, and show_recording_started flag
    if let Err(e) = mgr.show_recording_started(meeting).await {
        log::error!("Failed to show start notification: {}", e);
    }
}

```

### Updating Preferences from the Frontend

TypeScript invocation from React:

```typescript
// Frontend React component
await invoke('update_notification_settings', {
  newSettings: {
    recording_notifications: true,
    time_based_reminders: false,
    notification_preferences: {
      show_recording_started: true,
      show_recording_stopped: false,
      meeting_reminder_minutes: [10, 2],
    },
  },
});

```

Corresponding Rust command handler:

```rust
// frontend/src-tauri/src/notifications/commands.rs
#[tauri::command]
pub async fn update_notification_settings<R: Runtime>(
    app: AppHandle<R>,
    new_settings: NotificationSettings,
) -> Result<()> {
    let mgr = /* retrieve from AppState */;
    mgr.update_settings(new_settings).await
}

```

### Toggling Do-Not-Disturb Mode

```rust
#[tauri::command]
pub async fn set_manual_dnd<R: Runtime>(app: AppHandle<R>, enabled: bool) -> Result<()> {
    let mgr = /* retrieve from AppState */;
    mgr.set_manual_dnd(enabled).await
}

```

## Key Source Files and Module Organization

The NotificationManagerState pattern spans five core files within the Tauri application:

- **[`frontend/src-tauri/src/notifications/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/manager.rs)** – Central coordinator containing `NotificationManager` struct, `Arc<RwLock<T>>` state containers, and the `should_show_notification` decision logic
- **[`frontend/src-tauri/src/notifications/settings.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/settings.rs)** – Defines `NotificationSettings` and `NotificationPreferences` structs, implements `ConsentManager` for JSON persistence, and provides `validate_settings` helpers
- **[`frontend/src-tauri/src/notifications/types.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/types.rs)** – Enumerations for notification types, priorities, and helper constructors like `recording_started`
- **[`frontend/src-tauri/src/notifications/system.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/system.rs)** – Thin OS-level notification API wrapper invoked after preference checks pass
- **[`frontend/src-tauri/src/notifications/commands.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/commands.rs)** – Tauri command surface exposing manager methods to the React frontend

## Summary

- **Meetily's NotificationManagerState pattern** centralizes notification preferences in [`frontend/src-tauri/src/notifications/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/manager.rs) using `Arc<RwLock<T>>` for thread-safe access
- **Immutable snapshots** ensure the React frontend receives consistent state without blocking async operations, while **persistent storage** via `ConsentManager` maintains settings across sessions
- **Decision gate architecture** guarantees every notification passes consent, DND, and per-type preference checks before reaching `SystemNotificationHandler`
- **Transactional updates** through `update_settings` validate and persist changes atomically, preventing state drift between memory and disk

## Frequently Asked Questions

### How does Meetily ensure thread safety when accessing notification preferences?

Meetily wraps all mutable notification state in `Arc<RwLock<T>>` structures within the `NotificationManager` struct. This allows multiple async tasks to read `NotificationSettings` simultaneously without blocking, while write operations acquire exclusive locks only during persistence. The pattern prevents race conditions when the UI queries state while background recording events trigger notification checks.

### Where does Meetily store notification user preferences persistently?

The `ConsentManager` abstraction in [`frontend/src-tauri/src/notifications/settings.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/settings.rs) handles JSON serialization to `$XDG_CONFIG/meetily/notifications.json`. The manager loads these settings during initialization via `get_settings_with_migration` and immediately persists any changes through `save_settings` when `update_settings` is called, ensuring preferences survive application restarts.

### What prevents unauthorized notifications when Do-Not-Disturb is enabled?

The `should_show_notification` method in [`frontend/src-tauri/src/notifications/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/notifications/manager.rs) implements a mandatory three-layer check: first validating consent and OS permissions, then checking both manual DND state and system DND status, and finally verifying per-type preferences like `show_recording_started`. Only after all checks pass does the manager invoke `SystemNotificationHandler`, effectively gatekeeping all notification displays.

### How does the frontend update notification settings without corrupting state?

The frontend invokes the `update_notification_settings` Tauri command, which routes to `NotificationManager::update_settings`. This method validates the payload, writes to disk via `ConsentManager`, and atomically updates the `Arc<RwLock<T>>` container. By cloning settings for reads and executing serialized writes, the pattern maintains a single source of truth while allowing concurrent access from multiple UI threads.