Files
Orchestrator/.kiro/specs/jury-voting/tasks.md
T
2026-06-30 20:37:40 +02:00

295 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Plan: Jury Voting
## Overview
Build a full-stack web application for the 15. UNIKAT Ideenwettbewerb jury voting process. The app enables 6 jurors to score 11 teams on 5 criteria (110) via smartphones, with real-time results displayed on a beamer. Stack: React 19 + Vite frontend, Express.js backend, JSON file persistence, SSE for live updates. TypeScript throughout.
## Tasks
- [x] 1. Project scaffolding and configuration
- [x] 1.1 Initialize Vite + React 19 + TypeScript project in `Jury-Voting/`
- Run `npm create vite@latest` with React + TypeScript template
- Configure `vite.config.ts` with proxy to Express backend (port 3001)
- Add dependencies: `react-router-dom`, `lucide-react`
- Add dev dependencies: `vitest`, `fast-check`, `@testing-library/react`, `jsdom`
- _Requirements: 5.2 (dark theme, Outfit font, glassmorphism)_
- [x] 1.2 Set up Express.js backend in `Jury-Voting/server/`
- Create `server/` directory with TypeScript configuration
- Install: `express`, `cors`, `tsx` (for dev), `typescript`
- Create `server/index.ts` entry point with basic Express app on port 3001
- Add `npm run dev:server` script using `tsx watch`
- _Requirements: 3.1 (score persistence)_
- [x] 1.3 Define shared TypeScript interfaces in `Jury-Voting/shared/types.ts`
- Create `Session`, `Team`, `Juror`, `Criterion`, `Score`, `TeamResult`, `PendingScore` interfaces
- Create `SessionConfig` interface with criteria and scale settings
- Export all types for use by both frontend and backend
- _Requirements: 6.2, 6.3 (configurable teams, criteria, scale)_
- [x] 1.4 Create seed data file `Jury-Voting/server/data/seed.json`
- Pre-populate 11 teams from `Teams/` folder (id, number, name, description)
- Pre-populate 6 jurors (Cielejewski, Finke, Freyer, Knie, Meyer, Trieschmann)
- Pre-populate 5 criteria (Gesamtidee, Kundennutzen, Markt, Realisierbarkeit, Präsentation)
- Set default session config (scale 110)
- _Requirements: 6.2, 6.3, 6.6_
- [x] 2. Backend data layer and core logic
- [x] 2.1 Implement JSON file store (`server/store.ts`)
- Read/write `server/data/session.json` atomically (write to temp, rename)
- Create `.bak` backup before each write
- Initialize from `seed.json` if `session.json` doesn't exist
- Expose typed read/write functions for session, teams, jurors, scores
- _Requirements: 3.1, 3.2 (persistence, reliability)_
- [x] 2.2 Implement score aggregation logic (`server/aggregation.ts`)
- `calculateTeamResult(teamId, scores[])` → total, average, jurorCount, complete
- `calculateRankings(teamResults[])` → sorted results with dense ranking (ties get same rank)
- `getCompletionStatus(jurorId, teamId, scores[])` → 'complete' | 'partial' | 'pending'
- Pure functions, no side effects — easy to test
- _Requirements: 4.2, 4.3, 4.4, 4.5_
- [ ]* 2.3 Write property test: Score Aggregation Correctness
- **Property 4: Score Aggregation Correctness**
- Generate random valid scores (110) for arbitrary juror/team/criterion combinations
- Assert: total equals arithmetic sum of all score values for a team
- Assert: average equals total / distinct jurors who scored
- **Validates: Requirements 4.2, 4.3**
- [ ]* 2.4 Write property test: Ranking Correctness with Ties
- **Property 5: Ranking Correctness with Ties**
- Generate random team totals, compute rankings
- Assert: rankings sorted descending by total
- Assert: equal totals receive equal rank numbers
- **Validates: Requirements 4.4, 4.5**
- [ ]* 2.5 Write property test: Team Completion Status
- **Property 1: Team Completion Status**
- Generate random subsets of criteria scores for a juror-team pair
- Assert: 5 scores → 'complete', 14 → 'partial', 0 → 'pending'
- **Validates: Requirements 2.4, 7.1**
- [x] 2.6 Implement last-write-wins conflict resolution (`server/conflict.ts`)
- Compare timestamps on incoming score vs stored score
- Accept score with later `updatedAt` timestamp
- Return conflict info (409) if client sends older timestamp without force flag
- _Requirements: 3.3, 3.4_
- [ ]* 2.7 Write property test: Last-Write-Wins Score Resolution
- **Property 3: Last-Write-Wins Score Resolution**
- Generate sequences of score updates with varying timestamps for same (jurorId, teamId, criterionId)
- Assert: final persisted value equals the value with the most recent timestamp
- **Validates: Requirements 3.3**
- [ ] 3. Backend API endpoints
- [x] 3.1 Implement session endpoints
- `GET /api/session` — return current session state
- `POST /api/session/start` — set status to 'active', record startedAt
- `POST /api/session/end` — set status to 'ended', record endedAt
- Validate: cannot start if already active, cannot end if not active
- _Requirements: 6.4, 6.5_
- [x] 3.2 Implement team and juror endpoints
- `GET /api/teams` — return all teams
- `GET /api/jurors` — return all jurors
- _Requirements: 6.2, 6.6_
- [x] 3.3 Implement score endpoints
- `POST /api/scores` — submit/update a score (validate: session active, score 110, valid jurorId/teamId/criterionId)
- `GET /api/scores/:jurorId` — return all scores for a juror
- `GET /api/scores` — return all scores (admin only)
- On successful score submission, trigger SSE broadcast
- _Requirements: 2.3, 3.1, 3.3_
- [ ]* 3.4 Write property test: Session Gate
- **Property 6: Session Gate**
- Generate score submissions with random session states ('setup', 'active', 'ended')
- Assert: accepted if and only if session status is 'active'
- **Validates: Requirements 6.4, 6.5**
- [x] 3.5 Implement results and progress endpoints
- `GET /api/results` — return aggregated TeamResult[] with rankings
- `GET /api/progress` — return juror completion matrix (jurorId → count of fully-scored teams)
- `GET /api/export/csv` — generate and return CSV file
- Progress endpoint must NOT include individual score values
- _Requirements: 4.1, 7.1, 7.3, 8.1_
- [ ]* 3.6 Write property test: Progress Privacy
- **Property 7: Progress Privacy**
- Generate random score sets, call progress endpoint logic
- Assert: response contains juror completion counts but no individual score values
- **Validates: Requirements 7.3**
- [x] 4. SSE real-time infrastructure
- [x] 4.1 Implement SSE endpoint (`server/sse.ts`)
- `GET /api/results/stream` — establish SSE connection
- Maintain list of connected clients
- On score change, broadcast `scores_updated` event with full results payload
- Handle client disconnect (remove from list)
- Set appropriate headers: `Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`
- _Requirements: 4.1, 5.3_
- [x] 4.2 Create frontend SSE hook (`src/hooks/useSSE.ts`)
- Custom React hook that connects to `/api/results/stream`
- Parse incoming events and update local state
- Auto-reconnect on connection loss (EventSource built-in)
- Return: `{ results, connected, lastUpdate }`
- _Requirements: 5.3 (update within 2 seconds without page refresh)_
- [x] 5. Checkpoint - Backend complete
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. Frontend: Juror selection and routing
- [x] 6.1 Set up React Router with route structure
- Configure routes: `/`, `/vote`, `/vote/:teamId`, `/results`, `/admin`
- Create `App.tsx` with `BrowserRouter` and route definitions
- Create layout wrapper with dark theme base styles
- _Requirements: 1.1, 2.1_
- [x] 6.2 Implement `JurorSelect` page (`src/pages/JurorSelect.tsx`)
- Fetch juror list from `GET /api/jurors`
- Display 6 juror names as large glass-panel cards (tap targets for mobile)
- On selection: store jurorId in localStorage, navigate to `/vote`
- Show warning if juror already has active session (allow override)
- _Requirements: 1.1, 1.2, 1.3, 1.4_
- [ ]* 6.3 Write unit tests for JurorSelect
- Test: renders 6 juror names
- Test: selection stores jurorId and navigates
- Test: shows conflict warning for active juror
- _Requirements: 1.1, 1.2, 1.3_
- [ ] 7. Frontend: Voting interface
- [x] 7.1 Implement `VotingDashboard` page (`src/pages/VotingDashboard.tsx`)
- Fetch teams from `GET /api/teams` and scores from `GET /api/scores/:jurorId`
- Display 11 `TeamCard` components in a scrollable list
- Each card shows: team name, description snippet, status icon (✓ complete / ◐ partial / ○ pending)
- Tap card navigates to `/vote/:teamId`
- _Requirements: 2.1, 2.4, 2.5_
- [x] 7.2 Implement `TeamScoring` page (`src/pages/TeamScoring.tsx`)
- Display team name and description at top
- Render 5 `ScoreSlider` components (one per criterion)
- Each slider: range 110, shows current value, large touch target
- Auto-save on change with 300ms debounce
- Show green flash confirmation on successful save
- Prev/Next team navigation buttons
- _Requirements: 2.2, 2.3, 2.5_
- [x] 7.3 Implement offline score queue (`src/hooks/useOfflineQueue.ts`)
- On score change: save to localStorage `pendingScores` array immediately
- Attempt POST to `/api/scores` — on success, mark as synced
- On network failure: keep in queue, show subtle "offline" indicator
- On reconnect: replay pending scores in order
- Exponential backoff for retries (max 3 attempts per score)
- _Requirements: 3.1, 3.2_
- [ ]* 7.4 Write property test: Offline Queue Preservation
- **Property 2: Offline Queue Preservation**
- Generate random sequences of scores submitted while "offline"
- Simulate sync, assert all scores present on server with original values/timestamps
- **Validates: Requirements 3.2**
- [ ]* 7.5 Write unit tests for TeamScoring
- Test: renders 5 criteria with correct labels
- Test: auto-saves on slider change (debounced)
- Test: shows confirmation flash on save
- Test: prev/next navigation works
- _Requirements: 2.2, 2.3_
- [ ] 8. Frontend: Results view (beamer)
- [x] 8.1 Implement `ResultsView` page (`src/pages/ResultsView.tsx`)
- Full-screen layout, no navigation chrome
- Connect to SSE via `useSSE` hook
- Display `RankingTable` component with animated transitions
- Show "Voting in Progress" or "Final Results" based on session status
- Optimized for 1920×1080 landscape projection
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6_
- [x] 8.2 Implement `RankingTable` component (`src/components/RankingTable.tsx`)
- Display ranked list: rank, team name, total score, average score
- Animate rank changes (CSS transitions on position)
- Highlight top 3 teams with special styling
- Handle ties (same rank number displayed)
- _Requirements: 4.4, 4.5, 5.4_
- [ ]* 8.3 Write property test: Results Display Completeness
- **Property 9: Results Display Completeness**
- Generate random TeamResult objects, render ResultsView
- Assert: each team's name, total, average, and rank are present in rendered output
- **Validates: Requirements 5.4**
- [ ] 9. Frontend: Admin panel
- [x] 9.1 Implement `AdminPanel` page (`src/pages/AdminPanel.tsx`)
- Session controls: Start Voting / End Voting buttons with confirmation dialogs
- Show current session status and timestamps
- Warning when ending session with incomplete scores
- _Requirements: 6.1, 6.4, 6.5_
- [x] 9.2 Implement `ProgressMatrix` component (`src/components/ProgressMatrix.tsx`)
- Fetch from `GET /api/progress`
- Display 6×11 grid (jurors × teams) with completion indicators
- Show per-juror totals (X/11 teams complete)
- Indicate when all voting is complete (6/6 jurors × 11/11 teams)
- No individual scores shown — only completion status
- _Requirements: 7.1, 7.2, 7.3_
- [x] 9.3 Implement CSV export functionality
- Export button calls `GET /api/export/csv` and triggers download
- CSV format: teams as rows, (juror × criterion) as columns, plus totals/averages/ranks
- Match structure of original Auswertungstabelle
- _Requirements: 8.1, 8.2, 8.3, 8.4_
- [ ]* 9.4 Write property test: Export Round-Trip
- **Property 8: Export Round-Trip**
- Generate complete score sets, export to CSV, parse CSV back
- Assert: all individual scores recovered, totals correct, averages correct, rankings correct
- **Validates: Requirements 8.1, 8.2, 8.3, 8.4**
- [x] 10. Checkpoint - Core features complete
- Ensure all tests pass, ask the user if questions arise.
- [ ] 11. Styling and visual polish
- [x] 11.1 Implement global theme and CSS variables
- Define CSS custom properties: `--bg-primary: #0a0a0a`, `--accent: #00FF00`, glassmorphism values
- Import Outfit font from Google Fonts
- Set up glassmorphism utility classes (backdrop-filter, border, shadow)
- Dark scrollbar styling, smooth transitions
- _Requirements: 5.2_
- [x] 11.2 Style all components to match Projekt-KIQ-HP design
- JurorSelect: glass-panel cards with hover/tap glow effect
- VotingDashboard: team cards with status indicators, neon green accents
- TeamScoring: custom slider styling (neon green track), score display
- ResultsView: large typography, animated rank transitions, glassmorphism table
- AdminPanel: clean grid layout, status badges
- Mobile-first responsive design (320px428px primary, scales up)
- _Requirements: 2.1, 5.1, 5.2_
- [ ] 12. Deployment configuration
- [x] 12.1 Create production build setup
- Configure Vite to build static assets to `dist/`
- Configure Express to serve static files from `dist/` in production
- Single-port deployment: Express serves both API and frontend
- Add `npm run build` and `npm start` scripts
- _Requirements: all (deployment readiness)_
- [x] 12.2 Create `docker-compose.yml` for containerized deployment
- Single container: Node.js serving Express + static frontend
- Volume mount for `server/data/` (persist scores across restarts)
- Expose port 3001 (or configurable via env)
- Add `.dockerignore` and `Dockerfile`
- _Requirements: all (deployment readiness)_
- [x] 13. Final checkpoint - All features complete
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional property-based tests — skip for faster MVP delivery given the tight timeline (event: 19. Mai 2026)
- Each task references specific requirements for traceability
- Checkpoints at tasks 5, 10, and 13 ensure incremental validation
- Property tests validate the 9 correctness properties from the design document
- The app is designed for single-event use — simplicity over scalability
- All seed data (teams, jurors, criteria) is pre-populated; no manual setup needed at event time