# How Desktop Notifications Work in the OpenWork App: Architecture and Implementation

> Discover how OpenWorks dual-layer notification system delivers desktop alerts and in-app messages. Explore the architecture and implementation details of this powerful feature.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: architecture
- Published: 2026-08-09

---

**OpenWork implements a dual-layer notification system combining a persistent in-app notification center for background events and native OS desktop notifications that fire only when the app window is hidden.**

The OpenWork desktop client manages user alerts through two complementary pathways. According to the `different-ai/openwork` source code, the application separates persistent in-app notifications from transient desktop toast notifications to balance user awareness with interruption minimization. This architecture ensures that background events like cloud syncs remain accessible without disrupting workflow when the window is active.

## In-App Notification Center Architecture

The in-app notification center handles persistent entries for background events that survive reloads and remain accessible through a bell icon interface.

### Adding Notifications via notifyEvent and notifyAlert

Background events call `notifyEvent(input)` from [`apps/app/src/react-app/shell/notifications.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/shell/notifications.ts), while alerts requiring immediate attention use `notifyAlert(input, options)`. Both functions forward a `NotificationInput` to the Zustand store, but `notifyAlert` can trigger additional toast actions.

```typescript
// Background event - adds to notification center only
import { notifyEvent } from "@/react-app/shell/notifications";

notifyEvent({
  kind: "cloud",
  title: "Sync finished",
  body: "Your files are up‑to‑date.",
});

```

```typescript
// Alert with toast action - adds to center AND shows immediate toast
import { notifyAlert } from "@/react-app/shell/notifications";

notifyAlert(
  {
    kind: "system",
    title: "Configuration error",
    body: "Failed to load user settings.",
  },
  {
    toastAction: {
      label: "Open Settings",
      onClick: () => window.dispatchEvent(new CustomEvent("openwork-open-settings")),
    },
  },
);

```

### Persistent Store Mechanics in notification-store.ts

The `useNotificationStore` in [`apps/app/src/react-app/kernel/notification-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/notification-store.ts) uses Zustand with the `persist` middleware to maintain state in `localStorage`. The store implements several data integrity mechanisms:

- **Deduplication**: Entries sharing a `dedupeKey` are merged while unread, with counts aggregated
- **Pruning**: The store enforces a maximum of 100 entries and removes items older than 30 days
- **Unread tracking**: The `useUnreadNotificationCount()` hook powers the bell badge indicator

### UI Integration with notification-center.tsx

The bell interface in [`apps/app/src/react-app/shell/notification-center.tsx`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/shell/notification-center.tsx) reads from the Zustand store and displays each entry with defined actions. This UI is deliberately decoupled from the toast system, ensuring background events never interrupt the user while remaining discoverable.

## Native Desktop Notifications

Desktop notifications provide OS-level toasts when the application is not in view, controlled by user preferences and window visibility state.

### User Preference Handling

Preferences are stored in `localStorage` using the key defined in `apps/app/src/react-app/kernel/local-preferences-storage`. The `DesktopNotificationPreference` type in [`apps/app/src/react-app/kernel/desktop-notification-preferences.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/desktop-notification-preferences.ts) supports three levels:

- `"off"`: Disable all desktop notifications
- `"important"`: Show only high-priority events (task failures, permission requests)
- `"all"`: Show every notification type

The `shouldNotify(pref, importance)` helper determines whether a notification meets the threshold to fire.

### Dispatching OS-Level Alerts

The `notifyDesktopEvent(event)` function in [`apps/app/src/react-app/shell/desktop-notifications.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/shell/desktop-notifications.ts) translates `DesktopNotificationEvent` types—such as `task.completed`, `task.failed`, `permission.asked`, or `question.asked`—into `NotificationCopy` objects containing title, body, and importance levels.

The system checks `document.visibilityState !== "visible"` before displaying native toasts. When running inside the OpenWork desktop runtime, it calls `desktopNotificationShow`, a thin wrapper around the OS API. In web contexts, it falls back to a configurable `webNotificationHandler` injectable via `setWebNotificationHandler`.

```typescript
// Fire native notification when task fails (only if window hidden)
import { notifyDesktopEvent } from "@/react-app/shell/desktop-notifications";

notifyDesktopEvent({
  type: "task.failed",
  sessionId: "abc123",
  errorText: "Network request timed out",
});

```

```typescript
// Permission request notification
notifyDesktopEvent({
  type: "question.asked",
  sessionId: "sess-42",
  question: "Do you want to allow access to your files?",
});

```

### Runtime Detection and Fallbacks

The notification layer automatically detects whether it is running inside the OpenWork desktop runtime or a standard browser environment. This detection determines whether to use native OS APIs or injected web notification handlers, ensuring consistent behavior across deployment targets.

## Summary

- **Dual system architecture**: OpenWork separates persistent in-app notifications (`notifyEvent`/`notifyAlert`) from transient desktop OS notifications (`notifyDesktopEvent`)
- **Persistence**: In-app notifications survive reloads via Zustand stores backed by `localStorage`, with automatic deduplication and pruning (max 100 entries, 30-day retention)
- **Visibility-aware**: Native desktop notifications only fire when `document.visibilityState !== "visible"` to prevent interruption during active use
- **Granular controls**: Users configure desktop notification levels ("off", "important", "all") stored in `localStorage` under the standard preferences key
- **Decoupled UI**: The notification center UI operates independently from toast systems, ensuring background events remain accessible without disrupting workflow

## Frequently Asked Questions

### What is the difference between notifyEvent and notifyAlert in OpenWork?

**`notifyEvent`** adds entries to the persistent notification center for background events like cloud sync completion, while **`notifyAlert`** adds entries to the center and can trigger immediate toast notifications with actionable buttons. Both functions live in [`apps/app/src/react-app/shell/notifications.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/shell/notifications.ts), but `notifyAlert` accepts an optional second parameter for toast configuration.

### How does OpenWork decide when to show a native OS notification instead of an in-app alert?

The `notifyDesktopEvent` function in [`apps/app/src/react-app/shell/desktop-notifications.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/shell/desktop-notifications.ts) checks three conditions: user preference settings (off/important/all), the importance level of the event, and whether the document visibility state indicates the window is hidden (`document.visibilityState !== "visible"`). Only when the window is not visible and the importance meets the user threshold will a native OS toast appear.

### Where are notification preferences stored in OpenWork?

Desktop notification preferences are stored in `localStorage` using the same key as other local preferences (`LOCAL_PREFERENCES_KEY` from `apps/app/src/react-app/kernel/local-preferences-storage`). The preference value is one of three strings: `"off"`, `"important"`, or `"all"`, as defined in [`apps/app/src/react-app/kernel/desktop-notification-preferences.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/desktop-notification-preferences.ts).

### How does OpenWork prevent notification duplication?

The Zustand store in [`apps/app/src/react-app/kernel/notification-store.ts`](https://github.com/different-ai/openwork/blob/main/apps/app/src/react-app/kernel/notification-store.ts) implements deduplication logic using the `dedupeKey` field. When a new notification arrives with a key matching an existing unread entry, the store merges them and increments a counter rather than creating a separate line item. This mechanism operates alongside automatic pruning that limits the store to 100 maximum entries and 30 days of retention.