Eliminate all synthetic resume hallucinations, integrate OpenRouter small-model support, and strip all emojis site-wide

This commit is contained in:
JobsBoard Deployer 2026-09-05 13:39:53 -04:00
parent 7f0ae931d5
commit 9205b88458
16 changed files with 194 additions and 221 deletions

View file

@ -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

View file

@ -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"}
</button>
</div>
@ -294,10 +294,10 @@ export default function JobAlertsPage() {
</div>
<div className="text-xs text-zinc-500 flex flex-wrap gap-x-3 gap-y-1">
{s.keywords && <span>🔍 "{s.keywords}"</span>}
{s.location && <span>📍 {s.location}</span>}
{s.department && <span>🏢 {s.department}</span>}
{s.minSalary && <span>💰 Min ${s.minSalary.toLocaleString()}</span>}
{s.keywords && <span> "{s.keywords}"</span>}
{s.location && <span> {s.location}</span>}
{s.department && <span> {s.department}</span>}
{s.minSalary && <span> Min ${s.minSalary.toLocaleString()}</span>}
</div>
{s.lastSentAt && (

View file

@ -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`,
});

View file

@ -99,8 +99,8 @@ export async function POST(req: Request) {
(j) =>
`<li style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #f1f5f9;">
<strong><a href="${j.jobUrl || `${baseUrl}/jobs/${j.id}`}" target="_blank" style="color: #2563eb; text-decoration: none; font-size: 16px;">${j.title}</a></strong>
<div style="color: #334155; font-size: 14px; margin-top: 4px;">🏢 ${j.company} • 📍 ${j.location} ${j.isRemote ? '• 🌐 Remote' : ''}</div>
${j.salaryMin ? `<div style="color: #059669; font-size: 13px; margin-top: 2px;">💵 $${j.salaryMin.toLocaleString()}${j.salaryMax ? ` - $${j.salaryMax.toLocaleString()}` : '+'}</div>` : ''}
<div style="color: #334155; font-size: 14px; margin-top: 4px;"> ${j.company} • ${j.location} ${j.isRemote ? '• Remote' : ''}</div>
${j.salaryMin ? `<div style="color: #059669; font-size: 13px; margin-top: 2px;"> $${j.salaryMin.toLocaleString()}${j.salaryMax ? ` - $${j.salaryMax.toLocaleString()}` : '+'}</div>` : ''}
</li>`
)
.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: `
<div style="font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 0 auto; color: #0f172a; border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden;">
<div style="background-color: #2563eb; color: #ffffff; padding: 20px; text-align: center;">
@ -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: `<div style="font-family: sans-serif;"><h2>Your Job Alert</h2><ul>${jobListHtml}</ul></div>`,
});

View file

@ -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: `
<div style="font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
<h2 style="margin-top: 0; color: #0f172a;">Password Reset Request</h2>

View file

@ -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<string, string[]> = {
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 });
}
}

View file

@ -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,
};
}

View file

@ -280,10 +280,10 @@ export default function ApplicationsPage() {
<div className="text-xs text-zinc-600 space-x-2">
<span className="font-semibold text-zinc-800">{job.company}</span>
<span>•</span>
<span>📍 {job.location}</span>
{job.isRemote && <span className="text-emerald-700 font-medium">• 🌐 Remote</span>}
<span> {job.location}</span>
{job.isRemote && <span className="text-emerald-700 font-medium">• Remote</span>}
{job.salaryMin && (
<span className="text-emerald-700 font-medium">• 💰 ${job.salaryMin.toLocaleString()}</span>
<span className="text-emerald-700 font-medium">• ${job.salaryMin.toLocaleString()}</span>
)}
</div>
)}
@ -342,7 +342,7 @@ export default function ApplicationsPage() {
{isExpanded && (
<div className="bg-zinc-50 p-3 rounded-md border border-zinc-200 text-xs space-y-2 mt-2">
<h4 className="font-bold text-zinc-900 text-xs flex items-center justify-between border-b border-zinc-200 pb-1.5">
<span>⏱️ Application Status Audit Trail</span>
<span> Application Status Audit Trail</span>
<span className="text-[10px] font-normal text-zinc-500 font-mono">Real-time status updates</span>
</h4>

View file

@ -252,7 +252,7 @@ export default function CompanyDetailPage() {
</div>
<div className="flex items-center space-x-3 text-xs text-zinc-500 mt-1">
{company.location && <span>📍 {company.location}</span>}
{company.location && <span> {company.location}</span>}
{company.website && (
<a
href={company.website}
@ -260,7 +260,7 @@ export default function CompanyDetailPage() {
rel="noopener noreferrer"
className="text-blue-600 hover:underline flex items-center space-x-1 font-medium"
>
<span>🌐 Website</span>
<span> Website</span>
</a>
)}
{company.verificationStatus === "UNCLAIMED" && (
@ -353,7 +353,7 @@ export default function CompanyDetailPage() {
{job.title}
</Link>
<div className="flex items-center space-x-3 text-xs text-zinc-500 mt-1">
<span>📍 {job.location}</span>
<span> {job.location}</span>
{job.isRemote && (
<span className="bg-emerald-50 text-emerald-700 px-1.5 py-0.5 rounded font-mono text-[10px]">
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
</button>
</div>
))}
@ -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
</button>
</div>

View file

@ -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}
</span>
))}
</div>
@ -500,7 +500,7 @@ export default function EmployerATSPage() {
{/* Snapshot Banner */}
{snap && (
<div className="bg-emerald-50 text-emerald-900 border border-emerald-200 p-3 rounded-md text-xs">
🔒 <strong>Frozen Candidate Snapshot:</strong> Captured on{" "}
<strong>Frozen Candidate Snapshot:</strong> Captured on{" "}
{new Date(snap.submittedAt || selectedApp.createdAt).toLocaleString()}
</div>
)}
@ -524,7 +524,7 @@ export default function EmployerATSPage() {
{cand.location && (
<div>
<span className="font-semibold text-zinc-500 block text-[10px] uppercase font-mono">Location</span>
<span className="font-medium text-zinc-800">📍 {cand.location}</span>
<span className="font-medium text-zinc-800"> {cand.location}</span>
</div>
)}
</div>
@ -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"
>
<span>🏷️ {t.tag}</span>
<span> {t.tag}</span>
<button
onClick={() => handleRemoveTag(selectedApp.id, t.tag)}
className="hover:text-red-700 font-bold ml-1"
@ -638,7 +638,7 @@ export default function EmployerATSPage() {
<div className="border-t border-zinc-100 pt-3">
<h4 className="font-bold text-zinc-900 mb-1">Attached Resume Document</h4>
<div className="bg-zinc-50 p-3 rounded-md border border-zinc-200 flex justify-between items-center">
<span className="font-semibold text-zinc-800">📄 {resume.title}</span>
<span className="font-semibold text-zinc-800"> {resume.title}</span>
<div className="flex items-center space-x-2">
<span className="text-[10px] font-mono text-emerald-700 bg-emerald-50 border border-emerald-200 px-2 py-0.5 rounded font-bold">
Verified Attached

View file

@ -190,7 +190,7 @@ export default function CandidateSearchPage() {
{errorMsg && (
<div className="p-4 bg-red-50 border border-red-200 text-red-700 text-xs rounded-md flex justify-between items-center">
<span>⚠️ {errorMsg}</span>
<span> {errorMsg}</span>
<button
onClick={searchCandidates}
className="px-3 py-1 bg-red-600 text-white rounded font-bold hover:bg-red-700 transition-colors"
@ -246,7 +246,7 @@ export default function CandidateSearchPage() {
{cand.user.name || "Anonymous Candidate"}
</h3>
<p className="text-xs font-medium text-zinc-600">
{cand.headline || "Job Candidate"} {cand.location && `• 📍 ${cand.location}`}
{cand.headline || "Job Candidate"} {cand.location && `• ${cand.location}`}
</p>
</div>
@ -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
</a>
</div>
</div>

View file

@ -213,7 +213,7 @@ export default function ProfileBuilderPage() {
</div>
<label className="cursor-pointer bg-zinc-100 hover:bg-zinc-200 border border-zinc-300 text-zinc-800 text-xs font-semibold px-3 py-1.5 rounded transition-colors inline-block">
{parsing ? "Parsing Resume..." : "✨ Auto-Fill from Resume PDF/docx"}
{parsing ? "Parsing Resume..." : " Auto-Fill from Resume PDF/docx"}
<input type="file" accept=".pdf,.docx,.txt" onChange={handleResumeUpload} className="hidden" />
</label>
</div>
@ -228,7 +228,7 @@ export default function ProfileBuilderPage() {
{/* Privacy & Employer Search Visibility Section */}
<div className="bg-white p-5 rounded-md border border-zinc-200 shadow-sm space-y-3">
<h2 className="text-sm font-bold text-zinc-900 flex items-center space-x-2">
<span>🔒 Privacy & Employer Search Visibility</span>
<span> Privacy & Employer Search Visibility</span>
</h2>
<div className="bg-blue-50 border border-blue-200 p-3.5 rounded text-xs text-blue-900 space-y-2">

View file

@ -184,11 +184,11 @@ export default function SavedJobsPage() {
<div className="mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-zinc-600">
<span className="font-medium text-zinc-800">{job.company}</span>
<span>📍 {job.location}</span>
{job.department && <span>🏢 {job.department}</span>}
<span> {job.location}</span>
{job.department && <span> {job.department}</span>}
{job.salaryMin && (
<span className="text-emerald-700 font-medium">
💰 ${job.salaryMin.toLocaleString()}
${job.salaryMin.toLocaleString()}
{job.salaryMax ? ` - $${job.salaryMax.toLocaleString()}` : "+"}
</span>
)}

View file

@ -70,7 +70,7 @@ export function NotificationPreferences() {
<div className="bg-white p-5 rounded-md border border-zinc-200 shadow-sm space-y-4">
<div className="flex justify-between items-center">
<div>
<h2 className="text-sm font-bold text-zinc-900">🔔 Notification Preferences</h2>
<h2 className="text-sm font-bold text-zinc-900"> Notification Preferences</h2>
<p className="text-xs text-zinc-500 mt-0.5">
Configure how and when you receive status updates and job alert notifications.
</p>

View file

@ -145,7 +145,7 @@ export function OneClickApplyModal({ job, onClose, onSuccess }: OneClickApplyMod
<form onSubmit={handleSubmit} className="space-y-4">
<div className="bg-zinc-50 p-4 rounded-md border border-zinc-200 text-xs space-y-2">
<div className="flex justify-between items-center pb-2 border-b border-zinc-200 font-bold text-zinc-800">
<span>📄 Application Snapshot Summary</span>
<span> Application Snapshot Summary</span>
<span className="text-[10px] font-mono bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded font-semibold">
Profile Verified
</span>
@ -156,20 +156,20 @@ export function OneClickApplyModal({ job, onClose, onSuccess }: OneClickApplyMod
)}
<div className="text-zinc-500 space-y-1 text-[11px]">
{profileData?.location && <div>📍 Location: {profileData.location}</div>}
{profileData?.location && <div> Location: {profileData.location}</div>}
{profileData?.skills?.length > 0 && (
<div>
💡 Skills:{" "}
Skills:{" "}
{profileData.skills.slice(0, 4).map((s: any) => s.name).join(", ")}
</div>
)}
{activeResume ? (
<div className="text-blue-700 font-medium">
📎 Active Resume: {activeResume.title}
Active Resume: {activeResume.title}
</div>
) : (
<div className="text-zinc-600">
📋 Profile details will be submitted directly to employer.
Profile details will be submitted directly to employer.
</div>
)}
</div>

View file

@ -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"}
</button>
<label className="inline-flex items-center space-x-2 font-mono text-xs text-stone-700 cursor-pointer select-none bg-stone-50 border border-stone-300 px-3 py-2">
@ -451,7 +451,7 @@ export function ResumeEditor() {
onChange={(e) => setIsActiveForMatching(e.target.checked)}
className="rounded text-stone-900 focus:ring-stone-900 h-3.5 w-3.5"
/>
<span>Active for Job Matching</span>
<span>Active for Matching</span>
</label>
<a
@ -460,7 +460,7 @@ export function ResumeEditor() {
download
className="bg-stone-900 hover:bg-stone-800 text-stone-100 font-mono text-xs font-semibold px-3.5 py-2 border border-stone-950 shadow-[2px_2px_0px_#1c1917] transition-all inline-flex items-center space-x-1"
>
<span>📥 Download PDF</span>
<span>Download PDF</span>
</a>
<button
@ -468,7 +468,7 @@ export function ResumeEditor() {
disabled={saving}
className="bg-amber-100 hover:bg-amber-200 text-stone-900 font-mono text-xs font-semibold px-4 py-2 border border-stone-400 shadow-[2px_2px_0px_#78350f] transition-all"
>
{savedSuccess ? "✓ Saved" : saving ? "Saving..." : "Save Changes"}
{savedSuccess ? "Saved" : saving ? "Saving..." : "Save Changes"}
</button>
</div>
</div>