diff --git a/apps/web/src/rightPanelStore.test.ts b/apps/web/src/rightPanelStore.test.ts index 2f4d8d6d70ed..8d883dbf97ff 100644 --- a/apps/web/src/rightPanelStore.test.ts +++ b/apps/web/src/rightPanelStore.test.ts @@ -39,6 +39,22 @@ async function reloadRightPanelStore() { } describe("rightPanelStore", () => { + it("reuses an open issue tab when the same issue is reopened without its host", () => { + const store = useRightPanelStore.getState(); + const issue = { + environmentId: "env-1", + projectId: "project-a", + tracker: "github", + scope: "xgooz3x/tolarian-academy", + key: "53", + }; + store.openIssue(refA, { ...issue, host: "github.com", url: "https://github.com/x/53" }); + store.openIssue(refA, issue); + const state = selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA); + expect(state.surfaces.filter((surface) => surface.kind === "issue")).toHaveLength(1); + expect(state.activeSurfaceId).toBe(state.surfaces[0]!.id); + }); + it("records single and bulk tab closes, newest first", () => { const store = useRightPanelStore.getState(); const pr = pullRequestSurface({ diff --git a/apps/web/src/rightPanelStore.ts b/apps/web/src/rightPanelStore.ts index 7323e03c10c5..7b9f520305ed 100644 --- a/apps/web/src/rightPanelStore.ts +++ b/apps/web/src/rightPanelStore.ts @@ -805,6 +805,20 @@ export const useRightPanelStore = create()( openIssue: (ref, target) => set((state) => userAction(state, scopedThreadKey(ref), (current) => { + // A link (the page URL) names an issue without its host; reuse the tab a row opened. + const existing = + target.host === undefined + ? current.surfaces.find( + (entry) => + entry.kind === "issue" && + entry.environmentId === target.environmentId && + entry.projectId === target.projectId && + entry.tracker === target.tracker && + entry.scope.toLowerCase() === target.scope.toLowerCase() && + entry.key === target.key, + ) + : undefined; + if (existing) return { ...current, isOpen: true, activeSurfaceId: existing.id }; const surface = issueSurface(target); const next = upsertSurface(current, surface); return target.url diff --git a/apps/web/src/routes/_chat.issues.tsx b/apps/web/src/routes/_chat.issues.tsx index 8bbfe8b0b6b4..b85134996ff4 100644 --- a/apps/web/src/routes/_chat.issues.tsx +++ b/apps/web/src/routes/_chat.issues.tsx @@ -703,7 +703,9 @@ function IssuesRoute() { options={INVOLVEMENT_TABS} onChange={(who) => updateSearch({ who })} /> - {hostOptions.length > 2 ? ( + {/* The server filters by host, so a filtered page only knows its own tracker; keep + the menu while filtered so "All trackers" stays reachable. */} + {hostOptions.length > 2 || search.host ? (