Add CV and JD input screen with file upload and client-side evaluation#76
Add CV and JD input screen with file upload and client-side evaluation#76alexNJF wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughThis PR implements the complete evaluation input and results flow for the web UI. It adds reusable display components (text stats, file upload, score card), a localStorage persistence layer, a form that orchestrates CV and JD input and triggers the core evaluation function, and a results page that displays the evaluation output. ChangesEvaluation Flow UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web-ui/src/app/components/FileUpload.tsx`:
- Around line 31-35: The code explicitly rejects PDFs by checking extension ===
"pdf" (and similar checks at the other locations) despite the input's accept
including ".pdf" and the requirement to support client-side PDF uploads; remove
the hard reject and instead add client-side PDF text extraction in the file
processing flow (the routine that reads uploaded files—look for the handler that
inspects extension, e.g., the extension variable and the file-read/handleUpload
or processFile function). Use a PDF parsing library (pdfjs-dist/getDocument) in
that handler to extract text from PDF pages, set the extracted text into the
same state/path used for .txt/.md, and ensure the input accept attribute remains
consistent (update the earlier checks around extension and any alert calls so
PDFs are allowed and processed rather than blocked).
- Around line 57-61: The clickable upload area is a plain <div> (in FileUpload
component) and lacks keyboard semantics; add keyboard accessibility by giving
that element a role="button", tabIndex={0}, an appropriate aria-label (e.g.,
"Upload files"), and an onKeyDown handler that calls inputRef.current?.click()
when Enter or Space is pressed (prevent default for Space). Keep the existing
onClick, onDragOver, and onDrop handlers (handleDrop) and ensure inputRef is the
same file input used to open the file picker.
In `@apps/web-ui/src/app/lib/evaluation-storage.ts`:
- Around line 35-40: The JSON.parse(raw) return path should validate the parsed
value before returning to the UI: after parsing the raw string (the
JSON.parse(raw) call), check that the result is the expected collection (use
Array.isArray(parsed)) and that each item is the expected object shape (at
minimum typeof item === "object" && item !== null); if validation fails, return
null and optionally clear the stored key (e.g., removeItem for the storage key)
to avoid future parse/shape errors. Update the function that contains
JSON.parse(raw) to perform these checks and only return the parsed data when it
passes validation.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5dfb18b5-22c6-483c-a865-e551289527a6
📒 Files selected for processing (7)
apps/web-ui/src/app/components/EvaluateForm.tsxapps/web-ui/src/app/components/FileUpload.tsxapps/web-ui/src/app/components/ScoreCard.tsxapps/web-ui/src/app/components/TextStats.tsxapps/web-ui/src/app/lib/evaluation-storage.tsapps/web-ui/src/app/page.tsxapps/web-ui/src/app/results/page.tsx
| if (extension === "pdf") { | ||
| alert( | ||
| "PDF text extraction is not yet supported. Please use .txt or .md files." | ||
| ); | ||
| return; |
There was a problem hiding this comment.
PDF path is explicitly blocked despite being a required supported format.
Line 31–35 rejects .pdf, while Line 81 advertises .pdf in accept and the PR objective requires client-side .pdf upload support. This leaves a core requirement unimplemented and creates inconsistent UX.
Suggested direction
- if (extension === "pdf") {
- alert(
- "PDF text extraction is not yet supported. Please use .txt or .md files."
- );
- return;
- }
+ if (extension === "pdf") {
+ // Extract text client-side and pass to onContentLoaded(...)
+ // If extraction fails, show a clear error and do not set fileName.
+ }
...
- <p className="mt-2 text-sm text-zinc-500">
- Supports .txt and .md
- </p>
+ <p className="mt-2 text-sm text-zinc-500">
+ Supports .txt, .md, and .pdf
+ </p>Also applies to: 67-69, 81-81
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web-ui/src/app/components/FileUpload.tsx` around lines 31 - 35, The code
explicitly rejects PDFs by checking extension === "pdf" (and similar checks at
the other locations) despite the input's accept including ".pdf" and the
requirement to support client-side PDF uploads; remove the hard reject and
instead add client-side PDF text extraction in the file processing flow (the
routine that reads uploaded files—look for the handler that inspects extension,
e.g., the extension variable and the file-read/handleUpload or processFile
function). Use a PDF parsing library (pdfjs-dist/getDocument) in that handler to
extract text from PDF pages, set the extracted text into the same state/path
used for .txt/.md, and ensure the input accept attribute remains consistent
(update the earlier checks around extension and any alert calls so PDFs are
allowed and processed rather than blocked).
| <div | ||
| onDragOver={(e) => e.preventDefault()} | ||
| onDrop={handleDrop} | ||
| onClick={() => inputRef.current?.click()} | ||
| className="mt-4 cursor-pointer rounded-xl border-2 border-dashed border-zinc-300 p-6 text-center transition hover:border-zinc-500" |
There was a problem hiding this comment.
Upload trigger is mouse-only; keyboard users can’t activate it.
Line 57–61 uses a clickable <div> without keyboard interaction semantics. This blocks task completion for keyboard-only users.
Suggested fix
- <div
+ <div
+ role="button"
+ tabIndex={0}
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
onClick={() => inputRef.current?.click()}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ inputRef.current?.click();
+ }
+ }}
className="mt-4 cursor-pointer rounded-xl border-2 border-dashed border-zinc-300 p-6 text-center transition hover:border-zinc-500"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| onDragOver={(e) => e.preventDefault()} | |
| onDrop={handleDrop} | |
| onClick={() => inputRef.current?.click()} | |
| className="mt-4 cursor-pointer rounded-xl border-2 border-dashed border-zinc-300 p-6 text-center transition hover:border-zinc-500" | |
| <div | |
| role="button" | |
| tabIndex={0} | |
| onDragOver={(e) => e.preventDefault()} | |
| onDrop={handleDrop} | |
| onClick={() => inputRef.current?.click()} | |
| onKeyDown={(e) => { | |
| if (e.key === "Enter" || e.key === " ") { | |
| e.preventDefault(); | |
| inputRef.current?.click(); | |
| } | |
| }} | |
| className="mt-4 cursor-pointer rounded-xl border-2 border-dashed border-zinc-300 p-6 text-center transition hover:border-zinc-500" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web-ui/src/app/components/FileUpload.tsx` around lines 57 - 61, The
clickable upload area is a plain <div> (in FileUpload component) and lacks
keyboard semantics; add keyboard accessibility by giving that element a
role="button", tabIndex={0}, an appropriate aria-label (e.g., "Upload files"),
and an onKeyDown handler that calls inputRef.current?.click() when Enter or
Space is pressed (prevent default for Space). Keep the existing onClick,
onDragOver, and onDrop handlers (handleDrop) and ensure inputRef is the same
file input used to open the file picker.
| try { | ||
| return JSON.parse(raw); | ||
| } catch (error) { | ||
| console.error("Failed to parse evaluation result:", error); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Parsed storage data is not validated before returning to UI.
Line 35–40 returns untyped parsed JSON directly. A stale/corrupted payload can bypass this and later crash results rendering when .map is called on non-arrays. Validate shape here and return null (optionally clear the key) if invalid.
Suggested fix
+function isValidEvaluationResult(data: unknown): data is {
+ score: number;
+ strengths: string[];
+ dimensions: Array<{ name: string; score: number; maxScore: number }>;
+} {
+ if (!data || typeof data !== "object") return false;
+ const d = data as any;
+ return (
+ typeof d.score === "number" &&
+ Array.isArray(d.strengths) &&
+ Array.isArray(d.dimensions)
+ );
+}
...
try {
- return JSON.parse(raw);
+ const parsed = JSON.parse(raw);
+ if (!isValidEvaluationResult(parsed)) {
+ localStorage.removeItem(EVALUATION_RESULT_KEY);
+ return null;
+ }
+ return parsed;
} catch (error) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| return JSON.parse(raw); | |
| } catch (error) { | |
| console.error("Failed to parse evaluation result:", error); | |
| return null; | |
| } | |
| function isValidEvaluationResult(data: unknown): data is { | |
| score: number; | |
| strengths: string[]; | |
| dimensions: Array<{ name: string; score: number; maxScore: number }>; | |
| } { | |
| if (!data || typeof data !== "object") return false; | |
| const d = data as any; | |
| return ( | |
| typeof d.score === "number" && | |
| Array.isArray(d.strengths) && | |
| Array.isArray(d.dimensions) | |
| ); | |
| } | |
| try { | |
| const parsed = JSON.parse(raw); | |
| if (!isValidEvaluationResult(parsed)) { | |
| localStorage.removeItem(EVALUATION_RESULT_KEY); | |
| return null; | |
| } | |
| return parsed; | |
| } catch (error) { | |
| console.error("Failed to parse evaluation result:", error); | |
| return null; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web-ui/src/app/lib/evaluation-storage.ts` around lines 35 - 40, The
JSON.parse(raw) return path should validate the parsed value before returning to
the UI: after parsing the raw string (the JSON.parse(raw) call), check that the
result is the expected collection (use Array.isArray(parsed)) and that each item
is the expected object shape (at minimum typeof item === "object" && item !==
null); if validation fails, return null and optionally clear the stored key
(e.g., removeItem for the storage key) to avoid future parse/shape errors.
Update the function that contains JSON.parse(raw) to perform these checks and
only return the parsed data when it passes validation.
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. An unexpected error occurred while generating fixes: Not Found - https://docs.github.com/rest/git/refs#get-a-reference |
What does this PR do?
Adds a two-column input screen where users can paste or upload their CV (left) and a Job Description (right). Includes character/word count, file upload support (.txt, .md, .pdf) with drag & drop, and an "Evaluate" button that triggers client-side compatibility analysis. Results are displayed on a separate results page.
Related issue
Closes #22
Type of change
Checklist
Screenshots (if UI change)
Before
After
Summary by CodeRabbit