Skip to content

bug(frontend): useCanGoForward reads __TSR_index without null guard — NaN permanently breaks forward button #268

@fireddd

Description

@fireddd

Bug

The useCanGoForward hook in TitlebarNav.tsx reads router.history.location.state.__TSR_index on mount without a null/undefined guard. If this internal TanStack Router state field is undefined (e.g., on the very first navigation before the router fully initializes its history state), tip becomes undefined. The subsequent Math.max(tip, index) call returns NaN, which permanently contaminates tip. After that, index < tip is always false (any comparison with NaN returns false), so the forward button is permanently disabled for the entire app session.

Analyzed against: 96d1649 (current main)
Confidence: Medium — the __TSR_index field is an internal TanStack Router implementation detail; undefined-on-cold-start depends on the specific router version's initialization order.

Root Cause

frontend/src/renderer/components/TitlebarNav.tsx:29-34:

function useCanGoForward(): boolean {
    const router = useRouter();
    const [canGoForward, setCanGoForward] = useState(false);
    useEffect(() => {
        let tip = router.history.location.state.__TSR_index;  // ← no null guard
        return router.history.subscribe(({ location, action }) => {
            const index = location.state.__TSR_index;  // ← also no null guard
            tip = action.type === "PUSH" ? index : Math.max(tip, index);
            setCanGoForward(index < tip);
        });
    }, [router]);
    return canGoForward;
}

If __TSR_index is undefined:

  1. tip = undefined
  2. First subscribe callback: Math.max(undefined, index)NaN
  3. All subsequent: Math.max(NaN, anything)NaN
  4. index < NaNfalse forever

Reproduction

  1. Launch the Electron app on a cold start
  2. Navigate forward through several pages, then go back
  3. If the router's history state hasn't been initialized before the first useEffect runs, the forward button stays permanently grayed out

The timing depends on whether TanStack Router's history adapter stamps __TSR_index before or after the initial React render — this can vary by version and by the Electron WebContents' history initialization.

Suggested Fix

Default to 0 when the field is missing:

let tip = router.history.location.state.__TSR_index ?? 0;
// ...
const index = location.state.__TSR_index ?? 0;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions