# 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-005 — Patient `relation` + `conditions` fields — filed by frontend-phase-2-b3 — 2026-07-02 - **Need:** Add two fields to `PatientDto` and the `patients/create` + `patients/update` bodies: 1. `relation` (`parent`|`spouse`|`child`|`self`, nullable) — the care-recipient's relation to the payer. 2. `conditions` (string[] of stable codes, e.g. `elderly`/`post_surgery`/`diabetes`/`mobility`/`dementia`). - **Why:** The A3 onboarding step captures the relation, and the A4 form + E1 patient cards show condition chips. Neither field exists on the wire `PatientDto` (only `initialMedicalNotes` free-text). The client currently augments them behind the `services/patients` seam (the mock persists them; `USE_PATIENTS_MOCK=true`) and drops them on the real path. Adding the columns lets the client flip the flag to the live endpoints. - **Proposed shape:** `PatientDto { …, relation: string|null, conditions: string[] }`; same fields accepted on create/update. Enum for `relation`; `conditions` a stable code list (could also be a normalized child table). - **Status:** open ## REQ-006 — Avatar / object-storage upload route (nurse & customer) — filed by frontend-phase-2-b3 — 2026-07-02 - **Need:** A multipart image-upload endpoint backed by `IObjectStorage` that returns a stored URL, plus an `avatarUrl` field on `NurseProfileDto` (and later `CustomerProfileDto`). e.g. `POST api/v1/nurse_profiles/avatar` (multipart/form-data) → `{ url }`, and persist `avatar_url` on the profile. - **Why:** The B7 nurse profile bootstrap and the customer profile both take a profile photo. The b3 contract has no avatar field or upload route, and the client fetch layer is JSON-only (can't send multipart). The client mocks this behind the `services/profiles` seam (`uploadAvatar` returns an object URL). The real `profilesClientApi.uploadAvatar` throws `501` until this lands. - **Status:** open ## REQ-007 — Customer name + preferred-language update — filed by frontend-phase-2-b3 — 2026-07-02 - **Need:** Either add `firstName`/`lastName`/`preferredLanguage` to the `customer_profiles/upsert` body + `CustomerProfileDto`, or confirm the customer name is only ever set elsewhere (and how). `MeResult` exposes `firstName`/`lastName` read-only with no update endpoint; `CustomerProfileDto` carries only the emergency contact. - **Why:** The customer profile screen edits first/last name + preferred language alongside the emergency contact. Absent a wire field/endpoint, the client augments name/language behind the `services/profiles` seam (mock-persisted; the real upsert sends only the emergency contact). Confirm the intended home for these so the client stops augmenting. - **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