[codex] Make notes category navigation client-side#13
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the notes directory view to support client-side category filtering and dynamic URL updates without full page refreshes. It introduces a new note-categories.ts module to isolate category-related logic and updates the view component to manage active category state, handle browser back/forward navigation via popstate, and update the document title dynamically. The feedback highlights two important issues: first, unconditionally calling event.preventDefault() on category link clicks breaks native browser behaviors (like Cmd/Ctrl + Click to open in a new tab); second, calling decodeURIComponent on URL segments without a try-catch block can throw a URIError and crash the client-side application on malformed URLs.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const handleCategoryClick = useCallback(( | ||
| event: MouseEvent<HTMLAnchorElement>, | ||
| category: NoteCategory | ||
| ) => { | ||
| event.preventDefault() | ||
|
|
||
| const nextHref = getCategoryHref(category) | ||
|
|
||
| if (window.location.pathname === nextHref) { | ||
| return | ||
| } | ||
|
|
||
| setActiveCategoryId(category.id) | ||
| window.history.pushState({ notesCategory: category.id }, '', nextHref) | ||
| }, []) |
There was a problem hiding this comment.
Unconditionally calling event.preventDefault() in the onClick handler of a <Link> component breaks standard browser behaviors, such as opening links in a new tab via Cmd + Click (macOS), Ctrl + Click (Windows/Linux), Shift + Click (new window), or middle-clicks.
To preserve these native browser features and maintain accessibility, the handler should check for modifier keys and non-primary mouse clicks, returning early without preventing the default behavior if any are detected.
const handleCategoryClick = useCallback((
event: MouseEvent<HTMLAnchorElement>,
category: NoteCategory
) => {
if (
event.defaultPrevented ||
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.altKey ||
event.shiftKey
) {
return
}
event.preventDefault()
const nextHref = getCategoryHref(category)
if (window.location.pathname === nextHref) {
return
}
setActiveCategoryId(category.id)
window.history.pushState({ notesCategory: category.id }, '', nextHref)
}, [])
| const getCategoryFromPathname = (pathname: string) => { | ||
| const normalizedPathname = pathname.replace(/\/+$/, '') | ||
|
|
||
| if (normalizedPathname === '/notes') { | ||
| return allNotesCategory | ||
| } | ||
|
|
||
| const categoryMatch = normalizedPathname.match(/^\/notes\/([^/]+)$/) | ||
|
|
||
| if (!categoryMatch) { | ||
| return undefined | ||
| } | ||
|
|
||
| return getNoteCategoryById(decodeURIComponent(categoryMatch[1])) | ||
| } |
There was a problem hiding this comment.
Calling decodeURIComponent on arbitrary URL path segments can throw a URIError if the URL contains a malformed percent-encoded sequence (e.g., /notes/tech%8). If a user navigates to or from such a URL, the popstate listener will throw an unhandled exception, potentially crashing the client-side application.
Wrapping the decoding logic in a try-catch block ensures the application handles malformed URLs gracefully by returning undefined instead of throwing an error.
const getCategoryFromPathname = (pathname: string) => {
const normalizedPathname = pathname.replace(/\/+$/, '')
if (normalizedPathname === '/notes') {
return allNotesCategory
}
const categoryMatch = normalizedPathname.match(/^\/notes\/([^/]+)$/)
if (!categoryMatch) {
return undefined
}
try {
return getNoteCategoryById(decodeURIComponent(categoryMatch[1]))
} catch {
return undefined
}
}
Summary
/notescategory tab changes update client-side state instead of triggering a full route refresh./notes/tech/and browser back/forward behavior working.src/lib/note-categories.tsso the client component does not import server-onlyfscode.Why
The category URL should still change for shareability and direct links, but switching categories should feel like a tab/filter interaction instead of a page reload.
Validation
npm run build/notes/->Techupdates URL to/notes/tech/and heading toTech Notes; back returns to/notes/andAll Notes; console errors: 0.