Skip to content

[codex] Make notes category navigation client-side#13

Draft
dd3ok wants to merge 1 commit into
mainfrom
codex/notes-client-navigation
Draft

[codex] Make notes category navigation client-side#13
dd3ok wants to merge 1 commit into
mainfrom
codex/notes-client-navigation

Conversation

@dd3ok

@dd3ok dd3ok commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Make /notes category tab changes update client-side state instead of triggering a full route refresh.
  • Keep direct category URLs like /notes/tech/ and browser back/forward behavior working.
  • Split browser-safe note category/filter logic into src/lib/note-categories.ts so the client component does not import server-only fs code.
  • Add a notes validation guard that requires client-side URL updates and popstate support for category tabs.

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
  • Browser check: /notes/ -> Tech updates URL to /notes/tech/ and heading to Tech Notes; back returns to /notes/ and All Notes; console errors: 0.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +94 to +108
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)
}, [])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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)
    }, [])

Comment on lines +36 to +50
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]))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant