feat: add pending edits functionality to ApprovalCard across all HITL tools

This commit is contained in:
Anish Sarkar 2026-03-20 21:38:19 +05:30
parent 4bd2071a8d
commit 9b38626723
9 changed files with 246 additions and 286 deletions

View file

@ -149,6 +149,7 @@ function ApprovalCard({
const wasAlreadyDecided = interruptData.__decided__ != null;
const [isPanelOpen, setIsPanelOpen] = useState(false);
const openHitlEditPanel = useSetAtom(openHitlEditPanelAtom);
const [pendingEdits, setPendingEdits] = useState<{ title: string; description: string } | null>(null);
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState("");
const [selectedTeamId, setSelectedTeamId] = useState("");
@ -171,7 +172,7 @@ function ApprovalCard({
[selectedWorkspace, selectedTeamId]
);
const isTitleValid = (args.title ?? "").trim().length > 0;
const isTitleValid = (pendingEdits?.title ?? args.title ?? "").trim().length > 0;
const canApprove = !!selectedWorkspaceId && !!selectedTeamId && isTitleValid;
const reviewConfig = interruptData.review_configs[0];
@ -180,8 +181,8 @@ function ApprovalCard({
const buildFinalArgs = useCallback((overrides?: { title?: string; description?: string }) => {
return {
title: overrides?.title ?? args.title,
description: overrides?.description ?? args.description ?? null,
title: overrides?.title ?? pendingEdits?.title ?? args.title,
description: overrides?.description ?? pendingEdits?.description ?? args.description ?? null,
connector_id: selectedWorkspaceId ? Number(selectedWorkspaceId) : null,
team_id: selectedTeamId || null,
state_id: selectedStateId === "__none__" ? null : selectedStateId,
@ -189,20 +190,21 @@ function ApprovalCard({
priority: Number(selectedPriority),
label_ids: selectedLabelIds,
};
}, [args.title, args.description, selectedWorkspaceId, selectedTeamId, selectedStateId, selectedAssigneeId, selectedPriority, selectedLabelIds]);
}, [args.title, args.description, selectedWorkspaceId, selectedTeamId, selectedStateId, selectedAssigneeId, selectedPriority, selectedLabelIds, pendingEdits]);
const handleApprove = useCallback(() => {
if (decided || isPanelOpen || !canApprove) return;
if (!allowedDecisions.includes("approve")) return;
setDecided("approve");
const isEdited = pendingEdits !== null;
setDecided(isEdited ? "edit" : "approve");
onDecision({
type: "approve",
type: isEdited ? "edit" : "approve",
edited_action: {
name: interruptData.action_requests[0].name,
args: buildFinalArgs(),
},
});
}, [decided, isPanelOpen, canApprove, allowedDecisions, onDecision, interruptData, buildFinalArgs]);
}, [decided, isPanelOpen, canApprove, allowedDecisions, onDecision, interruptData, buildFinalArgs, pendingEdits]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
@ -250,19 +252,12 @@ function ApprovalCard({
onClick={() => {
setIsPanelOpen(true);
openHitlEditPanel({
title: args.title ?? "",
content: args.description ?? "",
title: pendingEdits?.title ?? (args.title ?? ""),
content: pendingEdits?.description ?? (args.description ?? ""),
toolName: "Linear Issue",
onSave: (newTitle, newDescription) => {
setIsPanelOpen(false);
setDecided("edit");
onDecision({
type: "edit",
edited_action: {
name: interruptData.action_requests[0].name,
args: buildFinalArgs({ title: newTitle, description: newDescription }),
},
});
setPendingEdits({ title: newTitle, description: newDescription });
},
});
}}
@ -461,10 +456,10 @@ function ApprovalCard({
{/* Content preview */}
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 pt-3">
{args.title != null && (
<p className="text-sm font-medium text-foreground">{args.title}</p>
{(pendingEdits?.title ?? args.title) != null && (
<p className="text-sm font-medium text-foreground">{pendingEdits?.title ?? args.title}</p>
)}
{args.description != null && (
{(pendingEdits?.description ?? args.description) != null && (
<div
className="max-h-[7rem] overflow-hidden text-sm"
style={{
@ -473,7 +468,7 @@ function ApprovalCard({
}}
>
<PlateEditor
markdown={args.description}
markdown={pendingEdits?.description ?? args.description ?? ""}
readOnly
preset="readonly"
editorVariant="none"

View file

@ -189,6 +189,7 @@ function ApprovalCard({
const wasAlreadyDecided = interruptData.__decided__ != null;
const [isPanelOpen, setIsPanelOpen] = useState(false);
const [editedArgs, setEditedArgs] = useState(initialEditState);
const [hasPanelEdits, setHasPanelEdits] = useState(false);
const openHitlEditPanel = useSetAtom(openHitlEditPanelAtom);
const reviewConfig = interruptData.review_configs[0];
@ -255,15 +256,16 @@ function ApprovalCard({
const handleApprove = useCallback(() => {
if (decided || isPanelOpen) return;
if (!allowedDecisions.includes("approve")) return;
setDecided("approve");
const isEdited = hasPanelEdits;
setDecided(isEdited ? "edit" : "approve");
onDecision({
type: "approve",
type: isEdited ? "edit" : "approve",
edited_action: {
name: interruptData.action_requests[0].name,
args: buildFinalArgs(),
},
});
}, [decided, isPanelOpen, allowedDecisions, onDecision, interruptData, buildFinalArgs]);
}, [decided, isPanelOpen, allowedDecisions, onDecision, interruptData, buildFinalArgs, hasPanelEdits]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
@ -321,18 +323,7 @@ function ApprovalCard({
title: newTitle,
description: newDescription,
}));
setDecided("edit");
onDecision({
type: "edit",
edited_action: {
name: interruptData.action_requests[0].name,
args: {
...buildFinalArgs(),
new_title: newTitle || null,
new_description: newDescription || null,
},
},
});
setHasPanelEdits(true);
},
});
}}
@ -536,12 +527,12 @@ function ApprovalCard({
{/* Content preview — proposed changes */}
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 pt-3">
{hasProposedChanges ? (
{(hasProposedChanges || hasPanelEdits) ? (
<>
{actionArgs.new_title && (
<p className="text-sm font-medium text-foreground">{String(actionArgs.new_title)}</p>
{(hasPanelEdits ? editedArgs.title : actionArgs.new_title) && (
<p className="text-sm font-medium text-foreground">{String(hasPanelEdits ? editedArgs.title : actionArgs.new_title)}</p>
)}
{actionArgs.new_description && (
{(hasPanelEdits ? editedArgs.description : actionArgs.new_description) && (
<div
className="max-h-[7rem] overflow-hidden text-sm"
style={{
@ -550,7 +541,7 @@ function ApprovalCard({
}}
>
<PlateEditor
markdown={String(actionArgs.new_description)}
markdown={String(hasPanelEdits ? editedArgs.description : actionArgs.new_description)}
readOnly
preset="readonly"
editorVariant="none"