From e59047187b203cdc84a4d681fbeba605f6fc7869 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 12 Apr 2026 20:43:51 -0500 Subject: [PATCH] Reset scroll on issue detail navigation --- ui/src/lib/navigation-scroll.test.ts | 40 ++++++++++++++++++++++++++++ ui/src/lib/navigation-scroll.ts | 18 +++++++++++++ 2 files changed, 58 insertions(+) diff --git a/ui/src/lib/navigation-scroll.test.ts b/ui/src/lib/navigation-scroll.test.ts index 636c29cf..967dfa19 100644 --- a/ui/src/lib/navigation-scroll.test.ts +++ b/ui/src/lib/navigation-scroll.test.ts @@ -39,6 +39,46 @@ describe("navigation-scroll", () => { ).toBe(false); }); + it("resets scroll when navigating directly between issue detail routes", () => { + expect( + shouldResetScrollOnNavigation({ + previousPathname: "/issues/PAP-1389", + pathname: "/issues/PAP-1346", + navigationType: "PUSH", + state: null, + }), + ).toBe(true); + + expect( + shouldResetScrollOnNavigation({ + previousPathname: "/PAP/issues/PAP-1389", + pathname: "/PAP/issues/PAP-1346", + navigationType: "REPLACE", + state: null, + }), + ).toBe(true); + }); + + it("does not treat non-detail issue routes as issue-to-issue navigation", () => { + expect( + shouldResetScrollOnNavigation({ + previousPathname: "/projects/project-1/issues/all", + pathname: "/issues/PAP-1346", + navigationType: "PUSH", + state: null, + }), + ).toBe(false); + + expect( + shouldResetScrollOnNavigation({ + previousPathname: "/issues/PAP-1389", + pathname: "/projects/project-1/issues/all", + navigationType: "PUSH", + state: null, + }), + ).toBe(false); + }); + it("does not reset scroll on the initial render or when the pathname is unchanged", () => { expect( shouldResetScrollOnNavigation({ diff --git a/ui/src/lib/navigation-scroll.ts b/ui/src/lib/navigation-scroll.ts index a4c5de14..6ea8a1bb 100644 --- a/ui/src/lib/navigation-scroll.ts +++ b/ui/src/lib/navigation-scroll.ts @@ -14,6 +14,7 @@ export function shouldResetScrollOnNavigation(params: { if (previousPathname === null) return false; if (previousPathname === pathname) return false; if (navigationType === "POP") return false; + if (isIssueDetailPathChange(previousPathname, pathname)) return true; return hasSidebarScrollResetState(state); } @@ -43,3 +44,20 @@ function hasSidebarScrollResetState(state: unknown): boolean { if (!state || typeof state !== "object") return false; return (state as Record).paperclipSidebarScrollReset === true; } + +function isIssueDetailPathChange(previousPathname: string, pathname: string): boolean { + const previousIssueRef = readIssueDetailPathRef(previousPathname); + const nextIssueRef = readIssueDetailPathRef(pathname); + return previousIssueRef !== null && nextIssueRef !== null && previousIssueRef !== nextIssueRef; +} + +function readIssueDetailPathRef(pathname: string): string | null { + const segments = pathname.split("/").filter(Boolean); + if (segments.length === 2 && segments[0] === "issues") { + return segments[1] ?? null; + } + if (segments.length === 3 && segments[1] === "issues") { + return segments[2] ?? null; + } + return null; +}