From 8fb25ca07d29520aa81a636403bf3642f8247256 Mon Sep 17 00:00:00 2001 From: ppardi <6176270+ppardi@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:37:25 -0700 Subject: [PATCH] fix(notes-view): keep the open note visible in nested categories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The visibility check compared the selected category against only the first segment of the note's category, so an open note was hidden whenever the selected category was itself nested — for example after renaming a category into a nested path. Match the note against the selected category and its descendants, as the notes store already does when filtering. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: ppardi <6176270+ppardi@users.noreply.github.com> --- src/Util.js | 20 +++++++++++++++----- src/components/NotesView.vue | 4 ++-- src/tests/Util.spec.js | 30 ++++++++++++++++++++++-------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/Util.js b/src/Util.js index f073b4306..188c61ef1 100644 --- a/src/Util.js +++ b/src/Util.js @@ -29,12 +29,22 @@ export function categoryLabel(category) { return category === '' ? t('notes', 'Uncategorized') : category.replace(/\//g, ' / ') } -export function rootCategory(category) { - if (!category) { - return '' +/** + * Whether a note's category falls within the selected one. + * + * A null selection matches every note. Otherwise a note matches the selected + * category itself and any category nested below it. + * + * @param {string} noteCategory the note's category + * @param {string|null} selectedCategory the selected category, or null for all notes + * @return {boolean} whether the note belongs to the selection + */ +export function isInCategory(noteCategory, selectedCategory) { + if (selectedCategory === null) { + return true } - const separator = category.indexOf('/') - return separator === -1 ? category : category.substring(0, separator) + return noteCategory === selectedCategory + || noteCategory.startsWith(selectedCategory + '/') } export function routeIsNewNote($route) { diff --git a/src/components/NotesView.vue b/src/components/NotesView.vue index aeae89526..16324a8a4 100644 --- a/src/components/NotesView.vue +++ b/src/components/NotesView.vue @@ -93,7 +93,7 @@ import logger from '../Logger.js' import { createNote } from '../NotesService.js' import store from '../store.js' import { fetchNoteTemplates, fetchTemplateContent } from '../TemplateService.js' -import { categoryLabel, rootCategory } from '../Util.js' +import { categoryLabel, isInCategory } from '../Util.js' export default { name: 'NotesView', @@ -252,7 +252,7 @@ export default { } const selectedCategory = store.notes.getSelectedCategory() - if (selectedCategory !== null && selectedCategory !== rootCategory(this.note.category)) { + if (!isInCategory(this.note.category, selectedCategory)) { this.showNote = false } }, diff --git a/src/tests/Util.spec.js b/src/tests/Util.spec.js index 8f2dbf502..1fc0fb034 100644 --- a/src/tests/Util.spec.js +++ b/src/tests/Util.spec.js @@ -11,9 +11,9 @@ import { getDefaultSampleNote, getDefaultSampleNoteTitle, getDraggedNoteId, + isInCategory, isNoteDrag, noteAttributes, - rootCategory, routeIsNewNote, } from '../Util.js' @@ -81,17 +81,31 @@ describe('categoryLabel', () => { }) }) -describe('rootCategory', () => { - it('returns the first category segment', () => { - expect(rootCategory('a/b/c')).toBe('a') +describe('isInCategory', () => { + it('matches a note in the selected category itself', () => { + expect(isInCategory('Work', 'Work')).toBe(true) }) - it('leaves a plain category alone', () => { - expect(rootCategory('Recipes')).toBe('Recipes') + it('matches a note in a descendant of the selected category', () => { + expect(isInCategory('Work/Projects/2026', 'Work/Projects')).toBe(true) + }) + + it('matches a note whose nested category is selected exactly', () => { + expect(isInCategory('Personal/Work', 'Personal/Work')).toBe(true) + }) + + it('does not match a sibling category sharing a name prefix', () => { + expect(isInCategory('Workshop', 'Work')).toBe(false) + }) + + it('matches every note when no category is selected', () => { + expect(isInCategory('Work/Projects', null)).toBe(true) + expect(isInCategory('', null)).toBe(true) }) - it('leaves the empty category empty', () => { - expect(rootCategory('')).toBe('') + it('matches only uncategorized notes for the uncategorized selection', () => { + expect(isInCategory('', '')).toBe(true) + expect(isInCategory('Work', '')).toBe(false) }) })