Files
kst4contest/AGENTS.md
T
Rsclub2_2andClaude Fable 5.1 5491cbcf8b Test suite cleanup, JaCoCo coverage report and tests before packaging (#62) (#112)
* docs: add the test-coverage design spec (#62)

Records the state found for issue #62 (JUnit classes under src/main that
never run, test libraries in the shipped app image, dead 2023/2024 scratch
tests, no coverage figure), the decisions taken (delete, move to src/test,
JaCoCo report-only without a threshold, tests before packaging), what is
deliberately left to separate issues, and the definition of done.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: move the JUnit classes out of src/main and drop the dead 2023 tests (#62)

Gradle's test task scans src/test only, so the six JUnit classes under
src/main compiled on every build and were never executed. They move to
src/test of the module that owns the class under test, keeping their
package names: On4KstProtocolTest, On4KstSocketThreadTest and
ChatMemberDirectionOpportunityTest within core; Utils4KSTFrequencyTest,
FrequencyTextParserTest and ChatPreferencesStationMapVisibilityTest from
app-desktop into core, next to the sibling tests that already live there,
because they test core classes only. All 26 of them run and pass.

The fully commented-out MessageBusManagementThreadFrequencyContextTest is
deleted. Eight files under core/src/test/java/kst4contest/test from
2023/2024 go as well: they carry no assertion, five of them annotate a
static main() with @Test, which Jupiter never runs, and some write the real
preferences file, send real UDP broadcasts or open the real user database
when started by hand. The five main() scratch programs under src/main
(ReadUDPByWintestThreadTest, PatternMatcherTest, mapTest, RegexTester,
locatorUtils/TestLocatorUtils) and MockKstServer, which only an unused
import in ChatController still referenced, are removed too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* build: make JUnit and Mockito test-only dependencies (#62)

With no test class left under src/main, the implementation declarations of
junit-jupiter-api and mockito-core in core and app-desktop lose their only
justification. They put junit-jupiter-api, junit-platform-commons,
opentest4j, mockito-core, byte-buddy, byte-buddy-agent and objenesis on the
runtime classpath and into lib/app of every app image, Flatpak and
installer. The duplicate catalog entries junit-jupiter-api,
mockito-core-compile and the mockitoCompile version go with them.

Verified: `:app-desktop:dependencies --configuration runtimeClasspath` no
longer lists any of the seven artifacts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: state that test failures fail the build and tests live under src/test (#62)

AGENTS.md and docs/PROJECT_CONTEXT.md still said the build configuration
had allowed test failures to pass. That was true of the Maven build; under
Gradle a test failure fails the build, and only PMD and SpotBugs run with
ignoreFailures. Both documents now say so, note that test classes live under
src/test only and that JUnit and Mockito are test-only dependencies, and
keep the rule to read the test-result XML rather than the exit code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* build: add a JaCoCo coverage report (#62)

The jacoco plugin is applied to every module from the root build with the
tool version held in the catalog. jacocoTestReport now depends on test and
writes XML and HTML per module; a root task jacocoAggregatedReport merges
core and app-desktop into build/reports/jacoco/aggregated/ (XML plus HTML).
i18n-generator is excluded: it is a build tool and its coverage says nothing
about the shipped application. Kotlin classes are counted, because the
Kotlin plugin adds its output to the main source set the report reads.

Report only, on purpose. There is no jacocoTestCoverageVerification and no
threshold: a minimum set at today's number would trip on the next unrelated
change, and one set at a target would fail every build until tests exist for
the number's sake. The figure is published so it can be read when deciding
what to test next; the reasoning is in
docs/superpowers/specs/2026-10-11-test-coverage-design.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: run the tests before packaging and publish coverage (#62)

The nightly and tagged-release workflows built seven package formats from
commits nobody had tested on the way: pr-compile-check.yml runs the suite
for pull requests only, and main can be pushed to directly. Both workflows
now carry a copy of that test job, including the libgl1 step and the
XML-based summary, and every packaging job declares `needs: test`. Copied
rather than factored into a reusable workflow on purpose: the comments in
the job explain decisions the packaging workflows should carry with them,
and three copies read better than one indirection. build-docs-pdf runs
pandoc only and is left alone; publish-flatpak-repo and release-tag are
gated through the jobs they already need.

The PR test job additionally runs jacocoTestReport and
jacocoAggregatedReport once the tests have run (not `if: always()`: a
report from an incomplete run would mislead), prints a line/branch coverage
table per module and aggregated into the job summary from the JaCoCo XML,
and uploads the aggregated HTML report as the coverage-report artifact for
14 days whenever it exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: describe the coverage report in the project context (#62)

A short paragraph on JaCoCo in docs/PROJECT_CONTEXT.md: report only and
why, where the per-module and aggregated reports land, which Gradle tasks
write them, that i18n-generator is excluded, how the PR job summary and the
coverage-report artifact show the figure, and that the nightly and
tagged-release workflows run the tests before any packaging job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: round-trip every ChatPreferences field through the XML file (#62)

Drive all public single-argument setters by reflection to non-default
values, write preferences.xml, read it back and compare every getter.
Fields that are not persisted on purpose are listed with their evidence;
two real reader/writer gaps (second-category locator, sniffer words) are
documented with a BUG marker instead of being fixed here. Two further
tests load the bundled template and a legacy file without configVersion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: cover the DBController schema migration from older user_version values (#62)

Open the seeded template database and hand-built release 1.1 and 1.3
schema fixtures with a DBController and verify that PRAGMA user_version
reaches the current level, every column of the current CREATE TABLE
statements exists, legacy callsign keys are merged to base callsigns and
existing rows and flags survive. A further test proves that reopening an
up-to-date database changes neither schema nor rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: drop the committed JVM crash report and ignore future ones (#62)

hs_err_pid26344.log in the repository root was a HotSpot crash dump that
had been committed by accident. It carries no information about the
project and must not travel with the sources.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: drop the dead QSO sniffer word list that corrupted the callsign list on load (#103)

ChatPreferences carried a second sniffer list, lstNotify_QSOSniffer_sniffedWordsList,
that no UI, no manual and no consumer outside ChatPreferences ever touched. It was
still written to preferences.xml as <snifferWords><w>, and the matching reader block
was a copy of the <snifferCallSigns> block: it cleared the word list and then added
every word, normalised as a callsign, to lstNotify_QSOSniffer_sniffedCallSignList.
Any entry that ever reached the word list therefore leaked into QSO monitoring on
every load.

Remove the field, its accessors, the write block and the read block. A <snifferWords>
element in an older preferences file is now ignored like any unknown element; the
callsign list and its persistence are unchanged.

ChatPreferencesSnifferListTest loads a file that carries both lists and asserts that
only the <snifferCallSigns> entry is monitored. The round-trip test loses its
exclusion entry for the removed setter.

Fixes #103

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: remove the second-category locator that could never be set (#104)

ChatPreferences declared stn_loginLocatorSecondCat with the compiled-in default
"jn49fk". Nothing wrote it to or read it from preferences.xml, no settings tab
exposed it and no caller ever invoked the setter, so the field always held its
default. On4KstConnectionManager.configureMainChat nevertheless compared that
default with the station's locator and, with the second chat enabled, raised the
warning "ON4KST uses one locator per TCP session. The second-chat locator ... is
ignored" whenever the station's locator was not jn49fk, which is practically every
installation.

ON4KST uses one locator per TCP session by design, so a second-category locator can
never become meaningful. Remove the field, its accessors, the comparison and the
warning; the rest of configureMainChat is unchanged. The round-trip test loses its
exclusion entry for the removed setter.

No automated test covers configureMainChat: it is private, works on the manager's
private Session and needs a mocked ChatController plus a live socket handshake, so a
harness would far exceed the scope of this fix. The compile of core and app-desktop
and the shorter exclusion list are the evidence that nothing references the member.

Fixes #104

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 12:13:31 +02:00

9.4 KiB

KST4Contest agent instructions

These project instructions extend Marc's global Codex working agreements.

Project identity

KST4Contest is a Java/Kotlin desktop client for ON4KST chat, with a Compose Multiplatform user interface with contest-oriented workflows and integrations including logging software, AirScout, rotor control, DXCluster and local persistence.

Primary repository areas:

  • core/src/main/java/kst4contest/ and core/src/test/
  • app-desktop/src/main/java/kst4contest/ and app-desktop/src/test/
  • github_docs/
  • website/
  • docs/
  • packaging/
  • .github/
  • build.gradle.kts, settings.gradle.kts, gradle.properties, gradle/

Inspect the current tree before assuming an exact class/path still exists.

Mandatory interaction rule

For every planned code or documentation implementation:

  1. inspect first;
  2. explain the task understanding in German;
  3. identify and ask all relevant implementation questions;
  4. wait for answers when needed;
  5. present the final concept in German;
  6. state what must remain unchanged;
  7. request explicit concept approval;
  8. wait;
  9. implement only after approval.

If an answer is uncertain, do not interpolate it. Check current code/tests/docs/project context first and ask Marc when the uncertainty can affect behaviour.

Language

  • Communicate with Marc in German.
  • Write source-code comments and Javadoc exclusively in English.
  • Keep log/protocol/API literals in their canonical form.
  • Commit messages are concise English when a commit is explicitly requested.
  • User-facing DE/EN documentation follows $praktimarc-writing-style.

Java and user-interface architecture

  • Preserve or improve separation between network/parsing/service/controller/UI responsibilities.
  • Do not solve architecture problems by letting worker/model code directly manipulate user-interface collections.
  • Active chat-member domain state is conceptually thread-safe state and lives in an ObservableRoster in core; the ObservableList a TableView is bound to is a mirror of it, not the store.
  • MessageBusManagementThread must not directly read or mutate the row lists the user interface draws from; it works on snapshots.
  • Route UI-visible mutations through the controller and the UiDispatcher (AwtUiDispatcher hands them to the AWT event dispatch thread, where Compose draws). Do not reach for a toolkit thread call such as Platform.runLater or EventQueue.invokeLater in core; it has no toolkit dependency.
  • Prefer explicit DTOs over records when introducing transport/parser DTOs in this codebase unless the approved concept says otherwise.
  • Handle incomplete external/historical data defensively.
  • qrb, QTF and related external values can be absent. null means unavailable, not zero.
  • Unexpected input must not terminate message-processing or UI threads.

Callsign and category identity

  • Preserve full callsign variants as distinct chat-member identities where the server exposes them separately.
  • Category is part of chat identity. Do not merge messages across categories.
  • Base-call normalization may be used only for explicitly base-call-wide features such as worked status or monitoring rules.
  • Worked status is shared across suffix variants of the same base call.
  • Monitoring a callsign variant such as DN9APW-2 or DN9APW-70 is intended to monitor the base call DN9APW, so users do not need to enter every SSID.
  • Do not generalize suffix semantics beyond behaviour explicitly established by the current code/specification.

Bands and availability

  • ON4KST categories 2 and 3 are central to the normal VHF/UHF workflow, but other category values can occur and must fail safely.
  • Do not let unsupported categories produce exceptions.
  • Known-active-band logic and B+ interpretation must remain consistent across the application.
  • Band information parsed from names/text must respect explicit NOT-QRV information; NOT-QRV overrides positive availability hints.
  • Do not silently fall back to a fixed band/frequency when a required decision is ambiguous unless an approved fallback exists.
  • Manual band settings and actual current QRG must remain consistent with features that depend on frequency.

External protocols and integrations

Before changing ON4KST, AirScout, UCXLog/DXLog, Win-Test, PSTRotator or DXCluster handling:

  • inspect the current implementation;
  • preserve exact framing and compatibility;
  • inspect current tests;
  • check authoritative upstream documentation when the protocol detail is uncertain;
  • ask Marc if more than one behaviour is plausible.

Specific invariants and historical context are in $kst4contest-change references.

Never change CR/LF, XML framing, callsign normalization, frequency formatting or port/transport assumptions casually.

UI behaviour

  • Preserve contest workflow speed and discoverability.
  • Do not change zoom, selection, focus, sorting, tab choice or prefilled text as an incidental side effect.
  • Map reset behaviour should clear the selected target without changing the zoom unless a new task explicitly changes this.
  • New station selection should preserve the established /cq callsign prefill behaviour.
  • If no send category is selected, preserve the established Main-category fallback unless explicitly changed.
  • Null/unknown data must render as unavailable/empty according to current UI conventions, not as fake zero values.

Map

  • The station map is a Compose canvas. It fetches its tiles directly; the former JavaFX WebView, the bundled Leaflet resources, the CSS-3D-transform workaround and the TileProxyServer that worked around the WebView's TLS behaviour in AppImage and Flatpak packaging are all gone.
  • Map reset clears the selected target without changing the zoom. Offline DEM, terrain packages and the profile cache must keep working unchanged.

Autoanswer / beacon safety

  • Prevent automated-message loops.
  • Respect the established minimum interval/cooldown logic.
  • Do not consume a cooldown for a reply that is rejected before a complete valid TX item is queued.
  • Preserve priority of frequency/QRG requests where established.
  • Cooldown identity must not accidentally collapse unrelated callsign/category identities.
  • Treat the current implementation/tests as the source of truth for exact message markers and timer details.

Build and verification

Use the Gradle wrapper.

Windows:

.\gradlew.bat ...

Read the current gradle.properties and gradle/libs.versions.toml before relying on version numbers.

At this context snapshot the project uses Java 21, Kotlin 2.2 and Compose Multiplatform 1.8.x, with JUnit 5/Mockito, PMD and SpotBugs integrated. JavaFX is gone; there is no org.openjfx dependency left.

Important: a test failure fails the build, but PMD and SpotBugs run with ignoreFailures, so static-analysis findings do not. Test classes live under src/test only (since #62; before that a few sat under src/main and were never run), and JUnit/Mockito are test-only dependencies. Therefore:

  • inspect the Gradle test summary;
  • inspect the test-results XML under */build/test-results/ when needed;
  • do not infer "all tests passed" from exit code 0 -- read the XML;
  • report PMD/SpotBugs findings that are visible in the relevant build.

Run focused tests first, then normally the relevant broader test/build command for the scope.

Documentation and durable project context

Use $software-project-context, $kst4contest-documentation, and $praktimarc-writing-style as relevant.

Do not perform a full manual or website audit after every implementation.

After a completed change:

  1. perform a short documentation-impact classification;
  2. if user-visible behaviour is plausibly affected, search only the relevant German and English manual sections under github_docs/;
  3. keep both language versions semantically aligned when an update is required;
  4. check README and website feature text only when the changed feature/configuration is represented there or is likely to need representation;
  5. identify screenshots that are likely stale instead of fabricating replacements;
  6. update docs/PROJECT_CONTEXT.md for significant architectural, protocol, state/persistence, operational, integration, deployment, workaround, or long-lived behavioural decisions;
  7. keep Related Projects / Integration Points current when KST4Contest, its website, hamradioonline infrastructure or planned propagation services affect one another.

A full documentation audit is reserved for explicit audit requests, major release preparation, broad UI/workflow changes, or evidence that documentation is broadly stale.

Website

The repository contains an Eleventy-based website under website/ with its own tests/build logic.

Do not assume website deployment/update-feed details; inspect current scripts/workflows before changing them.

Change scope and Git

  • No unrelated refactoring.
  • No production dependency without prior approval.
  • No automatic version bump.
  • No commit/push/merge/tag/release/deploy without separate explicit authorization.
  • Preserve deliberate test data and easter eggs unless explicitly changed.
  • Never overwrite unrelated working-tree changes.

Completion

After implementation, report in German:

  • understanding fulfilled;
  • changed files;
  • important design decisions;
  • tests/builds and exact results;
  • documentation-impact classification;
  • manual/website/README/context updates made or why none were necessary;
  • related-project impact when relevant;
  • remaining uncertainty;
  • suggested next action, without performing it automatically.