From 0124c5e7f678b38701064658ef75f1e27e465fee Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sat, 21 Mar 2026 23:32:08 +0100 Subject: [PATCH] fix: unchecked child repositions after its parent and siblings When unchecking a child from the done section, it stayed at the end of the items array (where done items live), appearing as the last active item instead of under its parent. Now repositions after the last sibling or parent in the array. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/lib/utils/checklist.test.ts | 15 +++++++++++++++ src/lib/utils/checklist.ts | 20 +++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/checklist.test.ts b/src/lib/utils/checklist.test.ts index c235827..c3905b3 100644 --- a/src/lib/utils/checklist.test.ts +++ b/src/lib/utils/checklist.test.ts @@ -112,6 +112,21 @@ describe('toggleItemWithCascade', () => { expect(result[0].checked).toBe(false); expect(result[0].parentId).toBeNull(); }); + + it('unchecking a child repositions it after its parent and siblings', () => { + // Simulates: parent + 2 children, one child checked (at end of array as done item) + const items: ChecklistItem[] = [ + { id: '1', text: 'Parent', checked: false, parentId: null }, + { id: '3', text: 'Child B', checked: false, parentId: '1' }, + { id: '4', text: 'Other', checked: false, parentId: null }, + { id: '2', text: 'Child A', checked: true, parentId: '1' } // done, at end + ]; + const result = toggleItemWithCascade('2', items); + // Child A should be repositioned after Child B (last sibling), not stay at end + const ids = result.map(i => i.id); + expect(ids).toEqual(['1', '3', '2', '4']); + expect(result.find(i => i.id === '2')!.checked).toBe(false); + }); }); describe('indentItem', () => { diff --git a/src/lib/utils/checklist.ts b/src/lib/utils/checklist.ts index 73d68af..9843ad8 100644 --- a/src/lib/utils/checklist.ts +++ b/src/lib/utils/checklist.ts @@ -31,11 +31,29 @@ export function toggleItemWithCascade(id: string, items: ChecklistItem[]): Check return item; }); - // When unchecking a child from done: if parent no longer exists, orphan it + // When unchecking a child: reposition it after its parent/siblings in the array + // (otherwise it stays at the end of the array where done items live) if (!newChecked && target.parentId) { const parentExists = result.some((i) => i.id === target.parentId); if (!parentExists) { + // Parent deleted — orphan it result = result.map((i) => (i.id === id ? { ...i, parentId: null } : i)); + } else { + // Remove the item from its current position + const uncheckedItem = result.find((i) => i.id === id)!; + result = result.filter((i) => i.id !== id); + // Find the last sibling or the parent, insert after it + let insertAfter = -1; + for (let i = 0; i < result.length; i++) { + if (result[i].id === target.parentId || result[i].parentId === target.parentId) { + insertAfter = i; + } + } + if (insertAfter >= 0) { + result.splice(insertAfter + 1, 0, uncheckedItem); + } else { + result.push(uncheckedItem); + } } }