Skip to content

Commit 4e1c9d5

Browse files
saagar210claude
andcommitted
refactor(components): extract DraftTab defaults and diagnosis serializer
DraftTab.tsx was 1412 lines of orchestration, of which ~100 were module-scoped defaults/helpers and ~70 were a pure JSON serializer living inside a useCallback. Neither needs React to exist — both are side-effect-free data code the component imports once at top of file. Extracts to siblings: draftTabDefaults.ts (new, 122 LOC) - DRAFT_PANEL_DENSITY_STORAGE_KEY - WORKSPACE_PERSONALIZATION_STORAGE_KEY - DEFAULT_WORKSPACE_PERSONALIZATION - DEFAULT_RUNBOOK_TEMPLATES - loadWorkspacePersonalization() - createWorkspaceRunbookScopeKey() (+ private createWorkspaceScopeSeed) - readPanelDensityMode() — folds the ad-hoc useState initializer block that was reading/validating localStorage inline - DraftPanelDensityMode type buildDiagnosisJson.ts (new, 104 LOC) - Pure function taking a BuildDiagnosisJsonInput bundle; returns the serialized workspace-diagnosis JSON string (or null when empty). DraftTab's useCallback is now a thin wrapper delegating to it, preserving dep-array stability for downstream hooks that consume the memoized identity (useWorkspaceDerivedArtifacts, useWorkspaceDraftState, useDraftPersistence). DraftTab.tsx: 1412 -> 1278 lines (-134, -9.5%). Net project LOC grows by ~90 from the extraction scaffolding, but the orchestrator file is easier to scan and the two new files are independently testable without rendering the full workspace. No behavior change. Typecheck + vitest (241/241) + eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 04473dc commit 4e1c9d5

3 files changed

Lines changed: 272 additions & 180 deletions

File tree

src/components/Draft/DraftTab.tsx

Lines changed: 46 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import {
55
useImperativeHandle,
66
useMemo,
77
} from "react";
8+
import { buildDiagnosisJson as buildDiagnosisJsonImpl } from "./buildDiagnosisJson";
9+
import {
10+
type DraftPanelDensityMode,
11+
DEFAULT_RUNBOOK_TEMPLATES,
12+
DRAFT_PANEL_DENSITY_STORAGE_KEY,
13+
WORKSPACE_PERSONALIZATION_STORAGE_KEY,
14+
createWorkspaceRunbookScopeKey,
15+
loadWorkspacePersonalization,
16+
readPanelDensityMode,
17+
} from "./draftTabDefaults";
818
import { auditResponseCopyOverride, exportDraft } from "./draftTauriCommands";
919
import { DraftResponsePanel } from "./DraftResponsePanel";
1020
import { InputPanel } from "./InputPanel";
@@ -56,7 +66,6 @@ import type {
5666
} from "../../types/llm";
5767
import type { ContextSource } from "../../types/knowledge";
5868
import type {
59-
GuidedRunbookTemplate,
6069
NextActionRecommendation,
6170
ResponseLength,
6271
SavedDraft,
@@ -81,103 +90,6 @@ interface DraftTabProps {
8190
revampModeEnabled?: boolean;
8291
}
8392

84-
type DraftPanelDensityMode = "balanced" | "focus-intake" | "focus-response";
85-
86-
const DRAFT_PANEL_DENSITY_STORAGE_KEY = "draft-panel-density-mode";
87-
const WORKSPACE_PERSONALIZATION_STORAGE_KEY =
88-
"assistsupport.workspace.personalization.v1";
89-
90-
const DEFAULT_WORKSPACE_PERSONALIZATION: WorkspacePersonalization = {
91-
preferred_note_audience: "internal-note",
92-
preferred_output_length: "Medium",
93-
favorite_queue_view: "all",
94-
default_evidence_format: "clipboard",
95-
};
96-
97-
const DEFAULT_RUNBOOK_TEMPLATES: Array<Omit<GuidedRunbookTemplate, "id">> = [
98-
{
99-
name: "Security Incident",
100-
scenario: "security-incident",
101-
steps: [
102-
"Acknowledge the incident",
103-
"Confirm scope and impacted users",
104-
"Contain access or affected systems",
105-
"Notify stakeholders",
106-
"Prepare escalation or recovery note",
107-
],
108-
},
109-
{
110-
name: "Access Request Review",
111-
scenario: "access-request",
112-
steps: [
113-
"Confirm requester identity",
114-
"Check policy or entitlement path",
115-
"Verify required approver",
116-
"Document evidence and approval state",
117-
"Communicate approved or denied outcome",
118-
],
119-
},
120-
{
121-
name: "Device Troubleshooting",
122-
scenario: "device-troubleshooting",
123-
steps: [
124-
"Capture symptoms and environment",
125-
"Verify recent changes",
126-
"Run standard checks or reboot path",
127-
"Collect logs or screenshots",
128-
"Escalate with evidence if unresolved",
129-
],
130-
},
131-
];
132-
133-
function loadWorkspacePersonalization(): WorkspacePersonalization {
134-
if (typeof window === "undefined") {
135-
return DEFAULT_WORKSPACE_PERSONALIZATION;
136-
}
137-
138-
try {
139-
const raw = window.localStorage.getItem(
140-
WORKSPACE_PERSONALIZATION_STORAGE_KEY,
141-
);
142-
if (!raw) {
143-
return DEFAULT_WORKSPACE_PERSONALIZATION;
144-
}
145-
146-
const parsed = JSON.parse(raw) as Partial<WorkspacePersonalization>;
147-
return {
148-
preferred_note_audience:
149-
parsed.preferred_note_audience ??
150-
DEFAULT_WORKSPACE_PERSONALIZATION.preferred_note_audience,
151-
preferred_output_length:
152-
parsed.preferred_output_length ??
153-
DEFAULT_WORKSPACE_PERSONALIZATION.preferred_output_length,
154-
favorite_queue_view:
155-
parsed.favorite_queue_view ??
156-
DEFAULT_WORKSPACE_PERSONALIZATION.favorite_queue_view,
157-
default_evidence_format:
158-
parsed.default_evidence_format ??
159-
DEFAULT_WORKSPACE_PERSONALIZATION.default_evidence_format,
160-
};
161-
} catch {
162-
return DEFAULT_WORKSPACE_PERSONALIZATION;
163-
}
164-
}
165-
166-
function createWorkspaceScopeSeed(): string {
167-
if (
168-
typeof crypto !== "undefined" &&
169-
typeof crypto.randomUUID === "function"
170-
) {
171-
return crypto.randomUUID();
172-
}
173-
174-
return `workspace-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
175-
}
176-
177-
function createWorkspaceRunbookScopeKey(): string {
178-
return `workspace:${createWorkspaceScopeSeed()}`;
179-
}
180-
18193
export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
18294
function DraftTab(
18395
{ initialDraft, onNavigateToSource, revampModeEnabled = false },
@@ -296,17 +208,7 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
296208
);
297209
});
298210
const [panelDensityMode, setPanelDensityMode] =
299-
useState<DraftPanelDensityMode>(() => {
300-
const stored = localStorage.getItem(DRAFT_PANEL_DENSITY_STORAGE_KEY);
301-
if (
302-
stored === "balanced" ||
303-
stored === "focus-intake" ||
304-
stored === "focus-response"
305-
) {
306-
return stored;
307-
}
308-
return "balanced";
309-
});
211+
useState<DraftPanelDensityMode>(readPanelDensityMode);
310212
const [conversationEntries, setConversationEntries] = useState<
311213
ConversationEntry[]
312214
>([]);
@@ -673,77 +575,41 @@ export const DraftTab = forwardRef<DraftTabHandle, DraftTabProps>(
673575
[modelLoaded, responseLength, generateStreaming, clearStreamingText],
674576
);
675577

676-
const buildDiagnosisJson = useCallback(() => {
677-
const completedIds = Object.keys(checklistCompleted).filter(
678-
(id) => checklistCompleted[id],
679-
);
680-
const checklistState =
681-
checklistItems.length > 0
682-
? { items: checklistItems, completed_ids: completedIds }
683-
: null;
684-
const firstResponseState = firstResponse.trim()
685-
? { text: firstResponse, tone: firstResponseTone }
686-
: null;
687-
const approvalState =
688-
approvalQuery.trim() ||
689-
approvalSummary.trim() ||
690-
approvalSources.length > 0
691-
? {
692-
query: approvalQuery,
693-
summary: approvalSummary,
694-
sources: approvalSources,
695-
}
696-
: null;
697-
const trustState =
698-
confidence || grounding.length > 0 ? { confidence, grounding } : null;
699-
700-
const diagnosisData: Record<string, unknown> = {};
701-
if (diagnosticNotes.trim()) {
702-
diagnosisData.notes = diagnosticNotes;
703-
}
704-
if (treeResult) {
705-
diagnosisData.treeResult = treeResult;
706-
}
707-
if (checklistState) {
708-
diagnosisData.checklist = checklistState;
709-
}
710-
if (firstResponseState) {
711-
diagnosisData.firstResponse = firstResponseState;
712-
}
713-
if (approvalState) {
714-
diagnosisData.approval = approvalState;
715-
}
716-
if (trustState) {
717-
diagnosisData.trust = trustState;
718-
}
719-
if (savedDraftId) {
720-
diagnosisData.workspaceSavedDraftId = savedDraftId;
721-
diagnosisData.workspaceSavedDraftCreatedAt =
722-
savedDraftCreatedAt ?? new Date().toISOString();
723-
}
724-
if (guidedRunbookNote.trim()) {
725-
diagnosisData.guidedRunbookDraftNote = guidedRunbookNote;
726-
}
727-
728-
return Object.keys(diagnosisData).length > 0
729-
? JSON.stringify(diagnosisData)
730-
: null;
731-
}, [
732-
checklistCompleted,
733-
checklistItems,
734-
firstResponse,
735-
firstResponseTone,
736-
approvalQuery,
737-
approvalSummary,
738-
approvalSources,
739-
diagnosticNotes,
740-
treeResult,
741-
confidence,
742-
grounding,
743-
guidedRunbookNote,
744-
savedDraftCreatedAt,
745-
savedDraftId,
746-
]);
578+
const buildDiagnosisJson = useCallback(
579+
() =>
580+
buildDiagnosisJsonImpl({
581+
checklistItems,
582+
checklistCompleted,
583+
firstResponse,
584+
firstResponseTone,
585+
approvalQuery,
586+
approvalSummary,
587+
approvalSources,
588+
diagnosticNotes,
589+
treeResult,
590+
confidence,
591+
grounding,
592+
guidedRunbookNote,
593+
savedDraftId,
594+
savedDraftCreatedAt,
595+
}),
596+
[
597+
checklistCompleted,
598+
checklistItems,
599+
firstResponse,
600+
firstResponseTone,
601+
approvalQuery,
602+
approvalSummary,
603+
approvalSources,
604+
diagnosticNotes,
605+
treeResult,
606+
confidence,
607+
grounding,
608+
guidedRunbookNote,
609+
savedDraftCreatedAt,
610+
savedDraftId,
611+
],
612+
);
747613

748614
const {
749615
handoffPack,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type {
2+
ChecklistItem,
3+
ConfidenceAssessment,
4+
GroundedClaim,
5+
} from "../../types/llm";
6+
import type { TreeResult } from "./DiagnosisPanel";
7+
import type { ContextSource } from "../../types/knowledge";
8+
9+
export interface BuildDiagnosisJsonInput {
10+
checklistItems: ChecklistItem[];
11+
checklistCompleted: Record<string, boolean>;
12+
firstResponse: string;
13+
firstResponseTone: string;
14+
approvalQuery: string;
15+
approvalSummary: string;
16+
approvalSources: ContextSource[];
17+
diagnosticNotes: string;
18+
treeResult: TreeResult | null;
19+
confidence: ConfidenceAssessment | null;
20+
grounding: GroundedClaim[];
21+
guidedRunbookNote: string;
22+
savedDraftId: string | null;
23+
savedDraftCreatedAt: string | null;
24+
}
25+
26+
/**
27+
* Serializes the workspace's diagnosis state into a JSON string payload that
28+
* is persisted alongside a saved draft. Returns null when no meaningful state
29+
* is present (e.g., an empty workspace with no notes, checklist, or trust
30+
* signals). Pure function — no React bindings.
31+
*/
32+
export function buildDiagnosisJson(
33+
input: BuildDiagnosisJsonInput,
34+
): string | null {
35+
const {
36+
checklistItems,
37+
checklistCompleted,
38+
firstResponse,
39+
firstResponseTone,
40+
approvalQuery,
41+
approvalSummary,
42+
approvalSources,
43+
diagnosticNotes,
44+
treeResult,
45+
confidence,
46+
grounding,
47+
guidedRunbookNote,
48+
savedDraftId,
49+
savedDraftCreatedAt,
50+
} = input;
51+
52+
const completedIds = Object.keys(checklistCompleted).filter(
53+
(id) => checklistCompleted[id],
54+
);
55+
const checklistState =
56+
checklistItems.length > 0
57+
? { items: checklistItems, completed_ids: completedIds }
58+
: null;
59+
const firstResponseState = firstResponse.trim()
60+
? { text: firstResponse, tone: firstResponseTone }
61+
: null;
62+
const approvalState =
63+
approvalQuery.trim() || approvalSummary.trim() || approvalSources.length > 0
64+
? {
65+
query: approvalQuery,
66+
summary: approvalSummary,
67+
sources: approvalSources,
68+
}
69+
: null;
70+
const trustState =
71+
confidence || grounding.length > 0 ? { confidence, grounding } : null;
72+
73+
const diagnosisData: Record<string, unknown> = {};
74+
if (diagnosticNotes.trim()) {
75+
diagnosisData.notes = diagnosticNotes;
76+
}
77+
if (treeResult) {
78+
diagnosisData.treeResult = treeResult;
79+
}
80+
if (checklistState) {
81+
diagnosisData.checklist = checklistState;
82+
}
83+
if (firstResponseState) {
84+
diagnosisData.firstResponse = firstResponseState;
85+
}
86+
if (approvalState) {
87+
diagnosisData.approval = approvalState;
88+
}
89+
if (trustState) {
90+
diagnosisData.trust = trustState;
91+
}
92+
if (savedDraftId) {
93+
diagnosisData.workspaceSavedDraftId = savedDraftId;
94+
diagnosisData.workspaceSavedDraftCreatedAt =
95+
savedDraftCreatedAt ?? new Date().toISOString();
96+
}
97+
if (guidedRunbookNote.trim()) {
98+
diagnosisData.guidedRunbookDraftNote = guidedRunbookNote;
99+
}
100+
101+
return Object.keys(diagnosisData).length > 0
102+
? JSON.stringify(diagnosisData)
103+
: null;
104+
}

0 commit comments

Comments
 (0)