Commit d93ddc4
fix(jd): unify paste / upload / load-from-search through LLM parser
The 2026-05-27 jobagent re-verify report caught the JD parser STILL
leaking section headers ("REQUIREMENTS / MUST-HAVES") and
misclassifying items across sections on Step 03 — even though
backend commit 2d75bae had already shipped the section-label
artifact scrub + benefits keyword filter in
src/services/jd_llm_parser_service.py.
Root cause was a frontend architecture issue, not a regression in
that backend fix:
* JDReview.tsx read its Must-Have / Nice-to-Have data from
EITHER analysisState (post-Step-04 LLM pipeline) OR a frontend
`buildJobReview` regex over the raw text. It NEVER read from
`jobFileState` — the LLM-parsed response from the
/workspace/job-description/upload endpoint, which already had
the cleaned LLM data sitting unused in state after every file
upload.
* Pasted text was an even bigger gap: pasting hit nothing but the
frontend regex. No backend call at all until Step 04 fired —
which a Step-03-only verifier with no résumé loaded can never
trigger.
Two-phase fix, both in this commit because they only deliver value
together (Phase 1 adds the display path; Phase 2 fills it for the
paste / load-from-search paths that didn't populate jobFileState
before).
Phase 1 — JDReview reads jobFileState (frontend wiring only,
no new backend calls):
* Introduced a unified precedence chain used everywhere derived
JD fields are computed:
analysisState (Step 04, !stale) > jobFileState > review (regex)
* Applies to: heroTitle, heroLocation, heroSource label,
hardSkills, softSkills, summaryText, bodySections (Must-Have
Themes / Nice-to-Have Signals / etc.), and the new
pre-analysis Hard-skills / Years-required metric tiles.
* Removed the now-unused summaryHeadlineFromAnalysis helper.
* Existing Match-Score tile stays placeholder pre-analysis
(jobFileState has no fit_analysis — only Step 04 produces that).
Phase 2 — auto-parse pasted / loaded text via the existing
upload endpoint (debounced + cached):
* New useEffect in WorkspaceShell watches manualJobText. After
1500ms of no changes AND text >=100 chars AND auth signed_in,
fires uploadJobDescriptionFile with a synthetic ``pasted.txt``
Blob. The backend extracts text from .txt as a no-op and
routes the same build_job_description_from_text_auto path the
file-upload UI uses.
* Result lands in jobFileState — same state slot, same shape, so
Phase 1's display wiring picks it up automatically.
* Refs (lastParsedTextRef / parseAbortRef / parseDebounceRef)
handle: dedup (skip re-parsing identical text), abort
(in-flight requests cancel if the user keeps typing), debounce
(timer resets on every keystroke).
* Already-uploaded text via the explicit upload UI also gets
short-circuited via the
``jobFileState.job_description_text === text`` check — avoids
a second redundant parse of the same content.
* Failures don't surface a toast (regex preview still renders);
quota / auth errors come through the request wrapper's own
interceptor.
Result: ALL three input paths (paste / upload / load-from-search)
hit the LLM parser within ~1.5s of the user settling on text. The
deterministic regex remains only as a true fallback for the
brief debounce window, auth-loading states, and offline scenarios.
tsc + eslint clean on touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent ef493df commit d93ddc4
2 files changed
Lines changed: 188 additions & 22 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | 129 | | |
137 | 130 | | |
138 | 131 | | |
| |||
157 | 150 | | |
158 | 151 | | |
159 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
160 | 185 | | |
161 | 186 | | |
162 | | - | |
163 | | - | |
| 187 | + | |
164 | 188 | | |
165 | 189 | | |
166 | 190 | | |
| |||
171 | 195 | | |
172 | 196 | | |
173 | 197 | | |
| 198 | + | |
174 | 199 | | |
175 | 200 | | |
176 | 201 | | |
177 | 202 | | |
178 | 203 | | |
179 | | - | |
| 204 | + | |
180 | 205 | | |
181 | 206 | | |
182 | 207 | | |
| |||
197 | 222 | | |
198 | 223 | | |
199 | 224 | | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
200 | 229 | | |
201 | 230 | | |
202 | 231 | | |
| |||
224 | 253 | | |
225 | 254 | | |
226 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
227 | 287 | | |
228 | 288 | | |
229 | 289 | | |
| |||
259 | 319 | | |
260 | 320 | | |
261 | 321 | | |
| 322 | + | |
| 323 | + | |
262 | 324 | | |
263 | | - | |
| 325 | + | |
264 | 326 | | |
265 | 327 | | |
266 | 328 | | |
267 | 329 | | |
| 330 | + | |
268 | 331 | | |
269 | | - | |
270 | | - | |
271 | | - | |
| 332 | + | |
272 | 333 | | |
273 | | - | |
274 | | - | |
275 | | - | |
| 334 | + | |
276 | 335 | | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
277 | 342 | | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
283 | 348 | | |
284 | 349 | | |
285 | 350 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
516 | 516 | | |
517 | 517 | | |
518 | 518 | | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
519 | 620 | | |
520 | 621 | | |
521 | 622 | | |
| |||
0 commit comments