mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-11-09 06:38:37 +01:00
feat: improve station map navigation and overlays
This commit is contained in:
1 parent
b8bb86977f
commit
3d2fb70f1a
38 files changed
+1783
-166
No files matched your search
@@ -186,23 +186,25 @@ CR/LF framing, XML framing, ports/transports, callsign normalization and frequen
|
||||
|
||||
- Contest operating speed and low-friction interaction are primary goals.
|
||||
- Incidental code changes must not unexpectedly change selection, focus, sorting, tab state, map zoom or prefilled text.
|
||||
- Map reset clears the selected target without changing zoom unless explicitly redesigned.
|
||||
- Map reset clears the selected target and recentres on the local station without changing zoom.
|
||||
- **Grouping** changes only spatial clustering of the existing Compose map markers. It must not reload tiles or station data, request a new controller snapshot, or change zoom, viewport or selection.
|
||||
- Base-callsign aggregation into one geographical marker happens before screen-based clustering. Disabling clustering displays each resulting positionable map station individually but never splits active variants of the same normalised base callsign into separate geographical markers.
|
||||
- A hidden Station Map is not continuously refreshed. Relevant background changes mark it dirty; the next `showWindow()` performs one current full refresh before normal visible-window updates resume.
|
||||
- Maidenhead grid construction depends only on viewport bounds, zoom and WebView size. Ordinary station-data refreshes must not rebuild or serialise the grid when those inputs are unchanged.
|
||||
- Maidenhead grid construction depends only on viewport bounds, zoom and Compose-canvas size. Unwrapped viewport bounds may cross the international date line and must be split into valid world intervals without duplicating grid cells. Ordinary station-data refreshes must not rebuild the grid when those inputs are unchanged.
|
||||
- Panning updates viewport and grid state through `notifyViewport()` without rebuilding station markers or screen-based clusters. Zoom changes, station-data changes and clustering toggles still rebuild the marker layer.
|
||||
- Debug file logging reports Station Map refreshes only when total FX-thread refresh time reaches 100 ms. The diagnostic separates snapshot and render time and includes visible-member and station counts.
|
||||
- Station selection preserves the established `/cq callsign` prefill behaviour.
|
||||
- `StationSelectionState` is the canonical selection shared by the station table and Further Info panel. Its identity is the complete callsign plus chat category. Explicit actions carry a `StationSelectionSource` and increment a separate scroll-request revision even when the same station is selected again; roster reconciliation replaces the selected instance without moving the table.
|
||||
- `StationSelectionState` is the canonical selection shared by the station table and Further Info panel. Its identity is the complete callsign plus chat category. Explicit actions carry a `StationSelectionSource` and increment separate scroll and explicit-selection revisions even when the same station is selected again; roster reconciliation replaces the selected instance without moving the table or map.
|
||||
- Recent-join state is transient domain state keyed by complete callsign plus chat category. Only live `UA5`/`UA2` frames can create an entry; the `UA0`/`UE` initial or reconnect snapshot and `UM3` profile updates never do. Duplicate live updates do not renew the timestamp, a leave removes the entry, and a genuine rejoin begins a new ten-minute interval. The local login callsign is excluded. The core `ObservableRoster` is mirrored through `UiDispatcher`; New-joined actions reuse the canonical station selection and scroll request.
|
||||
- The station table starts with a case-insensitive ascending callsign sort. A user-selected sort remains authoritative across roster and filter updates. An externally selected filtered station is pinned into the table only; `filteredRows` remains the exact filter result used by the map.
|
||||
- The station table starts with a case-insensitive ascending callsign sort. A user-selected sort remains authoritative across roster and filter updates. An externally selected filtered station is pinned into the table. `filteredRows` remains the exact filter result used for ordinary map markers; the map renders the current filtered-out selection separately without counting or clustering it.
|
||||
- PM, Public, QSO-of-the-other, priority, timeline and individual map-marker actions route through the application-level station selector. Directed messages target the sender, except that an outgoing local PM targets its receiver. Public and QSO-of-the-other rows target their sender in the message category. Direct clicks and context clicks inside the station table select without issuing an automatic scroll request; selections originating in the map, messages, priority, New joined or other station controls issue one even when the identity was already selected.
|
||||
- The selected-station message radio mode is independent of station identity and always retains the complete callsign plus chat category. `PM_TO_ME` is the private conversation with the local station plus public traffic from the selected station carrying the structured leading `(LOCALCALL)` address, `PM_TO_OTHER` is private traffic sent by the selected station to other participants, `PUBLIC` is traffic sent by it to `ALL`, and `NOTHING` applies no additional type/receiver restriction. An arbitrary callsign mention in prose is not a structured public address.
|
||||
- ON4KST chat parsing records `messageDirectedToCommunity` from the receiver field and `messageDirectedToMe` from either a genuine private receiver or the exact leading public address. The selected-station Compose message mirror is initialized with the main window state, before the first selection callback, and roster changes recompute and push the filtered rows automatically.
|
||||
- The map's **Reset filters** action uses the same `StationFilterState.reset()` path as the main filter bar and does not change sort, selection, zoom, viewport or the display-only Grid-color toggle. Individual raw-callsign markers retain an already selected category variant; otherwise they use the configured Main category. Spatial clusters zoom and never choose an arbitrary member.
|
||||
- The user list and map share one station context-menu implementation. A secondary click selects a unique target through `StationSelectionState`; base-call markers or screen clusters containing several full-callsign/category identities first require an explicit target choice. Message snippets reuse the existing send-field action, and the context sked editor owns an isolated draft with the target fixed at opening time.
|
||||
- Map markers draw only the display callsign; bands, `B+`, locator and QRG details remain in tooltips. When Grid color is enabled, the canvas draws a black outline and a 30 percent black fill overlay for four-character squares from `WorkedGrossFieldCache.snapshotWorkedGrossFieldsAny()`, independently of visible station markers. The cache is rebuilt from SQLite at startup and is the same any-band state used by **Only new grids**.
|
||||
- Explicit station selections publish a map-navigation request separate from ordinary selected-state reconciliation. The map fits the complete home-to-target path with padding, preserves an already sufficient zoom and only zooms out when required. Explicit QTF field and compass-button actions preserve zoom and shift the local station 25 percent of the shorter canvas dimension opposite the selected bearing. Background roster changes, filter restoration, reset, rotor feedback and asynchronous path analysis never publish these requests; the latest explicit request wins. A selection without valid coordinates leaves the camera unchanged. Reset view clears selection and recentres on home without changing zoom.
|
||||
- Active QTF and QRB predicates are visualised by a distinct semi-transparent overlay using the same filter state and semantics: minimum QRB is exclusive, maximum QRB inclusive, and the QTF sector uses the configured complete beamwidth. An unbounded distance range is clipped by the visible canvas rather than a fabricated radio range. An empty minimum/maximum combination has no fill but retains both boundary lines. The actual antenna beam remains a separate overlay driven by `actualQTF`, `stn_antennaBeamWidthDeg` and `stn_maxQRBDefault`; applying Station settings refreshes its Compose-observable geometry immediately without issuing rotor commands. The map header shows a localized red/blue legend only for overlays that are currently drawable, and the legend reuses the exact canvas line colours rather than defining a parallel colour scheme.
|
||||
- The AP timeline renders the producer's four candidate lanes in order (two in-beam, two global priority) plus a separate sked lane. Its relative 0–30 minute axis normally uses five-minute ticks and increases the interval when labels would overlap. The vertical message splitter applies pane-specific minimums for the timeline, shortcut row and send row, so older stored divider positions cannot clip these fixed-height controls; stored positions remain the unconstrained baseline.
|
||||
- Sending without an explicitly selected send category preserves the established Main-category fallback unless explicitly changed.
|
||||
- The lower selected-station panel contains a vertical two-pane `SplitterPane`: the upper pane owns the independently scrollable message table, while the lower pane scrolls NOT-QRV and Sked above a pinned lookup group. The lower pane reserves 160 dp before stored divider positions are applied, so normal small-window layouts retain direct access to lookup actions; impossible smaller layouts keep the action groups scrollable. `GUIselectedCallSignSplitPane_dividerposition` persists this divider through the existing profile-scoped layout autosave. Station or message updates never reset it. Empty `DataTableView` instances retain their header and border and show an explicit empty-state label. Header metadata and message filters use width-aware flow layouts; the NOT-QRV and sked groups switch between a row and a column from the panel's actual Compose constraint, without duplicating their domain state.
|
||||
@@ -415,7 +417,7 @@ Before implementing planned items, re-check current decisions and obtain a fresh
|
||||
- Historical project context is useful but may be stale; current code/tests win.
|
||||
- External service/API behaviour must be verified against current upstream documentation when uncertain.
|
||||
- Screenshots in manuals/website may need targeted replacement after visible UI changes; never fabricate them.
|
||||
- `station_map_path_analysis.png` and `station_map_reset.png` predate the current header controls, callsign-only markers, shared station context menu and Worked-square overlay. Replace them with current screenshots when suitable source images are available; the website reuses `station_map_path_analysis.png` through `/manual/assets/`.
|
||||
- `station_map_path_analysis.png` and `station_map_reset.png` predate the current header controls, callsign-only markers, shared station context menu and Worked-square overlay. The former remains useful only where the visible analysis panel itself is the subject. `station_map_spatial_filters.png` is the current general map screenshot shared by the DE/EN interface manuals and the website through `/manual/assets/`.
|
||||
|
||||
## Recent Significant Changes
|
||||
|
||||
|
||||
@@ -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**
|
||||
@@ -0,0 +1,26 @@
|
||||
# Map Overlay Legend and Screenshot Design
|
||||
|
||||
## Intent
|
||||
|
||||
Make the two map overlays understandable without consulting the manual and replace the outdated general map image with the supplied screenshot of an active QRB range.
|
||||
|
||||
## Behaviour
|
||||
|
||||
- The map header shows a compact red **Antenna** legend item only while the antenna sector can be drawn.
|
||||
- It shows a compact blue **Active filters** legend item only while a valid QTF or QRB overlay is active.
|
||||
- A localized tooltip explains that red represents the actual antenna heading, beamwidth and maximum QRB, while blue represents active QTF and QRB filters.
|
||||
- The existing responsive `FlowRow` may wrap the legend with the other header controls.
|
||||
- Overlay rendering, filtering, antenna heading and rotor behaviour do not change.
|
||||
|
||||
## Screenshot
|
||||
|
||||
- Store the supplied image as `github_docs/station_map_spatial_filters.png` without altering its pixels.
|
||||
- Use it in the German and English UI manual and on the Station Map website feature page.
|
||||
- Describe the visible range correctly as minimum and maximum QRB, not QTF.
|
||||
- Retain `station_map_path_analysis.png` where the visible path-analysis panels are the subject.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Reuse the exact overlay colours rather than duplicating colour literals.
|
||||
- Keep English and German UI strings and documentation aligned.
|
||||
- Do not stage, commit, push or publish any change.
|
||||
Reference in new issue
Block a user