Files
pairgoth/CLAUDE.md
Claude Brisson 9a379052e5 Add user preference for black vs white display order
- Gear icon in header opens settings modal
- Preference stored in cookie for server-side Velocity rendering
- ViewServlet reads blackFirst cookie into Velocity context
- Velocity conditionals in pairing, results, and result-sheets templates
2025-11-30 10:54:52 +01:00

5.9 KiB

Pairgoth Project

Purpose

Pairgoth is a modern Go tournament pairing engine - successor to OpenGotha. It manages tournaments using Swiss and MacMahon pairing systems, handling player registration, automatic pairing, results entry, and standings calculation.

Version: 0.20 | License: org.jeudego (French Go Association)

Tech Stack

  • Backend: Kotlin 2.1 + Maven + Jetty 10
  • Frontend: Fomantic UI CSS 2.9.2 + Vanilla JavaScript (no jQuery/React)
  • Templates: Apache Velocity 2.4
  • Storage: File-based XML (no database required)
  • JDK: 11+

Project Structure

pairgoth/
├── pairgoth-common/     # Shared utilities (JSON, XML, crypto, logging)
├── api-webapp/          # REST API backend (port 8085)
│   └── model/           #   Domain: Tournament, Player, Game, Pairing
│   └── pairing/         #   Solvers: Swiss, MacMahon algorithms
│   └── store/           #   Persistence: File/Memory storage
│   └── api/             #   Handlers: Tournament, Player, Results, etc.
│   └── ext/             #   OpenGotha import/export
├── view-webapp/         # Web UI frontend (port 8080)
│   └── webapp/js/       #   Vanilla JS: domhelper, api, main, tour-*.inc
│   └── webapp/sass/     #   Styles: main, tour, explain, index
│   └── templates/       #   Velocity: index, tour, explain, login
│   └── kotlin/          #   Servlets, OAuth, Ratings integration
├── webserver/           # Standalone Jetty launcher
├── application/         # Final JAR packaging
└── docker/              # Container deployment

Architecture

Dual-Webapp Pattern

[Browser] <--8080--> [view-webapp] <--8085--> [api-webapp]
              │              │                     │
        Velocity HTML    ApiClient.kt         REST JSON
        + vanilla JS                          + FileStore
  • api-webapp - Pure REST API, business logic, pairing engine
  • view-webapp - Web UI, proxies API calls, handles auth/i18n/ratings

Key Architectural Decisions

  1. No JS Framework - 2200 lines of vanilla JS vs typical 50KB+ bundle
  2. Fomantic CSS Only - Using CSS framework without its jQuery-dependent JS
  3. CSS @layer - Clean cascade: semantic layer < pairgoth layer
  4. File Storage - XML files for portability, no database setup needed
  5. Read/Write Locks - Simple concurrency on API servlet
  6. SSE Events - Real-time updates via Server-Sent Events

Domain Model

Tournament (sealed class)
├── IndividualTournament
├── PairTournament
├── TeamTournament
└── RengoTournament

Player → Pairable (interface)
Game { white, black, result, handicap }
Pairing { Swiss | MacMahon }
TimeSystem { ByoYomi | SuddenDeath | Canadian | Fischer }
Rules { French | Japanese | AGA | Chinese }

Pairing Engine

Location: api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/

  • SwissSolver - Swiss system pairing algorithm
  • MacMahonSolver - MacMahon bands system
  • HistoryHelper - Criteria: wins, SOS, SOSOS, colors, CUSS, etc.
  • PairingListener - Progress callbacks for UI

Key Files

Purpose Path
DOM utilities view-webapp/.../js/domhelper.js
API client view-webapp/.../js/api.js
Core UI view-webapp/.../js/main.js
Main styles view-webapp/.../sass/main.scss
Tournament model api-webapp/.../model/Tournament.kt
Swiss solver api-webapp/.../pairing/solver/SwissSolver.kt
API router api-webapp/.../server/ApiServlet.kt
App launcher webserver/.../application/Pairgoth.kt

Build & Run

# Build
mvn clean package

# Run standalone (both webapps)
java -jar application/target/pairgoth-engine.jar

# Or separate:
# API: localhost:8085/api
# UI:  localhost:8080/

Configuration

File: pairgoth.properties (user) or pairgoth.default.properties (defaults)

webapp.port = 8080
api.port = 8085
store = file                    # file | memory
store.file.path = tournamentfiles
auth = none                     # none | oauth | sesame

Frontend Patterns

State via CSS Classes

  • .active - tabs, accordions, visible elements
  • .shown - modals/popups
  • .hidden / .disabled / .selected / .dimmed

Component Communication

// Custom events
box.dispatchEvent(new CustomEvent('listitem-dblclk', { detail: id }));

// jQuery-like API (domhelper.js)
$('.item').addClass('active').on('click', handler);

API Integration

api.getJson('/tour/123/players')
   .then(players => render(players))
   .catch(err => error(err));

External Integrations

  • Ratings: FFG (French), EGF (European), AGA (Australian)
  • OAuth: FFG, Google, Facebook, Twitter, Instagram
  • Import/Export: OpenGotha XML format compatibility

i18n

Translations in view-webapp/.../WEB-INF/translations/

  • English (default)
  • French (fr)
  • German (de)
  • Korean (ko)

Current Work

User Preferences (feature/user-preferences branch)

Implemented "black vs white" display order option:

  • Gear icon in header opens settings modal
  • Preference stored in cookie (blackFirst) for server-side Velocity rendering
  • localStorage backup via store2 (prefs.blackFirst)
  • Velocity conditionals in tour-pairing.inc.html, tour-results.inc.html, result-sheets.html
  • ViewServlet reads cookie and sets $blackFirst in Velocity context

Files modified:

  • view-webapp/.../layouts/standard.html - gear icon + settings modal
  • view-webapp/.../sass/main.scss - settings modal styles
  • view-webapp/.../js/main.js - prefs object + modal handlers + cookie set
  • view-webapp/.../kotlin/.../ViewServlet.kt - read blackFirst cookie
  • view-webapp/.../tour-pairing.inc.html - #if($blackFirst) conditionals
  • view-webapp/.../tour-results.inc.html - #if($blackFirst) conditionals + inverted result display
  • view-webapp/.../result-sheets.html - #if($blackFirst) conditionals