Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const STORAGE_SAVE_KEY = "launchdesk-v1-items";
const STORAGE_LOAD_KEY = "launchdesk-items-v1"; // Intentional bug: this key should match STORAGE_SAVE_KEY.
// Fixed:Intentional bug: this key should match STORAGE_SAVE_KEY.

const demoChecks = [
{
Expand Down Expand Up @@ -91,7 +91,7 @@ const activityLog = document.getElementById("activityLog");
let checks = loadChecks();
let currentView = checks;

form.addEventListener("submit", (event) => handleAddChek(event)); // Intentional bug: misspelled function name.
form.addEventListener("submit", (event) => handleAddCheck(event)); // Fixed:Intentional bug: misspelled function name.
searchInput.addEventListener("input", applyFilters);
statusFilter.addEventListener("change", applyFilters);
priorityFilter.addEventListener("change", applyFilters);
Expand All @@ -104,7 +104,7 @@ renderApp();
logActivity("Demo data loaded. Start by testing the checklist workflows.");

function loadChecks() {
const saved = localStorage.getItem(STORAGE_LOAD_KEY);
const saved = localStorage.getItem(STORAGE_SAVE_KEY);

if (!saved) {
return [...demoChecks];
Expand Down Expand Up @@ -132,8 +132,8 @@ function handleAddCheck(event) {
const owner = ownerInput.value.trim() || "Unassigned";
const dueDate = dueDateInput.value || new Date().toISOString().slice(0, 10);

if (!title && !category) {
// Intentional bug: validation should stop when either required field is missing.
if (!title || !category) {
// Fixed:Intentional bug: validation should stop when either required field is missing.
formMessage.textContent =
"Please enter a check title and choose a category.";
return;
Expand Down Expand Up @@ -164,12 +164,16 @@ function applyFilters() {
const selectedPriority = priorityFilter.value;

let filtered = checks.filter((check) =>
check.owner.toLowerCase().includes(searchTerm),
); // Intentional bug: search should include title, category, priority, status, and owner.
check.title.toLowerCase().includes(searchTerm) ||
check.category.toLowerCase().includes(searchTerm) ||
check.priority.toLowerCase().includes(searchTerm) ||
check.status.toLowerCase().includes(searchTerm) ||
check.owner.toLowerCase().includes(searchTerm)
);

if (selectedStatus !== "All") {
filtered = filtered.filter((check) => check.priority === selectedStatus);
} // Intentional bug: status filter compares against priority.
filtered = filtered.filter((check) => check.status === selectedStatus);
} // Fixed:Intentional bug: status filter compares against priority.

if (selectedPriority !== "All") {
filtered = filtered.filter((check) => check.priority === selectedPriority);
Expand All @@ -191,7 +195,7 @@ function renderRows(list) {

const rows = list.map((check) => {
const priorityClass = `priority-${check.priority.toLowerCase()}`;
const statusClass = `status-${check.status.toLowerCase()}`; // Intentional bug: "In Progress" needs a slug class.
const statusClass = `status-${check.status.toLowerCase().replace(" ", "-")}`; // Fixed:Intentional bug: "In Progress" needs a slug class.

return `
<tr>
Expand Down Expand Up @@ -231,11 +235,15 @@ function renderRows(list) {

function updateMetrics() {
const total = checks.length;
const fixed = checks.filter((check) => check.status === "Complete").length; // Intentional bug: valid fixed status is "Fixed".
const fixed = checks.filter((check) => check.status === "Fixed").length; // Fixed:Intentional bug: valid fixed status is "Fixed".
const criticalOpen = checks.filter(
(check) => check.priority === "Critical" && check.status !== "Fixed",
).length;
const dueSoon = checks.filter((check) => daysUntil(check.dueDate) > 7).length; // Intentional bug: this should count items due within 7 days.
const dueSoon = checks.filter((check) => {
const days = daysUntil(check.dueDate);
return days >= 0 && days <= 7;
}).length; // Fixed:Intentional bug: this should count items due within 7 days.

const score = total === 0 ? 0 : Math.round((fixed / total) * 100);

totalCount.textContent = total;
Expand All @@ -247,13 +255,13 @@ function updateMetrics() {
}

function handleTableClick(event) {
const deleteButton = event.target.closest("[data-delete-id]"); // Intentional bug: button uses data-remove-id.
const deleteButton = event.target.closest("[data-remove-id]"); // Fixed:Intentional bug: button uses data-remove-id.

if (!deleteButton) {
return;
}

const id = Number(deleteButton.dataset.deleteId);
const id = Number(deleteButton.dataset.removeId);
const removed = checks.find((check) => check.id === id);
checks = checks.filter((check) => check.id !== id);
saveChecks();
Expand All @@ -278,14 +286,17 @@ function handleStatusChange(event) {
check.status = statusSelect.value;
renderRows(currentView);
logActivity(`Changed "${check.title}" to ${check.status}.`);
// Intentional bug: status changes should save, update filters, and refresh metrics.
saveChecks();
applyFilters();
updateMetrics();
// Fixed:Intentional bug: status changes should save, update filters, and refresh metrics.
}

async function resetDemoData() {
formMessage.textContent = "";

try {
const response = await fetch("data/launch-seed.json"); // Intentional bug: real file is data/launch-checks.json.
const response = await fetch("data/launch-checks.json"); // Fixed:Intentional bug: real file is data/launch-checks.json.

if (!response.ok) {
throw new Error(`Demo data request failed with ${response.status}`);
Expand All @@ -312,7 +323,7 @@ function exportCsv() {
"Due Date",
];
const rows = currentView.map((check) => [
check.name, // Intentional bug: property should be check.title.
check.title, // Fixed:Intentional bug: property should be check.title.
check.category,
check.priority,
check.status,
Expand Down Expand Up @@ -349,8 +360,12 @@ function logActivity(message) {

function daysUntil(dateValue) {
const today = new Date();
today.setHours(0, 0, 0, 0);

const target = new Date(dateValue);
const difference = target.getTime() - today.getTime();
target.setHours(0, 0, 0, 0);

const difference = target - today;
return Math.ceil(difference / 86400000);
}
Comment on lines 361 to 370
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Timezone parsing can shift dueDate by one day in negative UTC offsets.

new Date("YYYY-MM-DD") parses the string as UTC midnight per the ECMAScript spec, then setHours(0,0,0,0) snaps it to local midnight of whatever local date that UTC instant falls in. For users west of UTC (e.g. Pacific/Honolulu, UTC−10), new Date("2026-05-04") becomes 2026-05-03 14:00 locally, and setHours(0,0,0,0) collapses it to 2026-05-03 — a day earlier than intended. daysUntil (and formatDate) will then be off by one.

Parsing the components explicitly avoids the UTC interpretation:

🛠️ Suggested fix
 function daysUntil(dateValue) {
   const today = new Date();
   today.setHours(0, 0, 0, 0);
 
-  const target = new Date(dateValue);
-  target.setHours(0, 0, 0, 0);
-  
+  const [y, m, d] = String(dateValue).split("-").map(Number);
+  const target = new Date(y, m - 1, d);
+
   const difference = target.getTime() - today.getTime();
   return Math.ceil(difference / 86400000);
 }

Consider the same treatment inside formatDate.

Also note: across DST transitions the millisecond delta is 23h or 25h, so Math.ceil can round up an extra day. Using Math.round is more forgiving here since both endpoints are already snapped to midnight.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function daysUntil(dateValue) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const target = new Date(dateValue);
target.setHours(0, 0, 0, 0);
const difference = target.getTime() - today.getTime();
return Math.ceil(difference / 86400000);
}
function daysUntil(dateValue) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const [y, m, d] = String(dateValue).split("-").map(Number);
const target = new Date(y, m - 1, d);
const difference = target.getTime() - today.getTime();
return Math.ceil(difference / 86400000);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/app.js` around lines 361 - 370, The daysUntil function currently does new
Date(dateValue) which interprets "YYYY-MM-DD" as UTC and then setHours(0,0,0,0)
can shift the day in negative UTC offsets; change daysUntil to parse the date
string into numeric year, month, day and construct a local-midnight Date via new
Date(year, month-1, day) (so you get local midnight of the intended day) and
replace Math.ceil(difference / 86400000) with Math.round(...) to avoid DST
off-by-one; apply the same explicit-parsing approach to formatDate to ensure
consistency when displaying dueDate.


Expand Down
7 changes: 7 additions & 0 deletions launchdesk-checks (1).csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"Title","Category","Priority","Status","Owner","Due Date"
"Contact form tested","Frontend","Critical","Pending","Naveen","2026-05-04"
"Homepage meta title added","SEO","High","Fixed","Amani","2026-05-02"
"Mobile navigation checked","QA","High","In Progress","Dilan","2026-05-03"
"SSL certificate verified","Security","Critical","Blocked","Mira","2026-05-01"
"Image compression pass complete","Performance","Medium","Pending","Raveen","2026-05-05"
"Privacy policy linked in footer","Content","Low","Fixed","Ishara","2026-05-06"
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.