QA Report · Pass 1 · Comprehensive

Skill Portal — Pakistan AI Skill Assessment

Date: 2026-08-18 Tester: X2 (browser + API hybrid) App: skill.production1.jugaar.ai Stack: Vite React SPA + nginx/1.24.0 Endpoints tested: 18

Executive Summary

The core learner flow works end-to-end (register → take adaptive test → see AI gap analysis). The Idea Lab AI chat and certificate flows are functional. However, the platform is NOT ready for public advertising. Major claims are wrong, security posture is build-default-empty, and the footer has 10 dead href="#" links — a credibility problem on a platform meant for Pakistani learner trust.

Key issues: (1) meta + landing + dashboard everywhere claim "16 categories, 186 skills" but the real API returns 35 categories / 492 skills; (2) zero security headers (no HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy); (3) hero card promotes a Python assessment that doesn't exist as a slug; (4) dashboard Quick Action links to a non-existent /idea-lab route (real route is /ideas); (5) footer dead links to Privacy/Terms/Cookie/Security.

5
🔴 High
6
🟡 Medium
3
🟢 Low
7
✅ Good
14
All findings

Final Verdict

⚠️ USABLE but NOT READY FOR PUBLIC ADVERTISING. Core app is real, AI features work, adaptive testing works. Front-end is rich and clean. But major marketing claims are wrong, security posture is empty, and several footer/legal links go nowhere. Fix the 5 HIGH items before going public.

H1Counts lie everywhere — "16 categories / 166-186 skills" vs reality 35 / 492
🔴 High

Where: Meta description, OG tags, landing hero stats, landing "Browse all 16" link, dashboard widget, Quick Actions card, /categories H1, /skills H1.

Evidence

Fix

One source of truth (server-side count) bound to all UI claims. If the goal is genuinely to launch with X, X is what you say. If you've grown to 35/492, say 35/492.

H2Zero security headers — no HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
🔴 High

Where: All endpoints.

Evidence

curl -sI https://skill.production1.jugaar.ai/ returns only Server, Date, Content-Type, Content-Length, Last-Modified, Connection, ETag, Cache-Control, Pragma, Expires, Accept-Ranges — no security headers at all.

Fix

Add at minimum in /etc/nginx/sites-available/skill.production1.jugaar.ai:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://*.airouter.ch; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self' https://*.airouter.ch wss: https:; frame-ancestors 'none'" always;

A platform that handles Pakistani learner data, exam results, and certificates cannot ship without these.

H3Hero card promotes "python-assessment" but the slug doesn't exist — Skill not found
🔴 High

Where: Landing hero "demo card" right next to the sign-up CTAs.

Evidence

The card shows: "python-assessment Q 4 / 10 Level 3 — What does the @staticmethod decorator do?". Clicking through to /skills/python-assessment returns the SPA's "Skill not found" page with a "Back to Skills" link. There are real Python skills in the system: python-fundamentals, python-advanced-oop-decorators-scripting.

Fix

Either rename the slug to a real one or update the demo card to point at an existing skill. Whichever — never advertise a slug that 404s.

H4Dashboard "Idea Lab" Quick Action links to /idea-lab (404) — real route is /ideas
🔴 High

Where: /dashboard → Quick Actions → "💡 Idea Lab" tile.

Evidence

The Quick Action card href is /idea-lab. SPA returns 404 ("Oops! Lost in cyberspace"). The app's actual route is /ideas (no hyphen), which loads correctly with the Idea Lab UI. Even though the user is still authenticated, the 404 page renders the public marketing navbar.

Fix

Update Quick Action href from /idea-lab to /ideas. (Or add a redirect, or rename the route consistently.)

H5Footer has 10 dead href="#" links — including Privacy / Terms / Cookie / Security
🔴 High

Where: / footer (5 social icons + Careers + 4 Legal).

Evidence

Browser console: Array.from(document.querySelectorAll('a[href="#"]')).length === 10. The links:

Fix

Build real pages for Privacy / Terms / Cookie / Security (CV-cert platform = legal exposure). Replace social href="#" with real profile URLs or remove the icons. Careers page can be deferred but should at minimum say "We're not hiring" rather than #.

M1/robots.txt and /sitemap.xml return SPA HTML, not real files
🟡 Medium

Evidence: curl /robots.txt → 200 with HTML containing <title>Skill Portal — AI-Powered Skill Assessment for Pakistan</title>. Same for /sitemap.xml. SEO crawlers won't know what to crawl or block.

Fix

Add real static files. For SPA with auth-required routes, allow everything in robots.txt but only list public marketing pages in sitemap.xml:

# /robots.txt User-agent: * Allow: / Disallow: /api/ Disallow: /dashboard Disallow: /skills/* # (or Allow /skills/ for SEO) Sitemap: https://skill.production1.jugaar.ai/sitemap.xml
M2Score inconsistency on test results: 67% (circle) vs 66.67% (stat card)
🟡 Medium

Evidence: I scored 4/6 on test #76. The big circle indicator shows "67 %" while the bottom stat card shows "66.67%". Two visualizations, two values for the same number.

Fix

Pick one rounding rule and apply it everywhere. Recommend: Math.round(score) on circle, full precision on stat card (the inconsistency is the bug, not the precision).

M3Idea Lab session detail page renders an empty <h1></h1>
🟡 Medium

Evidence: After clicking "Start Session" with a description, the URL is /ideas (or session id) and the page renders <h1></h1> empty. The AI message + question render below, but the heading is missing.

Fix

Add a session title H1 (e.g., "Session started — [type]" or the user's idea description as title).

M4Registration form has no Terms checkbox / consent
🟡 Medium

Evidence: /register has 3 fields (Name, Email, Password) and a single "Create Account" button. No "I agree to Terms" checkbox. Combined with H5 (Terms = href="#"), the legal gap is real.

Fix

Add a checkbox + visible Terms/Privacy links.

M5Idea Lab session lacks loading indicator beyond static "Calculating your results"
🟡 Medium

Evidence: On test submission, the page shows plain text "Calculating your results…" with no spinner or progress bar. Same pattern on Idea Lab AI chat — long pause before first AI message.

Fix

Add Skeleton/spinner + cancel + 30s timeout with retry CTA.

M6Footer "© 2026 Skill Portal" hardcoded — but the year will silently drift
🟡 Medium

Evidence: © 2026 Skill Portal. Made within Pakistan. at bottom of every page. Hardcoded string.

Fix

Use new Date().getFullYear() in the rendered output. Tiny fix but it's the kind of polish Pakistan-focused sites need.

L1"Time" stat on results page shows em-dash "—" with no explanation
🟢 Low

The Time stat renders "—" because nothing was tracked for this run. Cosmetic — but inconsistent with the three other real stat cards above it.

L2Landing categories grid shows only 6 of 35 categories
🟢 Low

Hero "Pick a category, start testing" section shows: Programming & Dev, Cyber Security, IT Fundamentals, Certification Prep, Business & Ent., Financial Literacy. The "Browse all 16" CTA itself says 16, not 35 (related to H1).

L3Many category descriptions use generic boilerplate "Comprehensive skills in [category]"
🟢 Low

Some categories (Executive Leadership, Kids & Teens, Cyber Safety) have rich custom descriptions; most don't. Either standardize all or remove them — the inconsistency is more jarring than uniformity.

✅ What's Working Well

FeatureStatus
Registration → Dashboard flow✅ Instant, no friction. Lands on /dashboard with name greeting.
Adaptive testing✅ Real Level 1 → Level 2 jump observed (Q5 correct → Q6 at L2). Multi-select, true/false, scenario, and short-answer question types all present.
AI gap analysis✅ Rich Pakistan-specific narrative on results page — names JazzCash, Karachi, Rawalpindi, Lahore contexts. Strengths + areas to improve + 4 recommendations.
AI Idea Lab✅ Multi-step session. Type picker (App/Business/Career/Other) + description → real AI conversation starts. Pakistan-context responses confirmed.
Certificate flow✅ Empty state works (I scored 67% so didn't pass 70%; correct gating). Search box + filter rendered.
Test question content✅ Real PK scenarios: "Ahmed runs a small electronics shop in Saddar, Karachi", "Maria works as a clerk at a JazzCash agent outlet in Rawalpindi", "Ayesha is using a company-issued laptop for her daily data entry tasks at a telecom office in Karachi".
PWA manifest✅ Properly configured at /manifest.webmanifest with 3 shortcuts (Dashboard, Browse Skills, My Certificates), 192 + 512 icons, standalone display.

🔍 Tech Stack Fingerprint (evidence)

LayerObservation
Web serverServer: nginx/1.24.0 (Ubuntu)
FrontendVite-bundled React SPA, single chunk assets/index-D4-rUwho.js (193 KB) + assets/index-rrDTAZw9.css (120 KB)
HTML rootContent-Length: 3264, Cache-Control: no-cache, no-store, must-revalidate (good for SPA — index never cached)
RoutingPushState SPA — ALL paths return 200 with same HTML. Nginx try_files masks 404s. Only the SPA knows the route exists.
AuthBearer token in localStorage.token
Backend API/api/categories returns rich nested JSON with skills per category. No evidence of /api/health endpoint.
CSS frameworkTailwind (visible from grid classes grid grid-cols-2 sm:grid-cols-4, flex-shrink-0)
PWAManifest served correctly with 3 shortcuts. Icons 192 + 512 present.

🛡️ Security Audit

CheckResultSeverity
HSTS❌ Missing🔴 High
CSP❌ Missing🔴 High
X-Frame-Options / frame-ancestors❌ Missing — clickjacking unprotected🔴 High
X-Content-Type-Options❌ Missing🟡 Medium
Referrer-Policy❌ Missing🟡 Medium
Permissions-Policy❌ Missing🟢 Low
SRI on Google Fonts CSS❌ Missing (load via plain <link>)🟢 Low
/.well-known/security.txt403 returned (no security.txt present)🟢 Low
Index HTML cache✅ Properly no-cache, no-store, must-revalidate✅ Good

📋 Pages & Flows Tested

PageStatusNotes
/ (landing)Hero, 3-step "How it works", 6 category cards, 3 testimonials, CTA, footer. Renders fully.
/register3 fields → instant dashboard. Aggressively minimal — see M4 (no terms).
/dashboardGreeting, 4 KPI cards (all 0 for new user), recommended skills, Quick Actions. See H4 (Idea Lab link).
/skills⚠️Filter shows 34 chips + "All 492", grid shows "Showing 492 skills". H1 says "186 skills" — see H1.
/skills/{slug}Curriculum modules, sample Qs, pass mark 70%, retakes, certificate mention. Loaded "Computer Fundamentals" successfully.
/skills/python-assessment"Skill not found" — see H3.
/test/76Adaptive testing — 6 questions, L1→L2 jump on Q5→Q6, real PK scenarios, timer running.
/test/76 (results)Score 4/6=66.67% (67% in circle), AI Gap Analysis narrative, 3 Strengths, 2 Areas-to-Improve, 4 Recommendations, per-Q review.
/ideasIdea Lab: type picker, description, AI chat starts. Session detail H1 empty — see M3.
/idea-lab404 — see H4.
/certificatesEmpty state works correctly (gated by 70% pass). Search + filter rendered.
/categories⚠️Renders 35 cards but H1 says 16 — see H1.
/robots.txtReturns SPA HTML — see M1.
/sitemap.xmlReturns SPA HTML — see M1.

🎯 Claims vs Reality Table

SourceClaimReality
Meta description16 categories, 166 skills35 categories, 492 skills
Landing hero stat"492+" skills, "7,000+" questions, "16" categories"16" wrong, 492/35 are right
Landing categories pill"35 categories · 492 skills"✅ matches reality
Landing category cardsOnly 6 of 35 shownOK if intentional, but undersells
/categories H1"Explore 16 skill categories"❌ actual = 35
/skills H1"186 skills to master"❌ actual = 492
/skills filter chips34 categories + "All 492"✅ correct
Dashboard Quick Actions"Explore 186 skills across 16 categories"❌ both wrong (real: 492 / 35)
OG image/og-image.png returns 200✅ exists (not verified to be a valid PNG)

🔁 Suggested 5-Minute Re-QA Recipe (after fixes)

# 1. Verify counts curl -s https://skill.production1.jugaar.ai/api/categories | python3 -c "import sys,json; print(len(json.load(sys.stdin)['data']))" # should be 35 curl -s https://skill.production1.jugaar.ai/ | grep -oE '16 categories|166 skills|186 skills' # should be empty after fix # 2. Verify security headers curl -sI https://skill.production1.jugaar.ai/ | grep -iE 'strict-transport|content-security|x-frame|x-content|referrer-policy' # should NOT be empty # 3. Verify dead links gone curl -s https://skill.production1.jugaar.ai/ | grep -oE 'href="#"' | wc -l # currently 10, target 0 (except in-page anchors) # 4. Verify broken routes fixed curl -sIL https://skill.production1.jugaar.ai/skills/python-assessment | head -1 # should NOT 404 # 5. Verify robots/sitemap curl -s https://skill.production1.jugaar.ai/robots.txt | head -5 # should look like a robots.txt curl -s https://skill.production1.jugaar.ai/sitemap.xml | head -5 # should look like XML