Frontend Bug Fixes — February 2026
Audit of 6 reported frontend bugs, their root causes, and applied fixes.
Summary
| Bug | Severity | Status | Description |
|---|---|---|---|
| BUG 1 | CRITICAL | No action needed | Missing /jobseeker/connections/companies route |
| BUG 2 | MEDIUM | Fixed | Hardcoded CVType in talentRowBuilder.ts |
| BUG 3 | MEDIUM | Fixed | Hardcoded CVType in candidateRowBuilder.ts |
| BUG 4 | MEDIUM | Fixed | useEffect cleanup missing on early returns in authGuard.tsx |
| BUG 5 | LOW | No action needed | useEffect without dependency array in WebSocketContext.tsx |
| BUG 6 | LOW | Fixed | npm run lint broken — next lint removed in Next.js 16 |
BUG 1 — Missing /jobseeker/connections/companies Route
Status: No action needed (not a real bug)
Report: TypeScript emits TS2307: Cannot find module for app/jobseeker/connections/companies/page.js from the Next.js route validator.
Investigation:
- The
/jobseeker/connections/directory contains onlycollaboratingpartner/,contacts/, and_mockData.ts - Navigation config (
lib/config/navigation.ts) does not define a "Companies" submenu item for job seekers - Route config (
lib/config/routes.ts) has no/jobseeker/connections/companiesentry - No code anywhere references this path
Conclusion: This route was never intended for job seekers. The company role has /company/connections/companies, but job seekers use "Collaborating Partners" instead. The TS2307 warning from the Next.js type validator is harmless noise — Next.js handles missing routes gracefully at runtime.
BUG 2 — Hardcoded CVType in talentRowBuilder.ts
Status: Fixed
File: lib/utils/candidates/talentRowBuilder.ts
Root cause: Line 233 hardcoded CVType: "MANUAL ENTRY" for every talent row, ignoring the source field that the backend already returns via the GET_COMPANY_TALENTS GraphQL query.
The source field supports these values (defined as TalentSource in lib/hooks/talents/useCompanyTalents.ts):
MANUAL_ENTRY | TALENT_POOL_IMPORT | CV_UPLOAD | APPLICATION_DIRECT |
REFERRAL | RECRUITMENT_AGENCY | LINKEDIN_IMPORT | BULK_IMPORT |
API_INTEGRATION | INTERNAL_TRANSFER
Changes:
- Added import for
formatEnumLabelfrom@utils/formatting/formatEnumLabel - Replaced hardcoded value (line 233):
- CVType: "MANUAL ENTRY", // per requirement
+ CVType: formatEnumLabel(t.source ?? "MANUAL_ENTRY"), - Updated type comment (line 22):
- CVType: string // show "MANUAL ENTRY" for talents
+ CVType: string // talent source label (e.g. "Manual Entry", "Invited", "Shared")
How it works: formatEnumLabel converts enum-style strings like "CV_UPLOAD" into readable labels like "Cv Upload". When source is null/undefined, it falls back to "MANUAL_ENTRY" which renders as "Manual Entry".
No GraphQL changes needed — the GET_COMPANY_TALENTS query already fetches source, and the GqlTalent interface already includes it.
BUG 3 — Hardcoded CVType in candidateRowBuilder.ts
Status: Fixed
File: lib/utils/candidates/candidateRowBuilder.ts
Root cause: The buildRows() function (line 228) hardcoded CVType: "MANUAL ENTRY" identically to BUG 2. Note: the buildCandidateRows() and buildCandidateRowsWithTalents() functions in the same file were already correct — they used c.source || "N/A".
Changes:
- Added import for
formatEnumLabelfrom@utils/formatting/formatEnumLabel - Replaced hardcoded value in
buildRows()(line 228):- CVType: "MANUAL ENTRY", // per requirement
+ CVType: formatEnumLabel(t.source ?? "MANUAL_ENTRY"), - Updated type comment (line 22):
- CVType: string // show "MANUAL ENTRY" for talents
+ CVType: string // talent source label (e.g. "Manual Entry", "Invited", "Shared") - Added
sourcetoGqlTalentinterface inlib/hooks/useCompanyCandidates.ts(line 90):export interface GqlTalent {
id: string
userId: string
+ source?: string | null
user?: GqlUser | null
activeCV?: GqlActiveCV | null
}
Why the type change was needed: The GqlTalent interface in useCompanyCandidates.ts (used by buildRows) was missing the source field, even though the GET_COMPANY_TALENTS GraphQL query already fetches it. The GqlTalent in useCompanyTalents.ts already had it.
BUG 4 — useEffect Cleanup Missing on Early Returns in authGuard.tsx
Status: Fixed
File: components/auth/authGuard.tsx
Root cause: The useEffect had multiple early return statements (for loading, not logged in, onboarding redirects, etc.) that exited the effect without returning a cleanup function. The cleanup that clears redirectTimer was only returned on the "happy path" (line 130). This meant:
- Effect sets a 100ms redirect timer (line 120-122) when a role mismatch is detected
- Component unmounts before the timer fires
- No cleanup runs because the previous effect execution returned
void(early exit on line 123) - The stale timer fires and calls
router.replace("/auth/unauthorized")on an unmounted component
Note: Re-renders were safe because the effect already clears pending timers at the top (lines 27-30). Only the unmount case was broken.
Fix: Wrapped all guard logic in a local runGuard() function, so the cleanup is always returned regardless of which code path executes:
useEffect(() => {
setHasAccess(false)
setAccessChecked(false)
if (redirectTimer.current) {
clearTimeout(redirectTimer.current)
redirectTimer.current = null
}
const runGuard = () => {
if (loading) return
if (!isLoggedIn) { router.replace("/auth/signin"); return }
// ... all other guard logic with early returns ...
}
runGuard()
// Cleanup ALWAYS runs on unmount now
return () => {
if (redirectTimer.current) {
clearTimeout(redirectTimer.current)
redirectTimer.current = null
}
}
}, [loading, user, isLoggedIn, isCompany, requiredRole, router, pathname])
BUG 5 — useEffect Without Dependency Array in WebSocketContext.tsx
Status: No action needed (intentional pattern)
File: contexts/WebSocketContext.tsx (lines 169-173)
useEffect(() => {
autoConnectRef.current = autoConnect
connectRef.current = connect
disconnectRef.current = disconnect
}) // no dependency array
Analysis: This is a standard React "ref-sync" pattern. The effect runs on every render to keep refs in sync with the latest values. This allows the auto-connect effect (lines 175-202) to use stable refs in its dependency array while still accessing the latest connect/disconnect callbacks.
Running on every render is intentional — ref assignments (ref.current = value) are trivially cheap and cause no re-renders. Adding a dependency array would risk stale refs.
BUG 6 — npm run lint Broken
Status: Fixed
File: package.json
Root cause: The lint script used next lint, but Next.js 16 removed the lint subcommand. When next lint runs, Next.js interprets lint as a [directory] argument, producing:
Invalid project directory provided, no such directory: .../aiqlick-frontend/lint
The available Next.js 16 commands are: build, dev, start, info, telemetry, typegen, upgrade, experimental-test, internal — no lint.
Fix:
- "lint": "next lint"
+ "lint": "eslint ."
This calls ESLint directly using the project's eslint.config.mjs flat config, which already extends next/core-web-vitals and next/typescript.
Files Changed
| File | Change |
|---|---|
lib/utils/candidates/talentRowBuilder.ts | Added formatEnumLabel import; replaced hardcoded CVType; updated comment |
lib/utils/candidates/candidateRowBuilder.ts | Added formatEnumLabel import; replaced hardcoded CVType in buildRows(); updated comment |
lib/hooks/useCompanyCandidates.ts | Added source?: string | null to GqlTalent interface |
components/auth/authGuard.tsx | Restructured useEffect so cleanup function is always returned |
package.json | Changed lint script from next lint to eslint . |