···270270Get notified even when AT Todo isn't open.
271271272272**Enabling Push Notifications:**
273273-1. Open Settings
274274-2. Click "Enable Push Notifications"
275275-3. Grant permission when prompted
276276-4. Configure your preferences
273273+274274+1. **Open Settings**:
275275+ - Click the "Settings" link in the top navigation bar
276276+ - A settings dialog (modal) will open
277277+278278+2. **Find Notification Settings**:
279279+ - In the Settings dialog, scroll to "Notification Settings"
280280+ - You'll see your current notification status
281281+282282+3. **Enable Notifications**:
283283+ - Click "Enable Push Notifications" button
284284+ - Your browser will ask for permission - click "Allow"
285285+ - The page will automatically subscribe this device
286286+287287+4. **Configure Your Preferences**:
288288+ - Once enabled, notification preferences will appear
289289+ - Customize timing, frequency, and quiet hours
290290+ - Click "Save Preferences"
277291278292**Notification Settings:**
279293···338352- **Privacy-first**: All tracking stored in your AT Protocol repository
339353- **Automatic**: No configuration needed
340354355355+### Multi-Device Notifications
356356+357357+AT Todo supports receiving notifications on multiple devices (phone, tablet, desktop, etc.).
358358+359359+**Registering Multiple Devices:**
360360+361361+Each device needs to be registered separately to receive notifications:
362362+363363+1. **On Each Device**:
364364+ - Open AT Todo in your browser
365365+ - Click the "Settings" link in the top navigation
366366+ - Scroll to "Notification Settings" in the settings dialog
367367+ - Click "Enable Push Notifications" (or "Register This Device" if already enabled)
368368+ - Grant browser permission when prompted
369369+370370+2. **Verify Registration**:
371371+ - In Settings, look for "Registered Devices" section
372372+ - You should see a list of all your registered devices
373373+ - Each device shows its browser/OS and registration date
374374+ - Example: "Device 1: Mozilla/5.0 (Macintosh; Intel Mac OS...) (added 11/22/2024)"
375375+376376+3. **Managing Devices**:
377377+ - Click the X button next to any device to remove it from notifications
378378+ - Devices can be re-registered at any time by clicking "Register This Device"
379379+ - Remove old/unused devices to keep your device list clean
380380+ - Inactive subscriptions expire automatically over time
381381+382382+**How It Works:**
383383+- Each browser/device gets a unique push subscription
384384+- Notifications are sent to **all registered devices simultaneously**
385385+- You'll receive notifications on your phone, tablet, and desktop
386386+- Completing a task on one device doesn't clear notifications on others
387387+341388### Test Notifications
342389343390**Test your notification setup:**
3443911. Open Settings
3453922. Enable notifications if not already enabled
3463933. Click "Send Test Notification"
347347-4. You should see a test notification appear
394394+4. You should see a test notification on **all registered devices**
395395+5. Check the response message to see how many devices received it
348396349397**If notifications aren't working:**
350398- Check browser notification permissions
351399- Ensure notifications aren't blocked in system settings
352400- Try a different browser (Chrome/Edge have best support)
353401- Check quiet hours settings
402402+- Click "Register This Device" to refresh registration
403403+- Verify device appears in "Registered Devices" list
354404355405### Browser Compatibility
356406
+416
docs/notifications.md
···11+# Notification Setup Guide
22+33+Complete guide to setting up and managing push notifications in AT Todo.
44+55+## Table of Contents
66+77+- [Getting Started](#getting-started)
88+- [Multi-Device Setup](#multi-device-setup)
99+- [Notification Settings](#notification-settings)
1010+- [Troubleshooting](#troubleshooting)
1111+1212+---
1313+1414+## Getting Started
1515+1616+### Enabling Push Notifications
1717+1818+Push notifications allow you to receive alerts about due tasks even when AT Todo isn't open.
1919+2020+**Step 1: Access Settings**
2121+2222+1. Click the "Settings" link in the navigation bar
2323+2. A settings dialog (modal) will open
2424+2525+**Step 2: Enable Notifications**
2626+2727+1. In the Settings dialog, scroll down to "Notification Settings"
2828+2. You'll see your current notification status (e.g., "Not enabled")
2929+3. Click the "Enable Push Notifications" button
3030+4. Your browser will prompt for permission - click "Allow"
3131+3232+**Step 3: Configure Preferences**
3333+3434+Once enabled, you'll see notification preferences:
3535+3636+- **Timing**: Choose which types of tasks trigger notifications
3737+ - Overdue tasks (default: on)
3838+ - Tasks due today (default: on)
3939+ - Tasks due within 3 days (default: off)
4040+4141+- **Check Frequency**: How often to check for new notifications
4242+ - Every 15 minutes
4343+ - Every 30 minutes (default)
4444+ - Every hour
4545+ - Every 2 hours
4646+4747+- **Quiet Hours**: Set Do Not Disturb times
4848+ - Enable/disable quiet hours
4949+ - Start time (default: 10 PM)
5050+ - End time (default: 8 AM)
5151+5252+**Step 4: Test Your Setup**
5353+5454+1. Click "Send Test Notification" button
5555+2. You should see a test notification appear
5656+3. If successful, you'll see a confirmation toast
5757+5858+---
5959+6060+## Multi-Device Setup
6161+6262+AT Todo supports notifications on multiple devices - your phone, tablet, desktop, etc.
6363+6464+### How Multi-Device Works
6565+6666+- Each browser/device needs to register separately
6767+- Notifications are sent to **all registered devices** simultaneously
6868+- Each device maintains its own subscription
6969+- All devices receive the same notifications
7070+7171+### Registering Additional Devices
7272+7373+**For each device you want to receive notifications:**
7474+7575+1. **Open AT Todo** on the new device
7676+2. **Login** with your account
7777+3. **Open Settings** (click the "Settings" link in the top navigation)
7878+4. **Enable Notifications**:
7979+ - If this is your first device: Click "Enable Push Notifications"
8080+ - If you already have notifications enabled on another device: Click "Register This Device"
8181+5. **Grant Permission** when your browser prompts
8282+6. **Verify** the device appears in "Registered Devices" list
8383+8484+### Viewing Registered Devices
8585+8686+In the Settings → Notification Settings section, you'll see a "Registered Devices" list showing:
8787+8888+- Device count
8989+- Browser/OS information for each device
9090+- Registration date
9191+9292+Example:
9393+```
9494+Registered Devices:
9595+• Device 1: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)... (added 11/22/2024)
9696+• Device 2: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)... (added 11/22/2024)
9797+• Device 3: Mozilla/5.0 (Windows NT 10.0; Win64; x64)... (added 11/23/2024)
9898+```
9999+100100+### Re-registering a Device
101101+102102+If notifications stop working on a device:
103103+104104+1. Open Settings on that device
105105+2. Click "Register This Device" button
106106+3. The device will refresh its subscription
107107+4. Test with "Send Test Notification"
108108+109109+### Removing Devices
110110+111111+To remove a device from receiving notifications:
112112+113113+1. **Open Settings** on any device
114114+2. **Find the device** in the "Registered Devices" list
115115+3. **Click the X button** next to the device you want to remove
116116+4. **Confirm removal** when prompted
117117+5. The device will no longer receive notifications
118118+119119+**Notes:**
120120+- You can remove devices from any logged-in device (not just the device itself)
121121+- Devices can be re-registered at any time
122122+- Removing the last device will reset the notification UI
123123+- If you remove the current device, you'll need to re-enable notifications
124124+125125+### Managing Old Devices
126126+127127+- Inactive device subscriptions expire automatically over time
128128+- Failed notifications to old devices won't affect active ones
129129+- Manual removal via the X button is the recommended cleanup method
130130+131131+---
132132+133133+## Notification Settings
134134+135135+### Timing Preferences
136136+137137+Control which tasks trigger notifications:
138138+139139+**Overdue Tasks** (Recommended: ON)
140140+- Notifies when tasks are past their due date
141141+- Highest priority - shown first
142142+- Example: "Submit report" was due yesterday
143143+144144+**Tasks Due Today** (Recommended: ON)
145145+- Notifies about tasks due within 24 hours
146146+- Shows time until due
147147+- Example: "Team meeting" due at 2:00 PM today
148148+149149+**Tasks Due Soon** (Optional)
150150+- Notifies about tasks due within 3 days
151151+- Helpful for planning ahead
152152+- Example: "Project deadline" due in 2 days
153153+154154+**Hours Before Due** (Advanced)
155155+- Get advance notice before tasks are due
156156+- Range: 0-72 hours
157157+- Example: Set to 2 hours to get notified 2 hours before due time
158158+159159+### Check Frequency
160160+161161+How often AT Todo checks for new due tasks:
162162+163163+- **Every 15 minutes**: Most responsive, more battery usage
164164+- **Every 30 minutes**: Balanced (default)
165165+- **Every hour**: Less frequent, better battery life
166166+- **Every 2 hours**: Minimal battery impact
167167+168168+**Note**: Server-side checks also run every 5 minutes regardless of client settings.
169169+170170+### Quiet Hours (Do Not Disturb)
171171+172172+Prevent notifications during sleep or focus time:
173173+174174+**Enable Quiet Hours:**
175175+1. Check "Enable Do Not Disturb mode"
176176+2. Set start time (hour, 0-23)
177177+ - Default: 22 (10 PM)
178178+3. Set end time (hour, 0-23)
179179+ - Default: 8 (8 AM)
180180+181181+**How It Works:**
182182+- No notifications during quiet hours
183183+- Notifications queued will appear after quiet hours end
184184+- Works independently on each device
185185+186186+### Saving Changes
187187+188188+After configuring preferences:
189189+1. Click "Save Preferences" button
190190+2. You'll see a success toast
191191+3. Settings are synced to your AT Protocol repository
192192+193193+---
194194+195195+## Troubleshooting
196196+197197+### Notifications Not Appearing
198198+199199+**Check Browser Permissions:**
200200+201201+1. **Chrome/Edge**:
202202+ - Click lock icon in address bar
203203+ - Check "Notifications" is set to "Allow"
204204+205205+2. **Firefox**:
206206+ - Click lock icon → Permissions → Notifications
207207+ - Should be "Allowed"
208208+209209+3. **Safari**:
210210+ - Safari → Settings → Websites → Notifications
211211+ - Find your domain, should be "Allow"
212212+213213+**Check System Settings:**
214214+215215+- **macOS**: System Settings → Notifications → [Your Browser]
216216+- **Windows**: Settings → System → Notifications → [Your Browser]
217217+- **iOS**: Settings → [Your Browser] → Notifications
218218+- **Android**: Settings → Apps → [Your Browser] → Notifications
219219+220220+**Verify AT Todo Settings:**
221221+222222+1. Open Settings
223223+2. Check notification status shows "Enabled ✓"
224224+3. Verify device appears in "Registered Devices"
225225+4. Try clicking "Register This Device" to refresh
226226+227227+**Test Connection:**
228228+229229+1. Click "Send Test Notification"
230230+2. Check the response:
231231+ - Success: "Test notification sent to X device(s)"
232232+ - Failure: Error message with details
233233+3. Check browser console for errors (F12 → Console)
234234+235235+### Device Not Listed
236236+237237+If a device doesn't appear in "Registered Devices":
238238+239239+1. Click "Enable Push Notifications" or "Register This Device"
240240+2. Grant permission when prompted
241241+3. Wait 2-3 seconds for registration
242242+4. Refresh the Settings page
243243+5. Device should now appear
244244+245245+### Notifications on Some Devices Only
246246+247247+If notifications work on one device but not others:
248248+249249+1. On the non-working device, open Settings
250250+2. Check notification permission status
251251+3. Click "Register This Device"
252252+4. Send test notification
253253+5. All devices should receive it
254254+255255+**Common causes:**
256256+- Device wasn't registered (no subscription created)
257257+- Browser permission denied
258258+- Browser doesn't support push notifications
259259+- System notifications disabled for browser
260260+261261+### Wrong Notification Times
262262+263263+If notifications appear at wrong times:
264264+265265+**Check Timezone:**
266266+1. Verify system timezone is correct
267267+2. Edit a task and check the time shown
268268+3. Times should match your local timezone
269269+270270+**Check Task Due Times:**
271271+1. Tasks without times trigger at midnight
272272+2. Add specific times for better scheduling
273273+3. Example: "tomorrow at 3pm" vs just "tomorrow"
274274+275275+### Too Many/Few Notifications
276276+277277+**Adjust Settings:**
278278+279279+1. **Too many**:
280280+ - Disable "Tasks due soon" notifications
281281+ - Increase check frequency to 2 hours
282282+ - Enable quiet hours
283283+284284+2. **Too few**:
285285+ - Enable all notification types
286286+ - Decrease check frequency to 15 minutes
287287+ - Check quiet hours aren't blocking
288288+289289+**Notification Cooldown:**
290290+- Each task only notifies once per 12 hours
291291+- Prevents spam for the same task
292292+- Resets after task is completed
293293+294294+### Browser Compatibility Issues
295295+296296+**Best Support:**
297297+- Chrome (desktop & Android)
298298+- Edge (desktop)
299299+- Safari (iOS 16.4+, macOS)
300300+301301+**Limited Support:**
302302+- Firefox (no background sync)
303303+- Older Safari versions
304304+- Mobile browsers (varies)
305305+306306+**If using unsupported browser:**
307307+1. Switch to Chrome or Edge for best experience
308308+2. Or keep AT Todo open for notifications
309309+3. Check server logs for notification sends
310310+311311+### Service Worker Issues
312312+313313+If notifications completely stop working:
314314+315315+**Reset Service Worker:**
316316+317317+1. Open browser DevTools (F12)
318318+2. Go to Application tab → Service Workers
319319+3. Click "Unregister" next to AT Todo worker
320320+4. Refresh the page
321321+5. Service worker will reinstall
322322+6. Re-enable notifications in Settings
323323+324324+**Check Service Worker Status:**
325325+326326+1. Navigate to `/app/settings`
327327+2. Open Console (F12)
328328+3. Look for `[Push]` prefixed logs
329329+4. Should see "Service worker ready"
330330+5. Should see "Subscription registered successfully"
331331+332332+---
333333+334334+## Advanced Topics
335335+336336+### How Notifications Work
337337+338338+**Architecture:**
339339+340340+1. **Client-Side Check** (Browser):
341341+ - Service worker runs periodic checks
342342+ - Compares current time to task due dates
343343+ - Shows notifications for matching tasks
344344+345345+2. **Server-Side Check** (Background Job):
346346+ - Runs every 5 minutes
347347+ - Checks all enabled users
348348+ - Sends push notifications to registered devices
349349+350350+3. **Push Service** (Browser Vendor):
351351+ - Chrome uses FCM (Firebase Cloud Messaging)
352352+ - Safari uses APNs (Apple Push Notification service)
353353+ - Delivers notifications to devices
354354+355355+**Data Flow:**
356356+357357+```
358358+Task Due → Server Check → Push Service → Your Device → Notification
359359+```
360360+361361+### Privacy & Security
362362+363363+**What's Stored:**
364364+365365+- **In Database**:
366366+ - Your DID (user identifier)
367367+ - Device push subscriptions (endpoints, keys)
368368+ - Notification history (prevents spam)
369369+370370+- **In AT Protocol**:
371371+ - Notification preferences
372372+ - Task data (titles, due dates)
373373+374374+- **Never Stored**:
375375+ - Notification content (generated on-demand)
376376+ - Which notifications you viewed
377377+ - Device location or tracking data
378378+379379+**Encryption:**
380380+381381+- Push subscriptions use public/private key encryption
382382+- VAPID (Voluntary Application Server Identification)
383383+- End-to-end encrypted between server and device
384384+385385+### Notification History
386386+387387+Prevent notification spam with built-in cooldown:
388388+389389+- Each task can only notify once per 12 hours
390390+- Tracked in `notification_history` table
391391+- Resets when task is completed
392392+- Separate tracking per notification type (overdue, today, soon)
393393+394394+---
395395+396396+## Getting Help
397397+398398+If you're still having issues:
399399+400400+1. Check the [Features Guide](/docs/features) for more details
401401+2. Review browser console for error messages
402402+3. Check server logs for notification sending
403403+4. Report issues on GitHub
404404+5. Contact via Bluesky
405405+406406+---
407407+408408+## Tips for Best Experience
409409+410410+1. **Enable on all devices** you regularly use
411411+2. **Set appropriate check frequency** based on urgency
412412+3. **Use quiet hours** to avoid sleep disruption
413413+4. **Test notifications** after first setup
414414+5. **Keep browser updated** for best compatibility
415415+6. **Grant persistent permissions** (don't use Incognito mode)
416416+7. **Check "Registered Devices"** periodically to verify active devices
+127-8
templates/partials/notification-settings.html
···99 </p>
1010 <div id="registered-devices" style="display: none; margin-top: 1rem;">
1111 <p><strong>Registered Devices:</strong></p>
1212- <ul id="device-list" style="font-size: 0.9rem; color: var(--pico-muted-color);"></ul>
1212+ <ul id="device-list" style="font-size: 0.9rem; color: var(--pico-muted-color); list-style: none; padding: 0;"></ul>
1313 </div>
1414 </div>
1515···183183 deviceList.innerHTML = '';
184184 subscriptions.forEach((sub, index) => {
185185 const li = document.createElement('li');
186186+ li.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; margin: 0.25rem 0; border: 1px solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius);';
187187+188188+ const deviceInfo = document.createElement('span');
186189 const userAgent = sub.userAgent || 'Unknown device';
187190 const createdAt = new Date(sub.createdAt).toLocaleDateString();
188188- li.textContent = `Device ${index + 1}: ${userAgent.substring(0, 50)}... (added ${createdAt})`;
191191+ deviceInfo.textContent = `Device ${index + 1}: ${userAgent.substring(0, 50)}... (added ${createdAt})`;
192192+ deviceInfo.style.flex = '1';
193193+194194+ const removeBtn = document.createElement('button');
195195+ removeBtn.innerHTML = '×';
196196+ removeBtn.className = 'secondary';
197197+ removeBtn.style.cssText = 'padding: 0.25rem 0.5rem; margin: 0; font-size: 1.25rem; line-height: 1; min-width: auto;';
198198+ removeBtn.setAttribute('aria-label', 'Remove device');
199199+ removeBtn.onclick = () => removeDevice(sub.endpoint, li);
200200+201201+ li.appendChild(deviceInfo);
202202+ li.appendChild(removeBtn);
189203 deviceList.appendChild(li);
190204 });
191205···196210 }
197211}
198212213213+async function removeDevice(endpoint, listItem) {
214214+ if (!confirm('Remove this device from receiving notifications?')) {
215215+ return;
216216+ }
217217+218218+ try {
219219+ const response = await fetch('/app/push/unsubscribe', {
220220+ method: 'POST',
221221+ headers: {
222222+ 'Content-Type': 'application/json'
223223+ },
224224+ body: JSON.stringify({ endpoint })
225225+ });
226226+227227+ if (!response.ok) {
228228+ const errorText = await response.text();
229229+ throw new Error(`Failed to unsubscribe: ${response.status} ${errorText}`);
230230+ }
231231+232232+ const result = await response.json();
233233+234234+ // Remove the device from the UI with animation
235235+ listItem.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
236236+ listItem.style.opacity = '0';
237237+ listItem.style.transform = 'translateX(-20px)';
238238+239239+ setTimeout(async () => {
240240+ listItem.remove();
241241+242242+ // Check if this was the last device
243243+ const deviceList = document.getElementById('device-list');
244244+ if (deviceList.children.length === 0) {
245245+ document.getElementById('registered-devices').style.display = 'none';
246246+247247+ // If we removed the current device, disable notifications UI
248248+ const currentEndpoint = await getCurrentDeviceEndpoint();
249249+ if (currentEndpoint === endpoint) {
250250+ // Reset UI to not enabled state
251251+ const statusEl = document.getElementById('notification-permission-status');
252252+ const registerBtn = document.getElementById('register-device');
253253+ const prefsEl = document.getElementById('notification-preferences');
254254+255255+ statusEl.textContent = 'Not enabled';
256256+ statusEl.style.color = '';
257257+ registerBtn.style.display = 'none';
258258+ prefsEl.style.display = 'none';
259259+260260+ // Show enable button
261261+ document.getElementById('enable-notifications').style.display = 'block';
262262+ }
263263+ }
264264+265265+ showToast('Device removed from notifications', 'success');
266266+ }, 300);
267267+268268+ } catch (error) {
269269+ console.error('Failed to remove device:', error);
270270+ showToast('Failed to remove device: ' + error.message, 'error');
271271+ }
272272+}
273273+274274+async function getCurrentDeviceEndpoint() {
275275+ try {
276276+ if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
277277+ return null;
278278+ }
279279+280280+ const registration = await navigator.serviceWorker.ready;
281281+ const subscription = await registration.pushManager.getSubscription();
282282+ return subscription ? subscription.endpoint : null;
283283+ } catch (error) {
284284+ console.error('Failed to get current device endpoint:', error);
285285+ return null;
286286+ }
287287+}
288288+199289async function requestNotificationPermission() {
200290 try {
201291 const permission = await Notification.requestPermission();
···206296 // Update UI to show preferences
207297 const statusEl = document.getElementById('notification-permission-status');
208298 const enableBtn = document.getElementById('enable-notifications');
299299+ const registerBtn = document.getElementById('register-device');
209300 const prefsEl = document.getElementById('notification-preferences');
210301211302 statusEl.textContent = 'Enabled ✓';
212303 statusEl.style.color = 'var(--pico-primary)';
213304 enableBtn.style.display = 'none';
305305+ registerBtn.style.display = 'inline-block';
214306 prefsEl.style.display = 'block';
215307216308 // Load current settings or use defaults (this populates the form)
···237329 }
238330239331 // Subscribe to push notifications
240240- await subscribeToPush();
332332+ const subResult = await subscribeToPush();
333333+334334+ // Show registration feedback
335335+ if (subResult && subResult.message === 'Subscription created successfully') {
336336+ showToast('Device registered for notifications!', 'success', 2000);
337337+ } else if (subResult && subResult.message === 'Subscription already exists') {
338338+ showToast('Device already registered', 'info', 2000);
339339+ }
340340+341341+ // Reload devices list to show the new registration
342342+ await loadRegisteredDevices();
241343242344 // Register periodic sync for background notifications
243345 await registerPeriodicSync();
···246348 }
247349 } catch (error) {
248350 console.error('Error requesting notification permission:', error);
249249- showToast('Failed to enable notifications', 'error');
351351+ showToast('Failed to enable notifications: ' + error.message, 'error');
250352 }
251353}
252354···415517 throw new Error(`Failed to register push subscription: ${subscribeResponse.status} ${errorText}`);
416518 }
417519418418- console.log('[Push] Subscription registered successfully');
520520+ const result = await subscribeResponse.json();
521521+ console.log('[Push] Subscription registered successfully:', result.message);
522522+523523+ // Return the result so caller can show appropriate message
524524+ return result;
419525 } catch (error) {
420526 console.error('[Push] Failed to subscribe:', error);
421527 // Don't show toast on page load - only when user explicitly enables
422528 // showToast('Failed to set up push notifications', 'error');
529529+ throw error;
423530 }
424531}
425532···503610// Add event listener for register device button
504611document.getElementById('register-device')?.addEventListener('click', async () => {
505612 try {
506506- await subscribeToPush();
613613+ const result = await subscribeToPush();
507614 await loadRegisteredDevices();
508508- showToast('Device registration refreshed!', 'success');
615615+616616+ // Show appropriate message based on backend response
617617+ if (result && result.message) {
618618+ if (result.message === 'Subscription already exists') {
619619+ showToast('This device is already registered for notifications', 'info');
620620+ } else if (result.message === 'Subscription created successfully') {
621621+ showToast('Device registered successfully!', 'success');
622622+ } else {
623623+ showToast('Device registration updated', 'success');
624624+ }
625625+ } else {
626626+ showToast('Device registration refreshed!', 'success');
627627+ }
509628 } catch (error) {
510629 console.error('Failed to register device:', error);
511511- showToast('Failed to register device', 'error');
630630+ showToast('Failed to register device: ' + error.message, 'error');
512631 }
513632});
514633</script>