# Customer Camera Side Panel (Phase C9)

**Status:** Complete · committed locally (not pushed)
**Date:** 2026-11-12
**Scope:** Frontend only (reuses the C8 backend photo pipeline unchanged)

## 1. Objective

Provide a reusable, real-time **Customer Camera** side panel reachable from the
TechTitans POS top navigation — **independent of the New Customer form** — that
reuses the completed C8 camera capture, cropper and private customer-photo
storage. Staff can open the panel from anywhere in the POS, see live
face-presence detection, select an existing customer, and save a photo without
walking through the New Customer flow.

## 2. What was built

### Global camera button (§1)
- A **Camera icon-button** was added to the POS top navigation
  (`app-header__right`, before the notification bell).
- It is gated on the `customers.manage` permission — users without it never see
  the button and the panel is not mounted for them.
- Clicking it opens a **right-side drawer** (the existing `FormDrawer`,
  `size="compact"`, ~440px desktop). The drawer is mounted once at the app shell
  (`Header`), so it is available from Customer Management, Sales/POS, Repairs,
  IPTV and every other authorised staff screen (verified open from the
  Dashboard — see screenshots).
- **The camera does not start on application load**, and does not start on
  drawer open. It starts only when the operator clicks **Start camera**.

### Live camera interface (§2)
- Genuinely **live webcam preview** via `navigator.mediaDevices.getUserMedia`
  (`facingMode: 'user'`, or an exact `deviceId` when a specific camera is
  chosen). Not a prerecorded image or a mock.
- **Camera device selector** — a `<select>` is shown when more than one
  `videoinput` device is enumerated; switching restarts the stream on the chosen
  device.
- **Start / Stop camera** and **Capture photo / Retake** controls.
- **Live face-presence indicator**: a status pill shows a face count
  (`N face(s) detected`) and lighting/position guidance
  (`No face detected — centre the customer in good light`).
- Face presence uses the browser-native **Shape Detection API
  (`window.FaceDetector`)**, feature-detected at runtime. It is only
  constructed while the preview is live, polls at ~500 ms, and reports
  **count only**. It never produces embeddings, never identifies people, never
  estimates attributes and never retains frames. When the browser does not
  expose `FaceDetector` the panel shows an honest
  **"Face check unavailable in this browser"** state — it is never faked.

### Customer lookup (§3)
- Below the camera, a **Find customer** search box queries by **name, phone,
  customer reference or email** (debounced, `customersApi.search`, min 2 chars).
- Matches render as a list with each customer's **saved avatar**, name and
  contact details.
- Staff must **select and confirm** a customer (the selected record shows with a
  "Change" affordance) before a save is possible or the profile can be opened.
- The **captured face is never used to search the customer database** — lookup
  is text only.

### Capture and save (§4)
- Flow: **Capture → Crop/zoom (round-mask `ImageCropper`) → Preview → Consent →
  Save**.
- On capture the still frame is drawn to a canvas and the camera is
  **immediately stopped** (tracks released) before cropping.
- Saving requires an explicit **photo-consent** checkbox; the Save button is
  disabled until a cropped photo **and** a selected customer **and** consent are
  all present.
- Save goes through the existing **C8 `CustomerPhotoService`**
  (`customersApi.uploadPhoto` → `POST /customers/{id}/photo`), which
  content-validates, re-encodes to a 512×512 EXIF-stripped JPEG and stores it on
  the private disk. The returned record **refreshes the avatar**, and the
  success state offers **Open customer profile** or **Take another**.
- **No duplicate customer records are created** — the panel only attaches a
  photo to an existing, staff-selected customer.

### UX (§5)
- Professional right-side panel, compact width on desktop, live preview kept
  prominent, clean status pills, and a proper stacked **mobile** layout
  (full-width, no horizontal overflow — verified at 390px).

### Privacy & security (§6)
- No continuous or store-wide surveillance; no silent camera activation
  (explicit Start only).
- No automatic facial identification and no biometric matching — the inline copy
  states the photo is *"not used for facial recognition or any biometric
  matching. The captured face is never used to search the customer database."*
- No background recording; frames are never saved without an explicit
  crop + consent + save.
- Camera **tracks are stopped** on Stop, on capture, on drawer close and on
  component unmount (`getTracks().forEach(t => t.stop())` + `srcObject = null`).
- Existing **tenant isolation** and **signed-URL secure photo delivery** from C8
  are retained unchanged; consent withdrawal / photo deletion remain available
  via the C8 `DELETE /customers/{id}/photo` endpoint.

## 3. Files changed

Frontend (`frontend/`, branch `techtitans/integration`, commit `02780f6`):

| File | Change |
|------|--------|
| `src/pages/customers/CustomerCameraPanel.tsx` | **New** — the global side panel |
| `src/lib/media/useFaceDetection.ts` | **New** — native FaceDetector presence hook |
| `src/components/layout/Header.tsx` | Gated camera button + mounted drawer |
| `src/components/ui/Icon.tsx` | Added `camera` icon |
| `src/pages/customers/customers.css` | Camera-panel styles |
| `src/pages/customers/CustomerCameraPanel.test.tsx` | **New** — panel tests |
| `src/lib/media/useFaceDetection.test.ts` | **New** — hook tests |

No backend changes — C9 reuses the C8 photo endpoints and `CustomerPhotoService`
as-is.

## 4. Testing (§7)

### Automated
- **Vitest** — C9 units pass in isolation: `CustomerCameraPanel.test.tsx` (3),
  `useFaceDetection.test.ts` (2), plus the touched `navigation`/header suites
  (21 tests green together). Covered: no-auto-start, customer lookup + select,
  capture → crop → consent-gated save → Open-profile, and the permission-denied
  message.
  - *Note:* two full-suite (1818-test) runs showed 1–3 **load-induced timeout
    flakes** (different tests each run, all ~5s `getByRole` timeouts under max
    parallelism); every one passes when its file is run in isolation. No C9
    assertion fails.
- **TypeScript** (`tsc --noEmit`): clean.
- **ESLint** (C9 files): clean.
- **Production build** (`vite build`): succeeds (only the pre-existing
  chunk-size advisory).

### Simulated-camera end-to-end (Playwright)
Verified headlessly with Chromium's **fake media device**
(`--use-fake-device-for-media-stream --use-fake-ui-for-media-stream`,
`permissions: ['camera']`). **This is a simulated camera, not physical
hardware.** Results:
- Panel opens from the top-nav button; title "Customer camera".
- Camera is **not** auto-started (Start button present on open).
- **Live** video element renders after Start (fake device stream, with the
  moving test pattern + timecode visible in screenshots — confirming a real
  live stream, not a static mock).
- Face indicator attempts detection and shows the honest **"Face check
  unavailable in this browser"** fallback (headless Chromium does not expose
  `FaceDetector`).
- Lookup returns a matching customer with avatar; capture opens the cropper.
- No horizontal overflow at desktop widths/zoom or on mobile; **0 console
  errors**.

**Physical-camera note:** a physical webcam was **not** used for the automated
run above, so all automated camera results are **simulated-camera** results. On
a real device in Chrome/Edge (which expose the Shape Detection API) the
face-count indicator shows live counts; the rest of the flow is hardware- and
browser-agnostic.

## 5. Screenshots

Stored in `screenshots/customer-camera-c9/`:

| File | Shows |
|------|-------|
| `c9-panel-idle.png` | Drawer open, camera off, Start camera (no auto-start) |
| `c9-panel-live.png` | Live preview + face-presence pill + Capture/Stop |
| `c9-panel-lookup.png` | Customer search returning a match with avatar |
| `c9-panel-crop.png` | Round-mask cropper; camera stopped behind it |
| `c9-panel-mobile.png` | Full-width stacked mobile layout, no overflow |

## 6. Limitations

- **Face presence requires the browser-native Shape Detection API.** It is
  available in Chromium-based browsers (Chrome/Edge, often behind the standard
  Shape Detection support) and absent in Firefox/Safari and headless Chromium,
  where the panel degrades gracefully to "Face check unavailable" — all other
  functionality (capture, crop, consent, save) is unaffected. No ML dependency
  was added, by design, to avoid shipping face embeddings/identification
  capability.
- Face detection is **presence/count only**. There is deliberately no
  recognition, matching, or attribute estimation anywhere in this module.
- The lighting/position guidance is derived from face presence, not a dedicated
  quality model.

## 7. Acceptance

> *Staff can open a camera side panel from anywhere in TechTitans POS, see live
> face-presence detection, select a customer and save a photo without navigating
> through the New Customer form.* — **Met.**

## 8. Commits

- Frontend: `02780f6` — *TechTitans C9: global Customer Camera side panel*
  (branch `techtitans/integration`).
- Backend: documentation + screenshots only (this file).

Committed **locally only** — not pushed or deployed.
