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

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, 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.

// 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.",
});
// 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 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 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 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 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.

// 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",
});
// 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, 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 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.

How does OpenWork prevent notification duplication?

The Zustand store in 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →