Initial commit: Gleisnetz-Editor JavaFX application#1
Conversation
- AGENTS.md with project overview, commands, and clean/structured conventions - Maven build (pom.xml) targeting Java 21 with JavaFX 21 and JUnit 5 - JPMS module descriptor and base package com.example.vibeapp - Runnable JavaFX app: App entry point, FXML main view, CSS styling - Greeter domain class kept UI-free, covered by unit tests - Expand .gitignore for Maven target/ and common IDE files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
Replace the demo scaffold with the first feature increment: - model: TrackNode, TrackEdge, Signal, Side, TrackNetwork — pure, UI-free domain. Signals use linear referencing (edge + relative position 0..1 + side) so they follow the track when nodes move. - geometry: Vec2 and Geometry with pure linear-referencing math (point at t, nearest param on a segment, perpendicular offset) — fully unit-tested. - view: NetworkEditorController (tool-mode editing: select/node/edge/signal, drag to move nodes) and NetworkRenderer drawing the model on a Canvas. - SampleNetworks.switzerland(): schematic Swiss topology with an example signal. - FXML/CSS UI, module-info wiring, and 11 unit tests (geometry + model). Update AGENTS.md to document the layered architecture and editing model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
- Methods: at most 3 parameters - Classes: at most 500 LOC - Add a Quality tooling section (Checkstyle/SpotBugs/PMD/JaCoCo) describing how the guardrails are enforced in the build Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
Wire four quality gates into the verify phase so the guardrails and common bug patterns fail the build automatically: - Checkstyle: ParameterNumber max 3 (methods and constructors) + FileLength max 500 (config/checkstyle/checkstyle.xml). module-info.java excluded. - PMD: curated small ruleset for dead code / likely bugs; @FXML methods exempt from UnusedPrivateMethod (config/pmd/ruleset.xml). - SpotBugs: bytecode analysis (effort Max, threshold Medium); intentional model exposure excluded (config/spotbugs/exclude.xml). - JaCoCo: >= 80% line coverage on model + geometry (view/App excluded). Refactor data-class constructors to satisfy the 3-parameter guardrail: - Introduce EdgeRef (edgeId + position + side) as the reusable linear-reference value object; Signal now holds an EdgeRef. - TrackNode/TrackEdge default their label to the id (constructors drop the label parameter); add TrackNetwork.addNode(label, x, y). - Suppress the two genuine 5-parameter methods (Geometry.offsetPoint, NetworkRenderer.render) inline with a documented reason. Add model tests (EdgeRef, Signal, TrackNode/Edge, Side) to back the coverage gate. Document the guardrails and tooling in AGENTS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
There was a problem hiding this comment.
Code Review
This pull request introduces the Gleisnetz-Editor, a JavaFX desktop application for drawing topological rail networks, complete with domain models, geometry utilities, unit tests, and a Maven build configuration with quality tooling (Checkstyle, PMD, SpotBugs, JaCoCo). Feedback on the changes highlights several improvements: fixing the canvas resizing logic to allow the window to shrink, restricting mouse interactions to the primary button, safely handling potentially missing nodes in the controller to prevent crashes, and validating against self-loop edges in the network model.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| canvas.widthProperty().bind(canvasHolder.widthProperty()); | ||
| canvas.heightProperty().bind(canvasHolder.heightProperty()); | ||
| canvas.widthProperty().addListener((obs, old, now) -> redraw()); | ||
| canvas.heightProperty().addListener((obs, old, now) -> redraw()); |
There was a problem hiding this comment.
Binding the width and height of a standard Canvas directly to its parent Pane's properties prevents the parent pane from shrinking. In JavaFX, a Canvas is not resizable by default, so its size is treated as a hard minimum constraint during layout passes once it has grown. To allow the window and canvas to shrink properly, use listeners on the parent pane's dimensions to manually update the canvas size and trigger a redraw.
| canvas.widthProperty().bind(canvasHolder.widthProperty()); | |
| canvas.heightProperty().bind(canvasHolder.heightProperty()); | |
| canvas.widthProperty().addListener((obs, old, now) -> redraw()); | |
| canvas.heightProperty().addListener((obs, old, now) -> redraw()); | |
| canvasHolder.widthProperty().addListener((obs, old, now) -> { | |
| canvas.setWidth(now.doubleValue()); | |
| redraw(); | |
| }); | |
| canvasHolder.heightProperty().addListener((obs, old, now) -> { | |
| canvas.setHeight(now.doubleValue()); | |
| redraw(); | |
| }); |
| private void onMousePressed(MouseEvent event) { | ||
| double x = event.getX(); | ||
| double y = event.getY(); |
There was a problem hiding this comment.
The onMousePressed handler currently reacts to any mouse button press (including right-clicks and middle-clicks). This can lead to accidental node, edge, or signal creation when the user is trying to perform other actions (like opening a context menu). It is safer to restrict these tool interactions to the primary (left) mouse button.
private void onMousePressed(MouseEvent event) {
if (event.getButton() != javafx.scene.input.MouseButton.PRIMARY) {
return;
}
double x = event.getX();
double y = event.getY();| for (TrackEdge edge : network.edges()) { | ||
| TrackNode from = network.node(edge.fromNodeId()).orElseThrow(); | ||
| TrackNode to = network.node(edge.toNodeId()).orElseThrow(); | ||
| Vec2 a = new Vec2(from.x(), from.y()); | ||
| Vec2 b = new Vec2(to.x(), to.y()); | ||
| double distance = Geometry.distanceToSegment(p, a, b); | ||
| if (distance <= closestDistance) { | ||
| closestDistance = distance; | ||
| closestParam = Geometry.nearestParam(p, a, b); | ||
| closestEdge = edge; | ||
| } | ||
| } |
There was a problem hiding this comment.
Using orElseThrow() to retrieve the from and to nodes of an edge can cause a NoSuchElementException and crash the application if there is any inconsistency in the network model (e.g., if a node is deleted or missing). It is safer to use orElse(null) and skip the edge if either node is missing, which is also consistent with how NetworkRenderer handles edge rendering.
for (TrackEdge edge : network.edges()) {
TrackNode from = network.node(edge.fromNodeId()).orElse(null);
TrackNode to = network.node(edge.toNodeId()).orElse(null);
if (from == null || to == null) {
continue;
}
Vec2 a = new Vec2(from.x(), from.y());
Vec2 b = new Vec2(to.x(), to.y());
double distance = Geometry.distanceToSegment(p, a, b);
if (distance <= closestDistance) {
closestDistance = distance;
closestParam = Geometry.nearestParam(p, a, b);
closestEdge = edge;
}
}| public TrackEdge addEdge(String fromNodeId, String toNodeId) { | ||
| if (!nodes.containsKey(fromNodeId) || !nodes.containsKey(toNodeId)) { | ||
| throw new IllegalArgumentException( | ||
| "Edge references unknown node: " + fromNodeId + " -> " + toNodeId); | ||
| } |
There was a problem hiding this comment.
An edge connecting a node to itself (a self-loop) is topologically invalid for a rail network. Consider adding a validation check in addEdge to prevent self-loops.
| public TrackEdge addEdge(String fromNodeId, String toNodeId) { | |
| if (!nodes.containsKey(fromNodeId) || !nodes.containsKey(toNodeId)) { | |
| throw new IllegalArgumentException( | |
| "Edge references unknown node: " + fromNodeId + " -> " + toNodeId); | |
| } | |
| public TrackEdge addEdge(String fromNodeId, String toNodeId) { | |
| if (java.util.Objects.equals(fromNodeId, toNodeId)) { | |
| throw new IllegalArgumentException("Cannot create self-loop edge on node: " + fromNodeId); | |
| } | |
| if (!nodes.containsKey(fromNodeId) || !nodes.containsKey(toNodeId)) { | |
| throw new IllegalArgumentException( | |
| "Edge references unknown node: " + fromNodeId + " -> " + toNodeId); | |
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb50edf3d1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| double x = event.getX(); | ||
| double y = event.getY(); | ||
| switch (tool) { | ||
| case NODE -> network.addNode(x, y); |
There was a problem hiding this comment.
Avoid adding nodes on occupied clicks
When the NODE tool is active and the user clicks an existing node or within its hit radius, this unconditionally creates another node at the same spot even though AGENTS.md's editing model says NODE clicks add nodes in empty space. That leaves visually overlapping nodes; hit-testing then prefers the last closest node, so the original can become effectively unselectable and new edges may attach to an invisible duplicate. Guard with findNodeAt(x, y) before calling addNode.
Useful? React with 👍 / 👎.
Run the test suite and all quality gates (Checkstyle, PMD, SpotBugs, JaCoCo) on every push to main and every pull request, so the guardrails are enforced in CI and not only locally. Headless build — the app is never launched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
- Canvas resize (Gemini, high): the Canvas is a non-resizable managed child, so binding its size pinned the holder's minimum and stopped the window from shrinking. Make the canvas unmanaged and drive its size from holder listeners. - Restrict tool clicks to the primary mouse button so secondary/middle clicks no longer create elements by accident (Gemini). - handleSignalClick: use orElse(null)+skip instead of orElseThrow() when an edge's nodes are missing, matching the renderer's defensive handling (Gemini). - TrackNetwork.addEdge: reject self-loops as a model invariant, with a test (Gemini). - NODE tool: only add a node on empty space; guard with findNodeAt so clicking an existing node no longer stacks an overlapping duplicate (Codex). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH
Summary
This is the initial commit of Gleisnetz-Editor, a JavaFX desktop application for editing topological rail networks. The application allows users to create and manipulate track nodes, edges, and edge-referenced domain data (signals) using a canvas-based editor with a toolbar-driven tool system.
Key Changes
Domain Model (
modelpackage)TrackNode,TrackEdge,Signal— immutable/semi-mutable domain entities with auto-generated sequential IDsTrackNetwork— container managing nodes, edges, and signals with validationEdgeRef— immutable linear-reference value object (edge + position + side) for positioning fachdatenSideenum — LEFT/RIGHT relative to edge directionSampleNetworks— factory providing a schematic Swiss rail network demoGeometry (
geometrypackage)Vec2— minimal immutable 2D vectorGeometry— pure static methods for linear referencing: point interpolation, nearest parameter on segment, perpendicular offset for signal placementView Layer (
viewpackage)NetworkEditorController— handles tool selection (SELECT/NODE/EDGE/SIGNAL), mouse input, and transient editing state (dragged node, pending edge)NetworkRenderer— renders the network onto a JavaFX Canvas with color-coded nodes, edges, and signalsnetwork-editor.fxml) with toolbar, canvas, and status barstyles.css)Application Entry Point
App— bootstraps JavaFX, loads FXML, and displays the editor windowTesting
TrackNetworkTest,TrackElementsTest,SignalTest,EdgeRefTest,SampleNetworksTest)GeometryTest) validating linear-reference mathsrc/test/javamirroring package structureBuild & Quality
pom.xmlwith JavaFX 21, JUnit 5, and quality pluginsmodule-info.java) with proper FXML opens and exportsDocumentation
AGENTS.md— comprehensive guidance for AI coding agents on architecture, conventions, and guardrailsNotable Implementation Details
viewpackage imports JavaFX.Vec2andEdgeRefare records with no side effects, keeping geometry pure and testable.https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH