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.
⚠️ 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.
Where: Meta description, OG tags, landing hero stats, landing "Browse all 16" link, dashboard widget, Quick Actions card, /categories H1, /skills H1.
"AI-Powered Skill Assessment for Pakistan's Workforce. 16 categories, 166 skills...""16 categories, 166 skills, Pakistan-focused."/api/categories returns 35 entries (ids 1-35)."Explore 186 skills across 16 categories"./categories H1: "Explore 16 skill categories" — but the page subline correctly says "35 categories · 492 skills" and 35 cards render./skills H1: "186 skills to master" — but filter chip shows "All 492" and grid below shows "Showing 492 skills".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.
Where: All endpoints.
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.
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.
Where: Landing hero "demo card" right next to the sign-up CTAs.
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.
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.
/idea-lab (404) — real route is /ideasWhere: /dashboard → Quick Actions → "💡 Idea Lab" tile.
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.
Update Quick Action href from /idea-lab to /ideas. (Or add a redirect, or rename the route consistently.)
href="#" links — including Privacy / Terms / Cookie / SecurityWhere: / footer (5 social icons + Careers + 4 Legal).
Browser console: Array.from(document.querySelectorAll('a[href="#"]')).length === 10. The links:
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 #.
/robots.txt and /sitemap.xml return SPA HTML, not real filesEvidence: 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.
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
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.
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).
<h1></h1>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.
Add a session title H1 (e.g., "Session started — [type]" or the user's idea description as title).
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.
Add a checkbox + visible Terms/Privacy links.
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.
Add Skeleton/spinner + cancel + 30s timeout with retry CTA.
Evidence: © 2026 Skill Portal. Made within Pakistan. at bottom of every page. Hardcoded string.
Use new Date().getFullYear() in the rendered output. Tiny fix but it's the kind of polish Pakistan-focused sites need.
The Time stat renders "—" because nothing was tracked for this run. Cosmetic — but inconsistent with the three other real stat cards above it.
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).
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.
| Feature | Status |
|---|---|
| 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. |
| Layer | Observation |
|---|---|
| Web server | Server: nginx/1.24.0 (Ubuntu) |
| Frontend | Vite-bundled React SPA, single chunk assets/index-D4-rUwho.js (193 KB) + assets/index-rrDTAZw9.css (120 KB) |
| HTML root | Content-Length: 3264, Cache-Control: no-cache, no-store, must-revalidate (good for SPA — index never cached) |
| Routing | PushState SPA — ALL paths return 200 with same HTML. Nginx try_files masks 404s. Only the SPA knows the route exists. |
| Auth | Bearer token in localStorage.token |
| Backend API | /api/categories returns rich nested JSON with skills per category. No evidence of /api/health endpoint. |
| CSS framework | Tailwind (visible from grid classes grid grid-cols-2 sm:grid-cols-4, flex-shrink-0) |
| PWA | Manifest served correctly with 3 shortcuts. Icons 192 + 512 present. |
| Check | Result | Severity |
|---|---|---|
| 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.txt | 403 returned (no security.txt present) | 🟢 Low |
| Index HTML cache | ✅ Properly no-cache, no-store, must-revalidate | ✅ Good |
| Page | Status | Notes |
|---|---|---|
/ (landing) | ✅ | Hero, 3-step "How it works", 6 category cards, 3 testimonials, CTA, footer. Renders fully. |
/register | ✅ | 3 fields → instant dashboard. Aggressively minimal — see M4 (no terms). |
/dashboard | ✅ | Greeting, 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/76 | ✅ | Adaptive 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. |
/ideas | ✅ | Idea Lab: type picker, description, AI chat starts. Session detail H1 empty — see M3. |
/idea-lab | ❌ | 404 — see H4. |
/certificates | ✅ | Empty state works correctly (gated by 70% pass). Search + filter rendered. |
/categories | ⚠️ | Renders 35 cards but H1 says 16 — see H1. |
/robots.txt | ❌ | Returns SPA HTML — see M1. |
/sitemap.xml | ❌ | Returns SPA HTML — see M1. |
| Source | Claim | Reality |
|---|---|---|
| Meta description | 16 categories, 166 skills | 35 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 cards | Only 6 of 35 shown | OK if intentional, but undersells |
| /categories H1 | "Explore 16 skill categories" | ❌ actual = 35 |
| /skills H1 | "186 skills to master" | ❌ actual = 492 |
| /skills filter chips | 34 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) |
# 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