Skip to main content

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).