Skip to content

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

@Tim020

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions