diff --git a/backend/routes/tasks.js b/backend/routes/tasks.js index 569f0e3..76c3c02 100644 --- a/backend/routes/tasks.js +++ b/backend/routes/tasks.js @@ -192,9 +192,11 @@ async function serializeTask(task, userTimezone = 'UTC', options = {}) { // For recurring task templates, show recurrence type instead of original name // unless skipDisplayNameTransform option is true + // Skip this transformation for 'today' type queries to show actual task names let displayName = taskJson.name; if ( !options.skipDisplayNameTransform && + !options.preserveOriginalName && taskJson.recurrence_type && taskJson.recurrence_type !== 'none' && !taskJson.recurring_parent_id @@ -516,6 +518,8 @@ async function filterTasksByParams(params, userId, userTimezone) { // Filter by type switch (params.type) { case 'today': + // Ensure we exclude recurring task instances for today view + whereClause.recurring_parent_id = null; whereClause.status = { [Op.notIn]: [ Task.STATUS.DONE, @@ -1162,10 +1166,18 @@ router.get('/tasks', async (req, res) => { req.currentUser.timezone ); + // Preserve original names for recurring tasks in 'today' view for productivity assistant + const serializationOptions = + req.query.type === 'today' ? { preserveOriginalName: true } : {}; + const response = { tasks: await Promise.all( tasks.map((task) => - serializeTask(task, req.currentUser.timezone) + serializeTask( + task, + req.currentUser.timezone, + serializationOptions + ) ) ), metrics: { @@ -1174,29 +1186,46 @@ router.get('/tasks', async (req, res) => { tasks_in_progress_count: metrics.tasks_in_progress_count, tasks_in_progress: await Promise.all( metrics.tasks_in_progress.map((task) => - serializeTask(task, req.currentUser.timezone) + serializeTask( + task, + req.currentUser.timezone, + serializationOptions + ) ) ), tasks_due_today: await Promise.all( metrics.tasks_due_today.map((task) => - serializeTask(task, req.currentUser.timezone) + serializeTask( + task, + req.currentUser.timezone, + serializationOptions + ) ) ), today_plan_tasks: await Promise.all( metrics.today_plan_tasks.map((task) => - serializeTask(task, req.currentUser.timezone) + serializeTask( + task, + req.currentUser.timezone, + serializationOptions + ) ) ), suggested_tasks: await Promise.all( metrics.suggested_tasks.map((task) => - serializeTask(task, req.currentUser.timezone) + serializeTask( + task, + req.currentUser.timezone, + serializationOptions + ) ) ), tasks_completed_today: await Promise.all( metrics.tasks_completed_today.map(async (task) => { const serialized = await serializeTask( task, - req.currentUser.timezone + req.currentUser.timezone, + serializationOptions ); return { ...serialized, diff --git a/backend/tests/integration/global-recurring-filter.test.js b/backend/tests/integration/global-recurring-filter.test.js index ca9e781..625d1da 100644 --- a/backend/tests/integration/global-recurring-filter.test.js +++ b/backend/tests/integration/global-recurring-filter.test.js @@ -124,7 +124,7 @@ describe('Global Recurring Task Instance Filtering', () => { expect(response.body.tasks).toBeDefined(); const taskNames = response.body.tasks.map((t) => t.name); - expect(taskNames).toContain('Daily'); + expect(taskNames).toContain('Daily Workout Template'); // Now preserves original name for type=today expect(taskNames).toContain('Regular Task'); expect(taskNames).not.toContain('Daily Workout - Aug 23'); expect(taskNames).not.toContain('Daily Workout - Aug 24'); diff --git a/backend/tests/integration/recurring-tasks.test.js b/backend/tests/integration/recurring-tasks.test.js index 3f25854..b9d8f7e 100644 --- a/backend/tests/integration/recurring-tasks.test.js +++ b/backend/tests/integration/recurring-tasks.test.js @@ -605,4 +605,90 @@ describe('Recurring Tasks API', () => { expect(parentStillExists).not.toBeNull(); }); }); + + describe('GET /api/tasks?type=today - Today view filtering and naming', () => { + let parentTask, childTask, regularTask; + + beforeEach(async () => { + // Create a regular non-recurring task + regularTask = await Task.create({ + name: 'Regular Task', + recurrence_type: 'none', + user_id: user.id, + status: 0, + today: true, + }); + + // Create a recurring parent task (template) + parentTask = await Task.create({ + name: 'Take vitamins', + recurrence_type: 'daily', + recurrence_interval: 1, + user_id: user.id, + status: 0, + today: true, + }); + + // Create a recurring child task (instance) + childTask = await Task.create({ + name: 'Take vitamins', + recurrence_type: 'none', + recurring_parent_id: parentTask.id, + user_id: user.id, + status: 0, + today: true, + due_date: new Date(), + }); + }); + + it('should exclude recurring task instances from type=today API response', async () => { + const response = await agent.get('/api/tasks?type=today'); + + expect(response.status).toBe(200); + expect(response.body.tasks).toBeDefined(); + + // Should only return regular task + recurring template, not the instance + expect(response.body.tasks.length).toBe(2); + + const taskIds = response.body.tasks.map((t) => t.id); + expect(taskIds).toContain(regularTask.id); + expect(taskIds).toContain(parentTask.id); + expect(taskIds).not.toContain(childTask.id); // Instance should be filtered out + }); + + it('should preserve original names for recurring tasks in type=today API response', async () => { + const response = await agent.get('/api/tasks?type=today'); + + expect(response.status).toBe(200); + expect(response.body.tasks).toBeDefined(); + + // Find the recurring task in the response + const recurringTask = response.body.tasks.find( + (t) => t.id === parentTask.id + ); + expect(recurringTask).toBeDefined(); + + // Should show original name, not "Daily" + expect(recurringTask.name).toBe('Take vitamins'); + expect(recurringTask.original_name).toBe('Take vitamins'); + expect(recurringTask.name).not.toBe('Daily'); + }); + + it('should show generic names for non-today API calls (backward compatibility)', async () => { + const response = await agent.get('/api/tasks'); // No type=today + + expect(response.status).toBe(200); + expect(response.body.tasks).toBeDefined(); + + // Find the recurring task in the response + const recurringTask = response.body.tasks.find( + (t) => t.id === parentTask.id + ); + expect(recurringTask).toBeDefined(); + + // Should show generic recurrence name for backward compatibility + expect(recurringTask.name).toBe('Daily'); + expect(recurringTask.original_name).toBe('Take vitamins'); + }); + }); });