Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesManaged MembersManaged Members — UI/UX Design Specification

Managed Members — UI/UX Design Specification

Ticket: TKN-382  Mode: Operate. The visitor is completing a task — enroll a child, book a lesson, resolve a missed session, decide a guardianship request. Scanability, consistency, native expectations and the real usage scene outrank expression. Brand lives in precise details. Binding: apps/web/DESIGN.md (Deep Ocean) is the system; apps/web/PRODUCT.md is the product truth. This file does not restate them — it records the decisions specific to these surfaces. Where the two conflict, DESIGN.md wins. Revision 2. Aligned with data-model.md revision 2: a managed member is a users row with kind = 'managed' — meaning a responsible adult may act for this person. It does not mean “no email” and does not mean “no login”; those are orthogonal, and the designs below must not assume their absence. A teenager with their own login whose parent still books and pays is a valid, expected state. Terminology, used throughout:

ConceptIn this spec and in codeEnglish UIHebrew UI
The child or dependentmanaged membermanaged memberחשבון מנוהל
The adult holding authorityguardian (guardianships)responsible adultאחראי

This spec says “guardian” when it means the role or the code concept. Wherever it specifies copy a user reads, the word is “responsible adult” / אחראי. אפוטרופוס appears only in legal artefacts and the staff record (Q-01).

The usage scene, which decides almost everything

A parent, standing, one hand, phone, in a corridor outside a pool, three minutes before a lesson, in Hebrew, with a second child pulling at them. Or: a front-desk staff member at 16:58 on a Sunday with six families queuing at class changeover.

Neither of them is evaluating software. Both are mid-task and interrupted. That rules out choreography, tours, modals-as-first-thought, and anything that needs two hands or a second read. It also means the acting context must be legible at a glance — the single most expensive error in this feature is a guardian booking the wrong child, and the most common one is not knowing which child they are looking at.

Per operate.md’s product-slop test: the bar is earned familiarity. A parent who has used Boostapp or Arbox must trust this immediately. Strangeness without purpose is the failure mode, not flatness.


1. The acting context — the feature’s signature problem

Every guardian surface answers “who am I acting for?” before it answers anything else. Three candidate patterns:

PatternVerdict
Profile switch — become the child, as Boostapp does («מעבר לחשבון הלקוח הקטין»)Rejected. It is impersonation, and RFC 8693 draws exactly the line we care about: under impersonation the actor “is indistinguishable from B”. A guardian must never be indistinguishable from their child. It also loses the attribution that decides every co-parent dispute, and it makes “which parent cancelled this?” unanswerable.
Flat list of everyone — all children’s sessions in one streamRejected as the default. A verified user complaint names this exact failure: «אני צריכה בכל פעם לסנן לפי סוג השיעור וזה מאוד לא נוח» — having to filter every single time. A household of three in a multi-program club is unreadable.
Named delegation context — the guardian is always themselves, acting for a named member, with a persistent switcherChosen.

The member switcher

Lives in the member shell header, beside OrgSwitcher and built as its twin: the same capsule, height and type, with a 32pt person tile (photo, else initial on the member’s hue) where the org shows its logo. It appears for any adult with at least one confirmed child — the earlier “below two managed members, no chrome” rule is superseded, because the header must always say whose data is on screen (behavior.md §4.7). On a narrow phone the org collapses to its logo so the viewed person stays legible.

  • Rest: the active member’s avatar (28px, rounded-full) + first name at Manrope 600 / 14px, with a Lucide chevrons-up-down at 14px in muted-foreground. Row height 36px, rounded-lg, no border at rest.
  • Hover: ink tint at 4%, matching the rail-row convention in DESIGN.md. No lift — the 2px lift belongs to filled buttons only.
  • Open: DropdownMenu, each row an avatar + name + a steel caption carrying the one fact that disambiguates (age or program, e.g. גיל 7 · שחייה מתחילים), checkmark on current. The guardian’s own row sits first, separated by a hairline, labelled with their own name — never “Me”, because a guardian who does not train still needs to find their billing.
  • Switch behaviour: client-side context change, not OrgSwitcher’s window.location.assign full reload. Switching child is frequent and lightweight; switching tenant is not. Re-scopes schedule, profile and forms via the query key.

Making the context unmistakable without shouting

The risk is a guardian glancing at a schedule and booking for the wrong child. The mitigation is redundant, quiet signalling rather than one loud banner:

  1. The switcher itself, always visible in the header.
  2. A 1px start-side rail in a per-member hue on every card that belongs to a managed member, with the member’s initial in the rail cap at 10px. Colour is assigned deterministically from the member’s user id across a fixed 6-step set derived from the existing token palette — not random, stable across sessions and devices.
  3. The member’s first name in the section heading, not a generic “Schedule”.

On the per-member hue. DESIGN.md is strict: teal is action and selection, green is settled, violet is second-tier and AI, mint is reserved for PR/streak/today. A seventh decorative colour family would break the One Teal Rule and steal meaning from the semantic set.

Resolution: the hue appears only as a 1px rail and a 10px initial cap — never as a fill, a badge, a chip, or text colour. It is an identity tag at the scale of a hairline, in the same visual register as the existing border hairline, and it never competes with teal for attention. If the 6-step set cannot be derived from existing tokens at AA against both grounds, drop the hue entirely and rely on the initial in a neutral cap. Identity legibility does not justify fracturing the colour system.


2. Guardian: the family hub

The landing surface for a guardian, under Profile at /(member)/profile/family (/family redirects there; there is no Family tab). Answers three questions in one screen, in this priority order:

  1. What needs me? — expiring health declaration, unsigned waiver, failed payment, waitlist promotion awaiting confirmation, a make-up credit expiring this month.
  2. What is next? — the next session per child, chronological across the household.
  3. Who is in my household? — per-child state and the route into each child’s detail.

Structure

A single column at max-w-lg (the member shell’s existing cap), stacked:

  • Action block. Zero to three items. Each is an Inline notice (16px radius, hairline, card fill, 12×16px padding) with the member’s rail, one sentence of plain copy, and one action as an underlined ink link — not a button. Multiple buttons in a list is how a “needs attention” block becomes a wall of teal. If more than three items exist, show two plus “ועוד N” as a link to a full list.
  • Next up. A chronological list across all children, each row: time, class, coach, member rail + name. Today’s rows carry the mint “today” treatment already reserved for it in DESIGN.md. Not a card per child — a parent’s mental model at 07:40 is “what is today”, not “tell me about each child in turn”.
  • Household. One tile per managed member: avatar, name, age, current program, plan state as a Badge. Tapping enters that child’s context and navigates to their profile. Age is a fact on the tile, never a countdown — see §7.

The empty state

A guardian with no children yet gets an empty state that teaches the interface, per operate.md. One line of what this is for, one primary teal action (הוספת ילד), and — critically — a sentence stating that the child does not need an email address or a phone. That single sentence removes the question every parent arrives with, and it is the thing every incumbent makes you discover by trial.


3. Guardian: add a child

A StepMarker + hairline-rail sequence in the setup-sheet geometry (42rem column, pinned footer with one action, state sentence at the start side) — reused because this is an ordered, completable sequence, which is precisely what that signature component exists for in DESIGN.md. Reuse also means a parent who did owner onboarding recognises it.

Steps: health declaration → waiver (incl. youth parental consent) → plan → first booking. Staff create the child (§3.1 of behavior.md); the adult reaches this sequence right after confirming the link.

Cabined progression, not a gate

Per pattern P1 (research.md): the managed member is created and appears on the roster after step 1. Steps 2–5 unlock capabilities. The flow can be abandoned at any point without losing the child, and the remaining steps surface in the family hub’s action block.

This is the difference between a parent at a front desk finishing enrollment and a parent at a front desk giving up. It also directly avoids the verified Arbox failure — “once you sign, it does not let you go further, no button for DONE, so it’s incomplete and I can not use the app to book further sessions, I have to call the place each time” — which is a dead-end created by a gate with no exit. Every step must have a visible way out that preserves progress.

Signing on behalf — the one screen that must not be slick

This screen creates legal liability for the person reading it. It is the only place in the feature where friction is a feature.

  • The verbatim attestation is displayed in full, at body size, not collapsed behind “read more”, not summarised. It is stored verbatim on the grant, so what is shown and what is stored are the same string by construction.
  • The guardian types their full name — not a checkbox. Typing is a deliberate act, and signerFullName is captured for the signature record.
  • The relationship is a radio-group, not a free-text field, because guardianRelationship is structured.
  • The child’s name appears in the attestation sentence itself, rendered from data: “אני מאשר/ת שאני ההורה או האפוטרופוס של נועה לוי…”. A form that does not name the specific child is the form עולם המים has today, and it is the defect we are fixing. The attestation is a legal artefact, so it keeps the statutory אפוטרופוס rather than the UI term אחראי (Q-01).
  • The health declaration is answered in the third person about the child, never the second person. Arbox asks «האם את/ה סובל/ת מבעיות לב?» to a parent answering for a seven-year-old. Copy must read «האם נועה סובלת מ…». This is a translation-layer detail that decides whether the artefact is evidentially coherent.

Per operate.md, no modal. This is a step in a flow, on its own surface.


4. Guardian: booking and make-up in a child’s context

Extends ScheduleListView and SessionDetailDrawer rather than forking them — consistency across screens is a virtue here, not a limitation.

  • The DaySelectorStrip gains per-member booking dots in the member’s rail hue, so a parent sees at a glance which days are covered for whom.
  • Eligibility reasons are per child and stated as the reason, not the rule. blockReasonLabel() already renders no_credits, overlap, daily_limit and friends; the child-context copy names the child: “נועה כבר רשומה לשיעור בשעה הזו”.
  • Sibling collision on the last seat (blindspot B-08) is the moment most likely to read as a bug. One remaining seat and two children needing it gets an explicit inline notice: “נשאר מקום אחד. אפשר לרשום ילד אחד בלבד לשיעור הזה.” — then the choice. Not a silent failure on the second attempt, and not a modal.
  • Payment block never blames the child. When the guardian’s card fails, the child’s membership blocks booking (bookings.service.ts:141-158). The copy is “הרישום מושהה כי התשלום לחודש הזה לא עבר” with the action routed to the payer — and if the viewing guardian is not the payer, it names who to speak to rather than offering an action they cannot take. A nine-year-old must never be rendered as being in debt.
  • Make-up credits (TKN-385’s surface, seamed here) show the expiry, because the pilot customer’s own policy time-boxes them to the calendar month. A credit with an invisible expiry is a complaint.

5. Guardian: capabilities and the second guardian

Capabilities are thirteen booleans (behavior.md §2). Thirteen switches is a settings panel, not a decision — so the UI presents three presets that write the booleans, with the full set behind a disclosure.

PresetWrites
שותף מלא (full partner)everything
רישום בלבד (booking only)schedule, book, attendance, progress, messages, notifications — not pay, subscription, forms, health, guardians, escorts
צפייה בלבד (viewer)schedule, attendance, notifications

The presets are a copywriting device, not a data model. The disclosure uses the existing selected-tile disclosure animation (grid rows 0fr → 1fr, 200ms).

canMessageStaff is written by the presets so the grant is complete when messaging arrives, but it has no surface in phase 0 — there is no messaging for managed members at all (see §7).

“Booking only” is the default for an invited second responsible adult, and it is the divorced-parent case the strongest Israeli competitor cannot express at all — Boostapp fuses guardian and payer into one לקוח משלם. This preset is the product’s clearest competitive edge, so it should be the one the UI makes easiest.

The capability ceiling, published

A link from this surface to a static “what a responsible adult can and cannot see” page (“מה אחראי יכול ולא יכול לראות”), in he/en/ru (pattern P11). It enumerates the negative list explicitly. This reduces support load, is a transparency artefact the member can rely on, and forces us to have decided the boundary rather than discovered it.

Revocation

An alert-dialog — one of the few places a modal is correct, because it is destructive and needs to interrupt. It must state, in plain copy and before the confirm:

  1. What the revoked guardian loses, and when (immediately).
  2. What happens to anything they paid for — entitlement stays with the child, and the operator can transfer or refund (Q-08, decided). Silence here produces a chargeback.
  3. That their own payment receipts remain available to them.

6. Staff: the household record and the decision queue

Dashboard shell, sidebar, full density. Staff want a table, not cards — operate.md’s density permission applies, and a front desk at changeover is the most time-pressured surface in the product.

Household record

Extends the existing member detail. Adds a “who may act for whom” table: guardian, relationship, capabilities as a compact icon row, source (self-asserted / guardian-approved / staff-approved / import), and when it was attested. source is visible because “how did this person get access?” is the first question in any dispute.

Health artefacts show expiry dates, not booleans — a declaration valid until 12.03.2027 and one that expired last week are operationally different facts, and member_profiles.medicalCert being a boolean is why staff cannot see that today.

Phase 1 builds a smaller version: the household section on member detail (apps/web/src/components/overview/members/member-household-section.tsx) lists a child’s responsible adults, or an adult’s children, with relationship, primary marker and pending/active status, each linking to the other’s record. Capabilities, source and attestation date are not shown yet, and expiry-dated health artefacts are not surfaced (WP-15).

The guardianship decision surface

This is where an adult asks for access to a child. behavior.md §3.2 is explicit that it is a decision surface, not an approval button, and the UI must carry that weight:

  • The requester’s verbatim assertion, quoted, not paraphrased.
  • Whether the existing primary guardian was notified, when, and whether they responded — including an objection if one was lodged.
  • Approve and reject as equal-weight choices. Not a teal primary next to a quiet link; that is a nudge, and nudging staff toward approving family-authority claims is exactly wrong. Two outline buttons, and the surface records who decided.

Two queues

  • Orphaned managed members — no active primary guardian (behavior.md §7.4). An incident state. High priority, always visible when non-empty.
  • Import reconciliation — children whose guardian could not be resolved (TKN-406 (WP-11)). Never auto-linked, never dropped.

Both are empty most of the time, so both collapse to nothing rather than showing a congratulatory empty state.

Phase 1: neither the decision surface nor the two queues has a screen. The request/approve routes and the reconciliation queue exist in the API only (behavior.md §3.2, §3.4).


7. What the design deliberately does not do

  • No gamification in any child-facing view. Streaks, scarcity, social comparison and “ask your parent” nudges are not merely off-brand — Israeli law forbids marketing to minors that exploits impressionability or encourages children to pester their parents (research.md C7, blindspot B-03). Mint stays reserved for PR/streak/today in adult views.
  • No child avatars on any public or semi-public surface. Managed members default off the minisite (R-07 — defence in depth; the minisite shows only coaches today).
  • No progress-photo surface for managed members at all. Not hidden behind a toggle, not behind consent, not available to staff: the endpoint 403s (R-08), and there is no upload control, empty state or placeholder that implies one could exist.
  • No messaging surface in phase 0. No thread, no inbox entry, no “message the coach” affordance in a child’s context (Q-05). Notifications about the child reach responsible adults through ordinary notification routing — that is fan-out, not messaging.
  • No age countdown and no automatic transitions. Nothing on any surface anticipates a birthday: no “turns 16 in N days”, no majority timeline, no scheduled handover. Access never changes because a date passed (Q-04). Giving a member their own login is out of phase 0; when it arrives it is an invitation the responsible adult chooses to send — available only where the org has enabled selfAccessMinAge, which defaults off — never a scheduled event the UI counts down to.
  • No “become this child” mode. See §1.
  • No onboarding tour. A parent in a corridor is not taking a tour.
  • No dashboard-style household analytics. A parent wants to know if their kid is going to the lesson, not their attendance rate. Adding a chart here would be product slop.

8. Accessibility, direction and theme

Non-negotiable and verified per surface, not assumed:

  • RTL is the default case. Hebrew is the primary locale. Logical properties only (ps-, pe-, start-, end-, border-s, text-start); directional icons flip with rtl:rotate-180; DropdownMenu and RadioGroup receive dir so arrow keys follow reading order. The member rail is a border-s, so it moves side with direction automatically — this is the reason to implement it as a border rather than an absolutely positioned element.
  • Every font-display takes rtl:font-[family-name:var(--font-display-he)] and rtl:tracking-normal. Clash Grotesk has no Hebrew; without the swap a heading falls to a system face.
  • Light and dark both first-class. Unselected tile edges raise to white at 14% alpha in dark.
  • Focus: the 3px ring in the ring token at 50% plus a ring-coloured border, on every interactive element without exception.
  • Every Radix overlay carries a visually hidden description (DrawerDescription, SheetDescription, DialogDescription).
  • Reduced motion honoured on every transition; motion-reduce:transition-none authored alongside each one, with transitioned properties listed explicitly rather than transition-all.
  • The member rail hue is never the only carrier of meaning — the name and initial always accompany it. A parent with colour-vision deficiency, or a greyscale screenshot in a support ticket, must lose nothing.
  • The Israeli accessibility complaint found against an incumbent — «האפליקציה כלל לא נגישה לעיוורים המשתמשים בקורא מסך voice over» — is a live obligation here, not a courtesy.

9. Component inventory

Everything below already exists in apps/web/src/components/ui/. No new primitive is required, which is itself the correctness check on this design: avatar, badge, button, card, checkbox, collapsible, dialog, alert-dialog, drawer, dropdown-menu, form-field, input, label, radio-group, scroll-area, separator, sheet, skeleton, switch, table, tabs, tooltip, plus StepMarker and the hairline rail from components/onboarding/step-marker.tsx.

New composites, all assembled from the above: MemberSwitcher, MemberRail, HouseholdTile, ActionNotice, AttestationPanel, CapabilityPresetGroup, GuardianDecisionCard.

10. States every new component ships

Per operate.md, none of these may be half-built: default, hover, focus, active, disabled, loading (skeleton mirroring the real geometry, never a centred spinner), empty (teaching, not “nothing here”), and error. The Not-Ready Action pattern from DESIGN.md applies to every form submit in these flows — the primary stays clickable with aria-disabled and routes the click to the first missing field, rather than greying into a dead control.