From 559c972b0304a19a3aaa5ea699da7aed68623d66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:26:44 +0000 Subject: [PATCH 1/6] Initial plan From ba922d6be2651d1e614cb17e0e3049a0439a6e52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:29:58 +0000 Subject: [PATCH 2/6] feat: add static task categories with medium default --- backend/controllers/tasksController.js | 9 ++++-- frontend/index.html | 5 +++ frontend/script.js | 11 +++++-- frontend/style.css | 42 +++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/backend/controllers/tasksController.js b/backend/controllers/tasksController.js index 745738d..c1db0c9 100644 --- a/backend/controllers/tasksController.js +++ b/backend/controllers/tasksController.js @@ -3,6 +3,7 @@ const path = require('path'); const { v4: uuidv4 } = require('uuid'); const filePath = path.join(__dirname, '../data/tasks.json'); +const VALID_CATEGORIES = ['High', 'Medium', 'Low']; function readTasks() { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); @@ -18,8 +19,9 @@ exports.getAllTasks = (req, res) => { exports.createTask = (req, res) => { const tasks = readTasks(); - const { title, completed = false } = req.body; - const newTask = { id: uuidv4(), title, completed }; + const { title, completed = false, category = 'Medium' } = req.body; + const normalizedCategory = VALID_CATEGORIES.includes(category) ? category : 'Medium'; + const newTask = { id: uuidv4(), title, completed, category: normalizedCategory }; tasks.push(newTask); writeTasks(tasks); res.status(201).json(newTask); @@ -32,6 +34,9 @@ exports.updateTask = (req, res) => { task.title = req.body.title ?? task.title; task.completed = req.body.completed ?? task.completed; + if (req.body.category !== undefined) { + task.category = VALID_CATEGORIES.includes(req.body.category) ? req.body.category : 'Medium'; + } writeTasks(tasks); res.json(task); }; diff --git a/frontend/index.html b/frontend/index.html index 915426f..4291634 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -14,6 +14,11 @@

Task List

+
diff --git a/frontend/script.js b/frontend/script.js index a519ccf..9f5c2e5 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -2,6 +2,7 @@ const API_URL = 'http://localhost:3000/api/tasks'; const taskList = document.getElementById('taskList'); const taskForm = document.getElementById('taskForm'); const taskInput = document.getElementById('taskInput'); +const taskCategory = document.getElementById('taskCategory'); const themeToggle = document.getElementById('themeToggle'); // Theme functionality @@ -35,9 +36,13 @@ async function fetchTasks() { } function addTaskToDOM(task) { + const category = task.category || 'Medium'; const li = document.createElement('li'); li.innerHTML = ` - ${task.title} +
+ ${task.title} + ${category} +
@@ -49,14 +54,16 @@ function addTaskToDOM(task) { taskForm.addEventListener('submit', async e => { e.preventDefault(); const title = taskInput.value; + const category = taskCategory.value || 'Medium'; const res = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }) + body: JSON.stringify({ title, category }) }); const task = await res.json(); addTaskToDOM(task); taskInput.value = ''; + taskCategory.value = 'Medium'; }); async function toggleComplete(id, completed) { diff --git a/frontend/style.css b/frontend/style.css index d351594..5c1b212 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -100,9 +100,22 @@ form { outline: none; transition: box-shadow 0.2s; } +#taskCategory { + padding: 0.75em 1em; + border: none; + border-radius: 4px; + background: var(--input-bg); + color: var(--text-color); + box-shadow: 0 1px 2px var(--input-shadow); + font-size: 1em; + outline: none; +} #taskInput:focus { box-shadow: 0 2px 8px var(--input-focus-shadow); } +#taskCategory:focus { + box-shadow: 0 2px 8px var(--input-focus-shadow); +} button[type="submit"] { background: var(--primary-color); color: #fff; @@ -138,12 +151,39 @@ li { li:hover { box-shadow: 0 4px 16px var(--task-hover-shadow); } -li span { +.task-title { font-size: 1.05em; flex: 1; color: var(--text-color); transition: color 0.2s; } +.task-content { + display: flex; + flex-direction: column; + gap: 4px; + flex: 1; +} +.category-badge { + display: inline-block; + width: fit-content; + padding: 0.15em 0.6em; + border-radius: 999px; + font-size: 0.75em; + font-weight: 500; + line-height: 1.4; +} +.category-high { + background: #ffebee; + color: #c62828; +} +.category-medium { + background: #fff8e1; + color: #ef6c00; +} +.category-low { + background: #e8f5e9; + color: #2e7d32; +} .completed { text-decoration: line-through; color: var(--completed-color); From e9f082b16ba672e03360055baa5889e72380bab2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:31:45 +0000 Subject: [PATCH 3/6] fix: sanitize task rendering and validate title input --- backend/controllers/tasksController.js | 5 +++- frontend/script.js | 39 +++++++++++++++++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/backend/controllers/tasksController.js b/backend/controllers/tasksController.js index c1db0c9..42a02fe 100644 --- a/backend/controllers/tasksController.js +++ b/backend/controllers/tasksController.js @@ -20,8 +20,11 @@ exports.getAllTasks = (req, res) => { exports.createTask = (req, res) => { const tasks = readTasks(); const { title, completed = false, category = 'Medium' } = req.body; + if (typeof title !== 'string' || title.trim() === '') { + return res.status(400).json({ message: 'Title is required' }); + } const normalizedCategory = VALID_CATEGORIES.includes(category) ? category : 'Medium'; - const newTask = { id: uuidv4(), title, completed, category: normalizedCategory }; + const newTask = { id: uuidv4(), title: title.trim(), completed, category: normalizedCategory }; tasks.push(newTask); writeTasks(tasks); res.status(201).json(newTask); diff --git a/frontend/script.js b/frontend/script.js index 9f5c2e5..c9b4e26 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -38,16 +38,35 @@ async function fetchTasks() { function addTaskToDOM(task) { const category = task.category || 'Medium'; const li = document.createElement('li'); - li.innerHTML = ` -
- ${task.title} - ${category} -
-
- - -
- `; + const taskContent = document.createElement('div'); + taskContent.className = 'task-content'; + + const title = document.createElement('span'); + title.className = `task-title ${task.completed ? 'completed' : ''}`; + title.textContent = task.title; + + const badge = document.createElement('span'); + badge.className = `category-badge category-${category.toLowerCase()}`; + badge.textContent = category; + + const actions = document.createElement('div'); + + const completeButton = document.createElement('button'); + completeButton.textContent = '✓'; + completeButton.setAttribute('aria-label', 'Mark task as complete'); + completeButton.addEventListener('click', () => toggleComplete(task.id, !task.completed)); + + const deleteButton = document.createElement('button'); + deleteButton.textContent = '✕'; + deleteButton.setAttribute('aria-label', 'Delete task'); + deleteButton.addEventListener('click', () => deleteTask(task.id)); + + taskContent.appendChild(title); + taskContent.appendChild(badge); + actions.appendChild(completeButton); + actions.appendChild(deleteButton); + li.appendChild(taskContent); + li.appendChild(actions); taskList.appendChild(li); } From 2c75e888acea7fc36958d4351ed813e8d0d9e17d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:33:39 +0000 Subject: [PATCH 4/6] fix: address accessibility and maintainability feedback --- backend/controllers/tasksController.js | 10 +++++++--- frontend/script.js | 5 +++-- frontend/style.css | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/backend/controllers/tasksController.js b/backend/controllers/tasksController.js index 42a02fe..ed2837a 100644 --- a/backend/controllers/tasksController.js +++ b/backend/controllers/tasksController.js @@ -5,6 +5,10 @@ const { v4: uuidv4 } = require('uuid'); const filePath = path.join(__dirname, '../data/tasks.json'); const VALID_CATEGORIES = ['High', 'Medium', 'Low']; +function normalizeCategory(category) { + return VALID_CATEGORIES.includes(category) ? category : 'Medium'; +} + function readTasks() { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } @@ -21,9 +25,9 @@ exports.createTask = (req, res) => { const tasks = readTasks(); const { title, completed = false, category = 'Medium' } = req.body; if (typeof title !== 'string' || title.trim() === '') { - return res.status(400).json({ message: 'Title is required' }); + return res.status(400).json({ message: 'Title is required and cannot be empty' }); } - const normalizedCategory = VALID_CATEGORIES.includes(category) ? category : 'Medium'; + const normalizedCategory = normalizeCategory(category); const newTask = { id: uuidv4(), title: title.trim(), completed, category: normalizedCategory }; tasks.push(newTask); writeTasks(tasks); @@ -38,7 +42,7 @@ exports.updateTask = (req, res) => { task.title = req.body.title ?? task.title; task.completed = req.body.completed ?? task.completed; if (req.body.category !== undefined) { - task.category = VALID_CATEGORIES.includes(req.body.category) ? req.body.category : 'Medium'; + task.category = normalizeCategory(req.body.category); } writeTasks(tasks); res.json(task); diff --git a/frontend/script.js b/frontend/script.js index c9b4e26..f957e61 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -53,7 +53,7 @@ function addTaskToDOM(task) { const completeButton = document.createElement('button'); completeButton.textContent = '✓'; - completeButton.setAttribute('aria-label', 'Mark task as complete'); + completeButton.setAttribute('aria-label', task.completed ? 'Mark task as incomplete' : 'Mark task as complete'); completeButton.addEventListener('click', () => toggleComplete(task.id, !task.completed)); const deleteButton = document.createElement('button'); @@ -72,7 +72,8 @@ function addTaskToDOM(task) { taskForm.addEventListener('submit', async e => { e.preventDefault(); - const title = taskInput.value; + const title = taskInput.value.trim(); + if (!title) return; const category = taskCategory.value || 'Medium'; const res = await fetch(API_URL, { method: 'POST', diff --git a/frontend/style.css b/frontend/style.css index 5c1b212..dcf3498 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -174,15 +174,15 @@ li:hover { } .category-high { background: #ffebee; - color: #c62828; + color: #8b0000; } .category-medium { background: #fff8e1; - color: #ef6c00; + color: #8a3b00; } .category-low { background: #e8f5e9; - color: #2e7d32; + color: #1b5e20; } .completed { text-decoration: line-through; From d8c507a7e7fb7df4a0881fb7d84d3202e40f09d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:34:37 +0000 Subject: [PATCH 5/6] refactor: reduce frontend duplication for categories --- frontend/script.js | 7 ++++--- frontend/style.css | 13 ++----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/frontend/script.js b/frontend/script.js index f957e61..e7cc9e2 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -1,4 +1,5 @@ const API_URL = 'http://localhost:3000/api/tasks'; +const DEFAULT_CATEGORY = 'Medium'; const taskList = document.getElementById('taskList'); const taskForm = document.getElementById('taskForm'); const taskInput = document.getElementById('taskInput'); @@ -36,7 +37,7 @@ async function fetchTasks() { } function addTaskToDOM(task) { - const category = task.category || 'Medium'; + const category = task.category || DEFAULT_CATEGORY; const li = document.createElement('li'); const taskContent = document.createElement('div'); taskContent.className = 'task-content'; @@ -74,7 +75,7 @@ taskForm.addEventListener('submit', async e => { e.preventDefault(); const title = taskInput.value.trim(); if (!title) return; - const category = taskCategory.value || 'Medium'; + const category = taskCategory.value || DEFAULT_CATEGORY; const res = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -83,7 +84,7 @@ taskForm.addEventListener('submit', async e => { const task = await res.json(); addTaskToDOM(task); taskInput.value = ''; - taskCategory.value = 'Medium'; + taskCategory.value = DEFAULT_CATEGORY; }); async function toggleComplete(id, completed) { diff --git a/frontend/style.css b/frontend/style.css index dcf3498..8f6eeec 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -88,7 +88,8 @@ form { gap: 12px; margin-bottom: 1.5em; } -#taskInput { +#taskInput, +#taskCategory { flex: 1; padding: 0.75em 1em; border: none; @@ -100,16 +101,6 @@ form { outline: none; transition: box-shadow 0.2s; } -#taskCategory { - padding: 0.75em 1em; - border: none; - border-radius: 4px; - background: var(--input-bg); - color: var(--text-color); - box-shadow: 0 1px 2px var(--input-shadow); - font-size: 1em; - outline: none; -} #taskInput:focus { box-shadow: 0 2px 8px var(--input-focus-shadow); } From 6fc8190266b3626e22783a851a29f8d3b10209aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 20:36:01 +0000 Subject: [PATCH 6/6] fix: validate and trim updated task titles --- backend/controllers/tasksController.js | 7 ++++++- frontend/style.css | 4 +--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/controllers/tasksController.js b/backend/controllers/tasksController.js index ed2837a..2f1e314 100644 --- a/backend/controllers/tasksController.js +++ b/backend/controllers/tasksController.js @@ -39,7 +39,12 @@ exports.updateTask = (req, res) => { const task = tasks.find(t => t.id === req.params.id); if (!task) return res.status(404).json({ message: 'Task not found' }); - task.title = req.body.title ?? task.title; + if (req.body.title !== undefined) { + if (typeof req.body.title !== 'string' || req.body.title.trim() === '') { + return res.status(400).json({ message: 'Title is required and cannot be empty' }); + } + task.title = req.body.title.trim(); + } task.completed = req.body.completed ?? task.completed; if (req.body.category !== undefined) { task.category = normalizeCategory(req.body.category); diff --git a/frontend/style.css b/frontend/style.css index 8f6eeec..65e6af1 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -101,9 +101,7 @@ form { outline: none; transition: box-shadow 0.2s; } -#taskInput:focus { - box-shadow: 0 2px 8px var(--input-focus-shadow); -} +#taskInput:focus, #taskCategory:focus { box-shadow: 0 2px 8px var(--input-focus-shadow); }