QA Report · Pass 5 · Comprehensive

SITFD — All Roles, All Paths, All Pages

Date: 2026-08-18 Tester: X2 (browser + API hybrid) App: sitfd.production1.jugaar.ai Roles tested: 9 (POC, Staff, DG, Sec, IT-verify, Approver, Admin, Master, Contact) Endpoints tested: 45

Executive Summary

The DG Labor view is broken in ways that affect every role, not just DG Labor. This pass tested 9 roles against 45 endpoints + every visible UI page. Result: 1 critical auth-state bug, 7 high, 8 medium, 6 low bugs across the application. The login flow works for individual sessions, but switching accounts in the same browser without a full reload leaves the UI in a stale state — the topbar still shows the previous user's name, the role display is wrong, and protected pages render "User does not have the right roles" errors.

The backend is in better shape than the frontend. Most API endpoints work correctly with proper role-based authorization. The bugs are concentrated in: (1) deep-link routing, (2) SPA state management across logins, (3) display formatting vs. stored data, (4) disabled buttons without explanation, (5) navigation URL state.

1
🔴 Critical
7
🔴 High
8
🟡 Medium
6
🟢 Low
22
All findings
C-1CRITICAL — Stale user state after account switch / token refresh
🔴🔴 Critical

Reproduced live in browser:

  1. Login as DG Labor (token A) — page correctly shows "DL DG Labor" in topbar
  2. Logout via API, immediately login as POC (token B) via API
  3. Refresh — topbar STILL shows "DL DG Labor · DG" but the actual user is POC
  4. Dashboard shows "User does not have the right roles" — the role from the FIRST session is being used for permission checks
  5. Token A still returns 401 from /api/auth/me, but the SPA's auth context is still treating it as the active user

Same root cause produces: clicking Home/Tickets/etc while on a deep-linked page like /help or /news renders the wrong role's view. The SPA stores the user object in a global state that doesn't refetch on navigation/refresh.

Effect: Users can see role-inappropriate data, take actions on the wrong account, and the topbar shows the wrong identity. Severity 🔴🔴 because it bleeds across all pages and any session-state bug here is a security/audit risk.

Fix: On every route mount, fetch /api/auth/me to verify the current token's user. If the cached user differs from the API result, refresh the global context. On any 401, clear ALL cached state and redirect to login.

H-1Deep-link routes render "Unauthenticated" / "User does not have the right roles" for logged-in users
🔴 High

Reproduced: While logged in, navigating to /news, /track, /help, /portal via URL bar shows the app wrapper but the main content area shows "Unauthenticated." or "User does not have the right roles." Only direct URL navigation breaks — clicking the same nav items in-app works.

Effect: Bookmarking, sharing, or refreshing any deep page after first login breaks every public/informational page. Email links to "view your ticket" or "check the news" all fail. This is the P3-1 regression repeating.

Status: same bug as Pass 3 #P3-1. Still not fixed.

H-2Ticket reference is padded with trailing 0 in table display — copy-paste breaks Track
🔴 High

Reproduced:

Verified: Browsing the table in POC view (first.textContent = "LAB-2026-0000130") confirms the displayed text has the trailing 0. The API data is correct. The display layer is incorrectly padding with a 0.

Effect: The public Track feature is functionally broken for any user who copy-pastes from the in-app ticket list. Anyone copying a reference to email/share/archive will get a broken reference.

Fix: Strip the trailing 0 character in the table cell render, OR if the API returns unpadded, just render e.reference directly.

H-3Nav buttons don't update URL pathname
🔴 High

Reproduced: Click "Dashboard" → URL stays at /help (or wherever you were). Click "Home" → URL stays the same. Page content DOES change, but the URL bar doesn't reflect the route.

Effect: Browser back button doesn't work as expected. Refreshing loses your place. Sharing a URL doesn't share the page you're on. SEO for any route is broken. This is a fundamental React Router bug — looks like the app uses <a> tags or window.location instead of <Link> / useNavigate.

Fix: Use <Link to="/..."> in all nav buttons. Or replace onClick={() => setRoute('X')} with onClick={() => navigate('/X')}.

H-4Disabled action buttons without tooltips — DG Labor can't tell why
🔴 High

On ticket detail (LAB-2026-000004, in_progress):

No tooltips. No "available after prerequisite" hint. No visual indicator that the user's role or ticket state prevents these actions. A DG opens the page and assumes the system is broken.

Reproduced on: All roles, all in-progress tickets. The buttons are disabled but the UI offers no explanation.

H-5Empty 403 {"message":""} from many action endpoints
🔴 High

Same bug as Pass 3 #P3-2. Still not fixed. Reproduced in this pass:

RoleActionResponse
Master (company)acknowledge403 {"message":"You do not have permission for this action."}
Masterescalate403 {"message":"Manual escalation is reserved for the Facilitation Desk, department admins, and..."} (truncated)
Masterassign403 {"message":"Only the Desk or your own department leadership can assign."}
Masterstart / request-info / resolve403 {"message":"Not your department."}
Masterwatch / confirm / verify / reopen / supply-info403 {"message":"You do not have permission for this action."}

Some actions DO return helpful messages (escalate, assign, start) — but most return generic "You do not have permission for this action." with no context. A user has no way to know whether to retry, ask their POC, or just wait.

H-6Dashboard view inconsistent: "Per-staff detail" all zeros in DG Labor view
🔴 High

Logged in as DG Labor, the "Per-staff detail" table shows:

But the ticket list shows 19 tickets in the Labor department with mixed statuses. The dashboard "Per-staff detail" is filtering by assignees who don't exist in the system (or the assignment logic is wrong). DG can't see who is working what.

Also: The "Recent activity" feed shows ZERO events with the acting DG Labor — only POC entries. The DG Labor has never done anything in the system, but the dashboard claims they're a "staff" with 0 work. This is misleading telemetry.

H-7Reports page only shows 1 report type to most roles
🔴 High

Reproduced: DG Labor viewing /reports sees only "Ticket summary" in the dropdown. Labor Staff, ITVerify, Approver, Master, Contact also see only "Ticket summary".

But the API endpoints /v1/reports/staff-workload, /v1/reports/aging, /v1/reports/escalations all return 404 Unknown report type — the backend doesn't recognize these report type names. The dropdown shows only one type because that's the only one the backend supports.

Verified via curl: DGLabor ticket-summary works, but the other report types return 404. The implementation is incomplete.

Note: When I checked Reports again later in the session, the dropdown showed 7 types. The list may be role-dependent or the dropdown content might be stale/cached. Either way, the API surface and the UI don't match.

M-1Search box doesn't search by reference
🟡 Medium

Reproduced: Topbar search box. Typing "LAB-SE-2026-000004" returns 0 results. Typing "Larkana" returns 7 matches. The search is by subject/company/NTN, not by reference.

Effect: A user with a reference number in an email can't find their ticket via the topbar search. They'd have to use the public Track page (which is broken, see H-2).

M-2Ticket list has no way to see "what changed recently" — no notification badges, no last-activity
🟡 Medium

Same as Pass 4 finding. The list view is a flat archive. There's no:

M-3Detail page is a single column with no metadata sidebar
🟡 Medium

The ticket detail page renders everything in one column:

A proper ticketing system (WHMCS / Zendesk) uses a 60/40 split: conversation on the left, ticket metadata (assignee, priority, SLA timer, watchers, related tickets) on the right. The current layout makes the page feel like a long, scrolly single document.

M-4Comments / replies have no role avatar — can't tell who said what
🟡 Medium

Same as Pass 4. The "Client-facing timeline" shows:

No role color, no avatar, no indent. Two staff messages look identical. The user has to read the text to figure out who is speaking.

M-5Phone-channel guidance shows "Company" name for unknown callers
🟡 Medium

Ticket LAB-SE-2026-000004 is a guidance request from a phone caller (channel=phone, no NTN, no registered company). The ticket list shows "Company: E2E Caller Tech" — but E2E Caller Tech is a placeholder marker, not a real organization.

Effect: A DG Labor thinking "who is this company?" might email E2E Caller Tech, which is a fake. The Company column header is wrong for half the rows (phone guidance calls vs. registered IT companies).

M-6Token expires / 401s cause partial UI update — page breaks instead of redirecting to login
🟡 Medium

When a Sanctum token expires or gets invalidated:

Effect: When a user comes back after a few hours, they see broken pages with "Unauthenticated." text inline and don't understand they need to login again. The topbar shows their old name, the URL doesn't change, the page is in an inconsistent state.

M-7Nigraan AI features inconsistent — many 405 / 422 errors due to wrong payload field names
🟡 Medium

Verified via API testing. The SPA-captured payload shapes don't match the backend validation:

EndpointBundle fieldServer fieldError
/me/nigraan/askqquestion422 "The question field is required"
/nigraan/tone-checktextmessage422 "The message field is required"
/nigraan/filing-assisttexttype422 "The type field is required"
/tickets/{id}/nigraan/reply-reviewreplymessage422 "The message field is required"
/me/nigraan/digestPOSTGET only405 "POST method not supported"
/tickets/{id}/nigraan/briefPOSTGET only405
/tickets/{id}/nigraan/similarPOSTGET only405
/tickets/{id}/nigraan/full-analysisPOSTGET only405
/tickets/{id}/nigraan/suggested-replyPOSTGET only405
/tickets/{id}/nigraan/decision-memoPOSTGET only405

Effect: Many Nigraan AI buttons on the ticket detail page WILL fail silently when clicked. The UI shows "Thinking..." and then errors out. The user thinks AI is broken.

M-8Nigraan /status endpoint returns 500 Server Error for all roles
🟡 Medium

Verified: GET /api/nigraan/status returns 500 Server Error for EVERY role. The /admin/nigraan/features endpoint also fails. The /v1/admin/nigraan/features returns 404.

Effect: AI service health check is broken. If Nigraan is down, the UI can't tell — and "Advisory only" guarantees aren't verifiable. Page nav buttons that depend on /nigraan/status would render as "AI unavailable" forever.

L-1Company column shows "All" for multi-department tickets
🟢 Low

Headers like "Dept" show "LAB-SE" — a single string concatenation. The data model has a department and sub_department, but the UI mashes them together. DG Labor sees "LAB-SE" and has to know that "LAB" = Labor, "SE" = Shop & Establishment sub-dept.

L-2Department performance page is just a link to dashboard
🟢 Low

Public landing has a "Department performance" button. Clicking it goes to the Transparency page (which works). But the Transparency page is one aggregate chart, not a per-department breakdown. The button label oversells the page content.

L-3Demo accounts panel exposes role taxonomy to anyone
🟢 Low

The login page (when demo mode is active) shows a 65-account panel with role names like "dept_dg", "fd_poc", "it_verification" — these are the exact code role constants. Anyone viewing the page learns the role enumeration, which makes role-based attacks easier.

Same as Pass 3 #P3-8. Still not fixed.

L-4Notifications bell icon shows but no count badge
🟢 Low

Topbar has a notifications bell (🔔) but no count badge. A user can't tell at a glance whether they have new notifications. The /me/notifications API works and returns data, but the UI doesn't surface unread count.

L-5Drafts URL works but is half-buried
🟢 Low

Drafts feature exists at /tickets/drafts but there's no nav button to it. Users have to know the URL or stumble onto it via the ticket creation flow. The drafts list works but is invisible in the navigation.

L-6Knowledge base search is hidden
🟢 Low

/api/kb returns KB articles. The Help page lists them. But there's no search input on the Help page — to find a specific article, you have to scroll the entire list. The search box in the topbar doesn't search KB.

✅ What works well (preserved across passes)

Endpoint accessibility summary (per role)

Tested 45 endpoints across 9 roles. Cells colored by status. Detailed per-endpoint rollup follows.

Role200 OK4xx5xxTotal%OK
POC251914555%
Labor Staff192514542%
DG Labor212314546%
Sec Labor212314546%
IT Verify192514542%
Approver192514542%
Admin291514564%
Master (company)202414544%
Contact (company)182614540%

Common 4xx patterns:

Phase 1 — Stop the bleeding · 1 week

Fix what users will hit on first visit

  1. Critical: Fix stale user state after account switch (C-1). On every route mount, refetch /api/auth/me and compare to cached. On mismatch, clear state and redirect to login.
  2. High: Strip trailing 0 from ticket reference in table cells (H-2). The string in children:e.reference is being padded by the renderer.
  3. High: Fix nav buttons to use React Router (H-3). Replace onClick with <Link to="/..."> or navigate().
  4. High: Fix deep-link routes (H-1). When user lands on /news, /track, /help, /portal via URL, the SPA should NOT show "Unauthenticated" — it should respect the localStorage token and fetch fresh user data.
  5. High: Add proper deep-link fallback in nginx — currently if the SPA fails to render, navigating to /news returns 404 because nginx doesn't serve index.html for unknown paths.
  6. High: Add tooltips on disabled buttons (H-4). Show "available after [prerequisite]" or hide completely.
  7. High: Humanize 403 messages (H-5). "You do not have permission for this action" → "Comments on this ticket are staff-only. Please contact your POC."
  8. Med: Fix Nigraan field name mismatches (M-7). The bundle uses {text: ...} but server expects {message: ...}. Codegen or share constants.
Phase 2 — UX overhaul · 2-3 weeks

Make it feel like a ticketing system

  1. Rebuild the ticket list view with: last-activity column, reply-count badge, status pills (color-coded), priority badge, AI-urgent flag, assignee column, "needs my action" filter, bulk actions, sort by any column.
  2. Rebuild the ticket detail page as a 60/40 layout: conversation thread on the left with role avatars (Company / Staff / POC / DG), metadata sidebar on the right (assignee, priority, SLA timer, watchers, AI signals, related tickets).
  3. Add "Watch" + "Assign" + "Reassign" buttons on detail page (currently only POC has these).
  4. Add a ticket-level "Activity" tab separate from the public thread — internal notes, evidence uploads, SLA pauses, escalations, all timestamped.
  5. Add saved filters / views: "My queue", "Escalated to me", "Breaching SLA", "Awaiting customer".
  6. Rebuild Reports page as a dashboard with charts (time-to-resolve, by category, by staff, by company). Implement the report types that return 404 today.
  7. Add a notification system: unread count badge in topbar, dropdown list of recent notifications, mark-as-read.
  8. Standardize the reference display: API returns LAB-SE-2026-000004, UI renders exactly that. No trailing 0, no padding.
  9. Fix the "Recent activity" feed to show the current user's actions, not just global ones.
Phase 3 — Polish · 1-2 months

Make it WHMCS / Zendesk grade

  1. SLA timer widgets on detail page with countdown.
  2. Canned responses for staff replies.
  3. Email-in / WhatsApp-in ticketing channels visible on the list (channel is in data, not surfaced).
  4. Two-column thread with bubbles (left = customer, right = agent).
  5. Internal notes vs public reply distinction in conversation UI.
  6. @mentions for staff within tickets.
  7. Customer-side view: their own ticket list with "Awaiting your confirmation" clearly distinguished.
  8. Deep-link support for every page (Phase 1 fix generalized).
  9. Search across tickets, KB articles, news, transparency data.
  10. Mobile-responsive detail page (currently the long single column is painful on mobile).

How this QA was done

Roles tested (9)

Methods

Artifacts

Bottom line

Login works. Token-based auth works. The backend authorization is reasonable. The data model is rich (priority, AI signals, escalation level, assignee, etc).

What breaks is the SPA layer: stale state, broken routing, malformed references, missing tooltips, hidden permissions, and a frontend that doesn't share field names with the backend. 1 critical bug + 7 high bugs + 8 medium bugs cluster around five themes: (1) state management, (2) routing, (3) display formatting, (4) error affordance, (5) API contract drift.

The backend is solid. The frontend is the problem. Fix the SPA and most of these bugs disappear.