Skip to content
Open
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
44 changes: 26 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.
const STORAGE_LOAD_KEY = "launchdesk-v1-items";

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)); // Intentional bug: misspelled function name.
searchInput.addEventListener("input", applyFilters);
statusFilter.addEventListener("change", applyFilters);
priorityFilter.addEventListener("change", applyFilters);
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) {
// Required fields must both be provided.
formMessage.textContent =
"Please enter a check title and choose a category.";
return;
Expand Down Expand Up @@ -163,13 +163,18 @@ function applyFilters() {
const selectedStatus = statusFilter.value;
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.
let filtered = checks.filter(
(check) =>
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);
}

if (selectedPriority !== "All") {
filtered = filtered.filter((check) => check.priority === selectedPriority);
Expand All @@ -191,7 +196,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().replaceAll(" ", "-")}`;

return `
<tr>
Expand Down Expand Up @@ -231,11 +236,14 @@ 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;
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;
Comment on lines +243 to +246
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
for tz in America/Los_Angeles America/New_York UTC; do
  echo "=== $tz ==="
  TZ=$tz node <<'NODE'
const dateValue = "2026-05-09";
const now = new Date("2026-05-09T23:00:00");
const target = new Date(dateValue);
const days = Math.ceil((target.getTime() - now.getTime()) / 86400000);
const display = new Intl.DateTimeFormat("en-US", {
  month: "short",
  day: "numeric",
  year: "numeric",
}).format(target);

console.log({
  now: now.toString(),
  target: target.toString(),
  display,
  days,
});
NODE
done

Repository: codezelaca/fs-se-debugging-bugs

Length of output: 690


🏁 Script executed:

# First, check if js/app.js exists and its size
wc -l js/app.js

Repository: codezelaca/fs-se-debugging-bugs

Length of output: 89


🏁 Script executed:

# Read lines around 243-246 to verify the code snippet
sed -n '240,250p' js/app.js

Repository: codezelaca/fs-se-debugging-bugs

Length of output: 473


🏁 Script executed:

# Find daysUntil function to verify implementation
rg -n "function daysUntil|daysUntil\s*\(" js/app.js -A 5

Repository: codezelaca/fs-se-debugging-bugs

Length of output: 521


🏁 Script executed:

# Find formatDate function to verify implementation
rg -n "function formatDate|formatDate\s*\(" js/app.js -A 8

Repository: codezelaca/fs-se-debugging-bugs

Length of output: 776


Parse dueDate as a local calendar date before computing "due soon".

The daysUntil() function parses YYYY-MM-DD strings with new Date(dateValue), which treats them as UTC. In US browsers this shifts the date to the previous local calendar day. A check due on May 9 renders as May 8 and can fall out of the 0..7 window late in the local day, making the due-soon count incorrect. Parse dates into local dates using new Date(year, month - 1, day) and align formatDate() to use the same helper.

Suggested fix
+function parseLocalDate(dateValue) {
+  if (!dateValue) {
+    return null;
+  }
+
+  const [year, month, day] = dateValue.split("-").map(Number);
+  return new Date(year, month - 1, day);
+}
+
 function daysUntil(dateValue) {
-  const today = new Date();
-  const target = new Date(dateValue);
-  const difference = target.getTime() - today.getTime();
-  return Math.ceil(difference / 86400000);
+  const target = parseLocalDate(dateValue);
+  if (!target) {
+    return Number.POSITIVE_INFINITY;
+  }
+
+  const today = new Date();
+  const startOfToday = new Date(
+    today.getFullYear(),
+    today.getMonth(),
+    today.getDate(),
+  );
+
+  return Math.round((target.getTime() - startOfToday.getTime()) / 86400000);
 }
 
 function formatDate(dateValue) {
   if (!dateValue) {
     return "No date";
   }
 
   return new Intl.DateTimeFormat("en", {
     month: "short",
     day: "numeric",
     year: "numeric",
-  }).format(new Date(dateValue));
+  }).format(parseLocalDate(dateValue));
 }
🤖 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 243 - 246, The due-soon count is wrong because
daysUntil(dateValue) currently uses new Date(dateValue) which parses
"YYYY-MM-DD" as UTC; update daysUntil to parse string inputs as local calendar
dates by splitting "YYYY-MM-DD" into year, month, day and constructing new
Date(year, month-1, day) (and handle Date inputs unchanged), then use the same
local-date construction logic in formatDate so both functions agree; update any
callers (e.g., the dueSoon calculation over checks and any formatting paths) to
rely on the corrected daysUntil and formatDate behavior.

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]");

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 @@ -276,16 +284,16 @@ function handleStatusChange(event) {
}

check.status = statusSelect.value;
renderRows(currentView);
saveChecks();
applyFilters();
logActivity(`Changed "${check.title}" to ${check.status}.`);
// 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");

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