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 @@
@@ -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);
}