Problem
Two surfaces have outgrown the project's own structure rules. App.tsx carries orchestration logic inline (parse state machine, handleFile, JD memo, formatBytes) instead of delegating to a hook — violating CLAUDE.md "Keep business logic in src/lib/… Extract cross-cutting interaction state into src/hooks/." And Result.tsx is ~390 LOC, well past the ~200 LOC decomposition guideline, bundling six distinct concerns in one file.
Current state
src/App.tsx (231 LOC): ParseState union (L23-34), handleFile (L56-101), jdMatch memo (L46-54), formatBytes (L36-40), reset (L103). All interaction/orchestration state sits in the component.
src/components/Result.tsx (390 LOC): branch + StatusPill + ParsedCard + LimitedParsingCard + AtsScoreReadout (L161-265) + LayoutFlagsList (L133-159) + Dimension (L267-316) + the inline Evidence panel (Source-PDF + extracted-text, L108-128).
Plan
src/hooks/useResumeAnalysis.ts — owns ParseState, handleFile (parse → score → telemetry), reset. Returns { state, handleFile, reset }. App becomes layout-only: drop zone, result region, JD card, footer. formatBytes moves to a small lib/ util (or into the hook).
- Note: if the edit-loop issue (option A) lands, override state + recompute live here too — coordinate so this hook is the single orchestration owner.
- Decompose
Result.tsx:
src/components/features/EvidencePanel.tsx — the Source-PDF + extracted-text two-pane block (currently L108-128).
src/components/features/AtsScoreReadout.tsx — the score readout incl. Dimension (L161-316).
src/components/features/LayoutFlagsList.tsx — (L133-159).
Result.tsx keeps only the isFontsUnmappable branch + ParsedCard/LimitedParsingCard composition. StatusPill is removed in favor of shared/StatusBadge (primitives issue).
- Pure move/extract — no behavior change. Each extracted piece keeps the same props it reads today.
Acceptance criteria
Notes
Coordinates with the shared-primitives issue (StatusBadge removal) and the edit-loop issue (hook ownership). Order: primitives → this → edit-loop, or land together.
Labels
refactor
Problem
Two surfaces have outgrown the project's own structure rules.
App.tsxcarries orchestration logic inline (parse state machine,handleFile, JD memo,formatBytes) instead of delegating to a hook — violating CLAUDE.md "Keep business logic insrc/lib/… Extract cross-cutting interaction state intosrc/hooks/." AndResult.tsxis ~390 LOC, well past the ~200 LOC decomposition guideline, bundling six distinct concerns in one file.Current state
src/App.tsx(231 LOC):ParseStateunion (L23-34),handleFile(L56-101),jdMatchmemo (L46-54),formatBytes(L36-40),reset(L103). All interaction/orchestration state sits in the component.src/components/Result.tsx(390 LOC): branch +StatusPill+ParsedCard+LimitedParsingCard+AtsScoreReadout(L161-265) +LayoutFlagsList(L133-159) +Dimension(L267-316) + the inline Evidence panel (Source-PDF + extracted-text, L108-128).Plan
src/hooks/useResumeAnalysis.ts— ownsParseState,handleFile(parse → score → telemetry),reset. Returns{ state, handleFile, reset }.Appbecomes layout-only: drop zone, result region, JD card, footer.formatBytesmoves to a smalllib/util (or into the hook).Result.tsx:src/components/features/EvidencePanel.tsx— the Source-PDF + extracted-text two-pane block (currently L108-128).src/components/features/AtsScoreReadout.tsx— the score readout incl.Dimension(L161-316).src/components/features/LayoutFlagsList.tsx— (L133-159).Result.tsxkeeps only theisFontsUnmappablebranch +ParsedCard/LimitedParsingCardcomposition.StatusPillis removed in favor ofshared/StatusBadge(primitives issue).Acceptance criteria
App.tsxno longer contains theParseStatemachine orhandleFilebody — it consumesuseResumeAnalysis.Result.tsxis under ~200 LOC;EvidencePanel,AtsScoreReadout,LayoutFlagsListare their own files underfeatures/.npm run test/npm run typecheckpass with no new warnings.Notes
Coordinates with the shared-primitives issue (StatusBadge removal) and the edit-loop issue (hook ownership). Order: primitives → this → edit-loop, or land together.
Labels
refactor