From a123b4e9ee06f5e79018069e4a28b5a53fe6bb53 Mon Sep 17 00:00:00 2001 From: Saagar Patel Date: Tue, 21 Apr 2026 19:47:10 +0200 Subject: [PATCH] refactor(components): extract useDraftPersistence hook Move handleSaveDraft + handleConfirmOpenDraft + handleConfirmOpenSimilarCase out of DraftTab.tsx into a dedicated hook. Hook accepts the shell's saveable state, the workspaceDraftState-derived appliers (applyLoadedDraft, loadSimilarCaseIntoWorkspace), setters for the runbook scope and saved/ autosave draft ids, and toast callbacks. Shell drops from 1893 to 1738 LOC and six new renderHook tests cover the save / update / compare branches. --- src/components/Draft/DraftTab.tsx | 233 ++---------- .../Draft/useDraftPersistence.test.ts | 172 +++++++++ src/components/Draft/useDraftPersistence.ts | 339 ++++++++++++++++++ 3 files changed, 550 insertions(+), 194 deletions(-) create mode 100644 src/components/Draft/useDraftPersistence.test.ts create mode 100644 src/components/Draft/useDraftPersistence.ts diff --git a/src/components/Draft/DraftTab.tsx b/src/components/Draft/DraftTab.tsx index 83dd28e..4c56b23 100644 --- a/src/components/Draft/DraftTab.tsx +++ b/src/components/Draft/DraftTab.tsx @@ -16,6 +16,7 @@ import { useDraftChecklist } from "./useDraftChecklist"; import { useDraftFirstResponse } from "./useDraftFirstResponse"; import { useDraftGeneration } from "./useDraftGeneration"; import { useDraftIntake } from "./useDraftIntake"; +import { useDraftPersistence } from "./useDraftPersistence"; import { useGuidedRunbook } from "./useGuidedRunbook"; import { useWorkspaceClipboardPacks } from "./useWorkspaceClipboardPacks"; import { ConversationInput } from "./ConversationInput"; @@ -47,10 +48,6 @@ import { compactLines, parseCaseIntake, } from "../../features/workspace/workspaceAssistant"; -import { - shouldMigrateVisibleRunbookSession, - shouldProceedAfterSaveAttempt, -} from "../../features/workspace/workspaceDraftSession"; import { calculateEditRatio, countWords, @@ -1324,202 +1321,50 @@ export const DraftTab = forwardRef( onCompareLastResolution: handleCompareLastResolution, }); - const handleSaveDraft = useCallback(async () => { - if (!hasSaveableWorkspaceContent) { - showError("Cannot save empty draft"); - return null; - } - - const diagnosisData = buildDiagnosisJson(); - const currentCreatedAt = savedDraftCreatedAt ?? new Date().toISOString(); - const draftPayload = { - input_text: input, - summary_text: currentTicket?.summary ?? null, - diagnosis_json: diagnosisData, - response_text: response || null, - ticket_id: currentTicketId, - kb_sources_json: sources.length > 0 ? JSON.stringify(sources) : null, - is_autosave: false, - model_name: loadedModelName, - case_intake_json: serializedCaseIntake, - handoff_summary: handoffPack.summary, - status: "draft" as const, - }; - - const draftId = savedDraftId - ? await updateDraft({ - id: savedDraftId, - created_at: currentCreatedAt, - updated_at: activeWorkspaceDraft.updated_at, - finalized_at: null, - finalized_by: null, - ...draftPayload, - }) - : await saveDraft(draftPayload); - - if (draftId) { - const nextScopeKey = `draft:${draftId}`; - let runbookScopeLinked = true; - if (workspaceRunbookScopeKey !== nextScopeKey) { - try { - const shouldMigrateActiveRunbookSession = guidedRunbookSession - ? shouldMigrateVisibleRunbookSession({ - hasGuidedRunbookSession: true, - runbookSessionTouched, - runbookSessionSourceScopeKey, - workspaceRunbookScopeKey, - }) - : false; - - const activeRunbookSessionId = guidedRunbookSession?.id ?? null; - if (shouldMigrateActiveRunbookSession && activeRunbookSessionId) { - await reassignRunbookSessionById( - activeRunbookSessionId, - nextScopeKey, - ); - } else { - await reassignRunbookSessionScope( - workspaceRunbookScopeKey, - nextScopeKey, - ); - } - setWorkspaceRunbookScopeKey(nextScopeKey); - setRunbookSessionSourceScopeKey(nextScopeKey); - } catch { - runbookScopeLinked = false; - } - } - setAutosaveDraftId(null); - setSavedDraftId(draftId); - setSavedDraftCreatedAt(currentCreatedAt); - const responseWordCount = countWords(response); - const editRatio = calculateEditRatio(originalResponse, response); - logEvent("response_saved", { - draft_id: draftId, - word_count: responseWordCount, - is_edited: isResponseEdited, - edit_ratio: Number(editRatio.toFixed(3)), - }); - if (runbookScopeLinked) { - showSuccess("Draft saved"); - } else { - showError( - "Draft saved, but guided runbook progress stayed attached to the previous workspace state", - ); - } - return draftId; - } - return null; - }, [ - activeWorkspaceDraft.updated_at, - buildDiagnosisJson, - currentTicket?.summary, - currentTicketId, - guidedRunbookSession, - handoffPack.summary, - hasSaveableWorkspaceContent, + const { + handleSaveDraft, + handleConfirmOpenDraft, + handleConfirmOpenSimilarCase, + } = useDraftPersistence({ input, - isResponseEdited, - loadedModelName, - logEvent, - originalResponse, - reassignRunbookSessionById, - reassignRunbookSessionScope, response, + sources, + currentTicket, + currentTicketId, + savedDraftId, savedDraftCreatedAt, - runbookSessionSourceScopeKey, + loadedModelName, + handoffPack, + serializedCaseIntake, + isResponseEdited, + originalResponse, + hasSaveableWorkspaceContent, + activeWorkspaceDraft, + workspaceRunbookScopeKey, + guidedRunbookSession, runbookSessionTouched, - savedDraftId, + runbookSessionSourceScopeKey, + buildDiagnosisJson, saveDraft, - serializedCaseIntake, - showError, - showSuccess, - sources, updateDraft, - workspaceRunbookScopeKey, - ]); - - const handleConfirmOpenSimilarCase = useCallback( - async (mode: "replace" | "save-and-open" | "compare") => { - if (!pendingSimilarCaseOpen) { - return; - } - - if (mode === "compare") { - setCompareCase(pendingSimilarCaseOpen); - setPendingSimilarCaseOpen(null); - return; - } - - try { - if (mode === "save-and-open") { - const savedId = await handleSaveDraft(); - if (!shouldProceedAfterSaveAttempt(mode, savedId)) { - return; - } - } - - await loadSimilarCaseIntoWorkspace(pendingSimilarCaseOpen); - setPendingSimilarCaseOpen(null); - void logEvent("workspace_similar_case_opened", { - ticket_id: currentTicketId, - similar_case_id: pendingSimilarCaseOpen.draft_id, - similar_case_ticket: pendingSimilarCaseOpen.ticket_id, - open_mode: mode, - }); - showSuccess( - mode === "save-and-open" - ? "Saved the current workspace and opened the saved case" - : "Opened the saved case in the workspace", - ); - } catch { - showError("Failed to open the saved case"); - } - }, - [ - currentTicketId, - handleSaveDraft, - loadSimilarCaseIntoWorkspace, - logEvent, - pendingSimilarCaseOpen, - showError, - showSuccess, - ], - ); - - const handleConfirmOpenDraft = useCallback( - async (mode: "replace" | "save-and-open") => { - if (!pendingDraftOpen) { - return; - } - - try { - if (mode === "save-and-open") { - const savedId = await handleSaveDraft(); - if (!shouldProceedAfterSaveAttempt(mode, savedId)) { - return; - } - } - - applyLoadedDraft(pendingDraftOpen); - setPendingDraftOpen(null); - showSuccess( - mode === "save-and-open" - ? "Saved the current workspace and opened the selected draft" - : "Opened the selected draft in the workspace", - ); - } catch { - showError("Failed to open the selected draft"); - } - }, - [ - applyLoadedDraft, - handleSaveDraft, - pendingDraftOpen, - showError, - showSuccess, - ], - ); + reassignRunbookSessionById, + reassignRunbookSessionScope, + logEvent, + setWorkspaceRunbookScopeKey, + setRunbookSessionSourceScopeKey, + setAutosaveDraftId, + setSavedDraftId, + setSavedDraftCreatedAt, + pendingDraftOpen, + setPendingDraftOpen, + applyLoadedDraft, + pendingSimilarCaseOpen, + setPendingSimilarCaseOpen, + loadSimilarCaseIntoWorkspace, + setCompareCase, + onShowSuccess: showSuccess, + onShowError: showError, + }); // Load initial draft if provided useEffect(() => { diff --git a/src/components/Draft/useDraftPersistence.test.ts b/src/components/Draft/useDraftPersistence.test.ts new file mode 100644 index 0000000..87e51a9 --- /dev/null +++ b/src/components/Draft/useDraftPersistence.test.ts @@ -0,0 +1,172 @@ +// @vitest-environment jsdom +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { useDraftPersistence } from "./useDraftPersistence"; + +type HookOptions = Parameters[0]; + +function makeOptions(overrides: Partial = {}): HookOptions { + const baseHandoffPack = { summary: "handoff" } as HookOptions["handoffPack"]; + const baseActiveDraft = { + updated_at: "2026-01-01T00:00:00.000Z", + } as HookOptions["activeWorkspaceDraft"]; + + return { + input: "user cannot log in", + response: "try resetting", + sources: [], + currentTicket: null, + currentTicketId: null, + savedDraftId: null, + savedDraftCreatedAt: null, + loadedModelName: "llama-3.1", + handoffPack: baseHandoffPack, + serializedCaseIntake: null, + isResponseEdited: false, + originalResponse: "try resetting", + hasSaveableWorkspaceContent: true, + activeWorkspaceDraft: baseActiveDraft, + workspaceRunbookScopeKey: "workspace:abc", + guidedRunbookSession: null, + runbookSessionTouched: false, + runbookSessionSourceScopeKey: null, + + buildDiagnosisJson: vi.fn().mockReturnValue(null), + saveDraft: vi.fn().mockResolvedValue("new-draft-42"), + updateDraft: vi.fn().mockResolvedValue("updated-draft-42"), + reassignRunbookSessionById: vi.fn().mockResolvedValue(undefined), + reassignRunbookSessionScope: vi.fn().mockResolvedValue(undefined), + logEvent: vi.fn(), + + setWorkspaceRunbookScopeKey: vi.fn(), + setRunbookSessionSourceScopeKey: vi.fn(), + setAutosaveDraftId: vi.fn(), + setSavedDraftId: vi.fn(), + setSavedDraftCreatedAt: vi.fn(), + + pendingDraftOpen: null, + setPendingDraftOpen: vi.fn(), + applyLoadedDraft: vi.fn(), + + pendingSimilarCaseOpen: null, + setPendingSimilarCaseOpen: vi.fn(), + loadSimilarCaseIntoWorkspace: vi.fn().mockResolvedValue(undefined), + setCompareCase: vi.fn(), + + onShowSuccess: vi.fn(), + onShowError: vi.fn(), + ...overrides, + }; +} + +describe("useDraftPersistence.handleSaveDraft", () => { + it("errors and returns null when the workspace is empty", async () => { + const options = makeOptions({ hasSaveableWorkspaceContent: false }); + const { result } = renderHook(() => useDraftPersistence(options)); + + let returned: string | null = "unset"; + await act(async () => { + returned = await result.current.handleSaveDraft(); + }); + + expect(returned).toBeNull(); + expect(options.onShowError).toHaveBeenCalledWith("Cannot save empty draft"); + expect(options.saveDraft).not.toHaveBeenCalled(); + expect(options.updateDraft).not.toHaveBeenCalled(); + }); + + it("calls saveDraft for a new draft and updates derived ids/timestamps", async () => { + const options = makeOptions(); + const { result } = renderHook(() => useDraftPersistence(options)); + + let returned: string | null = null; + await act(async () => { + returned = await result.current.handleSaveDraft(); + }); + + expect(returned).toBe("new-draft-42"); + expect(options.saveDraft).toHaveBeenCalledWith( + expect.objectContaining({ + input_text: "user cannot log in", + response_text: "try resetting", + is_autosave: false, + status: "draft", + }), + ); + expect(options.setSavedDraftId).toHaveBeenCalledWith("new-draft-42"); + expect(options.setAutosaveDraftId).toHaveBeenCalledWith(null); + expect(options.onShowSuccess).toHaveBeenCalledWith("Draft saved"); + }); + + it("calls updateDraft (not saveDraft) when a savedDraftId exists", async () => { + const options = makeOptions({ + savedDraftId: "existing-1", + savedDraftCreatedAt: "2025-12-01T00:00:00.000Z", + }); + const { result } = renderHook(() => useDraftPersistence(options)); + + await act(async () => { + await result.current.handleSaveDraft(); + }); + + expect(options.updateDraft).toHaveBeenCalledWith( + expect.objectContaining({ + id: "existing-1", + created_at: "2025-12-01T00:00:00.000Z", + }), + ); + expect(options.saveDraft).not.toHaveBeenCalled(); + }); +}); + +describe("useDraftPersistence.handleConfirmOpenDraft", () => { + it("is a no-op when no draft is pending", async () => { + const options = makeOptions(); + const { result } = renderHook(() => useDraftPersistence(options)); + + await act(async () => { + await result.current.handleConfirmOpenDraft("replace"); + }); + + expect(options.applyLoadedDraft).not.toHaveBeenCalled(); + expect(options.saveDraft).not.toHaveBeenCalled(); + }); + + it("applies the pending draft in replace mode without calling saveDraft", async () => { + const pendingDraftOpen = { + id: "draft-9", + } as HookOptions["pendingDraftOpen"]; + const options = makeOptions({ pendingDraftOpen }); + const { result } = renderHook(() => useDraftPersistence(options)); + + await act(async () => { + await result.current.handleConfirmOpenDraft("replace"); + }); + + expect(options.saveDraft).not.toHaveBeenCalled(); + expect(options.applyLoadedDraft).toHaveBeenCalledWith(pendingDraftOpen); + expect(options.setPendingDraftOpen).toHaveBeenCalledWith(null); + expect(options.onShowSuccess).toHaveBeenCalledWith( + expect.stringContaining("Opened the selected draft"), + ); + }); +}); + +describe("useDraftPersistence.handleConfirmOpenSimilarCase", () => { + it("opens compare mode without loading the case", async () => { + const pendingSimilarCaseOpen = { + draft_id: "s1", + ticket_id: "T-1", + } as HookOptions["pendingSimilarCaseOpen"]; + const options = makeOptions({ pendingSimilarCaseOpen }); + const { result } = renderHook(() => useDraftPersistence(options)); + + await act(async () => { + await result.current.handleConfirmOpenSimilarCase("compare"); + }); + + expect(options.setCompareCase).toHaveBeenCalledWith(pendingSimilarCaseOpen); + expect(options.loadSimilarCaseIntoWorkspace).not.toHaveBeenCalled(); + expect(options.saveDraft).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Draft/useDraftPersistence.ts b/src/components/Draft/useDraftPersistence.ts new file mode 100644 index 0000000..7019ef2 --- /dev/null +++ b/src/components/Draft/useDraftPersistence.ts @@ -0,0 +1,339 @@ +import { useCallback } from "react"; +import { shouldMigrateVisibleRunbookSession } from "../../features/workspace/workspaceDraftSession"; +import { shouldProceedAfterSaveAttempt } from "../../features/workspace/workspaceDraftSession"; +import { + calculateEditRatio, + countWords, +} from "../../features/analytics/qualityMetrics"; +import type { JiraTicket } from "../../hooks/useJira"; +import type { ContextSource } from "../../types/knowledge"; +import type { + GuidedRunbookSession, + HandoffPack, + SavedDraft, + SimilarCase, +} from "../../types/workspace"; + +interface SaveDraftPayload { + input_text: string; + summary_text: string | null; + diagnosis_json: string | null; + response_text: string | null; + ticket_id: string | null; + kb_sources_json: string | null; + is_autosave: boolean; + model_name: string | null; + case_intake_json: string | null; + handoff_summary: string; + status: "draft"; +} + +interface ActiveWorkspaceDraft { + updated_at: string; +} + +interface UseDraftPersistenceOptions { + input: string; + response: string; + sources: ContextSource[]; + currentTicket: JiraTicket | null; + currentTicketId: string | null; + savedDraftId: string | null; + savedDraftCreatedAt: string | null; + loadedModelName: string | null; + handoffPack: HandoffPack; + serializedCaseIntake: string | null; + isResponseEdited: boolean; + originalResponse: string; + hasSaveableWorkspaceContent: boolean; + activeWorkspaceDraft: ActiveWorkspaceDraft; + workspaceRunbookScopeKey: string; + guidedRunbookSession: GuidedRunbookSession | null; + runbookSessionTouched: boolean; + runbookSessionSourceScopeKey: string | null; + + buildDiagnosisJson: () => string | null; + saveDraft: (payload: SaveDraftPayload) => Promise; + updateDraft: (draft: SavedDraft) => Promise; + reassignRunbookSessionById: ( + sessionId: string, + nextScopeKey: string, + ) => Promise; + reassignRunbookSessionScope: ( + previousScopeKey: string, + nextScopeKey: string, + ) => Promise; + logEvent: (event: string, payload?: Record) => unknown; + + setWorkspaceRunbookScopeKey: (value: string) => void; + setRunbookSessionSourceScopeKey: (value: string | null) => void; + setAutosaveDraftId: (value: string | null) => void; + setSavedDraftId: (value: string | null) => void; + setSavedDraftCreatedAt: (value: string | null) => void; + + pendingDraftOpen: SavedDraft | null; + setPendingDraftOpen: (value: SavedDraft | null) => void; + applyLoadedDraft: (draft: SavedDraft) => void; + + pendingSimilarCaseOpen: SimilarCase | null; + setPendingSimilarCaseOpen: (value: SimilarCase | null) => void; + loadSimilarCaseIntoWorkspace: (similarCase: SimilarCase) => Promise; + setCompareCase: (value: SimilarCase | null) => void; + + onShowSuccess: (message: string) => void; + onShowError: (message: string) => void; +} + +export function useDraftPersistence(options: UseDraftPersistenceOptions) { + const { + input, + response, + sources, + currentTicket, + currentTicketId, + savedDraftId, + savedDraftCreatedAt, + loadedModelName, + handoffPack, + serializedCaseIntake, + isResponseEdited, + originalResponse, + hasSaveableWorkspaceContent, + activeWorkspaceDraft, + workspaceRunbookScopeKey, + guidedRunbookSession, + runbookSessionTouched, + runbookSessionSourceScopeKey, + buildDiagnosisJson, + saveDraft, + updateDraft, + reassignRunbookSessionById, + reassignRunbookSessionScope, + logEvent, + setWorkspaceRunbookScopeKey, + setRunbookSessionSourceScopeKey, + setAutosaveDraftId, + setSavedDraftId, + setSavedDraftCreatedAt, + pendingDraftOpen, + setPendingDraftOpen, + applyLoadedDraft, + pendingSimilarCaseOpen, + setPendingSimilarCaseOpen, + loadSimilarCaseIntoWorkspace, + setCompareCase, + onShowSuccess, + onShowError, + } = options; + + const handleSaveDraft = useCallback(async (): Promise => { + if (!hasSaveableWorkspaceContent) { + onShowError("Cannot save empty draft"); + return null; + } + + const diagnosisData = buildDiagnosisJson(); + const currentCreatedAt = savedDraftCreatedAt ?? new Date().toISOString(); + const draftPayload: SaveDraftPayload = { + input_text: input, + summary_text: currentTicket?.summary ?? null, + diagnosis_json: diagnosisData, + response_text: response || null, + ticket_id: currentTicketId, + kb_sources_json: sources.length > 0 ? JSON.stringify(sources) : null, + is_autosave: false, + model_name: loadedModelName, + case_intake_json: serializedCaseIntake, + handoff_summary: handoffPack.summary, + status: "draft", + }; + + const draftId = savedDraftId + ? await updateDraft({ + id: savedDraftId, + created_at: currentCreatedAt, + updated_at: activeWorkspaceDraft.updated_at, + finalized_at: null, + finalized_by: null, + ...draftPayload, + }) + : await saveDraft(draftPayload); + + if (draftId) { + const nextScopeKey = `draft:${draftId}`; + let runbookScopeLinked = true; + if (workspaceRunbookScopeKey !== nextScopeKey) { + try { + const shouldMigrateActiveRunbookSession = guidedRunbookSession + ? shouldMigrateVisibleRunbookSession({ + hasGuidedRunbookSession: true, + runbookSessionTouched, + runbookSessionSourceScopeKey, + workspaceRunbookScopeKey, + }) + : false; + + const activeRunbookSessionId = guidedRunbookSession?.id ?? null; + if (shouldMigrateActiveRunbookSession && activeRunbookSessionId) { + await reassignRunbookSessionById( + activeRunbookSessionId, + nextScopeKey, + ); + } else { + await reassignRunbookSessionScope( + workspaceRunbookScopeKey, + nextScopeKey, + ); + } + setWorkspaceRunbookScopeKey(nextScopeKey); + setRunbookSessionSourceScopeKey(nextScopeKey); + } catch { + runbookScopeLinked = false; + } + } + setAutosaveDraftId(null); + setSavedDraftId(draftId); + setSavedDraftCreatedAt(currentCreatedAt); + const responseWordCount = countWords(response); + const editRatio = calculateEditRatio(originalResponse, response); + logEvent("response_saved", { + draft_id: draftId, + word_count: responseWordCount, + is_edited: isResponseEdited, + edit_ratio: Number(editRatio.toFixed(3)), + }); + if (runbookScopeLinked) { + onShowSuccess("Draft saved"); + } else { + onShowError( + "Draft saved, but guided runbook progress stayed attached to the previous workspace state", + ); + } + return draftId; + } + return null; + }, [ + activeWorkspaceDraft.updated_at, + buildDiagnosisJson, + currentTicket?.summary, + currentTicketId, + guidedRunbookSession, + handoffPack.summary, + hasSaveableWorkspaceContent, + input, + isResponseEdited, + loadedModelName, + logEvent, + originalResponse, + reassignRunbookSessionById, + reassignRunbookSessionScope, + response, + savedDraftCreatedAt, + runbookSessionSourceScopeKey, + runbookSessionTouched, + savedDraftId, + saveDraft, + serializedCaseIntake, + sources, + updateDraft, + workspaceRunbookScopeKey, + setWorkspaceRunbookScopeKey, + setRunbookSessionSourceScopeKey, + setAutosaveDraftId, + setSavedDraftId, + setSavedDraftCreatedAt, + onShowSuccess, + onShowError, + ]); + + const handleConfirmOpenSimilarCase = useCallback( + async (mode: "replace" | "save-and-open" | "compare") => { + if (!pendingSimilarCaseOpen) { + return; + } + + if (mode === "compare") { + setCompareCase(pendingSimilarCaseOpen); + setPendingSimilarCaseOpen(null); + return; + } + + try { + if (mode === "save-and-open") { + const savedId = await handleSaveDraft(); + if (!shouldProceedAfterSaveAttempt(mode, savedId)) { + return; + } + } + + await loadSimilarCaseIntoWorkspace(pendingSimilarCaseOpen); + setPendingSimilarCaseOpen(null); + void logEvent("workspace_similar_case_opened", { + ticket_id: currentTicketId, + similar_case_id: pendingSimilarCaseOpen.draft_id, + similar_case_ticket: pendingSimilarCaseOpen.ticket_id, + open_mode: mode, + }); + onShowSuccess( + mode === "save-and-open" + ? "Saved the current workspace and opened the saved case" + : "Opened the saved case in the workspace", + ); + } catch { + onShowError("Failed to open the saved case"); + } + }, + [ + currentTicketId, + handleSaveDraft, + loadSimilarCaseIntoWorkspace, + logEvent, + pendingSimilarCaseOpen, + setCompareCase, + setPendingSimilarCaseOpen, + onShowError, + onShowSuccess, + ], + ); + + const handleConfirmOpenDraft = useCallback( + async (mode: "replace" | "save-and-open") => { + if (!pendingDraftOpen) { + return; + } + + try { + if (mode === "save-and-open") { + const savedId = await handleSaveDraft(); + if (!shouldProceedAfterSaveAttempt(mode, savedId)) { + return; + } + } + + applyLoadedDraft(pendingDraftOpen); + setPendingDraftOpen(null); + onShowSuccess( + mode === "save-and-open" + ? "Saved the current workspace and opened the selected draft" + : "Opened the selected draft in the workspace", + ); + } catch { + onShowError("Failed to open the selected draft"); + } + }, + [ + applyLoadedDraft, + handleSaveDraft, + pendingDraftOpen, + setPendingDraftOpen, + onShowError, + onShowSuccess, + ], + ); + + return { + handleSaveDraft, + handleConfirmOpenDraft, + handleConfirmOpenSimilarCase, + }; +}