diff --git a/README.md b/README.md
index 405a3ef..2768052 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ GuiInteraction enables standalone Groovy applications to have the same user inte
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-swing:0.4.0' // or gi-fx, gi-console
+ implementation 'se.alipsa.gi:gi-swing:0.4.1' // or gi-fx, gi-console
}
```
@@ -40,14 +40,14 @@ dependencies {
se.alipsa.gi
gi-swing
- 0.4.0
+ 0.4.1
```
### Groovy Script with Grape
```groovy
-@Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0')
+@Grab('se.alipsa.gi:gi-swing:0.4.1')
import se.alipsa.gi.swing.InOut
def io = new InOut()
@@ -57,14 +57,17 @@ println("File chosen was $file")
## Gade Compatibility
-Scripts can run both in Gade and standalone by checking for the `io` variable:
+Scripts can run both in Gade and standalone by checking for the `io` variable.
+The dependency is loaded only when Gade has not already supplied `io`:
```groovy
+import static groovy.grape.Grape.grab
+
// This makes the code run equally in Gade and in a standalone Groovy script
if (!binding.hasVariable('io')) {
- @Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0')
- import se.alipsa.gi.swing.InOut
- binding.setVariable('io', new InOut())
+ grab(group: 'se.alipsa.gi', module: 'gi-swing', version: '0.4.1')
+ def inOutClass = this.class.classLoader.loadClass('se.alipsa.gi.swing.InOut')
+ binding.setVariable('io', inOutClass.newInstance())
}
def file = io.chooseFile("Choose a file", ".", "Pick a file please!")
@@ -81,6 +84,9 @@ println("File chosen was $file")
## Building from Source
+Install a JDK 21 toolchain before building. Gradle uses that toolchain even if
+the Gradle daemon runs on a newer JDK.
+
```bash
./gradlew build
```
@@ -109,7 +115,7 @@ refresh needs an API key and can be rate limited; it is not a PR gate. Run
## Requirements
-- JDK 21 or later
+- Java 21 or later to use the published libraries; JDK 21 to build from source
- For gi-fx: JDK with JavaFX support (e.g., BellSoft Liberica Full JDK)
## License
diff --git a/build.gradle b/build.gradle
index 15ed37e..b7fa17f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@ plugins {
}
group = 'se.alipsa.gi'
-version = '0.4.1-SNAPSHOT'
+version = '0.4.1'
description = 'Allows Gade Gui Interactive capabilities from a standalone app'
java {
diff --git a/docs/API-Guide.md b/docs/API-Guide.md
index 395fefa..6e640bb 100644
--- a/docs/API-Guide.md
+++ b/docs/API-Guide.md
@@ -16,7 +16,7 @@ This guide provides usage examples for the GuiInteraction library.
```groovy
// Using Grape for dependency resolution
-@Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0')
+@Grab('se.alipsa.gi:gi-swing:0.4.1')
import se.alipsa.gi.swing.InOut
def io = new InOut()
@@ -26,7 +26,7 @@ Or in your `build.gradle`:
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-swing:0.4.0'
+ implementation 'se.alipsa.gi:gi-swing:0.4.1'
}
```
@@ -326,14 +326,17 @@ assert FileUtils.baseName("/tmp/report#2.pdf") == "report#2.pdf"
## Gade Compatibility
-The library is designed to be compatible with [Gade](https://github.com/Alipsa/gade). Scripts can detect the environment:
+The library is designed to be compatible with [Gade](https://github.com/Alipsa/gade).
+Scripts can detect the environment and load gi-swing only when `io` is absent:
```groovy
+import static groovy.grape.Grape.grab
+
// Check if running in Gade or standalone
if (!binding.hasVariable('io')) {
- @Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0')
- import se.alipsa.gi.swing.InOut
- binding.setVariable('io', new InOut())
+ grab(group: 'se.alipsa.gi', module: 'gi-swing', version: '0.4.1')
+ def inOutClass = this.class.classLoader.loadClass('se.alipsa.gi.swing.InOut')
+ binding.setVariable('io', inOutClass.newInstance())
}
// Now 'io' works the same in both Gade and standalone
diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md
index a944c41..82984a7 100644
--- a/docs/DEPENDENCIES.md
+++ b/docs/DEPENDENCIES.md
@@ -18,8 +18,8 @@ The core module includes these key dependencies:
### gi-fx, gi-swing, gi-console
UI modules add minimal additional dependencies:
-- **gi-fx**: JavaFX (provided by JVM), YearMonthPicker
-- **gi-swing**: Batik (SVG), LGoodDatePicker, Swing YearMonthPicker
+- **gi-fx**: JavaFX (supplied by the JDK or consumer), fx-yearmonth-picker
+- **gi-swing**: swing-widgets (date and year-month pickers); SVG rendering uses Matrix Charts from `gi-common`
- **gi-console**: JSoup (HTML parsing)
## Apache Tika Dependency Impact
@@ -49,9 +49,9 @@ PDFs or Office documents), add the full parser package:
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-common:0.4.0'
+ implementation 'se.alipsa.gi:gi-common:0.4.1'
// Add full Tika parsers for document content extraction
- implementation 'org.apache.tika:tika-parsers-standard-package:3.2.3'
+ implementation 'org.apache.tika:tika-parsers-standard-package:3.3.2'
}
```
@@ -64,11 +64,13 @@ This adds support for:
### Fat JAR for Standalone Scripts
-The fat JARs (`gi-swing-fatjar`, `gi-fx-fatjar`, etc.) include all dependencies
-and are designed for standalone Groovy scripts:
+The fat JAR artifacts bundle runtime dependencies but exclude Groovy, which the
+consuming application must supply. `gi-fx` also needs JavaFX at runtime, either
+from the JDK or as separate dependencies. The artifact classifier is `fatjar`;
+disable transitive resolution to avoid loading separate copies of bundled dependencies:
```groovy
-@Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0', classifier:'fatjar')
+@Grab(group='se.alipsa.gi', module='gi-swing', version='0.4.1', classifier='fatjar', transitive=false)
import se.alipsa.gi.swing.InOut
```
diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md
index 50a82a2..26167b1 100644
--- a/docs/PUBLISHING.md
+++ b/docs/PUBLISHING.md
@@ -189,7 +189,7 @@ After releasing:
- [ ] Push tag: `git push origin vX.Y.Z`
- [ ] Create GitHub release: https://github.com/Alipsa/GuiInteraction/releases/new
- [ ] Verify on Maven Central (may take 10-30 minutes): https://central.sonatype.com/search?q=se.alipsa.gi
-- [ ] Bump version to next SNAPSHOT for development
+- [ ] Immediately bump version to the next SNAPSHOT for development before other changes land on main
## Troubleshooting
@@ -285,6 +285,9 @@ script generates notes from commit subjects. Both paths are idempotent for a
version already in the changelog, so `--skip` retries do not duplicate notes.
If a retry finds additional curated notes under `## Unreleased`, the script
warns that they are not included in the already-recorded version.
+If `release.md` already has a dated heading for the version, the script leaves
+that date unchanged. Check it against the actual publication date and re-date
+the heading before running a delayed release.
Commit curated notes before running `release.sh`: every release path requires
`release.md` to be clean. SNAPSHOT and `--bump` releases also require clean
diff --git a/gi-common/README.md b/gi-common/README.md
index 7e469d0..7159655 100644
--- a/gi-common/README.md
+++ b/gi-common/README.md
@@ -37,7 +37,7 @@ See the [API Guide](../docs/API-Guide.md) for detailed usage examples.
se.alipsa.gi
gi-common
- 0.4.0
+ 0.4.1
```
diff --git a/gi-console/README.md b/gi-console/README.md
index 7142c98..7b0b98c 100644
--- a/gi-console/README.md
+++ b/gi-console/README.md
@@ -26,7 +26,7 @@ This module provides console-based (text mode) interaction capabilities. It's de
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-console:0.4.0'
+ implementation 'se.alipsa.gi:gi-console:0.4.1'
}
```
@@ -36,14 +36,14 @@ dependencies {
se.alipsa.gi
gi-console
- 0.4.0
+ 0.4.1
```
### Grape (Groovy Script)
```groovy
-@Grab(group:'se.alipsa.gi', module:'gi-console', version:'0.4.0')
+@Grab('se.alipsa.gi:gi-console:0.4.1')
import se.alipsa.gi.console.InOut
def io = new InOut()
@@ -76,11 +76,11 @@ io.view("
Hello
Welcome!
", "Greeting")
## Fat JAR
-A fat JAR (with all dependencies bundled) is available:
+A fat JAR bundles runtime dependencies; the consuming application supplies Groovy:
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-console:0.4.0:fatjar'
+ implementation 'se.alipsa.gi:gi-console:0.4.1:fatjar'
}
```
@@ -88,7 +88,7 @@ dependencies {
- **Clipboard**: Clipboard access is unavailable in headless environments; clipboard methods log the issue and return `null` or no-op.
- **Password Input**: Uses masked input through `System.console()` when available. In IDEs and some CI environments, it falls back to visible stdin input and logs a warning.
-- **Charts/Images**: `display(Chart)` and `display(File)` for images print a message instead of showing graphics
+- **Charts/Images**: chart display prints a message instead of showing graphics; `display(File)` asks the desktop application to open an existing file when the platform supports it
- **Swing Components**: `display(JComponent)` is not supported
- **File Choosers**: User manually types file paths instead of browsing
diff --git a/gi-fx/README.md b/gi-fx/README.md
index cd7fc40..fb35b79 100644
--- a/gi-fx/README.md
+++ b/gi-fx/README.md
@@ -13,7 +13,7 @@ This module provides JavaFX-based dialogs and viewers for user interaction. It o
## Features
-- Native JavaFX dialogs for file/directory selection
+- JavaFX dialogs for file/directory selection
- Rich date and year-month pickers with calendar UI
- WebView-based HTML rendering
- Markdown viewing with full formatting support
@@ -26,7 +26,7 @@ This module provides JavaFX-based dialogs and viewers for user interaction. It o
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-fx:0.4.0'
+ implementation 'se.alipsa.gi:gi-fx:0.4.1'
}
```
@@ -36,14 +36,14 @@ dependencies {
se.alipsa.gi
gi-fx
- 0.4.0
+ 0.4.1
```
### Grape (Groovy Script)
```groovy
-@Grab(group:'se.alipsa.gi', module:'gi-fx', version:'0.4.0')
+@Grab('se.alipsa.gi:gi-fx:0.4.1')
import se.alipsa.gi.fx.InOut
def io = new InOut()
@@ -71,11 +71,11 @@ io.viewMarkdown("# Title\n\nSome **bold** text", "Document")
## Fat JAR
-A fat JAR (with all dependencies bundled) is available:
+A fat JAR bundles runtime dependencies; the consuming application supplies Groovy and JavaFX:
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-fx:0.4.0:fatjar'
+ implementation 'se.alipsa.gi:gi-fx:0.4.1:fatjar'
}
```
diff --git a/gi-swing/README.md b/gi-swing/README.md
index 44edd58..9564eac 100644
--- a/gi-swing/README.md
+++ b/gi-swing/README.md
@@ -13,7 +13,7 @@ This module provides Swing-based dialogs and viewers for user interaction. It wo
## Features
- Standard Swing file and directory choosers
-- Date and year-month pickers (via LGoodDatePicker)
+- Date and year-month pickers via `se.alipsa:swing-widgets`
- HTML viewing via JEditorPane
- Table display with JTable
- Clipboard operations
@@ -26,7 +26,7 @@ This module provides Swing-based dialogs and viewers for user interaction. It wo
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-swing:0.4.0'
+ implementation 'se.alipsa.gi:gi-swing:0.4.1'
}
```
@@ -36,14 +36,14 @@ dependencies {
se.alipsa.gi
gi-swing
- 0.4.0
+ 0.4.1
```
### Grape (Groovy Script)
```groovy
-@Grab(group:'se.alipsa.gi', module:'gi-swing', version:'0.4.0')
+@Grab('se.alipsa.gi:gi-swing:0.4.1')
import se.alipsa.gi.swing.InOut
def io = new InOut()
@@ -75,11 +75,11 @@ io.view([
## Fat JAR
-A fat JAR (with all dependencies bundled) is available:
+A fat JAR bundles runtime dependencies; the consuming application supplies Groovy:
```groovy
dependencies {
- implementation 'se.alipsa.gi:gi-swing:0.4.0:fatjar'
+ implementation 'se.alipsa.gi:gi-swing:0.4.1:fatjar'
}
```
@@ -89,8 +89,7 @@ dependencies {
## Dependencies
-- [LGoodDatePicker](https://github.com/LGoodDatePicker/LGoodDatePicker) - Date picker component
-- [swing-yearmonth-picker](https://github.com/Alipsa/swing-yearmonth-picker) - Year-month picker
+- `se.alipsa:swing-widgets` - Date and year-month picker components
## API Documentation
diff --git a/gi-swing/test.gsh b/gi-swing/test.gsh
index fe82c63..4c4406c 100644
--- a/gi-swing/test.gsh
+++ b/gi-swing/test.gsh
@@ -2,8 +2,8 @@ import static groovy.grape.Grape.grab
if (! binding.hasVariable('io')) {
grab(group:"se.alipsa.gi", module: "gi-swing", version:"0.4.0")
- binding.setVariable('io', this.class.classLoader.loadClass("se.alipsa.gi.swing.InOut").getDeclaredConstructor().newInstance())
+ binding.setVariable('io', this.class.classLoader.loadClass("se.alipsa.gi.swing.InOut").newInstance())
}
def file = io.chooseFile("Choose a file", ".", "Pick a file please!" )
-println("File chosen was $file")
\ No newline at end of file
+println("File chosen was $file")
diff --git a/release.md b/release.md
index 8d2e37f..d23d5cd 100644
--- a/release.md
+++ b/release.md
@@ -1,14 +1,17 @@
# Gui Interaction Release Notes
## Unreleased
+
+## 0.4.1 - 2026-09-27
- Fixed (build): pin Java 21 toolchains in all modules; run Spotless and SpotBugs in default builds; scan all modules with `dependencyCheckAggregate`.
-- Fixed (build): select JavaFX natives for the Gradle JVM's OS and architecture; unsupported combinations fail when JavaFX dependencies resolve, leaving unrelated tasks available.
-- Fixed: console null file names and password EOF no longer throw; null matrix views are guarded and unnamed JavaFX tables use `Table` as their title.
+- Fixed (build): select JavaFX natives for the host OS and Java 21 toolchain architecture; unsupported combinations fail when JavaFX dependencies resolve, leaving unrelated tasks available.
+- Fixed: console null file names and password EOF no longer throw; null matrix views are guarded and unnamed Swing and JavaFX tables use `Table` as their title.
- Fixed: JavaFX copy shortcut detection uses locale-independent casing, and the redundant `rowsForViewer` helper was removed.
- Fixed (packaging): merge duplicate licence, notice, and dependency metadata in fat JARs.
- Fixed (release): promote curated Unreleased notes, preserve backslashes in commit subjects, handle release retries, and validate version shapes before mutating files.
-- Added: debug diagnostics for `urlExists` failures and Windows shell branch coverage with a CI smoke job.
-- Build: upgraded Groovy to 5.1.3, Matrix BOM to 2.6.0, CommonMark to 0.30.0, jsoup to 1.23.2, JUnit to 6.1.3, Gradle Versions to 0.64.0, Spotless to 8.10.2, and SpotBugs (plugin/engine) to 6.5.11/4.10.4.
+- Added: `urlExists` warnings for unsupported schemes and broken redirects, with debug diagnostics for connection and HTTP failures. Logged URLs redact credentials, including malformed URL paths.
+- Added (test): Windows shell execution integration coverage.
+- Build: upgraded Groovy to 5.1.3, Matrix BOM to 2.6.0, CommonMark to 0.30.0, jsoup to 1.23.2, JUnit to 6.1.3, Gradle Versions to 0.64.0, Spotless to 8.10.3, and SpotBugs (plugin/engine) to 6.5.12/4.10.4.
- Common: added `sh` and `shell` methods for executing platform shell commands. `sh` returns captured standard output and can stream output while the command runs; `shell` returns a `ShellResult` containing standard output, standard error, exit code, and success status. Both support optional timeouts. These methods execute arbitrary commands and should not receive untrusted input.
- Breaking (packaging): fat JARs are now built with GradleUp Shadow via the `shadowJar` task instead of a hand-rolled `fatJar` task. The published artifact name is unchanged (`--fatjar.jar`), but the Gradle task is now `shadowJar`.
- Fixed: fat JARs dropped all but the first copy of each `META-INF/services` entry, disabling most ph-css/gsvg service registrations and part of Tika's. Service files are now merged.
@@ -25,10 +28,12 @@
- Fixed: console `display(File)` called `Desktop.open` without checking that the OPEN action is supported, and threw on a null file. Both are now reported on stdout.
- Fixed: console input now honors `stdin.encoding` (JDK 25+) or the platform console's charset (JDK 17+) instead of always using the JVM default.
- Fixed: `saveToClipboard(File)` wrote `Lost ownership` to stdout when clipboard ownership changed.
-- Build: CI now runs `./gradlew check --no-configuration-cache`, which is the only invocation under which Spotless and SpotBugs are applied. The scheduled dependency-check job is reachable again.
+- Build: CI builds and tests all modules on Linux and Windows, including Spotless and SpotBugs. The dependency-check job runs on a schedule or by manual dispatch.
- Build: `release.sh` verifies the version was actually written to `build.gradle`, names the modules already published when a release fails partway, validates/explicitly confirms `--skip ` before resuming without re-publishing them, and rejects `--skip` with `--bump`.
- Fixed: publishing `gi-console`, `gi-fx`, or `gi-swing` failed with "multiple artifacts with the identical extension and classifier" because the Shadow migration both declared the fat-jar artifact explicitly and let Shadow's own publication variant add it. The explicit declaration is removed; Shadow's variant is sufficient.
- Fixed: fat jars now also merge `META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule`, so bundling multiple Groovy extension-module jars (e.g. `matrix-core` and `matrix-groovy-ext`) no longer silently hides one's extension methods behind the other's.
+- Fixed (release): choose the newest installed JavaFX-capable SDKMAN JDK 21 when several versions are available.
+- Fixed (test): load the Swing HTML page test from a stable fixture so Windows can complete it without a temporary-file deletion race.
## 0.4.0 - 2026-08-03
- Breaking: `FileUtils.getResourceUrl` returns `null` for paths that do not exist; use `File` for output targets that will be created later.