From 5b3d30ed1d6a5074dc781764f078eee08345c967 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 10 May 2026 01:28:20 -0500 Subject: [PATCH] Honor legacy board access for recovery actions --- ui/src/pages/IssueDetail.test.tsx | 38 ++++++++++++++++++++++++++++++- ui/src/pages/IssueDetail.tsx | 9 +++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index 331d71ca764..e4dd4c379cc 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -5,7 +5,7 @@ import type { Agent, Issue, IssueTreeControlPreview, IssueTreeHold } from "@pape import { act, type ButtonHTMLAttributes, type ReactNode } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { IssueDetail } from "./IssueDetail"; +import { canBoardResolveRecoveryAction, IssueDetail } from "./IssueDetail"; const mockIssuesApi = vi.hoisted(() => ({ get: vi.fn(), @@ -1447,3 +1447,39 @@ describe("IssueDetail", () => { expect(footer?.className).toContain("bg-background"); }); }); + +describe("canBoardResolveRecoveryAction", () => { + it("falls back to companyIds when memberships are not populated", () => { + expect( + canBoardResolveRecoveryAction("company-1", { + companyIds: ["company-1"], + memberships: [], + isInstanceAdmin: false, + source: "session", + keyId: null, + user: null, + userId: "user-1", + }), + ).toBe(true); + }); + + it("uses populated memberships as the authoritative board access source", () => { + expect( + canBoardResolveRecoveryAction("company-1", { + companyIds: ["company-1"], + memberships: [ + { + companyId: "company-1", + membershipRole: "viewer", + status: "active", + }, + ], + isInstanceAdmin: false, + source: "session", + keyId: null, + user: null, + userId: "user-1", + }), + ).toBe(false); + }); +}); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 36a90997013..a3d875b7cb9 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -212,17 +212,20 @@ function treeControlPreviewErrorCopy(error: unknown): string { return error instanceof Error ? error.message : "Unable to load preview."; } -function canBoardResolveRecoveryAction( +export function canBoardResolveRecoveryAction( companyId: string | null | undefined, boardAccess: CurrentBoardAccess | undefined, ) { if (!companyId || !boardAccess) return false; if (boardAccess.source === "local_implicit" || boardAccess.isInstanceAdmin) return true; + if (!boardAccess.memberships || boardAccess.memberships.length === 0) { + return boardAccess.companyIds.includes(companyId); + } - const membership = boardAccess.memberships?.find( + const membership = boardAccess.memberships.find( (item) => item.companyId === companyId && item.status === "active", ); - if (!membership) return boardAccess.companyIds.includes(companyId) && !boardAccess.memberships; + if (!membership) return false; return membership.membershipRole !== "viewer" && membership.membershipRole !== null; }