| name | task-filter |
| description | Provides comprehensive task filtering across three dimensions (Status, Priority, Due Date) with cumulative AND logic, localStorage persistence, and removable filter chips UI. Supports nine total filter options across the three categories. |
Task Filter Skill
Overview
The task filter skill enables users to narrow down task lists using multiple filter criteria applied simultaneously. Filters are cumulative (AND logic), persist across sessions, and display as removable chips for easy management.
When to Apply
Apply this skill:
- When user selects a filter option from filter controls
- When displaying the current active filters as chips
- When user removes a filter chip
- When loading the app (to restore saved filter state)
- When combining filters with search results
- When filter state needs to persist to localStorage
Filter Types and Options
This skill defines three filter types with specific options:
1. Status Filter
Four options to filter by task completion status:
- Not Started - Tasks that haven't been begun
- In Progress - Tasks currently being worked on
- Complete - Finished tasks
- All - No status filtering (shows all statuses)
const STATUS_OPTIONS = {
NOT_STARTED: 'Not Started',
IN_PROGRESS: 'In Progress',
COMPLETE: 'Complete',
ALL: 'All'
};
2. Priority Filter
Five options to filter by priority level:
- VERY IMPORTANT - Highest priority tasks only
- HIGH - High priority tasks only
- MEDIUM - Medium priority tasks only
- LOW - Low priority tasks only
- All - No priority filtering (shows all priorities)
const PRIORITY_OPTIONS = {
VERY_IMPORTANT: 'VERY IMPORTANT',
HIGH: 'HIGH',
MEDIUM: 'MEDIUM',
LOW: 'LOW',
ALL: 'All'
};
3. Due Date Filter
Five options to filter by temporal urgency:
- Overdue - Tasks with past due dates
- Today - Tasks due today (current date)
- This Week - Tasks due within current calendar week
- This Month - Tasks due within current calendar month
- All - No due date filtering (shows all due dates)
const DUE_DATE_OPTIONS = {
OVERDUE: 'Overdue',
TODAY: 'Today',
THIS_WEEK: 'This Week',
THIS_MONTH: 'This Month',
ALL: 'All'
};
Filter Combination Logic
Cumulative AND Logic
Multiple filters combine using AND logic - a task must match ALL active filters:
function applyFilters(tasks, filters) {
return tasks.filter(task => {
if (filters.status !== 'All' && task.status !== filters.status) {
return false;
}
if (filters.priority !== 'All' && task.priority !== filters.priority) {
return false;
}
if (filters.dueDate !== 'All' && !matchesDueDateFilter(task, filters.dueDate)) {
return false;
}
return true;
});
}
AND Logic Examples
"All" Option Behavior
Setting a filter to "All" clears that specific filter dimension:
Due Date Filter Logic
Date Comparison Implementation
function matchesDueDateFilter(task, dueDateFilter) {
if (!task.dueDate) return false;
const taskDue = new Date(task.dueDate);
const now = new Date();
now.setHours(0, 0, 0, 0);
switch (dueDateFilter) {
case 'Overdue':
return taskDue < now;
case 'Today':
const today = new Date(now);
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
return taskDue >= today && taskDue < tomorrow;
case 'This Week':
const weekStart = getStartOfWeek(now);
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekEnd.getDate() + 7);
return taskDue >= weekStart && taskDue < weekEnd;
case 'This Month':
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 1);
return taskDue >= monthStart && taskDue < monthEnd;
case 'All':
return true;
default:
return true;
}
}
function getStartOfWeek(date) {
const day = date.getDay();
const diff = date.getDate() - day;
const startOfWeek = new Date(date);
startOfWeek.setDate(diff);
startOfWeek.setHours(0, 0, 0, 0);
return startOfWeek;
}
Due Date Examples
matchesDueDateFilter(task, 'Overdue');
matchesDueDateFilter(task, 'Today');
matchesDueDateFilter(task, 'Overdue');
matchesDueDateFilter(task, 'Today');
matchesDueDateFilter(task, 'This Week');
matchesDueDateFilter(task, 'This Week');
matchesDueDateFilter(task, 'This Month');
matchesDueDateFilter(task, 'This Week');
matchesDueDateFilter(task, 'This Month');
matchesDueDateFilter(task, 'Overdue');
matchesDueDateFilter(task, 'All');
Filter Persistence
localStorage Storage
Filter state persists across browser sessions using localStorage:
const FILTER_STORAGE_KEY = 'todo-app-filters';
function saveFilters(filters) {
try {
localStorage.setItem(FILTER_STORAGE_KEY, JSON.stringify(filters));
} catch (error) {
console.error('Failed to save filters:', error);
}
}
function loadFilters() {
try {
const saved = localStorage.getItem(FILTER_STORAGE_KEY);
if (saved) {
return JSON.parse(saved);
}
} catch (error) {
console.error('Failed to load filters:', error);
}
return {
status: 'All',
priority: 'All',
dueDate: 'All'
};
}
function clearFilters() {
try {
localStorage.removeItem(FILTER_STORAGE_KEY);
} catch (error) {
console.error('Failed to clear filters:', error);
}
}
Persistence Behavior
- On filter change: Save immediately to localStorage
- On app load: Restore filters from localStorage
- On clear all: Remove from localStorage and reset to defaults
- On logout: Optionally clear filters (user preference)
Filter Chips UI
Active Filter Display
Active filters display as removable chips above the task list:
function FilterChips({ filters, onRemoveFilter }) {
const activeFilters = [];
if (filters.status !== 'All') {
activeFilters.push({ type: 'status', value: filters.status });
}
if (filters.priority !== 'All') {
activeFilters.push({ type: 'priority', value: filters.priority });
}
if (filters.dueDate !== 'All') {
activeFilters.push({ type: 'dueDate', value: filters.dueDate });
}
if (activeFilters.length === 0) {
return null;
}
return (
<div className="filter-chips">
<span className="filter-label">Active filters:</span>
{activeFilters.map(filter => (
<div key={filter.type} className="filter-chip">
<span className="filter-value">{filter.value}</span>
<button
className="remove-btn"
onClick={() => onRemoveFilter(filter.type)}
aria-label={`Remove ${filter.value} filter`}
>
×
</button>
</div>
))}
{activeFilters.length > 1 && (
<button
className="clear-all-btn"
onClick={() => onRemoveFilter('all')}
>
Clear all
</button>
)}
</div>
);
}
Filter Chip Styling
.filter-chips {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 0;
flex-wrap: wrap;
}
.filter-label {
font-size: 12px;
font-weight: 600;
color: #6B7280;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.filter-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background-color: #EDE9FE;
color: #6B21A8;
border: 1px solid #C4B5FD;
border-radius: 16px;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
}
.filter-chip:hover {
background-color: #DDD6FE;
border-color: #A78BFA;
}
.filter-value {
user-select: none;
}
.remove-btn {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
background: transparent;
border: none;
font-size: 18px;
line-height: 1;
color: #6B21A8;
cursor: pointer;
padding: 0;
transition: color 0.2s ease;
}
.remove-btn:hover {
color: #DC2626;
}
.clear-all-btn {
padding: 6px 12px;
background-color: transparent;
color: #DC2626;
border: 1px solid #FCA5A5;
border-radius: 16px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.clear-all-btn:hover {
background-color: #FEE2E2;
border-color: #DC2626;
}
Chip Removal Behavior
Clicking a chip's remove button clears that specific filter:
function handleRemoveFilter(filterType) {
if (filterType === 'all') {
setFilters({
status: 'All',
priority: 'All',
dueDate: 'All'
});
} else {
setFilters(prev => ({
...prev,
[filterType]: 'All'
}));
}
}
Complete Filter Implementation
function TaskFilterSystem({ tasks, onFilteredTasks }) {
const [filters, setFilters] = useState(() => loadFilters());
useEffect(() => {
saveFilters(filters);
}, [filters]);
const filteredTasks = useMemo(() => {
return applyFilters(tasks, filters);
}, [tasks, filters]);
useEffect(() => {
onFilteredTasks(filteredTasks);
}, [filteredTasks, onFilteredTasks]);
const handleFilterChange = (filterType, value) => {
setFilters(prev => ({
...prev,
[filterType]: value
}));
};
const handleRemoveFilter = (filterType) => {
if (filterType === 'all') {
setFilters({
status: 'All',
priority: 'All',
dueDate: 'All'
});
} else {
setFilters(prev => ({
...prev,
[filterType]: 'All'
}));
}
};
return (
<div className="task-filter-system">
<FilterControls filters={filters} onChange={handleFilterChange} />
<FilterChips filters={filters} onRemoveFilter={handleRemoveFilter} />
<TaskList tasks={filteredTasks} />
</div>
);
}
Filter Controls UI
function FilterControls({ filters, onChange }) {
return (
<div className="filter-controls">
{/* Status Filter */}
<div className="filter-group">
<label>Status</label>
<select
value={filters.status}
onChange={(e) => onChange('status', e.target.value)}
className="filter-select"
>
<option value="All">All</option>
<option value="Not Started">Not Started</option>
<option value="In Progress">In Progress</option>
<option value="Complete">Complete</option>
</select>
</div>
{/* Priority Filter */}
<div className="filter-group">
<label>Priority</label>
<select
value={filters.priority}
onChange={(e) => onChange('priority', e.target.value)}
className="filter-select"
>
<option value="All">All</option>
<option value="VERY IMPORTANT">VERY IMPORTANT</option>
<option value="HIGH">HIGH</option>
<option value="MEDIUM">MEDIUM</option>
<option value="LOW">LOW</option>
</select>
</div>
{/* Due Date Filter */}
<div className="filter-group">
<label>Due Date</label>
<select
value={filters.dueDate}
onChange={(e) => onChange('dueDate', e.target.value)}
className="filter-select"
>
<option value="All">All</option>
<option value="Overdue">Overdue</option>
<option value="Today">Today</option>
<option value="This Week">This Week</option>
<option value="This Month">This Month</option>
</select>
</div>
</div>
);
}
Filter Controls Styling
.filter-controls {
display: flex;
gap: 16px;
padding: 16px 0;
flex-wrap: wrap;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 160px;
}
.filter-group label {
font-size: 12px;
font-weight: 600;
color: #374151;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.filter-select {
padding: 8px 12px;
border: 1px solid #D1D5DB;
border-radius: 6px;
font-size: 14px;
color: #374151;
background-color: white;
cursor: pointer;
transition: all 0.2s ease;
}
.filter-select:hover {
border-color: #9CA3AF;
}
.filter-select:focus {
outline: none;
border-color: #8B5CF6;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}
Testing Examples
Test Case 1: Single Status Filter
const tasks = [
{ id: 1, status: 'Not Started', priority: 'HIGH', dueDate: null },
{ id: 2, status: 'In Progress', priority: 'MEDIUM', dueDate: null },
{ id: 3, status: 'Complete', priority: 'LOW', dueDate: null }
];
const filters = { status: 'In Progress', priority: 'All', dueDate: 'All' };
const result = applyFilters(tasks, filters);
Test Case 2: Multiple Filters (AND Logic)
const tasks = [
{ id: 1, status: 'In Progress', priority: 'HIGH', dueDate: '2025-12-16' },
{ id: 2, status: 'In Progress', priority: 'MEDIUM', dueDate: '2025-12-16' },
{ id: 3, status: 'Not Started', priority: 'HIGH', dueDate: '2025-12-16' }
];
const filters = { status: 'In Progress', priority: 'HIGH', dueDate: 'Today' };
const result = applyFilters(tasks, filters);
Test Case 3: Overdue Filter
const now = new Date('2025-12-16');
const tasks = [
{ id: 1, status: 'Not Started', priority: 'HIGH', dueDate: '2025-12-15' },
{ id: 2, status: 'In Progress', priority: 'MEDIUM', dueDate: '2025-12-17' }
];
const filters = { status: 'All', priority: 'All', dueDate: 'Overdue' };
const result = applyFilters(tasks, filters);
Test Case 4: All Filters Set to "All"
const tasks = [
{ id: 1, status: 'Not Started', priority: 'HIGH', dueDate: null },
{ id: 2, status: 'In Progress', priority: 'MEDIUM', dueDate: null },
{ id: 3, status: 'Complete', priority: 'LOW', dueDate: null }
];
const filters = { status: 'All', priority: 'All', dueDate: 'All' };
const result = applyFilters(tasks, filters);
Integration Points
This skill integrates with:
- Task Search Skill: Filters applied after search (search + filter)
- Task Sorting Skill: Filtered results can be sorted
- Priority Classification Skill: Filters by priority levels
- Temporal Evaluation Skill: Provides due date filtering logic
- Task Organization Agent: Uses filtering for task management
Performance Considerations
- Filter application should complete in < 100ms for 1000 tasks
- localStorage operations are wrapped in try-catch for error handling
- useMemo prevents unnecessary re-filtering
- Filter state changes are batched with React state updates
Edge Cases
Tasks Without Due Dates
Tasks with no due date are excluded from all due date filters except "All":
matchesDueDateFilter(task, 'Overdue');
matchesDueDateFilter(task, 'Today');
matchesDueDateFilter(task, 'This Week');
matchesDueDateFilter(task, 'All');
Week Boundaries
Week calculation uses Sunday as the start of the week (configurable):
function getStartOfWeek(date, weekStartDay = 0) {
const day = date.getDay();
const diff = (day < weekStartDay ? 7 : 0) + day - weekStartDay;
const startOfWeek = new Date(date);
startOfWeek.setDate(date.getDate() - diff);
startOfWeek.setHours(0, 0, 0, 0);
return startOfWeek;
}
Accessibility
- Filter dropdowns have descriptive labels
- Remove buttons have aria-labels
- Filter chips are keyboard navigable
- Clear all button has clear visual affordance
- Active filters announced to screen readers