You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The recent 27-commit series (Spec 01 → 06) landed a large chunk of the BLE / menu / config / test-infrastructure work identified in the April 2026 codebase audit. All of it builds green on ./idf.sh build and passes 42 host-side Catch2 unit tests via ctest --test-dir host_test/build.
What remains is (a) the parts of the harmonization refactor that are behavior-preserving but can only be verified with hardware smoke, (b) a hardware-validation pass against every spec that landed, and (c) deferred BLE hardening that the initial fix intentionally left out of scope.
Before landing any new refactor, flash the current main and walk a full regression sweep. Because partitions.csv changed, the first on-device flash must erase:
Coredump retrieval flow works after a forced panic (./idf.sh coredump-info)
build/config/sdkconfig.h contains CONFIG_BT_ENABLED=1 and CONFIG_BT_NIMBLE_NVS_PERSIST=1
No nvs_key partition referenced by the running firmware
Buttons + menu UX (Spec 03):
Short-tap Button A within 3 s of boot — serial shows btn A short + menu open; display shows menu
Short-tap A again → btn A short + menu close; menu hides, mode unchanged
Button B short × 6 → selection wraps past About back to Dashboard
Button B long with menu open → menu confirm:N + action fires
Button B long in Terminal with menu closed → returns to Dashboard
Long-press A (menu closed) → enters BLE pairing
In Terminal, short B cycles font sizes (0→1→2→0)
With paired BLE keyboard in Terminal/Dashboard, F1 → menu open (F1)
F1 does NOT leak through to the remote shell (verify with cat -v)
Esc closes menu (menu close (esc))
Arrow keys navigate menu; Enter confirms (menu confirm (kbd):N)
BLE HID keypress path (Spec 02):
Pair a fresh BLE keyboard (Logitech K380, Keychron K3, or generic HID)
Keystrokes reach the terminal on first session
Power-cycle the board → keyboard reconnects without re-pairing (bond persistence)
Serial log shows Report slot[N]: val_handle=X for each Report chr the keyboard exposes
Wrote Protocol Mode = Report (0x01) and Wrote HID Control Point = Exit Suspend (0x01) appear post-discovery
Subscribed to Report slot val=X cccd=Y id=Z type=T fires per Input slot before any keystroke is seen
Second keyboard: pair, disconnect, reconnect old keyboard → reconnects from the existing bond
Test boot-protocol-only keyboard AND report-protocol-with-report-IDs keyboard (different code paths)
NKRO keyboard: expected behaviour is "most keys work, NKRO bitmap reports silently dropped" — verify no crash
Cross-cutting bug fixes (Spec 04):
SSH into a Linux box, run vim hello.c, cycle font with Button B → vim redraws cleanly (DSR responses flowing after setFont rebuild)
Dashboard with an output that exceeds 1024 bytes (e.g. a machine with many filesystems running df -h) — after 5 s the log shows sentinel not found after overflow, advancing and the cycle continues
Put \"host\": \"../../secrets\" in an SD-card server JSON — boot log shows Rejected invalid SSH host and the firmware refuses to connect
Serial log at INFO level is quiet during typing (no per-notify / per-frame spam)
Input queue + pushOrDrop (Spec 05 partial):
Spam keystrokes for 10+ seconds at maximum speed → no dropped N events log under normal conditions
Under synthetic backpressure (e.g. large paste into SSH), if events are dropped the rate-limited 1 Hz dropped N events log appears
Stability soak:
10-minute SSH + dashboard + menu cycling session with no crash or watchdog reset
2. Spec 05 — remaining harmonization (deferred from the 27-commit series)
Low-risk but behavior-preserving refactors that were deferred because they can only be validated with on-device regression.
Extract app::InputDispatcher — pull the ~200-line event-pop loop from main.cpp into a dedicated class that owns a menu-overlay handler (first dibs) and a {AppMode → ModeHandler} table. The dispatcher's menu handler should cover short-A toggle, long-A pairing, long-B confirm, F1 intercept, menu-open arrows / Enter / Escape. Per-mode handlers cover font cycle, terminal↔dashboard swap, SSH passthrough, NetworkSelect/Error retry. Main's dispatch collapses to a single dispatcher.pumpOnce(currentMode) call.
Trim components/app/CMakeLists.txt REQUIRES — once InputDispatcher lives in the app component, drop buttons, ble_hid, wifi_manager, sensors from REQUIRES (main.cpp already lists those).
std::unique_ptr migrations — ConfigManager in main.cpp (currently new + never deleted) and TerminalMode's buffer_/parser_/renderer_ members (currently raw new/delete).
Binary-size check — post-refactor, confirm ./idf.sh size .text section monotonically non-increases relative to the baseline at commit 1c79509 (~937 KB).
Each item above should land in its own commit so git bisect can narrow any regression in the 20-item checklist.
3. Deferred BLE hardening
Intentionally out of scope for the initial BLE fix — track separately so we don't lose the items.
Passkey entry UI — passkey-required keyboards currently get ble_sm_inject_io with numcmp_accept=0 (default decline) and won't pair. Needs an on-screen passkey input screen that routes into ble_sm_inject_io.
Consumer-control reports — volume/media/brightness keys currently drop; map to terminal-recognisable sequences or explicit menu actions.
Mouse/trackpad support — intentionally out of scope; flag the current behaviour (any 8-byte report treated as keyboard input) as a known limitation or add an early-reject for mouse descriptors.
4. Config / robustness follow-ups
NVS encryption decision — currently dropped from partitions.csv with a "deferred" note. Either implement eFuse provisioning and enable CONFIG_NVS_ENCRYPTION, or update the top-level README to remove the last credential-storage ambiguity (the current plaintext-NVS note is accurate but not ideal).
Ed25519 key generation — ssh_client.cpp:151generateKeypair is a // TODO returning false. Users must copy a private key to /littlefs/ssh_ed25519 out-of-band. Either implement on-device keygen or document the workaround prominently.
Settings editor screen — two // TODO: settings editor screen branches in main.cpp currently no-op on the menu's Settings item. Either implement (server host/port/username edit with BLE keyboard input) or remove the Settings menu item.
WiFi scan + on-screen add-network UI — NetworkSelect mode has only a "Press B to retry" affordance; README previously claimed on-screen password entry (now corrected). Implement the actual scan-and-select flow if we want the README's original promise.
Light-sleep revisit — disabled in commit b1f82a6 because ST7305's SPI teardown blanks the reflective display. Investigate whether the SPI bus can be kept powered during light-sleep so we can reclaim the ~1.3 mA dashboard-idle figure.
5. Test coverage gaps
Host-side Catch2 harness is in place with 42 tests (see host_test/README.md). These would benefit from coverage:
app::Dashboard::parseOutputs — real-distro free -h / df -h / uptime -p regression table so format drift surfaces as a test failure before hitting hardware.
ble_hid::processHidReport report-ID variants — add cases that feed 9-byte report-protocol payloads with report_id != 0 once a host-side mock for the slot-routing layer exists.
Queue thread-safety — 2-producer / 1-consumer 1 kHz test exercising the real xQueueSend/xQueueReceive path via the shim.
app::Settings NVS round-trip — with an NVS mock, bit-exact save/load across all fields and missing-key defaults.
CI workflow — .github/workflows/host-tests.yml already runs ctest on ubuntu-latest + macos-latest. Add an idf.py build matrix job using the Espressif docker image so header drift and Kconfig regressions fail CI instead of local flashing.
6. Architectural follow-ups from the original audits
Dashboard command schema — currently hard-coded parseOutputs switch. A pluggable schema (label + parser function per metric) would make it easier to add metrics without editing the renderer.
SSH stream buffer — 8 KB StreamBufferHandle_t is a file-static in main.cpp. Move into a dedicated class or at least a factory so the SSH producer/main-consumer coupling is explicit.
app component split — defer until the InputDispatcher extraction is in (item 2.1). Then split app_core (menu, terminal, settings, input dispatcher) from app_net (dashboard, SSH-servers switching) so components can be tested in isolation.
Document the 1bit-display dependency — CMake falls back to ../../1bit-display but the dependency has no idf_component.yml entry, submodule, or commit pin. Either pin a commit, add as submodule, or publish it as an ESP-IDF component.
References
The 27-commit series that landed this week covered Specs 01–06 from the 2026-04-18 audit. See git log --oneline 5fe1443..c8f05fb for the full list. Each commit message explains the specific fix and its relationship to the audit/review findings.
Context
The recent 27-commit series (Spec 01 → 06) landed a large chunk of the BLE / menu / config / test-infrastructure work identified in the April 2026 codebase audit. All of it builds green on
./idf.sh buildand passes 42 host-side Catch2 unit tests viactest --test-dir host_test/build.What remains is (a) the parts of the harmonization refactor that are behavior-preserving but can only be verified with hardware smoke, (b) a hardware-validation pass against every spec that landed, and (c) deferred BLE hardening that the initial fix intentionally left out of scope.
1. Hardware validation pass (blocks everything below)
Before landing any new refactor, flash the current
mainand walk a full regression sweep. Becausepartitions.csvchanged, the first on-device flash must erase:Boot + config hygiene (Spec 01):
./idf.sh coredump-info)build/config/sdkconfig.hcontainsCONFIG_BT_ENABLED=1andCONFIG_BT_NIMBLE_NVS_PERSIST=1nvs_keypartition referenced by the running firmwareButtons + menu UX (Spec 03):
btn A short+menu open; display shows menubtn A short+menu close; menu hides, mode unchangedmenu confirm:N+ action firesmenu open (F1)cat -v)menu close (esc))menu confirm (kbd):N)BLE HID keypress path (Spec 02):
Report slot[N]: val_handle=Xfor each Report chr the keyboard exposesWrote Protocol Mode = Report (0x01)andWrote HID Control Point = Exit Suspend (0x01)appear post-discoverySubscribed to Report slot val=X cccd=Y id=Z type=Tfires per Input slot before any keystroke is seenCross-cutting bug fixes (Spec 04):
vim hello.c, cycle font with Button B → vim redraws cleanly (DSR responses flowing aftersetFontrebuild)df -h) — after 5 s the log showssentinel not found after overflow, advancingand the cycle continues\"host\": \"../../secrets\"in an SD-card server JSON — boot log showsRejected invalid SSH hostand the firmware refuses to connectInput queue + pushOrDrop (Spec 05 partial):
dropped N eventslog under normal conditionsdropped N eventslog appearsStability soak:
2. Spec 05 — remaining harmonization (deferred from the 27-commit series)
Low-risk but behavior-preserving refactors that were deferred because they can only be validated with on-device regression.
app::InputDispatcher— pull the ~200-line event-pop loop frommain.cppinto a dedicated class that owns a menu-overlay handler (first dibs) and a{AppMode → ModeHandler}table. The dispatcher's menu handler should cover short-A toggle, long-A pairing, long-B confirm, F1 intercept, menu-open arrows / Enter / Escape. Per-mode handlers cover font cycle, terminal↔dashboard swap, SSH passthrough, NetworkSelect/Error retry. Main's dispatch collapses to a singledispatcher.pumpOnce(currentMode)call.components/app/CMakeLists.txtREQUIRES — once InputDispatcher lives in the app component, dropbuttons,ble_hid,wifi_manager,sensorsfromREQUIRES(main.cpp already lists those).std::unique_ptrmigrations —ConfigManagerin main.cpp (currentlynew+ never deleted) and TerminalMode'sbuffer_/parser_/renderer_members (currently raw new/delete)../idf.sh size.text section monotonically non-increases relative to the baseline at commit1c79509(~937 KB).Each item above should land in its own commit so
git bisectcan narrow any regression in the 20-item checklist.3. Deferred BLE hardening
Intentionally out of scope for the initial BLE fix — track separately so we don't lose the items.
ble_sm_inject_iowithnumcmp_accept=0(default decline) and won't pair. Needs an on-screen passkey input screen that routes intoble_sm_inject_io.4. Config / robustness follow-ups
partitions.csvwith a "deferred" note. Either implement eFuse provisioning and enableCONFIG_NVS_ENCRYPTION, or update the top-level README to remove the last credential-storage ambiguity (the current plaintext-NVS note is accurate but not ideal).ssh_client.cpp:151generateKeypairis a// TODOreturning false. Users must copy a private key to/littlefs/ssh_ed25519out-of-band. Either implement on-device keygen or document the workaround prominently.// TODO: settings editor screenbranches inmain.cppcurrently no-op on the menu's Settings item. Either implement (server host/port/username edit with BLE keyboard input) or remove the Settings menu item.NetworkSelectmode has only a "Press B to retry" affordance; README previously claimed on-screen password entry (now corrected). Implement the actual scan-and-select flow if we want the README's original promise.b1f82a6because ST7305's SPI teardown blanks the reflective display. Investigate whether the SPI bus can be kept powered during light-sleep so we can reclaim the ~1.3 mA dashboard-idle figure.5. Test coverage gaps
Host-side Catch2 harness is in place with 42 tests (see
host_test/README.md). These would benefit from coverage:app::Dashboard::parseOutputs— real-distrofree -h/df -h/uptime -pregression table so format drift surfaces as a test failure before hitting hardware.ble_hid::processHidReportreport-ID variants — add cases that feed 9-byte report-protocol payloads withreport_id != 0once a host-side mock for the slot-routing layer exists.xQueueSend/xQueueReceivepath via the shim.ssh::SshClient::verifyHostKeyTOFU — needs a LittleFS mock; covers first-connect-stores, second-connect-matches, fingerprint-mismatch errors.app::SettingsNVS round-trip — with an NVS mock, bit-exact save/load across all fields and missing-key defaults..github/workflows/host-tests.ymlalready runscteston ubuntu-latest + macos-latest. Add anidf.py buildmatrix job using the Espressif docker image so header drift and Kconfig regressions fail CI instead of local flashing.6. Architectural follow-ups from the original audits
parseOutputsswitch. A pluggable schema (label + parser function per metric) would make it easier to add metrics without editing the renderer.StreamBufferHandle_tis a file-static inmain.cpp. Move into a dedicated class or at least a factory so the SSH producer/main-consumer coupling is explicit.appcomponent split — defer until the InputDispatcher extraction is in (item 2.1). Then splitapp_core(menu, terminal, settings, input dispatcher) fromapp_net(dashboard, SSH-servers switching) so components can be tested in isolation.1bit-displaydependency — CMake falls back to../../1bit-displaybut the dependency has noidf_component.ymlentry, submodule, or commit pin. Either pin a commit, add as submodule, or publish it as an ESP-IDF component.References
The 27-commit series that landed this week covered Specs 01–06 from the 2026-04-18 audit. See
git log --oneline 5fe1443..c8f05fbfor the full list. Each commit message explains the specific fix and its relationship to the audit/review findings.