refactor(career): Phase 1 increment 2 — extract editor sections, hide duplicate CV concepts

UI-only. No change to APIs, save payloads, extraction behaviour, or data
models. The parent CareerProfilePage still owns loading, state, saving,
and all extraction/import actions; the new sections are presentational
(value + onChange, plus a getMetadata callback for review chips).

Extracted into src/views/career/CareerProfileSections.tsx:
PersonalInformation, ProfessionalSummary, Skills, Interests, Languages,
WorkExperience, Education, OtherSections. FieldReviewNote + confidenceTone
moved there verbatim and shared with the parent. CareerProfilePage went
from 1376 to ~1200 lines.

No Projects/Certifications sections were created -- the editor never had
them (they are not editable structured fields here). Inventing them would
add functionality, which this refactor avoids; noted for a product
decision later.

Hid the duplicate CV concepts behind an "Advanced CV tools" toggle,
collapsed by default: the CV Structure Overview parse block and the
Template-driven CV builder. Both stay mounted and functional (gated with
display:none), so no tested functionality is removed -- the real CV
Builder at /career/builder is the single generation surface. Future
removal plan documented.

Tests: added "editing a field in an extracted section updates parent
state and flows into save" (render -> edit -> PUT /career/profile
{profile,cvText}); existing parse/rewrite tests reveal the advanced tools
first. The increment-1 save-invariant test still pins the payload.

Verified: tsc clean, production build clean, 137 frontend tests pass.
Backend untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-20 11:00:26 +02:00
parent 63473bae85
commit fe9cd4dda1
4 changed files with 361 additions and 181 deletions
+36
View File
@@ -222,6 +222,9 @@ test('profile page loads persisted structured cv and can re-parse it', async ()
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'true');
expect(await screen.findByLabelText(/profile cv \/ master resume text/i)).toHaveValue('Professional Summary\nBuilt backend systems');
// Structure overview is hidden from the default workflow (Phase 1 increment 2); reveal it.
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
const analyzeButton = screen.getByRole('button', { name: /read sections/i });
await waitFor(() => expect(analyzeButton).toBeEnabled());
fireEvent.click(analyzeButton);
@@ -251,6 +254,33 @@ test('saving the career profile PUTs the structured profile and cv text unchange
});
});
test('editing a field in an extracted section updates parent state and flows into save (Phase 1 increment 2)', async () => {
renderPage();
// Renders existing data through the extracted PersonalInformationSection.
const nameField = await screen.findByLabelText(/full name/i);
expect(nameField).toHaveValue('Demo User');
// Editing the child field updates the parent's structuredCv (controlled input reflects it back).
fireEvent.change(nameField, { target: { value: 'Edited Name' } });
expect(nameField).toHaveValue('Edited Name');
const saveButton = screen.getByRole('button', { name: /save changes/i });
await waitFor(() => expect(saveButton).toBeEnabled());
fireEvent.click(saveButton);
// The edited value reaches the unchanged save path.
await waitFor(() => {
expect(mockedApi.put).toHaveBeenCalledWith(
'/career/profile',
expect.objectContaining({
profile: expect.objectContaining({ contact: expect.objectContaining({ fullName: 'Edited Name' }) }),
cvText: expect.any(String),
}),
);
});
});
test('profile page can reprocess from stored artifact history', async () => {
renderPage();
@@ -269,6 +299,9 @@ test('profile page keeps raw extraction collapsed until expanded', async () => {
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
expect(screen.getByText(/your career information stays front and center/i)).toBeInTheDocument();
// Reveal the advanced tools (Phase 1 increment 2) — the template-builder copy button lives there.
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
const originalExtractionToggle = screen.getByRole('button', { name: /original import/i });
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'false');
const copyButton = screen.getByRole('button', { name: /copy cv text/i });
@@ -285,6 +318,9 @@ test('profile page keeps raw extraction collapsed until expanded', async () => {
test('profile page rewrite tools use selected template and saved job context', async () => {
renderPage();
// Template-driven CV builder is hidden from the default workflow (Phase 1 increment 2); reveal it.
await screen.findByRole('button', { name: /advanced cv tools/i });
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
expect(await screen.findByText(/template-driven cv builder/i)).toBeInTheDocument();
fireEvent.click(screen.getByText(/harvard/i));
fireEvent.change(screen.getByLabelText(/prompt-based cv brief/i), { target: { value: 'Highlight backend platform ownership, distributed systems, and cross-team delivery.' } });