From da57418de9b2ba258f0388769f4ec7f9f5688ccb Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sat, 7 Mar 2026 21:56:51 +0100 Subject: [PATCH] fix: always separate completed checklist items from active list Completed items now consistently appear below unchecked items in both the editor and the note card preview. Replaces the flaky two-state hide/show toggle with a simple expand/collapse for the done section. Co-Authored-By: Claude Opus 4.6 --- src/lib/components/Checklist.svelte | 25 ++++++++----------------- src/lib/components/NoteCard.svelte | 25 +++++++++++++------------ tests/e2e/checklist.spec.ts | 27 ++++++++++++++------------- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/lib/components/Checklist.svelte b/src/lib/components/Checklist.svelte index 9eae59d..d6492c8 100644 --- a/src/lib/components/Checklist.svelte +++ b/src/lib/components/Checklist.svelte @@ -20,11 +20,10 @@ let { content, onChange }: Props = $props(); let items = $state(parseChecklist(content)); - let hideCompleted = $state(false); - let showDoneSection = $state(false); + let doneExpanded = $state(false); const flipDurationMs = 150; - let activeItems = $derived(hideCompleted ? items.filter((i) => !i.checked) : items); + let activeItems = $derived(items.filter((i) => !i.checked)); let doneItems = $derived(items.filter((i) => i.checked)); let doneCount = $derived(doneItems.length); @@ -103,11 +102,12 @@ } function handleDndConsider(e: CustomEvent>) { - items = e.detail.items; + // Merge reordered active items with done items + items = [...e.detail.items, ...items.filter((i) => i.checked)]; } function handleDndFinalize(e: CustomEvent>) { - items = e.detail.items; + items = [...e.detail.items, ...items.filter((i) => i.checked)]; emitChange(); } @@ -164,24 +164,15 @@ {#if doneCount > 0}
- {#if !hideCompleted} - - {:else if showDoneSection} + {#if doneExpanded}
{#each doneItems as item (item.id)}
diff --git a/src/lib/components/NoteCard.svelte b/src/lib/components/NoteCard.svelte index 52f221a..f192315 100644 --- a/src/lib/components/NoteCard.svelte +++ b/src/lib/components/NoteCard.svelte @@ -40,6 +40,10 @@ : [] ); + const activeChecklistItems = $derived(checklistItems.filter(i => !i.checked)); + const doneChecklistItems = $derived(checklistItems.filter(i => i.checked)); + const sortedChecklistItems = $derived([...activeChecklistItems, ...doneChecklistItems]); + function stop(fn: () => void) { return (e: Event) => { e.stopPropagation(); @@ -47,19 +51,16 @@ }; } - function toggleChecklistItem(index: number) { + function toggleChecklistItem(item: ChecklistItem) { const lines = note.content.split('\n'); - let checkIndex = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; - if (line.startsWith('- [x] ') || line.startsWith('- [ ] ')) { - if (checkIndex === index) { - lines[i] = line.startsWith('- [x] ') - ? `- [ ] ${line.slice(6)}` - : `- [x] ${line.slice(6)}`; - break; - } - checkIndex++; + const text = line.replace(/^- \[[ x]\] /, ''); + if (text === item.text && line.startsWith(item.checked ? '- [x] ' : '- [ ] ')) { + lines[i] = item.checked + ? `- [ ] ${text}` + : `- [x] ${text}`; + break; } } updateNote(note.id, { content: lines.join('\n') }); @@ -93,12 +94,12 @@ {#if note.checklistMode && checklistItems.length > 0}
    - {#each checklistItems.slice(0, 8) as item, index} + {#each sortedChecklistItems.slice(0, 8) as item}
  • { e.stopPropagation(); toggleChecklistItem(index); }} + onclick={(e) => { e.stopPropagation(); toggleChecklistItem(item); }} class="h-3.5 w-3.5 rounded border-[var(--border-subtle)] text-[var(--primary)] cursor-pointer" data-testid="card-checklist-checkbox" /> diff --git a/tests/e2e/checklist.spec.ts b/tests/e2e/checklist.spec.ts index 2a933a6..faf4ca5 100644 --- a/tests/e2e/checklist.spec.ts +++ b/tests/e2e/checklist.spec.ts @@ -44,12 +44,16 @@ test.describe('Checklist', () => { // When the user reopens the note and checks the item await noteCard(page, 'Tasks').click(); - await page.getByTestId('checklist-checkbox').first().check(); + await page.getByTestId('checklist-checkbox').first().click(); + + // Then the item moves to the done section + await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); await page.getByTestId('close-editor-btn').click(); - // Then reopening the note shows the item is still checked + // And reopening the note shows the item in the done section await noteCard(page, 'Tasks').click(); - await expect(page.getByTestId('checklist-checkbox').first()).toBeChecked(); + await page.getByTestId('checklist-toggle-done').click(); + await expect(page.getByTestId('checklist-done-checkbox').first()).toBeChecked(); }); test('Scenario: New checklist item is added by pressing Enter', async ({ authenticatedPage: page }) => { @@ -80,7 +84,7 @@ test.describe('Checklist', () => { await expect(page.getByTestId('checklist-input')).toHaveCount(1); }); - test('Scenario: Completed checklist items can be hidden and shown', async ({ authenticatedPage: page }) => { + test('Scenario: Completed checklist items are separated and can be expanded', async ({ authenticatedPage: page }) => { // Given a checklist note with a completed item and an uncompleted item await page.getByTestId('new-note-btn').click(); await page.getByTestId('note-title-input').fill('Hide Done Test'); @@ -88,19 +92,16 @@ test.describe('Checklist', () => { await page.getByTestId('checklist-input').first().fill('Done task'); await page.getByTestId('checklist-input').first().press('Enter'); await page.getByTestId('checklist-input').nth(1).fill('Pending task'); - await page.getByTestId('checklist-checkbox').first().check(); + await page.getByTestId('checklist-checkbox').first().click(); - // Then the "done" toggle is visible - await expect(page.getByTestId('checklist-toggle-done')).toBeVisible(); - await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); - - // When the user clicks the "done" toggle to hide completed items - await page.getByTestId('checklist-toggle-done').click(); - - // Then the completed item is hidden from the main list + // Then the completed item is separated from the active list await expect(page.getByTestId('checklist-input')).toHaveCount(1); await expect(page.getByTestId('checklist-input').first()).toHaveValue('Pending task'); + // And the "done" toggle shows the count + await expect(page.getByTestId('checklist-toggle-done')).toBeVisible(); + await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); + // When the user expands the done section await page.getByTestId('checklist-toggle-done').click();