Skip to content

Add per-character eligible cast pool and per-session casting matrix #1113

Description

@Tim020

Summary

Characters currently have a single played_by link to a cast member. This adds:

  1. An ordered eligible cast pool per character (principal + understudies/covers)
  2. A new Casting page — a character × session matrix where each cell records who plays that character for a given performance
  3. Scheduling a session (from Add forward-planned session scheduling #1112) auto-seeds the matrix with all-principal cast; cells can be edited to assign covers

Part 2 of 3 for issue #792. Depends on #1112 (session scheduling).


Backend changes

New model CharacterEligibleCast (server/models/show.py):

class CharacterEligibleCast(db.Model):
    __tablename__ = "character_eligible_cast"
    character_id: Mapped[int] = mapped_column(ForeignKey("character.id", ondelete="CASCADE"), primary_key=True)
    cast_id: Mapped[int] = mapped_column(ForeignKey("cast.id", ondelete="CASCADE"), primary_key=True)
    priority: Mapped[int] = mapped_column(default=0)  # 0 = principal, ascending = covers

Add eligible_cast relationship to Character (ordered by priority).

New model SessionCasting (server/models/session.py):

class SessionCasting(db.Model):
    __tablename__ = "session_casting"
    session_id: Mapped[int] = mapped_column(ForeignKey("showsession.id", ondelete="CASCADE"), primary_key=True)
    character_id: Mapped[int] = mapped_column(ForeignKey("character.id", ondelete="CASCADE"), primary_key=True)
    cast_id: Mapped[int] = mapped_column(ForeignKey("cast.id", ondelete="RESTRICT"))

Add casting relationship to ShowSession.

Alembic migration: alembic revision --autogenerate -m "add_eligible_cast_and_session_casting" — include data migration: each Character.played_byCharacterEligibleCast(priority=0). Also update SessionStartController from #1112 to seed SessionCasting rows for all characters using priority-0 cast when a session is started/created.

Schema updates: CharacterSchema → add eligible_cast; ShowSessionSchema → add casting; new SessionCastingSchema, CharacterEligibleCastSchema.

New endpoints:

Method Path Role Description
GET /api/v1/show/characters/{id}/eligible-cast READ Ordered eligible cast list
PUT /api/v1/show/characters/{id}/eligible-cast WRITE Replace entire list. Body: {cast_ids: [ordered]}. Syncs Character.played_by to cast_ids[0].
GET /api/v1/show/sessions/{id}/casting READ All SessionCasting rows for a session
PATCH /api/v1/show/sessions/{id}/casting WRITE Bulk-update. Body: [{character_id, cast_id}]. 409 if session already started. Validates cast_id is in eligible pool.

Frontend changes (client-v3/)

New route: show-config-castingConfigCasting.vue

New nav item in ShowConfigView.vue (between Characters and Mics): Casting

New components (components/show/config/casting/):

  • ConfigCasting.vue — page; "Preparing for:" session picker; contains CastingMatrix.vue; shows MicPlanStaleBanner.vue when active session has any non-principal cast
  • CastingMatrix.vue — horizontal-scroll table; sticky "Character" + "Eligible Cast" columns; one column per session; active column green-tinted; cover cells amber-tinted; click cell → CastPickerDropdown.vue
  • CastPickerDropdown.vue — eligible cast list with PRINCIPAL badge; "↺ Reset to principal"; "+ Add cast to eligible pool…"
  • EligibleCastChips.vue — chip per eligible cast member; × to remove non-principals; "+ Cover" add button
  • MicPlanStaleBanner.vue (stub) — amber bar: "Casting changes may require a mic re-run. Review on Mics →"

ConfigCharacters.vue — add "Eligible Cast" column showing EligibleCastChips; eligible cast section in edit modal.

ConfigSessions.vue (from #1112) — add "Cast" action button → navigates to casting page for that session; extend start-session confirmation to show full cast roster table.

stores/show.ts additions:

  • State: sessionCasting: Record<string, SessionCasting[]>, characterEligibleCast: Record<string, EligibleCast[]>
  • Actions: getSessionCasting, updateSessionCasting, getCharacterEligibleCast, updateCharacterEligibleCast
  • Getter: castDiffForSession(sessionId): CastDiff[] — compares session casting vs each character's priority-0 cast

New src/types/api/casting.ts:

interface EligibleCast { character_id: number; cast_id: number; priority: number }
interface SessionCasting { session_id: number; character_id: number; cast_id: number }
interface CastDiff { character_id: number; character_name: string; principal_cast_id: number; session_cast_id: number }

E2E tests

New client-v3/e2e/tests/12b-show-config-casting.spec.ts:

  • Verify "Casting" nav item visible
  • Matrix shows characters × sessions
  • Click cell → pick cover → amber highlight
  • Add eligible cast via "+ Cover"
  • Stale mic banner appears after cover selection
  • Banner links to Mics page

Update 12-show-config-sessions.spec.ts:

  • "Cast" button navigates to Casting page
  • Start-session confirmation shows cast roster

Notes

  • Character.played_by is kept for backward compatibility; PUT /eligible-cast keeps it in sync with priority-0 cast.
  • Once start_date_time is set on a session, PATCH /sessions/{id}/casting returns 409. Frontend disables cell editing for started/completed sessions.
  • Casting is not shown in the live show view.

Verification

  • pytest — new tests for eligible cast and session casting endpoints
  • npm run typecheck + npm run lint
  • npm run test:e2e — full suite

Metadata

Metadata

Assignees

No one assigned

    Labels

    claudeIssues created by Claude

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions