Skip to content

feat: member profiles, per-viewer display config, and tracks owning the schedule - #73

Merged
ethnjs merged 148 commits into
mainfrom
feat/member-profiles
Sep 12, 2026
Merged

feat: member profiles, per-viewer display config, and tracks owning the schedule#73
ethnjs merged 148 commits into
mainfrom
feat/member-profiles

Conversation

@ethnjs

@ethnjs ethnjs commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

Three interlocking features, in the order they had to be built:

  1. Member profiles — everything a member already told the tournament through onboarding forms is visible on the TD-facing panel and a full member page, with an opt-in 18+/21+ disclosure and self-service editing.
  2. Tracks own the schedule — a track gains its own dates, venue, and divisions, and the tournament derives a list of dates instead of a span. A two-day regional running Day 1 at UCI on 02/13 and Day 2 at Northwood on 02/20 is two days, not an eight-day span, and nothing before this could say that.
  3. Per-viewer display config — config moves to the membership, so two coordinators reading the same roster stop overwriting each other's view.

Breaking: the tournament loses its own location, dates, and divisions.

Closes #69, #71, #72

What changed

Backend

Models and migrations

alembic upgrade head required — 8 revisions.

  • Tracks gain is_primary, start_date/end_date, university_id or location, and division. (eb2e5d683865)
  • Shifts gain track_id, backfilled from the tournament's primary track. (eb2e5d683865)
  • New tournament_event_tracks join table, replacing track derivation through shifts. (eb2e5d683865)
  • New age disclosure toggles on the tournament and consent status on the membership. (e38f3f7cddb9, 657302df78d5)
  • Lunch write-through swaps date for track_id; event preferences gain track_id and drop key. (eb2e5d683865)
  • Destroys data: tournament drops start_date, end_date, university_id, location, and division. (eb2e5d683865)
  • Destroys data: events drop start_time and end_time — an event's schedule is now the days its shifts fall on. (eb2e5d683865)
  • Destroys data: reserved form fields revert to plain questions (labels, descriptions, and option labels are kept) and the four write-through tables are emptied outright. Presets and member responses are not migrated; re-bind presets to tracks by hand. (eb2e5d683865)
  • Destroys data: saved display configs are reset, then the column moves from tournaments to tournament_memberships rather than being fanned out. Everyone starts from defaults. (c7d1e93a5b02, d3f8a1c74b26)

Schemas and routes

  • Membership serialization and eager loading are driven by a fields query with named groups (field_groups.py), collapsing the slim/full response split into one base schema.
  • Breaking: tournament responses no longer carry location, start/end date, or divisions — a derived date list replaces them.
  • New self-service routes for a member's own availability, lunch, event preferences, and track status.
  • New full member page route, plus /members/me for self-reads.
  • New display config read/write on the membership, gated on manage_members.
  • New roster filter-options endpoint; roster filtering moves server-side.
  • New route listing a user's tournament memberships visible to the caller.
  • New public read of the shift, track, and event catalog — it carries nothing confidential.
  • The member search route folds into the roster list route as a query param.
  • Track delete responds with the blocking shifts, events, and fields plus the count of responses that would be lost.
  • The role catalog is gated to manage_members or manage_roles.
  • A referenced person embeds only a name and roles.

Logic

Members and consent

  • Age flags require both tournament collection and member consent; the age filter is gated on consent so withheld flags can't leak through filtering. DOB is never shared.
  • Declining consent marks the membership inactive without deleting anything; consenting later restores it.
  • Removing a member now deletes their responses to that tournament's forms.

Tracks

  • A primary track requires dates, venue, and divisions; a cosmetic track (test writing, review) leaves them empty.
  • Every tournament needs at least one live primary track — deleting or demoting the last one is rejected.
  • Track deletion replaces the archive toggle: an unreferenced track deletes immediately with member data cascading, a referenced one goes pending-delete.
  • Repointing the last reference to a pending-delete track purges it automatically.
  • Both track transitions write audit entries.
  • Shifts validate against their own track's dates; attaching a shift to an event auto-links its track.
  • The event tournament-bounds check is deleted rather than repointed, since events no longer carry times.

Forms and field keys

  • Form field keys re-key from dates to tracks for availability, lunch, and event preference; event preference collapses to one question per track.
  • Availability's wrong-day check becomes a wrong-track check; event-preference options validate against the events linked to that track.

Display config

  • Display config reads lenient, writes strict — a dangling item or unknown surface reads back as-is, a write rejects unknown surfaces, namespaces, columns, and filter keys.
  • Filter values stay opaque, so a filter for a deleted track is inert rather than a 422 on every save.
  • First/last day survive internally for age derivation, auto-archiving, and sorting, but never appear in a response.

Frontend

Member profile

  • New full member page with availability, lunch, event preferences, track statuses, age flags, and custom responses grouped by source form.
  • Member panel and member page now render the same components — extracted PanelField, SectionHeading, and shared field primitives.
  • Availability renders as a per-day timeline bar with an hour ruler, shift badges, and hover popovers.
  • Blocking consent modal appears for existing members the moment a TD turns on age disclosure.
  • Member edit page is limited to forms they actually answered and matches each form's question layout; a declined track locks its questions rather than leaving them editable.

Tracks

  • Tracks settings page removed and folded into general settings, which loses its own location, date, and division fields.
  • Advanced multi-track tournament creation added alongside the existing simple mode.
  • Form builder swaps day pickers for track pickers and greys unlinked events with a reason.
  • Every read of the tournament's start/end date was rewritten against the date list.

Display config and roster

  • Display config modal split into table-column and panel-section editors, behind a shared useDisplayConfigDraft hook.
  • Roster filters and sort move out of localStorage into display config; the events tab still uses localStorage.
  • Sorting stays client-side; the server stores and validates it.
  • Roster table renders as one subgrid and dashboard cards lay out as masonry, both for layout performance.
  • The open member panel is kept in the URL so a refresh returns to it.

Shared UI

  • New: PillMenu, MasonryGrid, FloatingEditButton, TrackFields/TrackDayPicker/TrackEditSection.
  • Fixes locked state on RadioList/CheckboxList, and a RadioCircle that stays round in any layout.

Out of scope

  • Migrating existing presets and member responses to track keys — dev data only, re-bound by hand.
  • Migrating saved display configs to the new shape.

Test plan

  • pytest passes locally
  • Migration included for every model change
  • Clicked through the UI change in a browser

Automated

  • New test_display_config.py — lenient reads, strict writes, per-viewer isolation, filter round-trip.
  • New core/test_field_groups.py — the fields query groups.
  • Extended test_memberships.py — consent gating, self-service edit routes, field selection, server-side filters.
  • Extended test_tracks.py — primary/cosmetic rules, delete vs pending-delete, auto-purge.
  • Extended test_shifts.py, test_events.py — track scoping, event/track links, dropped time bounds.
  • Extended test_forms.py, test_form_validation.py, test_form_write_through.py — track-keyed presets, one event-preference question per track, re-scoped write-through.
  • Extended test_join.py, test_audit.py, test_roles.py, test_core.py.

Manual

  • Turned on age disclosure with existing members — blocking modal appears, declining pauses access without data loss, consenting restores it.
  • Created a two-track tournament in advanced mode with different dates and venues — dashboard card and overview report two days, not a span.
  • Deleted a track referenced by a shift — went pending-delete with blockers named; repointing the shift purged it.
  • Saved different columns and filters as two coordinators — views stayed independent across a browser change.
  • Edited own availability and lunch from the member edit page — write-through landed.

ethnjs added 30 commits August 31, 2026 12:52
@ethnjs ethnjs linked an issue Sep 8, 2026 that may be closed by this pull request
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nexus Ready Ready Preview Sep 8, 2026 4:33am UTC

@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-73 September 8, 2026 04:33 Destroyed
@railway-app

railway-app Bot commented Sep 8, 2026

Copy link
Copy Markdown

🚅 Deployed to the nexus-pr-73 environment in nexus

Service Status Web Updated
nexus ✅ Success (View Logs) Sep 8, 2026 at 4:34 am UTC

@ethnjs ethnjs added enhancement New feature or request backend frontend database Updates database schemas or models breaking-change Removes or alters existing behavior requiring a migration, coordination, or careful rollout events tournaments forms labels Sep 8, 2026
@ethnjs
ethnjs marked this pull request as ready for review September 8, 2026 04:38
@ethnjs
ethnjs added this pull request to stack #75 September 11, 2026 06:02
@ethnjs
ethnjs merged commit 4484bfd into main Sep 12, 2026
4 checks passed
@ethnjs ethnjs mentioned this pull request Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend breaking-change Removes or alters existing behavior requiring a migration, coordination, or careful rollout database Updates database schemas or models enhancement New feature or request events forms frontend tournaments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: per-viewer display configuration for the roster and member panel feat: member profile

1 participant