# Frontend → Backend requests (append-only) The frontend lane appends here when it needs a contract that doesn't exist yet, finds a shape mismatch, or needs a new field/filter/endpoint. The backend agent reads this at the start of each phase and delivers fixes in its own change. **Frontend never edits backend code to "fix" a gap — it requests it.** ## REQ-001 — Confirm response envelope, wire casing & pagination shape — filed by frontend-phase-0 — 2026-07-02 - **Need:** Authoritative confirmation of three things the frontend types depend on: 1. **Envelope unwrapping.** The b0 swagger shows every response wrapped in `ApiResult` (`{ isSuccess, statusCode, message, requestId, data }`). The frontend's `clientFetch` currently returns the **raw body**, so domain `clientApi`s read the payload via `unwrap()` (`data`). Confirm this is the intended shape for all endpoints (i.e. payload always under `data`), so the pattern is correct before f1+ copy it. 2. **Wire casing.** Observed swagger properties are **camelCase** (`isSuccess`, `serverTimeUtc`) — not the snake_case `api-conventions.md` implies for URL segments. Please confirm JSON body casing is camelCase (and, if so, we can note it in the convention doc), or state where it differs. 3. **Pagination payload.** `api-conventions.md` says lists return `items` + `total` (+ `page`/`page_size`). Confirm the exact field names/casing on the wire (we've typed `Paginated` as `{ items, total, page, pageSize }` in `client/src/lib/api/types.ts`). - **Why:** These fix the shared `ApiEnvelope`/`Paginated` types and the `services/{domain}` reference pattern every later frontend phase inherits. - **Proposed shape:** `{ isSuccess: boolean, statusCode: number, message?: string, requestId?: string, data?: T }` and `data: { items: T[], total: number, page: number, pageSize: number }` for lists. - **Status:** open ## REQ-002 — OTP length + expiry in RequestOtpResult — filed by frontend-phase-1-b2 — 2026-07-02 - **Need:** Add `codeLength` (int) and `expiresInSeconds` (int) to `RequestOtpResult`. - **Why:** The A2/B2 OTP screen renders one box per digit and (later) a code-expiry hint. `RequestOtpResult` currently exposes only `otpSent` + `resendAvailableInSeconds`, so the frontend hardcodes the box count (`OTP_CODE_LENGTH = 6`, inferred from the live 6-digit verify example, not the 4-box wireframe). Surfacing the length makes the box count contract-driven; the expiry lets us show "code expires in …". - **Proposed shape:** `{ otpSent: boolean, resendAvailableInSeconds: number, codeLength: number, expiresInSeconds: number }` - **Status:** open ## REQ-003 — Machine-readable error codes for verify_otp failures — filed by frontend-phase-1-b2 — 2026-07-02 - **Need:** A stable `code` on the 400 envelope for verify_otp that distinguishes wrong code vs expired code vs max-attempts lockout (e.g. `otp_invalid` | `otp_expired` | `otp_locked`), and — for lockout — a `retryAfterSeconds` (or `lockedUntil`) field. - **Why:** The OTP screen has explicit **wrong-code**, **expired-code**, and **max-attempts-lockout** states (per the phase spec), but the contract returns the same safe 400 message for all of them, so the frontend can't reliably tell them apart. Today it maps a lockout only when it sees the mock's `otp_locked` code and otherwise degrades to a generic "incorrect or expired" message. A stable machine code (kept generic enough to avoid account enumeration) would let the UI render the precise state + the unlock countdown. - **Proposed shape:** `{ isSuccess: false, statusCode: 400, message: "…", code: "otp_locked", data: { retryAfterSeconds: 60 } }` - **Status:** open ## REQ-004 — Confirm multi-role disambiguation (activeRole?) — filed by frontend-phase-1-b2 — 2026-07-02 - **Need:** Confirm whether `MeResult` will gain an `activeRole` (the user's currently-selected actor) for a user who holds **both** `customer` and `nurse`, or whether the client should keep owning that choice. - **Why:** The role router must pick one app for a dual-role user. Absent an `activeRole` in the contract, it currently uses the **intended role** carried from the login switch (A1 vs B1), defaulting to the family app. If the backend intends to persist a "current role", the router should prefer it. Also note: verify_otp returns `roles` but no user `id` (only `/me` has it) — fine for now (context id is hydrated from `/me`), flagging in case that changes. - **Status:** open