diff --git a/src/components/NoteList.test.tsx b/src/components/NoteList.test.tsx
index 480ebc3e..c975b2b8 100644
--- a/src/components/NoteList.test.tsx
+++ b/src/components/NoteList.test.tsx
@@ -1,7 +1,7 @@
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { NoteList } from './NoteList'
-import { getSortComparator, filterEntries, countByFilter } from '../utils/noteListHelpers'
+import { getSortComparator, filterEntries, countByFilter, countAllByFilter } from '../utils/noteListHelpers'
import type { NoteListFilter } from '../utils/noteListHelpers'
import type { VaultEntry, SidebarSelection } from '../types'
@@ -1293,11 +1293,44 @@ describe('NoteList — filter pills', () => {
expect(screen.getByTestId('filter-pill-trashed')).toBeInTheDocument()
})
- it('does not show filter pills in All Notes view', () => {
+ it('shows filter pills in All Notes view', () => {
render(
)
- expect(screen.queryByTestId('filter-pills')).not.toBeInTheDocument()
+ expect(screen.getByTestId('filter-pills')).toBeInTheDocument()
+ expect(screen.getByTestId('filter-pill-open')).toBeInTheDocument()
+ expect(screen.getByTestId('filter-pill-archived')).toBeInTheDocument()
+ expect(screen.getByTestId('filter-pill-trashed')).toBeInTheDocument()
+ })
+
+ it('shows correct All Notes count badges across all types', () => {
+ render(
+
+ )
+ // projectEntries: 2 open Projects + 1 open Note = 3 open, 1 archived, 1 trashed
+ const openPill = screen.getByTestId('filter-pill-open')
+ const archivedPill = screen.getByTestId('filter-pill-archived')
+ const trashedPill = screen.getByTestId('filter-pill-trashed')
+ expect(openPill).toHaveTextContent('3')
+ expect(archivedPill).toHaveTextContent('1')
+ expect(trashedPill).toHaveTextContent('1')
+ })
+
+ it('shows archived notes in All Notes when filter is archived', () => {
+ render(
+
+ )
+ expect(screen.getByText('Archived Project')).toBeInTheDocument()
+ expect(screen.queryByText('Open Project 1')).not.toBeInTheDocument()
+ expect(screen.queryByText('Some Note')).not.toBeInTheDocument()
+ })
+
+ it('shows trashed notes in All Notes when filter is trashed', () => {
+ render(
+
+ )
+ expect(screen.getByText('Trashed Project')).toBeInTheDocument()
+ expect(screen.queryByText('Open Project 1')).not.toBeInTheDocument()
})
it('shows correct count badges for each filter', () => {
@@ -1377,4 +1410,33 @@ describe('NoteList — filterEntries with subFilter', () => {
const result = filterEntries(entries, { kind: 'sectionGroup', type: 'Project' })
expect(result.map(e => e.title)).toEqual(['Active'])
})
+
+ it('filters all notes by open sub-filter', () => {
+ const result = filterEntries(entries, { kind: 'filter', filter: 'all' }, 'open')
+ expect(result.map(e => e.title)).toEqual(['Active', 'Other'])
+ })
+
+ it('filters all notes by archived sub-filter', () => {
+ const result = filterEntries(entries, { kind: 'filter', filter: 'all' }, 'archived')
+ expect(result.map(e => e.title)).toEqual(['Archived'])
+ })
+
+ it('filters all notes by trashed sub-filter', () => {
+ const result = filterEntries(entries, { kind: 'filter', filter: 'all' }, 'trashed')
+ expect(result.map(e => e.title)).toEqual(['Trashed'])
+ })
+})
+
+describe('countAllByFilter', () => {
+ it('counts all entries by filter status', () => {
+ const entries = [
+ makeEntry({ path: '/1.md', isA: 'Project' }),
+ makeEntry({ path: '/2.md', isA: 'Note' }),
+ makeEntry({ path: '/3.md', isA: 'Project', archived: true }),
+ makeEntry({ path: '/4.md', isA: 'Note', trashed: true }),
+ makeEntry({ path: '/5.md', isA: 'Person', archived: true, trashed: true }),
+ ]
+ const counts = countAllByFilter(entries)
+ expect(counts).toEqual({ open: 2, archived: 1, trashed: 2 })
+ })
})
diff --git a/src/components/NoteList.tsx b/src/components/NoteList.tsx
index 73d791e8..e9cecfa8 100644
--- a/src/components/NoteList.tsx
+++ b/src/components/NoteList.tsx
@@ -1,7 +1,7 @@
import { useState, useMemo, useCallback, useEffect, memo } from 'react'
import type { VaultEntry, SidebarSelection, ModifiedFile, NoteStatus, InboxPeriod } from '../types'
import type { NoteListFilter } from '../utils/noteListHelpers'
-import { countByFilter, countInboxByPeriod } from '../utils/noteListHelpers'
+import { countByFilter, countAllByFilter, countInboxByPeriod } from '../utils/noteListHelpers'
import { NoteItem } from './NoteItem'
import { prefetchNoteContent } from '../hooks/useTabManagement'
import { BulkActionBar } from './BulkActionBar'
@@ -48,11 +48,13 @@ function NoteListInner({ entries, selection, selectedNote, noteListFilter, onNot
const isSectionGroup = selection.kind === 'sectionGroup'
const isInboxView = selection.kind === 'filter' && selection.filter === 'inbox'
- const subFilter = isSectionGroup ? noteListFilter : undefined
+ const isAllNotesView = selection.kind === 'filter' && selection.filter === 'all'
+ const showFilterPills = isSectionGroup || isAllNotesView
+ const subFilter = showFilterPills ? noteListFilter : undefined
const filterCounts = useMemo(
- () => isSectionGroup ? countByFilter(entries, selection.type) : { open: 0, archived: 0, trashed: 0 },
- [entries, isSectionGroup, selection],
+ () => isSectionGroup ? countByFilter(entries, selection.type) : isAllNotesView ? countAllByFilter(entries) : { open: 0, archived: 0, trashed: 0 },
+ [entries, isSectionGroup, isAllNotesView, selection],
)
const inboxCounts = useMemo(
@@ -75,7 +77,7 @@ function NoteListInner({ entries, selection, selectedNote, noteListFilter, onNot
const noteListKeyboard = useNoteListKeyboard({ items: searched, selectedNotePath: selectedNote?.path ?? null, onOpen: onReplaceActiveTab, enabled: !isEntityView })
const multiSelect = useMultiSelect(searched, selectedNote?.path ?? null)
- useEffect(() => { multiSelect.clear() }, [selection]) // eslint-disable-line react-hooks/exhaustive-deps -- clear on selection change only
+ useEffect(() => { multiSelect.clear() }, [selection, noteListFilter]) // eslint-disable-line react-hooks/exhaustive-deps -- clear on selection/filter change
const handleClickNote = useCallback((entry: VaultEntry, e: React.MouseEvent) => {
routeNoteClick(entry, e, { onReplace: onReplaceActiveTab, onSelect: onSelectNote, onOpenInNewWindow, multiSelect })
@@ -100,7 +102,7 @@ function NoteListInner({ entries, selection, selectedNote, noteListFilter, onNot
return (
- {isSectionGroup &&
}
+ {showFilterPills &&
}
{isInboxView && onInboxPeriodChange &&
}
diff --git a/src/utils/noteListHelpers.ts b/src/utils/noteListHelpers.ts
index 23202b55..aeaa8c26 100644
--- a/src/utils/noteListHelpers.ts
+++ b/src/utils/noteListHelpers.ts
@@ -315,6 +315,7 @@ function filterByKind(entries: VaultEntry[], selection: SidebarSelection, subFil
const typeEntries = entries.filter((e) => e.isA === selection.type)
return subFilter ? applySubFilter(typeEntries, subFilter) : typeEntries.filter(isActive)
}
+ if (selection.filter === 'all' && subFilter) return applySubFilter(entries, subFilter)
return filterByFilterType(entries, selection.filter)
}
@@ -342,6 +343,17 @@ export function countByFilter(entries: VaultEntry[], type: string): Record {
+ let open = 0, archived = 0, trashed = 0
+ for (const e of entries) {
+ if (e.trashed) trashed++
+ else if (e.archived) archived++
+ else open++
+ }
+ return { open, archived, trashed }
+}
+
// --- Inbox ---
/** Build a set of all valid link targets (titles, aliases, filename stems, path stems). */