* test: draw and click the in-window menu row (#107) Pins what MainMenuModelTest cannot see: every item picked through the drawn row runs its own action, the enabled states and the away and window-toggle labels follow their state while the menu is down, and a palette or language change reaches the row on screen. Named regressions:fe4215bc(menu actions hung on deleted JavaFX MenuItems) and83e23f55(unthemed Swing menu bar), both in main as2a5c89b9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the station list with its real row key (#107) The station row key moves from an inline Java lambda into StationColumns.rowKey, unchanged in its text, so the list can be tested with the key the application really uses. The screen test pins three regressions on the drawn table: the same callsign in two categories collided under one key and sorting dropped the filters (both21251377), and the AP column sorted by its text (ab282162, #87). It also pins that rows replaced after the first draw reach the screen and that the selection follows a station by key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the monitor window's live tables (#107) MonitorContent becomes internal so its body can be drawn headless. The screen test pins the defects the port shipped in this window (cc8c9020, in main as2a5c89b9): rows arriving while the window is open, also after a redraw that was not about the rows, must reach both tables, and two messages that read alike stay two rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw and click the station map window's body (#107) StationMapWindow.show now hands its body to an internal Content composable that takes the tile fetcher, the member roster and the map state as parameters, with the old inline values as defaults, and the map canvas carries a test tag. The screen test pins the map selection fixed in21251377: a marker click and Reset view go through the central station selector, a click keeps the category variant already selected and prepares "/cq CALL", and neither changes the zoom. The header switches write the profile. Tiles never arrive: an empty temporary cache and a failing source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the Station, Log synch, TRX synch and Airscout tabs (#107) Each tab is composed headless and checked for its sections and key controls, and editing a control through the drawn form must write through to ChatPreferences and redraw. Refused entries stay on screen as typed and leave the stored value alone. A small helper finds a control by the label beside it, the way the operator finds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the Message handling, GUI, Beacon and Notification tabs (#107) Each tab is composed headless with its sections and key controls, and edits through the drawn controls reach ChatPreferences and the application callbacks: the auto answer for both categories, the debug-to-file switchc8ce0e98restored, the startup design and the language, the beacon templates and interval, the cluster server, the fallback band, the test spot and the monitored callsign dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the Shortcuts and Worked database tabs (#107) The shortcut and snippet lists show what the profile holds, and an edit or an added entry made in the drawn list reaches the roster and the refresh hook the button row depends on (the hook that was called into thin air beforefe4215bc). The worked database lists its rows, re-reads them on refresh, asks before a reset and leaves the lists alone when the database reset fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: draw the Profiles and Colours tabs (#107) Profiles: the default profile and every action are on screen, deleting it is refused after the confirmation, a cancelled New dialog creates nothing, and switching hands the selected profile to the application. Only a temporary registry is touched. Colours: the six roles show the resolved colour, a typed colour is committed into the store, a mistyped one is refused and put back, the shipped reset undoes a change, and the stylesheet export writes once into a temporary profile directory and never over an existing file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: give the map's path analysis a seam and tag the monitoring field (#107) StationMapWindow.Content takes a MapPathAnalysis supplier. The default looks the running application's reachability service up on every selection, exactly as the inline chat-controller lookup did, and null still means: no pan, no analysis. This lets a test run the zoom rule on selection thatcc8c9020fixed. The callsign field of the add-to-monitoring dialog in NotificationTab carries a test tag, so a test types into it by name rather than by position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: isolate and harden the screen tests after review (#107) - No screen test builds ChatPreferences() any more: that constructor resolves the real user profile and seeds preferences.xml there. Each class uses ChatPreferences(Path) under a @TempDir. - Compose state changed from the test thread (roster refresh, language reset) goes through runOnIdle. - Dialogs and dropdowns are waited for with waitUntil and found inside their overlay (isDialog / isPopup) instead of by tree order; the monitored-callsign field is found by its test tag. - The menu test now asserts Disconnect when online and Connect when offline instead of skipping one. - The map test runs the zoom-on-selection rule through the new seam, closes its tile fetcher after each test and keeps its cache in the @TempDir. - StationColumns.rowKey is pinned directly: two categories, two keys. - The menu colour is sampled further inside the row, and the station list click picks its row by position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: prove the map's selection effect ran when no analysis is available (#107) Review follow-up. The no-analysis test only asserted that the map stayed put, which would also hold if the selection effect never ran; it now counts the analysis lookup. The effect reads the lookup through rememberUpdatedState, so a caller passing a new lambda is not served a stale one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
KST4Contest
KST4Contest is a Java-based client for the ON4KST chat, developed for coordinated VHF, UHF and microwave contest operation.
The application is developed by Marc Fröhlich (DO5AMF) and since 2026 Philipp Wagner (DN9APW).
Start here
- Project website
- Download Stable, Beta and Nightly builds
- Online manual
- GitHub wiki
- Issues and bug reports
- Development roadmap
What KST4Contest does
KST4Contest combines the ON4KST chat with information that is useful when coordinating contacts during a contest.
Among other things, it can:
- display and filter stations from the supported ON4KST chat categories;
- derive band and frequency information from chat messages and station names;
- maintain Worked and NOT QRV information for the available bands;
- calculate station priorities from distance, activity and other available information;
- manage internal skeds and received Win-Test skeds;
- use AirScout information when evaluating possible aircraft-scatter contacts;
- display stations, paths and additional propagation information on maps;
- exchange information with supported logging programs, Win-Test, PSTRotator and a local DX Cluster interface;
- provide configurable automatic replies for recurring chat requests.
Calculated scores, aircraft-scatter information and path assessments are operating aids. They depend on the available data and should not be treated as guarantees that a contact is possible.
Installation
Ready-to-use packages are available for Windows, Linux and macOS. These packages include the required Java runtime, so a separate Java installation is normally not necessary.
Use the central download page to select the appropriate build:
- Stable is intended for normal contest operation.
- Beta contains changes that are being prepared for a stable release.
- Nightly contains the latest automated development build and is mainly intended for testing.
Documentation
The documentation is available in German and English:
The Markdown sources used for the wiki and the generated PDF manuals are stored in github_docs.
Changes to operating behaviour should be documented together with their purpose and limitations. This is especially important for functions whose result depends on external data, heuristics or information derived from chat messages.
Building from source
Building KST4Contest requires JDK 21. The Gradle Wrapper included in the repository should be used, so a separate Gradle installation is not required.
Linux and macOS:
./gradlew clean build
Windows:
.\gradlew.bat clean build
Repository structure
core/– domain, controller and network code, free of user-interface technologyapp-desktop/– the Compose Multiplatform user interface and the packaging tasksgithub_docs/– German and English manual sourceswebsite/– project website sourcespackaging/– platform-specific packaging files
CI status
Documentation
Builds
License
KST4Contest is distributed under the GNU General Public License v3.0.
Supporters/Sponsor
This Project is possible due to donations of the community.
You can sponsor KST4Contest here: Support on Ko-Fi. If you add an Display Name there you will be mentioned here.
The following people are sponsoring KST4Contest:
- Marco (DJ5KW)