From 7bb1ee39abd45e518d5a179718d304d25bdb2ead Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Thu, 18 Jun 2026 21:09:09 +0300 Subject: [PATCH] fix: lock AI proposal cards after Add to prevent duplicate commits Approval state lived only in each card's local React state, so after committing (or after reloading a saved conversation) the Add / Review & add / Import buttons reappeared active and the same records could be added again. Persist the resolution onto the message data part via a chat-panel handler (setMessages), and initialize each card's status from data.resolved so committed/discarded proposals stay locked across re-render and history reload. Co-Authored-By: Claude Opus 4.8 --- .../components/chat/action-preview-card.tsx | 24 +++++++++-- .../chat/batch-action-preview-card.tsx | 39 +++++++++++++----- frontend/components/chat/chat-panel.tsx | 41 ++++++++++++++++++- .../components/chat/import-preview-card.tsx | 37 +++++++++++++---- frontend/lib/ai-chat.ts | 6 +++ frontend/lib/i18n/locales/en/translation.json | 2 + 6 files changed, 126 insertions(+), 23 deletions(-) diff --git a/frontend/components/chat/action-preview-card.tsx b/frontend/components/chat/action-preview-card.tsx index e3c8658..ec9e203 100644 --- a/frontend/components/chat/action-preview-card.tsx +++ b/frontend/components/chat/action-preview-card.tsx @@ -214,9 +214,23 @@ export function RecordSummary({ // The human approval gate for an agent-proposed add. The agent drafts the record // (dry run, nothing written); the clinician reviews it here, may edit it, and // must approve before it is committed via the matching RBAC-gated create endpoint. -export function ActionPreviewCard({ data }: { data: ActionPreviewData }) { +export function ActionPreviewCard({ + data, + onResolved, +}: { + data: ActionPreviewData; + // Called once committed/discarded so the parent can persist the resolution + // (prevents re-adding after re-render or conversation reload). + onResolved?: (resolution: "added" | "discarded") => void; +}) { const { t } = useTranslation(); - const [status, setStatus] = useState("pending"); + const [status, setStatus] = useState( + data.resolved === "added" + ? "done" + : data.resolved === "discarded" + ? "rejected" + : "pending", + ); // Editable working copy of the proposed record (edits commit, not the draft). const [record, setRecord] = useState>( data.record as Record, @@ -230,6 +244,7 @@ export function ActionPreviewCard({ data }: { data: ActionPreviewData }) { try { await commitAction({ ...data, record }); setStatus("done"); + onResolved?.("added"); notify.success( t("chat.actionCard.addedTitle"), t(`chat.actionCard.kind.${data.kind}`), @@ -300,7 +315,10 @@ export function ActionPreviewCard({ data }: { data: ActionPreviewData }) {