Appearance
0032 — Member Directory: field visibility and role-based access ​
Status: Accepted (revised 2026-06-22 — see §Amendment: Corrected member-detail model)
Date: 2026-06-22
ADO work item: AB#3074 (Platform Epic); story AB#3693 under Members feature
Deciders: Kristopher Turner (platform owner)
Context ​
ADR 0021 (Admin & Ministry Portal, capability area 5) established that the member directory exists and that admins have elevated access compared to ordinary members. It did not define what fields exist in a member's directory record, which fields each role can see, or which fields each role can edit on behalf of another member. ADR 0006 defines the six canonical Plane-2 roles but does not specify per-feature field-level visibility rules.
The result is a gap in the API contract for GET /members/:id and PUT /members/:id — the platform build (AB#3074) cannot proceed on the member detail surface without this decision.
Additionally, the current MembersPage.tsx renders a minimal inline panel (toggleSelected) that shows only name, role badge, status badge, email, and member ID. This is insufficient for a community member directory and is not a full-page route. This ADR establishes the correct model.
Decision ​
The member detail screen shows the same information to every authenticated role who views it. All roles see the same fields for a given member — there are no inline "extra" fields that appear only for admin or ministry leaders. Role-specific management capabilities (editing, suspending, role changes, pastoral oversight) are accessed via a "Manage" button that opens a separate management screen (
/app/members/:id/manage). The server enforces who may navigate to the management screen and what they may edit there.Adult member fields (everyone sees the same set): birthday, anniversary (if the member is married and has provided it), address, phone number, and email.
Child member fields: birthday only. No contact information is shown for minors.
A member viewing their own profile at
/app/profilesees all their own fields for editing regardless of their role.
Member detail fields (by member type) ​
| Field | Adult member | Child member |
|---|---|---|
| Display name / full name | ✅ | ✅ |
| Avatar / photo | ✅ | ✅ |
| Role label (read-only) | ✅ | ✅ |
| Family group name | ✅ | ✅ |
| Relationship in family | ✅ | ✅ |
| Birthday | ✅ (month/day) | ✅ (month/day only) |
| Anniversary (if married) | ✅ if provided | ❌ |
| Phone number | ✅ | ❌ |
| Email address | ✅ | ❌ |
| Address | ✅ | ❌ |
| Bio / personal note | ✅ | ❌ |
All fields are read-only on the detail screen for viewers who are not the profile owner. The "Manage" button is shown only to admin and ministry_leader — it is hidden for ordinary member callers.
Manage button and management screen ​
The "Manage" button appears at the top of the detail screen for admin and ministry_leader callers. It navigates to /app/members/:id/manage, which loads MemberManagePage:
| Role | Management screen capabilities |
|---|---|
admin | Edit any member's profile fields; change account status (suspend/reactivate); change role (links to Role Assignment page at /app/admin/roles); view audit log for this member |
ministry_leader | Pastoral oversight — view family context, pastoral notes (TBD scope); initiate approval-queue actions where applicable |
The management screen is a separate page — no management content appears inline on the detail screen. Server-side: GET /members/:id/manage returns 403 for callers whose role is not admin or ministry_leader; PUT /members/:id operations (edit, status, role) are also 403 for callers outside those roles.
Children in the directory ​
Child sub-accounts (accountType: 'child') show only their birthday on the detail screen — no contact information. Children do not appear in the general member directory listing for ordinary member callers. Children appear to:
- The child's own family head (primary account) — on the Family page
ministry_leaderandadmin— in their full member view
A child's /app/members/:id detail page is accessible to admin, ministry_leader, and the child's own family head. It renders the kid-only field set (birthday only).
Members list ​
The GET /members list page (/app/members):
- Sorted alphabetically by last name (A → Z)
- Family members grouped together under their family name
- Full-text search by name (
?q=parameter) - Clicking a member navigates to
/app/members/:id(replaces the current inlinetoggleSelectedpanel) - Children included for
adminandministry_leader; hidden frommember-tier callers - Role label is not displayed in the list row; it is visible on the member detail page only.
API contract ​
typescript
// Returned for all authenticated callers viewing a member
// Server returns the same shape regardless of caller role;
// admin/ministry_leader receive no extra inline fields.
type MemberDetailView = {
id: string;
displayName: string;
firstName: string;
lastName: string;
role: AppRole;
roleLabel: string; // "Community Leader" | "Ministry Leader" | … | "Member"
familyName: string;
relationship: 'primary' | 'spouse' | 'child';
accountType: 'primary' | 'spouse' | 'child';
// Adult-only fields (absent for child accounts):
birthdayMonthDay?: string; // "March 12" — month and day only for all callers
anniversary?: string; // "June 8" — present if married and provided
phone?: string;
email?: string;
address?: { street: string; city: string; state: string; zip: string };
bio?: string;
// Manage affordance:
canManage: boolean; // true for admin/ministry_leader; drives "Manage" button visibility
};The server returns canManage: true for admin and ministry_leader callers. The client renders the "Manage" button based on canManage — it does not inspect the caller's own role from a JWT claim (consistent with ADR 0006: role is never trusted from the client).
Write contract (/members/:id/manage) ​
PUT /members/:idwith profile fields — accepted foradminon any member; accepted formemberon their own record only (403 otherwise).PUT /members/:idwithrole—adminonly; 403 for all other roles.PUT /members/:idwithstatus(suspend/reactivate) —adminonly; 403 for all other roles.ministry_leaderhas no write access to other members' profile data via this endpoint. Their write authority is the Approval Queue and pastoral-scope actions on the management screen.
Amendment: corrected member-detail model (owner, 2026-06-22) ​
The original decision used a tiered inline field model — different fields appeared inline for member, ministry_leader, and admin based on their role. The platform owner clarified (2026-06-22) that this model is wrong:
Everyone sees the same detail screen. Admin and minister do NOT get extra inline fields — they get a "Manage" button that opens a separate management screen. This keeps the detail screen simple and puts management capabilities where they belong.
Additionally, the original model exposed full birthday (including year) to pastoral/admin tiers for all members. The corrected model restricts:
- Kids: birthday only, no other fields.
- Adults: birthday shown as month/day only on the public detail screen to all viewers. The full year is accessible to
adminon the management screen, not on the shared detail screen.
The design doc (docs/internal/design/member-directory-rbac.md) and the three role-specific mockups (member-detail-*.html) have been revised to reflect this model.
Alternatives considered ​
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Single detail screen + Manage button (chosen) | Simple, consistent view for all roles; management capabilities scoped to a separate surface | Requires an additional management page | — chosen |
| Tiered inline fields (original model) | Role-differentiated detail in one screen | Complexity grows as roles multiply; "extra" fields visible inline feel invasive | Rejected by owner 2026-06-22 |
| All fields visible to all authenticated members | Simplest API | Exposes contact info and minors' data to every member | Privacy; rejected |
| Inline panel only (status quo) | Already built | No route; insufficient fields; not discoverable via URL | Insufficient for directory use; rejected |
Consequences ​
Positive ​
- Every role sees the same clean detail screen — no complex conditional field rendering.
- Admin and minister management capabilities are cleanly separated onto their own screen.
- Children's privacy is enforced: only birthday shown, no contact information exposed.
- The
canManageflag keeps role logic server-driven; the client does not branch on role.
Negative / trade-offs ​
- Two screens instead of one (detail + management). Mitigation: management screen is accessed via a single button; the detail screen is simple and consistent.
- The
phonePubliccolumn from the original model is no longer needed for tier control. Phone is simply always shown for adult members. If member-controlled phone visibility is needed in the future, that is a separate ADR. - The inline
toggleSelectedpanel inMembersPage.tsxmust be replaced with navigation to/app/members/:id.
Risks ​
- Management screen access control — if the
/manageroute is not server-guarded, amember-tier user could navigate directly to the URL. Mitigation:GET /members/:id/managereturns 403 for callers outsideadmin/ministry_leader; the client router hides the route unlesscanManage: trueis set in the detail response. - Kid detail visibility — if a child's
/members/:idroute is not gated, peer members can view it. Mitigation:GET /members/:idreturns 403 formember-tier callers viewing a child account that is not in their own family.
References ​
- ADR 0006 — Two-plane RBAC — role model; admin-only write operations
- ADR 0007 — Account & Family-Group identity — child sub-accounts; family group composition
- ADR 0021 — Admin & Ministry Portal — capability area 5: member directory administration; amended to reference Manage screen
- ADR 0033 — Platform Foundation UI & View Inventory — view inventory; M2 (adult detail), M3 (kid detail), M4 (admin manage), M5 (minister manage)
- Design doc — Member Directory RBAC — field design, API types, mockup reference (revised to corrected model)
- ADO: Epic AB#3074 (Platform build); Story AB#3693 (re-scoped to corrected model)