# Operation Head (Web) — Per-Screen Spec

> Acceptance detail per screen (Given/When/Then where useful). RBAC: OH has **full access across ALL Game Zones** (`gameZoneScope = all`, `FOUNDATION_SPEC §5`) — but every gate is **resolved at runtime from `RolePermission`**, never hardcoded. The OH is the **terminal approver** in the cascade (level 3); approval = `Done`.
> References: `FOUNDATION_SPEC.md` (schema, conventions, auto-assign, cascade, `renderStatusForViewer()`, multi-branch scope), `DESIGN_SYSTEM.md` (§2.4 state colors, §6.2 Sidebar, §6.3 Topbar, §6.6 DataTable, §6.7 Modal/Drawer, §6.10 KpiCard, §6.11 StatusBadge, §6.12 PhotoUpload viewer, §6.14 ApprovalTimeline, §6.16 Tabs, §6.20 HierarchyTree, §8.1 web shell), `PROJECT_PLAN §3/§4a/§4b/§4c/§9`.

---

## Global rules (apply to every screen)

- **Chrome:** navy **240px sidebar** (wordmark lockup → OVERVIEW/PEOPLE/OPERATIONS groups → utility cluster + user chip; active item = 3px orange left bar + orange icon/label, `DESIGN_SYSTEM §6.2`) + **64px sticky topbar** (breadcrumb left · search center · **Game-Zone selector chip** + bell + profile right). Content well max 1440, 40px top / 32px side padding (`DESIGN_SYSTEM §8.1`).
- **Game-Zone selector (OH-only, critical):** the topbar chip reads `Game Zone · All ▾` by default → every `[GZ: all]` list/dashboard aggregates across branches. Switching to a single branch re-scopes the same views. The selector is a **convenience filter, not a security boundary** — OH retains full rights everywhere (`FOUNDATION_SPEC §5`, L5).
- **RBAC:** sidebar + permissions come from `GET /me` (`FOUNDATION_SPEC §2`). The app never hardcodes role logic. Approve/Send-back affordances render because the resolver grants `checklist.approve` at OH scope — not because the code checks `role === 'OH'`.
- **Derived Pending (§9 #5, L8):** any checklist not yet `OH_APPROVED` renders as **Pending** (amber) in OH views — for daily/weekly/monthly/quarterly/yearly alike. Pending is **never stored**; it is computed by the shared `renderStatusForViewer()` (`FOUNDATION_SPEC §4`). On OH approve, the instance becomes `OH_APPROVED` = `Done` and drops off the Pending list.
- **Send-back to filler + restart-from-TL (§9 #3 / #8):** when OH sends back (reason required), the instance → `SENT_BACK` and returns to the **original filler** (Monitor/CS/TL), not to SM. On re-submit the cascade restarts fresh from TL.
- **Server time + auto-initials (§9 #9):** item-level `recordedAt` + `initials` are server-stamped at fill; OH views them read-only and never edits them.
- **Concurrency:** approval mutations carry `If-Match: <instance.version>` → **409** if another approver acted first (e.g. SM moved it). The UI refetches and re-renders the timeline.
- **States:** loading = skeleton tables/cards (never bare spinner); 401 → redirect to login; 403 → no-access page (defensive); error → inline card + retry.

---

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

- **Given** the app is opened logged-out, **When** Anjali 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 `OH-DASH` (role resolved = OH).
- **Given** bad credentials → inline `danger` error; **Given** too many attempts → **locked** state (countdown). Primary = **navy-on-orange** "Sign In" (44px web). Maniax wordmark lockup above the form.

## `OH-DASH` — Operation Head Dashboard (`screens/02-dashboard.html`) — CRITICAL

- **Purpose:** org-wide health at a glance across **all** Game Zones.
- **Given** the default `Game Zone · All` scope, **Then** show: a row of `KpiCard`s — **Pending** (amber, count of all not-yet-OH-approved across all GZ), **Overdue** (solid-red), **Done today**, **Compliance %** (G-rate), **Roster gaps**; then a grid of **per-Game-Zone tiles**, each showing the branch name, its Store Manager, compliance %, Pending count, overdue count, and roster-gap badge.
- **Given** Anjali clicks a per-GZ tile, **Then** the topbar selector switches to that branch and the dashboard (and all nav targets) re-scope to it (drilldown).
- **Pending breakdown (§4b/§9 #5):** the Pending KPI is segmentable by frequency (daily/weekly/monthly/quarterly/yearly) — a small tabbed/segmented control under the tile, since the rule applies to all frequencies.
- Loading = skeleton KPI cards + tile grid; empty (no branches yet) → EmptyState with "Add your first Game Zone" → `OH-GZ-NEW`.

## `OH-GZ-LIST` — Game Zones List (`screens/03-game-zones.html`)

- **Purpose:** all branches in one table; one Store Manager each (`GameZone.storeManagerId @unique`, §4c).
- **Given** the list, **Then** a `DataTable`: GZ name · location · capacity · Store Manager · #staff · #rides · compliance% · status; row actions View (`OH-GZ-DETAIL`) / Edit (`OH-GZ-EDIT`); header action **+ Add Game Zone** (navy-on-orange) → `OH-GZ-NEW`.
- Search + status filter; empty state when no branches; no-results after filter.

## `OH-GZ-NEW` — Add Game Zone (`screens/04-game-zone-new.html`)

- **Purpose:** create a branch.
- **Given** the form, **Then** fields: name (unique, validated), location, capacity (default 400), **assign Store Manager** (opens the **assign-SM modal** — picks an SM-role user not already managing a GZ, since one SM per GZ). On save → `POST /game-zones`; on success route to `OH-GZ-LIST` with a success toast.
- Validation: duplicate name → field `danger`; capacity numeric.

## `OH-GZ-EDIT` — Edit Game Zone (`screens/05-game-zone-edit.html`)

- **Purpose:** edit branch + reassign its Store Manager.
- **Given** a reassign of the Store Manager, **Then** show the **reassign-SM confirm modal** (warns the previous SM loses scope to this GZ); on confirm → `PATCH /game-zones/:id`. Deactivate toggles `isActive` (with confirm). `If-Match` concurrency.

## `OH-GZ-DETAIL` — Game Zone Detail (`screens/06-game-zone-detail.html`)

- **Purpose:** one branch end-to-end, tabbed (`DESIGN_SYSTEM §6.16`): **Staff** (the GZ's users + roles) · **Rides** (→ `OH-RIDE-MASTER` for this GZ) · **Rosters** (read-only roster for the branch) · **Checklists** (the branch's cascade status). Each tab is server-scoped to this `gameZoneId`.
- Rides tab surfaces the **Rides master** entry point (add/edit/deactivate rides for this branch).

## `OH-RIDE-MASTER` — Rides / Activity Zones Master (`screens/07-ride-master.html`)

- **Purpose:** maintain the ride/activity-zone list **per Game Zone** (Trampoline, Toddler Zone, ZipZag, Warrior Assault, Ball Pool, Café, Reception, Party Rooms).
- **Given** a ride, **Then** rows: name · code (unique within GZ, `Ride @@unique([gameZoneId, code])`) · active toggle; add/edit ride modal; deactivate (soft) keeps history. Reached from `OH-GZ-DETAIL` Rides tab; scoped to the selected branch.

## `OH-USERS` — All Users (cross-GZ) (`screens/08-users.html`)

- **Purpose:** every user across **all** Game Zones.
- **Given** the table, **Then** columns: name · role · Game Zone · reports-to · certified rides · status; filters by **Game Zone**, **role**, search; row → `OH-USER-DETAIL`; header **+ Onboard User** → `OH-USER-NEW`.
- Cross-GZ visibility is the OH differentiator (SM sees only own GZ).

## `OH-USER-NEW` — Onboard User (`screens/09-user-new.html`)

- **Purpose:** create a user with role, reporting line, Game Zone, and certified rides — a **wizard** (`DESIGN_SYSTEM §6.17`): details → role → reports-to → Game Zone → certified rides.
- **Given** role + Game Zone are chosen, **Then** the **reports-to picker** is filtered to valid managers (the §3 hierarchy: a MON reports to a TL, a TL to an SM, etc.) within that Game Zone; **certified-rides** is a multiselect of that GZ's rides (the auto-assign gate, §9 #2). On finish → `POST /users` → `OH-USERS`.
- OH can create **any** role (incl. SM/OH) — full access (SM is limited to ≤ SM in the SM pack).

## `OH-USER-EDIT` — Edit User (`screens/10-user-edit.html`)

- **Purpose:** change role / reports-to / Game Zone / certs / deactivate.
- **Given** a reports-to change, **Then** the **reassign-manager confirm modal** (shows old → new manager + the sub-tree impact); on confirm → `PATCH /users/:id`. **Deactivate** opens the deactivate modal (warns about open assignments). `If-Match` concurrency.

## `OH-USER-DETAIL` — User Detail (`screens/11-user-detail.html`)

- **Purpose:** profile + position in the hierarchy + assigned-checklist history. Opens as a right-side **drawer** from `OH-USERS` (or full page).
- **Given** the user, **Then** show identity, role, Game Zone, reports-to + direct reports, certified rides, and a history list of their checklist instances with `StatusBadge` (OH sees Pending/Done per the derived rule).

## `OH-ROLES` — Roles & Access Matrix (editable) (`screens/12-roles-matrix.html`) — CRITICAL

- **Purpose:** view **and live-edit** the **dynamic** 7-role RBAC matrix (`RolePermission`, `FOUNDATION_SPEC §1/§2`, §9 #4).
- **Given** the matrix, **Then** a grid: rows = permissions (module.action, e.g. `checklist.approve`, `roster.create`, `user.manage`), columns = roles (OH/SM/TL/SCM/MON/CS); each cell = an **allowed toggle** + a **scope select** (`all | own | own_team`). Trainee TL maps to TL (§9 #4) — shown as a note, not a separate column.
- **Given** Anjali toggles a cell, **Then** `PATCH /roles/:roleId/permissions` writes the `RolePermission` row (with `updatedBy` audit); **the change takes effect on the very next request** — no redeploy (`FOUNDATION_SPEC §2`, L4). A scope change shows a confirm.
- **Hard rule:** because RBAC is dynamic, **no endpoint in this pack hardcodes a role check** — this screen is the single source of truth the resolver reads. Editing here is what makes the "fully open RBAC" posture real.
- A revert/seed-reset affordance restores the `PROJECT_PLAN §3` starting matrix.

## `OH-HIER-TREE` — Hierarchy Tree (org-wide) (`screens/13-hierarchy.html`)

- **Purpose:** the visual 7-level org tree across **all** Game Zones (`DESIGN_SYSTEM §6.20`): OH → SM (one per GZ) → TL/Trainee TL → SCM/MON/CS.
- **Given** the tree, **Then** expand/collapse nodes; a **per-Game-Zone filter** narrows the tree to one branch; role-colored node chips; click a node → `OH-USER-DETAIL` drawer.

## `OH-ROSTER-ALL` — All-Zones Roster Overview (`screens/14-roster-all.html`)

- **Purpose:** cross-branch roster **read-only** overview (`DESIGN_SYSTEM §6.19`) — NOT a builder.
- **Given** day/week/month toggle, **Then** show roster blocks colored by shift template (Morning light-blue / Evening blue) across all GZ (or one, via the selector), with a **gap-alert badge** (`danger`) where a required ride/shift is unstaffed. No create/edit/publish affordance (those live in SM/TL). Empty state when no roster published.

## `OH-CHK-OVERVIEW` — Checklist Status Overview (`screens/15-checklist-overview.html`) — CRITICAL

- **Purpose:** every checklist across **all** Game Zones with its live cascade status; the entry to final approval.
- **Given** the table (`DataTable`), **Then** columns: Game Zone · ride · shift · frequency/period · filler · **cascade** (compact `ApprovalTimeline` — Filled→TL→SM→OH) · **Status** (`StatusBadge`, **Pending** until OH-approved per the derived rule). Filters: GZ, ride, shift, period (daily/weekly/monthly/quarterly/yearly), status; an **Overdue tab** (solid-red).
- **Given** Anjali clicks a row that has reached the OH step (`SM_APPROVED`), **Then** open `OH-CHK-APPROVE` (drawer) — the Approve/Send-back affordances appear only at the OH step. Rows below SM-approved are read-only drilldowns (she sees the cascade but cannot approve out of order).
- The Pending count here matches the `OH-DASH` Pending KPI (one shared query, `FOUNDATION_SPEC §4`).

## `OH-CHK-APPROVE` — OH Approval / Verify (`screens/16-checklist-approve.html`) — CRITICAL

- **Purpose:** the **final** (level-3) approval — Approve (→ `Done`) or Send-back (→ original filler). Opens as a right-side drawer or full page from `OH-CHK-OVERVIEW`.
- **Given** an instance at `SM_APPROVED`, **Then** show: the full `ApprovalTimeline` (Filled by Ramesh ✓ · TL approved ✓ · SM approved ✓ · **OH pending — you are here** ring); the item list with each G/A response + server time + initials (read-only); the **completion photo(s)** + every **A-item issue photo** as a thumbnail grid (click → **photo-lightbox** modal, swipe, item caption); and the action bar.
- **Approve:** **green** "Approve (Final)" button (`DESIGN_SYSTEM §6.4` Approve = green) → **approve-confirm modal** → `POST /checklist-instances/:id/approve` (`If-Match`) → state `OH_APPROVED` (= Done); the instance drops off the Pending list; success toast; cascade engine logs `ApprovalLog`.
- **Send-back:** **red-orange** "Send back" button → **send-back-reason modal** (required textarea) → `POST /checklist-instances/:id/sendback` (`If-Match`, `{ reason }`) → state `SENT_BACK`; **the original filler (not SM) is notified** (§9 #3); on their re-submit the cascade **restarts from TL** (§9 #8). Anjali cannot send back without a reason (422 if blank).
- **Given** another approver/state moved the instance (stale version), **Then** 409 → refetch + re-render; the action is not double-applied.
- OH does **not** edit items, times, initials, or photos — view + decide only.

## `OH-REP-POSITIVE` — Positive Report (org) (`screens/17-report-positive.html`)

- **Purpose:** all-G/compliant checklists across all GZ — the reassuring-green compliance view.
- **Given** the report, **Then** a `DataTable` filterable by Game Zone / ride / shift / day, with a **date range** picker and **Export ▾ (PDF / Excel)**. Rows show compliant instances (all-G) with their GZ/ride/shift/period. Empty → all-clear EmptyState.

## `OH-REP-NEGATIVE` — Negative Report (org) (`screens/18-report-negative.html`) — CRITICAL

- **Purpose:** every **A (Action required)** item + its issue photo across all GZ — the safety-critical view (§4a).
- **Given** the report, **Then** rows of A-items: GZ · ride · shift · item text · note · filler · time · **issue photo thumbnail** (→ **photo-lightbox**); filter by Game Zone; **Export ▾**. This is where photo evidence rolls up so the OH can act/escalate. Photos load from **local disk** (`Photo.url`).

## `OH-REP-OVERDUE` — Overdue / Escalation Report (`screens/20-report-overdue.html`)

- **Purpose:** missed/overdue checklists escalated to the OH (the top of the escalation chain).
- **Given** the report, **Then** rows: GZ · ride · shift · template · `dueAt` · how-late · current state (solid-red `Overdue` badge); drilldown to the GZ; **Export ▾**. Overdue is derived (`now() > dueAt && state < SUBMITTED`, `FOUNDATION_SPEC §4`).

## `OH-CHK-UPLOAD` — Upload Check List (template list + create) (`screens/21-checklist-upload.html`) *(CL-1)* — CRITICAL

- **Purpose:** manage checklist **templates across all Game Zones** — list (seeded + custom) and **create / upload** new ones. Replaces the old view-only templates view (`README` "What's NOT" amended). Uses the `ChecklistBuilder` (`DESIGN_SYSTEM §6.21`).
- **Given** the template list, **Then** a `DataTable`: name · frequency · ride · fill-role · GZ scope · source (Seeded/Custom) · status (Active/Draft/Inactive); filters by GZ / frequency / ride / source; row actions **Assign** (`OH-CHK-TPL-ASSIGN`) / **Edit** (`OH-CHK-TPL-EDIT`); header **+ New Check List** (navy-on-orange) opens the builder.
- **Given** **+ New Check List**, **Then** the `ChecklistBuilder` **Stepper** opens: *Details → Sections & Items → Assign → Review*. **Details** = name, **frequency** (`daily | weekly | monthly | quarterly | yearly | opening | closing` — CL-1 made opening/closing first-class), **ride / activity zone**, **which role fills it** (role picker), **Game-Zone scope**. **Sections & Items** = left section/item tree (add/reorder/drag) + right item editor; each **item** = text, **test method**, input type **G/A**, **requires-photo-on-A** toggle (default on, §4a).
- **Validation:** name + frequency + fill-role required; a template with **G/A items must have ≥1 item**; an item requiring-photo-on-A is honored at fill time (the filler is forced to attach an issue photo on A). On finish → `POST /checklist-templates` → list with a success toast; Save-draft persists `isActive=false`.
- **RBAC:** OH authors at **all GZ** (`gameZoneId=null` = org-wide/seeded, or a specific branch); SM = own GZ, TL = own team (their packs). The create/edit affordances render because the resolver grants `checklist.template.manage` at OH scope — not a hardcoded role check.
- States: loading skeleton table; empty (no custom templates yet) → EmptyState "Create your first Check List"; no-results after filter.

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

- **Purpose:** map a template to **role / ride / shift / Game-Zone scope** — the input that drives **auto-assign** (`FOUNDATION_SPEC §3`).
- **Given** the Assign step (builder step 3, or the standalone **assign-template modal** from the list), **Then** pick **fill role**, **ride**, **shift**, and **GZ scope**; frequency is carried read-only from Details. **When** Anjali saves, **Then** `POST /checklist-templates/:id/assign` writes the mapping; **on the next roster publish**, the auto-assign engine generates an instance for every rostered filler matching `fillRoleCode + rideId + gameZoneId` (honoring those exact template fields).
- **Given** GZ scope = "All Game Zones", **Then** the template auto-assigns org-wide; **Given** a specific branch, **Then** only that GZ's matching rostered fillers receive it.
- The Assign mapping is a **definition**, not an immediate dispatch — generation happens server-side at roster-publish / period-rollover, never from this screen directly.

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

- **Purpose:** edit a template's **items / test methods / mapping** — reuses the same `ChecklistBuilder`, re-entered at Sections & Items (or any step).
- **Given** Anjali edits an **active, assigned** template, **Then** a warning banner explains: already-generated instances keep their original items; changes take effect on the **next** auto-assign cycle (templates are versioned). **When** she saves, **Then** the **change-items-confirm modal** appears (shows the diff: items edited / photo-on-A toggled / mapping changes) → `PATCH /checklist-templates/:id` writes a new version.
- **Given** a deactivate, **Then** `isActive=false` (soft) — stops future auto-assign, keeps history. `If-Match` concurrency → 409 if another admin edited it.
- OH edits **any** template (seeded or custom, any GZ); SM/TL are scope-limited in their packs.

## `OH-WO-OVERVIEW` — Action / Work-Order Overview (org) (`screens/24-wo-overview.html`) *(CL-2)* — CRITICAL

- **Purpose:** **observe** every maintenance **Work-Order** across **all** Game Zones — the org-wide view of the Action track that branches off A-items. **Read / monitor only** for OH (the maintenance track is owned by `maintenance-tl-web` + `technician-mobile`).
- **Given** the screen, **Then** a `wo-*` state-KPI strip (Open / Routed / In-progress / Held checklists / Outsourced / Overdue) + a `DataTable`: WO# · GZ · ride · A-item text · source checklist · technician · age/SLA · **state** (`StatusBadge wo=…`, `DESIGN_SYSTEM §2.4`). Filters: GZ / ride / state / priority. State machine shown: **Open → Routed → Assigned → In-progress → (Done | Returned); Returned → (Assigned | Outsourced)**.
- **Given** OH views this, **Then** there are **no route/assign/fix/return/outsource actions** — OH cannot act on a WO; clicking a row opens a **read-only drilldown drawer** (source A-item + issue photos + WorkOrder `ApprovalTimeline`).
- **Single-track hold (locked):** a checklist with any open A-item WO reads **Held** (`st-held`, deep-orange) on `OH-CHK-OVERVIEW` and **cannot reach Done** until **every** WO closes (Done or Outsourced). **Only `Outsourced` WOs reach the Store Manager** (`SM-WO-OUTSOURCE`); internally-fixed WOs close at the maintenance level.
- Overdue WO (`wo-overdue`, solid-red) escalates up; surfaces in the KPI strip + state filter.

## `SH-PROFILE` (utility) — Profile / Settings (`screens/19-profile.html`)

- **Purpose:** the utility-cluster landing — own profile, role ("Operation Head"), logout. OH has no `reportsTo`; certified-rides not applicable. Logout confirms → clears tokens → `AUTH-LOGIN`. Alerts (notification center) reachable from here + the top-bar bell.

---

## Acceptance summary (the slot's must-pass behaviours)
- [ ] Login → `/me` role-resolve → OH Dashboard; sidebar + permissions server-resolved; **no hardcoded role logic**.
- [ ] Dashboard aggregates across **all** Game Zones; per-GZ tiles drill down; Pending KPI matches the overview's derived-Pending query.
- [ ] Game-Zone selector re-scopes every `[GZ: all]` view; OH never loses cross-GZ rights (scope = convenience, not boundary).
- [ ] `OH-ROLES` matrix edits write `RolePermission` and take effect on the next request — no redeploy; no spec'd endpoint hardcodes a role check.
- [ ] `OH-CHK-OVERVIEW` shows **Pending** (amber) for anything not yet OH-approved (all frequencies); Overdue tab is solid-red.
- [ ] `OH-CHK-APPROVE`: **Approve = green** → `OH_APPROVED`/Done (drops off Pending); **Send-back = red-orange**, reason required (422 if blank) → returns to the **original filler**, restart-from-TL on re-submit.
- [ ] OH views item time + initials + photos **read-only** (server-stamped); photo-lightbox loads from local disk.
- [ ] Game Zone CRUD enforces **one Store Manager per GZ**; user onboarding filters reports-to by the §3 hierarchy + GZ; certified-rides multiselect feeds auto-assign.
- [ ] Concurrency: approval `If-Match` → 409 on stale; states (loading/empty/no-results/error/401/403) render and are reachable.
- [ ] **CL-1:** `OH-CHK-UPLOAD` lists seeded + custom templates across all GZ and the `ChecklistBuilder` (Details → Sections&Items → Assign → Review) creates a template with name/frequency(incl. opening/closing)/ride/fill-role/GZ-scope + sections→items (text, test method, G/A, requires-photo-on-A); builder validation blocks an empty/incomplete template.
- [ ] **CL-1:** `OH-CHK-TPL-ASSIGN` writes a role/ride/shift/GZ mapping that **feeds auto-assign** (honors `fillRoleCode` + `gameZoneId`, `FOUNDATION_SPEC §3`); `OH-CHK-TPL-EDIT` reuses the builder + change-items confirm; OH has full CRUD across **all** GZ.
- [ ] **CL-2:** `OH-WO-OVERVIEW` shows all Work-Orders across GZ with `wo-*` badges and is **read/monitor only** (no route/assign/close for OH); **single-track hold** = checklist reads **Held** until all its WOs close; **only Outsourced WOs reach SM**.
