Apply live beacon settings and improve station map (#164)

Apply beacon checkbox changes at runtime and keep the aggregate status chip synchronized.

Improve explicit station-map navigation, spatial filter overlays, overlay legend, selected-path contrast, and the documented map screenshot.
This commit is contained in:
Praktimarc authored and GitHub committed 2026-11-05 00:52:17 +01:00
1 parent 4d96822c0c
commit d925c83412
61 files changed
+2686 -299

No files matched your search

@@ -0,0 +1,108 @@
# Live Beacon Control Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make both beacon enable switches effective immediately at runtime and make the aggregate beacon status chip an accurate shared toggle.
**Architecture:** Keep `ChatPreferences` as the profile-scoped source of truth, but expose the two activation flags as thread-safe observable values. Route settings and chip actions through `ChatController`, publish one aggregate `MyBeacon` status, and give only that thread-status chip an optional click action.
**Tech Stack:** Java 21, Kotlin 2.2, Compose Multiplatform, JUnit 5, Mockito, Gradle wrapper
**Spec:** `docs/superpowers/specs/2026-11-04-live-beacon-control-design.md`
## Global Constraints
- Work directly on `main`, as explicitly authorized.
- Do not stage, commit, push, merge, tag, release or deploy.
- Keep beacon templates, interval handling, TX framing and second-chat eligibility unchanged.
- Write source-code and configuration comments in English.
- Use focused tests first and finish with `./gradlew.bat build --console=plain`.
## Review Focus
- A disabled second chat must leave its dormant second-beacon flag unchanged when the chip is clicked.
- An external chip toggle must update an already open Beacon settings tab without reopening it.
- A runtime toggle must not restart the timer or enqueue an immediate message.
- An invalid beacon template must still be reported as a critical status rather than hidden by the aggregate state.
- Profile reload must replace the observable values without carrying the previous profile's runtime flags across.
---
### Task 1: Observable profile-scoped beacon activation
**Files:**
- Modify: `core/src/main/java/kst4contest/model/ChatPreferences.java`
- Test: `core/src/test/java/kst4contest/model/ChatPreferencesBeaconStateTest.java`
**Interfaces:**
- Produces: existing boolean getters/setters plus observable main/second beacon value accessors
- Consumes: existing `MutableValue` / `SimpleValue` infrastructure
- [x] Write tests that changes are observable, values remain independent, XML reload restores both values, and an unsaved in-memory change does not survive a fresh load.
- [x] Run `./gradlew.bat :core:test --tests kst4contest.model.ChatPreferencesBeaconStateTest --console=plain` and verify the new observable-accessor tests fail because the API is absent.
- [x] Back the two flags with `SimpleValue<Boolean>` while preserving the existing getters, setters and XML element names.
- [x] Re-run the focused test and the existing ChatPreferences tests.
### Task 2: Immediate settings effect and aggregate controller semantics
**Files:**
- Modify: `core/src/main/java/kst4contest/controller/ChatController.java`
- Modify: `core/src/main/java/kst4contest/controller/BeaconTask.java`
- Create: `core/src/main/java/kst4contest/controller/BeaconStatus.java`
- Test: `core/src/test/java/kst4contest/controller/BeaconControlTest.java`
- Test: `core/src/test/java/kst4contest/controller/BeaconTaskTest.java`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/tabs/BeaconTabState.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/tabs/BeaconTab.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/ObservedValue.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/SettingsTabs.kt`
- Modify: `app-desktop/src/test/kotlin/kst4contest/view/compose/tabs/BeaconTabStateTest.kt`
- Modify: `app-desktop/src/test/kotlin/kst4contest/view/compose/tabs/SettingsApplyScreenTest.kt`
- Modify: `app-desktop/src/test/kotlin/kst4contest/view/compose/tabs/DraftedSettingsTableTest.kt`
**Interfaces:**
- Consumes: observable preference values from Task 1
- Produces: controller setters, usable-beacon toggle, aggregate status publication, Compose observation bridge
- [x] Write failing controller tests for `off`, `1/2`, `on`, main-only toggling when second chat is disabled, and both-category toggling when it is enabled.
- [x] Write failing BeaconTask tests proving a flag changed between two runs changes queued output without restarting a timer, and that the final status is aggregate.
- [x] Change Beacon settings tests so activation is immediate while templates and interval still wait for Apply; verify RED against the current draft-backed flags.
- [x] Implement the aggregate status helper and controller actions without changing the outbound-message path.
- [x] Make only the enable flags bypass `SettingsDraft`, observe their preference values in Compose, and wire their setters through the controller.
- [x] Run the core and app-desktop focused tests and verify GREEN.
### Task 3: Clickable beacon status chip
**Files:**
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/StatusIndicatorState.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/StatusBar.kt`
- Modify: `app-desktop/src/main/java/kst4contest/view/Kst4ContestApplication.java`
- Modify: `app-desktop/src/test/kotlin/kst4contest/view/compose/StatusIndicatorStateTest.kt`
- Create or modify: focused Compose status-bar rendering test under `app-desktop/src/test/kotlin/kst4contest/view/compose/`
**Interfaces:**
- Consumes: controller usable-beacon toggle from Task 2
- Produces: optional per-source chip action registered only for `MyBeacon`
- [x] Write failing state tests for actions registered before and after the first status report and for non-action thread chips.
- [x] Write a failing Compose test proving the beacon chip invokes its action while an ordinary status chip remains non-clickable.
- [x] Add optional actions to thread-status buttons and apply button semantics only when an action exists.
- [x] Register the controller toggle for `MyBeacon` during main-window state construction.
- [x] Run the focused status tests and the affected main-window tests.
### Task 4: Documentation and verification
**Files:**
- Modify: relevant beacon sections in `github_docs/de-Konfiguration.md`
- Modify: corresponding sections in `github_docs/en-Configuration.md`
- Modify: `docs/PROJECT_CONTEXT.md`
- Review: relevant screenshots and changelog impact only
**Interfaces:**
- Consumes: completed runtime and UI behavior from Tasks 1-3
- Produces: aligned user documentation and durable state/persistence invariant
- [x] Update both manuals with immediate runtime effect, explicit-save persistence, aggregate chip behavior and second-chat handling.
- [x] Record the observable canonical state, timer-read rule and persistence boundary in `docs/PROJECT_CONTEXT.md`.
- [x] Run all focused tests, then `./gradlew.bat build --console=plain` and read test-result XML summaries.
- [x] Run `git diff --check`, inspect PMD/SpotBugs XML for findings in changed production files, and review the final diff for unrelated changes.
- [x] Leave all changes unstaged and uncommitted and report remaining manual UI checks.
@@ -0,0 +1,110 @@
# Map Overlay Legend and Screenshot Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a responsive, localized map-overlay legend and document the spatial QRB overlay with the supplied screenshot.
**Architecture:** A focused Compose legend consumes the same red antenna and theme-aware blue filter colours as the canvas. `StationMapWindow` derives visibility from the effective antenna and spatial-filter state and places the legend in the existing header `FlowRow`. Documentation receives a new immutable screenshot asset without repurposing the path-analysis image.
**Tech Stack:** Kotlin, Compose Multiplatform, generated properties-based i18n, JUnit 5 Compose UI tests, Markdown, Eleventy
**Spec:** `docs/superpowers/specs/2026-11-04-map-overlay-legend-and-screenshot-design.md`
## Global Constraints
- Preserve map filtering, station selection, antenna heading, rotor control and overlay geometry.
- Reuse existing design tokens and exact overlay colours; add no dependency.
- Keep German and English strings and documentation semantically aligned.
- Keep all changes ungestaged and uncommitted.
## Review Focus
- A map with no drawable overlay must not reserve an empty legend area; Task 1 tests both items absent.
- A QRB-only filter must show the blue item without implying QTF is active; Task 1 tests the generic **Active filters** wording.
- Invalid spatial values must not show the blue item; Task 1 uses `hasRenderableValues` as the gate.
- Narrow windows must wrap rather than clip controls; Task 1 retains the existing `FlowRow` and adds a narrow rendering assertion.
- The screenshot must not replace an image whose caption promises visible path-analysis panels; Task 2 checks all asset references.
---
### Task 1: Localized responsive overlay legend
**Files:**
- Create: `app-desktop/src/main/kotlin/kst4contest/view/compose/map/MapOverlayLegend.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/map/ComposeStationMap.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/map/SpatialFilterOverlay.kt`
- Modify: `app-desktop/src/main/kotlin/kst4contest/view/compose/map/StationMapWindow.kt`
- Modify: `app-desktop/src/main/i18n/strings_en.properties`
- Modify: `app-desktop/src/main/i18n/strings_de.properties`
- Test: `app-desktop/src/test/kotlin/kst4contest/view/compose/map/StationMapWindowScreenTest.kt`
- Test: existing i18n-generator and translation tests
**Interfaces:**
- Consumes: antenna-sector visibility, `SpatialFilterOverlayState.isActive`, `SpatialFilterOverlayState.hasRenderableValues`, current theme.
- Produces: `MapOverlayLegend(showAntenna: Boolean, showSpatialFilters: Boolean, darkMode: Boolean)` and shared overlay-colour accessors.
- [ ] **Step 1: Add failing Compose tests**
Assert that the red item appears for a drawable antenna sector, the blue item appears only for an active valid spatial filter, both disappear with neither overlay, and the header remains laid out at a narrow test width.
- [ ] **Step 2: Run the focused screen test and confirm the missing legend fails**
Run: `./gradlew.bat :app-desktop:test --tests kst4contest.view.compose.map.StationMapWindowScreenTest --console=plain`
- [ ] **Step 3: Add localized labels and tooltip text**
Add English `Antenna`, `Active filters` and the two-colour explanation, with equivalent German strings `Antenne`, `Aktive Filter` and its explanation.
- [ ] **Step 4: Implement the legend with shared colours**
Expose the existing antenna colour and theme-aware spatial-filter line colour within the map package. Implement compact colour swatches and labels in `MapOverlayLegend.kt`, and add the component to the existing header `FlowRow` with dynamic visibility.
- [ ] **Step 5: Run focused Compose and i18n tests**
Expected: all selected tests pass in both languages; existing map interactions remain unchanged.
### Task 2: Replace the outdated general map screenshot
**Files:**
- Create: `github_docs/station_map_spatial_filters.png`
- Modify: `github_docs/de-Benutzeroberflaeche.md`
- Modify: `github_docs/en-User-Interface.md`
- Modify: `website/src/features/station-map.md`
- Modify when the durable UI invariant changes: `docs/PROJECT_CONTEXT.md`
**Interfaces:**
- Consumes: supplied 1257×1016 PNG showing `QRB > 500 km` and `QRB ≤ 666 km`.
- Produces: one shared manual/website asset copied by Eleventy to `/manual/assets/`.
- [ ] **Step 1: Copy the supplied PNG without resampling**
Verify its dimensions and file hash after copying.
- [ ] **Step 2: Update targeted DE/EN manual references**
Use captions that describe the selected path, red antenna sector and blue QRB ring. Remove only the obsolete screenshot note attached to the replaced general map image.
- [ ] **Step 3: Update the Station Map website feature page**
Reference `/manual/assets/station_map_spatial_filters.png` and retain the older path-analysis image for manual sections where the analysis panels are explicitly described.
- [ ] **Step 4: Verify asset references and website output**
Run: `npm.cmd test` and `npm.cmd run build` in `website`; verify the generated image path exists.
### Task 3: Final verification
**Files:**
- Review all files from Tasks 1 and 2.
**Interfaces:**
- Consumes: completed legend, translations and screenshot references.
- Produces: verified uncommitted working tree.
- [ ] **Step 1: Run focused map and translation tests**
- [ ] **Step 2: Run `./gradlew.bat build --console=plain` and inspect test XML plus PMD/SpotBugs reports**
- [ ] **Step 3: Run `git diff --check` and inspect `git status --short --branch`**
- [ ] **Step 4: Report native visual checks separately from automated rendering evidence**