Full-stack tournament management app with real-time scoring: - Go 1.26 backend with REST API and WebSocket live scoring - React 19 + Vite 8 frontend with mobile-first design - File-based JSON storage with JSONL audit logs - Multi-stage Docker build with Gitea CI/CD pipeline - Post-tournament questionnaire with spirit voting - Technical documentation and project description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
Disc Agenda -- Project Description
1. Overview
Disc Agenda is a self-hosted web application for managing ultimate frisbee tournaments. It provides a single URL where players and spectators can view the tournament schedule, follow live scores in real-time, fill out post-tournament questionnaires, and browse final standings -- all from their phones, with no app install required.
Built for the Czech ultimate frisbee community, the platform is currently deployed for the Fujarna tournament series in Prague.
2. Problem Statement
Small and casual frisbee tournaments typically lack proper digital infrastructure. Organizers rely on a patchwork of tools:
- Google Sheets for schedules (not real-time, clunky on mobile)
- WhatsApp/Telegram groups for score announcements (noisy, easy to miss)
- Paper scorecards at the field (single point of truth, hard to read from a distance)
- Google Forms for post-tournament feedback (disconnected from the event)
This creates friction for everyone: players don't know when their next game starts or what the current score is in a parallel game, spectators can't follow along remotely, and organizers spend time relaying information manually.
Disc Agenda solves this by providing a single, mobile-friendly web app where:
- The schedule is always visible and up-to-date
- Any participant can update scores in real-time
- Scores appear live on everyone's screen via WebSocket
- Post-tournament feedback is collected in context
3. Key Features
3.1 Tournament Hub
The landing page for each tournament displays all essential information at a glance: event name, date, location, venue, participating teams, and rules. Clear navigation links guide users to the schedule, questionnaire, and results.
3.2 Live Scoreboard
The core feature. Each game has its own scoring page with large, touch-friendly controls:
- +/- buttons for quick score increments
- SET button for direct value entry (correcting mistakes)
- Real-time sync -- multiple people can score simultaneously, and all changes appear instantly on every connected device via WebSocket
- Audit trail -- every score change is logged with timestamp and user identity, viewable on the game page
- QR code -- each game page includes a QR code linking to the post-tournament questionnaire, making it easy to share
Game states transition automatically: Scheduled -> Live (when the first point is scored) -> Final (set manually). Once a game is marked as final, the score is locked.
3.3 Live Schedule
The schedule page groups all games by round (Pool A, Pool B, placement bracket) with start times and field assignments. Scores update in real-time across all games -- viewers don't need to open individual game pages to see current scores.
3.4 Post-Tournament Questionnaire
A mobile-friendly survey form with:
- Spirit of the Game voting -- select which team showed the best sportsmanship
- Team selector -- identify your own team
- Custom questions -- organizer-configurable (food rating, field conditions, free-text feedback)
- Attend next tournament -- gauge interest for future events
Responses are stored per tournament and can be reviewed by organizers.
3.5 Results & Standings
A final standings table showing each team's position, win/loss/draw record, points scored and conceded, point differential, and spirit score. The team with the highest spirit score receives a highlighted award.
3.6 Past Tournaments Archive
An archive page listing completed tournaments, allowing the community to look back at previous events and their results.
4. How It Works
For the Organizer
- Prepare data -- create JSON files defining the tournament (teams, schedule, questionnaire)
- Deploy --
docker compose upon any server (a Raspberry Pi works fine) - Share the URL -- send it to teams before or on tournament day
On Tournament Day
- Players open the URL on their phones
- Designated scorers (or anyone) navigate to a game page and tap +/- to update scores
- All connected viewers see score changes instantly
- The schedule page reflects live scores across all games in real-time
- Between games, players can check upcoming matches and current standings
After the Tournament
- Organizer creates
results.jsonwith final standings - Players fill out the questionnaire (linked via QR codes on game pages)
- Organizer reviews feedback via the questionnaire results API
- Tournament status is set to
completedand appears in the archive
5. Technology Stack & Rationale
Go Backend
Why Go: A single statically-compiled binary with excellent concurrency primitives. Perfect for WebSocket-heavy applications where multiple clients need real-time updates. Minimal runtime dependencies -- the compiled binary runs on bare Alpine Linux.
Libraries: Only three external dependencies:
gorilla/mux-- HTTP routergorilla/websocket-- WebSocket protocolrs/cors-- Cross-origin request handling
React + Vite Frontend
Why React: The component model maps naturally to the interactive scoreboard UI -- each game card, score counter, and status badge is a composable piece. React's state management handles the bidirectional WebSocket data flow cleanly.
Why Vite: Modern build tooling with fast hot-reload during development. Produces optimized static assets for production.
Minimal dependencies: No state management library (React's built-in state is sufficient), no UI component framework (custom CSS provides an athletic visual identity), just react-router-dom for routing and qrcode.react for QR generation.
File-Based Storage
Why no database: The application serves small tournaments (10-20 teams, 20-30 games). JSON files provide:
- Zero operational overhead (no database to install, configure, or maintain)
- Human-readable data (edit tournament data with any text editor)
- Easy backup (copy a directory)
- Portability (works anywhere a filesystem exists)
Audit logs use JSONL (JSON Lines) format for append-only writes that survive crashes without corruption.
Trade-off: Not suitable for high-concurrency write scenarios or multi-server deployments. Single-server, single-process is the intended deployment model.
Docker
Why Docker: Reproducible deployment in a single command. The multi-stage build produces a minimal image (~15 MB) containing just the Go binary, frontend assets, and Alpine Linux. A named volume persists tournament data across container updates.
6. Architecture Overview
┌─────────────────────────────────────────────────┐
│ Go Backend (single binary) │
│ ├── REST API → JSON file storage │
│ ├── WebSocket hubs → real-time broadcasting │
│ └── SPA handler → serves React frontend │
├─────────────────────────────────────────────────┤
│ React Frontend (compiled static assets) │
│ ├── Schedule view ← tournament WebSocket │
│ ├── Scoreboard ← game WebSocket │
│ └── Forms → REST API │
├─────────────────────────────────────────────────┤
│ Data (flat JSON files) │
│ ├── Tournament config, schedule, results │
│ ├── Per-game score state │
│ └── JSONL audit logs │
└─────────────────────────────────────────────────┘
The entire application runs as a single process. The Go server handles HTTP requests, WebSocket connections, and file I/O. The React frontend is compiled to static files and served by the same Go process. No separate web server, no reverse proxy (though one is recommended for TLS), no database.
7. Current State
First Tournament: Fujarna - 14.3.2026
- Location: Prague, Czech Republic
- Venue: Kotlarka multi-purpose sports facility
- Teams: 10 teams in 2 pools of 5
- Pool A: FUJ 1, Kocicaci, Spitalska, Sunset, Hoko-Coko Diskyto
- Pool B: FUJ 2, Bjorn, GyBot, Poletime, Kachny
- Format: Full round-robin within pools (10 games per pool), followed by crossover placement bracket (5th place through Grand Final)
- Schedule: 25 games total, 08:30 - 17:10, 20-minute games
- Rules: 20 min per game, no breaks, no timeouts, no draws allowed
Application Status
- All core features are implemented and functional
- UI is in Czech language throughout
- Ready for production deployment via Docker
- CI/CD pipeline configured for Gitea container registry
8. Design Philosophy
Simple over complex. No admin UI, no user accounts, no database. Tournament data is managed by editing JSON files. This keeps the codebase small, the deployment trivial, and the maintenance burden near zero.
Mobile-first. Every page is designed for phone screens -- the primary use case is field-side scoring and schedule checking. Large touch targets, readable typography, responsive layout.
Real-time everywhere. WebSocket connections power both the scoreboard and the schedule. If a score changes, everyone sees it within milliseconds. No polling, no manual refresh.
Self-hosted. Full control over data and infrastructure. No external service dependencies, no API keys, no subscriptions. Runs on any machine that can run Docker -- a home server, a VPS, or a Raspberry Pi.
Trust-based. In a community of ~50 frisbee players, authentication is overhead without benefit. Anyone can update scores, and the audit log provides accountability.
9. Future Plans
Potential directions for the platform:
- Admin panel -- web UI for managing tournaments, teams, and schedules (replacing manual JSON editing)
- Authentication -- optional login for score-keeping to prevent accidental edits
- Automatic standings -- calculate standings from game results instead of manual
results.json - Photo gallery -- tournament photos integrated into the event page
- Push notifications -- alerts for game start, game end, or close scores
- Player registration -- team sign-up and roster management
- Statistics -- game history, scoring trends, spirit score analytics
- Multi-language support -- currently Czech-only
- Multiple fields -- field assignment management for larger tournaments
10. Contributing
The project is organized for easy contribution:
- Backend (
backend/): Standard Go project structure withcmd/andinternal/packages - Frontend (
frontend/): React SPA with page-based component organization - Data (
data/): JSON seed data that can be modified for testing
For detailed technical reference, API documentation, and development setup instructions, see the Technical Documentation. For quick start and project structure, see the README.