Initial monorepo structure
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user