# ADR-098: Native Web Page Is the Primary Client Branding Approval Channel — PPTX/Excel Are Fallbacks

## Status

Accepted, 2026-07-01.

## Status History

```yaml
status_history:
  - date: 2026-07-01
    status: Proposed
    changed_by: hkl
    reason: |
      Three approval channels now exist for branding items: PPTX approval form (Phase 0–1),
      Excel approval form (Phases 0–6), and native per-item web page (Phase 7). Need to
      document which is primary, how they coexist, and the data contract between them.
    changed_via: adr-kit (360lm)
  - date: 2026-07-01
    status: Accepted
    changed_by: hkl
    reason: |
      Native web page (s-respond screen in recce-client) is the preferred path: zero
      client tooling required, instant feedback, structured data. PPTX and Excel remain
      valid fallbacks for clients without reliable internet or who prefer offline review.
      All three channels write to the same branding_approvals table.
    changed_via: adr-kit (360lm)
```

## Context

Branding item approval is the process by which a client (KFC, HP, McDonald's etc.) reviews each branding item proposed in a Recce (type, dimensions, qty) and responds: Approved / Approved with Changes / Resubmission Required / Cancelled.

Three channels have been built across phases:

| Channel | Introduced | Mechanism | Client tooling |
|---------|-----------|-----------|----------------|
| PPTX approval form | Phase 0–1 (recce-v37) | Supervisor generates .pptm; client fills named shapes in PowerPoint; supervisor uploads | MS PowerPoint (Windows) |
| Excel approval form | Phase 4–6 (recce-v38/v39) | Supervisor generates .xlsm; client fills dropdowns; VBA macro POSTs to /submit-excel | MS Excel (Windows) + internet |
| Native web (s-respond) | Phase 7 (recce-client-v12) | Client opens recce-client portal, taps "📋 Review Items", fills per-item cards, submits | Any browser |

All three write to `recce.branding_approvals` and log to `recce.pptx_submission_log` (channel field distinguishes: `pptm_upload` / `xlsx_direct` / `xlsm_upload` / `native_client`).

The question: which channel is authoritative, and what happens when multiple channels submit for the same submission?

## Decision

### 1. Native web (s-respond screen) is the primary channel

The recce-client portal `s-respond` screen is the preferred approval channel for all clients who can access their magic-link URL. Reasons:
- Zero client tooling — any browser on any device
- Structured data entry (dropdowns, validated fields) — no free-text normalisation needed
- Immediate DB write — no file upload step by supervisor
- Photos shown inline (via signed `/photo/` proxy) — client sees exactly what they are approving

### 2. PPTX and Excel are fallbacks, not replacements

PPTX and Excel remain valid for:
- Clients with intermittent internet (offline-fill, then supervisor uploads or VBA submits when online)
- Enterprise clients where IT blocks browser access to external portals
- Cases where client prefers a paper trail in their own file system

### 3. Last-write-wins per branding item

`branding_approvals` uses upsert keyed on `(submission_id, branding_idx)`. A client who fills the native web form and then a supervisor uploads a corrected PPTX — the PPTX upload wins (later timestamp). There is no conflict resolution beyond timestamp. This is intentional: the supervisor is the final gatekeeper.

### 4. Photo proxy extends ADR-049 — same HMAC token, new route

The `/photo/:sub_id/:filename?t=<token>` endpoint in `recce-view-proxy` reuses the existing HMAC signing from ADR-049. The same signed token that opens the report viewer also authorises photo fetches in the s-respond screen. No new auth mechanism needed.

Pattern: `(brandings|front|inside|addl)_\d+\.(jpg|jpeg|png)` — path traversal blocked at the allowlist.

### 5. overall client_status is derived server-side from per-item decisions

`counters.client_set_branding_response` derives `client_status` from the submitted items array:
- All approved → `approved`
- Any resubmission_required → `resubmission_required`
- Any approved_with_changes (and no resubmission) → `approved_with_comments`
- All cancelled → `cancelled`

This derivation is authoritative. PPTX/Excel channels use the same logic in `_save_pptm_approvals`.

**Decision Maker:** hkl

## Consequences

**Positive:**
- Clients on mobile or non-Windows devices can now respond without needing MS Office
- Per-item granularity: client can approve some items and flag others for resubmission in one submit
- Photo shown inline — reduces back-and-forth clarification
- Single branding_approvals table regardless of channel — admin overlay shows unified view

**Negative / Watch:**
- s-respond requires the client to have a valid magic-link session; expired invites block access. Invite TTL is currently 15 minutes — must be bumped to 24h+ before native channel is viable in prod.
- `PGRST_URL` in recce-view-proxy points to dev PostgREST (`postgrest:3000`) — prod sessions validated via Hub auth (HMAC-only, no DB call), so sign/photo work on prod. But client-portal sessions (DB-validated) only work on dev. A separate prod proxy instance (or PGRST_URL env switch) is needed before client portal works on prod.
- HTML/PPTX export from s-respond screen is client-side — no server record of the export. Treat as convenience copy only; DB is the record of truth.

**Future:**
- When invite TTL is bumped: the native channel becomes the first-line experience; PPTX/Excel can be hidden from the UI unless requested.
- Supervisor adjusted-sizes feature (deferred from Phase 7.3): `adjusted_type/w/h/qty` columns in branding_approvals + admin overlay diff view — build when a client returns items "with changes" for the first time.
- Prod proxy instance: add `recce-view-proxy-prod` container pointing to `postgrest-prod:3000` so ClientPortal sign auth works on prod.

## References

- `recce-client/index.html` — s-respond screen (Phase 7.2, recce-client-v12)
- `slides_proxy.py` — `_gen_client_pptx()` (Phase 7.3)
- `counters` schema — `get_branding_items`, `client_set_branding_response` RPCs (Phase 7.0)
- `recce-view-proxy/server.js` — `/photo/:sub_id/:filename` route (Phase 7.1)
- ADR-048 — External client magic-link auth
- ADR-049 — Recce view files via HMAC-signed proxy
- ADR-096 — Excel approval form one-time tokens
