Mobile PWA and Push Notifications
Moltis can be installed as a Progressive Web App (PWA) on mobile devices, providing a native app-like experience with push notifications.
Installing on Mobile
iOS (Safari)
- Open moltis in Safari
- Tap the Share button (box with arrow)
- Scroll down and tap “Add to Home Screen”
- Tap “Add” to confirm
The app will appear on your home screen with the moltis icon.
Android (Chrome)
- Open moltis in Chrome
- You should see an install banner at the bottom - tap “Install”
- Or tap the three-dot menu and select “Install app” or “Add to Home Screen”
- Tap “Install” to confirm
The app will appear in your app drawer and home screen.
PWA Features
When installed as a PWA, moltis provides:
- Standalone mode: Full-screen experience without browser UI
- Offline support: Previously loaded content remains accessible, with a dedicated offline page that reloads automatically once the connection returns
- Fast loading: Assets are cached locally
- Home screen icon: Quick access from your device’s home screen
- App shortcuts: Long-press the icon to jump straight to Chats, Projects, or Settings
- App badge: The installed app icon shows an unread count while notifications are pending (Android, Windows, macOS Dock), including when the app is fully closed — the service worker badges the icon as the push arrives. Badging only applies to an installed app; in a plain browser tab there is no icon to draw on.
- Safe area support: Proper spacing for notched devices (iPhone X+)
- Non-disruptive updates: A new version installs in the background and takes over after all existing app windows close. It never reloads a hidden page or discards an unsent draft.
Push Notifications
Push notifications allow you to receive alerts when the LLM responds, even when you’re not actively viewing the app.
Enabling Push Notifications
- Open the moltis app (must be installed as PWA on Safari/iOS)
- Go to Settings > Notifications
- Click Enable to subscribe to push notifications
- When prompted, allow notification permissions
Safari/iOS Note: Push notifications only work when the app is installed as a PWA. If you see “Installation required”, add moltis to your Dock first:
- macOS: File → Add to Dock
- iOS: Share → Add to Home Screen
Managing Subscriptions
The Settings > Notifications page shows all subscribed devices:
- Device name: Parsed from user agent (e.g., “Safari on macOS”, “iPhone”)
- IP address: Client IP at subscription time (supports proxies via X-Forwarded-For)
- Subscription date: When the device subscribed
You can remove any subscription by clicking the Remove button. This works from any device and durably revokes that endpoint: automatic presence and rotation recovery cannot silently add it back. The user must explicitly enable notifications again on the removed device.
Subscription changes are broadcast in real-time via WebSocket, so all connected clients see updates immediately.
How It Works
Moltis uses the Web Push API with VAPID (Voluntary Application Server Identification) keys:
- VAPID Keys: On first run, the server generates a P-256 ECDSA key pair
- Subscription: The browser creates a push subscription using the server’s public key
- Registration: The subscription details are sent to the server and stored
- Notification: When you need to be notified, the server encrypts and sends a push message
Subscription endpoints must be absolute public HTTPS URLs. Moltis rejects loopback, private, link-local, and malformed destinations before storing them and checks them again before delivery. Sends have a ten-second deadline and bounded concurrency, so an unavailable push service cannot hold up channel delivery.
Push API Routes
The gateway exposes these API endpoints for push notifications:
| Endpoint | Method | Description |
|---|---|---|
/api/push/vapid-key | GET | Get the VAPID public key for subscription |
/api/push/subscribe | POST | Register a push subscription |
/api/push/unsubscribe | POST | Remove a push subscription |
/api/push/presence | POST | Report which session this device is viewing |
/api/push/test | POST | Send a test notification to every subscribed device |
/api/push/status | GET | Get push service status and subscription list |
API keys need operator.read for the VAPID key and operator.write for
subscription, presence, and test operations. Because status includes device
metadata and complete endpoints, /api/push/status requires operator.admin.
Password, passkey, and local access retain full access.
Subscribe Request
{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"keys": {
"p256dh": "base64url-encoded-key",
"auth": "base64url-encoded-auth"
},
"replaces": "https://fcm.googleapis.com/fcm/send/old-endpoint",
"revive": false
}
replaces is optional. The service worker sends it when the browser rotates a
subscription (pushsubscriptionchange) so the dead endpoint is retired in the
same request rather than lingering until its next delivery failure.
revive defaults to false. Automatic reconciliation always leaves it false,
so a remotely removed device stays removed. Only an explicit user Enable
action sends true; that clears the endpoint’s durable revocation marker.
Registering a revoked endpoint without that authorization returns 410 Gone.
Presence Request
{
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"client_id": "7ec0d5ae-...",
"sequence": 42,
"session_key": "main",
"visible": true
}
Returns 204 No Content when recorded, 404 Not Found if the server merely
does not know the endpoint, or 410 Gone if it was explicitly revoked. A 404
replaces the unknown browser capability with a fresh subscription before
registering it. A 410 instead disables and removes the browser subscription so
remote revocation remains effective.
Status Response
{
"enabled": true,
"subscription_count": 2,
"subscriptions": [
{
"endpoint": "https://fcm.googleapis.com/...",
"device": "Safari on macOS",
"ip": "192.168.1.100",
"created_at": "2025-02-05T23:30:00Z"
}
]
}
Notification Payload
Push notifications include:
{
"title": "moltis",
"body": "Rolled out to staging and the smoke tests pass.",
"url": "/chats/main",
"sessionKey": "main",
"order": 42,
"notificationId": "5f1c…",
"timestamp": "2026-07-26T09:12:44Z"
}
The title is always moltis, so a private session label is not exposed on a
lock screen. The body is the reply with markdown syntax converted to readable
plain text. Notification previews may still be visible on a locked device;
control that exposure with the operating system’s notification-preview setting.
Clicking a notification focuses an existing window — preferring one already showing the target chat — and routes in place rather than reloading the app. If no window is open, one is opened at the chat.
Delivery Behaviour
Several details keep notifications from piling up or stepping on each other:
- Per-session grouping: Notifications are tagged per session, so one busy
chat produces one notification rather than a wall of them. The replacement
sets
renotify, so it still alerts you instead of being swapped in silently, and its body says how many earlier messages it folded in. - Foreground suppression: Each browser window maintains an ordered presence lease and refreshes it while focused. A device is skipped only when at least one window is visible, focused, and showing that exact chat. Other tabs and devices still get notified.
- Server-side collapsing: Messages carry a per-session
Topic, so a device that was offline wakes to the latest message per session, not a backlog. - Ordered completion delivery: Detached sends retain the assistant message order. A slow older send cannot replace a newer response from the same session.
- Expiry: Messages carry a 6-hour TTL. A push older than that is dropped by the push service rather than delivered as stale news.
- Endpoint hygiene: Push-service endpoints that expire are removed automatically. Rotated or merely server-forgotten subscriptions re-register, while explicitly revoked endpoints remain disabled until the user enables them again.
- Badges: The app badge is derived from every outstanding notification, not only the latest chat. Opening or dismissing one chat decrements the total without clearing notifications from other chats.
Configuration
Feature Flag
Push notifications are controlled by the push-notifications feature flag, which is enabled by default. To disable:
# In your Cargo.toml or when building
[dependencies]
moltis-gateway = { default-features = false, features = ["web-ui", "tls"] }
Or build without the feature:
cargo build --no-default-features --features web-ui,tls,tailscale,file-watcher
Data Storage
Push notification data is stored in push.json in the data directory:
- VAPID keys: Generated once and reused
- Subscriptions: List of all registered browser subscriptions
- Revocations: Removed endpoints that automatic recovery must not recreate
The store is replaced atomically, and malformed JSON disables push initialization instead of silently rotating VAPID keys and invalidating every subscription. The rest of the gateway still starts. VAPID keys are persisted so subscriptions remain valid across restarts.
Mobile UI Considerations
The mobile interface adapts for smaller screens:
- Navigation drawer: The sidebar becomes a slide-out drawer on mobile
- Sessions panel: Displayed as a bottom sheet that can be swiped
- Touch targets: Minimum 44px touch targets for accessibility
- Safe areas: Proper insets for devices with notches or home indicators
Responsive Breakpoints
- Mobile: < 768px width (drawer navigation)
- Desktop: ≥ 768px width (sidebar navigation)
Browser Support
| Feature | Chrome | Safari | Firefox | Edge |
|---|---|---|---|---|
| PWA Install | ✅ | ✅ (iOS) | ❌ | ✅ |
| Push Notifications | ✅ | ✅ (iOS 16.4+) | ✅ | ✅ |
| Service Worker | ✅ | ✅ | ✅ | ✅ |
| Offline Support | ✅ | ✅ | ✅ | ✅ |
Note: iOS push notifications require iOS 16.4 or later and the app must be installed as a PWA.
Troubleshooting
Notifications Not Working
- Send a test notification: Settings > Notifications has a Send button that pushes to every subscribed device and reports how many accepted it. This is the fastest way to tell a broken subscription apart from a chat that never fired one.
- Check permissions: Ensure notifications are allowed in browser/OS settings
- Check subscription: Go to Settings > Notifications to see if your device is listed
- Check server logs: Look for
push notificationmessages for delivery status - Safari/iOS specific:
- Must be installed as PWA (Add to Dock/Home Screen)
- iOS requires version 16.4 or later
- The Enable button is disabled until installed as PWA
- Behind a proxy: Ensure your proxy forwards
X-Forwarded-FororX-Real-IPheaders
PWA Not Installing
- HTTPS required: PWAs require a secure connection (or localhost)
- Valid manifest: Ensure
/manifest.jsonloads correctly - Service worker: Check that
/sw.jsregisters without errors - Clear cache: Try clearing browser cache and reloading
Service Worker Issues
Clear the service worker registration:
- Open browser DevTools
- Go to Application > Service Workers
- Click “Unregister” on the moltis service worker
- Reload the page