commit 5a85fb24598eabfcd581497e3ad1467dec612321
parent 326d6cb66c96e5828371f023dda2c2c0bdbf1bdf
Author: Hunter
Date:   Sat,  7 Mar 2026 16:44:18 -0500

fix event listener leak; ensure unique IDs (fix jumping bug)

Diffstat:
Mindex.html | 57+++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 37 insertions(+), 20 deletions(-)

diff --git a/index.html b/index.html @@ -454,6 +454,10 @@ return serialized; } + function generateId() { + return Date.now().toString(36) + Math.random().toString(36).slice(2) + } + function loadTasksFromLocalStorage() { const savedTasks = localStorage.getItem('taskTree'); if (savedTasks) { @@ -461,7 +465,7 @@ console.log(savedTasks); return deserializeTaskTree(savedTasks); } else { - return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null }; + return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: generateId(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null }; } } @@ -476,7 +480,7 @@ const text = line.slice(depth + 2); const newTask = { - id: depth === 0 ? 'root' : Date.now() + Math.random(), + id: depth === 0 ? 'root' : generateId(), text: text, state: status === '_' ? 0 : (status === 'x' ? 1 : 2), subtasks: [], @@ -627,16 +631,6 @@ taskInput.addEventListener('keydown', keydownHandler); taskInput.addEventListener('keyup', keyupHandler); taskInput.addEventListener('keydown', handleCopyAndCut); - appContainer.addEventListener('focusin', function(e) { - if (e.target.tagName === 'INPUT' && e.target.type === 'text') { - document.querySelectorAll('input[type="text"]').forEach(input => { - if (input !== e.target) { - placeCursorAtBeginning(input); - } - }); - } - }); - taskInput.addEventListener('input', () => { task.text = taskInput.value; if (task === currentTask) { @@ -1059,7 +1053,7 @@ function addNewSubtask(parentTask, currentSubtask = null) { - const newSubtask = { id: Date.now(), text: '', state: 0, subtasks: [], selectedSubtaskId: null }; + const newSubtask = { id: generateId(), text: '', state: 0, subtasks: [], selectedSubtaskId: null }; if (currentSubtask) { const index = parentTask.subtasks.findIndex(t => t.id === currentSubtask.id); parentTask.subtasks.splice(index + 1, 0, newSubtask); @@ -1089,18 +1083,30 @@ function navigateTasks(direction) { const tasks = currentTask.subtasks; + const subtaskIndex = tasks.findIndex(t => t.id === currentTask.selectedSubtaskId); + + // Check if the parent task itself is focused const currentElement = document.activeElement; - const currentContainer = currentElement.closest('.task-container'); - const currentIndex = Array.from(appContainer.querySelectorAll('.task-container')).indexOf(currentContainer); + const currentContainer = currentElement ? currentElement.closest('.task-container') : null; + const isParentFocused = currentContainer && currentContainer.dataset.id == currentTask.id; if (direction === 'up') { - if (currentIndex > 0) { - const prevTask = currentIndex === 1 ? currentTask : tasks[currentIndex - 2]; - selectAndFocusTask(prevTask); + if (isParentFocused) { + // Already at parent, can't go higher + } else if (subtaskIndex <= 0) { + // At first subtask or not found, go to parent + selectAndFocusTask(currentTask); + } else { + selectAndFocusTask(tasks[subtaskIndex - 1]); } } else { - if (currentIndex < tasks.length) { - selectAndFocusTask(tasks[currentIndex]); + if (isParentFocused) { + // Move from parent to first subtask + if (tasks.length > 0) { + selectAndFocusTask(tasks[0]); + } + } else if (subtaskIndex >= 0 && subtaskIndex < tasks.length - 1) { + selectAndFocusTask(tasks[subtaskIndex + 1]); } } lastSubtaskDownArrowReleased = false; @@ -1437,6 +1443,17 @@ e.preventDefault(); } + // Register focusin handler once (not per-element) to reset cursor on non-focused inputs + appContainer.addEventListener('focusin', function(e) { + if (e.target.tagName === 'INPUT' && e.target.type === 'text') { + document.querySelectorAll('input[type="text"]').forEach(input => { + if (input !== e.target) { + placeCursorAtBeginning(input); + } + }); + } + }); + getThemesFromCSS(); setInitialTheme(); renderCurrentView();