feat: data-driven contract rendering system #34

Open
opened 2026-04-03 23:47:52 +00:00 by forgejo_admin · 0 comments
Contributor

Type

Feature

Lineage

Standalone — discovered during Kiana Sikander contract setup (2026-04-03). Custom player deals can't be rendered by the current hardcoded template system.

Repo

forgejo_admin/westside-contracts (primary), forgejo_admin/basketball-api (migrations + seed)

User Story

As a parent receiving a Westside contract, I want to see a player agreement that reflects my child's actual deal — the correct fee, only the tournaments they're committed to, and the practice schedule they agreed to — so that I can sign with confidence the contract matches what was discussed.

As an admin, I want to set up a custom deal for a player (different fee, fewer tournaments, modified schedule) and have the contract page render it correctly — so that I don't need a developer to edit HTML or redeploy.

Context

The contract page has three hardcoded HTML blocks — boys travel (Power 32), girls travel (Prep Hoops Girls), and local. Team name picks the variant via isLocal/isGirls flags. monthly_fee is the only dynamic value. Everything else (5 tournaments with full cost breakdowns, 3 practice days, payment schedules) is literal HTML in a 725-line monolithic Svelte component.

Kiana Sikander (player_id=184, 17U Elite Queens) agreed to $100/month, 1 practice/week, Arizona + Vegas tournaments only. The current system has no way to render a contract that differs from the team default. The custom_notes field renders as a paragraph at the bottom — it can't suppress or replace sections above it.

Architecture change: move contract content from hardcoded HTML into structured JSONB data on teams (defaults) and players (overrides). Extract the monolithic Svelte component into focused, data-driven components.

File Targets

basketball-api:

  • alembic/versions/NNN_add_contract_config_to_teams.py — new migration
  • alembic/versions/NNN_add_contract_overrides_to_players.py — new migration
  • alembic/versions/NNN_seed_team_contract_configs.py — data migration
  • src/basketball_api/models.py — add contract_config to Team, contract_overrides to Player

westside-contracts:

  • src/routes/contract/[token]/+page.svelte — refactor from 725-line monolith to ~60-line orchestrator
  • src/routes/contract/[token]/+page.server.ts — load contract_config + contract_overrides, merge, pass structured data
  • src/lib/components/ContractHeader.svelte — new
  • src/lib/components/FeeSection.svelte — new
  • src/lib/components/TournamentCard.svelte — new
  • src/lib/components/TournamentList.svelte — new
  • src/lib/components/PracticeSchedule.svelte — new
  • src/lib/components/PaymentSchedule.svelte — new
  • src/lib/components/StaticSection.svelte — new
  • src/lib/components/ContractCallout.svelte — new
  • src/lib/components/CustomNote.svelte — new
  • src/lib/components/SigningSection.svelte — new
  • src/lib/components/SuccessOverlay.svelte — new
  • src/lib/components/AlreadySigned.svelte — new
  • src/lib/types.ts — add ContractConfig, ContractOverrides, MergedConfig interfaces
  • src/routes/contract/[token]/sign/+server.ts — read variant from contract_config

Files NOT to touch:

  • src/lib/validation.ts — signing validation unchanged
  • src/lib/minio.ts — signature upload unchanged
  • src/lib/db.ts — connection pool unchanged

Acceptance Criteria

  • When a team has contract_config set, the contract page renders tournaments, practices, and payments from that data
  • When a player has contract_overrides with a tournaments filter, only those tournaments appear on the contract page
  • When a player has contract_overrides with custom practices, those replace the team default practice schedule
  • When a team has no contract_config (NULL), the page falls back to current hardcoded rendering
  • When a player has no contract_overrides (NULL), team defaults render
  • Kiana Sikander's contract page shows: $100/month, Mesa AZ + Nike Vegas only, 1 practice (Friday BWill 7-9 PM), custom note
  • All 19 existing signed contracts still render "Already Signed" correctly
  • All 34 existing offered contracts render identically to current (no visual regression)
  • Contract signing flow works end-to-end (signature → MinIO → DB → outbox → welcome email)
  • Queens pink branding still applies for Queens teams

Test Expectations

  • Unit test: ContractConfig merge logic — team defaults, player overrides, tournament filtering, null handling
  • Unit test: Each extracted component renders correct HTML from props
  • Integration test: Contract page loads with seeded team config, renders tournaments/practices from data
  • Integration test: Contract signing still works after refactor
  • Existing validation tests still pass
  • Run command: npm test (westside-contracts), pytest tests/ (basketball-api)

Constraints

  • No new npm dependencies — pure Svelte 5 components with runes ($props, $derived)
  • JSONB columns must be nullable with DEFAULT NULL for backwards compatibility
  • Seed migration must exactly reproduce current hardcoded content (boys travel, girls travel, local)
  • CSS stays in app.css — components use existing CSS classes, no scoped styles
  • Deploy order: basketball-api migrations first → westside-contracts second

Checklist

  • PR opened (basketball-api migrations)
  • PR opened (westside-contracts components + data-driven rendering)
  • Tests pass
  • No unrelated changes
  • Kiana's contract renders correctly
  • Existing contracts have no visual regression
  • westside-agency — parent project for Westside basketball operations
  • Spec: pal-e-platform/docs/superpowers/specs/2026-04-03-data-driven-contracts-design.md
### Type Feature ### Lineage Standalone — discovered during Kiana Sikander contract setup (2026-04-03). Custom player deals can't be rendered by the current hardcoded template system. ### Repo `forgejo_admin/westside-contracts` (primary), `forgejo_admin/basketball-api` (migrations + seed) ### User Story As a parent receiving a Westside contract, I want to see a player agreement that reflects my child's actual deal — the correct fee, only the tournaments they're committed to, and the practice schedule they agreed to — so that I can sign with confidence the contract matches what was discussed. As an admin, I want to set up a custom deal for a player (different fee, fewer tournaments, modified schedule) and have the contract page render it correctly — so that I don't need a developer to edit HTML or redeploy. ### Context The contract page has three hardcoded HTML blocks — boys travel (Power 32), girls travel (Prep Hoops Girls), and local. Team name picks the variant via `isLocal`/`isGirls` flags. `monthly_fee` is the only dynamic value. Everything else (5 tournaments with full cost breakdowns, 3 practice days, payment schedules) is literal HTML in a 725-line monolithic Svelte component. Kiana Sikander (player_id=184, 17U Elite Queens) agreed to $100/month, 1 practice/week, Arizona + Vegas tournaments only. The current system has no way to render a contract that differs from the team default. The `custom_notes` field renders as a paragraph at the bottom — it can't suppress or replace sections above it. Architecture change: move contract content from hardcoded HTML into structured JSONB data on teams (defaults) and players (overrides). Extract the monolithic Svelte component into focused, data-driven components. ### File Targets **basketball-api:** - `alembic/versions/NNN_add_contract_config_to_teams.py` — new migration - `alembic/versions/NNN_add_contract_overrides_to_players.py` — new migration - `alembic/versions/NNN_seed_team_contract_configs.py` — data migration - `src/basketball_api/models.py` — add contract_config to Team, contract_overrides to Player **westside-contracts:** - `src/routes/contract/[token]/+page.svelte` — refactor from 725-line monolith to ~60-line orchestrator - `src/routes/contract/[token]/+page.server.ts` — load contract_config + contract_overrides, merge, pass structured data - `src/lib/components/ContractHeader.svelte` — new - `src/lib/components/FeeSection.svelte` — new - `src/lib/components/TournamentCard.svelte` — new - `src/lib/components/TournamentList.svelte` — new - `src/lib/components/PracticeSchedule.svelte` — new - `src/lib/components/PaymentSchedule.svelte` — new - `src/lib/components/StaticSection.svelte` — new - `src/lib/components/ContractCallout.svelte` — new - `src/lib/components/CustomNote.svelte` — new - `src/lib/components/SigningSection.svelte` — new - `src/lib/components/SuccessOverlay.svelte` — new - `src/lib/components/AlreadySigned.svelte` — new - `src/lib/types.ts` — add ContractConfig, ContractOverrides, MergedConfig interfaces - `src/routes/contract/[token]/sign/+server.ts` — read variant from contract_config Files NOT to touch: - `src/lib/validation.ts` — signing validation unchanged - `src/lib/minio.ts` — signature upload unchanged - `src/lib/db.ts` — connection pool unchanged ### Acceptance Criteria - [ ] When a team has `contract_config` set, the contract page renders tournaments, practices, and payments from that data - [ ] When a player has `contract_overrides` with a tournaments filter, only those tournaments appear on the contract page - [ ] When a player has `contract_overrides` with custom practices, those replace the team default practice schedule - [ ] When a team has no `contract_config` (NULL), the page falls back to current hardcoded rendering - [ ] When a player has no `contract_overrides` (NULL), team defaults render - [ ] Kiana Sikander's contract page shows: $100/month, Mesa AZ + Nike Vegas only, 1 practice (Friday BWill 7-9 PM), custom note - [ ] All 19 existing signed contracts still render "Already Signed" correctly - [ ] All 34 existing offered contracts render identically to current (no visual regression) - [ ] Contract signing flow works end-to-end (signature → MinIO → DB → outbox → welcome email) - [ ] Queens pink branding still applies for Queens teams ### Test Expectations - [ ] Unit test: ContractConfig merge logic — team defaults, player overrides, tournament filtering, null handling - [ ] Unit test: Each extracted component renders correct HTML from props - [ ] Integration test: Contract page loads with seeded team config, renders tournaments/practices from data - [ ] Integration test: Contract signing still works after refactor - [ ] Existing validation tests still pass - Run command: `npm test` (westside-contracts), `pytest tests/` (basketball-api) ### Constraints - No new npm dependencies — pure Svelte 5 components with runes ($props, $derived) - JSONB columns must be nullable with DEFAULT NULL for backwards compatibility - Seed migration must exactly reproduce current hardcoded content (boys travel, girls travel, local) - CSS stays in app.css — components use existing CSS classes, no scoped styles - Deploy order: basketball-api migrations first → westside-contracts second ### Checklist - [ ] PR opened (basketball-api migrations) - [ ] PR opened (westside-contracts components + data-driven rendering) - [ ] Tests pass - [ ] No unrelated changes - [ ] Kiana's contract renders correctly - [ ] Existing contracts have no visual regression ### Related - `westside-agency` — parent project for Westside basketball operations - Spec: `pal-e-platform/docs/superpowers/specs/2026-04-03-data-driven-contracts-design.md`
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/westside-contracts#34
No description provided.