Problem
Access control is scattered and inconsistent across the system. Works have read_club/edit_club, annotations just got is_private: bool, links have no per-element ACL, and each feature has its own ad-hoc UI. We need a unified, reusable component for club-based access control.
Proposal
Build a reusable ClubSelector React component with adapters for each use case. Any element that needs access control uses the same UX pattern.
Component: ClubSelector
A dropdown/combobox that:
- Shows current access level as a label ("Public", "Private", "Members of X")
- Lists clubs the current session belongs to (fetched via
club_who_am_i)
- Supports single-club selection (read/edit club) or multi-club (annotation visibility)
- Returns selected club ID(s) to the parent via callback
- Includes a "Public" option (no club restriction) and "Private" option (creator only)
Hook up via adapters:
| Feature |
Current state |
Wire ops needed |
| Work read club |
DocumentSettings has basic UI |
work_set_read_club (exists) |
| Work edit club |
Same panel |
work_set_edit_club (exists) |
| Work history club |
Separate setting |
work_set_history_club (exists) |
| Annotation visibility |
Just added is_private: bool |
Extend to visible_to_clubs: Option<Vec<BeId>> |
| Link visibility |
No per-link ACL |
New: link_set_visibility |
| Club default clubs |
ClubSetDefaultReadClub etc. |
All exist |
Backend changes needed:
-
Generalize annotation visibility — replace is_private: bool with visible_to_clubs: Option<Vec<BeId>> on OtreeAnnotation. Mapping: None = public, Some([]) = private (creator only), Some([ids]) = visible to specific clubs. Backward compatible: is_private: true deserializes to Some([]).
-
Add annotation_set_visibility op — toggle/change visibility on existing annotations.
-
Add link_set_visibility op (optional, future) — per-link access control.
Existing infrastructure:
ClubGet, ClubNames, ClubWhoAmI ops already return club lists and membership
WorkSetReadClub, WorkSetEditClub, WorkSetHistoryClub ops already exist
- Clubs have
display_name(), name(), membership lists
- The
KeyMaster system resolves session authority to club IDs
- Named clubs with passwords for authenticated users
- Personal clubs for individual identity
Frontend:
ClubSelector component (~150 lines, reusable)
- Adapter for each panel (DocumentSettings, AnnotationPanel, LinkPanel, etc.)
- Fetch club list via existing
clubWhoAmI() client method
- Single-select vs multi-select modes
- Intuitive labels: "Everyone", "Just me", "My team", custom club names
Scope
- Effort: M (frontend-heavy, backend ops mostly exist)
- No breaking changes to existing wire protocol
- The ClubSelector can be built first, then adapters added incrementally
Related
Problem
Access control is scattered and inconsistent across the system. Works have
read_club/edit_club, annotations just gotis_private: bool, links have no per-element ACL, and each feature has its own ad-hoc UI. We need a unified, reusable component for club-based access control.Proposal
Build a reusable ClubSelector React component with adapters for each use case. Any element that needs access control uses the same UX pattern.
Component:
ClubSelectorA dropdown/combobox that:
club_who_am_i)Hook up via adapters:
DocumentSettingshas basic UIwork_set_read_club(exists)work_set_edit_club(exists)work_set_history_club(exists)is_private: boolvisible_to_clubs: Option<Vec<BeId>>link_set_visibilityClubSetDefaultReadClubetc.Backend changes needed:
Generalize annotation visibility — replace
is_private: boolwithvisible_to_clubs: Option<Vec<BeId>>onOtreeAnnotation. Mapping:None= public,Some([])= private (creator only),Some([ids])= visible to specific clubs. Backward compatible:is_private: truedeserializes toSome([]).Add
annotation_set_visibilityop — toggle/change visibility on existing annotations.Add
link_set_visibilityop (optional, future) — per-link access control.Existing infrastructure:
ClubGet,ClubNames,ClubWhoAmIops already return club lists and membershipWorkSetReadClub,WorkSetEditClub,WorkSetHistoryClubops already existdisplay_name(),name(), membership listsKeyMastersystem resolves session authority to club IDsFrontend:
ClubSelectorcomponent (~150 lines, reusable)clubWhoAmI()client methodScope
Related
is_privateflag added, would be replaced by this