The PHP version is a Laravel server-rendered app at sitfd-php.production1.jugaar.ai. It uses session cookies (XSRF-TOKEN + laravel-session) with one-click demo login via ?login=email in URL. Some bugs are fixed vs. the React version (no trailing 0 in references, working reports, server-side rendered so deep links work). But other bugs are introduced or remain (broken dashboard widget, 500 error on Staff role, no CSP, slow AI).
Status: 2 critical, 4 high, 6 medium, 4 low bugs found. The app is functional but the dashboard is broken, three roles can't see their home page, and AI features are slow with empty results.
| Feature | PHP (Laravel) | React (Vite SPA) |
|---|---|---|
| Login flow | Server form → 302 | AJAX + localStorage token |
| Reference display (table) | LAB-2026-000013 (no padding) | LAB-2026-0000130 (WITH padding bug) |
| Dashboard data | Broken (zeros) | Working (8 new, 21 acknowledged) |
| Reports | 7 types working, real data | 404 on most types |
| Public /track | Works for both refs | Works only for unpadded |
| Staff dashboard | 500 "User does not have the right roles" | Loads |
| IT-Verify / Approver dashboard | 500 same error | Loads |
| Admin dashboard load | 0.5s when no Nigraan in cache; 15s when first time | 0.5s |
| Ticket detail | Includes History section | No history section |
| AI features (Nigraan) | 0.4-26s, return advisory in flash | 0.4-26s, advisory inline |
| Detail URL pattern | /ticket/{ref} | /tickets/{id} — different! |
| Auth deep-link | Works (session-based) | BROKEN (Stale user state) |
| Security headers | No CSP, no HSTS | Strict CSP, X-Frame, X-Content |
| Help page KB search | 200 with search input | No search |
| One-click login | ?login=email in URL | /tickets?login=email |
| CSRF | 419 on stale token (manual refresh needed) | Bearer token (no CSRF) |
Every dashboard renders all-zero stat tiles, but the data exists in the system. Reports page shows 22 rows of real ticket data. The dashboard widget is calling a query that returns 0 (or the wrong table/column).
Reproduced:
Visual evidence: POC sees "0 new (unacknowledged)", "0 awaiting assignment", "0 pending registrations", "0 info requested", "0 incoming 30d", "0 portal", "0 phone" — even though the reports page shows real tickets across all those categories.
Effect: Every authenticated user opens their dashboard and sees a flat "0" page. They have no idea what their workload is. They have to navigate to /tickets or /reports to find anything. This is the most-viewed page of the entire app and it's broken.
Fix: Inspect the dashboard Blade view / DashboardController. The KPI query is returning empty. Likely the scope is wrong (e.g., filtering by user_id when it should be by department_id, or querying a model that doesn't have data seeded).
Three roles cannot view their dashboard at all. They get a red error page that says "Something went wrong — User does not have the right roles."
Reproduced fresh login each:
POC, DG Labor, Sec Labor, Admin, Master, Contact all get 200 OK on /dashboard.
Effect: Labor Staff Officers, IT Verification Officers, and Ministry Approvers have no home page. They must navigate to /tickets directly to see their work. This is a broken first impression for 3 of the 9 roles.
Fix: The dashboard controller probably has a role check that excludes dept_staff, it_verification, and ministry_approver roles. Either add support for these roles in the dashboard query, or render a different dashboard layout for them (e.g., for dept_staff, show their assigned tickets; for it_verification, show pending verification queue).
The React version displayed ticket references with a trailing 0 (e.g., LAB-2026-0000130) which broke the public Track feature. The PHP version fixes this.
Verified: Both LAB-2026-000013 AND LAB-2026-0000130 return 200 on the PHP version. The display layer shows the unpadded version LAB-2026-000013, and the URL accepts both. The React version should be patched to match.
Reproduced: First GET /dashboard as admin@sitfd.local takes 14.96s. Subsequent loads are 0.5s. The 15s first load is from the Nigraan AI generation for the dashboard insight text.
Effect: Every time the cache expires, anyone opening the admin dashboard waits 15s. The browser thinks the page is broken. Should be cached more aggressively or rendered asynchronously.
Fix: Cache the Nigel insight for 30+ minutes. Show "Loading..." skeleton while it runs in the background. Or render the page without the insight and stream the insight in via AJAX after page load.
Findings: Tested 4 URLs (/, /login, /dashboard, /tickets) — none have Content-Security-Policy or Strict-Transport-Security headers.
Present: X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin. These are good but incomplete.
Missing: CSP, HSTS, Permissions-Policy, X-XSS-Protection (legacy).
Effect: Without CSP, an XSS bug in any of the user-input fields (Ticket subject, message, etc.) could become much more dangerous. Without HSTS, downgrade attacks are possible.
Fix: Add to nginx config or middleware: Strict-Transport-Security: max-age=31536000; includeSubDomains and Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'
Reproduced: As Master (company), POST to /ticket/LAB-2026-000004/action returns 419 "Page Expired" instead of 403. The CSRF token from the cached page is stale.
Effect: A user opens a ticket detail page, walks away for 30+ minutes, comes back, clicks an action button → 419 error. They have to refresh the page first. This is a common UX failure pattern in Laravel apps.
Fix: Render the action form with a fresh CSRF token on every page load. Or invalidate the session-based CSRF token more aggressively and validate the form token against the user's session token, not the global app token.
AI features tested as POC against ticket LAB-2026-000013:
| Feature | Time | Result |
|---|---|---|
| brief | 0.4s | Returns full structured output (Reference, status, what was complained, what the department tried) |
| tone | 21.5s | Returns 【Reply tone — AI-generated, advisory】 with NO content |
| urgency | 0.4s | Returns 302 — no advisory content displayed on ticket page |
| similar | 0.4s | Returns 302 — content shown after redirect |
| full-analysis | 24.7s | Returns 302 — slow but works |
| suggested-reply | 0.5s | Returns 302 |
Issues:
Ticket LAB-2026-000004 is from "Larkana Data Labs" (a company from Larkana) but the City column shows "Sukkur".
Effect: Geography is wrong. A Sukkur-based officer might be assigned the ticket because the city matches their location, but the actual company is in Larkana. This affects routing and SLA assignment.
After clicking a Nigraan AI button (e.g., "brief"), the page redirects to /ticket/{ref} but the AI output is not displayed. Only the brief feature showed output (because it returned structured markdown). Tone, urgency, similar, suggested-reply all returned 302 but showed no content after the redirect.
Effect: User clicks "Run tone check" → waits 21s → gets blank page → assumes AI is broken.
Fix: The AI result should be stored in session flash OR passed as a query parameter to the redirect target. Then the redirected page renders the advisory in a blue box at the top.
The notification bell icon in the topbar shows a badge "2" but only on certain pages. No count consistency.
The "Propose resolution (evidence required below)" button assumes the user knows the form interaction. There's no inline hint of what needs to be filled in. A first-time user clicks it and gets a 422 error.
The SLA field shows "24 Aug 13:00" — a static date. No countdown. No "X hours remaining" visual. A user can't tell at a glance whether a ticket is approaching breach.
On the ticket list, the "Status" column shows plain text like "new", "in progress", "closed". For comparison, the ticket detail page shows colored pills (blue for "in progress", slate for "urgent", etc.). The list view should match.
Only filters: status, type. No "My Queue", "Unassigned", "Watched" filters. A DG Labor with 19 tickets still has to scan them all to find which ones need their action.
Evidence list shows "corrective-action.txt resolution 1 KB" but no download link. Clicking the text doesn't open the file. Compare to the React version which has a "Download" button.
The /transparency page shows aggregate stats but no per-company filter. A company cannot see "how did SITFD handle my industry?"
?login=email in URL. 65 demo accounts exposed on /login as clickable list.LAB-2026-000013, not LAB-2026-0000130.poc@sitfd.local (password: Demo@2026!)labor.staff@sitfd.localdg.labor@sitfd.localsec.labor@sitfd.localitverify@sitfd.localapprover@sitfd.localadmin@sitfd.localmaster@demotech.pkcontact@demotech.pk/tmp/test_php.py + /tmp/test_php.sh/tmp/php_qa_out.log/tmp/ai_*.html, /tmp/dash_*.html/root/.hermes/browser_screenshots/The PHP version is functional for basic ticket workflows and supports deep links. But the dashboard is broken — every user opens their home page and sees zeros. Three roles can't see their home page at all (500 error). The Nigraan AI is slow, sometimes returns empty, and the result is lost after redirect.
Critical priorities: (1) Fix the dashboard widget query. (2) Add Staff/IT-Verify/Approver dashboard. (3) Add CSP and HSTS. (4) Cache Nigraan insight. (5) Display AI advisory on the redirected page.
Comparison with React version: PHP wins on deep-links, history, reports, reference display. React wins on dashboard data, AI display, no CSRF. Neither is production-ready.