Skip to content

Initial commit: Gleisnetz-Editor JavaFX application#1

Merged
rradica merged 6 commits into
mainfrom
claude/vibe-coding-agents-md-g5shgw
Jul 9, 2026
Merged

Initial commit: Gleisnetz-Editor JavaFX application#1
rradica merged 6 commits into
mainfrom
claude/vibe-coding-agents-md-g5shgw

Conversation

@rradica

@rradica rradica commented Jul 9, 2026

Copy link
Copy Markdown
Owner

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 (model package)

    • TrackNode, TrackEdge, Signal — immutable/semi-mutable domain entities with auto-generated sequential IDs
    • TrackNetwork — container managing nodes, edges, and signals with validation
    • EdgeRef — immutable linear-reference value object (edge + position + side) for positioning fachdaten
    • Side enum — LEFT/RIGHT relative to edge direction
    • SampleNetworks — factory providing a schematic Swiss rail network demo
  • Geometry (geometry package)

    • Vec2 — minimal immutable 2D vector
    • Geometry — pure static methods for linear referencing: point interpolation, nearest parameter on segment, perpendicular offset for signal placement
    • All geometry is UI-free and fully unit-tested
  • View Layer (view package)

    • 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 signals
    • FXML layout (network-editor.fxml) with toolbar, canvas, and status bar
    • Dark theme CSS (styles.css)
  • Application Entry Point

    • App — bootstraps JavaFX, loads FXML, and displays the editor window
  • Testing

    • Comprehensive unit tests for domain model (TrackNetworkTest, TrackElementsTest, SignalTest, EdgeRefTest, SampleNetworksTest)
    • Geometry tests (GeometryTest) validating linear-reference math
    • All tests in src/test/java mirroring package structure
  • Build & Quality

    • Maven pom.xml with JavaFX 21, JUnit 5, and quality plugins
    • Checkstyle guardrails: max 3 method parameters, max 500 LOC per class
    • PMD for dead code and bug patterns
    • SpotBugs for bytecode analysis
    • JaCoCo coverage gate (80% minimum on domain/geometry)
    • Module descriptor (module-info.java) with proper FXML opens and exports
  • Documentation

    • AGENTS.md — comprehensive guidance for AI coding agents on architecture, conventions, and guardrails

Notable Implementation Details

  • Separation of Concerns: Model and geometry packages have zero JavaFX imports, ensuring domain logic is testable without the toolkit. Only the view package imports JavaFX.
  • Linear Referencing: Signals are positioned on edges via a relative parameter (0.0–1.0) and side, so they automatically stay correct when nodes move.
  • Tool-Driven Interaction: The editor uses a toolbar-selected tool pattern (SELECT/NODE/EDGE/SIGNAL) that translates mouse clicks into model operations.
  • Immutable Value Objects: Vec2 and EdgeRef are records with no side effects, keeping geometry pure and testable.
  • Guardrails Enforced: Checkstyle and PMD are configured to fail the build on violations, ensuring methods stay focused and classes stay small.

https://claude.ai/code/session_019dPnZoRJALVs9vRd9i1MPH

claude added 4 commits July 8, 2026 08:15
- 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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +60 to +63
canvas.widthProperty().bind(canvasHolder.widthProperty());
canvas.heightProperty().bind(canvasHolder.heightProperty());
canvas.widthProperty().addListener((obs, old, now) -> redraw());
canvas.heightProperty().addListener((obs, old, now) -> redraw());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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();
});

Comment on lines +93 to +95
private void onMousePressed(MouseEvent event) {
double x = event.getX();
double y = event.getY();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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();

Comment on lines +137 to +148
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;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
            }
        }

Comment on lines +59 to +63
public TrackEdge addEdge(String fromNodeId, String toNodeId) {
if (!nodes.containsKey(fromNodeId) || !nodes.containsKey(toNodeId)) {
throw new IllegalArgumentException(
"Edge references unknown node: " + fromNodeId + " -> " + toNodeId);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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);
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

claude added 2 commits July 9, 2026 04:05
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
@rradica rradica merged commit f87b988 into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants