# Team Leader (Web) — Per-Screen Spec

> Acceptance detail per screen (Given/When/Then where useful). Covers **Team Leader AND Trainee TL** — identical nav/screens; **Trainee TL = assist-only** (Approve/Send-back/Route disabled, CL-3 — amends `§9 #4`). RBAC: TL sees only **own Game Zone + own team** (the `reportsTo` chain ⊆ Priya) — enforced server-side, mirrored in the app.
> **Change List v1.1 applied:** `TL-CHK-VERIFY` Route-to-Maintenance + single-track Hold (CL-2); `TL-ROSTER-REASSIGN` mid-shift reassign (CL-4); `TL-CHK-UPLOAD` team-scoped builder (CL-1); Trainee-TL RBAC (CL-3).
> References: `FOUNDATION_SPEC.md` (§1 schema, §2 conventions, §3 auto-assign, §4 cascade — **TL = level 1, the first approver**, §5 scope), `DESIGN_SYSTEM.md` (§2.4 state colors, §6.4 buttons, §6.6 DataTable, §6.7 Modal/Drawer, §6.11 StatusBadge, §6.12 PhotoUpload viewer, §6.13 ChecklistItem, §6.14 ApprovalTimeline, §6.19 roster grid, §8.1 web shell), `PROJECT_PLAN §4a/§4b/§9`, `WORKFLOW_GUIDE Stage F1`.

---

## Global rules (apply to every screen)

- **Chrome:** navy **240px** sidebar (data-driven from `/me`, active item = orange left-edge bar + orange icon/label) + 64px sticky topbar (breadcrumb left; **branch badge** `Game Zone · Air Maniax Ahmedabad-1` + **team indicator** `My Team · 3` right; bell; profile chip "Priya Nair (TL)"). Content well `bg #F5F7FA`, max 1440, 40px top / 32px side padding. Page H1 + primary action top-right.
- **RBAC:** sidebar + permissions come from `GET /me` (`FOUNDATION_SPEC §2`). The app never hardcodes role logic. The **Approve / Send-back** affordances appear because the resolver grants `checklist.approve` (scope `own_team`) to TL — not because of any `if role==='TL'`.
- **Game-Zone + team scope:** every list is server-filtered to the TL's Game Zone AND own team (`gameZoneId = me.gameZoneId AND user.reportsToId chain ⊆ me`). An out-of-team / out-of-Game-Zone id → 403 (`states/forbidden.html`).
- **Offline:** any network failure shows the "No connection — retry" state; the **entered draft** (send-back reason text, fill G/A picks/notes/staged photos) is **held in memory** and the action re-POSTs on retry. No data silently lost on a momentary drop.
- **Status semantics (the TL is BOTH an approver and a viewer above the filler):** on the TL's own overview the TL sees the **real sub-state** up to and including TL-level (Submitted → awaiting **me** → TL Approved). Once TL approves, the instance reads **Pending** to SM/OH at their levels (derived, `§9 #5`); the TL still sees the real sub-state (SM Approved / Done) climbing above. Resolved by the shared `renderStatusForViewer()` (`FOUNDATION_SPEC §4`).
- **Approve = green, Send-back = red** (action color = outcome color, `§6.4`). Send-back **requires a reason** and always returns to the **original filler** (`§9 #3`); re-fill restarts the cascade **from TL** (`§9 #8`).
- **Trainee TL = assist-only (CL-3):** a Trainee Team Leader gets the **same nav + screens**, but the `/me` resolver returns `checklist.approve = allowed:false` and the route grant off → the **Approve / Send-back / Route-to-Maintenance** buttons render **disabled** (and `PATCH /roster-entries/:id/reassign` is rejected). Never branched on `if role==='TRAINEE_TL'` — the disabled state follows the permission flags. Documented variant: `states/trainee-tl-assist-only.html`.
- **Single-track hold (CL-2):** when a submitted checklist has **A-items**, each A-item is **routed to maintenance** (spawns a `WorkOrder`) and the instance enters **`HELD`** (`renderStatusForViewer` → "Action in progress / Held", `st-held` deep-orange `#C2410C`). The instance **cannot reach `TL_APPROVED`/Done until every WorkOrder closes** (`DONE`/`OUTSOURCED`); there is no parallel "approve while actions pending" track (`FOUNDATION_SPEC §4 / §4a`). HeldBanner = `DESIGN_SYSTEM §6.24`.

---

## `AUTH-LOGIN` — Login (`screens/01-login.html`)

- **Given** the app is launched logged-out, **When** Priya enters email/phone + password and submits, **Then** `POST /auth/login`, store access+refresh tokens, `GET /me`, route to `TL-DASH` (role resolved = TL).
- **Given** bad credentials, **Then** inline `danger` error ("Incorrect email or password"); password reveals on the eye icon.
- **Given** too many failed attempts, **Then** the **locked** state (`states/locked.html`) — countdown + "Too many attempts".
- Primary button = **navy-on-orange** "Sign In" (44px web). Maniax wordmark lockup (AIR orange / MANIAX white) on the navy brand band. "Forgot password?" in `brand-blue`. Web login is centered card on `bg`, not the full app shell.

## `TL-DASH` — Team Leader Dashboard (`screens/02-dashboard.html`)

- **Purpose:** team live status at a glance — what is **awaiting my approval**, how my monitors' checklists are progressing today, team roster gaps, overdue.
- **Given** Priya signs in, **Then** the dashboard shows KPI tiles (`KpiCard §6.10`): **Awaiting my approval · N** (amber, the verify queue size), **In progress · N**, **Done today · N**, **Overdue · N** (solid-red), **Roster gaps · N**. Plus two panels: a **Verify queue** preview (submitted checklists with filler + ride + `StatusBadge` "Submitted · awaiting TL", each row → `TL-CHK-VERIFY`) and a **Team progress** panel (each team member with a today checklist-progress bar).
- **Given** a checklist is past `dueAt` and unsubmitted, **Then** it carries a **solid-red Overdue** badge and counts into the Overdue KPI.
- **Given** nothing awaits approval, **Then** the verify-queue panel shows the all-clear empty state ("Nothing awaiting your approval"). Loading = skeleton cards + rows (`states/loading.html`), never a bare spinner.

## `TL-TEAM` — My Team (`screens/03-team.html`)

- **Purpose:** the roster of people reporting to this TL — Monitors + Cleaning Supervisor — with their live today-progress.
- **Given** the team list, **Then** a `DataTable §6.6`: Name · Role (SCM/MON/CS chip) · Certified rides · Today's shift · Checklist progress (mini bar: assigned / in-progress / submitted / done) · last active. Search + role filter. Each row → `TL-USER-DETAIL`.
- **Scope:** only direct + indirect reports in the `reportsTo` chain under Priya, own Game Zone. No create/edit user affordance (TL cannot onboard — OH/SM only). Empty state when the team is unstaffed.

## `TL-USER-DETAIL` — Team Member Detail (`screens/04-team-member-detail.html`)

- **Purpose:** one team member end-to-end (read-only for TL) — profile, certs, checklist history.
- **Given** Priya opens Ramesh, **Then** show his profile (name, role "Staff Court Monitor", Game Zone, reports-to "Priya Nair · Team Leader"), certified rides (Trampoline), and a checklist-history table (instance · ride · state · submitted · outcome) with cascade drilldown. Photos open the viewer modal. No edit affordance.

## `TL-CHK-OVERVIEW` — Team Checklist Status (`screens/05-checklist-overview.html`)

- **Purpose:** every checklist across the TL's team with live cascade status; the entry point to verify.
- **Given** the overview, **Then** a `DataTable`: Template · Ride · Shift · Filler · Frequency · **Cascade** (mini `ApprovalTimeline` showing Filled→TL→SM→OH) · **Status** (`StatusBadge` resolved for the TL viewer) · due. **Active / Overdue tabs** (`§6.16`). Filters: ride, shift, period, status. A **Submitted** row shows "awaiting TL" and a **Verify** action → `TL-CHK-VERIFY`.
- **Status rendering:** Submitted = "awaiting TL" (the TL's action is needed); after TL approves the row reads SM Approved / Done as it climbs (TL sees real sub-state above its level); a **Sent back** row is loud red. Loading skeleton; empty (no team checklists); error+retry; forbidden on out-of-team id.

## `TL-CHK-VERIFY` — TL Verify / Approve (`screens/06-checklist-verify.html`) — CRITICAL

- **Purpose:** the heart of the TL role — review a submitted checklist's items + photos and **Approve** (passes to SM) or **Send-back** (returns to the original filler with a reason).
- **Layout:** two-column — left: the checklist **read-only review** (sectioned `ChecklistItem` rows showing each item's G/A value + note + time + initials + filler; A items highlighted red-orange with their issue-photo thumbnail; completion photos in a strip). Right: a **decision rail** — filler card (Ramesh, Monitor, submitted 10:18), the **ApprovalTimeline** (`§6.14`) with the TL node ringed "awaiting you", and the two action buttons: **Approve** (green `success`, `§6.4`) + **Send back** (red `brand-red`).
- **Photo review:** every completion + A-item photo is a thumbnail; clicking opens the **photo-viewer modal** (`modals/photo-viewer.html`) — full-size, swipe/next, item-context caption, uploader + timestamp + device (`§4a`). Read-only (TL cannot edit responses).
- **Approve:**
  - **Given** Priya reviews and clicks **Approve**, **Then** a lightweight approve-confirm appears; on confirm `POST /checklist-instances/:id/approve` → state `TL_APPROVED`, `ApprovalLog` written, `ApprovalStep[1].APPROVE`, **SM (Rohit) notified** (FCM + in-app), the row leaves the TL verify queue, and the timeline node flips to `TL Approved ✓`.
- **Send-back (`§9 #3`):**
  - **Given** Priya clicks **Send back**, **Then** the **send-back-reason modal** (`modals/send-back-reason.html`) opens with a **required** reason textarea (submit disabled until non-empty). On confirm `POST /checklist-instances/:id/sendback` `{ reason }` → state `SENT_BACK`, log with reason, and the instance is returned to the **original filler (Ramesh)** — NOT the previous approver — surfaced on his `MON-CHK-STATUS` red banner + a `SENT_BACK` notification.
  - After Ramesh re-fills and re-submits, the cascade **restarts from TL** (`§9 #8`) — the instance re-enters Priya's verify queue fresh.
- **Route to Maintenance + single-track Hold (CL-2):**
  - **Given** the checklist has an **A-item** (`ChecklistResponse.value == A`), **When** Priya clicks **Route to Maintenance** on that A-item, **Then** the **route-to-maintenance modal** (`modals/route-to-maintenance.html`) opens (shows the A-item + photo, the maintenance target = the Game Zone's Maintenance TL, priority, optional note). On confirm `POST /work-orders { instanceId, responseId, ... }` → one `WorkOrder` (state `ROUTED`) per A-response, **Maintenance TL notified**, and the instance transitions to **`HELD`** (`SUBMITTED → HELD`).
  - **Given** the instance is `HELD`, **Then** the screen shows the **HeldBanner** (`§6.24`) "N actions open — checklist held until resolved" (`st-held` `#C2410C`), each A-item shows its WO chip (`wo-routed` light-blue once routed, `wo-open` gray until routed), and **Approve is disabled** ("Locked while held"). Send-back stays available (can return the whole instance to the filler).
  - **Given** every WorkOrder for the instance reaches a terminal state (`DONE` / `OUTSOURCED`, worked in `maintenance-tl-web` / `technician-mobile`), **Then** the hold releases (`HELD → TL_APPROVED`) and the cascade resumes to SM — the TL's verify view is no longer locked. The TL never assigns/closes WOs (route only).
  - **Trainee TL:** Route-to-Maintenance is **disabled** (assist-only, CL-3).
- **Scope + concurrency:** TL can only verify instances in its team + Game Zone; out-of-scope id → 403. Approve/sendback carry `If-Match: <version>` → 409 if a concurrent approver already acted (rare; mirrored from the contract).
- **No edit-of-responses:** the TL approves or rejects the filler's work; it never edits the filler's G/A or photos (that would defeat the evidence trail).

## `TL-CHK-FILL` — Fill Checklist (TL can fill) (`screens/07-checklist-fill.html`)

- **Purpose:** the TL may also tick/submit a checklist (per access matrix) — e.g. when covering a ride. Same fill contract as the anchor (`MON-CHK-FILL`), rendered in the web shell.
- **Layout:** sectioned `ChecklistItem` rows (`§6.13`): item text → segmented **G | A toggle** (G green / A red-orange when selected; neutral until chosen) → auto-stamped time (server time, `§9 #9`) + auto-derived initials → conditional note + A-photo tile (appears + **required** when A chosen) → a completion-photo `PhotoUpload` strip + a **Review & Submit** action.
- **Behaviour:** identical gates to the anchor — `PATCH …/responses` saves drafts (`IN_PROGRESS`); on **A** the note+photo is required; **submit is blocked** (UI + server 422) unless every item is answered, ≥1 completion photo exists, and every A item has its issue photo. On submit → `SUBMITTED`; because the **filler is the TL**, the cascade still starts at TL's manager — i.e. the instance goes to **SM** as the next approver (the TL cannot approve their own submission). Time-stamp = **server time**, initials **auto-derived from Priya** (`§9 #9`). Offline → in-memory draft + retry.

## `TL-ROSTER` — Team Roster Builder (`screens/08-roster-builder.html`)

- **Purpose:** create/edit the roster for **own team only** (Team scope, access matrix) — a drag/shift builder scoped to Priya's members + rides.
- **Layout:** `Calendar/Roster grid §6.19` — day/week toggle, rows = team members, columns = days; shift blocks colored by template (Morning light-blue / Evening blue wash); leave overlay (`gray-300` hatch); **gap-alert badge** (`danger`) where a required ride/shift is unstaffed; drag-drop with conflict warning. An **opener** toggle on a shift sets `RosterEntry.isOpener` (the 114-pt opener, `§9 #2`).
- **Publish:** a **Publish** button opens the **publish-roster confirm modal** (`modals/publish-roster.html`) — confirming **fires auto-assign for the team's published entries** (`FOUNDATION_SPEC §3`), creating each member's `ChecklistInstance`s. Re-publish is idempotent. Scope: TL can only roster own team; adding a non-team user → 403.

## `TL-ROSTER-REASSIGN` — Mid-Shift Reassign (`screens/13-roster-reassign.html`) — CL-4

- **Purpose:** mid-shift (~every 2 hrs) the TL re-targets a monitor to a **different ride**; the new ride's checklists follow the monitor, the old ride moves to whoever now holds it, and the monitor's mobile updates live (`FOUNDATION_SPEC §3a`, `DESIGN_SYSTEM §6.23` AllocationBoard).
- **Layout:** a **current-floor board** (one row per monitor): rostered ride · **current ride (live)** · on-ride-since · today's checklist counts · certified-for · **Reassign** action. Cleaning Supervisor row shows "Ride rotation N/A". A reassign-history tab + an inline confirm/undo toast.
- **Reassign:**
  - **Given** Priya clicks **Reassign** on Ramesh, **Then** the **reassign modal** (`modals/reassign-monitor.html`) opens with the **eligible rides** — only rides Ramesh is **certified** on are selectable (others disabled/"Not certified", the `§9 #2` gate). The modal previews the re-target effects + conflict warnings.
  - **Given** a certified new ride is chosen, **When** Priya confirms, **Then** `PATCH /roster-entries/:id/reassign { newRideId }` → (1) the new ride's **`NOT_STARTED`** due templates auto-assign to Ramesh; (2) the old ride re-targets to its **current holder**; (3) FCM `ride-changed` pushes to both monitors → their `*-CHK-TODAY` shows a **ride-changed banner** + the current ride's list; (4) `AuditLog("ROSTER_REASSIGNED")`.
  - **In-progress safety (Given/When/Then):** **Given** Ramesh has an `IN_PROGRESS` (or `SUBMITTED`) instance on the old ride, **When** the reassign runs, **Then** that instance is **NOT withdrawn** — it stays with Ramesh so partial work + photos are never lost; **only `NOT_STARTED` instances re-target**.
  - **Certified gate:** reassigning to a ride the monitor is **not certified** on is blocked in the UI and rejected server-side (`assertCertified`, 403/422).
  - **Trainee TL:** the Reassign action is **disabled** (assist-only, CL-3) — `PATCH …/reassign` is rejected by the resolver.
- **Scope:** own-team monitors + own Game Zone only; another team's entry → 403.

## `TL-CHK-UPLOAD` — Upload Check List (team templates) (`screens/14-checklist-upload.html`) — CL-1

- **Purpose:** the team-scoped **ChecklistBuilder** (`DESIGN_SYSTEM §6.21`) — the TL **creates / assigns / edits** checklist templates that fill within **their own team + Game Zone**. Replaces the old view-only templates screen for the TL surface.
- **Layout:** a **Stepper** (`§6.17`): *Details → Sections & Items → Assign (role/ride/shift) → Review*. The Sections & Items step is a **left section/item tree** (add/reorder via drag) + a **right item editor** (item text, **test method**, input type **G/A**, **requires-photo-on-A** toggle). Save = orange Primary.
- **Builder fields:** template **name**; **frequency** (`daily | weekly | monthly | quarterly | yearly | opening | closing`); **ride / activity zone**; **which role fills it**; **Game-Zone scope** (fixed to the TL's GZ); **sections → items**.
- **Assign sub-step:** maps the template to role / ride / shift → feeds the auto-assign engine (`FOUNDATION_SPEC §3`). **Edit** reopens the same builder.
- **Scope (Given/When/Then):** **Given** the TL authors a template, **Then** its fill-role/ride/GZ are constrained to **own team + own Game Zone** — branch / cross-team / GZ-wide template CRUD is **403** for TL (lives with SM/OH per the access matrix `§5`). Auto-assign **engine** stays server-side.
- **Trainee TL:** read-only / assist (no save/publish) per CL-3.

## `TL-ROSTER-VIEW` — Team Roster View (`screens/09-roster-view.html`)

- **Purpose:** read view of the team roster — day/week/month + gaps (no build affordance).
- **Given** the day/week/month toggle, **Then** show team `RosterEntry` rows (member · ride · shift), template-colored blocks, leave overlay, gap-alert badges. Empty when no shifts. This is the surface the TL mobile companion mirrors.

## `TL-CHK-ASSIGN` — Team Checklist Assign / Override (`screens/10-checklist-assign.html`)

- **Purpose:** view the auto-assigned checklists for the team and manually assign/reassign within own team (Team scope) — e.g. move a checklist to a different rostered member.
- **Given** the assign view, **Then** a `DataTable` of auto-assigned instances (template · ride · shift · current filler · reason "role+certified-ride+shift match" / "opener-114pt" · status). An **Assign / Reassign** action opens the **assign modal** (`modals/assign-checklist.html`) — pick a team member (filtered to certified + rostered) → reassign. Scope: only own-team members; auto-assign engine remains server-side (this is override, not template authoring).

## `TL-REP-NEGATIVE` — Team Negative Report (`screens/12-report-negative.html`)

- **Purpose:** all **A (Action required)** items + their issue photos for the team — the safety-action surface, per ride/shift/day.
- **Given** the report, **Then** a grouped list/table of A items: item text · ride · shift · filler · date · note · **issue photo thumbnail** (→ photo-viewer modal). Date-range + ride filters; **Export ▾** (PDF/Excel). When there are no A items, the **all-clear** empty state (green check, "All team checklists compliant"). Photos open read-only in the lightbox. (Positive + Overdue reports roll up to SM/OH, not built here.)

## `SH-PROFILE` (More) — Profile (`screens/11-profile.html`)

- **Purpose:** own profile, role, reports-to, certified rides; logout. Reached from the sidebar utility cluster + topbar profile chip.
- **Given** the profile, **Then** show name "Priya Nair", role "Team Leader", Game Zone "Ahmedabad-1", reports-to "Rohit Shah · Store Manager", certified rides (Trampoline, Toddler Zone). Logout confirms → clears tokens → `AUTH-LOGIN`.

---

## Acceptance summary (the slot's must-pass behaviours)
- [ ] Login → `/me` role-resolve → TL Dashboard; sidebar (6 items) + permissions come from the server — no hardcoded role logic.
- [ ] Dashboard shows the **awaiting-my-approval** queue + team progress + overdue/gaps, scoped to own team + Game Zone.
- [ ] `TL-CHK-VERIFY`: review items + photos (read-only); **Approve** → `TL_APPROVED`, SM notified, leaves queue; **Send-back** requires a reason and returns to the **original filler** (not the previous approver).
- [ ] `TL-CHK-VERIFY` **Route to Maintenance (CL-2):** an A-item routes via `POST /work-orders` (→ `WorkOrder ROUTED`, Maintenance TL notified) and the instance goes **`HELD`**; the **HeldBanner** shows and **Approve is locked**; the hold releases (`HELD → TL_APPROVED`) only when **every** WO closes (`DONE`/`OUTSOURCED`). Held also reads on `TL-DASH` + `TL-CHK-OVERVIEW`.
- [ ] **Trainee TL (CL-3):** identical nav/screens but **Approve / Send-back / Route / reassign disabled** — driven by `/me` permission flags (`checklist.approve = allowed:false`), not hardcoded role checks; `states/trainee-tl-assist-only.html` renders the disabled verify rail.
- [ ] `TL-ROSTER-REASSIGN` **(CL-4):** `PATCH /roster-entries/:id/reassign` re-targets the new ride's **`NOT_STARTED`** instances to the monitor, moves the old ride to its current holder, and FCM-updates both mobiles live; **`IN_PROGRESS`/`SUBMITTED` instances are never withdrawn**; certified-on-new-ride is gated.
- [ ] `TL-CHK-UPLOAD` **(CL-1):** team-scoped ChecklistBuilder (Details → Sections & Items → Assign → Review); template fill-role/ride/GZ constrained to own team + Game Zone (GZ-wide CRUD → 403).
- [ ] After a sent-back re-fill, the instance re-enters the TL queue (cascade restarts from TL, `§9 #8`).
- [ ] Derived Pending: once TL approves, SM/OH see Pending at their level; TL sees the real sub-state climbing above.
- [ ] `TL-CHK-FILL`: TL can fill (G/A, completion + per-A photo, submit-gated UI+422); a TL's own submission goes to **SM** next (TL can't self-approve). Server time + auto-initials (`§9 #9`).
- [ ] `TL-ROSTER`: team-scoped build + publish fires auto-assign for the team; out-of-team user → 403.
- [ ] `TL-REP-NEGATIVE`: A items + issue photos, all-clear empty state, export.
- [ ] States: loading skeleton, empty/all-clear, error+retry, offline-retry preserving in-memory draft, locked (login), 403 (out-of-team/Game-Zone) all render and are reachable.
- [ ] Chrome: navy 240px sidebar + topbar branch badge + team indicator (`§8.1`); no Game-Zone selector (OH-only); no cross-team/branch data leak; CI Dart/TS-model drift gate green.
