From 5ce28caa59682f27cd1a9db3da2cb750596d3e52 Mon Sep 17 00:00:00 2001
From: Stefano Novelli
Date: Wed, 10 Sep 2025 08:42:03 +0200
Subject: [PATCH 1/2] refactor: update menu item labels to English
---
src-tauri/src/lib.rs | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index db72a70..eed023a 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -968,19 +968,19 @@ pub fn run() {
});
let show_item =
- MenuItem::with_id(app, "show", "Mostra Presto", true, None::<&str>)?;
+ MenuItem::with_id(app, "show", "Show Presto", true, None::<&str>)?;
let start_session_item = MenuItem::with_id(
app,
"start_session",
- "Inizia sessione",
+ "Start Session",
false,
None::<&str>,
)?;
- let pause_item = MenuItem::with_id(app, "pause", "Pausa", false, None::<&str>)?;
+ let pause_item = MenuItem::with_id(app, "pause", "Pause", false, None::<&str>)?;
let skip_item =
- MenuItem::with_id(app, "skip", "Salta sessione", false, None::<&str>)?;
- let cancel_item = MenuItem::with_id(app, "cancel", "Annulla", false, None::<&str>)?;
- let quit_item = MenuItem::with_id(app, "quit", "Esci", true, None::<&str>)?;
+ MenuItem::with_id(app, "skip", "Skip Session", false, None::<&str>)?;
+ let cancel_item = MenuItem::with_id(app, "cancel", "Cancel", false, None::<&str>)?;
+ let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
let menu = Menu::with_items(
app,
&[
@@ -1285,43 +1285,43 @@ async fn update_tray_menu(
let tray = app.tray_by_id("main");
if let Some(tray) = tray {
- let show_item = MenuItem::with_id(&app, "show", "Mostra Presto", true, None::<&str>)
+ let show_item = MenuItem::with_id(&app, "show", "Show Presto", true, None::<&str>)
.map_err(|e| format!("Failed to create show item: {}", e))?;
- // Inizia sessione: abilitato solo se non è in esecuzione
+ // Start Session: enabled only if not running
let start_session_item = MenuItem::with_id(
&app,
"start_session",
- "Inizia sessione",
+ "Start Session",
!is_running,
None::<&str>,
)
.map_err(|e| format!("Failed to create start session item: {}", e))?;
- // Pausa: abilitata solo se è in esecuzione e non in pausa
+ // Pause: enabled only if running and not paused
let pause_item = MenuItem::with_id(
&app,
"pause",
- "Pausa",
+ "Pause",
is_running && !is_paused,
None::<&str>,
)
.map_err(|e| format!("Failed to create pause item: {}", e))?;
- // Skip: abilitato solo se è in esecuzione
- let skip_item = MenuItem::with_id(&app, "skip", "Salta sessione", is_running, None::<&str>)
+ // Skip: enabled only if running
+ let skip_item = MenuItem::with_id(&app, "skip", "Skip Session", is_running, None::<&str>)
.map_err(|e| format!("Failed to create skip item: {}", e))?;
- // Annulla: abilitato se è in modalità focus, disabilitato in break/longBreak (undo)
+ // Cancel: enabled if in focus mode, disabled in break/longBreak (undo)
let cancel_text = if current_mode == "focus" {
- "Annulla"
+ "Cancel"
} else {
- "Annulla ultima"
+ "Cancel Last"
};
let cancel_item = MenuItem::with_id(&app, "cancel", cancel_text, true, None::<&str>)
.map_err(|e| format!("Failed to create cancel item: {}", e))?;
- let quit_item = MenuItem::with_id(&app, "quit", "Esci", true, None::<&str>)
+ let quit_item = MenuItem::with_id(&app, "quit", "Quit", true, None::<&str>)
.map_err(|e| format!("Failed to create quit item: {}", e))?;
let new_menu = Menu::with_items(
From 3f52ed8f86b8eae0e5805f35704b950c0fb2fc44 Mon Sep 17 00:00:00 2001
From: Stefano Novelli
Date: Wed, 10 Sep 2025 09:34:58 +0200
Subject: [PATCH 2/2] feat: enhance notification system with user settings and
debugging features
---
src/core/pomodoro-timer.js | 226 +++++++++++++++++++++++++++----
src/index.html | 8 +-
src/managers/settings-manager.js | 128 ++++++++++++++++-
src/styles/notifications.css | 69 ++++++++++
src/utils/theme-loader.js | 2 +-
5 files changed, 396 insertions(+), 37 deletions(-)
diff --git a/src/core/pomodoro-timer.js b/src/core/pomodoro-timer.js
index b73f19e..a724fbd 100644
--- a/src/core/pomodoro-timer.js
+++ b/src/core/pomodoro-timer.js
@@ -778,10 +778,12 @@ export class PomodoroTimer {
);
// Show desktop notification if enabled
- NotificationUtils.showDesktopNotification(
- 'Session Time Limit Reached',
- `Your session has been automatically paused after ${maxTimeInMinutes} minutes. Consider taking a break!`
- );
+ if (this.enableDesktopNotifications) {
+ NotificationUtils.showDesktopNotification(
+ 'Session Time Limit Reached',
+ `Your session has been automatically paused after ${maxTimeInMinutes} minutes. Consider taking a break!`
+ );
+ }
}
}
@@ -2297,56 +2299,222 @@ export class PomodoroTimer {
}
}
- // Simple notification system
+ // Enhanced notification system with better error handling and debugging
async showNotification() {
+ // Only show desktop notifications if the setting is enabled
+ if (!this.enableDesktopNotifications) {
+ console.log('🔔 Desktop notifications are disabled in settings');
+ return;
+ }
+
+ const messages = {
+ focus: 'Break time! Take a rest 😌',
+ break: 'Break over! Time to focus 🍅',
+ longBreak: 'Long break over! Ready for more focus? 🚀'
+ };
+
+ const notificationTitle = 'Presto - Pomodoro Timer';
+ const notificationBody = messages[this.currentMode];
+
+ console.log(`🔔 Attempting to show desktop notification: "${notificationBody}"`);
+
try {
// Check if we're in a Tauri context and use Tauri notifications
if (window.__TAURI__ && window.__TAURI__.notification) {
+ console.log('🔔 Using Tauri notification system');
const { isPermissionGranted, requestPermission, sendNotification } = window.__TAURI__.notification;
// Check if permission is granted
let permissionGranted = await isPermissionGranted();
+ console.log(`🔔 Tauri notification permission status: ${permissionGranted}`);
// If not granted, request permission
if (!permissionGranted) {
+ console.log('🔔 Requesting Tauri notification permission...');
const permission = await requestPermission();
permissionGranted = permission === 'granted';
+ console.log(`🔔 Permission request result: ${permission} (granted: ${permissionGranted})`);
+
+ if (!permissionGranted) {
+ console.warn('❌ Tauri notification permission was denied');
+ NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in system settings to get timer alerts! 🔔', 'warning', this.currentMode);
+ return;
+ }
}
// Send notification if permission is granted
if (permissionGranted) {
- const messages = {
- focus: 'Break time! Take a rest 😌',
- break: 'Break over! Time to focus 🍅',
- longBreak: 'Long break over! Ready for more focus? 🚀'
- };
-
+ console.log('🔔 Sending Tauri notification...');
await sendNotification({
- title: 'Presto - Pomodoro Timer',
- body: messages[this.currentMode],
+ title: notificationTitle,
+ body: notificationBody,
icon: '/assets/tauri.svg'
});
+ console.log('✅ Tauri notification sent successfully');
+ } else {
+ console.warn('❌ Tauri notification permission not available');
+ this.fallbackToWebNotifications(notificationTitle, notificationBody);
}
} else {
- // Fallback to Web Notification API
- if ('Notification' in window && Notification.permission === 'granted') {
- const messages = {
- focus: 'Break time! Take a rest 😌',
- break: 'Break over! Time to focus 🍅',
- longBreak: 'Long break over! Ready for more focus? 🚀'
- };
-
- NotificationUtils.showDesktopNotification('Presto - Pomodoro Timer', messages[this.currentMode]);
+ console.log('🔔 Tauri not available, falling back to Web Notification API');
+ this.fallbackToWebNotifications(notificationTitle, notificationBody);
+ }
+ } catch (error) {
+ console.error('❌ Failed to show Tauri notification:', error);
+ console.log('🔄 Attempting fallback to Web Notification API...');
+ this.fallbackToWebNotifications(notificationTitle, notificationBody);
+ }
+ }
+
+ // Fallback to Web Notification API with improved error handling
+ async fallbackToWebNotifications(title, body) {
+ try {
+ if ('Notification' in window) {
+ console.log(`🔔 Web Notification API available, permission: ${Notification.permission}`);
+
+ if (Notification.permission === 'granted') {
+ console.log('🔔 Sending Web notification...');
+ NotificationUtils.showDesktopNotification(title, body);
+ console.log('✅ Web notification sent successfully');
+ } else if (Notification.permission === 'default') {
+ console.log('🔔 Requesting Web notification permission...');
+ const permission = await Notification.requestPermission();
+ console.log(`🔔 Web permission request result: ${permission}`);
+
+ if (permission === 'granted') {
+ NotificationUtils.showDesktopNotification(title, body);
+ console.log('✅ Web notification sent after permission granted');
+ } else {
+ console.warn('❌ Web notification permission was denied');
+ NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in your browser to get timer alerts! 🔔', 'warning', this.currentMode);
+ }
+ } else {
+ console.warn('❌ Web notification permission was previously denied');
+ NotificationUtils.showNotificationPing('Desktop notifications are disabled. Enable them in your browser settings to get timer alerts! 🔔', 'warning', this.currentMode);
}
+ } else {
+ console.warn('❌ Web Notification API not supported');
+ this.fallbackToInAppNotification(body);
}
} catch (error) {
- console.error('Failed to show notification:', error);
- // Fallback to in-app notification
- this.showNotificationPing(
- this.currentMode === 'focus' ? 'Break time! Take a rest 😌' :
- this.currentMode === 'break' ? 'Break over! Time to focus 🍅' :
- 'Long break over! Ready for more focus? 🚀'
- );
+ console.error('❌ Failed to show Web notification:', error);
+ this.fallbackToInAppNotification(body);
+ }
+ }
+
+ // Final fallback to in-app notification
+ fallbackToInAppNotification(message) {
+ console.log('🔔 Using in-app notification as final fallback');
+ NotificationUtils.showNotificationPing(message, 'info', this.currentMode);
+ }
+
+ // Test notification function for debugging
+ // Usage: Open browser console and type: window.pomodoroTimer.testNotification()
+ async testNotification() {
+ console.log('🧪 Testing notification system...');
+ console.log('📝 Instructions: This will test the notification system and show debug info in the console');
+ console.log(`🔧 Current settings: desktop notifications = ${this.enableDesktopNotifications}`);
+
+ // Detect if we're in development mode
+ const isDevMode = window.location.protocol === 'tauri:' ? false : true;
+ const bundleId = 'com.presto.app';
+
+ console.log(`🔧 Environment: ${isDevMode ? 'Development (tauri dev)' : 'Production (built app)'}`);
+ console.log(`🔧 Bundle ID: ${bundleId}`);
+
+ if (isDevMode) {
+ console.log('⚠️ IMPORTANT: You\'re running in development mode (tauri dev)');
+ console.log('⚠️ On macOS, Tauri notifications often don\'t work in dev mode due to:');
+ console.log(' 1. Tauri uses Terminal.app for dev mode, which may not have notification permissions');
+ console.log(' 2. Bundle identifier is handled differently in dev vs production');
+ console.log(' 3. macOS requires proper app bundle registration for notifications');
+ console.log('');
+ console.log('🔧 To test notifications properly:');
+ console.log(' 1. Run: npm run tauri build');
+ console.log(' 2. Install the built app from src-tauri/target/release/bundle/');
+ console.log(' 3. Test notifications in the installed production app');
+ console.log('');
+ console.log('🔧 For dev mode, check Terminal.app permissions:');
+ console.log(' - System Preferences > Notifications & Focus > Terminal');
+ console.log(' - Make sure "Allow Notifications" is enabled');
+ console.log('');
+ }
+
+ // Show in-app notification first
+ NotificationUtils.showNotificationPing('Testing notification system... 🧪', 'info', this.currentMode);
+
+ // Test desktop notification
+ const originalSetting = this.enableDesktopNotifications;
+ this.enableDesktopNotifications = true; // Temporarily enable for testing
+
+ try {
+ await this.showNotification();
+ console.log('✅ Test notification API call completed - check console logs above for detailed debug info');
+ console.log('🔍 Look for messages starting with 🔔 for notification flow details');
+
+ if (isDevMode) {
+ console.log('');
+ console.log('⚠️ If you see "✅ Tauri notification sent successfully" but no notification appeared:');
+ console.log(' - This is NORMAL in development mode on macOS');
+ console.log(' - Test with a production build to verify notifications work');
+ console.log('');
+ console.log('🔄 Trying Web Notification API as fallback...');
+ await this.testWebNotificationFallback();
+ }
+ } catch (error) {
+ console.error('❌ Test notification failed:', error);
+ console.log('💡 Troubleshooting steps:');
+ if (isDevMode) {
+ console.log(' 1. This is likely due to dev mode limitations on macOS');
+ console.log(' 2. Check Terminal.app notification permissions in System Preferences');
+ console.log(' 3. Test with a production build: npm run tauri build');
+ } else {
+ console.log(' 1. Check if notifications are enabled in System Preferences > Notifications');
+ console.log(' 2. Look for "presto" or "com.presto.app" in the notifications list');
+ console.log(' 3. Ensure "Allow Notifications" is enabled for the app');
+ }
+ } finally {
+ // Restore original setting
+ this.enableDesktopNotifications = originalSetting;
+ }
+ }
+
+ // Test Web Notification API fallback
+ async testWebNotificationFallback() {
+ try {
+ if ('Notification' in window) {
+ console.log('🌐 Web Notification API available');
+ console.log(`🌐 Current permission: ${Notification.permission}`);
+
+ if (Notification.permission === 'default') {
+ console.log('🌐 Requesting Web notification permission...');
+ const permission = await Notification.requestPermission();
+ console.log(`🌐 Permission result: ${permission}`);
+ }
+
+ if (Notification.permission === 'granted') {
+ console.log('🌐 Sending Web notification...');
+ const notification = new Notification('Presto - Test Web Notification', {
+ body: 'This is a fallback Web notification test',
+ icon: '/assets/tauri.svg'
+ });
+
+ notification.onshow = () => console.log('✅ Web notification displayed');
+ notification.onerror = (error) => console.error('❌ Web notification error:', error);
+
+ // Auto-close after 5 seconds
+ setTimeout(() => {
+ notification.close();
+ console.log('🌐 Web notification closed automatically');
+ }, 5000);
+ } else {
+ console.log('❌ Web notification permission denied');
+ }
+ } else {
+ console.log('❌ Web Notification API not available');
+ }
+ } catch (error) {
+ console.error('❌ Web notification test failed:', error);
}
}
diff --git a/src/index.html b/src/index.html
index db70a4b..83c0128 100644
--- a/src/index.html
+++ b/src/index.html
@@ -606,8 +606,12 @@ Notification Types
Desktop Notifications
Show system notifications when timer completes. Browser permission will
- be
- requested when enabled.
+ be requested when enabled.
+ Note: On macOS, notifications may not work in development mode. Test with a production build for full functionality.
+
+
+
+
diff --git a/src/managers/settings-manager.js b/src/managers/settings-manager.js
index b1adcba..3c97876 100644
--- a/src/managers/settings-manager.js
+++ b/src/managers/settings-manager.js
@@ -174,11 +174,8 @@ export class SettingsManager {
this.initializeTimerThemeSelector();
// Populate notification settings
- // Check current notification permission and adjust desktop notifications setting
- const hasNotificationPermission = NotificationUtils.getNotificationPermission() === 'granted';
- const desktopNotificationsEnabled = this.settings.notifications.desktop_notifications && hasNotificationPermission;
-
- document.getElementById('desktop-notifications').checked = desktopNotificationsEnabled;
+ // Always show the user's setting preference, regardless of system permission
+ document.getElementById('desktop-notifications').checked = this.settings.notifications.desktop_notifications;
document.getElementById('sound-notifications').checked = this.settings.notifications.sound_notifications;
document.getElementById('auto-start-timer').checked = this.settings.notifications.auto_start_timer;
@@ -599,7 +596,10 @@ export class SettingsManager {
if (e.target.checked) {
try {
// Request notification permission when enabling
+ console.log('🔔 Desktop notifications enabled, requesting permission...');
const permission = await NotificationUtils.requestNotificationPermission();
+ console.log('🔔 Notification permission result:', permission);
+
if (permission !== 'granted') {
// Show warning but don't prevent saving the setting
const message = permission === 'unsupported'
@@ -607,6 +607,9 @@ export class SettingsManager {
: 'Notification permission denied. Settings saved, but notifications won\'t work until permission is granted.';
NotificationUtils.showNotificationPing(message, 'warning');
// Don't uncheck the box - let the user's choice be saved
+ } else {
+ // Permission granted, show success message
+ NotificationUtils.showNotificationPing('✓ Desktop notifications enabled!', 'success');
}
} catch (error) {
console.warn('Failed to request notification permission, but allowing setting to be saved:', error);
@@ -614,12 +617,18 @@ export class SettingsManager {
// This allows the setting to work when Tauri notifications are properly configured
NotificationUtils.showNotificationPing('Settings saved. Notifications will work when properly configured.', 'info');
}
+ } else {
+ console.log('🔔 Desktop notifications disabled');
+ NotificationUtils.showNotificationPing('Desktop notifications disabled', 'info');
}
// Always save the setting regardless of permission status
this.scheduleAutoSave();
});
}
+ // Initialize notification status display and test button
+ this.setupNotificationStatusDisplay();
+
// Other notification checkboxes
const checkboxFields = [
'sound-notifications',
@@ -997,6 +1006,115 @@ export class SettingsManager {
}
}
+ // Notification status display and debugging functions
+ async setupNotificationStatusDisplay() {
+ const statusDiv = document.getElementById('notification-status');
+ const statusText = document.getElementById('notification-status-text');
+ const testBtn = document.getElementById('test-notifications-btn');
+
+ if (!statusDiv || !statusText || !testBtn) {
+ console.warn('Notification status elements not found in DOM');
+ return;
+ }
+
+ // Show the status div
+ statusDiv.style.display = 'block';
+
+ // Update status on load
+ await this.updateNotificationStatus();
+
+ // Set up test button
+ testBtn.addEventListener('click', async () => {
+ if (window.pomodoroTimer && typeof window.pomodoroTimer.testNotification === 'function') {
+ await window.pomodoroTimer.testNotification();
+ // Update status after test
+ setTimeout(() => this.updateNotificationStatus(), 1000);
+ } else {
+ console.warn('Test notification function not available');
+ NotificationUtils.showNotificationPing('Test function not available. Try again after the timer is fully loaded.', 'warning');
+ }
+ });
+
+ // Update status when desktop notifications setting changes
+ const desktopNotificationsCheckbox = document.getElementById('desktop-notifications');
+ if (desktopNotificationsCheckbox) {
+ desktopNotificationsCheckbox.addEventListener('change', () => {
+ setTimeout(() => this.updateNotificationStatus(), 500);
+ });
+ }
+ }
+
+ async updateNotificationStatus() {
+ const statusDiv = document.getElementById('notification-status');
+ const statusText = document.getElementById('notification-status-text');
+
+ if (!statusDiv || !statusText) return;
+
+ let status = '';
+ let className = '';
+
+ try {
+ // Detect if we're in development mode
+ const isDevMode = window.location.protocol === 'tauri:' ? false : true;
+
+ // Check if desktop notifications are enabled in settings
+ const isEnabledInSettings = document.getElementById('desktop-notifications')?.checked || false;
+
+ if (!isEnabledInSettings) {
+ status = '🔕 Disabled in settings';
+ className = 'status-disabled';
+ } else {
+ // Check Tauri notifications first
+ if (window.__TAURI__ && window.__TAURI__.notification) {
+ try {
+ const { isPermissionGranted } = window.__TAURI__.notification;
+ const granted = await isPermissionGranted();
+ if (granted) {
+ if (isDevMode) {
+ status = '⚠️ Dev mode - may not work on macOS';
+ className = 'status-warning';
+ } else {
+ status = '✅ Tauri notifications ready';
+ className = 'status-ready';
+ }
+ } else {
+ status = '⚠️ Tauri permission needed';
+ className = 'status-warning';
+ }
+ } catch (error) {
+ status = '❌ Tauri error: ' + error.message;
+ className = 'status-error';
+ }
+ } else {
+ // Check Web Notification API
+ if ('Notification' in window) {
+ const permission = Notification.permission;
+ if (permission === 'granted') {
+ status = '✅ Web notifications ready';
+ className = 'status-ready';
+ } else if (permission === 'denied') {
+ status = '❌ Web notifications blocked';
+ className = 'status-error';
+ } else {
+ status = '⚠️ Web permission needed';
+ className = 'status-warning';
+ }
+ } else {
+ status = '❌ Notifications not supported';
+ className = 'status-error';
+ }
+ }
+ }
+ } catch (error) {
+ status = '❌ Status check failed';
+ className = 'status-error';
+ console.error('Failed to check notification status:', error);
+ }
+
+ statusText.textContent = status;
+ statusDiv.className = `notification-status ${className}`;
+ }
+
// Theme management functions
async applyTheme(theme) {
const html = document.documentElement;
diff --git a/src/styles/notifications.css b/src/styles/notifications.css
index f5b16af..ae22da6 100644
--- a/src/styles/notifications.css
+++ b/src/styles/notifications.css
@@ -188,4 +188,73 @@
border-radius: 14px;
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.09);
}
+}
+
+/* Notification status indicator styles */
+.notification-status {
+ border-left: 3px solid #ccc;
+ background-color: var(--bg-secondary);
+ color: var(--text-secondary);
+ transition: all 0.3s ease;
+}
+
+.notification-status.status-ready {
+ border-left-color: #4CAF50;
+ background-color: rgba(76, 175, 80, 0.1);
+ color: #2E7D32;
+}
+
+.notification-status.status-warning {
+ border-left-color: #FF9800;
+ background-color: rgba(255, 152, 0, 0.1);
+ color: #E65100;
+}
+
+.notification-status.status-error {
+ border-left-color: #F44336;
+ background-color: rgba(244, 67, 54, 0.1);
+ color: #C62828;
+}
+
+.notification-status.status-disabled {
+ border-left-color: #9E9E9E;
+ background-color: rgba(158, 158, 158, 0.1);
+ color: #616161;
+}
+
+#test-notifications-btn {
+ background: var(--button-primary);
+ color: var(--button-text);
+ border: 1px solid var(--border);
+ transition: background-color 0.2s ease, transform 0.1s ease;
+}
+
+#test-notifications-btn:hover {
+ background: var(--button-primary-hover);
+ transform: translateY(-1px);
+}
+
+#test-notifications-btn:active {
+ transform: translateY(0);
+}
+
+/* Dark theme adjustments for notification status */
+[data-theme="dark"] .notification-status.status-ready {
+ background-color: rgba(76, 175, 80, 0.15);
+ color: #81C784;
+}
+
+[data-theme="dark"] .notification-status.status-warning {
+ background-color: rgba(255, 152, 0, 0.15);
+ color: #FFB74D;
+}
+
+[data-theme="dark"] .notification-status.status-error {
+ background-color: rgba(244, 67, 54, 0.15);
+ color: #E57373;
+}
+
+[data-theme="dark"] .notification-status.status-disabled {
+ background-color: rgba(158, 158, 158, 0.15);
+ color: #BDBDBD;
}
\ No newline at end of file
diff --git a/src/utils/theme-loader.js b/src/utils/theme-loader.js
index a2f9c22..ced74bb 100644
--- a/src/utils/theme-loader.js
+++ b/src/utils/theme-loader.js
@@ -39,7 +39,7 @@ class ThemeLoader {
// that gets updated by the build process or manually maintained
// This could be enhanced to use a build-time script that generates this list
- const knownThemes = [
+ const knownThemes = [
'espresso.css',
'pipboy.css',
'pommodore64.css'