Skip to content

Initial implementation of JavaFX Chat App with ntfy integration#34

Merged
fmazmz merged 33 commits intomainfrom
dev
Nov 18, 2025
Merged

Initial implementation of JavaFX Chat App with ntfy integration#34
fmazmz merged 33 commits intomainfrom
dev

Conversation

@fmazmz
Copy link
Member

@fmazmz fmazmz commented Nov 16, 2025

Summary by CodeRabbit

  • New Features

    • Added a chat messaging interface with message display, input fields for titles and tags, and support for file attachments.
    • Implemented a dark theme with styled messaging components.
  • Tests

    • Added unit tests for message handling and thread-safe operations.

@coderabbitai
Copy link

coderabbitai bot commented Nov 16, 2025

Walkthrough

This PR introduces a chat application with Ntfy integration. It adds a network client interface and HTTP implementation for messaging, domain models for Ntfy events and messages, refactors the UI controller to support network-driven messaging with file attachments, implements environment-based configuration via .env loading, adds CSS styling, and includes test utilities for JavaFX testing.

Changes

Cohort / File(s) Summary
Configuration & Build
.gitignore, pom.xml, src/main/java/module-info.java
Added .env to gitignore; added SLF4J, Jackson, TestFX, and JavaFX-Swing dependencies; configured Maven compiler to Java 25; updated module dependencies and opens/exports for Jackson and new packages.
Network Client Layer
src/main/java/com/example/client/ChatNetworkClient.java, src/main/java/com/example/client/NtfyHttpClient.java, src/main/java/com/example/client/HttpClientProvider.java
Introduced ChatNetworkClient interface defining subscribe() and send() contracts; implemented NtfyHttpClient record handling async HTTP GET subscriptions and POST/PUT sends with JSON/file payloads; added HttpClientProvider singleton for shared HttpClient instance.
Domain Models
src/main/java/com/example/domain/NtfyEventResponse.java, src/main/java/com/example/domain/NtfyMessage.java, src/main/java/com/example/domain/ChatModel.java
Added NtfyEventResponse and Attachment records for deserializing Ntfy events; added NtfyMessage record with Builder for constructing outbound messages; added ChatModel class maintaining observable message list with FX-thread-safe addMessage() and fallback mode.
UI Layer
src/main/java/com/example/HelloController.java, src/main/java/com/example/HelloFX.java, src/main/resources/com/example/hello-view.fxml, src/main/resources/com/example/styles.css
Refactored HelloController to support network client injection, message list rendering via custom MessageCell, file attachment picking/sending, and time-formatted display; updated HelloFX to load environment variables, wire controller with ChatModel and client, and subscribe to topic; replaced StackPane UI with VBox layout including ListView, inputs, and action buttons; added dark-themed CSS with message bubbles and styled inputs.
Utilities
src/main/java/com/example/utils/EnvLoader.java
Added EnvLoader utility to load .env files into Properties with IOException wrapping.
Testing
src/test/java/com/example/ChatModelTest.java, src/test/java/com/example/TestFxInitializer.java
Added ChatModelTest with three tests for message addition (sync, async, fallback modes); added TestFxInitializer base class to guard JavaFX Platform initialization.

Sequence Diagrams

sequenceDiagram
    participant App as HelloFX
    participant Ctrl as HelloController
    participant Client as NtfyHttpClient
    participant HTTP as HTTP Service
    participant Model as ChatModel

    App->>App: Load env vars (NTFY_BASE_URL, NTFY_TOPIC)
    App->>Ctrl: setClient(client, baseUrl, topic)
    App->>Ctrl: setModel(model)
    Ctrl->>Client: subscribe(baseUrl, topic)
    Client->>HTTP: GET baseUrl/topic/json (async)
    HTTP-->>Client: Stream NtfyEventResponse lines
    Client->>Client: Parse JSON, filter "message" events
    Client->>Model: addMessage(event)
    Model->>Model: runOnFx: enqueue on FX thread
    Model->>Ctrl: messagesList observes update
    Ctrl->>Ctrl: MessageCell renders message
Loading
sequenceDiagram
    participant User as User
    participant Ctrl as HelloController
    participant Client as NtfyHttpClient
    participant HTTP as HTTP Service

    User->>Ctrl: Fill title, message, tags
    User->>Ctrl: Click Send (optional attachment)
    Ctrl->>Ctrl: onSend() builds NtfyMessage
    alt Attachment selected
        Ctrl->>Client: send(baseUrl, message, file)
        Client->>HTTP: PUT baseUrl/topic with file
    else No attachment
        Ctrl->>Client: send(baseUrl, message, null)
        Client->>HTTP: POST baseUrl JSON payload
    end
    HTTP-->>Client: Response
    Client-->>Ctrl: Completes (or error)
    Ctrl->>Ctrl: Reset UI, clear inputs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Key areas requiring attention:

  • NtfyHttpClient.subscribe(): Async HTTP streaming with cancellation logic; verify proper stream handling, error propagation, and CompletableFuture lifecycle management.
  • ChatModel.runOnFx(): FX thread-safety logic with fallback mode; ensure Platform.startup() guards and IllegalStateException handling are robust for various initialization scenarios.
  • HelloController.onSend(): Message construction, optional file attachment, and network invocation; verify error handling and UI state reset paths.
  • Jackson deserialization: NtfyEventResponse with nested Attachment records; validate @JsonIgnoreProperties behavior and null-safety for optional fields.
  • Module visibility: Verify com.example.domain opens to Jackson and exports are correct for compile-time and runtime module resolution.

Poem

🐰 New connections spring to life,
Messages flow without strife,
Attachments dance through network streams,
Dark UI dreams with bubbling schemes,
Chat blooms where Ntfy beams! 📨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Initial implementation of JavaFX Chat App with ntfy integration' accurately reflects the main change: introducing a complete JavaFX chat application with ntfy service integration across multiple new files and refactored components.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide custom instructions to shape the summary (bullet lists, tables, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example:

"Create a concise high-level summary as a bullet-point list. Then include a Markdown table showing lines added and removed by each contributing author."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (3)
src/main/java/com/example/HelloFX.java (1)

22-22: Consider refactoring static model field.

Using a static field for the ChatModel in an Application class is unusual. JavaFX Application instances are typically instantiated by the framework, and static fields can lead to issues with testing and lifecycle management.

Consider making it an instance field:

-static final ChatModel model = new ChatModel();
+private final ChatModel model = new ChatModel();

If the model needs to be shared across components, consider using dependency injection or passing it explicitly through constructors/setters rather than relying on static access.

src/main/java/com/example/client/ChatNetworkClient.java (1)

9-17: Consider adding JavaDoc to document the public API.

This interface defines the contract for network clients but lacks documentation. Consider adding JavaDoc comments to clarify:

  • The purpose and behavior of subscribe and send methods
  • Expected exceptions and their causes
  • The lifecycle of the Subscription interface
src/main/java/com/example/domain/NtfyMessage.java (1)

60-62: Consider defensive copying of the tags list.

The tags() builder method stores the list reference directly without making a defensive copy. If the caller retains and modifies the list after passing it to the builder, it could affect the built message.

Apply this diff for defensive copying:

 public Builder tags(List<String> tags) {
-    this.tags = tags;
+    this.tags = tags != null ? List.copyOf(tags) : null;
     return this;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daa1037 and 945a5ec.

📒 Files selected for processing (16)
  • .gitignore (1 hunks)
  • pom.xml (3 hunks)
  • src/main/java/com/example/HelloController.java (1 hunks)
  • src/main/java/com/example/HelloFX.java (1 hunks)
  • src/main/java/com/example/client/ChatNetworkClient.java (1 hunks)
  • src/main/java/com/example/client/HttpClientProvider.java (1 hunks)
  • src/main/java/com/example/client/NtfyHttpClient.java (1 hunks)
  • src/main/java/com/example/domain/ChatModel.java (1 hunks)
  • src/main/java/com/example/domain/NtfyEventResponse.java (1 hunks)
  • src/main/java/com/example/domain/NtfyMessage.java (1 hunks)
  • src/main/java/com/example/utils/EnvLoader.java (1 hunks)
  • src/main/java/module-info.java (1 hunks)
  • src/main/resources/com/example/hello-view.fxml (1 hunks)
  • src/main/resources/com/example/styles.css (1 hunks)
  • src/test/java/com/example/ChatModelTest.java (1 hunks)
  • src/test/java/com/example/TestFxInitializer.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/com/example/client/HttpClientProvider.java (1)
src/main/java/com/example/ChatModel.java (1)
  • ChatModel (28-30)
src/test/java/com/example/ChatModelTest.java (2)
src/test/java/com/example/TestFxInitializer.java (1)
  • TestFxInitializer (6-21)
src/main/java/com/example/domain/ChatModel.java (1)
  • ChatModel (8-36)
src/main/java/com/example/HelloController.java (1)
src/main/java/com/example/domain/ChatModel.java (1)
  • ChatModel (8-36)
src/main/java/com/example/client/NtfyHttpClient.java (2)
src/main/java/com/example/domain/ChatModel.java (1)
  • ChatModel (8-36)
src/main/java/com/example/client/HttpClientProvider.java (1)
  • HttpClientProvider (5-15)
src/main/java/com/example/HelloFX.java (2)
src/main/java/com/example/domain/ChatModel.java (1)
  • ChatModel (8-36)
src/main/java/com/example/utils/EnvLoader.java (1)
  • EnvLoader (7-19)
🪛 Biome (2.1.2)
src/main/resources/com/example/styles.css

[error] 3-3: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 8-8: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 9-9: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 13-13: Unexpected unknown pseudo-class filled

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)


[error] 13-13: Unexpected unknown pseudo-class selected

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)


[error] 14-14: Unexpected unknown pseudo-class filled

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)


[error] 14-14: Unexpected unknown pseudo-class selected

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)


[error] 14-14: Unexpected unknown pseudo-class focused

See MDN web docs for more details.

(lint/correctness/noUnknownPseudoClass)


[error] 15-15: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 20-20: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 25-25: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 26-26: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 31-31: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 32-32: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 33-33: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 34-34: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 34-34: Unexpected unknown function: dropshadow

Use a known function instead.
See MDN web docs for more details.

(lint/correctness/noUnknownFunction)


[error] 39-39: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 40-40: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 40-40: Unexpected unknown function: dropshadow

Use a known function instead.
See MDN web docs for more details.

(lint/correctness/noUnknownFunction)


[error] 45-45: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 46-46: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 47-47: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 52-52: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 53-53: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 58-58: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 59-59: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 64-64: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 65-65: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 70-70: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 71-71: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 72-72: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 73-73: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 74-74: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 79-79: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 80-80: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 81-81: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)


[error] 85-85: Unknown property is not allowed.

See CSS Specifications and browser specific properties for more details.
To resolve this issue, replace the unknown property with a valid CSS property.

(lint/correctness/noUnknownProperty)

🔇 Additional comments (9)
.gitignore (1)

3-3: LGTM! Properly excluding environment configuration.

Adding .env to .gitignore is essential to prevent accidentally committing sensitive configuration like API keys or URLs.

src/main/resources/com/example/hello-view.fxml (1)

5-43: LGTM! Well-structured FXML layout.

The FXML defines a clean VBox-based layout with proper controller bindings (fx:id) and action handlers. The UI components are appropriately configured for a chat interface with message list, input fields, and action buttons.

src/test/java/com/example/TestFxInitializer.java (1)

6-21: LGTM! Proper JavaFX test initialization.

The TestFxInitializer correctly handles JavaFX Platform initialization for tests with appropriate guards for the already-started case. The static flag ensures one-time initialization across test classes.

src/main/resources/com/example/styles.css (1)

1-86: LGTM! JavaFX CSS is correctly implemented (ignore static analysis).

The dark theme styling is well-structured and appropriate for JavaFX. The static analysis errors from Biome are false positives—Biome is a web CSS linter that doesn't recognize JavaFX-specific properties (prefixed with -fx-) and pseudo-classes (:filled, :selected, :focused).

All the properties and pseudo-classes used here are valid JavaFX CSS constructs. The styling provides a cohesive dark theme with proper hover states, rounded corners, and visual hierarchy.

pom.xml (1)

12-12: Java 25 is production-ready and appropriate for this configuration.

Java 25 reached General Availability on September 16, 2025. Java 25 is the latest Java LTS release and was made generally available on September 16, 2025. JDK 25 receives quarterly security and performance updates until September 2028 under Oracle's No-Fee Terms and Conditions, with updates after that date offered under the Java SE OTN License planned until at least September 2033. The maven.compiler.release=25 configuration is valid and suitable for production use.

src/main/java/com/example/client/HttpClientProvider.java (1)

5-14: Original review comment is incorrect.

The assertion that "ChatModel creates its own HttpClient instance via HttpClient.newHttpClient()" is unfounded. ChatModel is a domain model that manages messages and JavaFX threading—it has no involvement with HttpClient whatsoever.

Verification shows the codebase actually uses HttpClient consistently:

  • HttpClientProvider correctly implements a singleton at line 7
  • All actual HttpClient usage in NtfyHttpClient (lines 35, 91, 107) properly delegates to HttpClientProvider.get()
  • No other direct instantiations of HttpClient exist elsewhere

The centralized HttpClient management is working as intended.

Likely an incorrect or invalid review comment.

src/main/java/com/example/domain/NtfyEventResponse.java (1)

7-26: LGTM!

The record structure is well-designed for JSON deserialization with appropriate field types and proper use of @JsonIgnoreProperties to handle API evolution gracefully.

src/main/java/module-info.java (1)

4-15: LGTM!

The module configuration correctly declares all dependencies and appropriately opens the domain package to Jackson for reflection-based deserialization.

src/main/java/com/example/client/NtfyHttpClient.java (1)

82-93: The original review comment is incorrect. The code properly implements two different ntfy API modes with appropriate URL handling for each.

The ntfy JSON publish mode requires posting JSON to the ntfy root URL (e.g., https://ntfy.sh/) with the "topic" field in the JSON body. The sendJsonOnly method correctly implements this: it posts to baseUrl directly (line 86) and the NtfyMessage record includes the topic field, which is serialized into the JSON body.

In contrast, sendWithAttachment uses the simple/non-JSON publish mode, which expects the topic in the URL path (e.g., https://ntfy.sh/mytopic). This is why it constructs baseUrl + "/" + msg.topic() on line 98.

These are not inconsistent implementations—they correctly follow the requirements of their respective ntfy API modes. The URL handling differs because the modes themselves differ fundamentally.

Likely an incorrect or invalid review comment.

@fmazmz fmazmz merged commit 7ee18ed into ithsjava25:main Nov 18, 2025
1 check failed
This was referenced Nov 19, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 28, 2025
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.

1 participant