Skip to content

Add Back/Cancel rows to the four menus that had no way out - #145

Open
TheAngryRaven wants to merge 1 commit into
BETAfrom
claude/cancel-buttons-forms-pcj1h8
Open

Add Back/Cancel rows to the four menus that had no way out#145
TheAngryRaven wants to merge 1 commit into
BETAfrom
claude/cancel-buttons-forms-pcj1h8

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Summary

Four menu pages offered only forward choices, with no Back or Cancel row:

Page Rows before Escape before
PAGE_TRANSFER_MENU Bluetooth / USB none — both modes exit by rebooting
PAGE_REPLAY_FILE_SELECT one per session none — had to open a session and walk its exit page
PAGE_COURSE_TRACK Here / New Track none — both go forward
PAGE_COURSE_TYPE Circuit / Sprint none — first Cancel is two screens deeper

None of these is the main menu, and the idle-shutdown timer only runs on the main menu (and the fault page), so none of them timed out either. Opening one by mistake left the Select + side-button 5 s reboot combo — which is not labelled on any screen — as the only way out.

What changed:

  • Transfer menu gains a Back row. It also joins the reverseDirection group in displayLoop(): it renders top-to-bottom like the other static menus and always belonged there, but with two items the scroll direction was unobservable (either button wrapped to the other row). The third row makes it visible.
  • Replay session browser gains a Back row after the last session, scrolling into view like any other row. replayItemCount() in replay.h is the single source of that layout for the renderer, the menu limit and the select handler, so the row can't be drawn in one place and unreachable in another. The three near-identical render blocks collapse into one replayDrawEntry() helper.
  • Course creator's two entry screens each gain a Cancel, mapped to the Row::kCancel the model already had (select() returns kExit for it, so no new plumbing). The type picker's matters most: entering the creator with no known track nearby skips the prompt and lands there, making it the first screen those users see.
  • Manual camera-serial entry: CANCEL now returns to the camera page, where OK already landed, instead of dropping to the main menu — backing out of a mistyped character no longer throws you two levels out.

Layout note: the course-type page drops the blank line under its title so three size-2 rows plus the hint line fit the panel, and the hint is blank on the Cancel row rather than describing a course type the cursor isn't on.

Type of change

  • Bug fix (no user-visible behavior change beyond the fix)
  • New feature / behavior
  • Refactor (no behavior change)
  • Tests only
  • CI / tooling / docs
  • Breaking change (track files, log format, BLE protocol, or a removed mode)

How it was verified

  • Host unit tests pass — 435/435, including two new course_creator cases covering the entry-screen Cancel rows and their out-of-range clamp
  • clang-tidy clean on course_creator.cpp. This also clears a pre-existing bugprone-branch-clone finding by merging the two identical rowCount branches (the file isn't in CI's tidy list, so it was never surfaced)
  • Sim ctest 6/6 (boot soak, determinism, goldens, both lap oracles, two-session carryover). The sim compiles the real .ino sources, so the display changes are compile-checked
  • Compiles for the XIAO nRF52840 Sense — left to CI (no arduino-cli in this environment)
  • Tested on real hardware

Golden fixtures regenerated: transfer_menu and course_type_select are the only two hashes that moved, which is exactly the two pages re-rendered. Both frames were eyeballed as PNGs — three rows plus the hint line fit the panel with nothing clipped.

Not covered by goldens: the track-prompt page and the replay browser. The golden walk enters the creator with no track in range (so it never renders the prompt), and the sim's VFS has no .dovex assets (so Review lands on the existing warning_no_dovex fixture). Adding coverage would mean embedding a DOVEX asset and a parked-on-track creator walk — worth doing, but a separate change.

Checklist

  • CHANGELOG.md updated under [Unreleased]
  • CLAUDE.md updated — page list, the USB subsystem's UI flow, and the course-creator screen description; adds an explicit "every menu page carries a Back/Cancel row" rule with the three deliberate exceptions (PAGE_INTERNAL_FAULT, PAGE_SD_FORMAT, the speed-gated race rotation)
  • New testable logic has a matching test in tests/
  • Branch is focused — one concern (missing escapes), with the renderer dedup falling out of the browser change

Generated by Claude Code

Four menu pages offered only forward choices: the transfer menu
(Bluetooth / USB), the replay session browser, and the course creator's
track prompt and type picker. None of them is the main menu, and the
idle-shutdown timer only runs there (and on the fault page), so none of
them timed out either — opening one by mistake left the unlabelled
Select + side-button 5 s reboot combo as the only escape.

- Transfer menu gains a Back row. It also joins the reverseDirection
  group in displayLoop(): it renders top-to-bottom like the other static
  menus and always belonged there, but with two items the scroll
  direction was unobservable (either button wrapped to the other row).
  The third row makes it visible.
- The replay browser gains a Back row after the last session, scrolling
  into view like any other row. replayItemCount() in replay.h is the
  single source of that layout for the renderer, the menu limit and the
  select handler, so the row cannot be drawn in one place and unreachable
  in another. The three near-identical render blocks collapse into one
  replayDrawEntry() helper.
- The course creator's two entry screens each gain a Cancel, mapped to
  the Row::kCancel the model already had (select() returns kExit for it,
  so no new plumbing). The type picker's matters most: entering with no
  known track nearby skips the prompt and lands there, making it the
  first screen those users see.

Also: cancelling manual camera-serial entry now returns to the camera
page, where OK already landed, instead of dropping to the main menu.

The course-type page drops the blank line under its title so three
size-2 rows plus the hint line fit the panel, and the hint is blank on
the Cancel row rather than describing a type the cursor is not on.

Tests: two new course_creator cases cover the entry-screen Cancel rows
and their out-of-range clamp. Sim goldens regenerated — transfer_menu
and course_type_select are the only two fixtures whose hash moved, and
both frames were eyeballed. Host suite 435/435, sim ctest 6/6,
clang-tidy clean on course_creator.cpp (which also clears a pre-existing
bugprone-branch-clone finding by merging the two identical rowCount
branches).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY4rxBEPYL9Zf3n7PCHovM
@github-actions

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 1498/1521 (98.5%)
Functions 🟢 160/160 (100.0%)
Branches 🟢 1110/1233 (90.0%)

📄 File coverage

File Lines Functions Branches
BirdsEye/ble_stream.cpp 🟢 34/34 (100.0%) 🟢 8/8 (100.0%) 🟡 17/20 (85.0%)
BirdsEye/camera_fsm.cpp 🟢 238/246 (96.7%) 🟢 20/20 (100.0%) 🟡 142/160 (88.8%)
BirdsEye/course_creator.cpp 🟢 213/221 (96.4%) 🟢 21/21 (100.0%) 🟡 119/136 (87.5%)
BirdsEye/course_prune.cpp 🟢 37/37 (100.0%) 🟢 5/5 (100.0%) 🟢 47/50 (94.0%)
BirdsEye/crc32.cpp 🟢 30/30 (100.0%) 🟢 4/4 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/crossing_pattern.cpp 🟢 15/15 (100.0%) 🟢 1/1 (100.0%) 🟢 12/12 (100.0%)
BirdsEye/dovex_header.cpp 🟢 106/107 (99.1%) 🟢 7/7 (100.0%) 🔴 62/88 (70.5%)
BirdsEye/filename_validator.cpp 🟢 14/14 (100.0%) 🟢 1/1 (100.0%) 🟢 30/30 (100.0%)
BirdsEye/gps_stats.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 8/8 (100.0%)
BirdsEye/gps_status_page.cpp 🟢 29/29 (100.0%) 🟢 4/4 (100.0%) 🟢 28/28 (100.0%)
BirdsEye/gps_time.cpp 🟢 45/45 (100.0%) 🟢 6/6 (100.0%) 🟢 30/32 (93.8%)
BirdsEye/gps_validation.cpp 🟢 24/24 (100.0%) 🟢 2/2 (100.0%) 🟢 66/66 (100.0%)
BirdsEye/haversine.cpp 🟢 8/8 (100.0%) 🟢 1/1 (100.0%) ⚫ 0/0 (0.0%)
BirdsEye/idle_policy.cpp 🟢 17/17 (100.0%) 🟢 2/2 (100.0%) 🟢 14/14 (100.0%)
BirdsEye/insta360_protocol.cpp 🟢 140/140 (100.0%) 🟢 16/16 (100.0%) 🟡 86/98 (87.8%)
BirdsEye/lap_format.cpp 🟢 18/18 (100.0%) 🟢 1/1 (100.0%) 🟢 9/9 (100.0%)
BirdsEye/led_animations.cpp 🟢 76/76 (100.0%) 🟢 5/5 (100.0%) 🟢 43/46 (93.5%)
BirdsEye/led_frame.cpp 🟢 21/21 (100.0%) 🟢 7/7 (100.0%) 🟢 6/6 (100.0%)
BirdsEye/led_modes.cpp 🟢 64/65 (98.5%) 🟢 5/5 (100.0%) 🟢 48/50 (96.0%)
BirdsEye/sat_bars.cpp 🟢 33/33 (100.0%) 🟢 2/2 (100.0%) 🟢 51/54 (94.4%)
BirdsEye/sd_access_policy.cpp 🟢 9/9 (100.0%) 🟢 3/3 (100.0%) 🟢 18/18 (100.0%)
BirdsEye/sd_format_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 25/26 (96.2%)
BirdsEye/sector_purple.cpp 🟢 51/51 (100.0%) 🟢 2/2 (100.0%) 🟡 42/50 (84.0%)
BirdsEye/sensoregg_protocol.cpp 🟢 44/45 (97.8%) 🟢 7/7 (100.0%) 🟢 33/34 (97.1%)
BirdsEye/sprint_select.cpp 🟢 25/25 (100.0%) 🟢 4/4 (100.0%) 🟢 46/48 (95.8%)
BirdsEye/tach_filter.cpp 🟢 27/27 (100.0%) 🟢 6/6 (100.0%) 🟢 17/18 (94.4%)
BirdsEye/track_json.cpp 🟢 116/120 (96.7%) 🟢 12/12 (100.0%) 🟡 67/88 (76.1%)
BirdsEye/wake_cause.cpp 🟢 14/14 (100.0%) 🟢 2/2 (100.0%) 🟢 20/20 (100.0%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants