# Store Manager (Web) — Per-Screen Spec

> Acceptance detail per screen (Given/When/Then where useful). RBAC: Store Manager is scoped to **own Game Zone** only (`gameZoneId = me.gameZoneId`) — staff, rosters, checklists, reports — enforced server-side (FOUNDATION §5), mirrored in the app. SM is the **second** approval level (TL → **SM** → OH).
> References: `FOUNDATION_SPEC.md` (§1 schema, §2 conventions + `/me`, §3 auto-assign, §4 cascade + derived-Pending, §5 multi-branch), `DESIGN_SYSTEM.md` (§2.4 state colors, §6.3 Topbar branch badge, §6.6 DataTable, §6.7 Modal/Drawer, §6.11 StatusBadge, §6.12 PhotoUpload viewer, §6.14 ApprovalTimeline, §6.17 Stepper, §6.19 Calendar/Roster grid, §8.1 web shell), `PROJECT_PLAN §3/§4a/§4b/§4c/§9`.

---

## Global rules (apply to every screen)

- **Chrome (`DESIGN_SYSTEM §8.1`):** 240px navy sidebar (wordmark lockup + the 8-item data-driven nav — Dashboard · Hierarchy · Team/Users · Roster · Checklists · **Upload Check List** (CL-1, replaces Templates) · **Outsource Inbox** (CL-2) · Reports, **orange active accent** = `brand-navy-soft` fill + 3px orange left bar) + 64px sticky topbar (`surface` bg, `shadow-sm` bottom) with global search center, a **non-interactive branch badge** right (`building-2` + `Game Zone · Air Maniax Ahmedabad-1`), bell, profile chip. A 40px breadcrumb row, then page H1 + primary action top-right. Content well `bg #F5F7FA`, max 1440, 40px top / 32px side padding.
- **No Game-Zone switcher.** SM is locked to one branch (`§4c`); the topbar badge is read-only. The OH selector is OH-only.
- **RBAC:** sidebar + permissions come from `GET /me` (FOUNDATION §2). The app never hardcodes role logic. SM's onboard is **Limited** — the role picker in `SM-USER-NEW`/`-EDIT` offers only roles **≤ SM** (Team Leader, Trainee TL, Senior/Staff Court Monitor, Cleaning Supervisor) — never OH or another SM.
- **Game-Zone scope:** every list/read is server-filtered to the SM's Game Zone. An out-of-zone id → 403 (`states/forbidden.html`). No cross-GZ data leak.
- **Derived Pending (`§9 #5`, FOUNDATION §4):** SM sees **Pending** (amber) for any instance not yet `SM_APPROVED`; once SM approves, SM sees it climb (SM Approved → awaiting OH → Done). Pending is rendered by the shared `renderStatusForViewer(instance, viewerRole=SM)` — never stored.
- **Status semantics:** `G` always green, `A` always red-orange; **Overdue** = solid-red badge; **Approve** = green button, **Send-back** = red-orange button (action color = outcome color).
- **Concurrency:** every mutation (publish, assign, approve, send-back, user edit) sends `If-Match: <version>` and handles **409** (stale) with a "reload — someone changed this" inline message.
- **401/403:** token expiry → `states/session-expired.html` (redirect to `AUTH-LOGIN`); out-of-scope → `states/forbidden.html`.

---

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

- **Given** the app is launched logged-out, **When** Rohit enters email/phone + password and clicks Sign In, **Then** the app calls `POST /auth/login`, stores access+refresh tokens, calls `GET /me`, and routes to `SM-DASH` (role resolved = SM, gameZoneScope = own, gameZone = Ahmedabad-1).
- **Given** bad credentials, **Then** show an inline `danger` error ("Incorrect email or password"); password field reveals on the eye icon.
- **Given** too many failed attempts, **Then** show the **locked** state (`states/locked.html`) — countdown + "Too many attempts".
- Primary button = **navy-on-orange** "Sign In" (44px web). Maniax wordmark lockup (AIR in orange / MANIAX in navy) above the form. Web login is a centered card on the `bg` page (no sidebar pre-auth).

## `SM-DASH` — Store Manager Dashboard (`screens/02-dashboard.html`)

- **Purpose:** own-Game-Zone health at a glance — compliance, what is **awaiting SM approval**, roster gaps, overdue.
- **Given** Rohit lands post-login, **Then** show KPI tiles (`KpiCard`): **Awaiting my approval · N** (amber, the SM-level Pending queue), **Compliant today · %**, **Overdue · N** (solid-red accent), **Roster gaps · N**; below, two panels — "Awaiting my approval" (a short `DataTable` of `SM_APPROVED`-pending instances → row click opens `SM-CHK-APPROVE`) and "Roster gaps this week" (uncovered ride+shift → click to `SM-ROSTER`).
- **Given** nothing awaits approval, **Then** the queue panel shows the all-clear empty state ("All caught up — no checklists awaiting your approval"). Loading = skeleton cards/rows (`states/loading.html`), never a bare spinner.
- All counts are **own Game Zone only** (server-scoped). Drilldowns deep-link to the relevant screen pre-filtered.

## `SM-USERS` — Team / Users (own GZ) (`screens/03-users.html`)

- **Purpose:** every staff member in Rohit's Game Zone with role + reports-to; the entry to onboard/edit.
- **Given** the list, **Then** render a `DataTable` (`DESIGN_SYSTEM §6.6`): columns Name · Role · Reports-to · Certified rides · Status (Active/Inactive) · actions (View/Edit). Search + filter chips (by role, by ride). Page header has a **+ Onboard staff** primary button (navy-on-orange) → `SM-USER-NEW`.
- **Given** SM scope, **Then** only own-GZ users appear; OH and other-zone users are never listed. Row View → `SM-USER-DETAIL` drawer; Edit → `SM-USER-EDIT`.
- Empty state when no staff yet ("No staff in this Game Zone yet — onboard your first Team Leader").

## `SM-USER-NEW` — Onboard Staff (own GZ) (`screens/04-user-new.html`)

- **Purpose:** create a user inside the SM's Game Zone, with the **Limited** role set.
- **Layout:** a `Stepper` (`§6.17`): **1 Details** (full name, email, phone, password/invite) → **2 Role** (radio of roles **≤ SM only**: Team Leader / Trainee TL / Senior Court Monitor / Staff Court Monitor / Cleaning Supervisor) → **3 Reports-to** (manager picker, filtered to valid managers in this GZ per the §3 line) → **4 Certified rides** (multiselect chips of this GZ's rides — the auto-assign gate, §9 #2). Game Zone is **auto-set to Ahmedabad-1** (not editable — SM cannot create cross-zone users).
- **Given** Rohit picks role = Staff Court Monitor, **Then** the reports-to picker offers Team Leaders in this GZ (e.g. Priya); certified-rides defaults empty and must be set for auto-assign to work.
- **Given** submit, **Then** `POST /users` (gameZoneId server-forced to me.gameZoneId; role validated ≤ SM → **422** if OH/SM attempted); on success route to `SM-USERS` with a success toast.
- Field-level validation (`danger` borders + helper); duplicate email → 422.

## `SM-USER-EDIT` — Edit Staff (own GZ) (`screens/05-user-edit.html`)

- **Purpose:** edit role / reports-to / certs / activate-deactivate within own GZ.
- **Given** Rohit changes **reports-to**, **Then** the **reassign-manager confirm** modal (`modals/reassign-manager.html`) warns "This moves <name> under <new manager> — their reporting line and roll-up change." On confirm → `PATCH /users/:id` (`If-Match`).
- **Given** Rohit toggles **Deactivate**, **Then** a confirm modal explains the user keeps history but cannot log in; deactivation is GZ-scoped.
- Role picker stays **≤ SM**; attempting to set OH/SM → 422. Cannot move a user out of the SM's Game Zone (no GZ field).

## `SM-USER-DETAIL` — Staff Detail (`screens/06-user-detail.html`)

- **Purpose:** read view of one staff member — profile + their sub-tree + checklist history.
- **Given** opened from `SM-USERS`, **Then** show a **right-side drawer** (`§6.7`, 420px) with: name/role/Game Zone/reports-to/certified rides, the person's reports (sub-tree chips), and a small history table of their recent checklist instances with `StatusBadge` (read-only). Edit button → `SM-USER-EDIT`.
- All data own-GZ scoped; an out-of-zone id → 403.

## `SM-HIER-TREE` — Hierarchy Tree (own GZ) (`screens/07-hierarchy.html`)

- **Purpose:** the SM's branch tree — SM → TL/Trainee → Monitors/Cleaning Supervisor.
- **Given** the tree, **Then** render `HierarchyTree` (`§6.20`) rooted at Rohit (SM), expand/collapse nodes, role-colored chips, each node showing name + role + certified-ride count. (OH is shown only as the parent breadcrumb, not expandable from here.)
- Own GZ only; no per-Game-Zone filter (that is OH's tree). Node click → `SM-USER-DETAIL` drawer.

## `SM-ROSTER` — Roster Builder (own GZ) (`screens/08-roster.html`) — CRITICAL

- **Purpose:** build the branch roster by ride + shift; the **publish** here is what fires auto-assign.
- **Layout:** the `Calendar / Roster grid` (`§6.19`) — day/week/month toggle (Tabs); rows = rides (Trampoline, Toddler Zone, ZipZag, …), columns = days (week view); shift blocks colored by template (**Morning `brand-light-blue` wash / Evening `brand-blue` wash**). A staff palette rail (right) to **drag** people onto a ride+shift cell. Header actions: **Bulk upload** (→ `SM-ROSTER-UPLOAD`), **Templates** (→ `SM-ROSTER-TEMPLATES`), **Leave** (→ `SM-ROSTER-LEAVE`), and the primary **Publish** (navy-on-orange).
- **Given** Rohit drags Ramesh onto **Trampoline · Morning**, **Then** a `RosterEntry` (DRAFT) is created (`POST /roster/entries`); an **opener** toggle on the cell marks the 114-pt opener (`isOpener`).
- **Given** a conflict (same person, two rides, same shift/day) → inline **conflict warning** on the cell; an uncovered required ride+shift shows a **gap-alert badge** (`danger`).
- **Given** Publish, **Then** the **publish-confirm modal** (`modals/publish-confirm.html`) explains "Publishing fires checklist auto-assign for every rostered person" → on confirm `POST /roster/publish` (`If-Match`) → entries `PUBLISHED`, auto-assign runs (see flow). Re-publish warns it is idempotent (no duplicate instances).
- Drag-drop is the design contract; the dev may ship a click-to-assign fallback that produces the same `RosterEntry` rows.

## `SM-ROSTER-UPLOAD` — Roster Bulk Upload (`screens/09-roster-upload.html`) — CRITICAL

- **Purpose:** Excel/CSV import of a roster with validation before commit.
- **Layout:** a `Stepper` (`§6.17`): **1 Upload** (drag-drop file zone + a downloadable template + the expected columns: `email, ride_code, shift_name, date, is_opener`) → **2 Validate / dry-run** (`POST /roster/upload?dryRun=true` parses + checks) → **3 Review errors** (a validation-errors table: row, field, message — e.g. "row 4: ride_code TRAMP not found in this Game Zone", "row 9: email not in your Game Zone", "row 12: duplicate person/ride/shift/day") → **4 Commit** (only the valid rows, or block until all pass per the chosen mode).
- **Given** validation finds errors, **Then** open the **upload-errors modal** (`modals/upload-errors.html`) listing each bad row with `danger` styling; a "Download error report" link; **Commit is disabled until errors are resolved** (or "commit valid rows only" if that mode is offered).
- **Given** a clean file, **Then** Commit → `POST /roster/upload` → DRAFT `RosterEntry` rows created in the SM's GZ; routes back to `SM-ROSTER` to review + publish. The importer never creates cross-GZ rows (out-of-zone email → row error, not a silent insert).

## `SM-ROSTER-TEMPLATES` — Shift Templates (`screens/10-shift-templates.html`)

- **Purpose:** manage the shift definitions used by the roster + auto-assign timing.
- **Given** the list, **Then** show `ShiftTemplate` rows (Morning 10:00–16:00, Evening 16:00–22:00, …) with name/start/end/active. **+ Add template** opens an add/edit modal (name + start + end time). Edit/deactivate per row.
- Used by the roster grid coloring and by `computeDue()` in auto-assign (daily due = shift end). Templates are org-level master data the SM can maintain.

## `SM-ROSTER-LEAVE` — Leave & Absence (`screens/11-leave.html`)

- **Purpose:** record/approve leave; absences drive roster gap alerts.
- **Given** the list, **Then** show pending + approved `Leave` rows (person, date, reason, status) with **Approve / Deny** actions; marking leave on a rostered day fires a **gap-alert** on the affected ride+shift in `SM-ROSTER`.
- **Given** Rohit approves a leave that uncovers a ride, **Then** a gap-alert badge appears and the dashboard "Roster gaps" KPI increments. Own-GZ scope only.

## `SM-ROSTER-PUBLISH` — Publish Roster (`screens/12-roster-publish.html`)

- **Purpose:** the explicit publish step / confirmation surface for a roster period → the auto-assign trigger.
- **Given** a DRAFT roster period, **Then** show a summary (date range, # entries, # rides covered, any remaining **gaps** as a warning), and a **Publish period** primary button. Publish opens the **publish-confirm modal** (`modals/publish-confirm.html`); on confirm → `POST /roster/publish` → entries `PUBLISHED` + auto-assign fires (creates `ChecklistInstance`s, state `NOT_STARTED`, tagged to the GZ).
- **Given** the period was already published and re-published, **Then** warn it is **idempotent** (`@@unique(rosterEntryId, templateId)` — no duplicate instances); new/changed entries get fresh instances. A `ROSTER_PUBLISHED` notification + audit row are emitted.
- This is the SM's most consequential action; the modal is deliberately explicit about the auto-assign side effect (`WORKFLOW_GUIDE` Stage C5→D).

## `SM-CHK-OVERVIEW` — Checklist Status (own GZ) (`screens/13-checklist-overview.html`)

- **Purpose:** every checklist in the SM's Game Zone with live cascade status; the queue into approval.
- **Given** the overview, **Then** render a `DataTable` (`§6.6`): columns Template · Ride · Shift · Filler · **Cascade** (a compact `ApprovalTimeline` or step-pill) · **Status** (`StatusBadge` via `renderStatusForViewer(SM)`) · Due. Tabs: **Active** / **Overdue**. Filters: ride, shift, frequency (daily/weekly/monthly/quarterly/annual), status. Row click → drilldown **drawer** with the full cascade timeline + photos; if the row is at the SM level (awaiting SM) the drawer's primary action is **Open approval** → `SM-CHK-APPROVE`.
- **Given** the SM viewer, **Then** anything not yet `SM_APPROVED` reads **Pending** (amber); `SM_APPROVED`/awaiting-OH and `OH_APPROVED` (Done) read their real climbing state; `SENT_BACK` and `OVERDUE` are loud. Counts match the dashboard.
- Empty/all-clear state ("All checklists compliant in your Game Zone"). Own-GZ scope only.

## `SM-CHK-ASSIGN` — Checklist Assignment / Override (`screens/14-checklist-assign.html`)

- **Purpose:** view auto-assigned checklists and manually assign/reassign/override.
- **Given** the screen, **Then** show the auto-assignment trace (which template landed on which roster entry + the `reason` — "role+certified-ride+shift match" / "opener-114pt") in a `DataTable`; a **+ Assign checklist** action opens the **assign/override modal** (`modals/assign-override.html`) — pick template + target (role / ride / employee / shift) → creates a `ChecklistAssignment` (+ instance). Reassign moves an instance's filler within the team.
- **Given** an override, **Then** confirm it (it deviates from the engine default) → `POST /checklist-assignments` / `PATCH …/reassign` (`If-Match`, audit). Own-GZ + own-roster scope; cannot target another Game Zone's ride/user (422/403).
- Manual assign is the safety valve over the generic engine; the engine remains the default path (publish → auto-assign).

## `SM-CHK-APPROVE` — Store Manager Approval (`screens/15-checklist-approve.html`) — CRITICAL

- **Purpose:** the **second** cascade level — Approve (passes to OH) or Send-back (returns to the **original filler**), with full photo evidence + the cascade timeline.
- **Layout:** a detail view — left: the filled checklist (sections → `ChecklistItem` rows showing G/A, note, **server time + auto initials** read-only, and any per-A-item **issue photo** thumbnail); a **completion-photo gallery**; right: the `ApprovalTimeline` (`§6.14`) `Filled ✓ → TL ✓ → Store Manager (you) → Operation Head`. Photos open a **lightbox** (full-screen, swipe, item-context caption — `§6.12` viewer mode). Action bar: **Approve** (green) + **Send-back** (red-orange).
- **Given** Rohit clicks **Approve**, **Then** an approve-confirm → `POST /checklist-instances/:id/approve` (level=SM, `If-Match`) → state `SM_APPROVED`, `ApprovalLog`, **OH notified**; the row leaves the SM queue. The instance now reads "awaiting OH" to Rohit (no longer Pending to him).
- **Given** Rohit clicks **Send-back**, **Then** the **send-back-reason modal** (`modals/sendback-reason.html`) requires a reason (textarea, non-empty) → `POST /checklist-instances/:id/sendback` (level=SM) → state `SENT_BACK`, returns to **the original filler (Ramesh), not the TL** (`§9 #3`); on the filler's re-submit the cascade **restarts from TL** (`§9 #8`). A `SENT_BACK` notification fires to the filler.
- **Given** an out-of-zone or already-decided instance, **Then** 403 / 409 with an inline message. SM can only approve **own-GZ** instances at the **SM level** (resolver-gated, not hardcoded). SM cannot approve at the TL or OH level.

## `SM-CHK-UPLOAD` — Upload Check List (list + create) (`screens/16-checklist-upload.html`) — CL-1

- **Purpose:** the SM now **authors** check lists scoped to its own Game Zone (create / assign / edit), replacing the old view-only templates screen. Seeded master templates remain read-only; own-GZ templates are fully editable.
- **Given** the list, **Then** show two groups — **Seeded master** (read-only: Trampoline D/W/M/Q, Toddler D/W/M/Q/A, ZipZag daily, Park Opening/Closing 114-pt, First-Aid) and **Your Game-Zone check lists** (own custom, with Assign / Edit / Duplicate / Deactivate). A **+ New check list** primary button (navy-on-orange) opens the **ChecklistBuilder** (`DESIGN_SYSTEM §6.21`).
- **Given** Rohit clicks **New check list**, **Then** the builder runs as a `Stepper` (`§6.17`): **1 Details** (name, **frequency** `daily | weekly | monthly | quarterly | yearly | opening | closing`, **ride / activity zone**, **which role fills it**, **Game-Zone scope locked to Ahmedabad-1**) → **2 Sections & Items** (left section/item tree with add/reorder/drag; right item editor — text, **test method**, input type **G/A**, **requires-photo-on-A** toggle) → **3 Assign** (role / ride / shift → feeds auto-assign) → **4 Review**.
- **Given** submit, **Then** `POST /checklist-templates` with `gameZoneId` **server-forced to me.gameZoneId** (own GZ only); a cross-GZ scope attempt → 422/403. The new template appears under "Your Game-Zone check lists" and becomes available to auto-assign once mapped. **Save draft** keeps it unpublished.
- **Given** scope, **Then** the SM can only author/edit own-GZ templates; seeded/global templates (`gameZoneId = null`) are read-only to the SM (no Edit/Delete). OH authors across all GZ (OH pack); TL is team-scoped (TL pack).

## `SM-CHK-TPL-ASSIGN` — Assign Check List (`screens/20-checklist-tpl-assign.html`) — CL-1

- **Purpose:** map a check list to **role / ride / shift** (own GZ) so the auto-assign engine targets the right staff on publish.
- **Given** the screen (reached from a template's **Assign** action or the builder's step 3), **Then** show the template recap + a mapping form: **fill role** (chips ≤ the GZ's roles), **ride / activity zone** (own-GZ rides + "Any park-wide"), **shift** (own-GZ shift templates + "Any"), **opener-only** toggle. A **match preview** lists the staff on the current published roster who would receive it.
- **Given** save, **Then** `POST /checklist-assignments` / `PUT …/template-mapping` creates a `ChecklistAssignment` mapping (own GZ); a cross-GZ ride/role target → 422/403. Existing mappings for the template are listed with **Remove**. Mappings feed `FOUNDATION §3` auto-assign on `POST /roster/publish` (`role + certified ride + shift → templates`).

## `SM-CHK-TPL-EDIT` — Edit Check List (`screens/21-checklist-tpl-edit.html`) — CL-1

- **Purpose:** edit an own-GZ check list's items / mapping — **reuses the ChecklistBuilder** opened at the Sections & Items step.
- **Given** Rohit edits items/mapping and saves, **Then** the **change-items confirm** modal explains it creates a **new version** (e.g. v4): existing in-progress instances keep the prior version, and the new version applies on the **next auto-assign** (no retro-edit of filled work). On confirm → `PATCH /checklist-templates/:id` (`If-Match`, own GZ); 409 on stale, 403 if the template is seeded/global or out-of-GZ.
- Only own-GZ (`gameZoneId = me.gameZoneId`) templates are editable; seeded master templates are read-only (the Edit action is absent).

## `SM-WO-OUTSOURCE` — Outsource / Action-needed Inbox (`screens/22-wo-outsource.html`) — CL-2 · CRITICAL

- **Purpose:** the SM inflow for the maintenance work-order flow — **only** work-orders the Maintenance TL could not fix internally and **escalated to outsource** reach the SM. Internally-fixed actions **close at the maintenance level** and never appear here.
- **Given** the inbox, **Then** show a `DataTable` of work-orders in state **`OUTSOURCED`** (`DESIGN_SYSTEM §2.4 wo-outsourced`) for own GZ — WO id · source A-item · ride · check list · Maintenance TL · state · SLA; tabs **Awaiting outsource decision** / **Outsourced** / **Closed**; `wo-overdue` rows use the **solid-red** badge. Row click → **WorkOrderDetail** (`§6.22`): the source A-item + its **issue photos** (lightbox), the **Technician return reason**, the **Maintenance TL escalation note**, and the **WorkOrder variant of the ApprovalTimeline** (`§6.14`) — `Routed (Ops TL) → Maintenance TL → Technician (returned, reason inline) → Outsourced (you)`.
- **Given** Rohit clicks **Confirm outsource**, **Then** the **outsource-confirm modal** (`modals/outsource-confirm.html`) takes a vendor/detail + a **required note** → `POST /work-orders/:id/outsource` (own GZ, `If-Match`); the decision is logged on the WO timeline and **notifies the Maintenance TL**. **Single-track hold (CL-2):** the parent `ChecklistInstance` stays **HELD** (`st-held`, deep-orange) until **every** A-item work-order is closed; only then does the checklist resume its approval cascade to Done.
- **Given** Rohit clicks **Return to Maintenance TL**, **Then** the WO goes back to the Maintenance TL (with a note) — the SM never assigns a Technician or runs the internal fix (that is `maintenance-tl-web`). **SM does NOT manage the maintenance queue** — it consumes only outsource escalations.
- **Given** an out-of-zone or non-`OUTSOURCED` work-order, **Then** 403 — no other WO state (`OPEN/ROUTED/ASSIGNED/IN_PROGRESS/RETURNED/DONE`) is visible to the SM (`FOUNDATION` WorkOrder state machine: only `OUTSOURCED` reaches SM).

## `SM-REP-POSITIVE` — Positive Report (own GZ) (`screens/17-report-positive.html`)

- **Purpose:** all-G / compliant checklists for the branch, per ride/shift/day.
- **Given** the report, **Then** a `DataTable` of compliant instances (all items G, or the G-item rows) with ride · shift · date · filler · `StatusBadge` (Done/compliant green). A **date-range** picker + ride/shift filters + **Export ▾** (PDF/Excel, `§6.6`). The Reports nav lands here; **Tabs** switch to Negative / Overdue.
- Own-GZ scope; roll-up aggregates only this branch (OH sees all branches). Empty/all-clear state when none.

## `SM-REP-NEGATIVE` — Negative Report (own GZ) (`screens/18-report-negative.html`)

- **Purpose:** every **A** item + its issue photo needing action in the branch.
- **Given** the report, **Then** a list/table of A items: template · ride · shift · date · item text · note · **issue photo thumbnail** (opens lightbox) · filler. Filters by ride/shift/date; **Export ▾**. A items are loud (red-orange row accent). This is the action queue for the branch.
- **Given** a photo thumbnail click, **Then** the lightbox shows the full issue photo with item-context caption + uploader/timestamp (read-only, `§4a`). Own-GZ scope.

## `SM-REP-OVERDUE` — Overdue Report (own GZ) (`screens/19-report-overdue.html`)

- **Purpose:** overdue/missed checklists in the branch with an escalate-up indicator.
- **Given** the report, **Then** a `DataTable` of instances where `now() > dueAt && state < SUBMITTED`, with a **solid-red Overdue** badge, ride/shift/date/filler/due, and an **escalate-up** indicator (these also surface on the OH overdue report). **Export ▾**.
- Own-GZ scope; the overdue sweep is server-side (`@@index([state, dueAt])`). Empty state ("Nothing overdue — your Game Zone is on track").

---

## Acceptance summary (the slot's must-pass behaviours)
- [ ] Login → `/me` role-resolve → SM Dashboard, gameZoneScope = own, branch badge = Ahmedabad-1; no nav/permission hardcoded.
- [ ] Every list/read is server-scoped to the SM's Game Zone; an out-of-zone id → 403; no cross-GZ leak.
- [ ] Onboard/edit offers only roles **≤ SM**; Game Zone is forced to the SM's own; OH/SM role attempts → 422.
- [ ] Roster build (drag/click) + **bulk upload with validation** (dry-run, error preview, commit) both create DRAFT entries; conflicts + gap alerts render.
- [ ] **Publish fires auto-assign** (confirm modal explicit); re-publish is idempotent; `ROSTER_PUBLISHED` audit + notification.
- [ ] Checklist overview renders cascade + `renderStatusForViewer(SM)` Pending; assign/override creates assignments.
- [ ] **SM approve → `SM_APPROVED`, OH notified, leaves SM queue;** **send-back → original filler (not TL), reason required, re-fill restarts cascade from TL.**
- [ ] Approval screen is a **photo viewer** (gallery + lightbox) over completion + per-A-item photos; item time/initials shown read-only (server-derived).
- [ ] Positive/Negative/Overdue reports render own-GZ data with Export; negative report shows A-item photos; overdue uses solid-red badge.
- [ ] **CL-1 Upload Check List:** SM **authors** own-GZ check lists via the ChecklistBuilder (Details → Sections&Items → Assign → Review); `gameZoneId` server-forced to own; seeded/global templates are read-only; assign maps role/ride/shift feeding auto-assign; edit creates a new version applied on next auto-assign (cross-GZ → 422/403).
- [ ] **CL-2 Outsource Inbox:** only `OUTSOURCED` work-orders reach SM (own GZ); WO detail shows issue photos + Technician return reason + Maintenance TL note + WorkOrder ApprovalTimeline; **Confirm outsource** (required note) notifies the Maintenance TL; the parent checklist stays **HELD** until every A-item WO closes; SM does NOT assign Technicians or manage the maintenance queue; non-`OUTSOURCED`/out-of-zone → 403.
- [ ] States: loading skeleton, empty/all-clear, error+retry, 403, 401-session-expired, locked all render and are reachable; 409 handled on concurrent edits.
