Skip to content

Commit 15d1de9

Browse files
saagar210claude
andcommitted
refactor(components): extract handleClear to new useDraftClear hook
handleClear in DraftTab reset ~25 state slices plus 6 sibling-hook reset orchestrators (checklist, first-response, approval, response actions, workspace artifacts, generation) and rotated the workspace runbook scope key. Its dep array listed only [workspacePersonalization.preferred_note_audience, resetGeneration], omitting the other five reset functions — another source of the existing react-hooks/exhaustive-deps warnings and a latent stale-closure hazard if any of those resetters ever gained non-empty deps. Extracts to useDraftClear with: - Explicit 32-field options interface: 1 "changing" read (preferredNoteAudience), 26 writers, 6 reset orchestrators - Narrow Setter<T> alias so the options accept both raw useState dispatchers and the narrower (value: T) => void setters that some sibling hooks expose - Complete dep array covering every closed-over reference - Moves parseCaseIntake + createWorkspaceRunbookScopeKey into the hook so DraftTab drops the parseCaseIntake import entirely Call site must live after useWorkspaceClipboardPacks so setPendingSimilarCaseOpen and resetWorkspaceArtifacts are defined at prop-pass time (the original useCallback tolerated forward references because its body evaluated lazily). The hook call is now placed immediately after that composition. DraftTab.tsx: 1171 -> 1169 lines (the 37-line handler body becomes a 33-line hook invocation, so the LOC delta is small, but the exhaustive dep array now lives inside the hook). Branch total vs master: 1412 -> 1169 (-243, -17.2%). react-hooks/exhaustive-deps warnings: 3 of the 4 originally cited in the audit are now resolved. No behavior change. Typecheck + vitest (241/241) + eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dcf218a commit 15d1de9

2 files changed

Lines changed: 218 additions & 39 deletions

File tree

src/components/Draft/DraftTab.tsx

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ConversationThread } from "./ConversationThread";
2323
import type { ConversationEntry } from "./ConversationThread";
2424
import { useDraftApproval } from "./useDraftApproval";
2525
import { useDraftChecklist } from "./useDraftChecklist";
26+
import { useDraftClear } from "./useDraftClear";
2627
import { useDraftFirstResponse } from "./useDraftFirstResponse";
2728
import { useDraftGeneration } from "./useDraftGeneration";
2829
import { useDraftIntake } from "./useDraftIntake";
@@ -56,7 +57,6 @@ import { useWorkspaceCatalog } from "../../features/workspace/useWorkspaceCatalo
5657
import { useWorkspaceDerivedArtifacts } from "../../features/workspace/useWorkspaceDerivedArtifacts";
5758
import { useWorkspaceCommandBridge } from "../../features/workspace/useWorkspaceCommandBridge";
5859
import { useWorkspaceDraftState } from "../../features/workspace/useWorkspaceDraftState";
59-
import { parseCaseIntake } from "../../features/workspace/workspaceAssistant";
6060
import { countWords } from "../../features/analytics/qualityMetrics";
6161
import type { JiraTicket } from "../../hooks/useJira";
6262
import type {
@@ -458,44 +458,6 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
458458
setSuggestionsDismissed(true);
459459
}, []);
460460

461-
const handleClear = useCallback(() => {
462-
setInput("");
463-
setOcrText(null);
464-
setDiagnosticNotes("");
465-
setTreeResult(null);
466-
resetChecklist();
467-
resetFirstResponse();
468-
resetApproval();
469-
setResponse("");
470-
setOriginalResponse("");
471-
setIsResponseEdited(false);
472-
setSources([]);
473-
setMetrics(null);
474-
setConfidence(null);
475-
setGrounding([]);
476-
setCurrentTicketId(null);
477-
setCurrentTicket(null);
478-
setSavedDraftId(null);
479-
setSavedDraftCreatedAt(null);
480-
setConversationEntries([]);
481-
setHandoffTouched(false);
482-
resetResponseActions();
483-
setSuggestionsDismissed(false);
484-
setCaseIntake({
485-
...parseCaseIntake(null),
486-
note_audience: workspacePersonalization.preferred_note_audience,
487-
});
488-
resetWorkspaceArtifacts();
489-
setGuidedRunbookSession(null);
490-
setGuidedRunbookNote("");
491-
setRunbookSessionSourceScopeKey(null);
492-
setRunbookSessionTouched(false);
493-
setAutosaveDraftId(null);
494-
setPendingSimilarCaseOpen(null);
495-
setWorkspaceRunbookScopeKey(createWorkspaceRunbookScopeKey());
496-
resetGeneration();
497-
}, [workspacePersonalization.preferred_note_audience, resetGeneration]);
498-
499461
const handleTreeComplete = useCallback((result: TreeResult) => {
500462
setTreeResult(result);
501463
}, []);
@@ -733,6 +695,42 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
733695
onShowError: showError,
734696
});
735697

698+
const handleClear = useDraftClear({
699+
preferredNoteAudience: workspacePersonalization.preferred_note_audience,
700+
setInput,
701+
setOcrText,
702+
setDiagnosticNotes,
703+
setTreeResult,
704+
setResponse,
705+
setOriginalResponse,
706+
setIsResponseEdited,
707+
setSources,
708+
setMetrics,
709+
setConfidence,
710+
setGrounding,
711+
setCurrentTicketId,
712+
setCurrentTicket,
713+
setSavedDraftId,
714+
setSavedDraftCreatedAt,
715+
setConversationEntries,
716+
setHandoffTouched,
717+
setSuggestionsDismissed,
718+
setCaseIntake,
719+
setGuidedRunbookSession,
720+
setGuidedRunbookNote,
721+
setRunbookSessionSourceScopeKey,
722+
setRunbookSessionTouched,
723+
setAutosaveDraftId,
724+
setPendingSimilarCaseOpen,
725+
setWorkspaceRunbookScopeKey,
726+
resetChecklist,
727+
resetFirstResponse,
728+
resetApproval,
729+
resetResponseActions,
730+
resetWorkspaceArtifacts,
731+
resetGeneration,
732+
});
733+
736734
const loadSimilarCaseIntoWorkspace = useCallback(
737735
async (similarCase: SimilarCase) => {
738736
const fullDraft = await getDraft(similarCase.draft_id);
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { useCallback } from "react";
2+
import { parseCaseIntake } from "../../features/workspace/workspaceAssistant";
3+
import type { JiraTicket } from "../../hooks/useJira";
4+
import type { ContextSource } from "../../types/knowledge";
5+
import type {
6+
ConfidenceAssessment,
7+
GenerationMetrics,
8+
GroundedClaim,
9+
} from "../../types/llm";
10+
import type {
11+
CaseIntake,
12+
GuidedRunbookSession,
13+
SimilarCase,
14+
} from "../../types/workspace";
15+
import type { ConversationEntry } from "./ConversationThread";
16+
import { createWorkspaceRunbookScopeKey } from "./draftTabDefaults";
17+
import type { TreeResult } from "./DiagnosisPanel";
18+
19+
// Narrow setter form: this hook only writes plain values, never updater
20+
// functions, so the option types stay assignable from both React useState
21+
// dispatchers and narrower setters returned by sibling hooks.
22+
type Setter<T> = (value: T) => void;
23+
24+
export interface UseDraftClearOptions {
25+
// Only "changing" read — drives the reset intake's note_audience default
26+
preferredNoteAudience: CaseIntake["note_audience"];
27+
28+
// Primitive state writers
29+
setInput: Setter<string>;
30+
setOcrText: Setter<string | null>;
31+
setDiagnosticNotes: Setter<string>;
32+
setTreeResult: Setter<TreeResult | null>;
33+
setResponse: Setter<string>;
34+
setOriginalResponse: Setter<string>;
35+
setIsResponseEdited: Setter<boolean>;
36+
setSources: Setter<ContextSource[]>;
37+
setMetrics: Setter<GenerationMetrics | null>;
38+
setConfidence: Setter<ConfidenceAssessment | null>;
39+
setGrounding: Setter<GroundedClaim[]>;
40+
setCurrentTicketId: Setter<string | null>;
41+
setCurrentTicket: Setter<JiraTicket | null>;
42+
setSavedDraftId: Setter<string | null>;
43+
setSavedDraftCreatedAt: Setter<string | null>;
44+
setConversationEntries: Setter<ConversationEntry[]>;
45+
setHandoffTouched: Setter<boolean>;
46+
setSuggestionsDismissed: Setter<boolean>;
47+
setCaseIntake: Setter<CaseIntake>;
48+
setGuidedRunbookSession: Setter<GuidedRunbookSession | null>;
49+
setGuidedRunbookNote: Setter<string>;
50+
setRunbookSessionSourceScopeKey: Setter<string | null>;
51+
setRunbookSessionTouched: Setter<boolean>;
52+
setAutosaveDraftId: Setter<string | null>;
53+
setPendingSimilarCaseOpen: Setter<SimilarCase | null>;
54+
setWorkspaceRunbookScopeKey: Setter<string>;
55+
56+
// Reset orchestrators exposed by sibling hooks
57+
resetChecklist: () => void;
58+
resetFirstResponse: () => void;
59+
resetApproval: () => void;
60+
resetResponseActions: () => void;
61+
resetWorkspaceArtifacts: () => void;
62+
resetGeneration: () => void;
63+
}
64+
65+
/**
66+
* Resets every workspace-level piece of state that participates in a draft:
67+
* inputs, generated output, trust signals, ticket context, workspace
68+
* artifacts, guided-runbook session, autosave pointer, and suggestion
69+
* dismissal. Also rotates the workspace runbook scope key so a fresh draft
70+
* never inherits the prior session's scope.
71+
*
72+
* Extracted from DraftTab so the 37-line reset lives next to its hook family
73+
* and keeps a complete dep array without bloating the orchestrator.
74+
*/
75+
export function useDraftClear({
76+
preferredNoteAudience,
77+
setInput,
78+
setOcrText,
79+
setDiagnosticNotes,
80+
setTreeResult,
81+
setResponse,
82+
setOriginalResponse,
83+
setIsResponseEdited,
84+
setSources,
85+
setMetrics,
86+
setConfidence,
87+
setGrounding,
88+
setCurrentTicketId,
89+
setCurrentTicket,
90+
setSavedDraftId,
91+
setSavedDraftCreatedAt,
92+
setConversationEntries,
93+
setHandoffTouched,
94+
setSuggestionsDismissed,
95+
setCaseIntake,
96+
setGuidedRunbookSession,
97+
setGuidedRunbookNote,
98+
setRunbookSessionSourceScopeKey,
99+
setRunbookSessionTouched,
100+
setAutosaveDraftId,
101+
setPendingSimilarCaseOpen,
102+
setWorkspaceRunbookScopeKey,
103+
resetChecklist,
104+
resetFirstResponse,
105+
resetApproval,
106+
resetResponseActions,
107+
resetWorkspaceArtifacts,
108+
resetGeneration,
109+
}: UseDraftClearOptions) {
110+
return useCallback(() => {
111+
setInput("");
112+
setOcrText(null);
113+
setDiagnosticNotes("");
114+
setTreeResult(null);
115+
resetChecklist();
116+
resetFirstResponse();
117+
resetApproval();
118+
setResponse("");
119+
setOriginalResponse("");
120+
setIsResponseEdited(false);
121+
setSources([]);
122+
setMetrics(null);
123+
setConfidence(null);
124+
setGrounding([]);
125+
setCurrentTicketId(null);
126+
setCurrentTicket(null);
127+
setSavedDraftId(null);
128+
setSavedDraftCreatedAt(null);
129+
setConversationEntries([]);
130+
setHandoffTouched(false);
131+
resetResponseActions();
132+
setSuggestionsDismissed(false);
133+
setCaseIntake({
134+
...parseCaseIntake(null),
135+
note_audience: preferredNoteAudience,
136+
});
137+
resetWorkspaceArtifacts();
138+
setGuidedRunbookSession(null);
139+
setGuidedRunbookNote("");
140+
setRunbookSessionSourceScopeKey(null);
141+
setRunbookSessionTouched(false);
142+
setAutosaveDraftId(null);
143+
setPendingSimilarCaseOpen(null);
144+
setWorkspaceRunbookScopeKey(createWorkspaceRunbookScopeKey());
145+
resetGeneration();
146+
}, [
147+
preferredNoteAudience,
148+
setInput,
149+
setOcrText,
150+
setDiagnosticNotes,
151+
setTreeResult,
152+
setResponse,
153+
setOriginalResponse,
154+
setIsResponseEdited,
155+
setSources,
156+
setMetrics,
157+
setConfidence,
158+
setGrounding,
159+
setCurrentTicketId,
160+
setCurrentTicket,
161+
setSavedDraftId,
162+
setSavedDraftCreatedAt,
163+
setConversationEntries,
164+
setHandoffTouched,
165+
setSuggestionsDismissed,
166+
setCaseIntake,
167+
setGuidedRunbookSession,
168+
setGuidedRunbookNote,
169+
setRunbookSessionSourceScopeKey,
170+
setRunbookSessionTouched,
171+
setAutosaveDraftId,
172+
setPendingSimilarCaseOpen,
173+
setWorkspaceRunbookScopeKey,
174+
resetChecklist,
175+
resetFirstResponse,
176+
resetApproval,
177+
resetResponseActions,
178+
resetWorkspaceArtifacts,
179+
resetGeneration,
180+
]);
181+
}

0 commit comments

Comments
 (0)