From 9205b884581eaa1210701b1b4692847fb36addb4 Mon Sep 17 00:00:00 2001 From: JobsBoard Deployer Date: Sat, 5 Sep 2026 13:39:53 -0400 Subject: [PATCH] Eliminate all synthetic resume hallucinations, integrate OpenRouter small-model support, and strip all emojis site-wide --- docker-compose.yml | 2 + web/src/app/alerts/page.tsx | 10 +- web/src/app/api/admin/claims/route.ts | 2 +- web/src/app/api/alerts/check/route.ts | 8 +- web/src/app/api/auth/forgot-password/route.ts | 2 +- web/src/app/api/resume/optimize/route.ts | 307 +++++++++--------- web/src/app/api/resume/parse/route.ts | 18 +- web/src/app/applications/page.tsx | 8 +- web/src/app/companies/[id]/page.tsx | 10 +- web/src/app/employer/ats/page.tsx | 10 +- web/src/app/employer/candidates/page.tsx | 6 +- web/src/app/profile/page.tsx | 4 +- web/src/app/saved-jobs/page.tsx | 6 +- .../components/NotificationPreferences.tsx | 2 +- web/src/components/OneClickApplyModal.tsx | 10 +- web/src/components/ResumeEditor.tsx | 10 +- 16 files changed, 194 insertions(+), 221 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 92265a8..6e7d29e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,8 @@ services: NEXTAUTH_SECRET: "${NEXTAUTH_SECRET:-jobsboard-secret-key-for-auth-sessions}" NEXTAUTH_URL: "${NEXTAUTH_URL:-http://localhost:3000}" NODE_ENV: "production" + OPENROUTER_API_KEY: "${OPENROUTER_API_KEY:-}" + OPENROUTER_MODEL: "${OPENROUTER_MODEL:-meta-llama/llama-3.2-3b-instruct}" depends_on: db: condition: service_healthy diff --git a/web/src/app/alerts/page.tsx b/web/src/app/alerts/page.tsx index de506f9..2e73be9 100644 --- a/web/src/app/alerts/page.tsx +++ b/web/src/app/alerts/page.tsx @@ -139,7 +139,7 @@ export default function JobAlertsPage() { disabled={testing} className="px-3 py-1.5 bg-zinc-900 text-white text-xs font-medium rounded hover:bg-zinc-800 disabled:opacity-50 transition-colors" > - {testing ? "Processing Alerts..." : "โšก Run Alert Matching Check Now"} + {testing ? "Processing Alerts..." : " Run Alert Matching Check Now"} @@ -294,10 +294,10 @@ export default function JobAlertsPage() {
- {s.keywords && ๐Ÿ” "{s.keywords}"} - {s.location && ๐Ÿ“ {s.location}} - {s.department && ๐Ÿข {s.department}} - {s.minSalary && ๐Ÿ’ฐ Min ${s.minSalary.toLocaleString()}} + {s.keywords && "{s.keywords}"} + {s.location && {s.location}} + {s.department && {s.department}} + {s.minSalary && Min ${s.minSalary.toLocaleString()}}
{s.lastSentAt && ( diff --git a/web/src/app/api/admin/claims/route.ts b/web/src/app/api/admin/claims/route.ts index 44bcce2..4a0a232 100644 --- a/web/src/app/api/admin/claims/route.ts +++ b/web/src/app/api/admin/claims/route.ts @@ -120,7 +120,7 @@ export async function PATCH(req: Request) { await createNotification({ userId: claim.claimantId, type: "COMPANY_CLAIM_STATUS", - title: "Company Claim Approved! ๐ŸŽ‰", + title: "Company Claim Approved! ", message: `Your ownership claim for ${claim.company.name} has been verified and approved. You are now the official Owner.`, link: `/employer/ats`, }); diff --git a/web/src/app/api/alerts/check/route.ts b/web/src/app/api/alerts/check/route.ts index 0eaf040..3aa9966 100644 --- a/web/src/app/api/alerts/check/route.ts +++ b/web/src/app/api/alerts/check/route.ts @@ -99,8 +99,8 @@ export async function POST(req: Request) { (j) => `
  • ${j.title} -
    ๐Ÿข ${j.company} โ€ข ๐Ÿ“ ${j.location} ${j.isRemote ? 'โ€ข ๐ŸŒ Remote' : ''}
    - ${j.salaryMin ? `
    ๐Ÿ’ต $${j.salaryMin.toLocaleString()}${j.salaryMax ? ` - $${j.salaryMax.toLocaleString()}` : '+'}
    ` : ''} +
    ${j.company} โ€ข ${j.location} ${j.isRemote ? 'โ€ข Remote' : ''}
    + ${j.salaryMin ? `
    $${j.salaryMin.toLocaleString()}${j.salaryMax ? ` - $${j.salaryMax.toLocaleString()}` : '+'}
    ` : ''}
  • ` ) .join(""); @@ -109,7 +109,7 @@ export async function POST(req: Request) { const emailResult = await sendEmail({ to: search.user.email, - subject: `๐ŸŽฏ ${matchingJobs.length} New Job Match${matchingJobs.length > 1 ? "es" : ""} for "${searchTitle}"`, + subject: ` ${matchingJobs.length} New Job Match${matchingJobs.length > 1 ? "es" : ""} for "${searchTitle}"`, html: `
    @@ -179,7 +179,7 @@ export async function POST(req: Request) { await sendEmail({ to: alert.user.email, - subject: `๐ŸŽฏ ${matchingJobs.length} New Job Match${matchingJobs.length > 1 ? "es" : ""} for "${alert.titleQuery || "your search"}"`, + subject: ` ${matchingJobs.length} New Job Match${matchingJobs.length > 1 ? "es" : ""} for "${alert.titleQuery || "your search"}"`, html: `

    Your Job Alert

      ${jobListHtml}
    `, }); diff --git a/web/src/app/api/auth/forgot-password/route.ts b/web/src/app/api/auth/forgot-password/route.ts index 21a29b6..a8d555e 100644 --- a/web/src/app/api/auth/forgot-password/route.ts +++ b/web/src/app/api/auth/forgot-password/route.ts @@ -48,7 +48,7 @@ export async function POST(req: Request) { await sendEmail({ to: user.email, - subject: "๐Ÿ”’ JobsBoard: Reset Your Password", + subject: " JobsBoard: Reset Your Password", html: `

    Password Reset Request

    diff --git a/web/src/app/api/resume/optimize/route.ts b/web/src/app/api/resume/optimize/route.ts index 6508816..83a9028 100644 --- a/web/src/app/api/resume/optimize/route.ts +++ b/web/src/app/api/resume/optimize/route.ts @@ -2,119 +2,77 @@ import { NextResponse } from "next/server"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; -// Action verb enhancement bank -const ACTIVE_VERBS_BY_DOMAIN: Record = { - engineering: ["Architected", "Engineered", "Developed", "Spearheaded", "Optimized", "Refactored", "Automated", "Deployed", "Orchestrated", "Implemented"], - systems: ["Administered", "Configured", "Monitored", "Diagnosed", "Maintained", "Hardened", "Virtualize", "Migrated", "Resolved", "Provisioned"], - leadership: ["Directed", "Supervised", "Coordinated", "Championed", "Mentored", "Facilitated", "Delivered", "Accelerated", "Strengthened"], - general: ["Delivered", "Constructed", "Streamlined", "Executed", "Formulated", "Analyzed", "Pioneered", "Standardized"], -}; +/** + * TRUTH-GROUNDED RESUME ENHANCER & OPTIMIZER + * + * Rules: + * 1. NEVER invent facts, employers, companies, degrees, dates, metrics, or technologies not in input. + * 2. ONLY polish grammar, replace passive phrasing with active professional verbs, and improve syntax. + * 3. Preserve the candidate's exact domain, authentic role, and truthfulness. + * 4. Pluggable: Uses OpenRouter small models (e.g. meta-llama/llama-3.2-3b-instruct) when key is supplied, with fallback to strict deterministic grammatical polish. + */ -function polishBullet(bullet: string, positionTitle: string): string { +// Active verb replacements for weak starting phrases that preserve original meaning +const WEAK_PHRASE_MAPPINGS: Array<[RegExp, string]> = [ + [/^\s*(worked on|working on|was working on)\b/i, "Developed and maintained"], + [/^\s*(worked with|working with)\b/i, "Collaborated with"], + [/^\s*(responsible for|was responsible for|held responsibility for)\b/i, "Managed and executed"], + [/^\s*(helped with|helped out with|assisted with|assisted in)\b/i, "Assisted with and contributed to"], + [/^\s*(helped|assisted)\b/i, "Collaborated on"], + [/^\s*(handled|handling)\b/i, "Managed and coordinated"], + [/^\s*(made|making|created|creating)\b/i, "Developed and produced"], + [/^\s*(did|doing|performed|performing)\b/i, "Executed and completed"], + [/^\s*(used|using|utilized|utilizing)\b/i, "Applied and utilized"], + [/^\s*(maintained|maintaining)\b/i, "Maintained and supported"], + [/^\s*(tested|testing)\b/i, "Tested and verified"], + [/^\s*(supported|supporting)\b/i, "Supported and resolved"], + [/^\s*(fixed|fixing)\b/i, "Diagnosed and resolved"], + [/^\s*(built|building)\b/i, "Constructed and implemented"], + [/^\s*(learned|learning)\b/i, "Acquired proficiency in"], + [/^\s*(talked to|communicated with)\b/i, "Liaised and coordinated with"], + [/^\s*(in charge of)\b/i, "Directed operations for"], +]; + +function polishBulletStrict(bullet: string): string { if (!bullet || typeof bullet !== "string") return ""; let clean = bullet.trim().replace(/^[โ€ขยท\-\*โ€“โ€”\d.)\s]+/, "").trim(); - if (clean.length < 5) return clean; + if (clean.length < 4) return clean; - const lower = clean.toLowerCase(); - - // 1. Specific domain-specific transformations - if (lower.includes("customer service") || lower.includes("front-line") || lower.includes("client relations")) { - return "Delivered high-volume client engagement and operational support, maintaining 99%+ satisfaction and strict service delivery standards."; - } - - if (lower.includes("homelab") || lower.includes("bare metal") || lower.includes("virtualization")) { - return "Architected and administered self-hosted virtualization environments, configuring automated backups, containerized services, and secure network segmentation."; - } - - if (lower.includes("troubleshoot") || lower.includes("help desk") || lower.includes("tier 1") || lower.includes("tier 2")) { - return "Diagnosed and resolved technical support escalations across operating systems, enterprise hardware, and network infrastructure with 98% first-touch SLA compliance."; - } - - // 2. Active verb replacements for weak starting phrases - const weakReplacements: Array<[RegExp, string]> = [ - [/^\s*(worked on|working on|worked with|was working on)/i, "Engineered and deployed"], - [/^\s*(responsible for|was responsible for|held responsibility for)/i, "Directed and executed"], - [/^\s*(helped with|helped out with|assisted with|assisted in)/i, "Streamlined and collaborated on"], - [/^\s*(helped|assisted)/i, "Spearheaded and facilitated"], - [/^\s*(handled|handling)/i, "Managed and optimized"], - [/^\s*(made|making|created|creating)/i, "Constructed and deployed"], - [/^\s*(did|doing|performed|performing)/i, "Executed and delivered"], - [/^\s*(used|using|utilized|utilizing)/i, "Leveraged and applied"], - [/^\s*(managed|managing)/i, "Orchestrated and managed"], - [/^\s*(maintained|maintaining)/i, "Maintained and safeguarded"], - [/^\s*(tested|testing)/i, "Tested and validated"], - [/^\s*(supported|supporting)/i, "Supported and optimized"], - [/^\s*(fixed|fixing)/i, "Diagnosed and resolved"], - [/^\s*(built|building)/i, "Architected and delivered"], - [/^\s*(learned|learning)/i, "Mastered and adopted"], - [/^\s*(talked to|communicated with)/i, "Consulted and liaised with"], - ]; - - let replaced = false; - for (const [pattern, replacement] of weakReplacements) { + // 1. Replace weak lead phrases while preserving the rest of the sentence verbatim + for (const [pattern, replacement] of WEAK_PHRASE_MAPPINGS) { if (pattern.test(clean)) { clean = clean.replace(pattern, replacement); - replaced = true; break; } } - // 3. If no weak phrase was replaced, ensure it starts with an active verb or metric - if (!replaced) { - const firstWord = clean.split(" ")[0].toLowerCase(); - const commonVerbs = ["engineered", "architected", "developed", "managed", "delivered", "deployed", "spearheaded", "administered", "configured", "optimized", "diagnosed", "executed", "collaborated"]; - const startsWithActive = commonVerbs.some(v => firstWord.startsWith(v)); + // 2. Ensure first letter is capitalized + clean = clean.charAt(0).toUpperCase() + clean.slice(1); - if (!startsWithActive) { - if (positionTitle.toLowerCase().includes("engineer") || positionTitle.toLowerCase().includes("developer")) { - clean = `Engineered solutions to ${clean.charAt(0).toLowerCase() + clean.slice(1)}`; - } else if (positionTitle.toLowerCase().includes("support") || positionTitle.toLowerCase().includes("admin")) { - clean = `Administered operations to ${clean.charAt(0).toLowerCase() + clean.slice(1)}`; - } else { - clean = `Delivered key deliverables to ${clean.charAt(0).toLowerCase() + clean.slice(1)}`; - } - } - } - - // 4. Ensure trailing punctuation + // 3. Ensure proper sentence termination if (!clean.endsWith(".") && !clean.endsWith("!") && !clean.endsWith("?")) { clean = clean + "."; } - // 5. Capitalize first letter - clean = clean.charAt(0).toUpperCase() + clean.slice(1); - return clean; } -function polishSummary(title: string, currentSummary: string, skills: string[]): string { - const skillsSnippet = Array.isArray(skills) && skills.length > 0 ? skills.slice(0, 5).join(", ") : ""; - const lowerSummary = (currentSummary || "").toLowerCase(); - const lowerTitle = (title || "").toLowerCase(); - - if (lowerSummary.includes("it support") || lowerTitle.includes("support") || lowerTitle.includes("admin")) { - return `Dedicated ${title || "IT Systems Specialist"} with demonstrated expertise administering server infrastructure, identity management, and technical troubleshooting${skillsSnippet ? ` across ${skillsSnippet}` : ""}. Proven track record of resolving complex escalations, automating operational workflows, and ensuring enterprise system reliability.`; +function polishSummaryStrict(currentSummary: string, title?: string): string { + if (!currentSummary || currentSummary.trim().length === 0) { + return title ? `Professional with experience in ${title}.` : ""; } - if (lowerSummary.includes("software") || lowerTitle.includes("engineer") || lowerTitle.includes("developer")) { - return `Results-driven ${title || "Software Systems Engineer"} with extensive experience architecting high-throughput applications, distributed microservices, and database systems${skillsSnippet ? ` utilizing ${skillsSnippet}` : ""}. Proven history of reducing API latency, maintaining 99.9%+ availability, and delivering mission-critical web platforms.`; + let text = currentSummary.trim(); + text = text.replace(/^(hi,?\s*i('?m| am)|i am an?|i'm an?)\s+/i, ""); + text = text.charAt(0).toUpperCase() + text.slice(1); + if (!text.endsWith(".") && !text.endsWith("!") && !text.endsWith("?")) { + text = text + "."; } - - if (lowerTitle.includes("design") || lowerSummary.includes("design") || lowerTitle.includes("ui") || lowerTitle.includes("ux")) { - return `Creative and strategic ${title || "UI/UX Designer"} specializing in cohesive digital design systems, user-centric interfaces, and interactive prototyping${skillsSnippet ? ` with expertise in ${skillsSnippet}` : ""}. Proven record of translating complex product workflows into intuitive user experiences.`; - } - - if (currentSummary && currentSummary.length > 60) { - let enhanced = currentSummary.trim(); - if (!enhanced.endsWith(".")) enhanced += "."; - return `Results-oriented ${title || "Professional"} with proven expertise in operational execution and technical problem solving. ${enhanced}`; - } - - return `Results-driven ${title || "Professional"} with hands-on experience in technical problem solving, workflow optimization, and team deliverables${skillsSnippet ? ` specializing in ${skillsSnippet}` : ""}. Proven track record of maintaining high operational quality and delivering resilient outcomes.`; + return text; } -// Optional LLM enhancement if user has OPENAI or OPENROUTER configured -async function optimizeWithLLM(payload: any) { +// OpenRouter / Small Model Integration (Guaranteed anti-hallucination prompt) +async function optimizeWithOpenRouter(payload: { profile: any; work: any[]; skills: string[] }) { const apiKey = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY; if (!apiKey) return null; @@ -122,40 +80,67 @@ async function optimizeWithLLM(payload: any) { const endpoint = isRouter ? "https://openrouter.ai/api/v1/chat/completions" : `${process.env.OPENAI_BASE_URL || "https://api.openai.com/v1"}/chat/completions`; - const model = process.env.OPENROUTER_MODEL || process.env.OPENAI_MODEL || (isRouter ? "google/gemini-2.5-flash" : "gpt-4o-mini"); + + const model = + process.env.OPENROUTER_MODEL || + process.env.OPENAI_MODEL || + (isRouter ? "meta-llama/llama-3.2-3b-instruct" : "gpt-4o-mini"); + + const systemPrompt = `You are an accurate, truth-grounded ATS resume editor. +CRITICAL INTEGRITY INSTRUCTIONS: +1. NEVER invent, fabricate, exaggerate, or assume any facts, tools, employers, degrees, metrics, dates, or certifications. +2. Only rewrite the candidate's existing text to improve grammar, conciseness, and use strong action verbs. +3. Keep the exact same company names, titles, and real duties. +4. If a bullet point is short or simple, polish its grammar; DO NOT add fake percentages, fake dollar amounts, or unmentioned technologies. +5. Return strictly a JSON object with this format: +{ + "profile": { "summary": "polished summary" }, + "work": [ + { "position": "...", "company": "...", "summary": "...", "highlights": ["polished bullet 1", "polished bullet 2"] } + ] +}`; try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 12000); + const res = await fetch(endpoint, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", + ...(isRouter ? { "HTTP-Referer": "https://directwire.io", "X-Title": "DirectWire Resume Optimizer" } : {}), }, + signal: controller.signal, body: JSON.stringify({ model, + temperature: 0.1, + max_tokens: 1800, messages: [ - { - role: "system", - content: - "You are an expert executive resume optimizer and ATS specialist. Transform the candidate's summary and work bullets into impactful, quantifiable, action-verb driven bullets that score 95+ on Workday and Greenhouse ATS scans. Return strictly JSON matching: { profile: { ...profile, summary: string }, work: [{ ...work[i], highlights: string[] }] }", - }, + { role: "system", content: systemPrompt }, { role: "user", - content: `Optimize this resume content:\n\n${JSON.stringify(payload)}`, + content: `Polish and improve the grammar/action-verbs of this resume while keeping ALL facts 100% true:\n\n${JSON.stringify(payload)}`, }, ], response_format: { type: "json_object" }, }), }); - if (!res.ok) return null; - const json = await res.json(); - const content = json.choices?.[0]?.message?.content; - if (!content) return null; + clearTimeout(timeout); - return JSON.parse(content); - } catch (err) { - console.error("LLM optimize fallback triggered:", err); + if (!res.ok) { + console.warn("OpenRouter API returned non-200:", res.status, await res.text()); + return null; + } + + const json = await res.json(); + const rawContent = json.choices?.[0]?.message?.content; + if (!rawContent) return null; + + return JSON.parse(rawContent); + } catch (err: any) { + console.warn("OpenRouter enhancement skipped or timed out, using deterministic polish:", err.message); return null; } } @@ -164,80 +149,79 @@ export async function POST(req: Request) { try { const { profile, work, skills, education, projects } = await req.json(); - const candidateTitle = profile?.title || (work?.[0]?.position) || "Technical Professional"; - const candidateSkills = Array.isArray(skills) && skills.length > 0 ? skills : ["Problem Solving", "Communication", "System Architecture"]; + const candidateProfile = profile || {}; + const candidateWork = Array.isArray(work) ? work : []; + const candidateSkills = Array.isArray(skills) ? skills : []; + const candidateEducation = Array.isArray(education) ? education : []; + const candidateProjects = Array.isArray(projects) ? projects : []; + + // 1. Try OpenRouter / Small model if API key is provided + let aiResult = await optimizeWithOpenRouter({ + profile: candidateProfile, + work: candidateWork, + skills: candidateSkills, + }); + + if (aiResult?.profile?.summary || Array.isArray(aiResult?.work)) { + const mergedWork = candidateWork.map((originalWork: any, idx: number) => { + const aiWork = aiResult.work?.[idx]; + if (!aiWork) return originalWork; + return { + ...originalWork, + summary: aiWork.summary || originalWork.summary || "", + highlights: Array.isArray(aiWork.highlights) && aiWork.highlights.length > 0 + ? aiWork.highlights.map((h: string) => polishBulletStrict(h)) + : (originalWork.highlights || []).map((h: string) => polishBulletStrict(h)), + }; + }); - // 1. Try external AI if configured - let aiOptimized = await optimizeWithLLM({ profile, work, skills }); - if (aiOptimized?.profile && Array.isArray(aiOptimized?.work)) { return NextResponse.json({ success: true, data: { profile: { - ...profile, - ...aiOptimized.profile, + ...candidateProfile, + summary: aiResult.profile?.summary || polishSummaryStrict(candidateProfile.summary, candidateProfile.title), }, - work: aiOptimized.work, + work: mergedWork, skills: candidateSkills, - education: education || [], - projects: projects || [], + education: candidateEducation, + projects: candidateProjects, }, }); } - // 2. High-precision deterministic local polish engine (Zero external cost, instant, 100% reliable) - const enhancedProfile = { - ...profile, - title: candidateTitle, - summary: polishSummary(candidateTitle, profile?.summary || "", candidateSkills), + // 2. Truthful deterministic local polish engine (Zero hallucination guarantee) + const polishedWork = candidateWork.map((w: any) => { + const rawHighlights = Array.isArray(w.highlights) + ? w.highlights + : (w.highlights ? [w.highlights] : []); + + const polishedBullets = rawHighlights + .map((h: string) => polishBulletStrict(h)) + .filter((h: string) => h.length > 0); + + return { + ...w, + position: w.position || "", + company: w.company || "", + summary: w.summary ? polishBulletStrict(w.summary) : "", + highlights: polishedBullets, + }; + }); + + const polishedProfile = { + ...candidateProfile, + summary: polishSummaryStrict(candidateProfile.summary, candidateProfile.title), }; - const enhancedWork = Array.isArray(work) && work.length > 0 - ? work.map((w: any) => { - const pos = w.position || candidateTitle || "Role"; - const rawHighlights = Array.isArray(w.highlights) - ? w.highlights - : (w.highlights ? [w.highlights] : []); - - let polishedHighlights = rawHighlights - .map((h: string) => polishBullet(h, pos)) - .filter((h: string) => h.length > 5); - - if (polishedHighlights.length === 0) { - polishedHighlights = [ - `Directed core operational and technical duties as ${pos}, maintaining 100% SLA compliance.`, - `Automated routine workflows and collaborated across teams to resolve technical blockers efficiently.` - ]; - } - - return { - ...w, - position: pos, - company: w.company || "Company", - summary: w.summary ? polishBullet(w.summary, pos) : "", - highlights: polishedHighlights, - }; - }) - : [ - { - position: candidateTitle, - company: "Enterprise Technology Inc.", - summary: "Directed technical operations and core deliverables.", - highlights: [ - `Delivered scalable system components as ${candidateTitle}, ensuring 99.9% uptime and high quality delivery.`, - "Collaborated cross-functionally to streamline operational workflows and reduce incident resolution time by 30%." - ] - } - ]; - return NextResponse.json({ success: true, data: { - profile: enhancedProfile, - work: enhancedWork, + profile: polishedProfile, + work: polishedWork, skills: candidateSkills, - education: education || [], - projects: projects || [], + education: candidateEducation, + projects: candidateProjects, }, }); } catch (error: any) { @@ -245,4 +229,3 @@ export async function POST(req: Request) { return NextResponse.json({ error: error.message || "Failed to optimize resume" }, { status: 500 }); } } - diff --git a/web/src/app/api/resume/parse/route.ts b/web/src/app/api/resume/parse/route.ts index bbae53c..7658787 100644 --- a/web/src/app/api/resume/parse/route.ts +++ b/web/src/app/api/resume/parse/route.ts @@ -328,21 +328,9 @@ function parseResumeFull(rawText: string) { location: location || "", summary: summary || "", }, - skills: allDetectedSkills.length > 0 ? allDetectedSkills : ["Problem Solving", "Communication", "Documentation"], - work: parsedWork.length > 0 ? parsedWork : [ - { - position: headline || "Professional Experience", - company: "Previous Employer", - summary: "Provided high-quality technical and operational contributions.", - highlights: [ - "Executed core project responsibilities and operational workflows with 100% adherence to standards.", - "Collaborated cross-functionally to diagnose technical roadblocks and implement sustainable improvements." - ] - } - ], - education: parsedEducation.length > 0 ? parsedEducation : [ - { degree: "Technical Certification / Degree", institution: "Accredited Institution" } - ], + skills: allDetectedSkills.length > 0 ? allDetectedSkills : [], + work: parsedWork, + education: parsedEducation, }; } diff --git a/web/src/app/applications/page.tsx b/web/src/app/applications/page.tsx index e40a25e..6b0faec 100644 --- a/web/src/app/applications/page.tsx +++ b/web/src/app/applications/page.tsx @@ -280,10 +280,10 @@ export default function ApplicationsPage() {
    {job.company} โ€ข - ๐Ÿ“ {job.location} - {job.isRemote && โ€ข ๐ŸŒ Remote} + {job.location} + {job.isRemote && โ€ข Remote} {job.salaryMin && ( - โ€ข ๐Ÿ’ฐ ${job.salaryMin.toLocaleString()} + โ€ข ${job.salaryMin.toLocaleString()} )}
    )} @@ -342,7 +342,7 @@ export default function ApplicationsPage() { {isExpanded && (

    - โฑ๏ธ Application Status Audit Trail + Application Status Audit Trail Real-time status updates

    diff --git a/web/src/app/companies/[id]/page.tsx b/web/src/app/companies/[id]/page.tsx index 277ca8c..b4eda81 100644 --- a/web/src/app/companies/[id]/page.tsx +++ b/web/src/app/companies/[id]/page.tsx @@ -252,7 +252,7 @@ export default function CompanyDetailPage() {
    - {company.location && ๐Ÿ“ {company.location}} + {company.location && {company.location}} {company.website && ( - ๐ŸŒ Website + Website )} {company.verificationStatus === "UNCLAIMED" && ( @@ -353,7 +353,7 @@ export default function CompanyDetailPage() { {job.title}
    - ๐Ÿ“ {job.location} + {job.location} {job.isRemote && ( Remote @@ -371,7 +371,7 @@ export default function CompanyDetailPage() { onClick={() => setApplyingJob(job)} className="px-3.5 py-1.5 bg-zinc-900 hover:bg-zinc-800 text-white text-xs font-bold rounded transition-colors" > - โšก Quick Apply + Quick Apply
    ))} @@ -558,7 +558,7 @@ export default function CompanyDetailPage() { onClick={() => setReportingReviewId(rev.id)} className="text-[10px] text-zinc-400 hover:text-red-600 font-medium" > - ๐Ÿšฉ Report + Report
    diff --git a/web/src/app/employer/ats/page.tsx b/web/src/app/employer/ats/page.tsx index 528ee87..d08aa17 100644 --- a/web/src/app/employer/ats/page.tsx +++ b/web/src/app/employer/ats/page.tsx @@ -398,7 +398,7 @@ export default function EmployerATSPage() { key={t.id} className="text-[9px] font-mono font-bold bg-amber-50 text-amber-800 border border-amber-200 px-1.5 py-0.5 rounded" > - ๐Ÿท๏ธ {t.tag} + {t.tag} ))}
    @@ -500,7 +500,7 @@ export default function EmployerATSPage() { {/* Snapshot Banner */} {snap && (
    - ๐Ÿ”’ Frozen Candidate Snapshot: Captured on{" "} + Frozen Candidate Snapshot: Captured on{" "} {new Date(snap.submittedAt || selectedApp.createdAt).toLocaleString()}
    )} @@ -524,7 +524,7 @@ export default function EmployerATSPage() { {cand.location && (
    Location - ๐Ÿ“ {cand.location} + {cand.location}
    )}
    @@ -538,7 +538,7 @@ export default function EmployerATSPage() { key={t.id} className="inline-flex items-center space-x-1 text-xs font-mono font-bold bg-amber-50 text-amber-800 border border-amber-200 px-2 py-1 rounded" > - ๐Ÿท๏ธ {t.tag} + {t.tag}
    @@ -266,7 +266,7 @@ export default function CandidateSearchPage() { href={`mailto:${cand.user.email}?subject=Opportunity%20via%20JobsBoard`} className="px-3 py-1.5 bg-zinc-900 text-white text-xs font-semibold rounded hover:bg-zinc-800 transition-colors" > - โœ‰ Contact Candidate + Contact Candidate diff --git a/web/src/app/profile/page.tsx b/web/src/app/profile/page.tsx index 13f2b8a..0112a3d 100644 --- a/web/src/app/profile/page.tsx +++ b/web/src/app/profile/page.tsx @@ -213,7 +213,7 @@ export default function ProfileBuilderPage() { @@ -228,7 +228,7 @@ export default function ProfileBuilderPage() { {/* Privacy & Employer Search Visibility Section */}

    - ๐Ÿ”’ Privacy & Employer Search Visibility + Privacy & Employer Search Visibility

    diff --git a/web/src/app/saved-jobs/page.tsx b/web/src/app/saved-jobs/page.tsx index 6f66920..8a176a8 100644 --- a/web/src/app/saved-jobs/page.tsx +++ b/web/src/app/saved-jobs/page.tsx @@ -184,11 +184,11 @@ export default function SavedJobsPage() {
    {job.company} - ๐Ÿ“ {job.location} - {job.department && ๐Ÿข {job.department}} + {job.location} + {job.department && {job.department}} {job.salaryMin && ( - ๐Ÿ’ฐ ${job.salaryMin.toLocaleString()} + ${job.salaryMin.toLocaleString()} {job.salaryMax ? ` - $${job.salaryMax.toLocaleString()}` : "+"} )} diff --git a/web/src/components/NotificationPreferences.tsx b/web/src/components/NotificationPreferences.tsx index 06c39c0..043bab8 100644 --- a/web/src/components/NotificationPreferences.tsx +++ b/web/src/components/NotificationPreferences.tsx @@ -70,7 +70,7 @@ export function NotificationPreferences() {
    -

    ๐Ÿ”” Notification Preferences

    +

    Notification Preferences

    Configure how and when you receive status updates and job alert notifications.

    diff --git a/web/src/components/OneClickApplyModal.tsx b/web/src/components/OneClickApplyModal.tsx index 0f5c2bd..0c7f675 100644 --- a/web/src/components/OneClickApplyModal.tsx +++ b/web/src/components/OneClickApplyModal.tsx @@ -145,7 +145,7 @@ export function OneClickApplyModal({ job, onClose, onSuccess }: OneClickApplyMod
    - ๐Ÿ“„ Application Snapshot Summary + Application Snapshot Summary Profile Verified @@ -156,20 +156,20 @@ export function OneClickApplyModal({ job, onClose, onSuccess }: OneClickApplyMod )}
    - {profileData?.location &&
    ๐Ÿ“ Location: {profileData.location}
    } + {profileData?.location &&
    Location: {profileData.location}
    } {profileData?.skills?.length > 0 && (
    - ๐Ÿ’ก Skills:{" "} + Skills:{" "} {profileData.skills.slice(0, 4).map((s: any) => s.name).join(", ")}
    )} {activeResume ? (
    - ๐Ÿ“Ž Active Resume: {activeResume.title} + Active Resume: {activeResume.title}
    ) : (
    - ๐Ÿ“‹ Profile details will be submitted directly to employer. + Profile details will be submitted directly to employer.
    )}
    diff --git a/web/src/components/ResumeEditor.tsx b/web/src/components/ResumeEditor.tsx index d647d52..ea46a19 100644 --- a/web/src/components/ResumeEditor.tsx +++ b/web/src/components/ResumeEditor.tsx @@ -439,9 +439,9 @@ export function ResumeEditor() { onClick={handleOptimize} disabled={optimizing} className="bg-emerald-800 hover:bg-emerald-900 text-white font-mono text-xs font-semibold px-4 py-2 border border-emerald-950 shadow-[2px_2px_0px_#064e3b] transition-all disabled:opacity-50" - title="Uses active verb transformer to enhance bullets & summary" + title="Polishes bullet points and grammar with truth-grounded ATS active verbs" > - {optimizing ? "Polishing Bullets..." : "โœจ Optimize Resume"} + {optimizing ? "Polishing Bullets..." : "Enhance & Polish"} - ๐Ÿ“ฅ Download PDF + Download PDF