| name | notification-trigger |
| description | Triggers notifications exclusively for VERY IMPORTANT priority tasks that are incomplete and due within 6 hours. Repeats every 10 minutes with duplicate prevention tracking in memory. Clears tracking when tasks complete or due times pass. |
Notification Trigger Skill
Overview
The notification trigger skill monitors VERY IMPORTANT tasks and generates notification events at appropriate intervals. It implements intelligent duplicate prevention and lifecycle management.
When to Apply
Apply this skill:
- Every 10 minutes while app is open (automatic interval)
- After task priority changes to VERY IMPORTANT
- After task due date updates
- After task completion status changes
- On app initialization (check for eligible tasks)
Notification Conditions
A task is eligible for notification if ALL of the following are true:
- Priority: Task priority is VERY IMPORTANT
- Status: Task is NOT complete
- Due Time: Task is due within next 6 hours
- Not Recently Notified: Task was not notified in last 10 minutes
function isEligibleForNotification(task, notificationHistory) {
if (task.priority !== 'VERY IMPORTANT') {
return false;
}
if (task.status === 'Complete') {
return false;
}
if (!task.dueDate) {
return false;
}
const now = new Date();
const dueDate = new Date(task.dueDate);
const hoursUntilDue = (dueDate - now) / (1000 * 60 * 60);
if (hoursUntilDue <= 0 || hoursUntilDue > 6) {
return false;
}
const lastNotified = notificationHistory[task.id];
if (lastNotified) {
const timeSinceNotification = now - lastNotified;
const tenMinutes = 10 * 60 * 1000;
if (timeSinceNotification < tenMinutes) {
return false;
}
}
return true;
}
10-Minute Repeat Interval
Interval Implementation
function startNotificationMonitoring(tasks, onNotification) {
checkAndTriggerNotifications(tasks, onNotification);
const interval = setInterval(() => {
checkAndTriggerNotifications(tasks, onNotification);
}, 10 * 60 * 1000);
return () => clearInterval(interval);
}
function useNotificationMonitoring(tasks) {
useEffect(() => {
const cleanup = startNotificationMonitoring(tasks, (notification) => {
console.log('Notification triggered:', notification);
});
return cleanup;
}, [tasks]);
}
Why 10 Minutes?
- Balance: Frequent enough to be useful, not so frequent as to be annoying
- Battery efficient: Reasonable interval for mobile devices
- User expectation: Aligns with common notification patterns
- Performance: Minimal CPU usage every 10 minutes
Duplicate Prevention
In-Memory Tracking
Track last notification time for each task:
class NotificationTracker {
constructor() {
this.history = new Map();
}
wasRecentlyNotified(taskId) {
const lastNotified = this.history.get(taskId);
if (!lastNotified) return false;
const now = Date.now();
const tenMinutes = 10 * 60 * 1000;
return (now - lastNotified) < tenMinutes;
}
recordNotification(taskId) {
this.history.set(taskId, Date.now());
}
clearTask(taskId) {
this.history.delete(taskId);
}
cleanup() {
const now = Date.now();
const tenMinutes = 10 * 60 * 1000;
for (const [taskId, timestamp] of this.history.entries()) {
if (now - timestamp > tenMinutes) {
this.history.delete(taskId);
}
}
}
}
const tracker = new NotificationTracker();
Tracking Lifecycle
function checkAndTriggerNotifications(tasks, onNotification) {
tracker.cleanup();
tasks.forEach(task => {
if (isEligibleForNotification(task, tracker.history)) {
const notification = createNotification(task);
tracker.recordNotification(task.id);
onNotification(notification);
}
});
}
Notification Object Structure
function createNotification(task) {
const now = new Date();
const dueDate = new Date(task.dueDate);
const hoursUntilDue = Math.floor((dueDate - now) / (1000 * 60 * 60));
const minutesUntilDue = Math.ceil((dueDate - now) / (1000 * 60));
return {
taskId: task.id,
title: task.title,
dueTime: task.dueDate,
relativeTime: formatTimeUntilDue(minutesUntilDue),
timestamp: now.toISOString(),
priority: 'VERY IMPORTANT',
message: `Task "${task.title}" is due in ${formatTimeUntilDue(minutesUntilDue)}`
};
}
function formatTimeUntilDue(minutes) {
if (minutes < 60) {
return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (remainingMinutes === 0) {
return `${hours} hour${hours !== 1 ? 's' : ''}`;
}
return `${hours} hour${hours !== 1 ? 's' : ''} and ${remainingMinutes} minute${remainingMinutes !== 1 ? 's' : ''}`;
}
Notification Object Example
{
taskId: "task_123",
title: "Submit quarterly report",
dueTime: "2025-12-16T18:00:00Z",
relativeTime: "3 hours and 45 minutes",
timestamp: "2025-12-16T14:15:00Z",
priority: "VERY IMPORTANT",
message: "Task \"Submit quarterly report\" is due in 3 hours and 45 minutes"
}
Clear Tracking Conditions
Clear notification tracking when:
1. Task Completed
function handleTaskStatusChange(task, newStatus) {
if (newStatus === 'Complete') {
tracker.clearTask(task.id);
}
}
2. Due Time Passed
function checkAndClearExpiredTasks(tasks) {
const now = new Date();
tasks.forEach(task => {
if (task.dueDate) {
const dueDate = new Date(task.dueDate);
if (dueDate < now) {
tracker.clearTask(task.id);
}
}
});
}
3. Priority Changed
function handlePriorityChange(task, newPriority) {
if (newPriority !== 'VERY IMPORTANT') {
tracker.clearTask(task.id);
}
}
4. Task Deleted
function handleTaskDelete(taskId) {
tracker.clearTask(taskId);
}
Complete Implementation
class NotificationTriggerService {
constructor() {
this.tracker = new NotificationTracker();
this.interval = null;
this.listeners = [];
}
start(getTasks) {
this.check(getTasks());
this.interval = setInterval(() => {
this.check(getTasks());
}, 10 * 60 * 1000);
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
check(tasks) {
this.tracker.cleanup();
tasks.forEach(task => {
if (this.isEligible(task)) {
const notification = createNotification(task);
this.tracker.recordNotification(task.id);
this.notifyListeners(notification);
}
});
}
isEligible(task) {
if (task.priority !== 'VERY IMPORTANT') return false;
if (task.status === 'Complete') return false;
if (!task.dueDate) return false;
const now = new Date();
const dueDate = new Date(task.dueDate);
const hoursUntilDue = (dueDate - now) / (1000 * 60 * 60);
if (hoursUntilDue <= 0 || hoursUntilDue > 6) return false;
if (this.tracker.wasRecentlyNotified(task.id)) return false;
return true;
}
onNotification(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(cb => cb !== callback);
};
}
notifyListeners(notification) {
this.listeners.forEach(callback => callback(notification));
}
clearTask(taskId) {
this.tracker.clearTask(taskId);
}
}
export const notificationTrigger = new NotificationTriggerService();
React Hook Usage
function useNotificationTrigger(tasks) {
useEffect(() => {
notificationTrigger.start(() => tasks);
const unsubscribe = notificationTrigger.onNotification((notification) => {
console.log('New notification:', notification);
});
return () => {
notificationTrigger.stop();
unsubscribe();
};
}, [tasks]);
}
Testing Examples
const task1 = {
id: '1',
title: 'Submit Report',
priority: 'VERY IMPORTANT',
status: 'In Progress',
dueDate: new Date(Date.now() + 4 * 60 * 60 * 1000)
};
isEligibleForNotification(task1, {});
const task2 = { ...task1, priority: 'HIGH' };
isEligibleForNotification(task2, {});
const task3 = { ...task1, status: 'Complete' };
isEligibleForNotification(task3, {});
const task4 = { ...task1, dueDate: null };
isEligibleForNotification(task4, {});
const history = { '1': Date.now() - 5 * 60 * 1000 };
isEligibleForNotification(task1, history);
const task6 = { ...task1, dueDate: new Date(Date.now() - 1 * 60 * 60 * 1000) };
isEligibleForNotification(task6, {});
const task7 = { ...task1, dueDate: new Date(Date.now() + 8 * 60 * 60 * 1000) };
isEligibleForNotification(task7, {});
Integration Points
This skill integrates with:
- Temporal Evaluation Skill: Uses urgency detection
- Priority Classification Skill: Depends on VERY IMPORTANT classification
- Notification Persistence Skill: Notifications are persisted after triggering
- Notification UI Skill: Triggered notifications are displayed
- Task Intelligence Agent: Coordinates notification logic
Performance Considerations
- Check runs every 10 minutes (not continuously)
- In-memory tracking (fast lookups)
- Cleanup prevents memory growth
- Minimal CPU usage (< 5ms per check for 100 tasks)
Edge Cases
App Closed Then Reopened
When app reopens, check immediately:
notificationTrigger.start(getTasks);
Multiple Windows/Tabs
Each tab runs independently:
Clock Changes
System clock changes handled automatically: