diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 9504ab5c426..b7d1985a72a 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -244,7 +244,7 @@ export const resolveIssueRecoveryActionSchema = z.object({ ) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "This recovery outcome may only move the source issue to done or in_review", + message: "This recovery outcome requires sourceIssueStatus to be done or in_review", path: ["sourceIssueStatus"], }); } diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index 180e879edcc..511f79a8bd7 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -301,7 +301,7 @@ interface IssueChatThreadProps { successfulRunHandoff?: SuccessfulRunHandoffState | null; recoveryAction?: IssueRecoveryAction | null; onResolveRecoveryAction?: (outcome: RecoveryResolveOutcome) => void; - canCancelRecoveryAction?: boolean; + canFalsePositiveRecoveryAction?: boolean; legacyRecoverySourceIssue?: { identifier: string | null; href: string; @@ -3621,7 +3621,7 @@ export function IssueChatThread({ successfulRunHandoff = null, recoveryAction = null, onResolveRecoveryAction, - canCancelRecoveryAction = false, + canFalsePositiveRecoveryAction = false, legacyRecoverySourceIssue = null, companyId, projectId, @@ -4263,7 +4263,7 @@ export function IssueChatThread({ action={recoveryAction} agentMap={agentMap} onResolve={onResolveRecoveryAction} - canCancelRecovery={canCancelRecoveryAction} + canFalsePositive={canFalsePositiveRecoveryAction} /> ) : null} {legacyRecoverySourceIssue ? ( diff --git a/ui/src/components/IssueRecoveryActionCard.test.tsx b/ui/src/components/IssueRecoveryActionCard.test.tsx index d31f216158f..56953732bba 100644 --- a/ui/src/components/IssueRecoveryActionCard.test.tsx +++ b/ui/src/components/IssueRecoveryActionCard.test.tsx @@ -182,7 +182,7 @@ describe("IssueRecoveryActionCard", () => { it("does not offer blocked recovery resolution without a blocker selection flow", () => { const node = render( - {}} canCancelRecovery />, + {}} canFalsePositive />, ); click(node.querySelector("[data-testid='recovery-action-resolve-trigger']")); @@ -193,7 +193,7 @@ describe("IssueRecoveryActionCard", () => { expect(document.body.textContent).not.toContain("Mark blocked"); }); - it("hides false-positive options unless canCancelRecovery is set", () => { + it("hides false-positive options unless canFalsePositive is set", () => { const first = render( {}} />, ); @@ -207,7 +207,7 @@ describe("IssueRecoveryActionCard", () => { const onResolve = vi.fn(); const second = render( - , + , ); click(second.querySelector("[data-testid='recovery-action-resolve-trigger']")); expect(document.body.textContent).toContain("False positive, done"); diff --git a/ui/src/components/IssueRecoveryActionCard.tsx b/ui/src/components/IssueRecoveryActionCard.tsx index ca8e1c493ba..88fdcebb8ea 100644 --- a/ui/src/components/IssueRecoveryActionCard.tsx +++ b/ui/src/components/IssueRecoveryActionCard.tsx @@ -37,8 +37,8 @@ export interface IssueRecoveryActionCardProps { forcedState?: RecoveryCardCardState; /** Optional click handler for resolve menu actions. If omitted, the buttons are not rendered. */ onResolve?: (outcome: RecoveryResolveOutcome) => void; - /** Whether the viewer can run destructive board-only actions (e.g. cancel recovery). */ - canCancelRecovery?: boolean; + /** Whether the viewer can run destructive board-only actions (e.g. false-positive dismissal). */ + canFalsePositive?: boolean; className?: string; } @@ -323,7 +323,7 @@ export function IssueRecoveryActionCard({ agentMap, forcedState, onResolve, - canCancelRecovery = false, + canFalsePositive = false, className, }: IssueRecoveryActionCardProps) { const cardState: RecoveryCardCardState = forcedState ?? deriveRecoveryCardState(action); @@ -364,7 +364,7 @@ export function IssueRecoveryActionCard({ const showResolveActions = onResolve !== undefined && cardState !== "resolved"; const visibleResolveOptions = RESOLVE_OPTIONS.filter((option) => { - if (option.boardOnly && !canCancelRecovery) return false; + if (option.boardOnly && !canFalsePositive) return false; return true; }); diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 7aa5b208dc6..ee7c744f5db 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -615,7 +615,7 @@ type IssueDetailChatTabProps = { successfulRunHandoff: Issue["successfulRunHandoff"] | null; recoveryAction: Issue["activeRecoveryAction"]; onResolveRecoveryAction?: (outcome: import("../components/IssueRecoveryActionCard").RecoveryResolveOutcome) => void; - canCancelRecoveryAction?: boolean; + canFalsePositiveRecoveryAction?: boolean; legacyRecoverySourceIssue?: { identifier: string | null; href: string; @@ -686,7 +686,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({ successfulRunHandoff, recoveryAction, onResolveRecoveryAction, - canCancelRecoveryAction, + canFalsePositiveRecoveryAction, legacyRecoverySourceIssue, comments, locallyQueuedCommentRunIds, @@ -896,7 +896,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({ successfulRunHandoff={successfulRunHandoff} recoveryAction={recoveryAction ?? null} onResolveRecoveryAction={onResolveRecoveryAction} - canCancelRecoveryAction={canCancelRecoveryAction} + canFalsePositiveRecoveryAction={canFalsePositiveRecoveryAction} legacyRecoverySourceIssue={legacyRecoverySourceIssue ?? null} companyId={companyId} projectId={projectId} @@ -3887,7 +3887,7 @@ export function IssueDetail() { successfulRunHandoff={issue.successfulRunHandoff ?? null} recoveryAction={issue.activeRecoveryAction ?? null} onResolveRecoveryAction={handleResolveRecoveryAction} - canCancelRecoveryAction={canResolveBoardRecoveryAction} + canFalsePositiveRecoveryAction={canResolveBoardRecoveryAction} legacyRecoverySourceIssue={legacyRecoverySourceIssue} comments={threadComments} locallyQueuedCommentRunIds={locallyQueuedCommentRunIds} diff --git a/ui/storybook/stories/source-issue-recovery.stories.tsx b/ui/storybook/stories/source-issue-recovery.stories.tsx index 7ab97c9967d..fd6257f5177 100644 --- a/ui/storybook/stories/source-issue-recovery.stories.tsx +++ b/ui/storybook/stories/source-issue-recovery.stories.tsx @@ -65,11 +65,11 @@ function buildAction(overrides: Partial = {}): IssueRecover }; } -function CardPanel({ caption, action, forcedState, canCancelRecovery }: { +function CardPanel({ caption, action, forcedState, canFalsePositive }: { caption: string; action: IssueRecoveryAction; forcedState?: React.ComponentProps["forcedState"]; - canCancelRecovery?: boolean; + canFalsePositive?: boolean; }) { return (
@@ -81,7 +81,7 @@ function CardPanel({ caption, action, forcedState, canCancelRecovery }: { agentMap={storybookAgentMap} forcedState={forcedState} onResolve={() => {}} - canCancelRecovery={canCancelRecovery} + canFalsePositive={canFalsePositive} />
); @@ -90,12 +90,12 @@ function CardPanel({ caption, action, forcedState, canCancelRecovery }: { function AllStatesPanel() { return (
- +