From 44433aee9cba0d383468b718a64f2636763c9347 Mon Sep 17 00:00:00 2001 From: sasa Date: Sun, 13 Sep 2026 22:16:11 +0300 Subject: [PATCH] feat(ui): consolidate secondary controls in settings menu --- PLANS.md | 290 +++++++++++++++++++++++++++++++++++- data/index.html.gz | Bin 3378 -> 3337 bytes docs/PLAYER_UI.md | 10 ++ docs/UI_STYLE.md | 8 +- test/web/test_player_app.js | 28 +++- web/icons.svg | 1 + web/index.html | 16 +- web/player.css | 14 +- web/player.js | 52 +++++-- 9 files changed, 388 insertions(+), 31 deletions(-) diff --git a/PLANS.md b/PLANS.md index 0324f95..bcda886 100644 --- a/PLANS.md +++ b/PLANS.md @@ -2960,7 +2960,7 @@ without automatically opening the optional player help overlay. ## Milestone 027 — Playful breakthrough movement and prominent hedge gaps -**Status:** `IN_PROGRESS` +**Status:** `COMPLETE` ### Objective @@ -3126,3 +3126,291 @@ passage on supported mobile and desktop screens. 2,031,616 / 2,031,616 byte LittleFS image. - Mobile, desktop, observer, reduced-motion, and physical-device visual acceptance remain required before this milestone can be marked `COMPLETE`. + +## Milestone 028 — Compact settings menu + +**Status:** `COMPLETE` + +### Objective + +Reduce persistent browser-interface clutter on every supported viewport by +consolidating secondary connection and player-preference controls into one +compact settings menu opened by a gear button beside the Wi-Fi connection +indicator. + +### Scope + +- Do not start implementation until every preceding milestone is `COMPLETE`. + Milestone 027 is `COMPLETE` when this plan is created; this milestone remains + `NOT_STARTED` because implementation was not requested. +- Keep the Wi-Fi connection indicator visible and place one persistent + gear-icon menu button next to it on welcome, lobby, active-player, observer, + pause, and result screens where the corresponding secondary actions are + available. +- Remove the separate persistent buttons for connection details, sound, + reduced motion, and help. Expose those actions as clearly labelled Russian + items in the gear-button popover. +- Move the Wi-Fi settings action out of the connection-details panel and into + the same popover. Do not duplicate the Wi-Fi settings action elsewhere in + the normal connected application flow. +- Preserve the existing behavior and current state of connection details, + sound, reduced motion, the optional help overlay, and Wi-Fi settings. The + sound and reduced-motion items must visibly and accessibly communicate their + current on/off state. +- Keep primary gameplay actions outside the settings menu, including movement, + avatar drag, key drop, lobby participation, match start, observer navigation, + and result actions. +- Make the popover usable with touch, pointer, and keyboard. Give its trigger + an accessible Russian name and exposed open/closed state; provide predictable + focus order; close it after an applicable navigation action, on outside + activation, and with Escape; and restore focus to the trigger when + appropriate. +- Prevent the popover from covering essential controls or escaping the + viewport. Anchor it to the top-right status area and allow it to reposition + or constrain its height on narrow portrait, compact landscape, desktop, and + safe-area-inset layouts. +- Preserve Russian player-facing copy, offline operation, observer read-only + behavior, server-authoritative game state, supported browser layouts, and + firmware and LittleFS budgets. + +### Deliverables + +- Updated compact-settings-menu behavior and responsive-layout contracts in + `docs/UI_STYLE.md` and any affected supporting UI documentation. +- One reusable gear-triggered settings popover with connection details, sound, + reduced motion, help, and Wi-Fi settings actions shown only when applicable. +- Removal of the superseded standalone secondary-control buttons and of the + duplicated Wi-Fi settings entry from the connection-details panel. +- Deterministic browser tests for visibility, state reflection, action routing, + keyboard and pointer dismissal, focus behavior, responsive placement, and + absence of duplicate controls. +- Browser, host-regression, compressed-web-asset, LittleFS, and representative + mobile and desktop visual-verification evidence. + +### Acceptance criteria + +1. Every supported screen and viewport shows at most one persistent secondary + settings control: a gear button beside the visible Wi-Fi connection + indicator. The previous standalone connection-details, sound, + reduced-motion, and help buttons are absent. +2. Opening the gear button displays the applicable connection details, sound, + reduced motion, help, and Wi-Fi settings actions with clear Russian labels. + No primary gameplay or lobby action is moved into the menu. +3. Connection details show the same live information as before, and their + former standalone round button is not rendered. +4. Sound and reduced-motion actions retain their existing behavior and reflect + their current on/off state visually and through accessible state. Changing + either setting updates the menu item without creating another persistent + control. +5. Help opens the existing optional help overlay without changing its content, + navigation, close behavior, or the direct countdown-to-maze transition. +6. Wi-Fi settings remain reachable from the menu through the existing + configuration flow, and the action is not duplicated in the + connection-details panel or elsewhere in the normal connected application + flow. +7. The trigger and popover work with touch, pointer, and keyboard. The trigger + has an accessible Russian name and accurate expanded state; focus order is + predictable; Escape and outside activation dismiss the popover; and focus + returns safely without trapping the player. +8. Opening, using, and closing the popover does not issue gameplay commands, + change authoritative game state, enable observer input, or interfere with + keyboard, swipe, directional-pad, avatar-drag, key-drop, pause, or result + controls. +9. At all supported mobile, compact-landscape, tablet, and desktop sizes, the + closed layout is more compact than the current standalone-control layout. + The open popover stays within safe areas and the viewport, remains readable, + introduces no page or map overflow, and does not permanently cover + essential controls. +10. Automated tests cover screen applicability, absence of standalone and + duplicate controls, state reflection, every routed action, dismissal, + focus restoration, supported responsive layouts, and regression behavior. +11. All host and browser tests pass; compressed-web-asset validation passes; + the payload remains within the 1.5 MiB web budget and LittleFS partition; + and firmware remains within the factory partition. +12. Physical-device verification confirms that the menu and all five + secondary actions work in the served application, and the project owner + confirms on representative mobile and desktop browsers that the interface + is less distracting and materially more compact. + +### Evidence + +- On 2026-09-13, planning-time inspection of the active game at + `http://192.168.1.50/` identified the separate round connection-details + button beside `ESP на связи` and the standalone sound, reduced-motion, and + help controls in the active-player layout. +- On 2026-09-13, the project owner chose a gear icon beside the connection + indicator, required all referenced secondary controls and Wi-Fi settings to + move into its popover without duplicating the Wi-Fi action, and required the + compact layout on all screen sizes. +- On 2026-09-13, implementation began with one header gear button, a bounded + safe-area-aware settings popover, and the removal of the superseded details, + sound, reduced-motion, and help buttons. The popover retains live connection + information and Wi-Fi navigation on every screen, while player preferences + and optional help appear only during an active player match. +- On 2026-09-13, `node --test test/web/test_player_app.js + test/web/test_hedge_svg.js` passed all 31 tests. The compact-settings test + covers the sole trigger, removed standalone controls, state labels, + player-screen applicability, Escape and outside dismissal, and safe-area + constrained placement. +- On 2026-09-13, `bash scripts/run_host_tests.sh` passed all host and bundled + browser checks. `python3 scripts/verify_web_assets.py` passed with a + deterministic 31,420 / 1,572,864 byte compressed payload, and + `/home/sasa/.platformio/penv/bin/pio run --target buildfs` succeeded with a + 2,031,616 / 2,031,616 byte LittleFS image. +- Mobile, desktop, and physical-device visual verification of all settings + actions were pending until the project-owner check below. +- On 2026-09-13, the project owner checked the UI in every available mode and + confirmed that the settings menu is present and works as expected. This + supplies the required visual and physical-device acceptance evidence. + +## Milestone 029 — Low-overhead live connection quality + +**Status:** `NOT_STARTED` + +### Objective + +Show a compact, continuously understandable indication of the browser's live +round-trip response to the ESP on every application screen, with an honest +breakdown between measured ESP handling time and the remaining communication +delay, without reducing gameplay responsiveness or consuming unbounded device +resources. + +### Scope + +- Do not start implementation until every preceding milestone is `COMPLETE`. + Milestone 028 is `COMPLETE` when this plan is created; this milestone remains + `NOT_STARTED` because implementation was not requested. +- Reuse the existing versioned WebSocket heartbeat request and acknowledgement + path. Correlate each probe with its unique request ID and use the browser's + monotonic clock to measure complete application round-trip time. +- Measure heartbeat handling duration only with the ESP's monotonic clock, + from bounded request handling to acknowledgement preparation, and return + that duration in the matching acknowledgement. Never subtract an ESP + timestamp from a browser timestamp or require clock synchronization. +- Derive the communication remainder as the non-negative difference between + browser-measured round-trip time and ESP-measured handling time. Label it + `Связь`, not pure `Wi-Fi`, because it can also include TCP/WebSocket + transport, task scheduling, and outbound queueing. Label the measured device + interval `ESP` and the full round trip `Отклик`. +- Keep one compact persistent quality indicator beside the existing connection + status and settings trigger. Show a connection-quality symbol and rounded + total milliseconds without expanding the header or displacing primary + content. +- Make the `Связь` and `ESP` values available in the existing connection + details reached through the settings menu and in the indicator's accessible + Russian description. Do not expose developer diagnostics or misleading + claims about which person or device is at fault. +- Show the indicator on welcome, lobby, active-player, observer, countdown, + pause, and result screens. Before the first valid sample, while a sample is + stale, and while disconnected, show explicit compact unknown or offline + states instead of retaining an old number as if it were current. +- Define documented quality thresholds from representative physical-device + measurements. Use stable good, delayed, and slow states with smoothing and + hysteresis so normal jitter does not make the indicator flicker. State must + not rely on color alone. +- Keep probing strictly bounded and lower priority than gameplay: at most one + heartbeat may be outstanding per browser, normal cadence may not exceed one + probe every five seconds, no retry burst is allowed, and a probe or its + response must be skipped or dropped rather than delay a move, projection, + pause, resume, or result message. +- Suspend probes while the WebSocket is closed and while the document is + hidden. Clear pending measurements on disconnect, session reset, and socket + replacement, then take one fresh sample after a successful connection before + resuming the bounded cadence. +- Keep a small fixed-size sample window in the browser only. Do not persist or + transmit latency history, player names, session tokens, network names, or + hidden game state as part of this feature. +- Preserve observer read-only behavior, server-authoritative gameplay, + existing connection-loss messaging, offline operation, Russian + player-facing copy, responsive layouts, and firmware and LittleFS budgets. + +### Deliverables + +- Updated measurement, protocol, quality-state, copy, and responsive-placement + contracts in `ARCHITECTURE.md`, `docs/UI_STYLE.md`, `docs/PLAYER_UI.md`, and + the Milestone 016 latency procedure where applicable. +- A bounded heartbeat round-trip sampler and ESP handling-duration field with + documented units, range validation, correlation, timeout, and stale-sample + behavior. +- A compact always-applicable connection-quality indicator showing total + milliseconds, plus `Связь` and `ESP` details integrated with the existing + settings and connection-details interface. +- Deterministic protocol, gateway, browser, accessibility, responsive-layout, + scheduling, smoothing, timeout, reconnect, and backpressure tests. +- Before-and-after maximum-session latency, queue, traffic, heap, web-asset, + firmware, LittleFS, and physical-device visual-verification evidence. + +### Acceptance criteria + +1. On every supported application screen, the header contains one compact + quality symbol and a rounded current total such as `85 мс` beside the + existing connection status and gear trigger, without moving or obscuring + primary game, lobby, observer, pause, or result controls. +2. Each displayed total is calculated from the browser monotonic send and + matching-heartbeat-acknowledgement times. Responses with a missing, + duplicate, expired, malformed, or mismatched request ID cannot update the + indicator. +3. The matching heartbeat acknowledgement carries a bounded ESP handling + duration measured solely with the ESP monotonic clock. The browser validates + the field and derives `Связь = max(0, Отклик - ESP)` without comparing clocks + from different devices. +4. Connection details show the same sample as three Russian-labelled values: + `Отклик`, `Связь`, and `ESP`. Supporting text explains that `Связь` is a + transport remainder and cannot isolate radio-only Wi-Fi delay or assign + blame with certainty. +5. Before the first sample, after the documented stale timeout, and while + disconnected, the indicator uses distinct unknown, stale, or offline text + and shape states. It never presents an old millisecond value as current. +6. Good, delayed, and slow thresholds, smoothing window, hysteresis, rounding, + sample timeout, and stale timeout are fixed in canonical documentation and + covered at every boundary by deterministic tests. Meaning remains clear in + grayscale and without relying on animation or color alone. +7. Each browser has at most one heartbeat in flight, sends no more than one + normal probe per five seconds, keeps only the documented fixed number of + recent samples, and produces no immediate retry loop after timeout, + malformed input, disconnect, background suspension, or reconnect. +8. Heartbeat work is constant and bounded on the ESP, causes no broadcast or + authoritative-state mutation, and cannot occupy queue capacity needed by + gameplay or lifecycle messages. Under backpressure its acknowledgement is + discarded before any move, view, pause, resume, or result message. +9. Pointer feedback, keyboard, swipe, directional-pad, avatar-drag, key-drop, + rendering, and observer navigation remain responsive while sampling. The + Milestone 017 controlled two-player and two-player/four-observer latency + budgets continue to pass, with no increased dropped gameplay messages or + queue high-water regression attributable to probes. +10. Visibility changes, socket replacement, disconnect, reconnection, session + reset, screen changes, and result transitions leave no orphan timer, + duplicate probe, stale accessible label, or retained pending correlation. +11. The indicator and its details remain readable and within the viewport on + supported narrow portrait, compact-landscape, tablet, and desktop layouts, + including safe-area insets and all player and observer states. +12. Automated protocol, gateway, browser, host, and web-asset tests pass. The + compressed payload remains within the 1.5 MiB web budget and LittleFS + partition, and firmware remains within the factory partition. +13. Physical-device testing with two players and four observers confirms the + displayed total and split track controlled fast and delayed conditions, + reconnect states recover correctly, and continuous sampling causes no + perceptible input delay, animation stutter, disconnect, or gameplay + regression. +14. The project owner confirms on representative mobile and desktop browsers + that the indicator is compact, understandable in every application state, + and useful for recognizing a delayed ESP response without distracting from + play, or explicitly approves a documented evidence deviation. + +### Evidence + +- On 2026-09-13, planning inspection confirmed that the protocol already + supports a correlated `heartbeat` acknowledgement and that the browser uses + monotonic timing with a bounded 256-sample diagnostic tracker for accepted + movement acknowledgements and painted projections. +- On 2026-09-13, the project owner requested a compact icon with a millisecond + value in every application state and asked to separate communication delay + from ESP processing when this can be done without complicating or slowing + gameplay. +- Planning analysis established that a single round trip cannot measure pure + radio-only Wi-Fi latency. This milestone therefore specifies a directly + measured ESP handling interval and an honestly labelled communication + remainder, while keeping the displayed headline value equal to the complete + browser-to-ESP application round trip. +- Implementation and verification have not started, as requested by the + project owner. diff --git a/data/index.html.gz b/data/index.html.gz index 4c8682ebef8179fc101aac8c7022aae0a9fdf75b..0093968f9ccb47132351ca51c5f8d866d3d5fb9d 100644 GIT binary patch literal 3337 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 I%5W?I09zw`xc~qF diff --git a/docs/PLAYER_UI.md b/docs/PLAYER_UI.md index cdf0fef..47426ae 100644 --- a/docs/PLAYER_UI.md +++ b/docs/PLAYER_UI.md @@ -56,6 +56,16 @@ a maze boundary, and is recalculated on resize and rotation. ## Feedback and message rail +## Compact settings + +The persistent gear button beside the connection indicator opens one compact +popover. It shows live connection details and Wi-Fi settings on every screen. +During an active player match it additionally provides sound, reduced-motion, +and optional-help controls with visible Russian on/off labels. Opening or +closing this presentation-only menu never sends a command or changes server +state. Escape and outside activation dismiss it; player actions also close it +before opening their existing destination. + The browser stores the direction for each outstanding move request. A matching server `blocked` rejection may include only that player's approved breakthrough stage, which bends the already rendered bush wall for at most 340 ms without diff --git a/docs/UI_STYLE.md b/docs/UI_STYLE.md index b495e86..e2010e9 100644 --- a/docs/UI_STYLE.md +++ b/docs/UI_STYLE.md @@ -100,8 +100,12 @@ are not MVP requirements. - The player identity is represented by a distinct character shape, not `P1` or `P2`. Object, blocked-move, pause, and result feedback must pair a shape or icon with its color. -- Connection details and the Wi-Fi settings link belong in a secondary panel, - not in the persistent status row. +- A single gear button beside the persistent Wi-Fi connection indicator opens + the secondary settings popover. It contains connection details and Wi-Fi + settings on every screen, plus the player-only sound, reduced-motion, and + help actions during an active player match. The popover is constrained to + safe areas, closes on outside activation or Escape, and never contains + primary game, lobby, observer, or result actions. - Countdown, pause, and result states appear as modal overlays above the map. - Textual system notices use one live message rail above the top bar and player status area. It shows one message at a time: connection loss has highest diff --git a/test/web/test_player_app.js b/test/web/test_player_app.js index 1ce9f4c..1eb52d0 100644 --- a/test/web/test_player_app.js +++ b/test/web/test_player_app.js @@ -140,7 +140,7 @@ test("countdown completion leaves help closed until the player requests it", () assert.match(activeView, /setDirectionalPadEnabled\(snapshot\.phase === "active"/); assert.match(activeView, /else hideModal\(\);/); assert.doesNotMatch(activeView, /showOnboarding\(0\)/); - assert.match(script, /byId\("onboarding-replay"\)\.addEventListener\("click", \(\) => showOnboarding\(0\)\)/); + assert.match(script, /byId\("settings-help"\)\.addEventListener\("click", \(\) => \{ closeSettings\(false\); showOnboarding\(0\); \}\)/); assert.match(script, /byId\("onboarding-skip"\)\.addEventListener\("click", hideOnboarding\)/); assert.match(script, /if \(next == null\) hideOnboarding\(\)/); }); @@ -162,7 +162,7 @@ test("avatar control resets safely and retains accessible alternatives", () => { assert.match(script, /setReducedMotion\(!state\.reducedMotion\)/); assert.match(html, /id="avatar-control"/); assert.match(html, /aria-label="Тянуть героя для движения"/); - assert.match(script, /onboarding-replay/); + assert.match(script, /settings-help/); assert.match(html, /id="onboarding-dots"[\s\S]*?<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>/); }); @@ -359,7 +359,7 @@ test("player assets are local, Russian, accessible, and responsive", () => { assert.match(css, /\.dpad \{[^}]*grid-template-columns: repeat\(3, 58px\)/); assert.match(html, /id="avatar-control"/); assert.match(html, /id="onboarding"/); - assert.match(html, /id="mute-toggle"/); + assert.match(html, /id="settings-mute"/); assert.match(css, /\.avatar-control \{[^}]*width: 96px; height: 96px/); assert.match(css, /\.avatar-handle \{[^}]*transition: transform/); assert.match(css, /\.avatar-handle \{[^}]*background: transparent/); @@ -370,6 +370,28 @@ test("player assets are local, Russian, accessible, and responsive", () => { assert.match(icons, /id="maze"/); }); +test("one compact settings popover replaces standalone secondary controls", () => { + const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8"); + const css = fs.readFileSync(path.join(project, "web/player.css"), "utf8"); + const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8"); + + assert.match(html, /id="settings-toggle"[^>]*aria-controls="settings-menu"[^>]*aria-expanded="false"[^>]*aria-label="Открыть настройки"/); + for (const identifier of ["settings-menu", "details-text", "settings-mute", "settings-motion", "settings-help", "settings-wifi"]) { + assert.match(html, new RegExp(`id="${identifier}"`)); + } + assert.doesNotMatch(html, /id="details-toggle"|id="details-panel"|id="mute-toggle"|id="motion-toggle"|id="onboarding-replay"/); + assert.doesNotMatch(html, /access-controls|access-button/); + assert.match(css, /\.settings-menu \{[^}]*max-height: calc\(100dvh/); + assert.match(css, /\.settings-item\[aria-pressed="true"\]/); + assert.match(script, /function openSettings\(\)/); + assert.match(script, /function closeSettings\(restoreFocus\)/); + assert.match(script, /event\.key === "Escape" && !byId\("settings-menu"\)\.hidden/); + assert.match(script, /!menu\.hidden && !menu\.contains\(event\.target\) && !trigger\.contains\(event\.target\)/); + assert.match(script, /\["settings-mute", "settings-motion", "settings-help"\]\.forEach\(id => \{ byId\(id\)\.hidden = name !== "match"; \}\)/); + assert.match(script, /button\.textContent = muted \? "Звук: выкл" : "Звук: вкл"/); + assert.match(script, /button\.textContent = reduced \? "Движение: выкл" : "Движение: вкл"/); +}); + test("reference-derived SVG gameplay symbols preserve local yellow and blue ownership", () => { const icons = fs.readFileSync(path.join(project, "web/icons.svg"), "utf8"); const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8"); diff --git a/web/icons.svg b/web/icons.svg index 25d94b1..4dda831 100644 --- a/web/icons.svg +++ b/web/icons.svg @@ -9,6 +9,7 @@ + diff --git a/web/index.html b/web/index.html index d1148cb..5067511 100644 --- a/web/index.html +++ b/web/index.html @@ -13,9 +13,16 @@
MAZE32
Ловим сигнал ESP…
- +
- +
@@ -59,11 +66,6 @@
-
- - - -