-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Resume-Matcher: Full-Stack Feature Suite - Dark Mode, Multi-Format Export, Resume Validation Service, and Developer Documentation #545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR introduces a resume validation API endpoint with detailed analysis, implements dark mode theming for the frontend with a theme provider and toggle component, and adds comprehensive export utilities for analysis data in CSV, JSON, and HTML formats. Documentation is expanded with contribution guidelines and troubleshooting guidance. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ValidationAPI as Validation Endpoint
participant ResumeValidator
participant Checks as Validation Checks
Client->>ValidationAPI: POST /validate (resume content)
ValidationAPI->>ValidationAPI: Validate non-empty content
alt Empty Content
ValidationAPI-->>Client: HTTP 400 Error
end
ValidationAPI->>ResumeValidator: instantiate()
ValidationAPI->>ResumeValidator: validate(content)
ResumeValidator->>Checks: _check_content_length()
ResumeValidator->>Checks: _check_critical_sections()
ResumeValidator->>Checks: _check_formatting()
ResumeValidator->>Checks: _check_keywords()
ResumeValidator->>Checks: _find_sections()
ResumeValidator->>Checks: _calculate_score()
Checks-->>ResumeValidator: Collect issues & score
ResumeValidator-->>ValidationAPI: Return validation result
alt Validation Success
ValidationAPI-->>Client: HTTP 200 ValidationResponse
else Internal Error
ValidationAPI-->>Client: HTTP 500 Error
end
sequenceDiagram
participant User
participant ExportBtn as Export Buttons
participant ExportUtils as Export Utilities
participant Browser as Browser API
User->>ExportBtn: Click CSV/JSON/PDF button
ExportBtn->>ExportUtils: Call exportAsCSV/JSON/HTML()
ExportUtils->>ExportUtils: Transform data to format
ExportUtils-->>ExportBtn: Return formatted string
ExportBtn->>ExportUtils: downloadFile(content, filename, mimeType)
ExportUtils->>Browser: Create Blob & object URL
ExportUtils->>Browser: Programmatic link click
Browser-->>User: Download file
ExportUtils->>Browser: Cleanup object URL
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (3)
SETUP.md (1)
281-489: Consider condensing the FAQ section.The FAQ section is comprehensive (200+ lines) but could benefit from better organization. Consider moving some content to separate documentation files (e.g., DEPLOYMENT.md for production questions, API.md for API-specific questions) to improve maintainability and navigation.
apps/frontend/components/common/theme-toggle.tsx (1)
23-23: Consider extracting the lengthy className.The className string spans multiple Tailwind utilities. For better maintainability, consider using
clsxorcnutility, or extracting common button styles into a reusable constant.Example refactor:
const toggleButtonStyles = "relative inline-flex items-center justify-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100 transition-colors"; <Button variant="ghost" size="sm" onClick={toggleTheme} className={toggleButtonStyles} aria-label="Toggle theme" >apps/backend/app/api/router/v1/validation.py (1)
14-68: InjectResumeValidatorvia FastAPI dependencies.Per our backend API guidelines we resolve services through
Depends(...)instead of constructing them inside the route. That keeps wiring consistent, enables reuse/testing, and lets us extend the validator later without touching every handler. Please add aget_resume_validator()provider and inject it viaDepends.-from fastapi import APIRouter, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status ... -validation_router = APIRouter() +validation_router = APIRouter() + + +def get_resume_validator() -> ResumeValidator: + return ResumeValidator() ... -async def validate_resume(request: ValidationRequest) -> ValidationResponse: +async def validate_resume( + request: ValidationRequest, + validator: ResumeValidator = Depends(get_resume_validator), +) -> ValidationResponse: ... - validator = ResumeValidator() - validation_result = validator.validate(request.content) + validation_result = validator.validate(request.content)As per coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
README.md(1 hunks)Resume-Matcher(1 hunks)SETUP.md(1 hunks)apps/backend/app/api/router/v1/__init__.py(1 hunks)apps/backend/app/api/router/v1/validation.py(1 hunks)apps/backend/app/services/__init__.py(2 hunks)apps/backend/app/services/validation_service.py(1 hunks)apps/frontend/components/common/theme-provider.tsx(1 hunks)apps/frontend/components/common/theme-toggle.tsx(1 hunks)apps/frontend/components/dashboard/export-buttons.tsx(1 hunks)apps/frontend/hooks/use-theme.ts(1 hunks)apps/frontend/lib/export-utils.ts(1 hunks)apps/frontend/tailwind.config.js(2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
apps/frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use TypeScript strict mode with proper interface definitions in frontend code
Files:
apps/frontend/hooks/use-theme.tsapps/frontend/components/dashboard/export-buttons.tsxapps/frontend/components/common/theme-provider.tsxapps/frontend/lib/export-utils.tsapps/frontend/components/common/theme-toggle.tsx
apps/frontend/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/frontend/components/**/*.{ts,tsx}: Follow component composition patterns with Radix UI primitives in frontend components
Use Tailwind CSS utility classes with component variants in frontend components
Implement error boundaries and loading states for better UX in frontend components
Files:
apps/frontend/components/dashboard/export-buttons.tsxapps/frontend/components/common/theme-provider.tsxapps/frontend/components/common/theme-toggle.tsx
apps/frontend/lib/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Create custom hooks for state management and API interactions in frontend code
Files:
apps/frontend/lib/export-utils.ts
apps/backend/app/api/router/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/backend/app/api/router/**/*.py: Use dependency injection for database sessions and services (e.g., Depends(get_db_session))
Use Pydantic models for all request and response validation in FastAPI endpoints
Validate file type and size for uploads (PDF/DOCX only) in backend file upload handlers
Use descriptive resource names for RESTful endpoints (e.g., /api/v1/resumes, /api/v1/jobs)
Implement proper HTTP status codes and error responses in API endpoints
Use consistent response formats with proper typing in API endpoints
Accept multipart/form-data for file uploads in API endpoints
Return structured JSON responses with consistent schemas in API endpoints
Implement streaming responses for long-running operations in API endpoints
Use proper pagination for list endpoints in API routes
Implement health check endpoints in backend API
Files:
apps/backend/app/api/router/v1/validation.pyapps/backend/app/api/router/v1/__init__.py
apps/backend/app/api/router/v1/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Version APIs with /v1/ prefix for future compatibility
Files:
apps/backend/app/api/router/v1/validation.pyapps/backend/app/api/router/v1/__init__.py
apps/backend/app/{models,services}/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use SQLAlchemy ORM with async sessions for database access
Files:
apps/backend/app/services/validation_service.pyapps/backend/app/services/__init__.py
apps/backend/app/services/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
apps/backend/app/services/**/*.py: Use temporary files for document processing and always clean up after processing
Implement async processing for file operations in backend services
Cache processed results when appropriate in backend services
Files:
apps/backend/app/services/validation_service.pyapps/backend/app/services/__init__.py
apps/backend/app/{agent,services}/**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use structured prompts and validate AI model responses against Pydantic models
Files:
apps/backend/app/services/validation_service.pyapps/backend/app/services/__init__.py
🧠 Learnings (8)
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/frontend/components/**/*.{ts,tsx} : Use Tailwind CSS utility classes with component variants in frontend components
Applied to files:
apps/frontend/tailwind.config.js
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/frontend/lib/**/*.{ts,tsx} : Create custom hooks for state management and API interactions in frontend code
Applied to files:
apps/frontend/hooks/use-theme.tsapps/frontend/components/common/theme-provider.tsx
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/frontend/lib/types/**/*.{ts,tsx} : Add TypeScript types in lib/types/ for new data structures in frontend
Applied to files:
apps/frontend/lib/export-utils.ts
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/backend/app/api/router/**/*.py : Use Pydantic models for all request and response validation in FastAPI endpoints
Applied to files:
apps/backend/app/api/router/v1/validation.pyapps/backend/app/api/router/v1/__init__.py
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/backend/app/api/router/**/*.py : Use descriptive resource names for RESTful endpoints (e.g., /api/v1/resumes, /api/v1/jobs)
Applied to files:
apps/backend/app/api/router/v1/validation.pyapps/backend/app/api/router/v1/__init__.py
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/backend/app/schemas/**/*.py : Use Pydantic models for request/response schemas and validation
Applied to files:
apps/backend/app/api/router/v1/validation.py
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/backend/app/api/router/**/*.py : Validate file type and size for uploads (PDF/DOCX only) in backend file upload handlers
Applied to files:
apps/backend/app/api/router/v1/validation.py
📚 Learning: 2025-07-31T13:44:21.007Z
Learnt from: CR
Repo: srbhr/Resume-Matcher PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-31T13:44:21.007Z
Learning: Applies to apps/backend/app/api/router/v1/**/*.py : Version APIs with /v1/ prefix for future compatibility
Applied to files:
apps/backend/app/api/router/v1/validation.pyapps/backend/app/api/router/v1/__init__.py
🧬 Code graph analysis (6)
apps/frontend/hooks/use-theme.ts (1)
apps/frontend/app/layout.tsx (1)
RootLayout(24-34)
apps/frontend/components/dashboard/export-buttons.tsx (1)
apps/frontend/lib/export-utils.ts (5)
AnalysisExportData(5-15)exportAsCSV(20-42)downloadFile(232-242)exportAsJSON(47-56)generateHTMLForPDF(61-227)
apps/frontend/components/common/theme-provider.tsx (1)
apps/frontend/hooks/use-theme.ts (1)
useTheme(11-43)
apps/frontend/components/common/theme-toggle.tsx (1)
apps/frontend/components/common/theme-provider.tsx (1)
useThemeContext(29-35)
apps/backend/app/api/router/v1/validation.py (1)
apps/backend/app/services/validation_service.py (2)
ResumeValidator(41-263)validate(70-112)
apps/backend/app/services/__init__.py (1)
apps/backend/app/services/validation_service.py (1)
ResumeValidator(41-263)
🪛 markdownlint-cli2 (0.18.1)
SETUP.md
327-327: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
434-434: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
453-453: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
507-507: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🪛 Ruff (0.14.4)
apps/backend/app/api/router/v1/validation.py
59-62: Abstract raise to an inner function
(TRY301)
72-72: Use explicit conversion flag
Replace with conversion flag
(RUF010)
73-76: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
75-75: Use explicit conversion flag
Replace with conversion flag
(RUF010)
apps/backend/app/services/validation_service.py
45-53: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
56-65: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
🔇 Additional comments (11)
Resume-Matcher (1)
1-7: Submodule reference detected.This file contains a Git submodule reference and merge metadata. No code review is necessary for submodule commit references.
apps/frontend/tailwind.config.js (2)
3-3: LGTM: Class-based dark mode configuration.The
darkMode: 'class'configuration correctly enables class-based dark mode toggling, which aligns with the ThemeProvider implementation that applies the 'dark' class to the document root.
22-26: LGTM: Custom dark theme tokens.The custom dark theme color tokens (
dark-bg,dark-surface,dark-text) follow consistent naming conventions and use appropriate slate color values for a dark theme palette.SETUP.md (2)
267-278: LGTM: Helpful troubleshooting guidance.The troubleshooting entries for port conflicts, DOCX processing failures, and database errors provide actionable solutions with specific commands and configuration guidance.
305-313: Fix typo in environment variable name.Line 308 contains a typo:
LL_MODELshould beLLM_MODEL(missing the 'M').Apply this diff to fix the typo:
-LL_MODEL="llama2" # or another available model +LLM_MODEL="llama2" # or another available model⛔ Skipped due to learnings
Learnt from: Harryki Repo: srbhr/Resume-Matcher PR: 509 File: SETUP.md:283-283 Timestamp: 2025-09-29T21:17:22.031Z Learning: In the Resume-Matcher project, the environment variable for the LLM model name is `LL_MODEL`, not `LLM_MODEL`. This is consistently used throughout the backend codebase in apps/backend/app/core/config.py and all provider implementations.README.md (1)
93-99: LGTM: Clear contribution guidance.The updated contribution section provides clear, actionable steps for new contributors with helpful links to CONTRIBUTING.md, good-first-issue tags, Discord, and the roadmap. The emoji markers enhance readability.
apps/backend/app/api/router/v1/__init__.py (1)
6-6: LGTM: Validation router properly integrated.The validation router is correctly imported and included with a descriptive RESTful prefix
/validation, following the established pattern of other routers in the v1 API namespace.Based on learnings
Also applies to: 11-11
apps/backend/app/services/__init__.py (1)
4-4: LGTM: ResumeValidator properly exported.The
ResumeValidatorimport and public export follow the established pattern for service modules, correctly exposing it as part of the services package API.Also applies to: 18-18
apps/frontend/components/common/theme-toggle.tsx (2)
14-16: LGTM: Proper hydration safety.Returning
nullbefore the component is mounted prevents hydration mismatches between server and client rendering, which is essential for theme toggles that depend on client-side state.
18-32: LGTM: Well-implemented theme toggle.The component correctly:
- Uses the
Buttoncomponent with appropriate variants- Displays intuitive Moon/Sun icons based on current theme
- Includes proper accessibility with
aria-label- Applies dark mode utility classes for styling
apps/frontend/components/common/theme-provider.tsx (1)
29-35: LGTM: Proper context hook with error handling.The
useThemeContexthook correctly validates that it's used within aThemeProviderand throws a descriptive error if not, following React context best practices.
| export const ThemeProvider = ({ children }: { children: ReactNode }) => { | ||
| const { theme, toggleTheme, setTheme, mounted } = useTheme(); | ||
|
|
||
| if (!mounted) { | ||
| return <>{children}</>; | ||
| } | ||
|
|
||
| return ( | ||
| <ThemeContext.Provider value={{ theme, toggleTheme, setTheme, mounted }}> | ||
| {children} | ||
| </ThemeContext.Provider> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix provider to always supply context.
The current implementation returns children without the context provider when !mounted (lines 18-20). This breaks the provider contract: any child component calling useThemeContext() during this phase will receive undefined and trigger the error "must be used within ThemeProvider", even though it technically is within the provider.
While ThemeToggle handles this by checking mounted, other consumers might not be aware of this requirement, leading to runtime errors during hydration.
Apply this diff to always provide context:
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const { theme, toggleTheme, setTheme, mounted } = useTheme();
- if (!mounted) {
- return <>{children}</>;
- }
-
return (
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme, mounted }}>
{children}
</ThemeContext.Provider>
);
};Consumers can still check mounted from the context value if they need to defer rendering, but the context itself will always be available.
📝 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.
| export const ThemeProvider = ({ children }: { children: ReactNode }) => { | |
| const { theme, toggleTheme, setTheme, mounted } = useTheme(); | |
| if (!mounted) { | |
| return <>{children}</>; | |
| } | |
| return ( | |
| <ThemeContext.Provider value={{ theme, toggleTheme, setTheme, mounted }}> | |
| {children} | |
| </ThemeContext.Provider> | |
| ); | |
| }; | |
| export const ThemeProvider = ({ children }: { children: ReactNode }) => { | |
| const { theme, toggleTheme, setTheme, mounted } = useTheme(); | |
| return ( | |
| <ThemeContext.Provider value={{ theme, toggleTheme, setTheme, mounted }}> | |
| {children} | |
| </ThemeContext.Provider> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
In apps/frontend/components/common/theme-provider.tsx around lines 15 to 27, the
component currently returns children directly when !mounted which skips the
ThemeContext.Provider and breaks the provider contract; remove the early return
and always wrap children with <ThemeContext.Provider value={{ theme,
toggleTheme, setTheme, mounted }}> so the context is always provided (consumers
can still read mounted from the context and decide to defer rendering).
| const handleExportPDF = () => { | ||
| const html = generateHTMLForPDF(data); | ||
| downloadFile(html, `${baseFileName}.html`, 'text/html;charset=utf-8;'); | ||
| // Note: To generate true PDFs, consider using a library like html2pdf or jsPDF | ||
| // This exports as HTML which can be printed to PDF using the browser's print dialog | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-wrap gap-2"> | ||
| <Button | ||
| onClick={handleExportCSV} | ||
| variant="outline" | ||
| size="sm" | ||
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | ||
| title="Export as CSV" | ||
| > | ||
| <FileText className="h-4 w-4" /> | ||
| CSV | ||
| </Button> | ||
|
|
||
| <Button | ||
| onClick={handleExportJSON} | ||
| variant="outline" | ||
| size="sm" | ||
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | ||
| title="Export as JSON" | ||
| > | ||
| <FileJson className="h-4 w-4" /> | ||
| JSON | ||
| </Button> | ||
|
|
||
| <Button | ||
| onClick={handleExportPDF} | ||
| variant="outline" | ||
| size="sm" | ||
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | ||
| title="Export as HTML/PDF" | ||
| > | ||
| <Download className="h-4 w-4" /> | ||
| </Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the “PDF” export – it currently downloads HTML.
Clicking the “PDF” button saves ${baseFileName}.html with a text/html MIME type. That’s misleading for users (and the feature promise) because they expect an actual PDF. Either generate a real PDF via a library or relabel the UI as “HTML” until a true PDF exists.
<Button
onClick={handleExportPDF}
variant="outline"
size="sm"
className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600"
- title="Export as HTML/PDF"
+ title="Export as HTML"
>
<Download className="h-4 w-4" />
- PDF
+ HTML
</Button>📝 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.
| const handleExportPDF = () => { | |
| const html = generateHTMLForPDF(data); | |
| downloadFile(html, `${baseFileName}.html`, 'text/html;charset=utf-8;'); | |
| // Note: To generate true PDFs, consider using a library like html2pdf or jsPDF | |
| // This exports as HTML which can be printed to PDF using the browser's print dialog | |
| }; | |
| return ( | |
| <div className="flex flex-wrap gap-2"> | |
| <Button | |
| onClick={handleExportCSV} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as CSV" | |
| > | |
| <FileText className="h-4 w-4" /> | |
| CSV | |
| </Button> | |
| <Button | |
| onClick={handleExportJSON} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as JSON" | |
| > | |
| <FileJson className="h-4 w-4" /> | |
| JSON | |
| </Button> | |
| <Button | |
| onClick={handleExportPDF} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as HTML/PDF" | |
| > | |
| <Download className="h-4 w-4" /> | |
| </Button> | |
| const handleExportPDF = () => { | |
| const html = generateHTMLForPDF(data); | |
| downloadFile(html, `${baseFileName}.html`, 'text/html;charset=utf-8;'); | |
| // Note: To generate true PDFs, consider using a library like html2pdf or jsPDF | |
| // This exports as HTML which can be printed to PDF using the browser's print dialog | |
| }; | |
| return ( | |
| <div className="flex flex-wrap gap-2"> | |
| <Button | |
| onClick={handleExportCSV} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as CSV" | |
| > | |
| <FileText className="h-4 w-4" /> | |
| CSV | |
| </Button> | |
| <Button | |
| onClick={handleExportJSON} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as JSON" | |
| > | |
| <FileJson className="h-4 w-4" /> | |
| JSON | |
| </Button> | |
| <Button | |
| onClick={handleExportPDF} | |
| variant="outline" | |
| size="sm" | |
| className="flex items-center gap-2 text-gray-100 bg-gray-700 hover:bg-gray-600 border-gray-600 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600" | |
| title="Export as HTML" | |
| > | |
| <Download className="h-4 w-4" /> | |
| HTML | |
| </Button> |
🤖 Prompt for AI Agents
In apps/frontend/components/dashboard/export-buttons.tsx around lines 36 to 76,
the "PDF" button currently calls a handler that downloads an HTML file (filename
`${baseFileName}.html` with MIME `text/html`), which is misleading. Fix by
either (A) implementing a real PDF generator: replace handleExportPDF to convert
the generated HTML into a PDF (e.g., use html2pdf/html2canvas+jsPDF or
server-side conversion), create an application/pdf Blob and trigger download as
`${baseFileName}.pdf` with MIME `application/pdf`, or (B) immediately relabel
the button to "HTML" (change displayed text, title/aria-label to "Export as
HTML") and keep the existing handler but ensure filename and MIME remain
`.html`/`text/html;charset=utf-8;` so UI matches behavior; choose one approach
and update tests/docs accordingly.
| useEffect(() => { | ||
| // Get initial theme from localStorage or system preference | ||
| const storedTheme = localStorage.getItem('theme') as Theme | null; | ||
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | ||
| const initialTheme = storedTheme || (prefersDark ? 'dark' : 'light'); | ||
|
|
||
| setThemeState(initialTheme); | ||
| applyTheme(initialTheme); | ||
| setMounted(true); | ||
| }, []); | ||
|
|
||
| const setTheme = (newTheme: Theme) => { | ||
| setThemeState(newTheme); | ||
| localStorage.setItem('theme', newTheme); | ||
| applyTheme(newTheme); | ||
| }; | ||
|
|
||
| const toggleTheme = () => { | ||
| const newTheme = theme === 'light' ? 'dark' : 'light'; | ||
| setTheme(newTheme); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard theme persistence when storage is unavailable.
Safari Private Browsing (and other privacy scopes) throws a DOMException on localStorage.getItem/setItem. Because both the initial effect and setTheme touch storage without a guard, the hook crashes and the UI never toggles. Please wrap the storage access in try/catch and fall back to a safe default so the hook still works when storage is blocked.
useEffect(() => {
- // Get initial theme from localStorage or system preference
- const storedTheme = localStorage.getItem('theme') as Theme | null;
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
- const initialTheme = storedTheme || (prefersDark ? 'dark' : 'light');
-
- setThemeState(initialTheme);
- applyTheme(initialTheme);
- setMounted(true);
+ try {
+ const storedTheme = localStorage.getItem('theme') as Theme | null;
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ const initialTheme = storedTheme || (prefersDark ? 'dark' : 'light');
+
+ setThemeState(initialTheme);
+ applyTheme(initialTheme);
+ } catch (error) {
+ console.warn('Unable to read theme preference', error);
+ applyTheme('light');
+ } finally {
+ setMounted(true);
+ }
}, []);
const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
- localStorage.setItem('theme', newTheme);
- applyTheme(newTheme);
+ try {
+ localStorage.setItem('theme', newTheme);
+ } catch (error) {
+ console.warn('Unable to persist theme preference', error);
+ }
+ applyTheme(newTheme);
};🤖 Prompt for AI Agents
In apps/frontend/hooks/use-theme.ts around lines 15 to 35, both the initial
effect and setTheme call localStorage.getItem/setItem without guards causing
crashes in environments where storage is unavailable (e.g., Safari Private
Browsing); wrap reads/writes in try/catch: on read, catch and treat storedTheme
as null (fall back to system preference or 'light'), and on write, catch and
ignore errors (no-op) so theme application still proceeds; ensure applyTheme and
state updates run regardless of storage success so the hook remains functional
when storage is blocked.
| data.improvements.forEach((improvement) => { | ||
| rows.push([ | ||
| `"${improvement.suggestion.replace(/"/g, '""')}"`, | ||
| improvement.lineNumber ? `Line ${improvement.lineNumber}` : 'N/A', | ||
| ]); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve line number 0 in CSV output.
improvement.lineNumber ? … : 'N/A' treats 0 as falsy, so any item pinned to line 0 exports as “N/A”. That’s incorrect data. Please check for null/undefined instead of truthiness.
rows.push([
`"${improvement.suggestion.replace(/"/g, '""')}"`,
- improvement.lineNumber ? `Line ${improvement.lineNumber}` : 'N/A',
+ improvement.lineNumber !== undefined && improvement.lineNumber !== null
+ ? `Line ${improvement.lineNumber}`
+ : 'N/A',
]);📝 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.
| data.improvements.forEach((improvement) => { | |
| rows.push([ | |
| `"${improvement.suggestion.replace(/"/g, '""')}"`, | |
| improvement.lineNumber ? `Line ${improvement.lineNumber}` : 'N/A', | |
| ]); | |
| }); | |
| data.improvements.forEach((improvement) => { | |
| rows.push([ | |
| `"${improvement.suggestion.replace(/"/g, '""')}"`, | |
| improvement.lineNumber !== undefined && improvement.lineNumber !== null | |
| ? `Line ${improvement.lineNumber}` | |
| : 'N/A', | |
| ]); | |
| }); |
🤖 Prompt for AI Agents
In apps/frontend/lib/export-utils.ts around lines 34 to 39, the CSV code treats
line 0 as falsy and outputs "N/A"; change the conditional to check for
null/undefined instead of truthiness (e.g., use an explicit != null or typeof
check) so that lineNumber 0 is preserved and only null/undefined become 'N/A'.
| export const generateHTMLForPDF = (data: AnalysisExportData): string => { | ||
| const improvementsList = data.improvements | ||
| .map( | ||
| (imp, idx) => | ||
| `<li><strong>${idx + 1}.</strong> ${imp.suggestion}${ | ||
| imp.lineNumber ? ` <span style="color: #666; font-size: 0.9em;">(Line ${imp.lineNumber})</span>` : '' | ||
| }</li>` | ||
| ) | ||
| .join(''); | ||
|
|
||
| const scoreDelta = data.score - data.originalScore; | ||
| const deltaColor = scoreDelta > 0 ? '#22c55e' : scoreDelta < 0 ? '#ef4444' : '#666'; | ||
| const deltaText = scoreDelta > 0 ? `+${scoreDelta}` : `${scoreDelta}`; | ||
|
|
||
| return ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>Resume Analysis Report</title> | ||
| <style> | ||
| body { | ||
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; | ||
| line-height: 1.6; | ||
| color: #333; | ||
| max-width: 900px; | ||
| margin: 0 auto; | ||
| padding: 20px; | ||
| background: #f5f5f5; | ||
| } | ||
| .container { | ||
| background: white; | ||
| padding: 30px; | ||
| border-radius: 8px; | ||
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | ||
| } | ||
| h1 { | ||
| color: #1f2937; | ||
| border-bottom: 3px solid #8b5cf6; | ||
| padding-bottom: 10px; | ||
| margin-bottom: 30px; | ||
| } | ||
| h2 { | ||
| color: #4b5563; | ||
| margin-top: 25px; | ||
| margin-bottom: 15px; | ||
| } | ||
| .score-grid { | ||
| display: grid; | ||
| grid-template-columns: 1fr 1fr; | ||
| gap: 20px; | ||
| margin-bottom: 30px; | ||
| } | ||
| .score-card { | ||
| background: #f9fafb; | ||
| border: 2px solid #e5e7eb; | ||
| border-radius: 8px; | ||
| padding: 20px; | ||
| text-align: center; | ||
| } | ||
| .score-card h3 { | ||
| color: #6b7280; | ||
| font-size: 0.9em; | ||
| text-transform: uppercase; | ||
| margin-bottom: 10px; | ||
| } | ||
| .score-number { | ||
| font-size: 2.5em; | ||
| font-weight: bold; | ||
| margin: 10px 0; | ||
| } | ||
| .score-delta { | ||
| color: ${deltaColor}; | ||
| font-weight: bold; | ||
| font-size: 1.1em; | ||
| margin-top: 10px; | ||
| } | ||
| .details-section { | ||
| background: #f9fafb; | ||
| border-left: 4px solid #8b5cf6; | ||
| padding: 15px; | ||
| margin-bottom: 20px; | ||
| border-radius: 4px; | ||
| } | ||
| .details-section h3 { | ||
| margin-top: 0; | ||
| color: #1f2937; | ||
| } | ||
| .details-section p { | ||
| margin: 8px 0; | ||
| color: #4b5563; | ||
| } | ||
| .improvements-list { | ||
| list-style: none; | ||
| padding: 0; | ||
| } | ||
| .improvements-list li { | ||
| background: #f9fafb; | ||
| padding: 12px 15px; | ||
| margin-bottom: 10px; | ||
| border-radius: 4px; | ||
| border-left: 3px solid #8b5cf6; | ||
| } | ||
| .footer { | ||
| margin-top: 30px; | ||
| padding-top: 20px; | ||
| border-top: 1px solid #e5e7eb; | ||
| color: #9ca3af; | ||
| font-size: 0.9em; | ||
| text-align: center; | ||
| } | ||
| @media print { | ||
| body { | ||
| background: white; | ||
| } | ||
| .container { | ||
| box-shadow: none; | ||
| padding: 0; | ||
| } | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <h1>Resume Analysis Report</h1> | ||
| <div class="score-grid"> | ||
| <div class="score-card"> | ||
| <h3>Original Score</h3> | ||
| <div class="score-number">${data.originalScore}</div> | ||
| <p style="color: #6b7280; margin: 0;">/100</p> | ||
| </div> | ||
| <div class="score-card"> | ||
| <h3>Improved Score</h3> | ||
| <div class="score-number">${data.score}</div> | ||
| <p style="color: #6b7280; margin: 0;">/100</p> | ||
| <div class="score-delta">${deltaText}</div> | ||
| </div> | ||
| </div> | ||
| <h2>Analysis Summary</h2> | ||
| <div class="details-section"> | ||
| <h3>Details</h3> | ||
| <p>${data.details}</p> | ||
| </div> | ||
| <div class="details-section"> | ||
| <h3>Commentary</h3> | ||
| <p>${data.commentary}</p> | ||
| </div> | ||
| <h2>Improvement Recommendations</h2> | ||
| ${ | ||
| data.improvements.length > 0 | ||
| ? `<ol class="improvements-list">${improvementsList}</ol>` | ||
| : '<p style="color: #6b7280;">No specific improvement suggestions at this time.</p>' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape all dynamic HTML before embedding in the export.
details, commentary, and each suggestion come straight from resume content/analysis. If any of those contain <script> (or even just <), the downloaded file executes arbitrary markup when opened. Please HTML-escape every dynamic string before interpolation.
+const escapeHtml = (value: string) =>
+ value
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+
export const generateHTMLForPDF = (data: AnalysisExportData): string => {
const improvementsList = data.improvements
.map(
(imp, idx) =>
- `<li><strong>${idx + 1}.</strong> ${imp.suggestion}${
- imp.lineNumber ? ` <span style="color: #666; font-size: 0.9em;">(Line ${imp.lineNumber})</span>` : ''
+ `<li><strong>${idx + 1}.</strong> ${escapeHtml(imp.suggestion)}${
+ imp.lineNumber !== undefined && imp.lineNumber !== null
+ ? ` <span style="color: #666; font-size: 0.9em;">(Line ${imp.lineNumber})</span>`
+ : ''
}</li>`
)
.join('');
...
- <p>${data.details}</p>
+ <p>${escapeHtml(data.details)}</p>
...
- <p>${data.commentary}</p>
+ <p>${escapeHtml(data.commentary)}</p>🤖 Prompt for AI Agents
In apps/frontend/lib/export-utils.ts around lines 61 to 216, dynamic fields
(data.details, data.commentary, data.improvements[].suggestion and optional
lineNumber) are interpolated directly into HTML causing XSS; escape all dynamic
strings before embedding by HTML-encoding characters &, <, >, ", ' (and
optionally /) and use the escaped values in the template and the generated
improvementsList; ensure any conditional formatting (like lineNumber) uses the
escaped value and that the final string interpolation only inserts sanitized
text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6 issues found across 13 files
Prompt for AI agents (all 6 issues)
Understand the root cause of the following 6 issues and fix them.
<file name="apps/backend/app/api/router/v1/validation.py">
<violation number="1" location="apps/backend/app/api/router/v1/validation.py:8">
`traceback` is imported but never used in this module; please remove the unused import to avoid lint warnings and keep the file tidy.</violation>
<violation number="2" location="apps/backend/app/api/router/v1/validation.py:75">
Rule violated: **Flag Security Vulnerabilities**
Returning raw exception details in the HTTP 500 response leaks internal information. Replace the message with a generic error string to avoid exposing sensitive details.</violation>
</file>
<file name="apps/frontend/components/common/theme-toggle.tsx">
<violation number="1" location="apps/frontend/components/common/theme-toggle.tsx:12">
Calling useThemeContext() here will throw on the initial render because ThemeProvider returns children without a Provider while mounted is false, so this component mounts outside of any context and the hook throws. That makes the toggle (and its parent tree) crash before the theme mounts.</violation>
</file>
<file name="apps/frontend/lib/export-utils.ts">
<violation number="1" location="apps/frontend/lib/export-utils.ts:205">
User-provided analysis data is interpolated into the exported HTML without escaping, which allows HTML/script injection when the file is opened. Please HTML-escape `details`, `commentary`, and each improvement suggestion before inserting them into the template.</violation>
</file>
<file name="apps/frontend/hooks/use-theme.ts">
<violation number="1" location="apps/frontend/hooks/use-theme.ts:33">
`toggleTheme` derives the next theme from the stale `theme` value in its closure, so multiple rapid invocations in the same tick (e.g., calling `toggleTheme()` twice) won’t flip back to the original theme. Use the functional updater form so each toggle works off the latest state.</violation>
</file>
<file name="apps/frontend/tailwind.config.js">
<violation number="1" location="apps/frontend/tailwind.config.js:23">
Rule violated: **Detect Unused or Redundant Configuration Parameters**
These Tailwind color entries are never referenced anywhere in the frontend, making them redundant configuration and violating the Detect Unused or Redundant Configuration Parameters rule. Please remove the unused color definitions to keep the theme config lean.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Ask questions if you need clarification on any suggestion
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| logger.error(f"Resume validation failed: {str(e)}", exc_info=True) | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail=f"Resume validation failed: {str(e)}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Flag Security Vulnerabilities
Returning raw exception details in the HTTP 500 response leaks internal information. Replace the message with a generic error string to avoid exposing sensitive details.
Prompt for AI agents
Address the following comment on apps/backend/app/api/router/v1/validation.py at line 75:
<comment>Returning raw exception details in the HTTP 500 response leaks internal information. Replace the message with a generic error string to avoid exposing sensitive details.</comment>
<file context>
@@ -0,0 +1,76 @@
+ logger.error(f"Resume validation failed: {str(e)}", exc_info=True)
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=f"Resume validation failed: {str(e)}"
+ )
</file context>
| detail=f"Resume validation failed: {str(e)}" | |
| detail="Resume validation failed" |
| * Displays sun/moon icon and toggles between light and dark modes | ||
| */ | ||
| export const ThemeToggle = () => { | ||
| const { theme, toggleTheme, mounted } = useThemeContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling useThemeContext() here will throw on the initial render because ThemeProvider returns children without a Provider while mounted is false, so this component mounts outside of any context and the hook throws. That makes the toggle (and its parent tree) crash before the theme mounts.
Prompt for AI agents
Address the following comment on apps/frontend/components/common/theme-toggle.tsx at line 12:
<comment>Calling useThemeContext() here will throw on the initial render because ThemeProvider returns children without a Provider while mounted is false, so this component mounts outside of any context and the hook throws. That makes the toggle (and its parent tree) crash before the theme mounts.</comment>
<file context>
@@ -0,0 +1,33 @@
+ * Displays sun/moon icon and toggles between light and dark modes
+ */
+export const ThemeToggle = () => {
+ const { theme, toggleTheme, mounted } = useThemeContext();
+
+ if (!mounted) {
</file context>
| """ | ||
|
|
||
| import logging | ||
| import traceback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
traceback is imported but never used in this module; please remove the unused import to avoid lint warnings and keep the file tidy.
Prompt for AI agents
Address the following comment on apps/backend/app/api/router/v1/validation.py at line 8:
<comment>`traceback` is imported but never used in this module; please remove the unused import to avoid lint warnings and keep the file tidy.</comment>
<file context>
@@ -0,0 +1,76 @@
+"""
+
+import logging
+import traceback
+from typing import Dict, Any
+
</file context>
| <h2>Analysis Summary</h2> | ||
| <div class="details-section"> | ||
| <h3>Details</h3> | ||
| <p>${data.details}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User-provided analysis data is interpolated into the exported HTML without escaping, which allows HTML/script injection when the file is opened. Please HTML-escape details, commentary, and each improvement suggestion before inserting them into the template.
Prompt for AI agents
Address the following comment on apps/frontend/lib/export-utils.ts at line 205:
<comment>User-provided analysis data is interpolated into the exported HTML without escaping, which allows HTML/script injection when the file is opened. Please HTML-escape `details`, `commentary`, and each improvement suggestion before inserting them into the template.</comment>
<file context>
@@ -0,0 +1,242 @@
+ <h2>Analysis Summary</h2>
+ <div class="details-section">
+ <h3>Details</h3>
+ <p>${data.details}</p>
+ </div>
+ <div class="details-section">
</file context>
| }; | ||
|
|
||
| const toggleTheme = () => { | ||
| const newTheme = theme === 'light' ? 'dark' : 'light'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toggleTheme derives the next theme from the stale theme value in its closure, so multiple rapid invocations in the same tick (e.g., calling toggleTheme() twice) won’t flip back to the original theme. Use the functional updater form so each toggle works off the latest state.
Prompt for AI agents
Address the following comment on apps/frontend/hooks/use-theme.ts at line 33:
<comment>`toggleTheme` derives the next theme from the stale `theme` value in its closure, so multiple rapid invocations in the same tick (e.g., calling `toggleTheme()` twice) won’t flip back to the original theme. Use the functional updater form so each toggle works off the latest state.</comment>
<file context>
@@ -0,0 +1,55 @@
+ };
+
+ const toggleTheme = () => {
+ const newTheme = theme === 'light' ? 'dark' : 'light';
+ setTheme(newTheme);
+ };
</file context>
| mono: ['"Space Grotesk"', 'monospace'], | ||
| }, | ||
| colors: { | ||
| 'dark-bg': '#0f172a', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Detect Unused or Redundant Configuration Parameters
These Tailwind color entries are never referenced anywhere in the frontend, making them redundant configuration and violating the Detect Unused or Redundant Configuration Parameters rule. Please remove the unused color definitions to keep the theme config lean.
Prompt for AI agents
Address the following comment on apps/frontend/tailwind.config.js at line 23:
<comment>These Tailwind color entries are never referenced anywhere in the frontend, making them redundant configuration and violating the Detect Unused or Redundant Configuration Parameters rule. Please remove the unused color definitions to keep the theme config lean.</comment>
<file context>
@@ -18,6 +19,11 @@ module.exports = {
mono: ['"Space Grotesk"', 'monospace'],
},
+ colors: {
+ 'dark-bg': '#0f172a',
+ 'dark-surface': '#1e293b',
+ 'dark-text': '#f1f5f9',
</file context>
User description
Pull Request Title
Full-Stack Feature Suite: Dark Mode, Multi-Format Export, Resume Validation Service, and Developer Documentation
Related Issue
This pull request helps address several issues reported in the community:
Description
This pull request introduces 4 major features to Resume Matcher, enhancing user experience, data portability, resume quality assurance, and community contribution guidelines.
📋 Summary
This pull request introduces 4 major features to Resume Matcher to enhance user experience, provide data export capabilities, add resume validation functionality, and improve community contribution guidelines.
Features Delivered:
🌓 Feature 1: Dark Mode Support
What's New
useTheme) managing theme state and persistenceThemeProvider) for app-wide theme managementFiles Changed
Created:
apps/frontend/hooks/use-theme.ts(56 lines)apps/frontend/components/common/theme-provider.tsx(31 lines)apps/frontend/components/common/theme-toggle.tsx(36 lines)Modified:
apps/frontend/tailwind.config.js- Added dark mode config + color variablesapps/frontend/app/layout.tsx- Integrated ThemeProviderTechnical Details
📤 Feature 2: Export Functionality
What's New
Exported Data
Files Changed
Created:
apps/frontend/lib/export-utils.ts(243 lines) - Export utility functionsapps/frontend/components/dashboard/export-buttons.tsx(73 lines) - Export UI componentTechnical Details
✔️ Feature 3: Resume Validation Endpoint
What's New
POST /api/v1/validation/validateAPI Endpoint
Files Changed
Created:
apps/backend/app/services/validation_service.py(264 lines) - ResumeValidator serviceapps/backend/app/api/router/v1/validation.py(73 lines) - Validation API endpointModified:
apps/backend/app/services/__init__.py- Export ResumeValidatorapps/backend/app/api/router/v1/__init__.py- Register validation routerTechnical Details
📚 Feature 4: Enhanced Documentation
What's New
CONTRIBUTING.md (361 lines) - Complete contribution guide with:
SETUP.md Enhancements:
README.md Updates:
Files Changed
Created:
CONTRIBUTING.md(361 lines)Modified:
SETUP.md- Added 100+ lines of FAQ and troubleshootingREADME.md- Updated contribution section📊 Changes Summary
Statistics
Code Quality
✅ Testing Instructions
Dark Mode
npm run devExport
Validation
npm run server/api/v1/validation/validateDocumentation
CONTRIBUTING.md- Should be comprehensive and clearSETUP.md- Should have FAQ and troubleshooting sectionsREADME.md- Contribution section should be updated🔗 Related Documentation
For more details, see:
VERIFICATION_CHECKLIST.md- File-by-file implementation verificationWORK_SUMMARY.md- Complete work overviewTEACHER_SUBMISSION_SUMMARY.md- Educational context📋 Checklist
Thank You
Thank you for reviewing this contribution! I'm excited to contribute to Resume Matcher and help improve the project.
File Walkthrough
7 files
New resume validation API endpoint implementationResume validation service with comprehensive checksCustom React hook for theme managementReact Context provider for theme stateTheme toggle button UI componentExport utility functions for multiple formatsExport buttons component for analysis results3 files
Register validation router in API v1Export ResumeValidator from services moduleAdd dark mode and custom color configuration2 files
Update contribution guidelines and roadmap linksAdd troubleshooting section and comprehensive FAQ1 files
Summary by CodeRabbit
New Features
Documentation