mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-18 21:34:03 +02:00
c64e9a9014d97f86cbf823da74be2cc477b902c7
348
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c64e9a9014 |
Open the release notes with the message of an annotated tag (#130)
The release body was built from direct commits on main and GitHub's generated PR list only; whatever was written into the tag was lost. An annotated tag's subject and body now open the notes, ahead of "What's Changed", and so reach the in-app update feed too. The tag object is fetched explicitly because actions/checkout can leave the triggering tag as a lightweight ref; contents:subject/body leave the SSH signature out. Lightweight tags contribute nothing, as before. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
88d10259d4 |
Retry stapler when Apple's ticket service drops the connection (#131)
Three nightly macOS DMG builds on the Intel runner failed in a row after a
successful notarization: `xcrun stapler validate` lost its connection to
api.apple-cloudkit.com ("The network connection was lost.", exit 68). The
ticket was fine; the runner's connection to Apple was not. staple and
validate now get up to five attempts with growing pauses (20, 40, 80,
160 s) before the build fails.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
c9b838e946 |
Translate the remaining hard-coded interface texts (#129)
* Translate the interface texts that stage 9 left in English The GUI tab checkboxes, the file chooser and dropdown placeholders, the profile dialog's Apply, the settings notice's OK, the chat clear button, the selected-station map button and "all", six column titles, the monitor window's title and heading, and the refusal and hint texts of the profiles, beacon and notification tab states now come from the translation files. Column titles go through CurrentStrings in a lambda like the existing ones, so the id stays untranslated and the stored layout keeps its keys. The tab states read CurrentStrings as StatusIndicatorState already does. The English values are unchanged, so the screen tests still find every control by its old label; a new test switches to German and checks one sample per path: a composed label, a column title and a tab-state refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Translate the settings notices, profile labels and alert button too Wires the settings.spot* and settings.write* keys that existed but were never used, translates the settings window title and its notice titles, the profile tab's "unknown", "Copy of" and database-mode labels, and lets the shared alert use alert.ok instead of a literal. The German for messages.qsoOfTheOther becomes "Fremd-QSOs", the word the menu already uses. "Copy of" is only the suggested display name; the profile id is derived from the entered name once at creation and stored, so a later language switch does not touch it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a9d8c013ef |
Settings tabs no longer commit on open; lock login fields while connected (#128)
* Commit settings fields only on a real edit, not when a tab opens Form.committed treated every onFocusChanged callback with "not focused" as the operator leaving the field. Compose also fires that callback once when the modifier is attached, so drawing a tab committed each of its committed fields: opening Colours wrote all six roles as overrides of themselves (shown as "changed", and after "Save settings" they hid the profile's stylesheet file for good), opening Beacon validated both templates and restarted the beacon timer, opening Notification re-committed the port and the spotter callsign. The field now commits only on a transition from focused to unfocused, or on Enter, and only when the draft differs from the stored value. A real edit still commits on leaving the field or on Enter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Lock the Station tab's login fields while a session exists StationTabState.loginFieldsEnabled was a plain field that nothing ever wrote and that Compose could not observe, so callsign, names, locator, password and both chat categories stayed editable while connected, although the running session does not pick up a change. The flag is now derived from the ON4KST connection state, read through a lambda over the main window's Compose state (MainWindowSurroundings), so the tab follows the session while it is open: the fields are locked for every state but DISCONNECTED and unlocked again after disconnecting. The screen-test helper fieldBeside now matches text fields by their editable text, because a disabled field offers no set-text action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop changelog entries for defects that never shipped The committed-field and login-field defects exist only in the Compose settings tabs, which are in neither v1.44.0 nor beta-v1.50.0-beta-001, so there is nothing for an operator to read about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare committed fields with their last commit, not a stale value Form.committed skipped a commit when the draft equalled `stored`, the value the caller passed at its last composition. Most callers read it from plain preference getters, so nothing recomposes them after a commit: after Enter, `stored` still named the old value, and leaving the field committed the same entry again, restarting the beacon timer twice for one edit. The field now keeps the value it last committed in its own holder, compares the draft with that, and falls back to it on a refusal. The comparison is pluggable. The Colours tab compares the way PaletteOverrides normalises a colour (trimmed, case ignored), so retyping the shown colour in lower case no longer becomes an override of itself. Behaviour kept on purpose: Enter on an unchanged value commits nothing, so it neither restarts the beacon timer nor reports a stored value the rule refuses. A field removed while it holds the focus (tab switched, window closed) still commits a typed entry once, because Compose reports the cleared focus as a focus loss; a test now pins this, as it was before. Also: a test drives the Station tab's lock from ConnectionStateFeed through MainWindowSurroundings, the chain the application wires, and the Javadoc of openSettingsWindow is back above its own method. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Judge a committed field against the live stored value be6862c8 kept the value a field last committed in a holder keyed on `stored`. That holder could outlive the truth: the field commits 9 over 5, nothing recomposes the tab, a profile switch puts 5 back and the tab recomposes with 5 -- the same key -- so the holder still said 9. The field showed 9, the preference held 5, and leaving the field committed nothing, where main would have committed 9. Form.committed now takes `current`, the value stored right now, read when the field commits; the draft is judged against it and falls back to it on a refusal. The holder is gone. Right after a commit the getter already returns the new value, so Enter followed by leaving the field still commits once. Every caller passes its preference getter: both beacon templates, the beacon interval, the DX cluster port, the spotter callsign and the colour roles. `isUnchanged` must agree with what commit stores, which the KDoc now says. The interval and the port compare as the number stored, the spotter callsign ignoring case and surrounding blanks, the colours as PaletteOverrides normalises them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Require the live value in committed fields and compare the spotter call as stored Review follow-up. `current` has no default any more, so a future caller cannot silently bring back the stale comparison. The spotter call is compared after the same uppercase(ROOT) the commit applies, so a typed "X" against a stored "x" still commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
81b16ad4c5 |
Keep tests out of the user profile; small socket fixes (#127)
* Keep tests out of the user profile The no-argument ChatPreferences constructor seeds preferences.xml in ~/.praktiKST when it is missing, so 21 older test files could create a profile file for whoever ran the build. They now use ChatPreferences(Path) with a @TempDir. None of them reads the file: their assertions are on the field initialisers, which both constructors share, so no assertion changes meaning. ChatPreferencesStationMapVisibilityTest passes its file to the constructor instead of redirecting it with the setter afterwards. The two opt-in ON4KST practice tests keep the no-argument constructor: they deliberately read the operator's real profile for live credentials. As a net, the test tasks run with user.home pointed at build/test-user-home, emptied before each run, and fail if a preferences.xml appears there. This also keeps the sound files, the map tile cache and the Skiko native library that tests unpacked into the real home directory out of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Make DXClusterThreadPooledServer.serverSocket volatile run() assigns the server socket on the server thread, and stop() closes it from another thread. closeServerSocket() is synchronized, but the write in run() is not, so nothing guaranteed that stop() saw the socket run() had opened; it could find null and leave the socket listening. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the ineffective SO_REUSEADDR from the Win-Test sked socket WinTestSkedSender.sendUdp() set reuse address on a socket the constructor had already bound, where it has no effect, so it was never in effect in any shipped version. The socket binds an ephemeral port and only sends, which gains nothing from it, and enabling it for real would change UDP multi-bind semantics on Windows. Removing the call keeps the behaviour exactly as shipped and stops the code from claiming an option it does not have. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Check the test user.home in a finalizer task The stray-preferences check ran as a doLast of the test task, which Gradle skips once a test fails, so a failing run could hide the file. It is now a separate task, checkTestUserHome, that every test task is finalized by; finalizers run when the test task fails too. It captures only a File, so it stays configuration-cache safe. Each test task now gets its own scratch home, build/test-user-home/<task name>, so a second test task cannot wipe another's. Emptying it checks the result of deleteRecursively(), which reports failure instead of throwing, and stops the task, so a leftover file is never blamed on the current run. The check stays limited to preferences.xml. Other files land in the scratch .praktiKST by design, such as the sound files PlayAudioUtils seeds from the bundled resources; absorbing those is the redirection's job, and a wider check would need an exclusion list that grows with every seeded file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep generateStrings configuration-cache safe The argument provider of generateStrings referenced the script-level generatedI18nDirectory, which captures the build script object, so storing the configuration cache failed for every build that included app-desktop. The task now works on a local copy of the provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Declare the test user.home as a test task output Test tasks are cacheable, and a cache hit skips doFirst, so the scratch home was neither emptied nor rewritten and checkTestUserHome judged whatever an earlier run had left there: a false alarm after a violating run, or a hidden violation when a passing result for violating inputs came from the cache. The scratch home is now a declared output, restored and replaced together with the cached result, so the check always sees the files belonging to the inputs. The doFirst wipe stays for real executions. Skiko unpacked its native library, about 30 MB, into the scratch home, which would now land in every app-desktop cache entry. skiko.data.path points it at build/skiko-data instead, outside the output and kept between runs. user.home and skiko.data.path are passed through an argument provider: as system properties they would be task inputs, and their absolute paths would make the cache key differ between checkouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2898c530d9 |
Restore the turn-antenna button and send the PSTRotator azimuth with one decimal (#126)
* Send the PSTRotator azimuth with one decimal instead of truncating it
setAzimuth cast the azimuth to int, so 145.7 degrees turned the antenna
to 145. It is now rounded to one decimal and formatted with Locale.ROOT,
so a German default locale cannot turn the dot into a comma; a value that
rounds up to 360.0 is sent as 0.0. The loopback test pinned the old
truncation and now expects the decimal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Bring back the turn-antenna button lost in the Compose migration
The JavaFX station panel had "Turn ant1 to <call>", the only caller of
ChatController.rotateTo; removing JavaFX (
|
||
|
|
e1e6fe7297 |
Make MessageBusManagementThread testable, first slice (#105) (#123)
* test: restore the frequency-context and Band.fromPrefix tests (#105) The test file MessageBusManagementThreadFrequencyContextTest sat under src/main, fully commented out, and was deleted with #62 without ever having run. Its two concerns come back as real tests: the bare three-digit context rule of hasExplicitBareFrequencyContext in the controller test package, and Band.fromPrefix as BandFromPrefixTest in the model package. The original expectations still match the current behaviour; a few boundary cases (context window, invalid indices, non-exact prefixes) are added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: characterise the autoanswer path of MessageBusManagementThread (#105) Runs real CH frames through a started thread (receive queue plus poison pill, real transmit queue, mocked controller collaborators, a real ChatPreferences in a temporary directory) and pins the current behaviour of the private-message branch before it is refactored: - a QRG question addressed to the login callsign yields exactly one reply with the fixed marker and the QRG of the incoming category; - a second question from the same station and category within the cooldown yields none, while another station or category is answered; - a frame carrying the autoanswer marker, a public frame, a frame to another station and a disabled setting yield none; - an empty QRG and a blank generic text are rejected before the cooldown is recorded, so a later valid reply still goes out; - //VER, the generic reply and its precedence below the QRG reply. One oddity is pinned, not fixed: a lower-case login callsign defeats the "directed to me" comparison because the receiver callsign is upper-cased by ChatMember.setCallSign but compared with equals. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor: extract the autoanswer decisions into pure functions (#105) The QRG-question texts become a constant set; matchesQrgQuestion, isAutoMessage and buildAutoAnswerMessageText are static package-private functions with explicit parameters instead of reaching into the preferences, and the cooldown reads an injectable millisecond clock (default System::currentTimeMillis) through a new constructor overload. Behaviour is unchanged: the characterisation test from the previous commit passes untouched, the new decision and cooldown tests cover the extracted functions without mocks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor: extract frequency detection into a pure function (#105) smartFrequencyExtraction now copies the sender's band timestamps, reads the fallback prefix and the injectable clock, and hands the text to the static package-private detectFrequencies, which returns the detections in message order as DetectedChatFrequency records. The thread still stores each one through applyDetectedFrequencyToActiveMembers and logs it as before. Behaviour is unchanged. In production the stored detection lands in the same map the next match of the message reads, so detectFrequencies feeds every detection back as context with the current time; a test pins that "10368.100 then .150" yields 10368.150. The unreachable NumberFormatException catches around parseExplicitFrequency are gone, since that parser already returns null instead of throwing. Also pinned: a plain decimal between 50 and 54 (e.g. "qrb 52,3 km") is read as a 6 m frequency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: extract the directional-opportunity and spot decisions (#105) classifyDirectionalOpportunity returns MISSING_LOCATOR, IN_ANGLE_AND_RANGE or OUT_OF_ANGLE_OR_RANGE from the locators, the maximum QRB and the beam width, and replaces the identical locator check plus isInAngleAndRange call in both the live (CH) and the history (CR) branch. shouldPlayDirectionalTick, decideDxClusterSpot (NO_FREQUENCY, ALREADY_WORKED, SPOT) and buildDxClusterSpotEntry take over the tick condition and the automatic DX Cluster spot of the CH branch. All are static package-private and covered without mocks. Behaviour is unchanged. Pinned: the spot decision only checks that the frequency text is non-blank, so a value outside every supported band is spotted even for a station flagged as worked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: remove dead code from MessageBusManagementThread (#105) processRXMessage23000 belonged to the long-retired raw port-23000 interface and had no caller. With it go the two helpers only it reached, checkIfItsUserListEntry and checkIfMessageInhibitsFrequency, plus the caller-less checkIfItsUserListEntry23001 and checkListForChatMemberIndexByCallSign, the patterns PTRN_USERLISTENTRY, PTRN_QRG_CAT2 and PTRN_QRG_CAT3, the unused chatMemberTable field and the serverReady flag with its never-called accessors. A grep across core and app-desktop finds no remaining reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: keep the sender-category fallback in the QRG auto-answer warning (#105) Review follow-up. The extracted buildAutoAnswerMessageText logged only the message category, while the old warning used autoAnswerCooldownKey, which falls back to the sender's category. The caller now passes the same fallback, so the log line is unchanged from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
4b10d96091 |
Compose screen tests with runComposeUiTest (#107) (#125)
* 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: |
||
|
|
2db71ee864 |
Loopback tests for the network threads (#106) (#124)
* test: pin the Win-Test sked frames over a loopback socket (#106) WinTestSkedSender is pointed at the loopback address instead of a broadcast address, and a socket on an ephemeral port receives the LOCKSKED/ADDSKED/UNLOCKSKED sequence. The frames and their checksum byte are written out literally, including the \260 degree escape and the SSB fallback for an unexpected mode ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: cover the PSTRotator UDP client over loopback (#106) A socket on an ephemeral loopback port plays PSTRotator. The tests pin the poll queries, the <PST> command frames, the AZ:/EL:/MODE: replies reaching the listener, an unparsable reply leaving the loop alive, and the explicit-port constructor reporting its status after start() (#115). The reply format is derived from the parser and the PSTRotator UDP documentation, not from a capture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: cover the AirScout UDP listener over loopback (#106) ReadUDPbyAirScoutMessageThread gains a package-private constructor that takes an already bound socket, as ReadUDPbyUCXMessageThread has; the public constructor still binds the configured port in run(). The tests pin applying an ASNEAREST response to every active variant of the receiver, the case-sensitive server/client match with a UTF-8 client name, the disabled listener, the poison pill, recovery from a malformed response, and the date losing its space because the parser strips all spaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: cover UCXLog/DXLog QSO packets over loopback (#106) The UCX loopback test now sends contactinfo and contactreplace packets: transport bytes before the XML are dropped, the QSO reaches the controller with band, locator and source, the gross field is registered, an unknown station is stored in the database and the UDP backup file, a known one is only updated, and nothing happens while log sync is disabled. A RadioInfo packet sent afterwards serves as an ordering marker instead of a sleep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: cover the local DX Cluster server over loopback (#106) DXClusterThreadPooledServer gains a package-private constructor that takes an already bound server socket; the public constructor still binds the configured port in run(). Plain sockets on the loopback address act as logging programs. The tests pin the listening status, the "login: " prompt, one DXSpider-style line with BEL BEL CR LF per spot to every client, the rejection of spots that cannot be formatted, and stop() closing the clients and ending the server thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the UCXLog/DXLog listener alive on bad input (#106) An unwritable or empty backup path left the FileWriter null, and the following BufferedWriter threw a NullPointerException; a RadioInfo without <Freq> or <Mode> threw one as well. Neither was caught, so the listener thread ended for good. The backup line is now written with try-with-resources and skipped with a log line when the file cannot be opened, an incomplete RadioInfo element is skipped, and run() drops a packet whose processing throws any RuntimeException, reports a non-critical status and keeps receiving. The loopback tests cover all three paths. The interrupt test now waits for a processed marker packet instead of sleeping and no longer claims more than it can observe, and the marker helper states its in-order assumption for UDP on loopback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let a second DX Cluster server run() bind again (#106) The pre-bound-socket constructor made run() skip binding whenever a server socket existed. A run() after the first one ended would then accept on a closed socket. Bind whenever the socket is missing or closed, which restores the behaviour of the public constructor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: make the ignored AirScout responses observable (#106) The tests for responses to another client or server and for the disabled listener only checked that nothing happened before the poison pill, which a listener that never read the datagrams would also pass. Each now sends a valid sentinel afterwards and waits for it, and the ignored responses name a different receiver, so a wrongly processed copy is detectable; the disabled case also shows that the switch was asked once per response. The date test is renamed and documented as a pinned known defect, so a parser fix knows to flip it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: wait for the PSTRotator listener thread after stop() (#106) The client keeps its listener thread private, so the test finds it by the name start() gives it and joins it after stop(), asserting that exactly one such thread existed and that it ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the UCX listener alive when the status callback throws (#106) Review follow-up. The safety net reported a dropped packet through the status callback without guarding it, so a throwing callback still ended the listener. The test for an unwritable backup file is renamed to what it proves, and the changelog mentions that any unexpected error now drops only the affected packet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4679c708bc |
Fix UDP listener defects (#115) (#122)
* fix: let ReadUDPbyUCXMessageThread own its DatagramSocket so interrupt() closes it (#115) The field `socket` was a java.net.Socket that was never assigned; the real DatagramSocket lived in a local variable of run(). interrupt() therefore closed nothing and terminateConnection() would have thrown a NullPointerException. The receive loop kept running in two-second timeout steps until the user-disconnect flag was set. The field is now the DatagramSocket, run() binds it when no pre-bound socket was handed in, the loop ends on the interrupt flag or a closed socket, and both interrupt() and terminateConnection() are null-safe. A package-private constructor accepts a pre-bound socket so loopback tests can listen on an ephemeral port; the public constructor used by ChatController is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: stop ReadUDPbyUCXMessageThread truncating datagrams after a short packet (#115) A DatagramPacket keeps the length of the last datagram it received, so a long RadioInfo packet following a short one was cut to the short one's length and failed to parse. The length is reset to the buffer size before every receive, the same way ReadUDPByWintestThread does it. The loopback test sends a short and then a long packet on port 0 and checks that both frequencies arrive. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: give PstRotatorClient's port constructor a callback, decode AirScout as UTF-8 (#115) The explicit-port constructor left the thread-status callback null, so start() and the first received packet threw an NPE; the default constructor now delegates to it. The AirScout listener decoded replies with the platform charset although the inquirer sends UTF-8. PROJECT_CONTEXT listed PSTRotator as TCP; the client speaks UDP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: decode UCX datagrams as UTF-8 and tighten the interrupt test (#115) Review follow-up. UCXLog sends UTF-8 XML, but the listener decoded with the platform charset before handing a String to the parser, so the XML header's encoding was ignored. The port-in-use fallback now sets its receive timeout before the first receive(). The interrupt test waits 1 s, below the 2 s receive timeout, so it fails if interrupt() stops closing the socket; the QRG latches now count one value each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
6b0c80f71e |
Add Supporters/Sponsor section to README
Added a Supporters/Sponsor section to acknowledge community contributions. |
||
|
|
fb38c858bf |
build(deps): bump actions/setup-node from 4 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0ee29dc585 |
build(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
71b2d3931c |
build(deps): bump actions/download-artifact
Bumps the minor-and-patch group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 8.0.1 to 8.0.2 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v8.0.1...v8.0.2) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
dd9f87a767
|
Build AUR source packages against their own Gradle home, not offline
Both source PKGBUILDs ran "./gradlew --offline" against the builder's personal ~/.gradle. That only worked as long as the local cache happened to hold every dependency; the SpotBugs plugin 6.5.12 added in #114 broke "yay -S kst4contest-git" with "could not resolve plugin artifact". - drop --offline and point GRADLE_USER_HOME into $srcdir, so the package builds the same on any machine - run with --no-daemon so no Gradle process outlives makepkg - drop gradle from makedepends: the wrapper fetches its own distribution Verified with makepkg -f on an empty Gradle home for kst4contest-git. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7b44eb0c2e |
Reference cases for the path-analysis mathematics (#108) (#117)
* test: pin the Maidenhead and great-circle maths of Location against definitions (#108) Subsquare centres from the Maidenhead definition, round trip through toMaidenhead (lower-case subsquare pinned as is), distance and bearing between locators against an independent spherical computation on R = 6371.009 km, consistency with PathGeometryUtils, and the NaN bearing for identical points pinned as current behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: pin PathGeometryUtils against ITU-R P.525/P.526 and textbook values (#108) Great circle against the Veness worked example, Fresnel radius against P.526-15 eq. (2) and (3), earth bulge against the 8500 km effective radius, radio horizon against 4.12*sqrt(h), knife-edge loss J(v) against eq. (31), the v parameter against eq. (26), free-space loss pinned at the project's 32.44 constant with the P.525 and exact values documented, plus wattsToDbm, the sample-count rule and the k-factor sanitiser. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: pin the path-analysis pipeline on a flat profile (#108) A flat 20 km profile with 10 m antennas at 144.3 MHz through an in-memory terrain provider: bulge, line-of-sight clearance, first Fresnel radius and intrusion, no knife edge and 0 dB diffraction. Pins that a partial Fresnel intrusion without a blocked line of sight contributes 0 dB to the link budget; P.526 would give 0 to 6 dB there, which is tracked separately. Fixes #108 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
91863fee1f |
Remove Thread.sleep timing coupling from the tests (#109) (#118)
* test: add bounded awaitUntil helper for timing-sensitive tests (#109) One small class per module (core and app-desktop share no test sources): polls a condition every 10 ms and fails with a message when the timeout elapses, so tests no longer guess with fixed Thread.sleep pauses. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(map): let TileFetcher take its nano clock as a test seam (#109) A package-private constructor accepts a LongSupplier for the monotonic clock the retry pause is measured against; the public constructors delegate with System::nanoTime, so production behaviour is unchanged. TileFetcherTest advances a fake clock instead of sleeping 20 ms three times and hoping the 1 ms pause has elapsed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(view): put CoalescingTrigger on a DelayTimer seam (#109) DelayTimer is a package-private one-shot timer (restart/stop) with a Factory; DelayTimer.SWING wraps javax.swing.Timer and is what the public constructor uses, so production behaviour is unchanged. A package-private constructor takes the factory. CoalescingTriggerTest fires a FakeDelayTimer itself: the negative checks (no second run, cancel prevents the run) are now deterministic and the four Thread.sleep calls are gone. One test keeps the real Swing timer end to end, waiting with awaitUntil. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(view): put LayoutAutosave on the DelayTimer seam (#109) The public constructor still builds a javax.swing.Timer via DelayTimer.SWING with the same 750 ms delay; a package-private constructor takes the factory. LayoutAutosaveTest fires a FakeDelayTimer on the event thread itself, so "cancelled means not written" no longer rests on a 1.2 s sleep and the burst test no longer sleeps 300 ms to prove the absence of a second write. The foreign-thread test keeps the real Swing timer path with awaitUntil. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor(controller): inject scheduler and clock into On4KstConnectionManager (#109) A package-private constructor takes the ScheduledExecutorService and a LongSupplier for wall-clock milliseconds; the existing constructor delegates with the same single-thread supervisor and System::currentTimeMillis, so production behaviour is unchanged. Session's idle clocks start from the injected clock instead of a field initializer. The static parseMessageTimestamp keeps the wall clock: it sanity-checks server timestamps and is not part of the supervisor's timing. On4KstConnectionProbeTest drives unansweredProbeUsesExistingReconnectFlow through a ManualScheduler (immediate work on a real thread, delays and the five-second tick released on demand) and a fake clock: 90 s of silence sends the CK probe, which the test reads off the socket, 210 s fails the session, and the released backoff opens the second connection. The reflection-based state setup is gone because the state is now reached the production way; the test takes well under a second instead of seven. The two ignored await(2 s) results in the server stubs are asserted now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: close the profile picker as soon as it shows instead of after 1.5 s (#109) The two display-bound picker tests disposed the modal dialog from a Swing timer after a fixed 1.5 s, which was long enough on a fast machine only and cost 3 s per run. The closer now waits with awaitUntil for the dialog to appear and disposes it on the event thread; the suite drops from 3.0 s to 0.26 s. Remaining fixed sleep: TopPriorityCandidatesWindowTest waits 400 ms for Compose's double-tap timeout; whether ImageComposeScene's time can advance it deterministically is still to be checked. Fixes #109 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
f2b0fdd5b4 |
Write SpotBugs reports and publish static analysis on pull requests (#110) (#114)
* build: write SpotBugs XML and HTML reports (#110) The SpotBugs tasks had no report configured, so findings went to stdout only and, with ignoreFailures, scrolled past unread. Every SpotBugsTask now writes XML (for CI to count) and HTML (for people) to the plugin's default location, <module>/build/reports/spotbugs/main.{xml,html}. ignoreFailures stays; the build still passes with findings present. Fixes #110 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: run PMD and SpotBugs on pull requests and publish the reports (#110) The compile job runs pmdMain and spotbugsMain after compiling (report only: both tools have ignoreFailures, a crash of a tool still fails), counts <violation> and <BugInstance> elements per module from the report XML into the job summary, and uploads */build/reports/pmd and */build/reports/spotbugs as the static-analysis artifact for 14 days. A missing report prints "no report" without failing the step. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: name the PMD and SpotBugs report files and the CI artifact (#110) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
89d37e62e1 |
Give beta DEB and RPM packages a pre-release number (#113) (#116)
* build: give beta DEB and RPM packages a pre-release number (#113) composePackageVersion is the package version of every Compose target format, so an RPM or DEB built from a beta tag carried exactly the version of the following stable one. dnf, rpm -U and dpkg refuse to replace a package by one of equal version, so a beta user could not move to the stable. Keep the version and tell the packages apart by the package release number, following the Fedora pre-release convention: the new Gradle property linuxAppRelease is passed to jpackage as --linux-app-release (Compose LinuxPlatformSettings.appRelease) and defaults to 1. beta-v1.50.0-beta-001 -> 1.50.0-0.beta.001 beta-v1.50.0-rc02 -> 1.50.0-0.rc02 v1.50.0 -> 1.50.0-1 Everything starting with "0." sorts below "1" for both rpm and dpkg. Nightlies and local builds do not set the property and keep release 1. Fixes #113 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: derive the Linux release number from the tag and verify the tag version (#113) The DEB and RPM jobs now compute the package release from the tag before calling Gradle and pass it as -PlinuxAppRelease: a stable vX.Y.Z tag gives 1, a beta-vX.Y.Z-<suffix> tag gives 0.<suffix> with "-" turned into ".". The result must match [A-Za-z0-9.]+, otherwise the step fails. A workflow_dispatch run from a branch keeps release 1 like the nightlies; nightly-artifacts.yml is unchanged. The test job, which every packaging job depends on, now checks right after the checkout that the X.Y.Z in the tag equals composePackageVersion in gradle.properties and fails with a clear message otherwise, so a mistagged commit fails before an hour of packaging rather than shipping packages whose version contradicts their file name and upgrade order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: describe the pre-release number of beta DEB and RPM packages (#113) The German and English installation manuals say in the Debian/Ubuntu and Fedora sections that beta packages carry a pre-release number below the stable one, so the stable installs as an upgrade over a beta. The v1.50 changelogs gain the matching "Fixed" entry, and PROJECT_CONTEXT.md records the rule next to the MSI decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2eb1ba22cf |
Windows MSI installer next to the portable ZIP (#61) (#111)
* build: configure the Windows MSI installer in the Compose windows block (#61) Add the jpackage/WiX options for a per-user MSI next to the existing icon: a fixed upgradeUuid so Windows Installer replaces an installed KST4Contest instead of installing side by side, a Start menu group and desktop shortcut, no UAC prompt, and a directory chooser. The portable ZIP (createDistributable) is unaffected; the MSI is produced only by the tagged-release workflow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: build and attach a Windows MSI installer for tagged releases (#61) The Windows job of tagged-release.yml now runs packageMsi after the portable ZIP, renames the result to KST4Contest-<tag>-windows-x64.msi (consistent with the other platforms; the ZIP keeps its praktiKST name) and uploads it as the windows-msi artifact, which the release job downloads and attaches next to the ZIP. The nightly workflow stays ZIP-only because a nightly MSI would always carry the same version. The website download data (downloads.js, beta.js) gets an MSI card that is shown only when the release actually has that asset, so the page never links to a file that does not exist for an older release. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: describe the Windows MSI installer next to the portable ZIP (#61) The German and English installation manuals list the MSI in the package table, describe both Windows forms (installer: per user, Start menu entry, desktop shortcut, chosen directory, in-place upgrade, removal via the Windows apps list; ZIP: portable) and the shared data directory %USERPROFILE%\.praktiKST, and add the MSI path to the Windows update section. Changelog entries for v1.50 and a note in PROJECT_CONTEXT.md record the fixed upgrade UUID and the tags-only decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci: build the Windows MSI for stable tags only (#61) A beta MSI would carry the same composePackageVersion as the following stable release. jpackage's MSI upgrade table only accepts strictly higher versions, so Windows Installer would refuse the stable over an installed beta and the tester would have to uninstall first. Betas therefore stay ZIP-only like nightlies; the build, upload and download steps are skipped for beta- tags, and the release action tolerates the missing asset path. Manual, changelog and project context say so. Raised by the Codex review on #111. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
4a90ae7d30
|
docs: describe worked-state check for automatic DX cluster spots (#91)
Add the new spot condition to the DX Cluster Server manual pages and the forwarding section of the Features page in German and English, record the fix in the v1.50 changelog, and note the rule in PROJECT_CONTEXT. Fixes #91 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
78de8b0a9c
|
fix: suppress automatic DX cluster spots for worked stations (#91)
Automatic spots from directional opportunities were generated without looking at the worked state, so stations already in the log kept being spotted to the connected loggers. The spot block in MessageBusManagementThread now asks isAlreadyWorkedForDxClusterSpot(sender) before broadcasting. The rule: - the band is taken from the sender's known frequency via FrequencyTextParser; an unknown band keeps the previous behaviour; - the per-band worked flag of that band suppresses the spot (ChatMember.isWorkedOnBand maps B_1296/B_2320/B_5760 to the historically named worked1240/worked2300/worked5600 flags); - the global worked flag suppresses the spot only when no per-band flag exists at all (Simplelogfile source without band information); - the global flag together with per-band flags for other bands does not suppress, because the per-band information is authoritative. Suppressed spots are logged at FINE. The direction check, the inAngleAndRange marker, the tick sound and manual spots from the map window and the settings test spot are unchanged. Fixes #91 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
ab28216280
|
fix: sort the AP column numerically instead of by its text (#87)
The "AP [minutes / pot%]" column of the station list and of the directed
messages had no comparator, so DataTableState fell back to the rendered
text and "13 (75%)" sorted above "3 (50%)".
AirplaneCell now owns a shared comparator, AirplaneCell.order, that both
column definitions use. The sort key is the first aeroplane of the cell,
which is the one the operator reads and the one the colour marks:
ascending by its arriving minutes, then by the better potential first for
equal minutes. A null info (nothing computed) and an empty list ("nil")
carry Int.MAX_VALUE, so they sort after every real aeroplane in ascending
order, consistent with the qrb and qtf sentinels. The second aeroplane of
a cell does not influence the order. Cell text, colours, column ids,
titles and weights are unchanged, as are DataTableState and the sort
cycle.
Fixes #87
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
456957808d
|
build(deps): bump the minor-and-patch group across 1 directory with 5 updates
Bumps the minor-and-patch group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [gradle-wrapper](https://github.com/gradle/gradle) | `9.7.1` | `9.8.1` | | [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) | `3.43.2.2` | `3.53.4.0` | | [org.mockito:mockito-core](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` | | [org.mockito:mockito-core](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` | | [org.mockito:mockito-junit-jupiter](https://github.com/mockito/mockito) | `5.7.0` | `5.24.0` | | com.github.spotbugs | `6.0.26` | `6.5.12` | Updates `gradle-wrapper` from 9.7.1 to 9.8.1 - [Release notes](https://github.com/gradle/gradle/releases) - [Commits](https://github.com/gradle/gradle/compare/v9.7.1...v9.8.1) Updates `org.xerial:sqlite-jdbc` from 3.43.2.2 to 3.53.4.0 - [Release notes](https://github.com/xerial/sqlite-jdbc/releases) - [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG) - [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.43.2.2...3.53.4.0) Updates `org.mockito:mockito-core` from 5.7.0 to 5.24.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0) Updates `org.mockito:mockito-core` from 5.7.0 to 5.24.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0) Updates `org.mockito:mockito-junit-jupiter` from 5.7.0 to 5.24.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.7.0...v5.24.0) Updates `com.github.spotbugs` from 6.0.26 to 6.5.12 --- updated-dependencies: - dependency-name: com.github.spotbugs dependency-version: 6.5.12 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: gradle-wrapper dependency-version: 9.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: org.mockito:mockito-core dependency-version: 5.24.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: org.mockito:mockito-core dependency-version: 5.24.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: org.mockito:mockito-junit-jupiter dependency-version: 5.24.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: org.xerial:sqlite-jdbc dependency-version: 3.53.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
e67e2db2aa
|
build(deps): bump junit from 5.10.1 to 6.1.3
Bumps `junit` from 5.10.1 to 6.1.3. Updates `org.junit.jupiter:junit-jupiter` from 5.10.1 to 6.1.3 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r5.10.1...r6.1.3) Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.1 to 6.1.3 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r5.10.1...r6.1.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-version: 6.1.3 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-version: 6.1.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
93c1423dcd
|
build(deps): bump org.jetbrains:annotations from 24.0.1 to 26.1.0
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.0.1 to 26.1.0. - [Release notes](https://github.com/JetBrains/java-annotations/releases) - [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md) - [Commits](https://github.com/JetBrains/java-annotations/compare/24.0.1...26.1.0) --- updated-dependencies: - dependency-name: org.jetbrains:annotations dependency-version: 26.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
eddcbc82e8
|
build(deps): bump actions/setup-java from 4.1.0 to 6.0.1
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.1.0 to 6.0.1. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4.1.0...v6.0.1) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
ab42c6ef35
|
build(deps): bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0039ec98e3
|
build(deps): bump actions/download-artifact from 4.1.3 to 8.0.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.3 to 8.0.1. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.1.3...v8.0.1) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
294a978c3a
|
build(deps): bump actions/checkout from 4 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
19b9bf0fca
|
build(deps): bump Andrew-Chen-Wang/github-wiki-action from 4 to 5
Bumps [Andrew-Chen-Wang/github-wiki-action](https://github.com/andrew-chen-wang/github-wiki-action) from 4 to 5. - [Release notes](https://github.com/andrew-chen-wang/github-wiki-action/releases) - [Commits](https://github.com/andrew-chen-wang/github-wiki-action/compare/v4...v5) --- updated-dependencies: - dependency-name: Andrew-Chen-Wang/github-wiki-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
5b9fe0d3c1
|
chore: add Dependabot configuration for Gradle and GitHub Actions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
2a5c89b943 |
Next major release/version1.50 (#89)
* Make DBController work on one database file per instance The controller was a static singleton: an eagerly created static instance opened the root database during class initialization, and both the connection and the path were static fields. A second operator profile in the same process was therefore impossible, and ChatController's own "new DBController()" never opened anything - it silently adopted the eagerly opened root connection. - drop the eager static instance in favour of a lazily created default instance - turn connection and path into instance state - add a constructor taking a database file name relative to the application directory plus a flag whether a missing file is seeded from the bundled template - create additional profile databases empty instead of seeding them: the bundled template carries 3452 foreign callsigns and user_version 0, which would show a new operator foreign data and trigger the full callsign normalization rebuild. The schema is created by the existing table setup. - remember the shutdown hook so closeDBConnection can deregister it; otherwise every profile switch would leave another hook holding a dead connection All SQL statements keep referencing the plain field and are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Allow preferences and chat controller to be bound to a file set ChatPreferences gains a constructor taking a preferences file name relative to the application directory, so "profiles/OP2/preferences.xml" is as valid as the historic flat "preferences.xml". A missing file is still seeded from the bundled template, which gives an additional operator the same clean defaults a first installation gets. ChatController gains a constructor that passes both relative file names and the seed flag through to ChatPreferences and DBController. The existing constructors delegate to the historic file names, so nothing changes yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Introduce the operator profile model and its registry Adds the descriptor, the path derivation and the registry persistence for operator profiles. Nothing calls them yet, so behaviour is unchanged. The descriptor stores only a shared/own flag, never a path. All file names are derived in OperatorProfilePaths, so a stored path can never drift apart from the flag that produced it. A profile identifier is a stable, file system safe slug assigned once, so renaming a profile never moves a directory. The registry is created lazily. An installation that only has the historic flat layout gets no registry file and no profiles directory; the root profile is synthesised in memory instead. That keeps a single operator installation byte for byte the one it was before, and it keeps a downgrade to an older release a no-op. A missing, unreadable or malformed registry is logged and treated like an installation without additional profiles, never as an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Select the operator profile at startup Resolves the active operator profile before the chat controller is built and passes its two file names on, so preferences, layout and worked data follow the profile. The resolution is deliberately quiet for existing installations. With no registry or exactly one profile nothing is asked and nothing is written, so a single operator start is unchanged. Only from two profiles on does a small picker appear with the last used profile preselected, where Enter or a double click starts immediately. A "--profile" argument, or the equivalent system property, skips the picker; an unknown name warns and falls back to the normal selection instead of refusing to start. The startup decision itself lives in OperatorProfileBootstrap and contains no user interface code, so it is covered by headless tests. The window title gains the profile name only when a second profile exists. Command line parsing happens in init() and is kept in a process wide holder, because JavaFX only knows the parameters of the instance it launched itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Release the background resources a discarded runtime owns Several resources outlived a disconnect on purpose, which was harmless while a process ran exactly one session for its whole life. They are now released when the controller itself is closed: - the ON4KST connection supervisor thread, which stopByUser did not touch - the sked reminder scheduler, which had no shutdown at all - the reachability executor, whose shutdown method existed but was never called - the PSTRotator retry scheduler and its pending retry - the map tile proxy, whose stop method existed but was never called, leaving a server socket and a twelve thread pool behind - the station map bridge listeners and its coalescing animation, which would otherwise keep firing into a dead user interface These are real leaks today; they only become visible when a second runtime is built in the same process. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Switch the operator profile while the application is running Splits the teardown out of stop() into a reusable, idempotent shutdownRuntime() and adds File - Switch operator profile..., which tears the current runtime down and builds a fresh one for the selected profile. The new runtime is a new application instance rather than a second start() on the existing one. Many controls are instance fields created once, so reusing the instance would re-parent mounted nodes and register every listener twice. A fresh instance is safe because the class keeps no mutable static state. Closing every window during a switch would end the process under the JavaFX default, so the application takes over the exit decision: implicit exit is turned off, the main window gets an explicit close handler, and every exit path runs through the launcher. That also fixes losing the window layout on exit after a switch, because JavaFX only calls stop() on the instance it launched. The two view timers are now cancelled null-safe; stop() used to dereference them unguarded, which would fail if shutdown happened before they were created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Manage operator profiles from the settings window Adds a "Profiles" tab, appended last so no established tab position shifts, offering create, duplicate, rename, delete, a switch between shared and own worked stations, and activation of another profile. Duplicating copies the whole configuration except callsign and password. The antenna, locator, layout and integration settings are exactly the work nobody wants to enter twice, while the credentials belong to one operator only. The root profile can neither be deleted nor moved off the common station database, because its files are the installation itself. The login callsign and its raw form now default to empty instead of a real callsign. The XML reader treats an empty element as "not set" and falls back to the field default, so without this change a profile created without credentials would come up carrying the callsign compiled into the defaults - and an operator could transmit under someone else's call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Document operator profiles Adds a manual section in both languages covering where profile files live, the choice between shared and own worked stations, managing profiles, the startup selection and the --profile argument, switching while running, and the fact that passwords stay in clear text so profiles are not an access boundary. Records the architecture in PROJECT_CONTEXT: lazy registry, derived paths, why an additional profile database is created empty, why the login callsign default is empty, and the constraints of rebuilding the runtime for a switch. Adds a v1.50 changelog entry in both languages including the upgrade notes: no file is moved, and going back to an older release stays possible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Make the terrain profile cache multi operator safe The cache stored a single owner identity in a meta table and dropped the whole TerrainProfileCache table whenever the configured callsign or locator differed from it. With several operator profiles that turns the cache into a permanent miss: every switch between two operators with different locators would discard every computed profile. Entries are separated by owner identity through the primary key already, so the wipe is replaced by an owner table that simply records which identities are in use. A different owner now misses the cache instead of clearing everybody's. The cache also moves out of the worked station database into its own global terrainprofilecache.db. Terrain profiles are pure geometry derived from two locators and a sample count; at a multi operator station both operators share one location, so a per profile copy would only double the traffic against the terrain service. No migration is needed, the new file refills itself, and the old tables stay readable for older releases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Fail cleanly when the registry path has no directory SpotBugs flagged a null passed to Files.createTempFile: the null check guarded only createDirectories, while the temporary file creation would still have dereferenced it. The path is resolved absolute so this is practically unreachable, but bailing out with a log line is cheaper than the latent NPE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hpa6bjie5qkeNG62y6FmXm * Code Version 1.50 * Add the in-app update concept for 1.50 Captures the agreed design so it can be handed over later: extended update feed with per-artifact metadata, a build-time package-type marker, a testable check service replacing the blocking call in the ChatController constructor, a non-modal hint suppressed while connected to ON4KST, verified downloads and platform-specific execution. Concept only, nothing implemented yet. Line anchors were verified against this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HT8R7zTMoQnmJj6dGbaqqf * Use one base font size and a per-profile startup design JavaFX picks a 13px default font on macOS and Linux but 12px on Windows, so labels in fixed-size controls were cut off outside Windows. Both stylesheets now set 12px on the root. Each operator profile now chooses light or dark mode for startup on the GUI settings tab. The Windows menu entries remain a quick switch for the running session. All windows, including the update window and the top candidates list, follow the design, and the main window no longer starts light while the station map starts dark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Show a drag-to-install layout in the macOS DMG The DMG window now has a background with an arrow from the app to the Applications folder, in the KST4Contest colours. Finder applies the layout through AppleScript; if that fails, e.g. on a headless runner, the script warns and builds the plain DMG as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use the macOS system menu bar in all windows On macOS the main menus move into the system menu bar and are shared with the secondary windows, so they stay available whichever window has the focus. The connection state indicator becomes a read-only LINK menu there instead of the status bar button. Other platforms are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List direct commits on main in the release notes GitHub's generated release notes only cover merged pull requests, so commits pushed straight to main were missing. The release job now builds the notes itself: "What's Changed" holds "Commits on main" (first-parent, without bot and squash-merge commits) and GitHub's PR list as "Merged PRs". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Build with Gradle in two modules, and free core of JavaFX Etappen 1 and 2 of the Compose migration (43 commits). The Maven build, the single source tree and the JPMS module-info went together. core now holds the domain and app-desktop the user interface, both built by Gradle, packaged through jpackage from Gradle, with SpotBugs and PMD wired in. JavaFX moves to the classpath, which is why the launcher is Main and not the Application subclass. The PR check, the nightly artifacts, the release and the AUR packages all build from Gradle; the macOS DMG points at its output. Verified on Linux, macOS and Windows. core then loses JavaFX entirely. It observes through kst4contest.observe -- ObservableValue, ObservableRoster, and UiDispatcher as the one boundary a worker thread crosses to touch the interface. The controller collections become rosters and the visible station list a pure derivation rather than a FilteredList/SortedList chain, which is also what fixed two focus defects only a load test had exposed: the keyboard focus stays where the operator put it, and the send field keeps it after a row click. The rule that outlives this commit: ObservableRoster is the store and the list a view is bound to is a mirror of it, never the other way round. * Draw the whole interface in Compose Etappen 3 to 6a of the migration (129 commits). Every window the operator sees is Compose; the JavaFX ones still exist beside them so the two could be compared against a live session, which is the only way the differences came to light. The toolchain first, then the settings window with its eleven tabs and 72 real preferences; then the multi-column table as a component, and with it the rules it needs -- stable row identity, a selection that survives a refresh, column order the operator can drag, and a repaint when the rows change rather than when the list instance does. Then the update and monitor windows, and the main window: 22 station columns, the filter bar, the selected-station panel, the message tabs, the priority bar, the sked controls, the status indicators and the timeline, assembled behind a splitter because Compose has none. The map stops being a WebView. A Compose canvas projects WebMercator tiles itself, with the clustering in core and an asynchronous fetcher in place of the local tile proxy; derive() becomes a pure Kotlin reimplementation instead of a reflection call into com.sun.javafx. Most of the volume here is parity the operator would notice rather than new behaviour: desktop density instead of Material's phone minimums, colours taken from the JavaFX stylesheets instead of Material's palette, row dividers, band columns, grid-square labels and their colours, the home marker and antenna beam, pinch-to-zoom under Wayland, and layouts that survive a window narrower than the chart they stack. Two lessons are worth more than the diff. A control bound to a value Compose cannot observe writes through and never redraws -- that one shipped repeatedly with a green build, because no test drew anything. And a layout that clips instead of wrapping is worse than one that is merely too narrow: half a word is the worst available outcome. * Remove JavaFX The second half of Etappe 6 (61 commits). grep -rl "javafx\." over both main source trees is empty, nothing imports javafx, neither classpath resolves it, and org.openjfx is out of the build and the version catalog. The finding that shaped the work: the JavaFX main window was built on every start and never shown -- primaryStage's setScene and show were both commented out. Most of those 11,655 lines were dead weight rather than a port. But not all of them, and that is the trap this commit is really about: dead-looking methods still fed live Compose sinks. They were cut at their seams instead of deleted, and a sweep script that follows calls was written to find them, because reading the code does not reveal them. In order: the colour derivation frozen as checked-in test data, so the pin survives JavaFX being removed instead of silently comparing against itself; the live feeds lifted out of the never-shown window and given names and tests; the dead scaffolding deleted in slices; then the lifecycle. UiDispatcher now delivers to the AWT event dispatch thread, the ten Alerts are modal Compose dialogs, HostServices gives way to java.awt.Desktop with the mailto/http split JavaFX did not need, and extends Application, start(Stage), stop and launch are gone -- main builds the runtime and a shutdown hook replaces stop(). An independent review found two deadlocks in that move, both the same failure class, both invisible to the build: a blocking wait on the event thread for work that can only advance on the event thread. The profile picker waited on a latch and froze the client for good; ComposeWindowHost.close waited on Thread.join and returned with the window still marked open, which made a profile switch raise the dead runtime's window and leave the rebuilt profile without its Connect button. A third came out of fixing them: a ComposeDialog built off the event thread cannot create its GL context and never paints. Three defects only a GUI run showed, never the build: an NPE on a deleted field, a Compose menu still hanging on deleted MenuItems, and a category bookkeeping field that looked dead but is read by the outgoing-message category resolution -- deleting it would have sent messages in the wrong category. The binding docs and the manual no longer describe a JavaFX client. * Draw the menu bar in Compose, and give the drawing layer a net What the removal left open (4 commits). Compose's MenuBar is a javax.swing.JMenuBar set on the window, so Swing draws it and no theme reaches it; with no look-and-feel configured that meant the default Metal menu above a themed window. The bar is drawn in the window now, except on macOS, where the system bar is where that platform expects it. Two renderers read one description -- mainMenuModel is a plain function of the state, which also gives the menus their first tests. Closing the main window quits the application again, and the window writes its size back to the profile; both had quietly stopped working when the last JavaFX stage went. Then Etappe 7. Four faults shipped in the settings-window branch alongside a green suite of 382 tests, because not one of those tests drew anything: a control that never redrew, a tab strip that clipped mid-word, a form taller than its window with no way down, and colours that came from Material rather than the stylesheet. One regression test per class, and the spec's acceptance criterion is met rather than assumed -- each fault was put back deliberately and the matching test watched to fail. The CI had never run the suite at all; it does now, headless. What that took was not Xvfb: skiko's native library links against libGL and only has to be loadable, since the drawing tests render in software. Without the package the whole Compose half of the suite dies with a message that reads like a missing display. Found by running the job under act rather than on GitHub. The six tests that open a real window skip headless by their own guard. The summary step counts from the result XML and fails on failures there, because this project's rule is never to infer a green suite from an exit code. * Make the colours adjustable per operator profile Six colour roles per design, per profile: surface, window surface, field interior, text, accent and separator line. A change applies at once in every open window and survives a restart once the settings are saved, like any other preference. Three sources, later wins role by role: the shipped stylesheet from the classpath, then the profile's own stylesheet file if there is one, then the overrides stored in the profile's preferences. resolvePalette is a pure function and reports where every role's colour came from, which the tab shows per field -- with two sources able to decide one colour, that is the only thing keeping "why is this colour what it is" answerable, and the spec calls it mandatory rather than decorative. Four things this had to get right, each learned the hard way and each pinned: A role is a selector and a property, not a property alone. Three of the six are stated outside .root -- text on .label, the separator on .separator *.line -- and -fx-background-color alone appears a dozen times in the evening sheet. Measured: the shipped daylight sheet states no colour at all, the evening sheet states five and implies the sixth. Kst4ContestTheme reads the palette from the store's state and not from remember. That read is what makes a change repaint every open window; remember(darkMode) held the palette until somebody toggled day/evening, so a change reached nothing. A derived colour follows its source's value, never its provenance. The root profile's own stylesheet is the one copyResourceIfRequired writes at every startup -- the shipped sheet itself -- so layer 2 declares the accent on every existing installation with nobody having chosen anything. Keying textAccent on "does this come from a file" repainted the evening accent from green to blue on upgrade and made "reset to shipped" disagree with "discard my changes". The contrast guard measures where colours are actually drawn, read off Theme.kt's Material mapping rather than off the role names: background and surface are both the window surface, and primary is textAccent, not accent. It reports a pair only when it is both under WCAG AA and worse than the shipped sheet -- the shipped sheets have three pairs under 4.5 themselves, so an absolute floor would warn on an untouched client and teach the operator to ignore warnings. The way back out of an unreadable palette is deliberately overbuilt, because it is the one failure that locks itself in. "Reset to the shipped colours" takes the shipped palette as a required parameter and reads no composition local, so there is no path by which it could be drawn in the colours it exists to undo; it looks out of place under a customised palette, and that is what makes it findable. Ctrl+Z, Ctrl+D and Ctrl+R reach the three ways back even with the caret in a colour field. A mistyped colour is refused and the field snaps back. The profile's stylesheet is the hand-editing route the copy under ~/.praktiKST has been promising since it was first written and never read. It is read now. A button writes the shipped template as a starting point and never replaces a file that is already there. An unreadable, truncated or non-CSS file does not prevent startup -- the role falls back to the shipped design. A sheet stating -fx-base and no -fx-background states the derived window surface too, the way JavaFX does, or the most natural hand edit would leave every window the shipped light grey. 101 new tests. All of it runs headless. * Document the adjustable colours, and record the design and the plan Both manuals get the Colours tab in full: the six roles, that a change applies at once but survives a restart only after Save settings, the three ways back with their keyboard shortcuts, what each provenance label means, and the readability notice that warns without refusing. Two things an operator would otherwise have to discover. The reset button is always drawn in the shipped colours and works when nothing else can be read -- the risk section of the design says plainly that a keyboard path only helps whoever knows it, so the manual is where this has to be written down. And the root profile already has both stylesheet files, because KST4Contest has been writing them at startup for years, so most of its roles report "from file" rather than "shipped" with identical values. PROJECT_CONTEXT.md records the decisions that outlive this stage: the three sources and their precedence, that a role is a selector and a property, that the theme must read the store's state rather than remember, where the contrast pairs come from, why the root profile's file layer always exists, and why the lifeline refuses the active palette. Also corrects its claim that CSS stays global -- a profile now owns its stylesheets. The design document and the implementation plan are kept as they were written, including the plan's own corrections where measuring the code contradicted it. * Speak the operator's language The interface is German or English, chosen per operator profile on the GUI tab, in force at once in every open window, and remembered across restarts. 376 texts in both languages. The texts are ordinary .properties files that nobody has to compile: a contributor copies the English one, translates the values, and opens a pull request. A third module -- build tooling that never ships -- turns them into Kotlin before anything compiles, which is what makes a mistyped key and a wrong argument count compile errors rather than things an operator discovers. It lives in the main build rather than buildSrc because buildSrc's tests do not run on `./gradlew build`, measured, and a generator whose tests never run is the exact failure it exists to prevent. Incompleteness is allowed and incorrectness is not. A missing key warns and falls back to English, so a partial contributed language can be submitted at all; an orphan key, a placeholder the translation drops, a duplicate key or an unreadable file fail the build with the generator's own message naming the file. The argument count always comes from the English base. German is held complete by a test because it ships; a contributed language is not. What is deliberately not translated is the harder half. Protocol frames and chat commands would break the connection or go unread; the beacon texts, auto answers, snippets and shortcuts go to other radio amateurs and sit in every operator's stored profile; the 273 println calls in core are diagnostics, and a German stack trace is worse for whoever reads the bug report; and QRA, QRG, QTF, LOS, Fresnel and wkd read the same in either language. Three tests guard the boundary by measuring behaviour rather than text: the protocol frames built under three locales, every preference default compared by reflection, and no translation value being a protocol command. The reflective one is the only one that covers what nobody thought of. One insight turned up three times. Anything built outside a composition freezes the language it was built in: the window title, the settings tab strip and every column header are all constructed once at startup -- the columns from Java -- so each now holds a function read when it is drawn. That also fixed something older and unrelated to language: the main window's title was a fixed "KST4Contest (Compose)" for the whole session, where the JavaFX window had carried the live connection line. It carries it again. A column's id is never translated. It is what the remembered widths and order are keyed on. * Document the two languages, and how to contribute a third Both manuals get the language picker in full: where it sits, that it applies per profile and takes effect at once in every open window, and -- at more length -- what is deliberately left alone and why. An operator who sees an English beacon text in a German client should be able to read that it is meant that way. The contribution route is written down in both, because plain text files are only useful if somebody knows they are there: copy, translate, submit; a partial file is welcome; the build reports mistakes by file name and writes a worksheet of what is still missing. PROJECT_CONTEXT records the decisions that outlive this stage -- the four kinds of text and the rule that the addressee decides, that core produces no display text, that incompleteness is allowed and incorrectness is not, that anything built outside a composition needs its text read inside one, and that a column id is never translated. CLAUDE.md said two modules; there are three, and the third never ships. It also states the German orthography now, which this session got wrong across a spec and two plans: ss after a short vowel, so muss and dass. * Remove the implementation plans now that every stage is executed Thirteen plans, all of them step-by-step instructions for work that is committed. A plan left behind reads to the next person like an open intention, and these describe a build that no longer exists -- the first still explains how to introduce Gradle. The specifications stay. They say why the architecture is what it is, which the code cannot, and three of them are still argued from. The live-findings note stays too: CLAUDE.md sends people to it, and its hardest lesson -- that a blocking wait on the AWT event thread deadlocks the interface while the build stays green -- is written down nowhere else. Its sweep script stays as well. The note argues explicitly that the script belongs beside the finding so the next reader can measure rather than believe, and removing it would contradict the reasoning it illustrates. Two references in the note and one in the JavaFX-removal spec pointed at plans and now say where the reasoning went instead. * Point the JavaFX-removal spec at where its plan's reasoning went * Remove the stray release-metadata patch from the working tree A 416-line patch file that was applied long ago and has sat in the repository root since. It predates this branch and stays in the shared history, where rewriting it would mean rewriting commits that are already on main and on origin. * Check the translation files in CI and say what they hold classes already runs the generator, so an orphan key, a dropped placeholder or a duplicate fails the compile job with the generator's own message naming the file -- verified by breaking strings_de.properties on purpose. A step summary counts each language against the base, so the author of a translation PR sees what they covered without reading the build log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Refuse a translation key that would not form a Kotlin name The generator escaped every translation value but emitted the key verbatim as the member identifier, and identifierFor strips only . _ - -- so a key carrying a space, a brace, a quote or a newline from a \uXXXX escape broke out of val <name>: String get() = lookup(...) into an init block or property initializer that runs when Strings is instantiated. Translation files are the contribution surface that arrives by pull request, so this was a path from a merged .properties change to code in every client. The generated identifier is now held to letters-then-letters-and-digits before any source is written, alongside the existing orphan and collision refusals. Proven by replaying a file-writing payload against the built generator: it now throws and writes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Port the rebased diagnostics and session logging onto the modules origin/main landed replayable session logging (MessageHistoryRecorder), file logging (ApplicationFileLogging) and a user-list refresh split on the old flat layout. The rebase carried the new files, but three things needed the new architecture: - ChatController.fireUserListUpdate() used javafx Platform; core is toolkit-free now, so it dispatches through uiDispatcher like every other fire method. - Two new tests were left in the old src/test/java; moved into core. - ScoreServiceRefreshTest mocked javafx Platform statically; it now injects the DirectUiDispatcher the no-arg constructor installs. The Leaflet map performance work was dropped with the HTML map it optimised. classes testClasses and test are green: 1069 tests, 0 failures, 3 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give the debug-to-file recording a home in the Compose settings origin/main added a checkbox to the old JavaFX settings that recorded the whole ON4KST session to file; the Compose redraw dropped the old settings UI, so the toggle had no control left even though the preference and its wiring survived the rebase. It returns to the message-handling tab: the state writes the preference and asks the application, through a new SettingsHost callback, to open or close the error-log handler and the history recorder in the order the old checkbox kept. The explanatory line and its German translation say that passwords are redacted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix Codex review findings on PR 89: station row key, map selection, sort after filter - Station row key now includes the chat category, so the same callsign in two categories no longer collides. - Map marker click and Reset view go through the central station selection. - Sorting now operates on the filtered rows, so filters stay effective. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the map drawing test off the network and the user's tile cache The test assumed no tile ever arrives, but it used the default TileFetcher, which reads the real disk cache and the OSM tile server. On CI a tile now and then landed before the frame was rendered and covered the background the 'unreachable tile source' test asserts on. An empty temporary cache and a source that always fails make the assumption hold by construction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3e8457eede | Optimize station map refresh performance | ||
|
|
df70cb1943 |
Update diagnostics and analytics context
Silence the UTC time formatting console diagnostic and document the current production analytics status. |
||
|
|
323c249bf7 |
Add replayable ON4KST session logging
Record redacted RX/TX frames with session rotation and runtime debug controls. Add targeted file diagnostics, tests, documentation, and a silent priority-score refresh path. |
||
|
|
f9760bcbaa | chore: update AUR packages to v1.44.0 [skip ci] | ||
|
|
a8f12876ec | Align private metrics with GoAccess request panel | ||
|
|
89422d18ad | Fix private analytics aggregation | ||
|
|
a3f3bf8872 | Changes at the stats analytics | ||
|
|
393c9c49ee | Changed link monitoring as described by on4kst, Alain | ||
|
|
f0508f51a2 | Added screenshot where thhey belongs to at the project hp | ||
|
|
923d76beb0 | refactoring project website landing page | ||
|
|
821535a24f | Documentation on user counter and analytics to the kst4contest page | ||
|
|
914a06cb7f | Corrections on user counter and analytics to the kst4contest page | ||
|
|
b23884bde1 | Added user counter and analytics to the kst4contest page | ||
|
|
4ad1cf71dd
|
Document that a sandboxed logging program needs its own network permission
A logging program started as a Flatpak, or through a Wine environment such as Bottles, uses the network permissions of that sandbox. Without shared=network its 127.0.0.1 is not the loopback KST4Contest listens on, so the connection is refused although KST4Contest reports the port correctly. Adds the check and the override command to the DX Cluster troubleshooting section in both language versions, and notes that the published KST4Contest Flatpak manifest already carries --share=network. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HT8R7zTMoQnmJj6dGbaqqf |
||
|
|
ad212e3e71 | chore: update AUR packages to v1.44.0 [skip ci] | ||
|
|
08d65a0e23 |
Serialize Flatpak repo publishes to prevent non-fast-forward push failures (#88)
* Initial plan * Serialize Flatpak repo publish jobs Co-authored-by: Rsclub22 <37273508+Rsclub22@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Rsclub22 <37273508+Rsclub22@users.noreply.github.com>v1.44.0 |