wanjakrah cf0e829346
Some checks failed
Build, test and publish / verify (push) Has been cancelled
Merge pull request 'Expose Playwright CI image for Renovate' (#23) from s5/playwright-image-declaration into main
Reviewed-on: #23
2026-11-12 13:27:29 +01:00
2026-10-29 22:05:11 +01:00
2026-10-26 22:45:14 +01:00
2026-10-29 22:05:11 +01:00
2026-11-03 16:12:30 +01:00
2026-10-30 19:58:03 +01:00
2026-10-27 15:34:56 +01:00

Landscape and shared engineering

Architecture decisions, specifications and project onboarding are maintained in landscape. Shared agent skills are maintained in landscape/skills/. Reusable tooling, standards and the project generator are maintained in engineering.

This project commits the engineering version recorded in engineering.lock.json under generated/. Ordinary builds need no other repository checkout. Regenerate with mvn -N -Pregenerate-engineering validate; CI checks the selected tag and commit with python3 scripts/engineering.py --check. To upgrade, update both tag and commit, follow engineering's UPGRADING.md, regenerate and review the diff. Project configuration in engineering.properties, workflows and deploy/ remains application-owned. Read CODING_STANDARDS.md and AGENTS.md for project guidance.

agiledancers

To map dance groups to performance days. The backend runs on Quarkus with Java 25.

Requires JDK 25 and Maven 3.9.0 or newer. Set JAVA_HOME to your JDK 25 installation.

Build and run all tests (including *Sit tests) from the repository root:

mvn clean verify

Start development mode with live reload from the repository root:

mvn -pl agiledancers-be quarkus:dev

Run the packaged application after building:

# Set DATABASE_URL, DATABASE_USER and DATABASE_PASSWORD first.
java -jar agiledancers-be/target/quarkus-app/quarkus-run.jar

Build a Docker container against an isolated test database (see connection setup):

docker build --network=host \
  --secret id=database_url,env=TEST_DATABASE_URL \
  --secret id=database_user,env=TEST_DATABASE_USER \
  --secret id=database_password,env=TEST_DATABASE_PASSWORD -t agiledancers .
docker run --rm --name agiledancers -p 8080:8080 \
  -e DATABASE_URL -e DATABASE_USER -e DATABASE_PASSWORD agiledancers

The Docker build compiles the application and runs the tests in a Maven build stage, then copies the Quarkus application into a Java 25 runtime image running as a non-root user.

Check readiness at http://localhost:8080/q/health/ready or overall health at http://localhost:8080/q/health. For example:

curl --fail http://localhost:8080/q/health/ready

Quarkus configuration can be overridden with environment variables, for example docker run --rm -e QUARKUS_HTTP_PORT=9090 -p 9090:9090 agiledancers.

PostgreSQL stores all school data, planning state and the latest scheduling run through JPA/Hibernate ORM. Persistence entities remain in the repository layer. Flyway initializes and versions the schema. Production requires DATABASE_URL (a JDBC PostgreSQL URL), DATABASE_USER and DATABASE_PASSWORD; missing credentials, connection failures and migration failures prevent startup. See database setup and transition before replacing an in-memory release: export every school before stopping it.

Architecture checks run with the normal test suite, or separately with:

mvn -pl agiledancers-be -Dtest=ArchitectureTest test

ArchitectureTest guards the following packages below nl.agilecc.agiledancers:

Package Responsibility Allowed dependencies within the application
model Domain entities and specifications Model
service Application use cases and future ports Model, repositories
repository JPA/Hibernate entities, repositories and transaction boundaries Model
rest Future REST services Services, model
solver Future OR-Tools calculations Services (ports), model

REST and solver packages are optional until implemented. OR-Tools dependencies are allowed only in solver; JAX-RS and RESTEasy dependencies only in rest. The model may depend only on itself, Java and Lombok. Package cycles and production classes outside these packages fail the architecture tests.

API models live in rest.apimodel, each in its own Java file as a public top-level class or record. Their names end in Request, Response, View, Dto or DTO. Architecture tests enforce this naming and location convention and require all records in the REST layer to reside in rest.apimodel.

The current services instantiate concrete repositories, so this is not yet strict Onion architecture. That requires repository interfaces in the core and injected implementations in an outer adapter. For future calculations, define a port in service.port using domain types and implement it in solver; services must not depend on solver implementations or expose OR-Tools types.

Handwritten production types each have their own file and must be top-level. Service records live in service.model; the school JSON snapshot records and import preview are service-owned data models. Scheduling records shared with the solver live in model.scheduling. Each group requirement combines its group ID and appearance count in one record.

service.port.SchedulingEngine is the application-owned interface for solving a scheduling problem. SchedulingRuns calls this interface for background runs; SchedulingService retains the synchronous service entry point. Dependency injection supplies solver.OrToolsSchedulingEngine. The port accepts and returns domain types, so neither the caller nor the contract depends on OR-Tools or the concrete school-management service. Architecture tests enforce the port's location and dependencies, the location of service records, and the top-level type rule (excluding generated Lombok builders and compiler helpers).

School management REST API

All endpoints consume and produce JSON. Resources are scoped to their dance school. A dancer can belong to multiple dance groups within the same school. Memberships are managed separately from group details and survive updates to dancers and groups.

Resource collection Item URL
/api/dance-schools /api/dance-schools/{schoolId}
/api/dance-schools/{schoolId}/dancers .../dancers/{dancerId}
/api/dance-schools/{schoolId}/dance-groups .../dance-groups/{groupId}
/api/dance-schools/{schoolId}/shows .../shows/{showId}

Each collection supports GET (list) and POST (create). Each item supports GET, PUT (replace editable details), and DELETE. Creation returns 201 with a Location header; updates return 200; deletion returns 204. Invalid input returns 400, and missing resources or resources belonging to another school return 404. Client-supplied IDs are not used.

Request bodies (dates use ISO yyyy-MM-dd):

{"name": "24 Dance Studio"}
{"name": "Sam", "birthDate": "2010-05-12"}
{
  "category": "Teens",
  "type": "REGULAR",
  "startAge": 12,
  "endAge": 18,
  "styles": ["HIPHOP", "JAZZ"]
}
{"name": "Annual show", "dayParts": [{"date": "2027-06-12", "type": "AFTERNOON"}]}

Names and categories must be nonblank. Birth dates cannot be in the future. Both ages must be nonnegative and startAge <= endAge; styles is required and may be empty. Group types are SHOW_CASE, PRE_EDUCATION, SELECTION, REGULAR, PRE_SCHOOLER. Styles are HIPHOP, JAZZ, MODERN, STREETDANCE, CLASSIC, BREAKDANCE.

School responses include dancerIds, danceGroupIds, and showIds; group responses include dancerIds. To add or remove a dancer's group membership:

curl -X PUT http://localhost:8080/api/dance-schools/0/dance-groups/0/dancers/0
curl -X DELETE http://localhost:8080/api/dance-schools/0/dance-groups/0/dancers/0

Both operations return 204 and are idempotent for existing groups and dancers. Deleting a dancer removes all their memberships; deleting a group retains the dancers; deleting a school also deletes its dancers, groups and shows.

Shows have a name, zero to six dated dayparts (MORNING, AFTERNOON, EVENING), with unique date/type combinations. The displayed date range comes from these dayparts. Each show stores preferredDayParts, defaulting to two or one for one daypart. Ordinary groups, including PRE_EDUCATION, perform in exactly preferredDayParts dayparts; preschool groups once; showcases every daypart. See show-scheduling-rules.md for the full hard constraints, coach exceptions, ordering and optimization rules.

POST /api/dance-schools/{schoolId}/shows/{showId}/schedule returns 202 with a run identifier and starts an ordered draft calculation in the background using OR-Tools with a total thirty-minute time limit. Duplicate starts for the same show return 409; different shows may run concurrently. A feasible result is accepted without claiming optimality. Stale results and failures retain existing assignments and appear as distinct terminal run states.

GET .../shows/{showId}/schedule returns the latest run (204 if none exists). GET /api/scheduling/events streams SSE JSON objects containing a runs snapshot on connection and changes, or runs: null as a fifteen-second heartbeat. Clients recover current state on reconnection rather than requiring event replay. The production proxy disables buffering. The UI shows phase, elapsed time, solution discovery and final objective scores, and reports completion in an application-wide snackbar. Reloading recovers the latest state without replaying old notifications. The latest run and saved assignments survive restart. Startup marks formerly running calculations INTERRUPTED, preserves the previous assignments, and allows a new run. Results and successful status commit in one database transaction.

PUT .../shows/{showId}/assignments accepts {"assignments":[[1,2],[3]]}, an ordered list of group IDs per daypart. Incomplete or invalid drafts can be saved; unknown IDs and malformed lists are rejected. POST .../shows/{showId}/finalize checks every hard rule before marking a schedule final. Show responses include preferredDayParts, finalized and violations. Relevant edits invalidate final status. The UI supports ordering and moving performances, and displays violations.

GET .../coaches lists coach registrations. POST .../coaches with {"dancerId":1} registers a dancer as a coach. PUT .../coaches/{coachId} uses the same body to link an existing coach. DELETE .../coaches/{coachId}/dancer removes the identity link. The dancers screen supports maintaining these links.

School JSON exports use format version 3, including preferredDayParts, schedule status and coach-to-dancer links. Version 2 remains importable; its schedules migrate to drafts with default preferredDayParts. Version 1 is unsupported. Legacy appearances fields are retained for file compatibility; S18 scheduling uses preferredDayParts and fixed type exceptions. The REST appearances input is deprecated; use preferredDayParts.

Run the HTTP integration tests with:

mvn -pl agiledancers-be -Dtest=SchoolManagementResourceTest test

Angular frontend

The Angular 22 frontend is in agiledancers-fe, next to agiledancers-be. It provides Dutch-language screens to manage schools, dancers, groups, memberships and shows through the REST API.

Use Node.js 22.22.3+ (22.x), 24.15.0+ (24.x), or 26+. Start the backend as described above, then run in a second terminal:

cd agiledancers-fe
npm ci
npm start

Open http://localhost:4200. The development proxy forwards API requests to the backend on port 8080. Frontend builds and tests run independently of Maven: npm run build and npm test. See the frontend README for browser tests and production hosting requirements.

Frontend lint and formatting

Run these commands from agiledancers-fe/:

npm run lint
npm run lint:fix

lint checks maintained application code, inline and external Angular templates, unit tests, Playwright tests and JavaScript/TypeScript configuration. It uses the recommended JavaScript, Angular and type-aware TypeScript rules. It changes no files; both errors and warnings cause failure. Dependencies, generated output and package-lock.json are excluded. lint:fix applies ESLint's available fixes; review the diff and resolve any remaining findings manually. Prettier retains responsibility for formatting with the existing configuration.

Every branch pipeline runs npm run lint immediately after frontend npm ci, before frontend tests/builds and container E2E. Failure prevents image publication; findings are in the job log and retained ci/artifacts/verify.log. CI only checks and never fixes or stages source files.

Local npm ci automatically installs Husky at the repository root. Its commit hook runs Prettier only, through lint-staged with backup, rollback and hiding of unstaged changes in partially staged files enabled. It formats applicable staged frontend TypeScript, JavaScript, HTML, CSS and JSON files, then stages the formatting. Unrelated unstaged edits and files remain outside the commit. A formatter error or a conflict restoring unstaged edits aborts the commit and restores the original staged and unstaged state. Generated files are excluded; commits without applicable frontend changes skip these checks.

The complete Prettier-plus-ESLint hook exceeds the agreed three-second budget when all maintained files are staged. The selected Prettier-only hook meets the budget for single-file, mixed and all-file commits. ESLint is therefore run explicitly during development and always by CI. See S2 validation for the measured environment, timings and preservation checks.

Hook installation is skipped when CI is set (including Docker builds) or HUSKY=0. Use HUSKY=0 npm ci to skip local installation, HUSKY=0 git commit to skip one hook execution, or git config --local core.hooksPath /dev/null to disable hooks for this clone. A subsequent normal npm ci enables them again.

VS Code recommends the ESLint extension in both root and frontend workspaces. With it installed, findings appear in the editor and Problems panel; the root workspace selects agiledancers-fe/ as ESLint's working directory. No automatic fixing or formatting on save is required. npm run lint uses the same rules as CI.

Gitea CI

Every branch push builds and tests both applications and their Docker images. Only successful main runs publish images to the Synology registry. See the CI management guide for runner setup, registry secrets, image names, diagnostics and recovery, and validation evidence for the current operational status.

Production releases and rollback use separate manual Gitea workflows. See the deployment runbook for Synology provisioning, semantic version tags, release/retry/rollback and the accepted data-loss limitation during the one-time transition from an in-memory release, and S6 evidence for deployment readiness.

Java formatting (S8)

Maven is the authoritative formatting path. The root build runs the actual Palantir Java Format 2.101.0, Palantir style, through a custom OpenRewrite 8.63.4 text recipe in generated/tooling/format/Format.java. The pinned exec-maven-plugin 3.6.3 launches that helper with JDK 25. Native import ordering and unused-import removal are enabled; extra Javadoc formatting is off. No OpenRewrite AutoFormat or additional refactoring recipes run.

From the repository root:

mvn -N exec:exec@format                       # apply, without application compilation
mvn -N exec:exec@format -Dformat.mode=check   # check, without application compilation
mvn compile                                 # applies during validate, before compilation
mvn -Dformat.mode=check clean verify         # CI: fails before compilation on differences

Root compile, test, verify and install may rewrite maintained Java files in the validate phase. CI's Java orchestration passes -Dformat.mode=check and never repairs source files. On failure, the output lists paths and the apply command. Apply locally, inspect and commit the result, then repeat the check. Run root Maven commands; selecting only the backend with -pl agiledancers-be bypasses the root formatting execution.

The scope is every .java file recursively under agiledancers-be/src/main/java, agiledancers-be/src/test/java, ci (including the runner helper), and the formatter helper itself. Generated target files and frontend files are excluded. The adapter keeps paths and UTF-8 content, computes changes before writing, and uses atomic per-file replacements. Unsupported syntax fails with its path/cause. The source launcher compiles the adapter directly in memory from a clean checkout; no custom recipe jar needs compiling or installing beforehand. Maven downloads only pinned external tooling dependencies on first use.

Successfully checked/formatted content hashes are cached in target/palantir-cache.properties; new or changed files are always processed. The root POM, adapter source, dependency classpath and JDK version invalidate the cache. clean removes it. Check and apply share the engine, options, scope and successful-content cache; a failing check does not write sources or update the cache. For controlled timing comparisons only, -Dformat.skip=true disables the execution.

Editors are optional. Palantir supplies an IntelliJ plugin (see the upstream instructions). Use it only with the same engine version/style and Javadoc setting; if the editor cannot match them, use Maven to format instead. Editor import optimization or format-on-save must not override Maven's result. Other editors can invoke the standalone Maven apply command as an external tool.

Verification and measurements are recorded in the S8 evidence. Reproduce the isolated failure, UTF-8, import, cache and idempotence checks with the formatter integration test in engineering (python3 tests/format_integration.py), and the sequential end-to-end build measurements with python3 tooling/format/benchmark.py (requires the ordinary backend test prerequisites). Results are written under target/.

Backend coding standards (S7)

Read CODING_STANDARDS.md before changing backend Java. It defines the agreed rules, exact metric limits, manual review duties, pinned analysis tools, coverage policy and standalone commands. mvn verify runs standards checks and requires at least 80% module-wide backend line coverage. CI also collects and merges coverage from Playwright before publication. Reports are retained with CI diagnostics. See S7 validation for current migration status.

Existing-school mutations require X-School-Revision with the revision loaded by the editor. GET /api/dance-schools/{id}/data returns the school and all its editor data from one consistent snapshot; school.revision identifies that snapshot. Missing or stale revisions receive 409 with a reload instruction. The import preview returns existingRevision for replacement confirmation. Creation of a new school does not need a revision. The frontend never automatically retries a conflicting edit with a newer revision.

Domain-to-view projection is generated by MapStruct in rest.mapping.DomainViewMapper. It is a CDI bean with unmappedTargetPolicy = ERROR, so unmatched target fields fail compilation. REST resources supply calculated validation results explicitly. The entity-to-domain graph mappings remain handwritten in the repository layer.

Description
No description provided
Readme 1.9 MiB
Languages
Java 72.5%
TypeScript 14.1%
HTML 3.6%
CSS 3.1%
Python 2.9%
Other 3.8%