Skip to content

feat: Context-Aware Input Policy Pipeline with Decoupled UI Command Dispatch#314

Open
mverch67 wants to merge 80 commits into
masterfrom
input-policy
Open

feat: Context-Aware Input Policy Pipeline with Decoupled UI Command Dispatch#314
mverch67 wants to merge 80 commits into
masterfrom
input-policy

Conversation

@mverch67

@mverch67 mverch67 commented May 18, 2026

Copy link
Copy Markdown
Collaborator
  • input handling is now policy-driven and context-aware, so the same keys can adapt to focused UI state instead of hard-coded per-driver behavior

  • the new pipeline cleanly separates key source, key-to-action mapping, policy evaluation, and UI command dispatch, which reduces coupling and makes behavior easier to extend

  • it also adds support for dedicated command keys and structured event decisions (pass, remap, consume, emit command/sequence) for predictable, testable input behavior

E.g. a keyboard with cursor keys can now use the cursor keys to navigate the map (context-aware) or move inside text input areas. ESC key to leave this mode.

Note:
As a usage example the new classes are used within the KeyMatrixInputDriver (PICOmputer). See also here.

Summary by CodeRabbit

  • New Features
    • Added a documented, policy-driven input pipeline with focus-aware remapping and UI command dispatch.
    • Introduced a WebDAV server with WiFi/MDNS start-stop, running/status reporting, and transfer progress tracking.
    • Expanded I2C keyboard support with new TM9 and STC8H drivers and pipeline-based matrix event handling.
  • Bug Fixes
    • Improved LVGL touch responsiveness with a configurable read timer (default 20ms) and safer timer setup.
    • Enhanced widget overlay/input grouping behavior and added WebDAV UI status update support.
    • Improved LVGL styling consistency (selector casting) and keyboard device probing reliability.
  • Tests
    • Added automated tests covering input policy and UI command dispatch behavior.

mverch67 and others added 30 commits May 13, 2026 03:23
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds an input policy pipeline with new interfaces, policies, dispatcher, driver integration, docs, and tests; introduces a WebDAV server singleton; and expands TFT debug/view support, filesystem adapters, and LVGL style and timing adjustments.

Changes

Input Policy Pipeline

Layer / File(s) Summary
Design documentation
docs/input-policy-strategy-design.md
Adds the input policy strategy design document covering pipeline structure, data model, dispatcher behavior, precedence, special keys, integration flow, and acceptance criteria.
Core input types and decisions
include/input/policy/InputTypes.h, include/input/policy/PolicyDecision.h
Defines the core input enums, event/context/capability structs, and policy decision payload/result types used by the pipeline.
Policy interfaces and registry
include/input/policy/IInputPolicy.h, include/input/policy/IActionBindingResolver.h, include/input/policy/IInputContextProvider.h, include/input/policy/IInputSource.h, include/input/policy/IUICommandDispatcher.h, include/input/policy/InputPolicyFactory.h, include/input/policy/InputSourceRegistry.h
Declares the abstract policy, binding, context, source, dispatcher, factory, and source-registry interfaces used to assemble and run the pipeline.
Policy implementations
include/input/policy/CommandDispatchPolicy.h, include/input/policy/FocusTraversalPolicy.h, include/input/policy/PassthroughPolicy.h, include/input/policy/DefaultBindingResolver.h, include/input/policy/PolicyChain.h, source/input/policy/*.cpp
Implements command dispatch, focus traversal remapping, passthrough fallback, binding resolution, and ordered policy-chain evaluation.
Dispatcher, context state, and pipeline
include/input/policy/UICommandDispatcher.h, include/input/policy/InputContextState.h, include/input/policy/InputPipeline.h, include/input/policy/DefaultInputPolicyFactory.h, source/input/policy/UICommandDispatcher.cpp, source/input/policy/InputContextState.cpp, source/input/policy/InputPipeline.cpp, source/input/policy/DefaultInputPolicyFactory.cpp, tests/test_InputPolicy.cpp
Implements the singleton UI command dispatcher, the mutable input context singleton, the runtime pipeline, the default policy factory, and the registry-backed tests for command dispatch and remapping.
Keyboard and matrix driver wiring
include/input/I2CKeyboardInputDriver.h, source/input/I2CKeyboardInputDriver.cpp, source/input/I2CKeyboardScanner.cpp, source/input/KeyMatrixInputDriver.cpp
Wires the pipeline into I2C keyboard drivers and the matrix keyboard scanner/reader path, including TwoWire injection, per-device attachment logic, and pipeline-driven event remapping.

WebDAV Server

Layer / File(s) Summary
WebDAVServer public interface
include/comms/WebDAVServer.h
Declares the WebDAVServer API for WiFi and mDNS setup, server lifecycle control, status queries, and transfer progress reporting.
WebDAVServer implementation
source/comms/WebDAVServer.cpp
Implements fallback stubs, singleton creation, WiFi lifecycle, transfer tracking, server startup and shutdown, and the server thread loop.

TFT View, Debug Tooling, and Filesystem/Style Fixes

Layer / File(s) Summary
TFTView_320x240 UI extensions
include/graphics/view/TFT/TFTView_320x240.h
Adds the group guard helper, WebDAV status hook, input-group changes, overlay cleanup, new event callbacks, and expanded view state.
TFTView_Debug LVGL introspection
include/graphics/view/TFT/TFTView_Debug.h, source/graphics/TFT/TFTView_Debug.cpp, source/graphics/TFT/object_name_entries_320x240.inc
Adds LVGL object lookup, hierarchy checks, flag formatting, object logging, focus debugging, and the generated object-name table.
Style selector and touch timing fixes
source/graphics/TFT/Themes.cpp, source/graphics/map/MapTile.cpp, include/graphics/driver/LGFXDriver.h
Updates LVGL style selector usage in theme and map rendering code, and changes touch read timing to use a configurable period with a null check.
SdFat filesystem wrapper
include/util/SdFatFileWrapper.h
Adds adapters that bridge SdFat and SdFs file APIs to the Arduino filesystem interfaces.
LogRotate minor cleanup
source/util/LogRotate.cpp
Removes the unused start-timestamp variable in LogRotate::write by commenting out the declaration.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant KeyMatrixInputDriver
  participant InputPipeline
  participant PolicyChain
  participant UICommandDispatcher

  KeyMatrixInputDriver->>InputPipeline: process(InputEvent, capabilities)
  InputPipeline->>PolicyChain: evaluate(event, context, capabilities)
  PolicyChain-->>InputPipeline: PolicyDecision
  alt EmitCommand
    InputPipeline->>UICommandDispatcher: dispatch(command, payload)
  else Remap/Pass
    InputPipeline-->>KeyMatrixInputDriver: outEvents
  end
Loading
sequenceDiagram
  participant WebDAVServer
  participant WiFiServer
  participant ESPWebDAV

  WebDAVServer->>WiFiServer: begin() port 80
  WebDAVServer->>ESPWebDAV: begin(tcpServer, filesystem)
  WebDAVServer->>WebDAVServer: spawn serverThread task
  loop while running
    WebDAVServer->>ESPWebDAV: handleClient()
  end
Loading

Estimated code review effort: 4 (Complex) | ~75 minutes

Poem

A rabbit hopped through keys and chains,
Policies danced in tidy lanes.
WebDAV files took a lunar ride,
While debug eyes peered deep inside.
A burrow full of careful care —
🐇✨ with polished paths and code to spare.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the new context-aware input policy pipeline and separate UI command dispatch.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (10)
tests/test_InputPolicy.cpp (2)

69-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for uncovered branches.

Both tests only exercise PressKind::Press. Consider adding a case for PressKind::Release (to confirm CommandDispatchPolicy correctly returns an empty decision, per CommandDispatchPolicy::evaluate's early return) and a case with a null dispatcher passed to factory.build (to confirm CommandDispatchPolicy is correctly omitted per DefaultInputPolicyFactory's conditional).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_InputPolicy.cpp` around lines 69 - 94, The current InputPolicy
tests only cover the PressKind::Press path in DefaultInputPolicyFactory and
CommandDispatchPolicy. Add a Release case in test_InputPolicy.cpp to verify
CommandDispatchPolicy::evaluate returns no decision on PressKind::Release, and
add a factory.build scenario with a null dispatcher to verify
DefaultInputPolicyFactory omits CommandDispatchPolicy when no dispatcher is
provided.

81-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Magic key-code values obscure test intent.

0x11 (raw/resolved input) and 11 (expected output) aren't self-explanatory without cross-referencing FocusTraversalPolicy. A short comment or named constant (e.g., referencing LV_KEY_UP/LV_KEY_PREV if that's the intended semantic) would make the assertion's purpose clear without needing to consult another file.

♻️ Suggested clarification
     InputEvent event{};
     event.sourceId = "test";
-    event.rawKeyCode = 0x11;
-    event.resolvedKeyCode = 0x11;
+    event.rawKeyCode = 0x11;     // LV_KEY_UP
+    event.resolvedKeyCode = 0x11;
     event.pressKind = PressKind::Press;

     std::vector<InputEvent> output;
     const bool forwarded = pipeline.process(event, InputCapabilities{}, output);

     CHECK(forwarded);
     REQUIRE(output.size() == 1);
-    CHECK(output.front().resolvedKeyCode == 11);
+    CHECK(output.front().resolvedKeyCode == 11); // remapped to LV_KEY_PREV
     CHECK(dispatcher->dispatchCount == 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_InputPolicy.cpp` around lines 81 - 94, Clarify the key-code intent
in the InputPolicy test by replacing the magic values in the InputEvent setup
and expected assertion with named constants or an inline comment tied to
FocusTraversalPolicy semantics. Use the existing test case around
pipeline.process and output.front().resolvedKeyCode to make it obvious whether
the values represent navigation keys like LV_KEY_UP or LV_KEY_PREV, so the
assertion reads as intentional rather than arbitrary.
docs/input-policy-strategy-design.md (1)

114-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

StrategyDecision naming drifted from implementation.

The design doc names the decision struct StrategyDecision, but the actual implementation (include/input/policy/PolicyDecision.h) names it PolicyDecision. Worth aligning terminology to avoid confusion for readers cross-referencing doc and code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/input-policy-strategy-design.md` around lines 114 - 122, Align the
terminology in the design doc with the implementation by renaming the documented
`StrategyDecision` concept to `PolicyDecision`, matching
`include/input/policy/PolicyDecision.h`; update the section title and field
description references so readers can cross-reference the doc with the actual
`PolicyDecision` type and its members without confusion.
include/input/policy/InputTypes.h (1)

54-63: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

UICommand enum ordering is a hidden contract for the dispatcher's handler array.

UICommandDispatcher (see include/input/policy/UICommandDispatcher.h) sizes its handler storage as std::array<CommandHandler, static_cast<size_t>(UICommand::LeaveEditMode) + 1>, implicitly relying on LeaveEditMode remaining the last enumerator. Inserting a new command after LeaveEditMode, or reordering, silently breaks that sizing with no compile-time signal. Consider adding an explicit sentinel (e.g., Count) as the last enumerator and using it for the array size, so any future insertion is safe by construction.

♻️ Suggested pattern
 enum class UICommand {
     None,
     GoHome,
     OpenChats,
     QuickChat,
     OpenMap,
     ToggleGps,
     SendPing,
     LeaveEditMode,
+    Count,
 };

And update UICommandDispatcher to size handlers as static_cast<size_t>(UICommand::Count).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/input/policy/InputTypes.h` around lines 54 - 63, The UICommand enum
in InputTypes.h is being used as a hidden size contract by UICommandDispatcher’s
handler array. Add an explicit terminal sentinel such as Count as the last
enumerator in UICommand, keep LeaveEditMode before it, and update
UICommandDispatcher to size handlers from static_cast<size_t>(UICommand::Count)
instead of relying on LeaveEditMode being last. This makes future command
insertions/reordering safe and keeps the enum-to-array mapping stable.
include/graphics/view/TFT/TFTView_320x240.h (2)

21-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider marking the single-argument constructor explicit.

Prevents accidental implicit conversion from lv_group_t* to GroupGuard at call sites.

♻️ Proposed tweak
-    GroupGuard(lv_group_t *target_group) : saved_group(lv_group_get_default())
+    explicit GroupGuard(lv_group_t *target_group) : saved_group(lv_group_get_default())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/graphics/view/TFT/TFTView_320x240.h` at line 21, The single-argument
constructor in GroupGuard should be marked explicit to prevent unintended
implicit conversions from lv_group_t* at call sites. Update the GroupGuard
constructor that takes target_group so it cannot be used for implicit
construction, while keeping the existing saved_group initialization behavior
unchanged.

236-236: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Friend grant is unconditional even though TFTView_Debug's body is gated by LVGL_DEBUG_FOCUS.

TFTView_Debug (per TFTView_Debug.h) only has members when LVGL_DEBUG_FOCUS is defined; otherwise it's an empty class. Granting friendship here unconditionally couples this view class to a debug-only type even in release builds where the friendship is unused. Consider gating the friend declaration the same way to keep the coupling scoped to debug builds.

♻️ Proposed tweak
+#if defined(LVGL_DEBUG_FOCUS)
     friend class TFTView_Debug;
+#endif
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/graphics/view/TFT/TFTView_320x240.h` at line 236, The unconditional
friend declaration for TFTView_Debug in TFTView_320x240 should be scoped to the
same debug guard used by TFTView_Debug itself. Update the TFTView_320x240 class
declaration so the friend relationship is only declared when LVGL_DEBUG_FOCUS is
enabled, keeping the debug-only coupling out of release builds and matching the
conditional structure in TFTView_Debug.
include/input/policy/InputContextState.h (1)

30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Missing explicit includes for uint32_t/std::string.

setActivePanelId(uint32_t panelId) and setFocusedClassHint(const std::string &hint) use uint32_t and std::string directly, but the file only includes IInputContextProvider.h. These types are presumably pulled in transitively, but that's fragile — an unrelated change to the transitive include chain could break this header's compilation.

🔧 Suggested fix
 `#pragma` once

 `#include` "input/policy/IInputContextProvider.h"
+#include <cstdint>
+#include <string>

Also applies to: 32-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/input/policy/InputContextState.h` at line 30, Add the missing direct
type includes in InputContextState.h so it does not rely on transitive headers
for uint32_t and std::string. Update the header around InputContextState,
setActivePanelId, and setFocusedClassHint to include the proper standard
definitions explicitly, keeping IInputContextProvider.h as the interface
dependency but making the parameter types self-contained.
source/input/policy/InputPipeline.cpp (1)

46-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Exhaustive switch with unreachable trailing fallback.

The switch on DecisionType covers Pass, Remap, Consume, EmitCommand, and EmitSequence — if that's the complete enum, lines 72-73 are unreachable dead code. This is likely intentional defensive coding against future enum extensions without a compiler -Wswitch warning being noticed, so leaving as-is is reasonable; just flagging for awareness.

include/util/SdFatFileWrapper.h (2)

34-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing override specifiers on virtual overrides.

None of the overridden virtuals in SdFatFSImpl (and SdFat32FSImpl/SdFsExFatImpl below) use override. Adding it lets the compiler catch signature drift against fs::FileImpl/fs::FSImpl (e.g. if a base signature changes in a library update), which is otherwise silent since these methods would just become new, non-overriding overloads. since correctness hinges on the exact upstream fs::FileImpl/fs::FSImpl signatures matching, not shown here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/util/SdFatFileWrapper.h` around lines 34 - 107, Add override to every
virtual method in SdFatFSImpl and the related SdFat32FSImpl/SdFsExFatImpl
classes so the compiler verifies they still match fs::FileImpl/fs::FSImpl.
Update the method declarations like write, read, flush, seek, position, size,
close, name, isDirectory, openNextFile, seekDir, getNextFileName,
rewindDirectory, and operator bool to use override, keeping the signatures
aligned with the base interfaces.

109-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate logic between SdFat32FSImpl and SdFsExFatImpl.

The two classes are identical except for the referenced type (SdFat& vs SdFs&). Consider a shared template base to avoid maintaining two copies of open/exists/rename/remove/mkdir/rmdir.

♻️ Suggested refactor sketch
template <typename SdT>
class SdFsImplBase : public fs::FSImpl
{
  protected:
    SdT &sd;
  public:
    SdFsImplBase(SdT &sd) : sd(sd) {}
    virtual fs::FileImplPtr open(const char *path, const char *mode, const bool create)
    {
        return std::make_shared<SdFatFSImpl>(sd.open(path, _convert_access_mode_to_flag(mode, create)));
    }
    virtual bool exists(const char *path) { return sd.exists(path); }
    virtual bool rename(const char *pathFrom, const char *pathTo) { return sd.rename(pathFrom, pathTo); }
    virtual bool remove(const char *path) { return sd.remove(path); }
    virtual bool mkdir(const char *path) { return sd.mkdir(path); }
    virtual bool rmdir(const char *path) { return sd.rmdir(path); }
};
using SdFat32FSImpl = SdFsImplBase<SdFat>;
using SdFsExFatImpl = SdFsImplBase<SdFs>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/util/SdFatFileWrapper.h` around lines 109 - 157, `SdFat32FSImpl` and
`SdFsExFatImpl` duplicate the same `open`, `exists`, `rename`, `remove`,
`mkdir`, and `rmdir` logic, differing only by the wrapped storage type. Refactor
these classes in `SdFatFileWrapper.h` into a shared template base (for example,
a generic `SdFsImplBase` that stores the `sd` reference and implements the
common `fs::FSImpl` methods), then make the two existing implementations thin
aliases or specializations for `SdFat` and `SdFs`. Keep `open` using the
existing `_convert_access_mode_to_flag` and `SdFatFSImpl` behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/input-policy-strategy-design.md`:
- Around line 296-312: The raw key code table in the policy doc is out of sync
with DefaultBindingResolver’s actual mapping, so update the documented
KeyMap/DefaultBindingResolver relationship to match the implementation. Fix the
table entries and surrounding text to reflect the real raw-code-to-InputAction
cases used by DefaultBindingResolver.cpp, including the CommandQuickChat entry
and the correct ordering for CommandHome, CommandOpenChats, CommandOpenMap,
CommandToggleGps, and CommandSendPing. Ensure the description of the resolver
path remains consistent with UICommandDispatcher::instance() and the emitted
DecisionType::EmitCommand flow.

In `@include/comms/WebDAVServer.h`:
- Line 51: The WebDAV entrypoint in WebDAVServer::start(fs::FS*) currently
exposes the filesystem without any access-control gating. Add an explicit
security gate to this API by either wiring in an auth layer before serving the
filesystem or requiring a clearly named unsecured-sharing flag/mode before
start() can expose the SD card. Make the restriction visible in the WebDAVServer
interface and enforce it in the start implementation so the server cannot be
launched anonymously by default.

In `@include/graphics/driver/LGFXDriver.h`:
- Line 339: The comment on the touch timer period is stale and conflicts with
defaultTouchReadPeriodMs being 20ms. Update the inline note near
lv_timer_set_period in LGFXDriver.h to match the current 50Hz rate, or remove
the frequency claim if it is no longer needed, so the comment accurately
reflects the code.

In `@include/input/policy/UICommandDispatcher.h`:
- Line 41: The handler array sizing in UICommandDispatcher is still tied to the
last enum value, so update the UICommand enum to include a Count sentinel and
use that for the std::array size in UICommandDispatcher::handlers. Make sure any
indexing or iteration over UICommand values uses the new Count-based bound
rather than relying on LeaveEditMode as the tail.

In `@include/util/SdFatFileWrapper.h`:
- Around line 1-6: Add a header include guard to SdFatFileWrapper.h so the class
and inline definitions are only seen once per translation unit. Place a guard at
the top of the header before the existing includes, or use `#pragma` once, and
make sure it wraps the current contents without changing the SdFat-related
declarations.
- Around line 8-32: The helper _convert_access_mode_to_flag currently has an
operator-precedence bug in the 'w' and 'a' branches, where O_WRONLY/O_APPEND is
combined with create incorrectly and the write/append access bits are lost.
Update those return expressions in _convert_access_mode_to_flag so the
conditional applies only to O_CREAT while preserving O_WRONLY or O_APPEND, and
verify the same fix for the append path. Keep the behavior consistent for all
mode cases handled by this function.
- Around line 101-105: The directory traversal stubs in SdFatFileWrapper are
returning a bogus filename and not resetting iteration, which breaks consumers
of the Arduino FS-style API. Update the getNextFileName overloads to return the
conventional empty-string sentinel instead of a placeholder, and make
rewindDirectory actually reset the wrapped file/directory state so listing can
restart correctly. Use the existing SdFatFileWrapper methods and _file member to
keep the behavior aligned with directory-listing semantics.
- Around line 86-93: The name() implementation in SdFatFileWrapper uses a shared
static _name buffer, so successive calls across files or instances can overwrite
previously returned names. Change SdFatFileWrapper::name() to use a per-instance
buffer or otherwise return storage owned by the object instead of a shared
static, and update any related construction/destruction logic so each file
wrapper preserves its own name across calls. Keep the existing _file.getName()
flow, but remove the aliasing risk introduced by the static buffer.

In `@source/comms/WebDAVServer.cpp`:
- Around line 130-153: Reset the WebDAV transfer state so the UI can’t get stuck
in “Transfer in progress...”: in WebDAVServer::stop() clear transferInProgress
and lastTransfer100Time, and in WebDAVServer::onTransferProgress() /
WebDAVServer::isTransferInProgress() add activity tracking for non-100% progress
so stalled or cancelled transfers can time out instead of relying only on a 100%
callback.
- Around line 68-102: initWiFi currently returns success immediately after
WiFi.begin() in WebDAVServer::initWiFi, even though the station may still be
connecting or may fail later. Update the method so it returns actual connection
status by waiting with a bounded timeout for WL_CONNECTED, or otherwise adjust
the caller/UI flow to treat this as “connecting” instead of “ready.” Use the
existing WiFi setup and WiFiEventHandler flow to keep the logic localized.
- Around line 255-296: The WebDAV server shutdown path has two competing
task-termination owners: WebDAVServer::stop() force-deletes serverTaskHandle
after a delay while the task wrapper also self-deletes with
vTaskDelete(nullptr), which can race and double-delete the same task. Update the
teardown flow so only one side owns task destruction, and have stop() wait for
an explicit task-exited signal before cleaning up dav/ownedTcpServer and
clearing serverTaskHandle, using the existing running/serverReady state and
WebDAVServer::stop() as the place to coordinate the shutdown.
- Around line 10-13: WebDAVServer::instance() in the fallback build currently
returns nullptr, which breaks callers that expect a usable object; change it to
return a no-op singleton stub instead so the UI toggle path can safely call
initWiFi(). Also add the missing initMDNS() stub on WebDAVServer so the fallback
public API remains link-consistent when ESPWebDAV.h is unavailable under
HAS_SDCARD.
- Around line 198-252: The successful path in WebDAVServer::start() deadlocks
because notifyStatusChange() is called while mutex is still held, and that
method re-enters the same lock through isWiFiConnected(). Move the
notifyStatusChange() call out of the locked section in start() (after the
std::lock_guard scope ends), keeping the state setup and xTaskCreatePinnedToCore
handling inside the mutex-protected block.
- Around line 91-95: Store the wifi_event_id_t values returned by
WebDAVServer::initWiFi() when registering WiFi.onEvent handlers, instead of
discarding them. Then update WebDAVServer::deinitWiFi() to call
WiFi.removeEvent(id) for each stored ID so the existing callbacks are explicitly
unregistered; do not rely on WiFi.onEvent(nullptr, ...) for cleanup.

In `@source/input/I2CKeyboardInputDriver.cpp`:
- Around line 626-638: The pipeline output in I2CKeyboardInputDriver::readInput
is only updating data->key, so STC8H remapped events can keep a stale LVGL state
from earlier processing. In the outEvent handling block, assign data->state from
outEvent.pressKind (pressed vs released) before returning, and keep the existing
outKey==0 early return behavior unchanged. Use the readInput pipeline output
section and the outEvent.pressKind mapping as the place to fix this.
- Around line 216-225: The CardKB read path is ignoring the injected I2C bus and
still using the global Wire instance. Update CardKBInputDriver::readKeyboard to
perform the request/availability/read operations through the stored wire member
initialized in CardKBInputDriver::CardKBInputDriver, so devices registered on
Wire1 use the correct bus. Keep the fix localized to the CardKBInputDriver
methods and ensure all I2C access in this flow uses the injected bus
consistently.

In `@source/input/I2CKeyboardScanner.cpp`:
- Around line 137-139: Remove the stale multi-pass retry logic from
I2CKeyboardScanner::scan, since the rewritten scanner no longer declares or uses
pass. Update the conditional block that checks
I2CKeyboardInputDriver::getI2CKeyboardList() and kScanPasses so the single-pass
flow compiles cleanly, and remove any related delay(kInterPassDelayMs) retry
handling that depends on the missing pass variable.

In `@source/input/KeyMatrixInputDriver.cpp`:
- Around line 199-210: The KeyMatrixInputDriver pipeline output handling in the
KeyMatrix input path updates data->key but never applies outEvent.pressKind to
data->state, so remapped release events can be reported with the wrong LVGL
state. Update the post-pipeline branch in the input processing logic around the
existing outEvent logging to set data->state from outEvent.pressKind (pressed vs
released) before returning, while preserving the current no-output fallback
behavior.
- Around line 68-70: KeyMatrixInputDriver::init() is currently inheriting the
wrong read callback from I2CKeyboardInputDriver::init(), so the matrix path
never uses KeyMatrixInputDriver::keyboard_read. Update
KeyMatrixInputDriver::init() to register the matrix-specific read callback
instead of the I2C scanner callback, using the
KeyMatrixInputDriver::keyboard_read symbol so the matrix keyboard input is
dispatched correctly.

In `@source/input/policy/DefaultBindingResolver.cpp`:
- Around line 11-18: The raw command key constants in DefaultBindingResolver are
out of sync with the design doc’s “Current raw code assignments” table. Update
the KEY_CMD_* values and labels in the resolver to match the documented mapping,
and remove or rename the undocumented CommandQuickChat/0x102 assignment if it is
not part of the current spec. Make sure the raw key table in
DefaultBindingResolver.cpp is aligned with docs/input-policy-strategy-design.md
so integrators wiring new keyboard sources see the correct values.

---

Nitpick comments:
In `@docs/input-policy-strategy-design.md`:
- Around line 114-122: Align the terminology in the design doc with the
implementation by renaming the documented `StrategyDecision` concept to
`PolicyDecision`, matching `include/input/policy/PolicyDecision.h`; update the
section title and field description references so readers can cross-reference
the doc with the actual `PolicyDecision` type and its members without confusion.

In `@include/graphics/view/TFT/TFTView_320x240.h`:
- Line 21: The single-argument constructor in GroupGuard should be marked
explicit to prevent unintended implicit conversions from lv_group_t* at call
sites. Update the GroupGuard constructor that takes target_group so it cannot be
used for implicit construction, while keeping the existing saved_group
initialization behavior unchanged.
- Line 236: The unconditional friend declaration for TFTView_Debug in
TFTView_320x240 should be scoped to the same debug guard used by TFTView_Debug
itself. Update the TFTView_320x240 class declaration so the friend relationship
is only declared when LVGL_DEBUG_FOCUS is enabled, keeping the debug-only
coupling out of release builds and matching the conditional structure in
TFTView_Debug.

In `@include/input/policy/InputContextState.h`:
- Line 30: Add the missing direct type includes in InputContextState.h so it
does not rely on transitive headers for uint32_t and std::string. Update the
header around InputContextState, setActivePanelId, and setFocusedClassHint to
include the proper standard definitions explicitly, keeping
IInputContextProvider.h as the interface dependency but making the parameter
types self-contained.

In `@include/input/policy/InputTypes.h`:
- Around line 54-63: The UICommand enum in InputTypes.h is being used as a
hidden size contract by UICommandDispatcher’s handler array. Add an explicit
terminal sentinel such as Count as the last enumerator in UICommand, keep
LeaveEditMode before it, and update UICommandDispatcher to size handlers from
static_cast<size_t>(UICommand::Count) instead of relying on LeaveEditMode being
last. This makes future command insertions/reordering safe and keeps the
enum-to-array mapping stable.

In `@include/util/SdFatFileWrapper.h`:
- Around line 34-107: Add override to every virtual method in SdFatFSImpl and
the related SdFat32FSImpl/SdFsExFatImpl classes so the compiler verifies they
still match fs::FileImpl/fs::FSImpl. Update the method declarations like write,
read, flush, seek, position, size, close, name, isDirectory, openNextFile,
seekDir, getNextFileName, rewindDirectory, and operator bool to use override,
keeping the signatures aligned with the base interfaces.
- Around line 109-157: `SdFat32FSImpl` and `SdFsExFatImpl` duplicate the same
`open`, `exists`, `rename`, `remove`, `mkdir`, and `rmdir` logic, differing only
by the wrapped storage type. Refactor these classes in `SdFatFileWrapper.h` into
a shared template base (for example, a generic `SdFsImplBase` that stores the
`sd` reference and implements the common `fs::FSImpl` methods), then make the
two existing implementations thin aliases or specializations for `SdFat` and
`SdFs`. Keep `open` using the existing `_convert_access_mode_to_flag` and
`SdFatFSImpl` behavior unchanged.

In `@tests/test_InputPolicy.cpp`:
- Around line 69-94: The current InputPolicy tests only cover the
PressKind::Press path in DefaultInputPolicyFactory and CommandDispatchPolicy.
Add a Release case in test_InputPolicy.cpp to verify
CommandDispatchPolicy::evaluate returns no decision on PressKind::Release, and
add a factory.build scenario with a null dispatcher to verify
DefaultInputPolicyFactory omits CommandDispatchPolicy when no dispatcher is
provided.
- Around line 81-94: Clarify the key-code intent in the InputPolicy test by
replacing the magic values in the InputEvent setup and expected assertion with
named constants or an inline comment tied to FocusTraversalPolicy semantics. Use
the existing test case around pipeline.process and
output.front().resolvedKeyCode to make it obvious whether the values represent
navigation keys like LV_KEY_UP or LV_KEY_PREV, so the assertion reads as
intentional rather than arbitrary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0beafb7e-eabb-46db-9d5a-53a8c6a92e73

📥 Commits

Reviewing files that changed from the base of the PR and between 1c45ebc and 9a89158.

⛔ Files ignored due to path filters (102)
  • generated/ui_320x240/.eez-project-build is excluded by !**/generated/**
  • generated/ui_320x240/actions.h is excluded by !**/generated/**
  • generated/ui_320x240/fonts.h is excluded by !**/generated/**
  • generated/ui_320x240/images.c is excluded by !**/generated/**
  • generated/ui_320x240/images.h is excluded by !**/generated/**
  • generated/ui_320x240/screens.c is excluded by !**/generated/**
  • generated/ui_320x240/screens.h is excluded by !**/generated/**
  • generated/ui_320x240/styles.c is excluded by !**/generated/**
  • generated/ui_320x240/styles.h is excluded by !**/generated/**
  • generated/ui_320x240/ui.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_bolt_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_empty_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_empty_warn_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_full_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_low_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_mid_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_battery_plug_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_bluetooth_on_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_circle_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_circle_question_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_debug_log_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_gps_location_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_bell_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_bell_slash_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_key_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_lock_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_groups_unlock_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_bell_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_bell_slash_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_bluetooth_off_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_bluetooth_on_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_clock_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_ethernet_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_fair_signal_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_good_signal_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_location_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_lora_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_mail_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_mail_unread_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_memory_button.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_no_signal_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_nodes_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_qr_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_sd_card_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_signal_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_strong_signal_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_weak_signal_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_webdav_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_wlan_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_home_wlan_off_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_hourgalss_start_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_key_generate_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_keyboard_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_knob_logo_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_location_lock_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_location_pin_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_lock_channel_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_lock_secure_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_lock_slash_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_map_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_meshtastic_boot_logo_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_meshtastic_logo_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_messages_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_mqtt_logo2_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_msg_popup_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nav_down_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nav_home_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nav_left_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nav_right_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nav_up_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_no_tile_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_node_client_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_node_location_pin24_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_node_router_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_node_sensor_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_nodes_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_radar_beam_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_reboot_bt_on_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_reboot_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_settings_button_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_settings_trash_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_shutdown_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_signal_full_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_signal_slider_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_chart_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_chats_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_group_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_loop_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_lora_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_map_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_message_node_images.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_neighbors_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_node_detector_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_nodes_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_top_settings_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_unmessagable_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_user_question_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_zoom_minus_image.c is excluded by !**/generated/**
  • generated/ui_320x240/ui_image_zoom_plus_image.c is excluded by !**/generated/**
  • generated/ui_320x240/vars.h is excluded by !**/generated/**
📒 Files selected for processing (47)
  • docs/input-policy-strategy-design.md
  • include/comms/WebDAVServer.h
  • include/graphics/driver/LGFXDriver.h
  • include/graphics/view/TFT/TFTView_320x240.h
  • include/graphics/view/TFT/TFTView_Debug.h
  • include/input/I2CKeyboardInputDriver.h
  • include/input/policy/CommandDispatchPolicy.h
  • include/input/policy/DefaultBindingResolver.h
  • include/input/policy/DefaultInputPolicyFactory.h
  • include/input/policy/FocusTraversalPolicy.h
  • include/input/policy/IActionBindingResolver.h
  • include/input/policy/IInputContextProvider.h
  • include/input/policy/IInputPolicy.h
  • include/input/policy/IInputSource.h
  • include/input/policy/IUICommandDispatcher.h
  • include/input/policy/InputContextState.h
  • include/input/policy/InputPipeline.h
  • include/input/policy/InputPolicyFactory.h
  • include/input/policy/InputSourceRegistry.h
  • include/input/policy/InputTypes.h
  • include/input/policy/PassthroughPolicy.h
  • include/input/policy/PolicyChain.h
  • include/input/policy/PolicyDecision.h
  • include/input/policy/UICommandDispatcher.h
  • include/util/SdFatFileWrapper.h
  • source/comms/WebDAVServer.cpp
  • source/graphics/TFT/TFTView_320x240.cpp
  • source/graphics/TFT/TFTView_Debug.cpp
  • source/graphics/TFT/Themes.cpp
  • source/graphics/TFT/object_name_entries_320x240.inc
  • source/graphics/map/MapTile.cpp
  • source/input/I2CKeyboardInputDriver.cpp
  • source/input/I2CKeyboardScanner.cpp
  • source/input/KeyMatrixInputDriver.cpp
  • source/input/policy/CommandDispatchPolicy.cpp
  • source/input/policy/DefaultBindingResolver.cpp
  • source/input/policy/DefaultInputPolicyFactory.cpp
  • source/input/policy/FocusTraversalPolicy.cpp
  • source/input/policy/InputContextState.cpp
  • source/input/policy/InputPipeline.cpp
  • source/input/policy/InputSourceRegistry.cpp
  • source/input/policy/PassthroughPolicy.cpp
  • source/input/policy/PolicyChain.cpp
  • source/input/policy/UICommandDispatcher.cpp
  • source/util/LogRotate.cpp
  • studio/320x240/TFT320x240.eez-project
  • tests/test_InputPolicy.cpp

Comment thread docs/input-policy-strategy-design.md
* @param filesystem Pointer to fs::FS (must be properly initialized)
* @return true if started successfully, false if WiFi not connected or already running
*/
bool start(fs::FS *filesystem);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether WebDAV auth is configured anywhere in this PR.
rg -n -C3 'ESPWebDAV|WebDAVServer|auth|Auth|credential|password|begin\s*\(' include source

Repository: meshtastic/device-ui

Length of output: 37910


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the WebDAV server interface and its call sites.
sed -n '1,220p' include/comms/WebDAVServer.h
printf '\n====\n'
sed -n '1,380p' source/comms/WebDAVServer.cpp
printf '\n====\n'
rg -n -C2 'WebDAVServer::instance\(|start\(fs::FS \*|setTransferStatusCallback|ESPWebDAV|dav->begin\(' source include

# Look for any config or UI path that could supply WebDAV credentials or an explicit unsecured mode.
printf '\n====\n'
rg -n -C3 'webdav|WebDAV|dav://|unsecured|auth|authent|credential|password|basic auth|username' source include

# If the ESPWebDAV dependency is vendored or referenced in repo docs, inspect for auth support.
printf '\n====\n'
rg -n -C3 'class ESPWebDAV|set.*Auth|auth.*ESPWebDAV|username|password' .

Repository: meshtastic/device-ui

Length of output: 50376


🌐 Web query:

ESPWebDAV authentication API begin username password docs

💡 Result:

The ESPWebDAV library by d-a-v does not provide a native API method for setting a username and password directly within the library class [1][2]. Because ESPWebDAV is designed to integrate with standard HTTP server libraries (such as ESP8266WebServer or WebServer for ESP32), authentication must be handled at the HTTP server level [1][2]. To implement authentication, you should use the built-in HTTP authentication methods provided by the web server framework you are using alongside ESPWebDAV. The general approach is: 1. Configure your web server instance (e.g., ESP8266WebServer) to handle authentication using its native request handling methods [3][4]. 2. When a request comes in, the web server checks for valid credentials before passing the request to the ESPWebDAV hook [1][2]. 3. If using hookWebDAVForWebserver to route WebDAV traffic, ensure your authentication logic is applied to the paths handled by the WebDAV server [2][3]. As noted in the library documentation, implementation of credentials is listed as a feature context for developers, and users are encouraged to handle security through the parent web server instance [1]. Please consult the documentation for your specific web server library (e.g., ESP8266WebServer::requestAuthentication) to configure the required username and password checks [3][4].

Citations:


Add an explicit auth or unsecured-only gate for WebDAV

start(fs::FS*) exposes the filesystem over WebDAV with no way to configure credentials or access control, and the ESPWebDAV integration here doesn’t provide a native auth path. Add an auth layer in front of this server or require an explicit unsecured sharing mode before exposing the SD card.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/comms/WebDAVServer.h` at line 51, The WebDAV entrypoint in
WebDAVServer::start(fs::FS*) currently exposes the filesystem without any
access-control gating. Add an explicit security gate to this API by either
wiring in an auth layer before serving the filesystem or requiring a clearly
named unsecured-sharing flag/mode before start() can expose the SD card. Make
the restriction visible in the WebDAVServer interface and enforce it in the
start implementation so the server cannot be launched anonymously by default.

Comment thread include/graphics/driver/LGFXDriver.h Outdated
Comment thread include/input/policy/UICommandDispatcher.h Outdated
Comment thread include/util/SdFatFileWrapper.h
Comment thread source/input/I2CKeyboardInputDriver.cpp
Comment thread source/input/I2CKeyboardScanner.cpp Outdated
Comment thread source/input/KeyMatrixInputDriver.cpp Outdated
Comment thread source/input/KeyMatrixInputDriver.cpp
Comment thread source/input/policy/DefaultBindingResolver.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
source/input/I2CKeyboardInputDriver.cpp (1)

628-628: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve all emitted pipeline events.

InputPipeline::process can return multiple outEvents for EmitSequence, but this path forwards only output.front() and silently drops the rest. Queue the remaining events and drain one per LVGL read, or explicitly reject sequence decisions for this driver path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/input/I2CKeyboardInputDriver.cpp` at line 628, The
I2CKeyboardInputDriver event forwarding path is dropping all but the first event
from InputPipeline::process output, so preserve the full emitted sequence
instead of only consuming output.front(). Update the logic around the affected
read/poll handling in I2CKeyboardInputDriver to either enqueue the remaining
outEvents and drain them across subsequent LVGL reads, or explicitly block
EmitSequence decisions for this driver so no events are silently lost.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@source/input/I2CKeyboardInputDriver.cpp`:
- Line 628: The I2CKeyboardInputDriver event forwarding path is dropping all but
the first event from InputPipeline::process output, so preserve the full emitted
sequence instead of only consuming output.front(). Update the logic around the
affected read/poll handling in I2CKeyboardInputDriver to either enqueue the
remaining outEvents and drain them across subsequent LVGL reads, or explicitly
block EmitSequence decisions for this driver so no events are silently lost.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ed152c2-af46-42eb-8178-a428feda2e18

📥 Commits

Reviewing files that changed from the base of the PR and between 9a89158 and b354140.

⛔ Files ignored due to path filters (1)
  • generated/ui_320x240/screens.c is excluded by !**/generated/**
📒 Files selected for processing (11)
  • docs/input-policy-strategy-design.md
  • include/graphics/driver/LGFXDriver.h
  • include/input/policy/InputTypes.h
  • include/input/policy/UICommandDispatcher.h
  • include/util/SdFatFileWrapper.h
  • source/graphics/TFT/TFTView_320x240.cpp
  • source/input/I2CKeyboardInputDriver.cpp
  • source/input/I2CKeyboardScanner.cpp
  • source/input/KeyMatrixInputDriver.cpp
  • source/input/policy/InputPipeline.cpp
  • studio/320x240/TFT320x240.eez-project
✅ Files skipped from review due to trivial changes (1)
  • docs/input-policy-strategy-design.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • include/input/policy/UICommandDispatcher.h
  • include/graphics/driver/LGFXDriver.h
  • source/input/policy/InputPipeline.cpp
  • include/input/policy/InputTypes.h
  • include/util/SdFatFileWrapper.h
  • source/input/KeyMatrixInputDriver.cpp
  • source/input/I2CKeyboardScanner.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
source/graphics/common/SdCard.cpp (1)

79-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Leftover debug conditional and misleading "SD_MMC" log strings in the SPI-based init path.

#if 1 // #ifdef SDCARD_INIT_SPI looks like a debug scaffold left permanently enabled; if the intent was to gate this block, use #ifdef SDCARD_INIT_SPI directly. Separately, the log strings ("No SD_MMC card detected/attached", "SD_MMC Card Type") are copy-pasted from the SD_MMC path but this branch initializes via SD.begin() over SPI, not SD_MMC — worth correcting to avoid confusing diagnostics when SDCARD_SHARE_SPI is in use.

♻️ Proposed cleanup
-#if 1 // `#ifdef` SDCARD_INIT_SPI
+#ifdef SDCARD_INIT_SPI
     SDHandler.end();
     SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
     SD.end();
     if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
-        ILOG_DEBUG("No SD_MMC card detected");
+        ILOG_DEBUG("No SD card detected");
         return false;
     }
 `#endif`
     uint8_t cardType = SD.cardType();
     if (cardType == CARD_NONE) {
-        ILOG_DEBUG("No SD_MMC card attached");
+        ILOG_DEBUG("No SD card attached");
         return false;
     }
-    ILOG_DEBUG("SD_MMC Card Type: %s", cardType == CARD_MMC    ? "MMC"
+    ILOG_DEBUG("SD Card Type: %s", cardType == CARD_MMC    ? "MMC"
                                        : cardType == CARD_SD   ? "SDSC"
                                        : cardType == CARD_SDHC ? "SDHC"
                                                                : "UNKNOWN");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/graphics/common/SdCard.cpp` around lines 79 - 96, The SPI-based SD
initialization in SdCard.cpp still has a leftover debug scaffold and misleading
SD_MMC diagnostics. Replace the temporary `#if` 1 // `#ifdef` SDCARD_INIT_SPI guard
around the SD.begin() path with the intended compile-time gate, and update the
log messages in this block (including the cardType logging) to refer to the SPI
SD/SD card path rather than SD_MMC. Use the existing SdCard initialization flow
and SD.begin()/SD.cardType() symbols to locate and correct the affected
messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@include/graphics/common/SdCard.h`:
- Around line 3-15: The SDCARD_SHARE_SPI branch in SdCard.h is using
FS_NO_GLOBALS, but SD.h on arduino-esp32 ignores that guard and still exposes
the global SD instance. Update the include guard in this branch to use the
supported NO_GLOBAL_INSTANCES or NO_GLOBAL_SD macro before including SD.h,
keeping the shared SPI setup while preventing the extra global from being
declared.

In `@include/util/ILog.h`:
- Around line 45-79: The log-level guards in ILOG.h are comparing against
ESP_LOG_* enum values, so the preprocessor checks do not gate the
ILOG_DEBUG/INFO/WARN/ERROR/TRACE macros correctly. Update the guard logic in the
ILOG_* definitions to use numeric compile-time levels instead of ESP_LOG_* enum
constants, or remove these wrappers and rely on ESP-IDF’s native log filtering;
keep the existing macro names and DEVICEUI_ESP_LOG_LEVEL usage so the change
stays localized.

In `@source/comms/WebDAVServer.cpp`:
- Around line 144-147: The initMDNS() flow in WebDAVServer currently logs
success unconditionally and lets the caller treat mDNS as initialized even when
MDNS.begin() fails. Update initMDNS() to detect the MDNS.begin(WEBDAV_HOSTNAME)
result, report failure explicitly, and return a status (or otherwise signal
error) so the caller only sets the initialized flag when startup succeeds; use
the WebDAVServer::initMDNS and its caller’s mDNS-initialized handling to locate
the fix.
- Around line 22-24: `WebDAVServer::RSSI()` is returning an unsigned type, which
wraps normal negative WiFi dBm readings into large positive values and breaks
the UI logic. Change the RSSI API to a signed type in `WebDAVServer::RSSI()` and
update all matching implementations/call sites, including the threshold handling
in `TFTView_320x240`, so the value stays negative when appropriate (use
`int32_t` or `int8_t` consistently).

In `@source/graphics/common/SdCard.cpp`:
- Around line 237-239: The SdFsCard::init() implementation has an empty
SDCARD_USE_EXT_SDHANDLER branch, so the non-void function can fall through
without returning a value. Update the SdFsCard::init() logic in the
SDCARD_USE_EXT_SDHANDLER path to return a valid result just like the other
branches, and ensure every conditional path in init() ends with an explicit
return.
- Around line 80-86: The SD card initialization path in SdCard.cpp is tearing
down and reinitializing the shared SPI bus via SDHandler.end() and
SDHandler.begin(), which can disrupt other active peripherals. Update the SDCard
initialization logic in the SdCard::begin/initialization flow to avoid touching
the shared bus state here, and rely on the existing SPI coordination/lock
mechanism instead of reinitializing the bus before SD.begin().

---

Nitpick comments:
In `@source/graphics/common/SdCard.cpp`:
- Around line 79-96: The SPI-based SD initialization in SdCard.cpp still has a
leftover debug scaffold and misleading SD_MMC diagnostics. Replace the temporary
`#if` 1 // `#ifdef` SDCARD_INIT_SPI guard around the SD.begin() path with the
intended compile-time gate, and update the log messages in this block (including
the cardType logging) to refer to the SPI SD/SD card path rather than SD_MMC.
Use the existing SdCard initialization flow and SD.begin()/SD.cardType() symbols
to locate and correct the affected messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b65275f-0ece-4070-bef8-8ffd2be00919

📥 Commits

Reviewing files that changed from the base of the PR and between 6cbed39 and fabccc2.

📒 Files selected for processing (8)
  • include/comms/WebDAVServer.h
  • include/graphics/common/SdCard.h
  • include/util/ILog.h
  • source/comms/WebDAVServer.cpp
  • source/graphics/TFT/TFTView_320x240.cpp
  • source/graphics/common/SdCard.cpp
  • source/graphics/map/SdFatService.cpp
  • source/input/I2CKeyboardInputDriver.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • include/comms/WebDAVServer.h
  • source/input/I2CKeyboardInputDriver.cpp

Comment on lines +3 to +15
#define FS_NO_GLOBALS

#if defined(ARCH_PORTDUINO)
#include "PortduinoFS.h"
extern fs::FS &SDFs;

#elif defined(HAS_SD_MMC)
#include "SD_MMC.h" // TODO: replace by SdFat SDIO
#include "SD_MMC.h" // TODO: replace by SdFat SDIO
extern fs::SDMMCFS &SDFs;

#elif defined(SDCARD_SHARE_SPI) // share SPI instance with main project
#include "SD.h"
#include <stdint.h>
extern fs::SDFS &SDFs;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does espressif arduino-esp32 SD.h library check for a macro named FS_NO_GLOBALS to suppress the global SD instance, or only NO_GLOBAL_SD/NO_GLOBAL_INSTANCES?

💡 Result:

The Espressif arduino-esp32 SD.h library checks for the macros NO_GLOBAL_INSTANCES and NO_GLOBAL_SD to suppress the global SD instance [1][2]. It does not check for a macro named FS_NO_GLOBALS. Specifically, the library uses the following preprocessor directive in SD.h to conditionally declare the external global SD object: #if!defined(NO_GLOBAL_INSTANCES) &&!defined(NO_GLOBAL_SD) extern fs::SDFS SD; #endif Defining either NO_GLOBAL_INSTANCES or NO_GLOBAL_SD will prevent the global SD instance from being declared [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== SdCard.h ==\n'
git ls-files include/graphics/common/SdCard.h src/graphics/common/SdCard.cpp include/graphics/common/SdCard.cpp 2>/dev/null || true

printf '\n== Search for FS_NO_GLOBALS / NO_GLOBAL_SD / NO_GLOBAL_INSTANCES ==\n'
rg -n --hidden --glob '!**/.git/**' 'FS_NO_GLOBALS|NO_GLOBAL_SD|NO_GLOBAL_INSTANCES|NO_FS_GLOBALS' .

printf '\n== Relevant includes/usages around SDFs ==\n'
rg -n --hidden --glob '!**/.git/**' 'SDFs|`#include` "SD.h"|`#include` "SD_MMC.h"|`#include` "PortduinoFS.h"' include src

printf '\n== File excerpt: include/graphics/common/SdCard.h ==\n'
sed -n '1,120p' include/graphics/common/SdCard.h

printf '\n== File excerpt: src/graphics/common/SdCard.cpp ==\n'
sed -n '1,120p' src/graphics/common/SdCard.cpp 2>/dev/null || true

Repository: meshtastic/device-ui

Length of output: 4263


Use NO_GLOBAL_INSTANCES/NO_GLOBAL_SD here SD.h on arduino-esp32 ignores FS_NO_GLOBALS, so this branch still exposes the global SD instance. If the intent is to share the SPI instance without the extra global, switch to one of the supported guards before including SD.h.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/graphics/common/SdCard.h` around lines 3 - 15, The SDCARD_SHARE_SPI
branch in SdCard.h is using FS_NO_GLOBALS, but SD.h on arduino-esp32 ignores
that guard and still exposes the global SD instance. Update the include guard in
this branch to use the supported NO_GLOBAL_INSTANCES or NO_GLOBAL_SD macro
before including SD.h, keeping the shared SPI setup while preventing the extra
global from being declared.

Comment thread include/util/ILog.h
Comment on lines +22 to +24
uint32_t WebDAVServer::RSSI() const
{
return 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all WebDAV RSSI declarations/callers are updated consistently.
rg -n -C2 '\bRSSI\s*\(' include source

Repository: meshtastic/device-ui

Length of output: 1734


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== WebDAVServer declarations =="
sed -n '1,120p' include/comms/WebDAVServer.h | cat -n

echo
echo "== WebDAVServer implementations =="
sed -n '1,240p' source/comms/WebDAVServer.cpp | cat -n | sed -n '1,220p'

echo
echo "== TFTView RSSI call site =="
sed -n '5954,5968p' source/graphics/TFT/TFTView_320x240.cpp | cat -n

Repository: meshtastic/device-ui

Length of output: 12238


Keep WebDAV RSSI signed. WebDAVServer::RSSI() is exposed as uint32_t, but WiFi RSSI values are negative dBm. That wraps normal readings into huge positives and also breaks the UI threshold check in source/graphics/TFT/TFTView_320x240.cpp; change the API and both implementations to int32_t (or int8_t if you want to mirror Arduino-ESP32).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/comms/WebDAVServer.cpp` around lines 22 - 24, `WebDAVServer::RSSI()`
is returning an unsigned type, which wraps normal negative WiFi dBm readings
into large positive values and breaks the UI logic. Change the RSSI API to a
signed type in `WebDAVServer::RSSI()` and update all matching
implementations/call sites, including the threshold handling in
`TFTView_320x240`, so the value stays negative when appropriate (use `int32_t`
or `int8_t` consistently).

Comment on lines +144 to +147
void WebDAVServer::initMDNS(void)
{
MDNS.begin(WEBDAV_HOSTNAME);
ILOG_INFO("[WebDAV] MDNS started. Advertising %s", WEBDAV_HOSTNAME ".local");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether callers rely on initMDNS() always succeeding or need surfaced status.
rg -n -C3 '\binitMDNS\s*\(' include source

Repository: meshtastic/device-ui

Length of output: 1950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file and surrounding declarations/uses.
wc -l source/comms/WebDAVServer.cpp include/comms/WebDAVServer.h
sed -n '1,220p' source/comms/WebDAVServer.cpp | cat -n
printf '\n--- HEADER ---\n'
sed -n '1,120p' include/comms/WebDAVServer.h | cat -n

Repository: meshtastic/device-ui

Length of output: 11531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l source/comms/WebDAVServer.cpp include/comms/WebDAVServer.h
sed -n '1,220p' source/comms/WebDAVServer.cpp | cat -n
printf '\n--- HEADER ---\n'
sed -n '1,120p' include/comms/WebDAVServer.h | cat -n

Repository: meshtastic/device-ui

Length of output: 11531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the local definition/use context for WebDAVServer::initMDNS and MDNS includes.
rg -n -C4 'initMDNS|MDNS\.' source/comms/WebDAVServer.cpp include/comms/WebDAVServer.h

Repository: meshtastic/device-ui

Length of output: 1836


Handle MDNS.begin() failure here.
initMDNS() always logs success, and the caller marks mDNS initialized unconditionally, so a failed start leaves the app believing meshtastic.local is advertised when it isn’t. Return a status or log the failure and avoid setting the initialized flag on error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/comms/WebDAVServer.cpp` around lines 144 - 147, The initMDNS() flow in
WebDAVServer currently logs success unconditionally and lets the caller treat
mDNS as initialized even when MDNS.begin() fails. Update initMDNS() to detect
the MDNS.begin(WEBDAV_HOSTNAME) result, report failure explicitly, and return a
status (or otherwise signal error) so the caller only sets the initialized flag
when startup succeeds; use the WebDAVServer::initMDNS and its caller’s
mDNS-initialized handling to locate the fix.

Comment thread source/graphics/common/SdCard.cpp
Comment thread source/graphics/common/SdCard.cpp Outdated
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.

1 participant