# Technician (Mobile) — Per-Screen Spec

> Acceptance detail per screen (Given/When/Then where useful). The Technician works the **Action / Maintenance Work-Order** track (CL-2): Start → Done(+fix photo) / Return(+reason). RBAC: he sees only **own assigned work-orders / own Game Zone / own maintenance team** — enforced server-side, mirrored in the app.
> References: `FOUNDATION_SPEC.md` (WorkOrder model + `WorkOrderState`, §4a WO flow + endpoints, §5 TECH scoping), `DESIGN_SYSTEM.md` (§2.4 `wo-*` + `st-held` colors, §6.12 PhotoUpload gate, §6.14 ApprovalTimeline WO variant, §6.22 WorkOrderCard, §7 TECH bottom-nav, §8.2 mobile shell), `CHANGE_LIST_v1.1.md` (CL-2).

---

## Global rules (apply to every screen)

- **Chrome:** navy 56px top app bar (white title + bell + avatar); a **Game-Zone scope strip** below it in light-blue wash reading `Game Zone · Ahmedabad-1 · Maintenance team` (read-only — Technician is locked to one Game Zone). 56px bottom nav, active slot orange. Orange FAB (camera) on the **Done** screen only.
- **RBAC:** nav + permissions come from `GET /me` (`FOUNDATION_SPEC §2`). The app never hardcodes role logic. **No Approve/Verify/Assign/Route/Outsource affordance exists anywhere** (Technician cannot approve or escalate, `§3` + CL-2).
- **Game-Zone + assignment scope:** every list is server-filtered to the Technician's Game Zone + **WOs assigned to him**. An out-of-scope / unassigned id → 403 (`states/forbidden.html`).
- **Work-Order state semantics:** chips use the `wo-*` token set (`DESIGN_SYSTEM §2.4`) — open `#94A3B8`, routed `#7FB2E5`, assigned `#2E6DB4`, in-progress `#C2410C`, returned `#E0860B`, done `#1E9E5A`, outsourced `#16284A`, overdue solid `#ED1C24`. Status by label+icon+color.
- **Online-only:** any network failure on a write (start / done / return / photo upload) shows the "No connection — retry" state; the **in-memory draft (fix note, staged photo, reason)** is held and the action re-POSTs on retry. No data is silently lost.
- **Concurrency:** every WO transition carries `If-Match: <workOrder.version>` → 409 if stale (e.g. the Maintenance TL reassigned it meanwhile).

---

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

- **Given** the app is launched logged-out, **When** Suresh enters email/phone + password and taps Sign In, **Then** the app calls `POST /auth/login`, stores the access+refresh tokens, calls `GET /me`, and routes to `TECH-DASH` (role resolved = TECH).
- **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".
- **Given** an expired token mid-session, **Then** show the **session-expired** state (`states/session-expired.html`) → re-login.
- Primary button = **navy-on-orange** "Sign In" (52px mobile). Maniax wordmark lockup (AIR orange / MANIAX navy) above the form.

## `TECH-DASH` — Technician Home (`screens/02-dashboard.html`)

- **Purpose:** at-a-glance — my maintenance load + WO counts by state, due-soon and overdue badges, list preview.
- **Given** Suresh has open WOs, **Then** Home shows a maintenance card ("4 open work-orders" · reports-to Mahesh Patil) + 4 KPI tiles (**Assigned · In-progress · Returned · Done**) + due-soon / overdue badges + a list preview of his WOs as WorkOrderCards (each with source checklist + ride, A-item, `wo-*` chip, SLA age), tappable into `TECH-WO-TODAY` / `TECH-WO-DETAIL`.
- **Given** a WO is past its SLA and not closed, **Then** its row shows a **solid-red Overdue** badge.
- **Given** nothing is assigned, **Then** show the nothing-assigned empty state ("No work-orders assigned", `states/empty.html`).
- Loading = skeleton list (`states/loading.html`), never a bare spinner.

## `TECH-WO-TODAY` — My Work-Orders (`screens/03-work-orders.html`)

- **Purpose:** the full list of WOs assigned to me by the Maintenance TL; the entry point to detail / start.
- **Given** WOs are assigned, **Then** the list shows each `WorkOrder` (`WorkOrderCard`, `DESIGN_SYSTEM §6.22`) with: the A-item text, WO id, source **checklist + ride**, issue-photo count, a `StatusBadge wo=…`, an **SLA/age chip**, and the assigner ("Assigned by Mahesh · Maintenance TL").
- **Filter chips:** Open / Assigned / In progress / Overdue.
- **When** Suresh taps an **Assigned** WO, **Then** open `TECH-WO-DETAIL` (he can Start from there); tapping an **In-progress** WO continues; tapping a **Returned** WO opens its read-only status (awaiting Maintenance TL).
- A **Returned** row is muted-warning (`#E0860B`) with "You returned this — awaiting Maintenance TL".

## `TECH-WO-DETAIL` — Work-Order Detail (`screens/04-wo-detail.html`)

- **Purpose:** everything needed to do the fix — the **source A-item**, its **issue photos**, the source checklist + ride + who found/routed/assigned it, the Maintenance TL's instructions, and the **WorkOrder timeline** (`ApprovalTimeline` WO variant, §6.14): Routed (Ops TL) → Assigned (Maint. TL) → *your turn*.
- **Given** the WO is `ASSIGNED`, **Then** the action bar shows **Start work** (primary) + **Return** (secondary).
- **When** Suresh taps **Start work**, **Then** `PATCH /work-orders/:id/start` (`If-Match`) → state `ASSIGNED → IN_PROGRESS`, `WorkOrderEvent` written; the screen now offers **Mark Done** + **Return**.
- **Given** Suresh opens a WO not assigned to him / outside his GZ, **Then** 403 (`states/forbidden.html`).
- Issue-photo thumbnails open a full-screen photo viewer. **No approve / outsource affordance.**

## `TECH-WO-DONE` — Mark Done (fixed) (`screens/05-wo-done.html`) — CRITICAL

- **Purpose:** mark the WO fixed with a **mandatory fix photo**; on success the action **closes at the maintenance level**.
- **Layout:** optional **fix note** textarea + the **fix-photo** `PhotoUpload` tile (≥96px) + the original issue photo for reference + an info card stating the "closes at maintenance level" rule. An orange **camera FAB** + a sticky **Mark Done** bar.
- **Photo gate (§4a / L7, mirrored for WOs):**
  - **Given** no fix photo, **Then** **Mark Done** is disabled ("add a fix photo first") and a `danger` hint shows.
  - **When** Suresh taps the fix-photo tile or the **camera FAB**, **Then** the **photo-capture sheet** (`photo-capture.html`, Camera / Gallery) opens; the shot uploads via `POST /uploads/photos` (`kind=COMPLETION`, `workOrderId`) → **local disk on VPS** → a `WorkOrderPhoto` row; the thumbnail appears and **Mark Done** enables.
- **Submit Done:** **When** Suresh taps **Mark Done** **and** confirms in the **confirm-done sheet** (`confirm-done.html`), **Then** `PATCH /work-orders/:id/done` (`If-Match`, ≥1 `WorkOrderPhoto`) → state `IN_PROGRESS → DONE`, `closedAt` set, `WorkOrderEvent` written.
  - The server independently re-checks the photo gate and returns **422** `{ fields: { fixPhoto } }` if no photo exists — the UI never bypasses it.
  - **Locked rule (CL-2):** a `DONE` WO **closes at the maintenance level** — it does **not** go to the Store Manager. If it's the **last open WO** for the source checklist, the parent instance releases from `HELD` and resumes the approval cascade.
- **Offline:** Done uploads a photo → online-only; offline shows `states/offline.html` (fix note + staged photo held, retry).

## `TECH-WO-RETURN` — Return with Reason (`screens/06-wo-return.html`)

- **Purpose:** when the fix can't be done internally, return the WO **with a required reason** (+ optional photo) to the Maintenance TL.
- **Layout:** an explainer ("back to your Maintenance TL"), quick-reason chips (Part not in stock / Needs specialist / Beyond on-site repair / Needs replacement panel), a **required reason** textarea, and an **optional** photo tile. A sticky **Return to Maintenance TL** bar.
- **Reason gate:**
  - **Given** the reason is empty, **Then** **Return** is disabled / blocked ("A reason is required").
  - **When** Suresh enters a reason **and** taps **Return** (confirming via `return-reason.html` sheet), **Then** `PATCH /work-orders/:id/return { reason }` (`If-Match`) → state `IN_PROGRESS → RETURNED`, `returnReason` stored, `WorkOrderEvent` written, and **the Maintenance TL is notified** (FCM + in-app).
  - The server rejects an empty reason with **422**.
- **Routing (CL-2):** Return goes **back to the Maintenance TL only** — never to the Store Manager directly. Only the Maintenance TL can re-assign or outsource. The source checklist **stays `HELD`** until the WO closes.

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

- **Purpose:** the More-tab landing — own profile, role, Game Zone, team, reports-to, maintenance skills; logout.
- **Given** the profile, **Then** show name, role ("Technician"), Game Zone ("Ahmedabad-1"), team ("Maintenance"), reports-to ("Mahesh Patil · Maintenance TL"), and skills. Logout confirms then clears tokens and returns to `AUTH-LOGIN`.
- Alerts (notification center) is reachable from here and the top-bar bell.

---

## Acceptance summary (the slot's must-pass behaviours)
- [ ] Login → `/me` role-resolve → Technician Home; no nav/permission is hardcoded.
- [ ] `TECH-WO-TODAY` shows exactly the WOs **assigned to Suresh** in his Game Zone, with correct `wo-*` state chips + SLA/age.
- [ ] **Start:** `ASSIGNED → IN_PROGRESS` via `/start` (If-Match); audit event written.
- [ ] **Done is photo-gated (UI + server 422):** no `WorkOrderPhoto` → no Done; on Done state → `DONE`, action **closes at maintenance** (never reaches SM); last-WO releases the parent `HELD`.
- [ ] **Return requires a reason (UI + server 422):** on Return state → `RETURNED`, Maintenance TL notified; routes back to the Maintenance TL (not the SM).
- [ ] **No approve / no route / no outsource** affordance anywhere; Technician cannot approve a checklist.
- [ ] Technician sees the **real WO state** of own WOs; out-of-scope / unassigned access → 403; expired token → 401 re-login.
- [ ] Offline retry preserves the in-memory draft; every transition is `If-Match`-guarded (409 on stale).
