Goal
Let the Core Team progressively reveal an edition's schedule (set times + stage assignments) to the public, independently of when the lineup goes live for voting.
Glossary, model rationale, and rejected alternatives are recorded in:
CONTEXT.md — the domain glossary
docs/adr/0001-schedule-reveal-level.md — the architectural decision
Locked decisions
Model
schedule_reveal_level — new ordered enum on festival_editions, default draft.
- Levels (low → high):
draft → days → stages → full. Each implies the previous.
- Independent of
edition.published. edition.published continues to mean "lineup visible + voting open."
- Schedule edits write through live (no staging table); the import wizard shows a warning at commit time when the current level is above
draft.
Per-level field visibility (non-admins)
| Level |
time_start date |
time_start time-of-day |
time_end |
stage_id |
draft |
hidden |
hidden |
hidden |
hidden |
days |
visible |
hidden |
hidden |
hidden |
stages |
visible |
hidden |
hidden |
visible |
full |
visible |
visible |
visible |
visible |
Admins see everything regardless of level.
Masking mechanism — client-side
- Server returns full set rows. Frontend hides embargoed fields based on
(schedule_reveal_level, isAdmin).
- Accepts the wire leak (devtools/direct Supabase queries can see the raw fields). Schedule is embargoed, not secret.
- A small util — e.g.
maskSetForReveal(set, level, isAdmin) — centralises the rule so new surfaces can't forget it.
- Server-side hardening (a
public_sets view + revoke public SELECT on sets) is the documented upgrade path if we ever need true leak-proofness.
Admin UX
- Dropdown for
schedule_reveal_level in the existing edition edit dialog (FestivalEditionManagement.tsx), next to edition.published.
- Progressive action buttons on the Schedule tab (admin-only): "Reveal Days", "Reveal Stages", "Reveal Times". Each becomes a "Revealed ✓" badge with an undo affordance once active.
- Demotion (e.g.
full → stages) is allowed via the same controls — no separate flow.
- Level is settable independently of
edition.published. When the edition is unpublished, UI shows "configured but not yet active."
Public UX
- Schedule tab stays in the nav at all levels. Below
full, it shows a "Schedule not yet published" placeholder (existing pattern) with level-aware copy.
- A small indicator icon on the Schedule tab button signals "not yet at
full" to public users.
- Set/Artist surfaces across the app (SetCard
SetMetadata.tsx, SetDetails, ExploreSetPage) call the masking util and naturally hide on null.
Out of scope (separate task)
Implementation plan
- DB: migration adding
schedule_reveal_level enum and column on festival_editions (NOT NULL, default draft); backfill existing rows. Regen src/integrations/supabase/types.ts.
- Util:
maskSetForReveal(set, level, isAdmin) — single source of truth for which fields to null at which level.
- Edition query: include
schedule_reveal_level in useFestivalEdition / useFestivalEditionBySlug results.
- Frontend hide rules: apply the util at the read boundary in
useEditionSetsQuery / useSetsByEditionQuery (or call it in components — TBD which is cleanest). Verify each surface: SetMetadata.tsx, SetDetails, ExploreSetPage, set filters that depend on time/stage.
- Schedule tab: level-aware placeholder copy; add the "not yet at
full" indicator icon on the tab nav button.
- Admin controls:
- Dropdown in
FestivalEditionManagement.tsx edit dialog.
- Admin-only progressive buttons + demote affordance on the Schedule tab, gated by
useUserPermissionsQuery(user?.id, "is_admin").
- Import wizard warning: in
ScheduleImportWizard commit step, when schedule_reveal_level > draft, render a warning block summarising what becomes visible.
- Tests: unit tests for
maskSetForReveal across the level × role matrix; component snapshots for SetCard at each level; admin level-change flow.
Deferred
Goal
Let the Core Team progressively reveal an edition's schedule (set times + stage assignments) to the public, independently of when the lineup goes live for voting.
Glossary, model rationale, and rejected alternatives are recorded in:
CONTEXT.md— the domain glossarydocs/adr/0001-schedule-reveal-level.md— the architectural decisionLocked decisions
Model
schedule_reveal_level— new ordered enum onfestival_editions, defaultdraft.draft→days→stages→full. Each implies the previous.edition.published.edition.publishedcontinues to mean "lineup visible + voting open."draft.Per-level field visibility (non-admins)
time_startdatetime_starttime-of-daytime_endstage_iddraftdaysstagesfullAdmins see everything regardless of level.
Masking mechanism — client-side
(schedule_reveal_level, isAdmin).maskSetForReveal(set, level, isAdmin)— centralises the rule so new surfaces can't forget it.public_setsview + revoke public SELECT onsets) is the documented upgrade path if we ever need true leak-proofness.Admin UX
schedule_reveal_levelin the existing edition edit dialog (FestivalEditionManagement.tsx), next toedition.published.full→stages) is allowed via the same controls — no separate flow.edition.published. When the edition is unpublished, UI shows "configured but not yet active."Public UX
full, it shows a "Schedule not yet published" placeholder (existing pattern) with level-aware copy.full" to public users.SetMetadata.tsx, SetDetails, ExploreSetPage) call the masking util and naturally hide on null.Out of scope (separate task)
days/stages— tracked in Multi-mode Schedule tab UX for days/stages reveal levels #47.Implementation plan
schedule_reveal_levelenum and column onfestival_editions(NOT NULL, defaultdraft); backfill existing rows. Regensrc/integrations/supabase/types.ts.maskSetForReveal(set, level, isAdmin)— single source of truth for which fields to null at which level.schedule_reveal_levelinuseFestivalEdition/useFestivalEditionBySlugresults.useEditionSetsQuery/useSetsByEditionQuery(or call it in components — TBD which is cleanest). Verify each surface:SetMetadata.tsx, SetDetails, ExploreSetPage, set filters that depend on time/stage.full" indicator icon on the tab nav button.FestivalEditionManagement.tsxedit dialog.useUserPermissionsQuery(user?.id, "is_admin").ScheduleImportWizardcommit step, whenschedule_reveal_level > draft, render a warning block summarising what becomes visible.maskSetForRevealacross the level × role matrix; component snapshots for SetCard at each level; admin level-change flow.Deferred
days/stages.