Skip to content

Commit 545aaa8

Browse files
authored
Merge pull request #58 from saagpatel/codex/refactor/wave5-7-draft-persistence
refactor(components): extract useDraftPersistence hook
2 parents a65c4ab + a123b4e commit 545aaa8

3 files changed

Lines changed: 550 additions & 194 deletions

File tree

src/components/Draft/DraftTab.tsx

Lines changed: 39 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useDraftChecklist } from "./useDraftChecklist";
1616
import { useDraftFirstResponse } from "./useDraftFirstResponse";
1717
import { useDraftGeneration } from "./useDraftGeneration";
1818
import { useDraftIntake } from "./useDraftIntake";
19+
import { useDraftPersistence } from "./useDraftPersistence";
1920
import { useGuidedRunbook } from "./useGuidedRunbook";
2021
import { useWorkspaceClipboardPacks } from "./useWorkspaceClipboardPacks";
2122
import { ConversationInput } from "./ConversationInput";
@@ -47,10 +48,6 @@ import {
4748
compactLines,
4849
parseCaseIntake,
4950
} from "../../features/workspace/workspaceAssistant";
50-
import {
51-
shouldMigrateVisibleRunbookSession,
52-
shouldProceedAfterSaveAttempt,
53-
} from "../../features/workspace/workspaceDraftSession";
5451
import {
5552
calculateEditRatio,
5653
countWords,
@@ -1324,202 +1321,50 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
13241321
onCompareLastResolution: handleCompareLastResolution,
13251322
});
13261323

1327-
const handleSaveDraft = useCallback(async () => {
1328-
if (!hasSaveableWorkspaceContent) {
1329-
showError("Cannot save empty draft");
1330-
return null;
1331-
}
1332-
1333-
const diagnosisData = buildDiagnosisJson();
1334-
const currentCreatedAt = savedDraftCreatedAt ?? new Date().toISOString();
1335-
const draftPayload = {
1336-
input_text: input,
1337-
summary_text: currentTicket?.summary ?? null,
1338-
diagnosis_json: diagnosisData,
1339-
response_text: response || null,
1340-
ticket_id: currentTicketId,
1341-
kb_sources_json: sources.length > 0 ? JSON.stringify(sources) : null,
1342-
is_autosave: false,
1343-
model_name: loadedModelName,
1344-
case_intake_json: serializedCaseIntake,
1345-
handoff_summary: handoffPack.summary,
1346-
status: "draft" as const,
1347-
};
1348-
1349-
const draftId = savedDraftId
1350-
? await updateDraft({
1351-
id: savedDraftId,
1352-
created_at: currentCreatedAt,
1353-
updated_at: activeWorkspaceDraft.updated_at,
1354-
finalized_at: null,
1355-
finalized_by: null,
1356-
...draftPayload,
1357-
})
1358-
: await saveDraft(draftPayload);
1359-
1360-
if (draftId) {
1361-
const nextScopeKey = `draft:${draftId}`;
1362-
let runbookScopeLinked = true;
1363-
if (workspaceRunbookScopeKey !== nextScopeKey) {
1364-
try {
1365-
const shouldMigrateActiveRunbookSession = guidedRunbookSession
1366-
? shouldMigrateVisibleRunbookSession({
1367-
hasGuidedRunbookSession: true,
1368-
runbookSessionTouched,
1369-
runbookSessionSourceScopeKey,
1370-
workspaceRunbookScopeKey,
1371-
})
1372-
: false;
1373-
1374-
const activeRunbookSessionId = guidedRunbookSession?.id ?? null;
1375-
if (shouldMigrateActiveRunbookSession && activeRunbookSessionId) {
1376-
await reassignRunbookSessionById(
1377-
activeRunbookSessionId,
1378-
nextScopeKey,
1379-
);
1380-
} else {
1381-
await reassignRunbookSessionScope(
1382-
workspaceRunbookScopeKey,
1383-
nextScopeKey,
1384-
);
1385-
}
1386-
setWorkspaceRunbookScopeKey(nextScopeKey);
1387-
setRunbookSessionSourceScopeKey(nextScopeKey);
1388-
} catch {
1389-
runbookScopeLinked = false;
1390-
}
1391-
}
1392-
setAutosaveDraftId(null);
1393-
setSavedDraftId(draftId);
1394-
setSavedDraftCreatedAt(currentCreatedAt);
1395-
const responseWordCount = countWords(response);
1396-
const editRatio = calculateEditRatio(originalResponse, response);
1397-
logEvent("response_saved", {
1398-
draft_id: draftId,
1399-
word_count: responseWordCount,
1400-
is_edited: isResponseEdited,
1401-
edit_ratio: Number(editRatio.toFixed(3)),
1402-
});
1403-
if (runbookScopeLinked) {
1404-
showSuccess("Draft saved");
1405-
} else {
1406-
showError(
1407-
"Draft saved, but guided runbook progress stayed attached to the previous workspace state",
1408-
);
1409-
}
1410-
return draftId;
1411-
}
1412-
return null;
1413-
}, [
1414-
activeWorkspaceDraft.updated_at,
1415-
buildDiagnosisJson,
1416-
currentTicket?.summary,
1417-
currentTicketId,
1418-
guidedRunbookSession,
1419-
handoffPack.summary,
1420-
hasSaveableWorkspaceContent,
1324+
const {
1325+
handleSaveDraft,
1326+
handleConfirmOpenDraft,
1327+
handleConfirmOpenSimilarCase,
1328+
} = useDraftPersistence({
14211329
input,
1422-
isResponseEdited,
1423-
loadedModelName,
1424-
logEvent,
1425-
originalResponse,
1426-
reassignRunbookSessionById,
1427-
reassignRunbookSessionScope,
14281330
response,
1331+
sources,
1332+
currentTicket,
1333+
currentTicketId,
1334+
savedDraftId,
14291335
savedDraftCreatedAt,
1430-
runbookSessionSourceScopeKey,
1336+
loadedModelName,
1337+
handoffPack,
1338+
serializedCaseIntake,
1339+
isResponseEdited,
1340+
originalResponse,
1341+
hasSaveableWorkspaceContent,
1342+
activeWorkspaceDraft,
1343+
workspaceRunbookScopeKey,
1344+
guidedRunbookSession,
14311345
runbookSessionTouched,
1432-
savedDraftId,
1346+
runbookSessionSourceScopeKey,
1347+
buildDiagnosisJson,
14331348
saveDraft,
1434-
serializedCaseIntake,
1435-
showError,
1436-
showSuccess,
1437-
sources,
14381349
updateDraft,
1439-
workspaceRunbookScopeKey,
1440-
]);
1441-
1442-
const handleConfirmOpenSimilarCase = useCallback(
1443-
async (mode: "replace" | "save-and-open" | "compare") => {
1444-
if (!pendingSimilarCaseOpen) {
1445-
return;
1446-
}
1447-
1448-
if (mode === "compare") {
1449-
setCompareCase(pendingSimilarCaseOpen);
1450-
setPendingSimilarCaseOpen(null);
1451-
return;
1452-
}
1453-
1454-
try {
1455-
if (mode === "save-and-open") {
1456-
const savedId = await handleSaveDraft();
1457-
if (!shouldProceedAfterSaveAttempt(mode, savedId)) {
1458-
return;
1459-
}
1460-
}
1461-
1462-
await loadSimilarCaseIntoWorkspace(pendingSimilarCaseOpen);
1463-
setPendingSimilarCaseOpen(null);
1464-
void logEvent("workspace_similar_case_opened", {
1465-
ticket_id: currentTicketId,
1466-
similar_case_id: pendingSimilarCaseOpen.draft_id,
1467-
similar_case_ticket: pendingSimilarCaseOpen.ticket_id,
1468-
open_mode: mode,
1469-
});
1470-
showSuccess(
1471-
mode === "save-and-open"
1472-
? "Saved the current workspace and opened the saved case"
1473-
: "Opened the saved case in the workspace",
1474-
);
1475-
} catch {
1476-
showError("Failed to open the saved case");
1477-
}
1478-
},
1479-
[
1480-
currentTicketId,
1481-
handleSaveDraft,
1482-
loadSimilarCaseIntoWorkspace,
1483-
logEvent,
1484-
pendingSimilarCaseOpen,
1485-
showError,
1486-
showSuccess,
1487-
],
1488-
);
1489-
1490-
const handleConfirmOpenDraft = useCallback(
1491-
async (mode: "replace" | "save-and-open") => {
1492-
if (!pendingDraftOpen) {
1493-
return;
1494-
}
1495-
1496-
try {
1497-
if (mode === "save-and-open") {
1498-
const savedId = await handleSaveDraft();
1499-
if (!shouldProceedAfterSaveAttempt(mode, savedId)) {
1500-
return;
1501-
}
1502-
}
1503-
1504-
applyLoadedDraft(pendingDraftOpen);
1505-
setPendingDraftOpen(null);
1506-
showSuccess(
1507-
mode === "save-and-open"
1508-
? "Saved the current workspace and opened the selected draft"
1509-
: "Opened the selected draft in the workspace",
1510-
);
1511-
} catch {
1512-
showError("Failed to open the selected draft");
1513-
}
1514-
},
1515-
[
1516-
applyLoadedDraft,
1517-
handleSaveDraft,
1518-
pendingDraftOpen,
1519-
showError,
1520-
showSuccess,
1521-
],
1522-
);
1350+
reassignRunbookSessionById,
1351+
reassignRunbookSessionScope,
1352+
logEvent,
1353+
setWorkspaceRunbookScopeKey,
1354+
setRunbookSessionSourceScopeKey,
1355+
setAutosaveDraftId,
1356+
setSavedDraftId,
1357+
setSavedDraftCreatedAt,
1358+
pendingDraftOpen,
1359+
setPendingDraftOpen,
1360+
applyLoadedDraft,
1361+
pendingSimilarCaseOpen,
1362+
setPendingSimilarCaseOpen,
1363+
loadSimilarCaseIntoWorkspace,
1364+
setCompareCase,
1365+
onShowSuccess: showSuccess,
1366+
onShowError: showError,
1367+
});
15231368

15241369
// Load initial draft if provided
15251370
useEffect(() => {

0 commit comments

Comments
 (0)