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.
Reproduced live in browser:
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.
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.
Reproduced:
LAB-SE-2026-000004 (no trailing 0)LAB-SE-2026-0000040 (with trailing 0)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.
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')}.
On ticket detail (LAB-2026-000004, in_progress):
Request info → disabledPropose resolution → disabledUpload (client/resolution/internal) → disabledPost note → disabledNo 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.
{"message":""} from many action endpointsSame bug as Pass 3 #P3-2. Still not fixed. Reproduced in this pass:
| Role | Action | Response |
|---|---|---|
| Master (company) | acknowledge | 403 {"message":"You do not have permission for this action."} |
| Master | escalate | 403 {"message":"Manual escalation is reserved for the Facilitation Desk, department admins, and..."} (truncated) |
| Master | assign | 403 {"message":"Only the Desk or your own department leadership can assign."} |
| Master | start / request-info / resolve | 403 {"message":"Not your department."} |
| Master | watch / confirm / verify / reopen / supply-info | 403 {"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.
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.
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.
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).
Same as Pass 4 finding. The list view is a flat archive. There's no:
created_at shown)ai_urgent/ai_risk fields but never surfaced)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.
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.
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).
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.
Verified via API testing. The SPA-captured payload shapes don't match the backend validation:
| Endpoint | Bundle field | Server field | Error |
|---|---|---|---|
| /me/nigraan/ask | q | question | 422 "The question field is required" |
| /nigraan/tone-check | text | message | 422 "The message field is required" |
| /nigraan/filing-assist | text | type | 422 "The type field is required" |
| /tickets/{id}/nigraan/reply-review | reply | message | 422 "The message field is required" |
| /me/nigraan/digest | POST | GET only | 405 "POST method not supported" |
| /tickets/{id}/nigraan/brief | POST | GET only | 405 |
| /tickets/{id}/nigraan/similar | POST | GET only | 405 |
| /tickets/{id}/nigraan/full-analysis | POST | GET only | 405 |
| /tickets/{id}/nigraan/suggested-reply | POST | GET only | 405 |
| /tickets/{id}/nigraan/decision-memo | POST | GET only | 405 |
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.
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.
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.
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.
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.
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.
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.
/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.
mark_duplicate → duplicate and other snake_case → kebab-case.script-src 'self'). No unsafe-inline or unsafe-eval.{id, email, label}).Tested 45 endpoints across 9 roles. Cells colored by status. Detailed per-endpoint rollup follows.
| Role | 200 OK | 4xx | 5xx | Total | %OK |
|---|---|---|---|---|---|
| POC | 25 | 19 | 1 | 45 | 55% |
| Labor Staff | 19 | 25 | 1 | 45 | 42% |
| DG Labor | 21 | 23 | 1 | 45 | 46% |
| Sec Labor | 21 | 23 | 1 | 45 | 46% |
| IT Verify | 19 | 25 | 1 | 45 | 42% |
| Approver | 19 | 25 | 1 | 45 | 42% |
| Admin | 29 | 15 | 1 | 45 | 64% |
| Master (company) | 20 | 24 | 1 | 45 | 44% |
| Contact (company) | 18 | 26 | 1 | 45 | 40% |
Common 4xx patterns:
children:e.reference is being padded by the renderer.onClick with <Link to="/..."> or navigate().{text: ...} but server expects {message: ...}. Codegen or share constants.LAB-SE-2026-000004, UI renders exactly that. No trailing 0, no padding.poc@sitfd.locallabor.staff@sitfd.localdg.labor@sitfd.localsec.labor@sitfd.localitverify@sitfd.localapprover@sitfd.localadmin@sitfd.localmaster@demotech.pkcontact@demotech.pk/tmp/sitfd_qa_pass5b.py (10K, executable)/tmp/qa_pass5.log (53K)/tmp/sitfd_spa.js (1.5M)/root/.hermes/browser_screenshots/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.