Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches: [ main ]
pull_request:

jobs:
build:
name: Build & verify (Java 21)
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: maven

# Runs tests and all quality gates (Checkstyle, PMD, SpotBugs, JaCoCo).
# Headless: the app is never launched, so no display is required.
- name: Build and verify
run: mvn -B verify

- name: Upload reports on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: build-reports
path: |
target/site
target/checkstyle-result.xml
target/pmd.xml
target/spotbugsXml.xml
target/surefire-reports
if-no-files-found: ignore
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Maven build output
target/

# IDE files
.idea/
*.iml
.vscode/
.settings/
.classpath
.project

# Compiled class file
*.class

Expand Down
166 changes: 166 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# AGENTS.md

Guidance for AI coding agents (Claude Code and compatible tools) working in this
repository. Read this before making changes.

## Project overview

**Gleisnetz-Editor** is a desktop **JavaFX** application for drawing a
**topological rail network** and attaching domain data to it:

- **Knoten / nodes** (`TrackNode`) — points in the network (stations, junctions).
- **Kanten / edges** (`TrackEdge`) — track edges connecting two nodes.
- **Fachdaten** such as **signals** (`Signal`) positioned by **linear
referencing**: a signal belongs to an edge and sits at a relative position
(0.0 = from-node … 1.0 = to-node) on a chosen side. It has no coordinates of
its own, so it stays correct when nodes move.

The layout is **topological, not geographic**: node coordinates are schematic
(canvas pixels) and express connectivity, not real-world position.

- **Language:** Java 21 (LTS) · **UI:** JavaFX 21 (FXML + Canvas + CSS)
- **Build:** Maven · **Tests:** JUnit 5 · **Modules:** JPMS

## Architecture (keep these layers separate)

```
com.example.vibeapp App JavaFX entry point
com.example.vibeapp.model TrackNode, TrackEdge, Signal, Side, EdgeRef,
TrackNetwork pure domain model (no JavaFX imports)
SampleNetworks example topological Switzerland
com.example.vibeapp.geometry Vec2, Geometry linear-referencing math (no JavaFX)
com.example.vibeapp.view NetworkEditorController tool state + mouse input
NetworkRenderer draws the model on a Canvas
```

- **`model` and `geometry` are UI-free** and fully unit-tested — this is the
core invariant. Keep JavaFX types out of them so the domain logic stays
testable without the toolkit.
- **`view` is the only package that may import JavaFX.** The controller owns
transient editing state (active tool, drag/edge-pending selection) and turns
input into model operations; the renderer only draws.

### Resources & module wiring

```
src/main/resources/com/example/vibeapp/
network-editor.fxml toolbar + canvas + status bar (fx:controller = NetworkEditorController)
styles.css dark theme
src/main/java/module-info.java opens ...view to javafx.fxml; exports base package
```

## Architecture guardrails

Hard limits that keep classes and methods small and readable. New and changed
code must respect them — refactor rather than exceed them.

- **Methods: at most 3 parameters.** If a method needs more, introduce a
parameter object / small record, or split the method. The limit targets
methods; a simple data holder's constructor may carry the fields it needs, but
when it would exceed ~4 prefer a builder, a factory, or a `record`.
- **Classes: at most 500 lines of code (LOC).** A class that grows past this is
doing too much — split it by responsibility.

These limits are meant to be enforced by static analysis in the build (see
[Quality tooling](#quality-tooling)); `mvn verify` should fail on a violation
rather than relying on review to catch it.

## Commands

Run from the repository root.

| Task | Command |
| ------------------- | ------------------- |
| Build | `mvn compile` |
| Run the tests | `mvn test` |
| Full verify | `mvn verify` |
| Run the app | `mvn javafx:run` |
| Clean | `mvn clean` |

> `mvn javafx:run` needs a graphical display. In a headless environment it will
> not open a window — use `mvn test` / `mvn verify` there. (A scene can be
> rendered headless via `Scene.snapshot` under `xvfb-run` with
> `-Dprism.order=sw` if a preview image is needed.)

## Editing model (how the UI works)

The toolbar selects a **tool**; clicks on the canvas act according to it:

- **Auswahl (SELECT):** click a node and drag to move it.
- **Knoten (NODE):** click empty space to add a node.
- **Kante (EDGE):** click a start node, then a target node to connect them.
- **Signal (SIGNAL):** click on/near an edge; the signal is placed at the
nearest position along that edge (`Geometry.nearestParam`).

`Beispielnetz` reloads the sample Switzerland; `Leeren` empties the network.

## Conventions (priority: clean & structured)

- **Domain logic stays in `model` / `geometry` with no JavaFX imports**, so it
can be unit-tested. Controllers only wire logic to the view.
- **Every non-trivial class or logic change gets a test** under
`src/test/java`, mirroring the package. Name them `<ClassName>Test`.
- **Geometry lives in `Geometry`** as pure, side-effect-free static methods —
add new linear-referencing math there and test it, don't inline it in the view.
- **FXML for layout, CSS for styling.** Don't hard-code colors/sizes in Java;
put them in `styles.css` and `*.fxml`.
- **Keep `module-info.java` correct.** New FXML-bound packages need an `opens`
entry; new third-party modules need a `requires` entry.
- **Style:** 4-space indent, one top-level class per file, standard Java naming,
short Javadoc on public classes and non-obvious methods. Small, focused commits.

## Definition of done

1. `mvn verify` passes (compiles cleanly, all tests green).
2. New or changed logic is covered by a test in `model` or `geometry`.
3. `module-info.java`, FXML `fx:controller`, and `pom.xml` `mainClass` stay
consistent with the code.
4. No unused imports, dead code, or leftover commented-out blocks.

## How to extend

- **New fachdatum type** (e.g. a balise or speed board): add a model class in
`model` referencing an edge + relative position (mirror `Signal`), store it in
`TrackNetwork`, render it in `NetworkRenderer`, add a tool in the controller.
- **New geometry** (e.g. curved edges, distance-in-meters): add pure methods to
`Geometry` (+ tests) before touching the view.
- **New screen:** add `*-view.fxml` + a controller in `view`, load it from `App`.
- **New dependency:** add it to `pom.xml` and a `requires` line in `module-info.java`.

## Quality tooling

Static analysis and coverage run in the `verify` phase, so the guardrails above
and common bug patterns fail the build automatically, not just in review. Run
everything with **`mvn verify`**. Config lives under `config/`.

| Tool | Plugin | Enforces | Config |
| ---- | ------ | -------- | ------ |
| **Checkstyle** | `maven-checkstyle-plugin` | The guardrails: `ParameterNumber` max 3 (methods **and** constructors) + `FileLength` max 500 | `config/checkstyle/checkstyle.xml` |
| **PMD** | `maven-pmd-plugin` | Dead code + likely bugs (curated, small ruleset) | `config/pmd/ruleset.xml` |
| **SpotBugs** | `spotbugs-maven-plugin` | Bytecode bug patterns (effort Max, threshold Medium) | `config/spotbugs/exclude.xml` |
| **JaCoCo** | `jacoco-maven-plugin` | Test coverage ≥ 80 % line coverage on `model` + `geometry` (`view`/`App` excluded as they need the running UI) | inline in `pom.xml` |

Conventions for the tooling:

- **Keep rulesets small and meaningful** — every enabled rule must earn its
place. Checkstyle owns the parameter/size guardrails; PMD does **not** repeat
them (no overlap/conflict).
- **Suppress narrowly and with a documented reason; never weaken a guardrail
globally.** How to suppress each tool:
- Checkstyle: `@SuppressWarnings("checkstyle:ParameterNumber")` on the member,
with a comment saying why (see `Geometry.offsetPoint`, `NetworkRenderer.render`).
- PMD: a `violationSuppressXPath` in the ruleset (e.g. `@FXML` methods are
reflectively invoked, so they are exempt from `UnusedPrivateMethod`).
- SpotBugs: a `<Match>` in `config/spotbugs/exclude.xml`.
- **`module-info.java` is excluded from Checkstyle** (it is not a class).
- The two known guardrail exceptions (`offsetPoint`, `render`, both 5 params)
are suppressed intentionally; a future refactor may fold their arguments into
value objects (`Segment`, a view-state object).

## Things to avoid

- Do not import JavaFX into `model` or `geometry`.
- Do not give fachdaten absolute coordinates — reference them to an edge.
- Do not put geometry math or magic values directly in controllers.
- Do not commit build output (`target/`) or IDE files — they are gitignored.
- Do not add a dependency without updating `module-info.java`.
30 changes: 30 additions & 0 deletions config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">

<!--
Deliberately minimal: this config enforces the two architecture guardrails
from AGENTS.md and nothing else, so every rule earns its place.
-->
<module name="Checker">
<property name="severity" value="error"/>

<!-- Guardrail: a class file may not exceed 500 lines. -->
<module name="FileLength">
<property name="max" value="500"/>
</module>

<!-- Honour inline @SuppressWarnings("checkstyle:...") annotations. -->
<module name="SuppressWarningsFilter"/>

<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>

<!-- Guardrail: methods and constructors take at most 3 parameters. -->
<module name="ParameterNumber">
<property name="max" value="3"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF"/>
</module>
</module>
</module>
31 changes: 31 additions & 0 deletions config/pmd/ruleset.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<ruleset name="vibeapp"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
https://pmd.sourceforge.io/ruleset_2_0_0.xsd">

<description>
Curated, high-signal PMD rules. Kept small on purpose: PMD catches likely
bugs and dead code here; the parameter/size guardrails are owned by
Checkstyle so the two tools do not overlap or conflict.
</description>

<!-- Likely bugs -->
<rule ref="category/java/errorprone.xml/EmptyCatchBlock"/>
<rule ref="category/java/errorprone.xml/CloseResource"/>
<rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"/>
<rule ref="category/java/errorprone.xml/CompareObjectsWithEquals"/>

<!-- Dead code -->
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateField"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateMethod">
<!-- @FXML methods are invoked reflectively by JavaFX, not from code. -->
<properties>
<property name="violationSuppressXPath"
value=".[ModifierList/Annotation[@SimpleName='FXML']]"/>
</properties>
</rule>
<rule ref="category/java/bestpractices.xml/UnusedFormalParameter"/>
</ruleset>
11 changes: 11 additions & 0 deletions config/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<!--
The domain model (TrackNode, TrackEdge, Signal) deliberately hands out its
mutable entities so the editor can move nodes and edit signals in place.
Exposing those references is intended here, not a defect.
-->
<Match>
<Bug pattern="EI_EXPOSE_REP,EI_EXPOSE_REP2"/>
</Match>
</FindBugsFilter>
Loading
Loading