From f4a524ba0bac764485101d1a57ebf53002ffba9f Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Tue, 5 May 2026 13:35:01 -0700 Subject: [PATCH] feat(resume-optimizer): make proposed text editable in review modal and preview Summary and experience bullet fields in the review modal are now editable textareas. Edited values flow through decisions to apply_review_decisions(), which uses edited_text/edited_bullets when the section is accepted. Clearing unwanted LLM-added bullets (empty lines filtered server-side) addresses the extra-bullets issue. The preview textarea in the apply workspace is also now editable; approveResume() passes preview_text_override so manual edits survive the approve step without re-rendering from struct. --- dev-api.py | 3 +- scripts/resume_optimizer.py | 28 ++++++---- web/src/components/ResumeOptimizerPanel.vue | 24 ++++----- web/src/components/ResumeReviewModal.vue | 29 ++++++++--- .../resume-review/ExperiencePage.vue | 51 ++++++++++++++++--- .../components/resume-review/SummaryPage.vue | 30 +++++++++-- 6 files changed, 127 insertions(+), 38 deletions(-) diff --git a/dev-api.py b/dev-api.py index d09b233..98bed9d 100644 --- a/dev-api.py +++ b/dev-api.py @@ -667,7 +667,8 @@ def approve_resume(job_id: int, body: dict): raise HTTPException(400, "preview_struct is required") from scripts.resume_optimizer import render_resume_text - final_text = render_resume_text(struct) + override = (body.get("preview_text_override") or "").strip() + final_text = override if override else render_resume_text(struct) # Persist plain text + struct (struct enables YAML export later) _finalize(db_path=db_path, job_id=job_id, final_text=final_text) diff --git a/scripts/resume_optimizer.py b/scripts/resume_optimizer.py index 9d0298c..b71268a 100644 --- a/scripts/resume_optimizer.py +++ b/scripts/resume_optimizer.py @@ -532,27 +532,37 @@ def apply_review_decisions( struct["skills"] = sorted(original_kept | approved_additions) break - # ── Summary: accept proposed or revert to original ────────────────────── - if not decisions.get("summary", {}).get("accepted", True): + # ── Summary: accept/reject + optional user-edited text ───────────────── + summary_dec = decisions.get("summary", {}) + if not summary_dec.get("accepted", True): for sec in sections: if sec["section"] == "summary": struct["career_summary"] = sec.get("original", struct.get("career_summary", "")) break + else: + edited_text = summary_dec.get("edited_text") + if edited_text is not None: + struct["career_summary"] = edited_text.strip() - # ── Experience: per-entry accept/reject ───────────────────────────────── - exp_decisions: dict[str, bool] = { - f"{ed.get('title', '')}|{ed.get('company', '')}": ed.get("accepted", True) + # ── Experience: per-entry accept/reject + optional user-edited bullets ── + exp_entry_map: dict[str, dict] = { + f"{ed.get('title', '')}|{ed.get('company', '')}": ed for ed in (decisions.get("experience", {}).get("accepted_entries") or []) } for sec in sections: if sec["section"] == "experience": for entry_diff in (sec.get("entries") or []): key = f"{entry_diff['title']}|{entry_diff['company']}" - if not exp_decisions.get(key, True): - for exp_entry in (struct.get("experience") or []): - if (exp_entry.get("title") == entry_diff["title"] and - exp_entry.get("company") == entry_diff["company"]): + entry_dec = exp_entry_map.get(key, {}) + accepted = entry_dec.get("accepted", True) + edited_bullets = entry_dec.get("edited_bullets") + for exp_entry in (struct.get("experience") or []): + if (exp_entry.get("title") == entry_diff["title"] and + exp_entry.get("company") == entry_diff["company"]): + if not accepted: exp_entry["bullets"] = entry_diff["original_bullets"] + elif edited_bullets is not None: + exp_entry["bullets"] = [b for b in edited_bullets if b.strip()] break return struct diff --git a/web/src/components/ResumeOptimizerPanel.vue b/web/src/components/ResumeOptimizerPanel.vue index e076ac0..41b84b3 100644 --- a/web/src/components/ResumeOptimizerPanel.vue +++ b/web/src/components/ResumeOptimizerPanel.vue @@ -112,16 +112,15 @@ Preview — not yet saved