# 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 (1–10) 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 1–10) - _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 (1–10) 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', 1–4 → '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 1–10, 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 1–10, 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 (320px–428px 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