DashboardSupportWelcome

👤 USER DOCS

Getting Started

Daily Operations

Shift Workspace & TasksPre-Shift SetupLine-Up CardsShift ReportsForms

Staff & Locations

Staff SchedulingManaging Locations

Oversight

Manager ReportsAnalyticsPre-Shift & Compliance

Incidents & Feedback

Incident ReportingAnonymous FeedbackMessages & Announcements

AI & Settings

AI ChatgearApp Settings

Administration

Dashboard & OnboardingAdmin

⚙️ DEVELOPER DOCS

Getting Started

Getting StartedDevelopmentDeployment Guide

Architecture

Architecture OverviewData FlowArchitecture Decision Records

Core Domain

Core DomainDatabase ReferenceLocations DomainAuth & RBACScheduling DomainReports DomainIncidents DomainNotifications DomainAudit Log & OptimizationDesign Audit Findings

Frontend

Frontend ArchitectureFormsLoading SkeletonsComponentsPWA & NotificationsimageScreenshots

API Reference

API Reference

Endpoints

POS Sales APIOptimization Data APISchedule Shifts APIEmployee Export APIReports APIIncidents APIAI Chat APIPush Notifications APIWebhooks APICron API

Contributing

ContributingcodeCode Examples

Security

Security & Compliance
Danvas IconDanvas
Danvas IconDanvas

PWA & Notifications

Installable mobile experience with real-time push alerts

Danvas is a Progressive Web App (PWA) with push notification support. Staff can install it on their mobile devices and receive alerts for staff messages, board announcements, pre-shift updates, incidents, and compliance reminders.

Installation

Danvas meets PWA installability criteria:

  • HTTPS required (Vercel deployment)
  • Service Worker for offline capability
  • Web App Manifest with icons and theme
  • Standalone display mode

Users can install via:

  • Mobile browser "Add to Home Screen" prompt
  • Desktop Chrome/Edge "Install" button in address bar
  • Settings page → Push Notifications → "Install App"

Service Worker

Danvas uses Serwist (v9.5.11), a Next.js/Service Worker library built on Workbox:

Configuration

Components

Custom components built on top of the design system

Screenshots

Adding UI screenshots to documentation

On this page

InstallationService WorkerConfigurationCaching StrategyPush NotificationsHow It WorksSubscription FlowSending NotificationsNotification DisplayClick HandlingVAPID KeysGenerate KeysEnvironment ConfigurationMatrix Push HandlerRate LimitingUninstall/UnsubscribeRelated Files
// apps/app/src/app/sw.ts
import { defaultCache } from "@serwist/next/worker";
import { Serwist } from "@serwist/core";

declare global {
  interface WorkerGlobalScope {
    __SW_MANIFEST: __SerwistConfig["manifest"] | undefined;
  }
}

const serwist = new Serwist({
  precacheCacheName: "precache",
  runtimeCacheCacheName: "runtime-cache",
  plugins: [


Caching Strategy

  • Precache: App shell, fonts, icons
  • Runtime cache: API responses with stale-while-revalidate
  • Network-first: Dynamic content with fallback

Push Notifications

How It Works

  1. User subscribes to push via Settings → Push Notifications
  2. Subscription stored in pushSubscriptions table
  3. App code sends notifications through the push helpers or an admin sends a test notification via /api/push/send
  4. web-push library delivers to all user devices
  5. Service worker displays native notification

Subscription Flow

// apps/app/src/app/api/push/subscribe/route.ts
// POST /api/push/subscribe
{
  endpoint: string,      // Push endpoint URL
  keys: {
    p256dh: string,      // Public key for encryption
    auth: string         // Auth secret
  }
}

Sending Notifications

// POST /api/push/send (admin only)
{
  title: string,         // Notification title
  body: string,          // Notification body
  url: string,          // Deep link URL
  targetUserId?: string  // Optional: specific user, otherwise all
}

Notification Display

Service worker push event handler:

self.addEventListener("push", (event) => {
  const { title, body, url } = event.data.json();
  event.waitUntil(
    self.registration.






Click Handling

When user clicks notification:

self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const url = event.notification.data.url;
  event.waitUntil(
    clients.matchAll({ type: "window" }).







VAPID Keys

Push notifications require VAPID (Voluntary Application Server Identification) keys:

Generate Keys

npx web-push generate-vapid-keys

Environment Configuration

NEXT_PUBLIC_VAPID_PUBLIC_KEY=<public key>
VAPID_PRIVATE_KEY=<private key>

Matrix Push Handler

Danvas uses Matrix for real-time chat. The Matrix push handler maps Matrix user IDs back to Clerk users and sends app-owned Web Push notifications to subscribed devices:

// packages/matrix/push-handler.ts
// Triggers Web Push notifications for Matrix-addressed workflows

Rate Limiting

Notification endpoints and queue processing are rate limited. User-facing notification preferences are still limited and should not be described as a complete per-category preference center yet.

Current notification categories include:

  • New form submissions
  • Incident alerts (for admins)
  • Compliance reminders
  • Chat messages

Uninstall/Unsubscribe

Users can unsubscribe via:

  • Settings → Push Notifications → Disable
  • Browser's notification permissions
  • Removing the PWA from home screen

Unsubscribing deletes the push subscription from the database.

Related Files

FilePurpose
apps/app/src/app/sw.tsService worker with Serwist
apps/app/src/app/api/push/subscribe/route.tsSubscribe endpoint
apps/app/src/app/api/push/send/route.tsAdmin send endpoint
apps/app/src/app/(authenticated)/settings/settings-client.tsxNotification settings UI
packages/notifications/push.tsGeneral server-side push helper
packages/matrix/push-handler.tsMatrix push handler
packages/database/src/schema/pushSubscriptions table
apps/app/next.config.tsSerwist plugin configuration
defaultCache
()],
skipWaiting: true,
clientsClaim: true,
});
showNotification
(title, {
body,
icon: "/apple-icon.png",
badge: "/favicon.ico",
data: { url }
})
);
});
then
((
clientList
)
=>
{
// Focus existing window or open new one
for (const client of clientList) {
if (client.url === url && "focus" in client) return client.focus();
}
return clients.openWindow(url);
})
);
});