0014 — Separate the user details form from the athlete view
Status: Accepted · Date: 2026-09-29
Context
The portal's user drawer served two jobs in one tabbed surface:
- a coaching view of a training subject — Summary, Goals, Activity — which a coach opens often, to see how an athlete is doing;
- the account form — name, email, role, body data, tags, photo, assignments — which an admin opens occasionally, to maintain the account.
Mixing a read-mostly view with a form cost clarity and correctness:
- The header Save applied to some tabs and not others. It was hidden on Goals, which needed its own "Save goals" bar, and shown elsewhere only for a pending photo or assignments.
- An unsaved Details edit could sit in the drawer while the coach browsed the coaching tabs, and the Goals save needed extra rules so it would never write a half-finished Details edit.
- For non-training users (coaches, facilities) the drawer was the form alone, behind a tab strip with one real tab.
Create mode already showed the form only.
Decision
Two surfaces, each with one job:
- The athlete drawer (
users/athlete-drawer.tsx) — Summary · Goals · Activity, for training subjects only. A read-only header shows the photo, name, role · age · body weight and tags. Goals keep their own save. - The details drawer (
users/user-drawer.tsx) — the account form, with assignments as a section, for creating a user and editing any user. Draft mode (roster review, migration) is unchanged.
Editing stays one tap from the person: the athlete drawer's "Edit"
opens the details drawer stacked above it (a nested DrawerStack), and
closing it returns to the same tab. A non-training user's row opens the details
drawer directly. The Users table also gets a per-row "…" with "Open profile" and
"Edit details". The ?edit=<id> deep link opens the details drawer; ?user=<id>
opens a user as a row tap would.
Account actions (unassign, archive, delete, and recalculate for training
subjects) are one shared menu (useUserAccountActions). The form model
(user-form-model.ts) and the API writes (user-persistence.ts) are shared, so
both surfaces write and invalidate identically.
Consequences
- Each surface has one save model: the details drawer has one Save with a discard guard; the athlete drawer only stages goal targets.
- The Goals save sends the athlete drawer's fetch-before-edit read with the new targets. A details save re-runs that read, so the Goals save can't write the pre-edit details back.
- Typing in the form no longer re-renders the coaching panels.
- Two drawers can be open at once (details above athlete). Escape and Back close the top one first (the stack's LIFO arbitration).
- The "Details" and "Assignments" tabs are gone. Testers and docs that walked through them follow the new entry points (user-drawer-data-flow.md).