Skip to content

Linting#134

Merged
ewowi merged 11 commits intomainfrom
linting
Mar 3, 2026
Merged

Linting#134
ewowi merged 11 commits intomainfrom
linting

Conversation

@ewowi
Copy link
Collaborator

@ewowi ewowi commented Mar 2, 2026

Summary by CodeRabbit

  • New Features
    • CI now runs include checks and new linting steps; frontend test runner added.
  • Bug Fixes
    • Visual alignment tweak for percussion/beat rendering.
  • Refactor
    • Utilities reorganized and memory allocation helpers introduced; control now supports signed 8-bit values.
  • Tests
    • Native backend unit tests and frontend Vitest suites added.
  • Chores
    • Expanded CI jobs, lint rules and tooling coverage.

- lint.yml: add cpp lint, cppcheck suppress updates
- cpp_lint.py: new

backend
- add cppcheck-suppress
- general: small changes
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

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

Adds CI linting and IWYU jobs, a custom C++ lint script, native and frontend unit tests, and Vitest config; extracts and reorganizes C++ utilities into testable headers; updates many include paths; tightens TypeScript typings across frontend components; and introduces native test harness and supporting stubs.

Changes

Cohort / File(s) Summary
CI / Linting
/.github/workflows/lint.yml, ci/cpp_lint.py, ci/iwyu.py, .claude/settings.json
New CI steps/jobs: frontend type gen & Vitest unit tests, IWYU job; adds Python linters (custom cpp_lint, iwyu) and a Claude stop-hook running lint/test commands.
Native Tests & PlatformIO
platformio.ini, src/native_stub.cpp, test/test_native/test_utilities.cpp
Adds native test env & stub, extensive native doctest suite for utilities, and updated platformio native UNIT TESTS blocks.
Utilities Reorg (C++)
src/MoonBase/Utilities.h, src/MoonBase/utilities/*, src/MoonBase/utilities/Utilities.cpp, src/MoonBase/utilities/Char.h
Splits utilities into Coord3D.h, PureFunctions.h, MemAlloc.h; moves/changes public declarations (Converter, file ops, isInPSRAM, yield helpers); removes older inline helpers from Utilities.h/Utilities.cpp.
Include Path Updates
src/MoonBase/Module.h, src/MoonBase/Modules/..., src/MoonLight/...
Standardizes include paths to MoonBase/utilities/... across multiple source files.
Custom C++ Lint Rules
ci/cpp_lint.py
Adds MoonLight-specific linter enforcing no-string-member, no-static-member, no-serial-print within Node subclasses and reports path:line: [rule] messages.
IWYU Helper
ci/iwyu.py
Adds IWYU runner that analyzes headers/source, skips ESP32-only files, prints suggestions (non-failing).
Control & Node Changes
src/MoonBase/Nodes.h, src/MoonBase/Nodes.cpp
Adds support for signed int8 controls (size code 108) and integrates handling in Node::updateControl/addControl logic.
Module / Device Fixes
src/MoonBase/Modules/FileManager.cpp, src/MoonBase/Modules/ModuleDevices.h, src/MoonBase/Modules/ModuleIO.h
Fixes extractPath call signature, inlines partOfGroup logic, removes RTC-specific GPIO read, adds cppcheck suppressions.
Effects / Layout / Drivers
src/MoonLight/Nodes/Effects/*, src/MoonLight/Nodes/Layouts/L_MoonLight.h, src/MoonLight/Nodes/Modifiers/M_MoonLight.h, src/MoonLight/Nodes/Drivers/*, src/main.cpp
Minor behavioral tweaks (percussion column change, clamp index bounds), change swirlVal to signed, member init defaults, and numerous cppcheck-suppress annotations.
Frontend Types & Typings
interface/src/lib/types/moonbase_models.ts
Adds Coord3D, PadValue, ModuleRow, ModuleValue, ModuleData, ModuleProperty types used across UI.
Frontend Component Typing
interface/src/lib/components/moonbase/..., interface/src/routes/...
Replaces many any usages with explicit types (ModuleProperty/ModuleRow/ModuleData), tightens onChange signatures, adds defaults and $effect.pre initialization in several components/routes, and narrows function parameter types.
Frontend Tests & Config
interface/package.json, interface/vitest.config.ts, interface/src/lib/stores/moonbase_utilities.test.ts
Adds vitest devDependency and npm test script, minimal Vitest config, and unit tests for moonbase utilities.
Socket & Build Hardening
interface/src/lib/stores/socket.ts, interface/vite-plugin-littlefs.ts
Improves binary WebSocket payload handling (emit raw binary to monitor) and ensures Rollup output object initialization in plugin.
Docs / Misc
CLAUDE.md, misc/upstream-prs/001-lint-fixes.md, .gitignore
Adds unit test docs, updates upstream PR notes for Vitest, and ignores .cache.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • v0.7.0 preparations #76 — Overlaps edits to interface/src/lib/components/moonbase/RowRenderer.svelte (reactive/typing changes).
  • pre 71 fixes #107 — Related memory/allocation patterns and safer alloc/realloc utilities (MemAlloc area).
  • Claude code setup #133 — Overlapping CI/linting and frontend/backend lint/test setup changes.

Suggested labels

enhancement

Poem

🐇
Types hop in tidy rows, a quiet code ballet,
Tests nibble bugs at dusk and through the day,
Pure helpers bound to native ground,
CI carrots call — the builds all stay,
A rabbit cheers: compile, then play! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Linting" is overly vague and does not clearly convey the primary changes in the pull request. Revise the title to be more specific and descriptive of the main changes, such as "Add linting infrastructure, refactor utilities headers, and implement unit tests" or similar to better reflect the comprehensive scope of the PR.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch linting

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/MoonLight/Nodes/Modifiers/M_MoonLight.h (1)

164-175: ⚠️ Potential issue | 🟠 Major

Unsigned slider bounds prevent negative swirl reversal from UI.

Line 174's addControl(swirlVal, "swirl", "slider") defaults to 0–255 bounds, but swirlVal is declared int8_t to support negative values for the reversal check at line 215. The UI slider cannot be adjusted below 0, making the swirlVal < 0 branch unreachable from the UI.

🔧 Suggested fix
-    addControl(swirlVal, "swirl", "slider");
+    addControl(swirlVal, "swirl", "slider", -127, 127);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonLight/Nodes/Modifiers/M_MoonLight.h` around lines 164 - 175, The
slider control added in setup uses addControl(swirlVal, "swirl", "slider") but
swirlVal is an int8_t allowing negative values (checked later via swirlVal < 0),
yet the slider defaults to unsigned 0–255 so the UI cannot produce negatives;
change the control to specify signed bounds or a control type that supports
negatives (e.g., provide min/max parameters or use a signed slider API) so the
UI can set swirlVal < 0, updating the addControl call in setup (the one that
registers swirlVal/"swirl") to include an explicit negative minimum (and
appropriate max) consistent with the int8_t range.
🧹 Nitpick comments (1)
src/MoonLight/Nodes/Effects/E_WLED.h (1)

159-159: Prefer removing dead writes in effect loops instead of suppressing them.

These assignments are currently unused/no-op, so deleting them is cleaner and avoids extra per-frame work.

♻️ Example cleanup direction
-    unsigned pixIntensity = MIN(2.0f * sharedData.bands[freqBand], 255);  // cppcheck-suppress unreadVariable -- WLED ported code
+    // removed unused pixIntensity

-        aux1 = aux0;  // cppcheck-suppress unreadVariable -- WLED ported code
+        // removed no-op state shuffle (locals are pass-by-value here)

-      CRGB color = CRGB(0, 0, 0);
+      CRGB color;

Also applies to: 1699-1699, 1949-1954, 2824-2824, 2857-2857, 3528-3533

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonLight/Nodes/Effects/E_WLED.h` at line 159, The assignment to
pixIntensity (and other similar dead writes flagged at the noted ranges) is
unused and should be removed rather than suppressed; find the dead-write
statements (e.g., the line assigning 'unsigned pixIntensity = MIN(2.0f *
sharedData.bands[freqBand], 255);' and the other occurrences around the
referenced ranges) inside the WLED effect loops and delete those unused variable
assignments and any accompanying cppcheck-suppress comments, ensuring no
remaining references rely on those variables (if they do, refactor to use the
value where intended).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/cpp_lint.py`:
- Around line 165-169: The serial-print checker currently strips comments only,
causing false positives when string literals contain "Serial.print"; update the
logic that builds line_no_comment before calling RE_SERIAL_PRINT.search by also
removing double-quoted and single-quoted string literals using the same regex
patterns used in count_braces() (i.e. strip "..." and '...' sequences), then run
RE_SERIAL_PRINT.search on that cleaned string and keep the existing RE_OK_LINT
check and violation append (refer to the symbols line_no_comment,
RE_SERIAL_PRINT, RE_OK_LINT and the count_braces() string regexes to implement
the change).
- Around line 108-120: The class inheritance detection (RE_CLASS_OPEN handling)
fails for multiline declarations; modify the logic so when RE_CLASS_OPEN matches
but the opening '{' is not yet seen on that line you buffer subsequent lines
into a single declaration string until you encounter the '{' (or reach
end-of-file), then run the Node-inheritance check (use re.search on the buffered
declaration for r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b') to set is_node; ensure
you do not shadow loop variables or mutate the main line index (use a local
iterator or while loop to read ahead), and record depth_at_open using the
current brace_depth before counting the buffered line's braces to avoid
off-by-one errors when pushing into class_stack.

---

Outside diff comments:
In `@src/MoonLight/Nodes/Modifiers/M_MoonLight.h`:
- Around line 164-175: The slider control added in setup uses
addControl(swirlVal, "swirl", "slider") but swirlVal is an int8_t allowing
negative values (checked later via swirlVal < 0), yet the slider defaults to
unsigned 0–255 so the UI cannot produce negatives; change the control to specify
signed bounds or a control type that supports negatives (e.g., provide min/max
parameters or use a signed slider API) so the UI can set swirlVal < 0, updating
the addControl call in setup (the one that registers swirlVal/"swirl") to
include an explicit negative minimum (and appropriate max) consistent with the
int8_t range.

---

Nitpick comments:
In `@src/MoonLight/Nodes/Effects/E_WLED.h`:
- Line 159: The assignment to pixIntensity (and other similar dead writes
flagged at the noted ranges) is unused and should be removed rather than
suppressed; find the dead-write statements (e.g., the line assigning 'unsigned
pixIntensity = MIN(2.0f * sharedData.bands[freqBand], 255);' and the other
occurrences around the referenced ranges) inside the WLED effect loops and
delete those unused variable assignments and any accompanying cppcheck-suppress
comments, ensuring no remaining references rely on those variables (if they do,
refactor to use the value where intended).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8afc4d and bc5f8e6.

📒 Files selected for processing (17)
  • .github/workflows/lint.yml
  • ci/cpp_lint.py
  • src/MoonBase/Char.h
  • src/MoonBase/Modules/FileManager.cpp
  • src/MoonBase/Modules/ModuleDevices.h
  • src/MoonBase/Modules/ModuleIO.h
  • src/MoonBase/Utilities.cpp
  • src/MoonLight/Modules/ModuleDrivers.h
  • src/MoonLight/Modules/ModuleEffects.h
  • src/MoonLight/Nodes/Drivers/D_IMU.h
  • src/MoonLight/Nodes/Drivers/D__Sandbox.h
  • src/MoonLight/Nodes/Effects/E_FastLED.h
  • src/MoonLight/Nodes/Effects/E_MoonLight.h
  • src/MoonLight/Nodes/Effects/E_WLED.h
  • src/MoonLight/Nodes/Layouts/L_MoonLight.h
  • src/MoonLight/Nodes/Modifiers/M_MoonLight.h
  • src/main.cpp
💤 Files with no reviewable changes (2)
  • src/MoonLight/Nodes/Layouts/L_MoonLight.h
  • src/MoonLight/Nodes/Effects/E_FastLED.h

Nodes: addControl and updateControl: add support for int8_t
PinWheel: enable negative swirl
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
ci/cpp_lint.py (1)

108-130: ⚠️ Potential issue | 🟠 Major

Buffer class declarations until { before entering class_stack.

At Line 116, a class frame is pushed even when the class body starts later; at Line 129 it is popped immediately when brace_depth is unchanged. This still misses valid multiline declarations and prevents Node-only rules from running for those classes.

🔧 Suggested direction
-        m = RE_CLASS_OPEN.match(line)
+        m = RE_CLASS_OPEN.match(line)
         if m:
             class_name = m.group(1)
-            is_node = bool(re.search(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b', line))
-            class_stack.append({
-                'name': class_name,
-                'depth_at_open': brace_depth,
-                'is_node': is_node,
-            })
+            # Parse declaration header across following lines until '{' or ';'
+            # Then push class frame only if '{' is part of that declaration.
+            # Evaluate Node inheritance against the buffered header.
#!/bin/bash
python3 - <<'PY'
import re
node_re = re.compile(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b')
cases = [
    ("class A : public Node {", True, "single-line"),
    ("class A", False, "multiline first line (current implementation checks only this line)"),
    (": public Node {", True, "multiline inheritance line"),
    ("class A : public Base, public Node {", False, "multiple inheritance with access specifier"),
]
for text, expected_current, name in cases:
    got = bool(node_re.search(text))
    print(f"{name:45} got={got} expected_current={expected_current}")
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/cpp_lint.py` around lines 108 - 130, The code currently pushes a class
frame to class_stack as soon as RE_CLASS_OPEN matches, which captures classes
whose '{' appears on a later line and then gets immediately popped because
brace_depth hasn't increased; instead buffer the class declaration until the
opening brace is seen: when RE_CLASS_OPEN matches, store a pending_class (with
'name' and an initial 'is_node' computed from that line) but do not append to
class_stack; always update brace_depth via count_braces(line); after updating
brace_depth, if a pending_class exists and this line contains the opening brace
(or delta>0 indicating a '{' was counted), push the pending_class onto
class_stack with 'depth_at_open' set to the brace_depth value before counting
the new brace (i.e., previous_brace_depth) and the final 'is_node' determined by
scanning the buffered declaration lines (include following lines until the '{')
using the same Node regex; clear pending_class after pushing; ensure the pop
loop that compares brace_depth to class_stack[-1]['depth_at_open'] remains
unchanged so Node-only rules run correctly for multi-line declarations
(reference symbols: RE_CLASS_OPEN, class_stack, pending_class, is_node,
count_braces, brace_depth).
🧹 Nitpick comments (1)
src/MoonBase/Nodes.h (1)

149-158: Replace magic control-size codes with named constants.

Line 150, Line 156, and Line 158 use encoded literals (108, 33, 34). Centralizing these in one constant set avoids mismatch with Node::updateControl.

♻️ Proposed refactor
--- a/src/MoonBase/Nodes.h
+++ b/src/MoonBase/Nodes.h
@@
 class Node {
  public:
+  static constexpr uint8_t CONTROL_SIZE_U8 = 8;
+  static constexpr uint8_t CONTROL_SIZE_I8 = 108;
+  static constexpr uint8_t CONTROL_SIZE_U16 = 16;
+  static constexpr uint8_t CONTROL_SIZE_U32 = 32;
+  static constexpr uint8_t CONTROL_SIZE_INT = 33;
+  static constexpr uint8_t CONTROL_SIZE_FLOAT = 34;
@@
-      if (std::is_same<ControlType, uint8_t>::value) {
-        control["size"] = 8;
+      if (std::is_same<ControlType, uint8_t>::value) {
+        control["size"] = CONTROL_SIZE_U8;
       } else if (std::is_same<ControlType, int8_t>::value) {
-        control["size"] = 108; // code for int8_t
+        control["size"] = CONTROL_SIZE_I8;  // code for int8_t
       } else if (std::is_same<ControlType, uint16_t>::value) {
-        control["size"] = 16;
+        control["size"] = CONTROL_SIZE_U16;
       } else if (std::is_same<ControlType, uint32_t>::value) {
-        control["size"] = 32;
+        control["size"] = CONTROL_SIZE_U32;
       } else if (std::is_same<ControlType, int>::value) {
-        control["size"] = 33;  // code for int (which is 32 bits)
+        control["size"] = CONTROL_SIZE_INT;  // code for int (32-bit)
       } else if (std::is_same<ControlType, float>::value) {
-        control["size"] = 34;  // code for float float (which is 32 bits)
+        control["size"] = CONTROL_SIZE_FLOAT;  // code for float (32-bit)
       } else {
--- a/src/MoonBase/Nodes.cpp
+++ b/src/MoonBase/Nodes.cpp
@@
-        if (control["size"] == 8) {
+        if (control["size"] == Node::CONTROL_SIZE_U8) {
@@
-        } else if (control["size"] == 108) {
+        } else if (control["size"] == Node::CONTROL_SIZE_I8) {
@@
-        } else if (control["size"] == 16) {
+        } else if (control["size"] == Node::CONTROL_SIZE_U16) {
@@
-        } else if (control["size"] == 32) {
+        } else if (control["size"] == Node::CONTROL_SIZE_U32) {
@@
-        } else if (control["size"] == 33) {
+        } else if (control["size"] == Node::CONTROL_SIZE_INT) {
@@
-        } else if (control["size"] == 34) {
+        } else if (control["size"] == Node::CONTROL_SIZE_FLOAT) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/Nodes.h` around lines 149 - 158, The code uses magic numeric
"size" codes (e.g., 108, 33, 34) when setting control["size"] for certain
ControlType specializations; replace these literals with named constants (e.g.,
CONTROL_SIZE_INT8, CONTROL_SIZE_INT, CONTROL_SIZE_FLOAT) declared centrally
(preferably as enum or constexpr in the same header/namespace) and use those
constants in the branches for std::is_same<ControlType,...>; ensure the new
constant names are consistent with Node::updateControl expectations and update
any references to the old numeric codes to use the new constants so the mapping
is centralized and less error-prone.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/cpp_lint.py`:
- Around line 165-167: The current sanitization removes line comments before
stripping string literals so occurrences of '//' inside strings get treated as
comments; in the code that builds line_no_comment (using re.sub calls), move the
string-literal stripping calls (the two re.subs that replace double-quoted and
single-quoted strings) to run before the re.sub that removes '//' comments
(i.e., perform the '"' and "'" regex replacements first, then remove //
comments) so Serial.print and similar tokens inside strings are not
inadvertently truncated; update the sequence around the line_no_comment variable
and ensure the three re.sub calls retain their existing patterns and order for
double vs single quotes.

---

Duplicate comments:
In `@ci/cpp_lint.py`:
- Around line 108-130: The code currently pushes a class frame to class_stack as
soon as RE_CLASS_OPEN matches, which captures classes whose '{' appears on a
later line and then gets immediately popped because brace_depth hasn't
increased; instead buffer the class declaration until the opening brace is seen:
when RE_CLASS_OPEN matches, store a pending_class (with 'name' and an initial
'is_node' computed from that line) but do not append to class_stack; always
update brace_depth via count_braces(line); after updating brace_depth, if a
pending_class exists and this line contains the opening brace (or delta>0
indicating a '{' was counted), push the pending_class onto class_stack with
'depth_at_open' set to the brace_depth value before counting the new brace
(i.e., previous_brace_depth) and the final 'is_node' determined by scanning the
buffered declaration lines (include following lines until the '{') using the
same Node regex; clear pending_class after pushing; ensure the pop loop that
compares brace_depth to class_stack[-1]['depth_at_open'] remains unchanged so
Node-only rules run correctly for multi-line declarations (reference symbols:
RE_CLASS_OPEN, class_stack, pending_class, is_node, count_braces, brace_depth).

---

Nitpick comments:
In `@src/MoonBase/Nodes.h`:
- Around line 149-158: The code uses magic numeric "size" codes (e.g., 108, 33,
34) when setting control["size"] for certain ControlType specializations;
replace these literals with named constants (e.g., CONTROL_SIZE_INT8,
CONTROL_SIZE_INT, CONTROL_SIZE_FLOAT) declared centrally (preferably as enum or
constexpr in the same header/namespace) and use those constants in the branches
for std::is_same<ControlType,...>; ensure the new constant names are consistent
with Node::updateControl expectations and update any references to the old
numeric codes to use the new constants so the mapping is centralized and less
error-prone.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc5f8e6 and 301277f.

📒 Files selected for processing (5)
  • ci/cpp_lint.py
  • src/MoonBase/Nodes.cpp
  • src/MoonBase/Nodes.h
  • src/MoonLight/Nodes/Effects/E_WLED.h
  • src/MoonLight/Nodes/Modifiers/M_MoonLight.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/MoonLight/Nodes/Effects/E_WLED.h

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
ci/cpp_lint.py (1)

108-120: ⚠️ Potential issue | 🟠 Major

Handle multiline class headers before computing is_node.

At Line 113, Node inheritance is checked only on the current line, and the frame pushed at Line 116 can be popped before a later { is seen (Line 129). This misses valid multiline C++ declarations and silently skips rule enforcement for those classes.

🔧 Suggested fix
 def check_file(path: Path) -> list:
@@
-    class_stack: list = []
+    class_stack: list = []
+    pending_class = None  # {'name': str, 'depth_at_open': int, 'header': str}
@@
-        m = RE_CLASS_OPEN.match(line)
-        if m:
-            class_name = m.group(1)
-            is_node = bool(re.search(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b', line))
-            class_stack.append({
-                'name': class_name,
-                'depth_at_open': brace_depth,
-                'is_node': is_node,
-            })
+        header_line = re.sub(r'//.*', '', line)
+        m = RE_CLASS_OPEN.match(header_line)
+        if pending_class:
+            pending_class['header'] += ' ' + header_line.strip()
+            if '{' in header_line:
+                class_stack.append({
+                    'name': pending_class['name'],
+                    'depth_at_open': pending_class['depth_at_open'],
+                    'is_node': bool(re.search(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b', pending_class['header'])),
+                })
+                pending_class = None
+        elif m:
+            pending_class = {
+                'name': m.group(1),
+                'depth_at_open': brace_depth,
+                'header': header_line.strip(),
+            }
+            if '{' in header_line:
+                class_stack.append({
+                    'name': pending_class['name'],
+                    'depth_at_open': pending_class['depth_at_open'],
+                    'is_node': bool(re.search(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b', pending_class['header'])),
+                })
+                pending_class = None
#!/bin/bash
python3 - << 'PY'
import re

RE_CLASS_OPEN = re.compile(r'^\s*(?:class|struct)\s+(\w+)')
RE_NODE = re.compile(r':\s*(?:public\s+)?(?:\w+,\s*)*Node\b')

def count_braces(line):
    s = re.sub(r'"(?:[^"\\]|\\.)*"', '""', line)
    s = re.sub(r"'(?:[^'\\]|\\.)*'", "''", s)
    s = re.sub(r'//.*', '', s)
    return s.count('{') - s.count('}')

lines = [
    "class Foo",
    "  : public Node",
    "{",
    "  String badMember;",
    "};",
]

class_stack = []
brace_depth = 0

for lineno, line in enumerate(lines, start=1):
    m = RE_CLASS_OPEN.match(line)
    if m:
        class_stack.append({
            "name": m.group(1),
            "depth_at_open": brace_depth,
            "is_node": bool(RE_NODE.search(line)),
        })
    brace_depth += count_braces(line)
    while class_stack and brace_depth <= class_stack[-1]["depth_at_open"]:
        class_stack.pop()
    print(f"{lineno}: depth={brace_depth}, stack={class_stack}")
PY

Also applies to: 129-130

🧹 Nitpick comments (2)
interface/src/routes/moonbase/filemanager/FileManager.svelte (1)

37-37: Type assertion on empty object may mask missing fields.

Using {} as FilesState bypasses TypeScript's check for required properties. While this works because getState() populates the object before use, consider initializing with a proper default object (similar to editableFile on lines 39-50) for consistency and to catch accidental early access.

♻️ Optional: Initialize with default values
-let filesState = $state({} as FilesState);
+let filesState: FilesState = $state({
+	name: '',
+	path: '',
+	isFile: false,
+	size: 0,
+	time: 0,
+	contents: '',
+	files: [],
+	fs_total: 0,
+	fs_used: 0,
+	showHidden: false
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/moonbase/filemanager/FileManager.svelte` at line 37, The
filesState is initialized using a type assertion "{} as FilesState", which hides
missing required fields; replace that with a proper default object that matches
the FilesState shape (similar to how editableFile is initialized) and pass that
default into $state instead of "{} as FilesState". Locate the filesState
declaration and FilesState type, create a defaultFilesState (with empty
arrays/nullable fields/default primitives matching the type), use getState() as
before to populate it later, and ensure any consumers of filesState still work
with the concrete defaults.
ci/cpp_lint.py (1)

74-83: Block comment handling is incomplete, though no instances exist in the current codebase.

The count_braces() function at lines 74–83 strips // comments but ignores /* ... */ blocks. While this could theoretically cause scope-tracking errors if braces appear inside block comments, a search of the codebase found zero instances of braces within block comments. This is a robustness gap rather than an active bug, but making the stripping logic more complete would be prudent for future-proofing.

The suggested refactor to track multiline block comments is reasonable if you want to make the code more defensive, though it is not urgent given the current state of the code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/cpp_lint.py` around lines 74 - 83, The count_braces function only strips
// and string/char literals and ignores /* ... */ block comments, so braces
inside block comments can be miscounted; update count_braces (or its caller) to
handle C-style block comments by tracking a multiline in_block_comment state:
either (A) pre-process the entire file to remove all /*...*/ spans before
calling count_braces, or (B) change count_braces signature to accept an
in_block_comment boolean and return (net_brace_delta, updated_in_block_comment),
then inside the function detect and strip /*...*/ content that starts and/or
ends on the current line (handling cases where the comment spans lines) before
counting '{' and '}', and update callers to propagate the in_block_comment
state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ci/cpp_lint.py`:
- Around line 74-83: The count_braces function only strips // and string/char
literals and ignores /* ... */ block comments, so braces inside block comments
can be miscounted; update count_braces (or its caller) to handle C-style block
comments by tracking a multiline in_block_comment state: either (A) pre-process
the entire file to remove all /*...*/ spans before calling count_braces, or (B)
change count_braces signature to accept an in_block_comment boolean and return
(net_brace_delta, updated_in_block_comment), then inside the function detect and
strip /*...*/ content that starts and/or ends on the current line (handling
cases where the comment spans lines) before counting '{' and '}', and update
callers to propagate the in_block_comment state.

In `@interface/src/routes/moonbase/filemanager/FileManager.svelte`:
- Line 37: The filesState is initialized using a type assertion "{} as
FilesState", which hides missing required fields; replace that with a proper
default object that matches the FilesState shape (similar to how editableFile is
initialized) and pass that default into $state instead of "{} as FilesState".
Locate the filesState declaration and FilesState type, create a
defaultFilesState (with empty arrays/nullable fields/default primitives matching
the type), use getState() as before to populate it later, and ensure any
consumers of filesState still work with the concrete defaults.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 301277f and d4b2866.

📒 Files selected for processing (8)
  • ci/cpp_lint.py
  • interface/src/lib/components/moonbase/EditRowWidget.svelte
  • interface/src/lib/components/moonbase/FieldRenderer.svelte
  • interface/src/lib/components/moonbase/FileEditWidget.svelte
  • interface/src/lib/components/moonbase/RowRenderer.svelte
  • interface/src/lib/types/moonbase_models.ts
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • interface/src/routes/moonbase/module/Module.svelte

- Add .cache in .gitignore
- Add unit tests in CLAUDE.md
- Add unit tests in .claude/settings.json
- pio.ini extend env:native
- add test_utilities.cpp
- cpp_lint.py, extend Track class/struct openings
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
lib/framework/WWWData.h (1)

5-5: Reduce generated binary churn by using deterministic gzip metadata.

These updates look like regenerated gzip headers across embedded assets. Consider generating with fixed mtime (and stable compression settings) so unrelated PRs don’t produce large binary-only diffs.

Also applies to: 36-36, 750-750, 765-765, 772-772, 779-779, 13557-13557, 14631-14631, 16110-16110, 17173-17173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/framework/WWWData.h` at line 5, The embedded gzip blobs in WWWData.h (the
generated byte arrays at the top of the file) are non-deterministic due to gzip
header metadata; regenerate these assets with deterministic gzip options (set a
fixed mtime like 0, strip original filename/OS fields via --no-name or
equivalent, and use consistent compression level) so the byte arrays (embedded
gzip blobs) become stable across builds; update your asset generation script or
tool that produces WWWData.h (the step that emits the 0x1F,0x8B,... arrays) to
pass --mtime=0 and --no-name (or use a reproducible gzip library with mtime
control) and re-run it for all affected embedded blobs so the diffs stop showing
spurious binary churn.
.claude/settings.json (1)

8-8: Avoid hardcoded PlatformIO binary path in the hook.

Line [8] uses ~/.platformio/penv/bin/pio, which is environment-specific and may fail for contributors/CI with different setups. Prefer pio test -e native (PATH-based) for portability.

Portable command adjustment
-            "command": "echo '=== MoonLight lint ===' && cd \"$CLAUDE_PROJECT_DIR/interface\" && npm run lint && echo '--- backend ---' && cd \"$CLAUDE_PROJECT_DIR\" && python3 ci/cpp_lint.py && echo '--- unit tests ---' && ~/.platformio/penv/bin/pio test -e native"
+            "command": "echo '=== MoonLight lint ===' && cd \"$CLAUDE_PROJECT_DIR/interface\" && npm run lint && echo '--- backend ---' && cd \"$CLAUDE_PROJECT_DIR\" && python3 ci/cpp_lint.py && echo '--- unit tests ---' && pio test -e native"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/settings.json at line 8, The hook command in the JSON "command"
value currently invokes the PlatformIO binary via a hardcoded path
(~/.platformio/penv/bin/pio test -e native); update that "command" entry to call
pio via PATH instead (replace "~/.platformio/penv/bin/pio test -e native" with
"pio test -e native") so the hook is portable across environments, and ensure
callers rely on a PATH-installed pio or document that pio must be in PATH.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/cpp_lint.py`:
- Around line 120-133: The code currently creates pending_class for any "class"
header line, causing forward declarations like "class Foo;" to be treated as
pending and later misalign class_stack; update the logic around pending_class
creation (the block that sets pending_class and inspects header_line) to first
detect and skip forward declarations (e.g., header_line matching a
forward-declaration pattern or ending with ';') so you only create pending_class
and push onto class_stack when the header is a real definition (has '{' or
otherwise not a mere declaration); reference pending_class, class_stack, and
header_line when applying the guard.

In `@test/test_native/test_utilities.cpp`:
- Line 71: The lcm function can divide by zero when gcd(a, b) == 0 (e.g., lcm(0,
0)); update the uint16_t lcm(uint16_t a, uint16_t b) function to guard against
zero inputs by checking a == 0 || b == 0 and returning 0 in that case before
performing a / gcd(a, b) * b; keep using the existing gcd(a, b) helper for
non-zero cases to compute the result.

---

Nitpick comments:
In @.claude/settings.json:
- Line 8: The hook command in the JSON "command" value currently invokes the
PlatformIO binary via a hardcoded path (~/.platformio/penv/bin/pio test -e
native); update that "command" entry to call pio via PATH instead (replace
"~/.platformio/penv/bin/pio test -e native" with "pio test -e native") so the
hook is portable across environments, and ensure callers rely on a
PATH-installed pio or document that pio must be in PATH.

In `@lib/framework/WWWData.h`:
- Line 5: The embedded gzip blobs in WWWData.h (the generated byte arrays at the
top of the file) are non-deterministic due to gzip header metadata; regenerate
these assets with deterministic gzip options (set a fixed mtime like 0, strip
original filename/OS fields via --no-name or equivalent, and use consistent
compression level) so the byte arrays (embedded gzip blobs) become stable across
builds; update your asset generation script or tool that produces WWWData.h (the
step that emits the 0x1F,0x8B,... arrays) to pass --mtime=0 and --no-name (or
use a reproducible gzip library with mtime control) and re-run it for all
affected embedded blobs so the diffs stop showing spurious binary churn.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4b2866 and 46ad520.

📒 Files selected for processing (8)
  • .claude/settings.json
  • .gitignore
  • CLAUDE.md
  • ci/cpp_lint.py
  • interface/src/routes/moonbase/filemanager/FileManager.svelte
  • lib/framework/WWWData.h
  • platformio.ini
  • test/test_native/test_utilities.cpp
🔥 Files not summarized due to errors (1)
  • lib/framework/WWWData.h: Error: Server error: no LLM provider could handle the message
✅ Files skipped from review due to trivial changes (1)
  • .gitignore

- pio.ini: move from unitytest to doctest

- Char: remove PureFunctions and move to PureFunctions.h
- Coord3D: remove from Utilities to new class
- Utiltities: remove Coord3D and PureFunctions
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
test/test_native/test_utilities.cpp (1)

31-35: Add lcm zero-input regression checks.

Please add edge-case assertions so the zero-handling contract is permanently covered.

✅ Proposed test additions
 TEST_CASE("lcm") {
   CHECK_EQ(lcm(12, 18), 36);
   CHECK_EQ(lcm(7, 13), 91);
   CHECK_EQ(lcm(12, 8), 24);
+  CHECK_EQ(lcm(0, 0), 0);
+  CHECK_EQ(lcm(0, 5), 0);
+  CHECK_EQ(lcm(5, 0), 0);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/test_native/test_utilities.cpp` around lines 31 - 35, Add assertions to
cover zero-handling for the lcm function: verify that lcm(0, 0) returns 0 and
that lcm(0, n) and lcm(n, 0) return 0 for several nonzero n (e.g., 1, 12, 18) in
the existing TEST_CASE("lcm") so the zero-input regression is permanently
covered; update the test to call lcm(0,0), lcm(0,12), lcm(12,0) (and optionally
lcm(0,1), lcm(1,0)) with CHECK_EQ expecting 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/MoonBase/Coord3D.h`:
- Around line 55-57: The division and modulo operators in Coord3D
(operator/(const Coord3D), operator/(const int), and operator%(const Coord3D))
can perform division/modulo by zero causing undefined behavior; update these
operators to check each divisor component (rhs.x, rhs.y, rhs.z for Coord3D and
rhs for int) before performing the operation and handle zero cases
deterministically (e.g., throw a std::invalid_argument or std::domain_error with
a clear message, or assert/return a safe value depending on project
error-handling conventions) so no division or modulo by zero occurs; ensure the
error message identifies the failing operator and the offending component to aid
debugging.
- Around line 60-77: Change the assignment-style operators in class Coord3D to
take their RHS by const reference and return a reference to *this so chaining
and copies are corrected: update operator=(const Coord3D rhs) to operator=(const
Coord3D& rhs) and return Coord3D&; update operator+=(const Coord3D rhs) to
operator+=(const Coord3D& rhs) and return Coord3D&; update operator/=(const
Coord3D rhs) to operator/=(const Coord3D& rhs) and return Coord3D&; keep the
existing member-wise operations but ensure the functions return *this as a
reference.

In `@src/MoonBase/PureFunctions.h`:
- Around line 30-39: extractPath's API is unsafe: it omits the destination
buffer size and uses nonstandard strlcpy; change extractPath(const char*
filepath, char* path) to accept a size parameter (e.g., extractPath(const char*
filepath, char* path, size_t pathSize)), compute the desired path length from
lastSlash (size_t pathLen = lastSlash - filepath + 1), then copy into path using
only standard functions with bounds checking (copy min(pathLen, pathSize-1)
bytes and always null-terminate), and handle the no-slash case by writing an
empty string when pathSize>0; update callers to pass the buffer size and
consider returning a status or the number of bytes written if helpful.

In `@test/test_native/test_utilities.cpp`:
- Around line 8-10: The file-level header comment in
test/test_native/test_utilities.cpp is stale: it claims helper functions are
copied into the file and references ESP32 header avoidance, but the tests now
include PureFunctions.h and Coord3D.h directly; update the top comment to state
that the tests consume PureFunctions.h and Coord3D.h (and mention the run
command if still relevant), and remove the outdated note about copied helpers
and ESP32 dependencies so the comment accurately reflects current usage (look
for the existing header comment at the top of test_utilities.cpp and edit it
accordingly).

---

Nitpick comments:
In `@test/test_native/test_utilities.cpp`:
- Around line 31-35: Add assertions to cover zero-handling for the lcm function:
verify that lcm(0, 0) returns 0 and that lcm(0, n) and lcm(n, 0) return 0 for
several nonzero n (e.g., 1, 12, 18) in the existing TEST_CASE("lcm") so the
zero-input regression is permanently covered; update the test to call lcm(0,0),
lcm(0,12), lcm(12,0) (and optionally lcm(0,1), lcm(1,0)) with CHECK_EQ expecting
0.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46ad520 and 93ef793.

📒 Files selected for processing (8)
  • platformio.ini
  • src/MoonBase/Char.h
  • src/MoonBase/Coord3D.h
  • src/MoonBase/PureFunctions.h
  • src/MoonBase/Utilities.cpp
  • src/MoonBase/Utilities.h
  • src/MoonLight/Nodes/Effects/E_MoonLight.h
  • test/test_native/test_utilities.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • platformio.ini
  • src/MoonLight/Nodes/Effects/E_MoonLight.h

Comment on lines +55 to +57
Coord3D operator/(const Coord3D rhs) const { return Coord3D(x / rhs.x, y / rhs.y, z / rhs.z); }
Coord3D operator/(const int rhs) const { return Coord3D(x / rhs, y / rhs, z / rhs); }
Coord3D operator%(const Coord3D rhs) const { return Coord3D(x % rhs.x, y % rhs.y, z % rhs.z); }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent UB in divide/modulo operators.

These operators can divide/mod by zero and invoke undefined behavior.

🐛 Proposed fix
-  Coord3D operator/(const Coord3D rhs) const { return Coord3D(x / rhs.x, y / rhs.y, z / rhs.z); }
-  Coord3D operator/(const int rhs) const { return Coord3D(x / rhs, y / rhs, z / rhs); }
-  Coord3D operator%(const Coord3D rhs) const { return Coord3D(x % rhs.x, y % rhs.y, z % rhs.z); }
+  Coord3D operator/(const Coord3D& rhs) const {
+    Coord3D out(*this);
+    out /= rhs;  // keeps current zero-handling contract from operator/=
+    return out;
+  }
+  Coord3D operator/(int rhs) const {
+    if (rhs == 0) return *this;
+    return Coord3D(x / rhs, y / rhs, z / rhs);
+  }
+  Coord3D operator%(const Coord3D& rhs) const {
+    return Coord3D(
+      rhs.x ? x % rhs.x : x,
+      rhs.y ? y % rhs.y : y,
+      rhs.z ? z % rhs.z : z
+    );
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Coord3D operator/(const Coord3D rhs) const { return Coord3D(x / rhs.x, y / rhs.y, z / rhs.z); }
Coord3D operator/(const int rhs) const { return Coord3D(x / rhs, y / rhs, z / rhs); }
Coord3D operator%(const Coord3D rhs) const { return Coord3D(x % rhs.x, y % rhs.y, z % rhs.z); }
Coord3D operator/(const Coord3D& rhs) const {
Coord3D out(*this);
out /= rhs; // keeps current zero-handling contract from operator/=
return out;
}
Coord3D operator/(int rhs) const {
if (rhs == 0) return *this;
return Coord3D(x / rhs, y / rhs, z / rhs);
}
Coord3D operator%(const Coord3D& rhs) const {
return Coord3D(
rhs.x ? x % rhs.x : x,
rhs.y ? y % rhs.y : y,
rhs.z ? z % rhs.z : z
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/Coord3D.h` around lines 55 - 57, The division and modulo
operators in Coord3D (operator/(const Coord3D), operator/(const int), and
operator%(const Coord3D)) can perform division/modulo by zero causing undefined
behavior; update these operators to check each divisor component (rhs.x, rhs.y,
rhs.z for Coord3D and rhs for int) before performing the operation and handle
zero cases deterministically (e.g., throw a std::invalid_argument or
std::domain_error with a clear message, or assert/return a safe value depending
on project error-handling conventions) so no division or modulo by zero occurs;
ensure the error message identifies the failing operator and the offending
component to aid debugging.

Comment on lines +60 to +77
Coord3D operator=(const Coord3D rhs) {
x = rhs.x;
y = rhs.y;
z = rhs.z;
return *this;
}
Coord3D operator+=(const Coord3D rhs) {
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
Coord3D operator/=(const Coord3D rhs) {
if (rhs.x) x /= rhs.x;
if (rhs.y) y /= rhs.y;
if (rhs.z) z /= rhs.z;
return *this;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify assignment-style operator signatures in Coord3D.
echo "=== Checking for operators returning by value ==="
rg -nP 'Coord3D\s+operator(?:=|\+=|/=)\s*\(' src/MoonBase/Coord3D.h || echo "No matches found"

echo ""
echo "=== Checking for operators returning by reference ==="
rg -nP 'Coord3D&\s+operator(?:=|\+=|/=)\s*\(' src/MoonBase/Coord3D.h || echo "No matches found"

echo ""
echo "=== Full context around line 60-77 ==="
sed -n '60,77p' src/MoonBase/Coord3D.h

Repository: MoonModules/MoonLight

Length of output: 725


Return references from assignment-style operators.

All three assignment operators (operator=, operator+=, operator/=) at lines 60, 66, and 72 return by value and take parameters by value, causing unnecessary copies and breaking expected C++ chaining semantics.

♻️ Proposed fix
-  Coord3D operator=(const Coord3D rhs) {
+  Coord3D& operator=(const Coord3D& rhs) {
     x = rhs.x;
     y = rhs.y;
     z = rhs.z;
     return *this;
   }
-  Coord3D operator+=(const Coord3D rhs) {
+  Coord3D& operator+=(const Coord3D& rhs) {
     x += rhs.x;
     y += rhs.y;
     z += rhs.z;
     return *this;
   }
-  Coord3D operator/=(const Coord3D rhs) {
+  Coord3D& operator/=(const Coord3D& rhs) {
     if (rhs.x) x /= rhs.x;
     if (rhs.y) y /= rhs.y;
     if (rhs.z) z /= rhs.z;
     return *this;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Coord3D operator=(const Coord3D rhs) {
x = rhs.x;
y = rhs.y;
z = rhs.z;
return *this;
}
Coord3D operator+=(const Coord3D rhs) {
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
Coord3D operator/=(const Coord3D rhs) {
if (rhs.x) x /= rhs.x;
if (rhs.y) y /= rhs.y;
if (rhs.z) z /= rhs.z;
return *this;
}
Coord3D& operator=(const Coord3D& rhs) {
x = rhs.x;
y = rhs.y;
z = rhs.z;
return *this;
}
Coord3D& operator+=(const Coord3D& rhs) {
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
Coord3D& operator/=(const Coord3D& rhs) {
if (rhs.x) x /= rhs.x;
if (rhs.y) y /= rhs.y;
if (rhs.z) z /= rhs.z;
return *this;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/Coord3D.h` around lines 60 - 77, Change the assignment-style
operators in class Coord3D to take their RHS by const reference and return a
reference to *this so chaining and copies are corrected: update operator=(const
Coord3D rhs) to operator=(const Coord3D& rhs) and return Coord3D&; update
operator+=(const Coord3D rhs) to operator+=(const Coord3D& rhs) and return
Coord3D&; update operator/=(const Coord3D rhs) to operator/=(const Coord3D& rhs)
and return Coord3D&; keep the existing member-wise operations but ensure the
functions return *this as a reference.

Lint
===
Add iwyu to lint.yml
Add frondend unit tests using Vitest
Add moonbase_utilities.test.ts

frontend
=======
- Initialize with safe defaults
- vite.config.ts: add rollupOptions

backend
=======
Move utility files to MoonBase/utilities
Extract alloc functions from Utilities.h and move to MemAlloc.h
Coord3D: add by reference (&)
@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai resume

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

✅ Actions performed

Reviews resumed.

@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai, please review da3bd4d

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch details of the specific commit
gh api repos/MoonModules/MoonLight/commits/da3bd4df04b9c9e49222b8384d71bbdd5911814e --jq '{
  sha: .sha,
  message: .commit.message,
  author: .commit.author.name,
  date: .commit.author.date,
  stats: .stats,
  files: [.files[] | {filename: .filename, status: .status, additions: .additions, deletions: .deletions, changes: .changes}]
}'

Length of output: 4486


🏁 Script executed:

#!/bin/bash
# Read key new files from the commit
echo "=== ci/iwyu.py ==="
cat ci/iwyu.py
echo -e "\n\n=== src/MoonBase/utilities/MemAlloc.h ==="
cat src/MoonBase/utilities/MemAlloc.h
echo -e "\n\n=== interface/src/lib/stores/moonbase_utilities.test.ts ==="
cat interface/src/lib/stores/moonbase_utilities.test.ts
echo -e "\n\n=== .github/workflows/lint.yml (relevant section) ==="
cat .github/workflows/lint.yml | head -60

Length of output: 9966


🏁 Script executed:

#!/bin/bash
# Check the utilities reorganization - verify some of the moved files
echo "=== Check Utilities.h to see what includes utilities now ==="
head -30 src/MoonBase/utilities/Utilities.h

echo -e "\n\n=== Check Coord3D.h move ==="
head -20 src/MoonBase/utilities/Coord3D.h

echo -e "\n\n=== Check PureFunctions.h move ==="
head -20 src/MoonBase/utilities/PureFunctions.h

echo -e "\n\n=== Check interface/package.json for Vitest ==="
cat interface/package.json | grep -A5 -B5 "vitest"

echo -e "\n\n=== Check vite.config.ts changes ==="
cat interface/vite.config.ts

Length of output: 4363


🏁 Script executed:

#!/bin/bash
# Check if any files are including the old paths vs new utilities paths
echo "=== Check if any files still reference old utility paths (should be none) ==="
rg -n "include.*MoonBase/(Coord3D|PureFunctions|Char)\.h" --type cpp --type-add 'cpp:*.h' -g '!**/utilities/**'

echo -e "\n\n=== Check files that include the new utilities path ==="
rg -n "include.*utilities/(Coord3D|PureFunctions|Char|MemAlloc)\.h" --type cpp --type-add 'cpp:*.h' | head -20

echo -e "\n\n=== Check CLAUDE.md updates ==="
cat CLAUDE.md | grep -A15 "Unit Tests"

Length of output: 1811


@ewowi, excellent refactoring commit! Here's my comprehensive review of da3bd4df:

✅ Highlights

1. IWYU Tool Implementation (ci/iwyu.py)
The IWYU script is professionally designed with proper error handling, timeout protection, and informational-only reporting that exits 0 to never block CI. Key strengths:

  • Intelligently skips ESP32-specific files that can't compile without the toolchain
  • Integrates with PlatformIO to fetch FastLED headers
  • Verbose mode for debugging
  • Clear reporting format

2. Memory Allocation Utilities (MemAlloc.h)
Great separation of concerns extracting allocation functions from Utilities.h:

  • Templates for PSRAM-preferential allocation (allocMB, reallocMB, freeMB)
  • Specialized allocators (VectorRAMAllocator, JsonRAMAllocator)
  • Fallback macros for standalone use (good for testing)
  • Proper tracking of totalAllocatedMB

3. Vitest Integration
Frontend unit tests added using Vitest, with tests in moonbase_utilities.test.ts covering edge cases for initCap and getTimeAgo.

  • Tests cover edge cases (non-string input, various time ranges)
  • Proper npm run test script added
  • Integrated into CI workflow

4. File Reorganization
Clean migration to src/MoonBase/utilities/ subdirectory:

  • ✅ No residual old include paths found
  • Proper header dependencies updated
  • Logical grouping: Coord3D.h, PureFunctions.h, Char.h, MemAlloc.h, Utilities.h

5. Enhanced Documentation
CLAUDE.md updated with clear guidance on when to add unit tests for both backend (PlatformIO native) and frontend (Vitest). Particularly good:

  • Focus on pure, self-contained functions
  • Clear scope boundaries (no ESP32 hardware dependencies for native tests)

🔍 Minor Observations

  1. Frontend Type Safety Improvements: Multiple Svelte files now have better TypeScript typing with concrete types replacing any, which is excellent practice.

  2. Lint Workflow: The IWYU job correctly uses continue-on-error: true so findings are informational only—good architectural decision.

  3. Build Configuration: The vite.config.ts shows build.minify: 'terser' and other build settings. Your commit message mentions "rollupOptions" but I see the build configuration enhancements—perhaps that's what you meant?

🎯 Overall Assessment

This is a well-executed refactoring that improves:

  • Code organization (utilities subdirectory)
  • Tooling (IWYU, Vitest)
  • Type safety (TypeScript improvements)
  • Test coverage (both frontend and backend)
  • Developer documentation

The changes maintain backward compatibility while setting up better foundations for future development. The "informational only" approach for IWYU is particularly wise—it provides value without blocking development flow.

Great work! 🚀


🐰✨
Thoroughly organized and well-tooled!


🧠 Learnings used
Learnt from: ewowi
Repo: MoonModules/MoonLight PR: 0
File: :0-0
Timestamp: 2026-01-26T14:59:22.091Z
Learning: In MoonModules/MoonLight PR `#114`: ModuleDevices deadlock caused by: (1) StatefulService.h line 204-205 bug - after 500ms mutex timeout, continues WITHOUT acquiring mutex, causing race conditions and JSON corruption. (2) receiveUDP() while-loop processes own UDP broadcasts via loopback, creating circular call chain: sendUDP→broadcast→receiveUDP→updateDevices→update→handlers→sendUDP. (3) updateDevices() holds _accessMutex during expensive sort/JSON operations with 15 devices, can exceed 500ms with nested calls. Solution: Fix StatefulService to wait forever or reboot on timeout; skip own broadcasts by checking message.name == ownHostname; optimize updateDevices to sort outside mutex.

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/interface/src/routes/moonbase/**/*.{ts,tsx,svelte,js} : ESLint only lints MoonLight-specific files in `src/routes/moonbase/`, `src/lib/components/moonbase/`, and moonbase stores/types — not upstream paths

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/*.{cpp,h,ts,tsx,js} : Mark upstream sveltekit changes with `// 🌙` (moonbase additions) or `// 💫` (moonlight additions) in source code and consider whether changes could be submitted as a PR to upstream

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (2)
interface/src/lib/stores/socket.ts (1)

51-67: ⚠️ Potential issue | 🟠 Major

Binary decode failure bypasses new monitor fallback path

At Line 58, the return inside catch exits before Lines 63-64 run. So binary frames that fail decode are emitted as errors and never routed to 'monitor', which conflicts with the new fallback intent at Line 61.

Suggested fix
 			try {
 				payload = binary ? msgpack.decode(new Uint8Array(payload)) : JSON.parse(payload);
 			} catch (error) {
-				console.error('[WebSocket] Decode error:', error); // 🌙
-				listeners.get('error')?.forEach((listener) => {
-					listener(error);
-				});
+				if (binary) {
+					listeners.get('monitor')?.forEach((listener) => {
+						listener(new Uint8Array(message.data));
+					});
+					return;
+				}
+				console.error('[WebSocket] Decode error:', error); // 🌙
+				listeners.get('error')?.forEach((listener) => {
+					listener(error);
+				});
 				return;
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/lib/stores/socket.ts` around lines 51 - 67, The try/catch in
the message handler currently returns on decode errors, preventing the
binary-fallback monitor path from running; instead, on decode failure you should
preserve the binary flag and avoid early return so that when payload is falsy or
non-object the existing check (binary -> listeners.get('monitor')...) runs;
modify the error handling around msgpack.decode/JSON.parse in the handler (the
payload variable and binary flag in this function) to call
listeners.get('error') for the decode error but do not return—allow execution to
reach the subsequent non-object check and route raw Uint8Array data to the
'monitor' listeners when binary is true.
src/MoonBase/utilities/Utilities.cpp (1)

114-116: ⚠️ Potential issue | 🔴 Critical

Fix missing moonmanpng.h include to unblock CI.

Line 115 is currently breaking PlatformIO CI (moonmanpng.h: No such file or directory). Please guard or correct the include path so esp32-s3-atoms3r builds reliably.

🔧 Proposed resilient include fix
 `#if` USE_M5UNIFIED
-  `#include` "moonmanpng.h"
+  `#if` __has_include("moonmanpng.h")
+    `#include` "moonmanpng.h"
+  `#elif` __has_include("../moonmanpng.h")
+    `#include` "../moonmanpng.h"
+  `#else`
+    // Fallback to keep builds working when the generated asset header is absent.
+    unsigned char moonmanpng[] = {0};
+    unsigned int moonmanpng_len = 0;
+  `#endif`
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/Utilities.cpp` around lines 114 - 116, The include for
"moonmanpng.h" in Utilities.cpp causes CI failures when the file is absent; fix
by guarding it with a compile-time presence check: replace the plain `#if`
USE_M5UNIFIED / `#include` "moonmanpng.h" block with a conditional that uses
defined(USE_M5UNIFIED) && __has_include("moonmanpng.h") (or an alternate known
path) so the compiler only includes moonmanpng.h when USE_M5UNIFIED is enabled
and the header is actually available; reference the existing symbols
USE_M5UNIFIED and the header name moonmanpng.h when making the change.
🧹 Nitpick comments (4)
ci/iwyu.py (1)

44-54: Optional: Use iterable unpacking for cleaner command construction.

The subprocess security warning (S603) is a false positive here since paths come from controlled local rglob() and flags are hardcoded constants.

✨ Optional style improvement using unpacking
 def run_iwyu(path: Path) -> tuple[bool, str]:
     """Run IWYU on one file. Returns (compiled_ok, iwyu_suggestions)."""
-    cmd = ["include-what-you-use"] + INCLUDE_FLAGS + [str(path)]
+    cmd = ["include-what-you-use", *INCLUDE_FLAGS, str(path)]
     try:
         result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/iwyu.py` around lines 44 - 54, The command list construction in run_iwyu
currently uses concatenation; replace it with iterable unpacking for clarity by
building cmd as ["include-what-you-use", *INCLUDE_FLAGS, str(path)] (keep use of
subprocess.run, capture_output, text, timeout and the existing return behavior),
referencing run_iwyu, INCLUDE_FLAGS and path to locate the code; no other logic
changes needed.
misc/upstream-prs/001-lint-fixes.md (1)

125-130: LGTM! Clear and consistent Vitest documentation.

The Vitest documentation follows the established pattern and correctly scopes testing to MoonLight-specific files, which aligns with the project's approach of minimizing changes to upstream code. The run commands are clearly documented alongside Prettier and ESLint.

Optional suggestion: The specific test count on Line 129 ("9 tests") may become stale as tests are added or removed. Consider either:

  • Removing the count and keeping just the scope: "Current test coverage: moonbase_utilities.test.ts covers initCap and getTimeAgo."
  • Or accepting that this will need periodic updates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@misc/upstream-prs/001-lint-fixes.md` around lines 125 - 130, The
documentation line in misc/upstream-prs/001-lint-fixes.md currently hard-codes
"9 tests" which will become stale; update the sentence in the block referencing
moonbase_utilities.test.ts to remove the numeric test count and instead state
that moonbase_utilities.test.ts covers the functions initCap and getTimeAgo (or
alternately make the count dynamic), so edit the line containing "Current test
coverage: `moonbase_utilities.test.ts` — 9 tests for `initCap` and `getTimeAgo`"
to a non-count form like "Current test coverage: `moonbase_utilities.test.ts`
covers `initCap` and `getTimeAgo`."
interface/src/routes/wifi/sta/EditNetwork.svelte (1)

37-46: Deduplicate the default KnownNetworkItem object to avoid drift.

The same default literal is declared twice (prop fallback and $state init), which is easy to desync later.

♻️ Proposed refactor
+const EMPTY_NETWORK_EDITABLE: KnownNetworkItem = {
+	ssid: '',
+	password: '',
+	static_ip_config: false,
+	local_ip: undefined,
+	subnet_mask: undefined,
+	gateway_ip: undefined,
+	dns_ip_1: undefined,
+	dns_ip_2: undefined
+};

 let {
 	isOpen,
 	title,
-	networkEditable: _networkEditable = {
-		ssid: '',
-		password: '',
-		static_ip_config: false,
-		local_ip: undefined,
-		subnet_mask: undefined,
-		gateway_ip: undefined,
-		dns_ip_1: undefined,
-		dns_ip_2: undefined
-	} as KnownNetworkItem,
+	networkEditable: _networkEditable = EMPTY_NETWORK_EDITABLE,
 	onSaveNetwork
 }: Props = $props();

-let networkEditable: KnownNetworkItem = $state({
-	ssid: '',
-	password: '',
-	static_ip_config: false,
-	local_ip: undefined,
-	subnet_mask: undefined,
-	gateway_ip: undefined,
-	dns_ip_1: undefined,
-	dns_ip_2: undefined
-});
+let networkEditable: KnownNetworkItem = $state({ ...EMPTY_NETWORK_EDITABLE });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/wifi/sta/EditNetwork.svelte` around lines 37 - 46,
Duplicate default KnownNetworkItem literal is declared twice (used as the prop
fallback and in the networkEditable $state init); extract that default into a
single constant (e.g., DEFAULT_KNOWN_NETWORK or defaultKnownNetwork) and use
that constant both where the prop fallback is defined and when initializing
networkEditable so the default shape is maintained in one place; update
references to KnownNetworkItem, networkEditable, and any prop that used the
inline literal to consume the new constant.
src/MoonBase/utilities/Coord3D.h (1)

66-83: Return references from mutating operators.

operator=, operator+=, and operator/= currently return Coord3D by value. Returning Coord3D& avoids unnecessary copies and matches standard C++ operator semantics.

♻️ Proposed signature fix
-  Coord3D operator=(const Coord3D& rhs) {
+  Coord3D& operator=(const Coord3D& rhs) {
     x = rhs.x;
     y = rhs.y;
     z = rhs.z;
     return *this;
   }
-  Coord3D operator+=(const Coord3D& rhs) {
+  Coord3D& operator+=(const Coord3D& rhs) {
     x += rhs.x;
     y += rhs.y;
     z += rhs.z;
     return *this;
   }
-  Coord3D operator/=(const Coord3D& rhs) {
+  Coord3D& operator/=(const Coord3D& rhs) {
     if (rhs.x) x /= rhs.x;
     if (rhs.y) y /= rhs.y;
     if (rhs.z) z /= rhs.z;
     return *this;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/Coord3D.h` around lines 66 - 83, The mutating
operators operator=, operator+=, and operator/= in Coord3D should return
Coord3D& instead of Coord3D to avoid unnecessary copies and follow C++
conventions; update the signatures of Coord3D::operator=(const Coord3D&),
Coord3D::operator+=(const Coord3D&), and Coord3D::operator/=(const Coord3D&) to
return Coord3D&, ensure each returns *this by reference, and (optionally) add a
self-assignment guard in operator= if desired.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@interface/src/lib/stores/socket.ts`:
- Around line 61-67: The change modifies upstream frontend file socket.ts
(inside the WebSocket message handler) which violates the frontend allowlist;
revert the edit in the handler that inspects payload/binary and sending monitor
data (the block referencing payload, binary, listeners.get('monitor'),
message.data and Uint8Array) and instead implement the desired behavior in an
allowed location (e.g., export a helper from
src/lib/stores/moonbase_utilities.ts or a component under
src/lib/components/moonbase/) that accepts the raw message and performs the
binary check and dispatch to listeners, then call that helper from the approved
frontend file; ensure the original socket.ts handler is restored to its prior
state and the new logic references the same symbols (listeners, monitor,
payload, binary, message.data) but lives only in an allowed path.
- Line 64: Convert all expression-bodied forEach callbacks in this file (the
calls on the Map named listeners such as listeners.get('monitor')?.forEach(...)
and the similar calls at the other locations) to use block-statement callbacks
so they don't implicitly return a value; for each occurrence (lines with
listeners.get('...')?.forEach((listener) => ...)) replace the arrow expression
form with a block body: (listener) => { listener(...); } ensuring the argument
construction (e.g., new Uint8Array(message.data) or whatever the original
expression was) is executed inside the block and no value is returned. Apply
this change consistently to the four instances referenced (the callbacks that
invoke listener) and keep the surrounding logic and variable names (listeners,
listener, message) intact.

In `@interface/src/routes/ethernet/Ethernet.svelte`:
- Around line 50-62: You modified upstream frontend route Ethernet.svelte
(symbols: ethernetEditable, strEthernetEditable, getEthernetSettings); move
these edits out of interface/src/routes/ethernet/Ethernet.svelte into an
approved MoonLight path or submit them upstream: either refactor the state
initialization and string-state logic into an allowed file (e.g., a new module
under src/lib/components/moonbase/ or src/lib/stores/moonbase_utilities.ts) and
import it into the route, or revert this file and open a PR against the upstream
repo with the intended changes; ensure references to ethernetEditable,
strEthernetEditable and getEthernetSettings are updated to use the new module or
the upstream change.
- Around line 61-64: The dirty flag is true by default because
strEthernetEditable is initialized to '' and compared to a JSON snapshot; change
initialization so strEthernetEditable is set to the serialized initial snapshot
instead of '', or assign/update strEthernetEditable after getEthernetSettings()
finishes loading (e.g., set strEthernetEditable =
JSON.stringify(ethernetEditable, emptyStringToUndefinedReplacer) once settings
are loaded) so the comparison in isSettingsDirty (which uses ethernetEditable
and strEthernetEditable) only becomes true after a real user change.

In `@interface/src/routes/user/EditUser.svelte`:
- Around line 31-41: You modified upstream component EditUser.svelte (variables
user, errorUsername, usernameEditable and the $effect.pre initializer) under
interface/src/routes/user/, which is not allowed; instead create a
MoonLight-specific variant or wrapper in an allowed path (e.g.,
src/routes/moonbase/ or src/lib/components/moonbase/) and move the customization
there: implement the same state initialization and the $effect.pre hook inside a
new Moonbase EditUser wrapper component (or expose the behavior from
src/lib/stores/moonbase_utilities.ts and import it), then have the upstream
EditUser import/use the Moonbase wrapper only when running MoonLight (or
register the Moonbase route) so no direct edits remain in
interface/src/routes/user/EditUser.svelte.

In `@interface/src/routes/wifi/sta/EditNetwork.svelte`:
- Around line 36-55: The change modifies an upstream frontend file that is
off-limits; move the edits out of
interface/src/routes/wifi/sta/EditNetwork.svelte by reverting this file and
applying equivalent updates in an approved MoonLight-specific location (e.g.,
under src/routes/moonbase/ or src/lib/components/moonbase/) or create a
documented exception; specifically, restore the original networkEditable and
staticIPConfig initialization and $effect.pre usage in EditNetwork.svelte, then
implement the intended behavior (initializing KnownNetworkItem, the $state
wrapper, and staticIPConfig assignment) in a MoonLight-managed component or
utility and wire it into the app, or add a formal exception note in the repo
policy before merging.
- Around line 51-55: The inline comment claiming "$effect.pre" is a Svelte 5.36+
feature is incorrect; update the comment above the $effect.pre block (where
networkEditable is set from _networkEditable and staticIPConfig from
_networkEditable.static_ip_config) to either remove the version claim or reword
it to say "$effect.pre is available in Svelte 5 (used here to load from prop
before first render)", leaving the code ($effect.pre(() => { networkEditable = {
..._networkEditable }; staticIPConfig = _networkEditable.static_ip_config; }))
unchanged.

In `@platformio.ini`:
- Around line 315-331: The native environment section [env:native] is missing a
test_framework declaration and includes platform-specific libdeps paths; add
test_framework = doctest under [env:native] and remove the hardcoded esp32-s3
include paths (-I.pio/libdeps/esp32-s3/FastLED/src and
-I.pio/libdeps/esp32-s3/FastLED/tests) from build_flags so the native config is
platform-independent; ensure framework, extra_scripts, lib_deps remain empty as
intended and that any test includes rely on the declared test_framework not on
esp32-specific libdeps.

In `@src/MoonBase/utilities/MemAlloc.h`:
- Around line 104-118: freeMBObject currently frees placement-new memory without
running destructors; change freeMBObject(T*& obj) to check obj for nullptr,
explicitly call the destructor (obj->~T()), then call freeMB(obj, "object") and
set obj to nullptr so non-trivial types are torn down correctly; this keeps
allocMBObject and freeMB usage but ensures placement-new objects are properly
destroyed before raw memory is released.
- Line 31: reallocMB() and reallocMB2() currently perform reallocations without
updating the global counter totalAllocatedMB, causing accounting drift; change
both functions to compute the old allocation size in MB (e.g., oldSizeMB) before
calling realloc, call realloc, and only on successful reallocation compute
newSizeMB and apply totalAllocatedMB += (newSizeMB - oldSizeMB) so the counter
increases or decreases by the size delta (mirror the behaviour of
allocMB/freeMB); ensure the adjustment happens after verifying the returned
pointer is non-null and use the same MB rounding logic as allocMB/freeMB.
- Around line 14-16: The header is missing direct includes for APIs it uses; add
`#include` <utility> to provide std::forward used in the template forwarding
functions and add `#include` <esp_heap_caps.h> to provide heap_caps_calloc_prefer,
heap_caps_realloc_prefer, heap_caps_get_allocated_size, heap_caps_free and the
MALLOC_CAP_SPIRAM / MALLOC_CAP_DEFAULT macros so MemAlloc.h does not rely on
include order; update the top of MemAlloc.h to include these two headers so
functions like the template that uses std::forward and the memory functions
compile reliably.
- Around line 37-39: reallocMB and reallocMB2 perform "n * sizeof(T)" without
guarding against integer overflow; before calling heap_caps_realloc_prefer or
similar, compute the byte count safely (e.g., size_t bytes = n * sizeof(T)) and
validate it does not wrap by checking n <= SIZE_MAX / sizeof(T) (handle n==0
appropriately), then use that validated byte count for the allocation; follow
the pattern used in allocMB (separate count/elem size) or explicitly pass the
checked byte size to avoid truncated allocations and potential buffer overruns.

In `@test/test_native/test_utilities.cpp`:
- Line 14: The native test environment is missing the doctest framework setting
required for tests that include doctest.h and use doctest macros; open
platformio.ini and in the [env:native] section (after platform = native) add the
directive test_framework = doctest so the native environment uses the doctest
framework for tests referenced by test/test_native/test_utilities.cpp and any
code calling doctest::Approx() or other doctest symbols.

---

Outside diff comments:
In `@interface/src/lib/stores/socket.ts`:
- Around line 51-67: The try/catch in the message handler currently returns on
decode errors, preventing the binary-fallback monitor path from running;
instead, on decode failure you should preserve the binary flag and avoid early
return so that when payload is falsy or non-object the existing check (binary ->
listeners.get('monitor')...) runs; modify the error handling around
msgpack.decode/JSON.parse in the handler (the payload variable and binary flag
in this function) to call listeners.get('error') for the decode error but do not
return—allow execution to reach the subsequent non-object check and route raw
Uint8Array data to the 'monitor' listeners when binary is true.

In `@src/MoonBase/utilities/Utilities.cpp`:
- Around line 114-116: The include for "moonmanpng.h" in Utilities.cpp causes CI
failures when the file is absent; fix by guarding it with a compile-time
presence check: replace the plain `#if` USE_M5UNIFIED / `#include` "moonmanpng.h"
block with a conditional that uses defined(USE_M5UNIFIED) &&
__has_include("moonmanpng.h") (or an alternate known path) so the compiler only
includes moonmanpng.h when USE_M5UNIFIED is enabled and the header is actually
available; reference the existing symbols USE_M5UNIFIED and the header name
moonmanpng.h when making the change.

---

Nitpick comments:
In `@ci/iwyu.py`:
- Around line 44-54: The command list construction in run_iwyu currently uses
concatenation; replace it with iterable unpacking for clarity by building cmd as
["include-what-you-use", *INCLUDE_FLAGS, str(path)] (keep use of subprocess.run,
capture_output, text, timeout and the existing return behavior), referencing
run_iwyu, INCLUDE_FLAGS and path to locate the code; no other logic changes
needed.

In `@interface/src/routes/wifi/sta/EditNetwork.svelte`:
- Around line 37-46: Duplicate default KnownNetworkItem literal is declared
twice (used as the prop fallback and in the networkEditable $state init);
extract that default into a single constant (e.g., DEFAULT_KNOWN_NETWORK or
defaultKnownNetwork) and use that constant both where the prop fallback is
defined and when initializing networkEditable so the default shape is maintained
in one place; update references to KnownNetworkItem, networkEditable, and any
prop that used the inline literal to consume the new constant.

In `@misc/upstream-prs/001-lint-fixes.md`:
- Around line 125-130: The documentation line in
misc/upstream-prs/001-lint-fixes.md currently hard-codes "9 tests" which will
become stale; update the sentence in the block referencing
moonbase_utilities.test.ts to remove the numeric test count and instead state
that moonbase_utilities.test.ts covers the functions initCap and getTimeAgo (or
alternately make the count dynamic), so edit the line containing "Current test
coverage: `moonbase_utilities.test.ts` — 9 tests for `initCap` and `getTimeAgo`"
to a non-count form like "Current test coverage: `moonbase_utilities.test.ts`
covers `initCap` and `getTimeAgo`."

In `@src/MoonBase/utilities/Coord3D.h`:
- Around line 66-83: The mutating operators operator=, operator+=, and
operator/= in Coord3D should return Coord3D& instead of Coord3D to avoid
unnecessary copies and follow C++ conventions; update the signatures of
Coord3D::operator=(const Coord3D&), Coord3D::operator+=(const Coord3D&), and
Coord3D::operator/=(const Coord3D&) to return Coord3D&, ensure each returns
*this by reference, and (optionally) add a self-assignment guard in operator= if
desired.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93ef793 and da3bd4d.

⛔ Files ignored due to path filters (1)
  • interface/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (34)
  • .claude/settings.json
  • .github/workflows/lint.yml
  • CLAUDE.md
  • ci/iwyu.py
  • interface/package.json
  • interface/src/lib/components/moonbase/EditRowWidget.svelte
  • interface/src/lib/components/moonbase/FileEditWidget.svelte
  • interface/src/lib/components/moonbase/RowRenderer.svelte
  • interface/src/lib/stores/moonbase_utilities.test.ts
  • interface/src/lib/stores/socket.ts
  • interface/src/routes/ethernet/Ethernet.svelte
  • interface/src/routes/user/EditUser.svelte
  • interface/src/routes/wifi/sta/EditNetwork.svelte
  • interface/vite.config.ts
  • lib/framework/WWWData.h
  • misc/upstream-prs/001-lint-fixes.md
  • platformio.ini
  • src/MoonBase/Module.h
  • src/MoonBase/Modules/FileManager.cpp
  • src/MoonBase/Modules/ModuleDevices.h
  • src/MoonBase/utilities/Char.h
  • src/MoonBase/utilities/Coord3D.h
  • src/MoonBase/utilities/MemAlloc.h
  • src/MoonBase/utilities/PureFunctions.h
  • src/MoonBase/utilities/Utilities.cpp
  • src/MoonBase/utilities/Utilities.h
  • src/MoonBase/utilities/pal.h
  • src/MoonBase/utilities/pal_espidf.cpp
  • src/MoonLight/Layers/PhysicalLayer.cpp
  • src/MoonLight/Layers/PhysicalLayer.h
  • src/MoonLight/Modules/ModuleLightsControl.h
  • src/MoonLight/Nodes/Drivers/parlio.cpp
  • src/MoonLight/Nodes/Effects/E_MoonLight.h
  • test/test_native/test_utilities.cpp
💤 Files with no reviewable changes (1)
  • interface/vite.config.ts
✅ Files skipped from review due to trivial changes (3)
  • src/MoonBase/Module.h
  • src/MoonLight/Modules/ModuleLightsControl.h
  • src/MoonLight/Layers/PhysicalLayer.h
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/lint.yml
  • src/MoonBase/Modules/FileManager.cpp
  • interface/src/lib/components/moonbase/FileEditWidget.svelte
  • src/MoonLight/Nodes/Effects/E_MoonLight.h

if (!payload || typeof payload !== 'object') {
console.error('[WebSocket] Invalid payload:', payload);
if (binary) {
listeners.get('monitor')?.forEach((listener) => listener(new Uint8Array(message.data)));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read content around line 64
if [ -f "interface/src/lib/stores/socket.ts" ]; then
  echo "=== File found ==="
  wc -l interface/src/lib/stores/socket.ts
  echo -e "\n=== Content around line 64 ==="
  sed -n '55,75p' interface/src/lib/stores/socket.ts | cat -n
else
  echo "File not found"
fi

Repository: MoonModules/MoonLight

Length of output: 875


🏁 Script executed:

# Search for forEach patterns in the file to understand the context
echo "=== All forEach patterns in socket.ts ==="
rg -n "\.forEach\(" interface/src/lib/stores/socket.ts -A 1

Repository: MoonModules/MoonLight

Length of output: 917


🏁 Script executed:

# Check if socket.ts is actually modified in this PR or if it's upstream
echo "=== Git status of socket.ts ==="
git status interface/src/lib/stores/socket.ts 2>/dev/null || echo "Not in git or file status unavailable"
echo -e "\n=== Check file history ==="
git log --oneline -5 interface/src/lib/stores/socket.ts 2>/dev/null | head -5

Repository: MoonModules/MoonLight

Length of output: 262


🏁 Script executed:

# Check for Biome configuration to understand the lint rule scope
echo "=== Looking for Biome configuration ==="
find . -name "biome.json*" -o -name ".biomeignore" 2>/dev/null | head -10

# Check current Biome configuration
if [ -f "biome.json" ]; then
  echo -e "\n=== biome.json content ==="
  cat biome.json
fi

Repository: MoonModules/MoonLight

Length of output: 1920


🏁 Script executed:

# Check if socket.ts is marked with upstream markers
echo "=== Checking for upstream markers in socket.ts ==="
rg -n "🌙|💫" interface/src/lib/stores/socket.ts

# Also check the specific lines with expression-bodied forEach
echo -e "\n=== All forEach expression-body instances ==="
rg -n "\.forEach\(\(\w+\)\s*=>\s*\w+\(" interface/src/lib/stores/socket.ts

Repository: MoonModules/MoonLight

Length of output: 732


🏁 Script executed:

# Final check: verify if socket.ts was in original repo or is a new MoonBase addition
echo "=== Check git history depth ==="
git log --oneline interface/src/lib/stores/socket.ts | wc -l

echo -e "\n=== First commit mentioning socket.ts ==="
git log --oneline --reverse interface/src/lib/stores/socket.ts | head -1

Repository: MoonModules/MoonLight

Length of output: 213


Fix all forEach callbacks with expression bodies to use block statements

Line 64 uses an expression-bodied callback that returns a value, which triggers the Biome lint error (useIterableCallbackReturn). However, this same pattern appears on lines 27, 38, and 49 and should be fixed consistently across the file.

Additionally, per coding guidelines, interface/src/lib/stores/socket.ts is not in the approved list of exceptions for upstream file modifications (allowed: src/routes/moonbase/, src/lib/components/moonbase/, src/lib/stores/moonbase_utilities.ts, src/lib/types/moonbase_models.ts, vite-plugin-littlefs.ts). Consider whether this file should be relocated or consolidated with MoonBase utilities.

Suggested fix for all instances

Lines 27, 38, 49, 64 should all be converted from:

-listeners.get('key')?.forEach((listener) => listener(value));
+listeners.get('key')?.forEach((listener) => {
+  listener(value);
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
listeners.get('monitor')?.forEach((listener) => listener(new Uint8Array(message.data)));
listeners.get('monitor')?.forEach((listener) => {
listener(new Uint8Array(message.data));
});
🧰 Tools
🪛 Biome (2.4.4)

[error] 64-64: This callback passed to forEach() iterable method should not return a value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/lib/stores/socket.ts` at line 64, Convert all expression-bodied
forEach callbacks in this file (the calls on the Map named listeners such as
listeners.get('monitor')?.forEach(...) and the similar calls at the other
locations) to use block-statement callbacks so they don't implicitly return a
value; for each occurrence (lines with listeners.get('...')?.forEach((listener)
=> ...)) replace the arrow expression form with a block body: (listener) => {
listener(...); } ensuring the argument construction (e.g., new
Uint8Array(message.data) or whatever the original expression was) is executed
inside the block and no value is returned. Apply this change consistently to the
four instances referenced (the callbacks that invoke listener) and keep the
surrounding logic and variable names (listeners, listener, message) intact.

Comment on lines +14 to +16
#include "ArduinoJson.h"
#include <cstddef>

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading MemAlloc.h includes and initial lines ==="
head -20 src/MoonBase/utilities/MemAlloc.h

echo -e "\n=== Checking for std::forward usage ==="
rg -n 'std::forward' src/MoonBase/utilities/MemAlloc.h || echo "Not found"

echo -e "\n=== Checking for heap_caps_* and MALLOC_CAP_* usage ==="
rg -n 'heap_caps_|MALLOC_CAP_' src/MoonBase/utilities/MemAlloc.h || echo "Not found"

echo -e "\n=== Full file includes section ==="
head -30 src/MoonBase/utilities/MemAlloc.h

Repository: MoonModules/MoonLight

Length of output: 3522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Build metadata search ==="
fd -HI 'platformio\.ini|CMakeLists\.txt|idf_component\.yml|library\.json|library\.properties' . | while read -r f; do
  if rg -q 'ArduinoJson|esp_heap_caps|heap_caps' "$f" 2>/dev/null; then
    echo "--- Found in: $f ---"
    rg -n -C2 'ArduinoJson|esp_heap_caps|heap_caps' "$f"
  fi
done

echo -e "\n=== Check for any existing includes of esp_heap_caps or utility in codebase ==="
rg -l 'include.*esp_heap_caps|include.*utility' src/ | head -10

Repository: MoonModules/MoonLight

Length of output: 572


Add explicit headers for APIs used in this header.

Line 108 uses std::forward, and lines 38, 40, 49, 59, 73, 75 use heap_caps_calloc_prefer, heap_caps_realloc_prefer, heap_caps_get_allocated_size, and heap_caps_free with MALLOC_CAP_SPIRAM and MALLOC_CAP_DEFAULT macros, but <utility> and <esp_heap_caps.h> are not explicitly included. This makes the header brittle to include order and violates C++ header best practices.

🔧 Suggested include fix
`#include` "ArduinoJson.h"
`#include` <cstddef>
+#include <esp_heap_caps.h>
+#include <utility>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include "ArduinoJson.h"
#include <cstddef>
`#include` "ArduinoJson.h"
`#include` <cstddef>
`#include` <esp_heap_caps.h>
`#include` <utility>
🧰 Tools
🪛 Clang (14.0.6)

[error] 14-14: 'ArduinoJson.h' file not found

(clang-diagnostic-error)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/MemAlloc.h` around lines 14 - 16, The header is
missing direct includes for APIs it uses; add `#include` <utility> to provide
std::forward used in the template forwarding functions and add `#include`
<esp_heap_caps.h> to provide heap_caps_calloc_prefer, heap_caps_realloc_prefer,
heap_caps_get_allocated_size, heap_caps_free and the MALLOC_CAP_SPIRAM /
MALLOC_CAP_DEFAULT macros so MemAlloc.h does not rely on include order; update
the top of MemAlloc.h to include these two headers so functions like the
template that uses std::forward and the memory functions compile reliably.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
interface/src/lib/stores/socket.ts (1)

54-75: ⚠️ Potential issue | 🟠 Major

Upstream frontend allowlist is still being violated in this file

These edits are in interface/src/lib/stores/socket.ts, which is outside the permitted MoonLight frontend exception paths. Please move this behavior to an allowed MoonLight path and keep upstream file modifications minimal.

As per coding guidelines "**/interface/src/**/*.{ts,tsx,svelte,js}: Minimize changes to upstream files in the frontend — avoid modifying files in /interface/src/ except for src/routes/moonbase/, src/lib/components/moonbase/, src/lib/stores/moonbase_utilities.ts, src/lib/types/moonbase_models.ts, and vite-plugin-littlefs.ts".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/lib/stores/socket.ts` around lines 54 - 75, Revert the upstream
edits in the socket handler so you no longer add special-case monitor/binary
routing and extra console.error calls directly in the socket file; instead
create a helper in the allowed MoonLight area (e.g., a new function like
handleRawMonitorData or routeMonitorMessage) that encapsulates the logic
checking binary, converting to new Uint8Array(message.data), and emitting to
listeners.get('monitor') or listeners.get('error'), and then call that helper
from the socket code where the payload decode/error and the check (!payload ||
typeof payload !== 'object') happen; keep the socket file minimal (emit
listeners.get('error') on decode failure using the existing error object and
only delegate binary/raw monitor routing to the new MoonLight helper so the
upstream file changes are minimal.
src/MoonBase/utilities/MemAlloc.h (3)

14-17: ⚠️ Potential issue | 🟠 Major

Add direct includes for APIs used in this header

Line 39, Line 50, and Line 60 use heap_caps_*/MALLOC_CAP_*, and Line 109 uses std::forward, but the header does not directly include their defining headers. This keeps MemAlloc.h include-order fragile.

Suggested fix
 `#include` <cstddef>
+#include <esp_heap_caps.h>
+#include <utility>
 
 `#include` "ArduinoJson.h"
#!/bin/bash
set -euo pipefail
echo "== API usage in MemAlloc.h =="
rg -n 'std::forward|heap_caps_|MALLOC_CAP_' src/MoonBase/utilities/MemAlloc.h
echo
echo "== Direct includes currently present =="
rg -n '^#include\s*<(utility|esp_heap_caps\.h)>' src/MoonBase/utilities/MemAlloc.h || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/MemAlloc.h` around lines 14 - 17, MemAlloc.h uses
heap_caps_* functions and MALLOC_CAP_* macros and std::forward but doesn't
include their defining headers; update the header to directly include the ESP
heap caps API and the C++ utility header by adding `#include` <esp_heap_caps.h>
(or the appropriate ESP-IDF header that defines heap_caps_* and MALLOC_CAP_*)
and `#include` <utility> so symbols like heap_caps_malloc, MALLOC_CAP_* and
std::forward are available without relying on include order; ensure these
includes are placed near the top of MemAlloc.h alongside the existing standard
includes to avoid fragile include-order dependencies.

50-60: ⚠️ Potential issue | 🔴 Critical

Guard n * sizeof(T) overflow before heap_caps_realloc_prefer

Line 50 and Line 60 multiply n * sizeof(T) without a bounds check. If it wraps, allocation can be smaller than expected and callers may overrun the buffer.

Suggested fix
 template <typename T>
 T* reallocMB(T* p, size_t n, const char* name = nullptr) {
+  if (n > SIZE_MAX / sizeof(T)) {
+    EXT_LOGE(MB_TAG, "realloc overflow for %s", name ? name : "x");
+    return nullptr;
+  }
+  const size_t bytes = n * sizeof(T);
-  T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);  // calloc is malloc + memset(0);
+  T* res = (T*)heap_caps_realloc_prefer(p, bytes, 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);
   ...
 }
 
 template <typename T>
 void reallocMB2(T*& p, size_t& pSize, size_t n, const char* name = nullptr) {
+  if (n > SIZE_MAX / sizeof(T)) {
+    EXT_LOGE(MB_TAG, "realloc overflow for %s, keeping old %zu", name ? name : "x", pSize);
+    return;
+  }
+  const size_t bytes = n * sizeof(T);
-  T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);  // calloc is malloc + memset(0);
+  T* res = (T*)heap_caps_realloc_prefer(p, bytes, 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);
   ...
 }
#!/bin/bash
set -euo pipefail
echo "== Realloc byte computations =="
rg -n 'reallocMB|reallocMB2|n \* sizeof\(T\)' src/MoonBase/utilities/MemAlloc.h
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/MemAlloc.h` around lines 50 - 60, The multiplication n
* sizeof(T) in reallocMB and reallocMB2 can overflow; before calling
heap_caps_realloc_prefer compute a safe byte_count and guard it with if
(sizeof(T) != 0 && n > SIZE_MAX / sizeof(T)) (or equivalently compare n to
SIZE_MAX/sizeof(T)), log/handle the error (e.g., EXT_LOGE and return nullptr)
and only call heap_caps_realloc_prefer with the verified byte_count; update any
pSize bookkeeping in reallocMB2 after a successful realloc. Ensure these checks
are added to both reallocMB and reallocMB2 and reference the exact expression "n
* sizeof(T)" in your change.

49-65: ⚠️ Potential issue | 🟠 Major

Fix totalAllocatedMB drift on successful reallocation

Line 41 and Line 74 maintain totalAllocatedMB for alloc/free, but Line 49–56 and Line 59–68 reallocate without applying size deltas. The counter will drift as buffers grow/shrink.

Suggested fix
 template <typename T>
 T* reallocMB(T* p, size_t n, const char* name = nullptr) {
+  const int oldSize = p ? static_cast<int>(heap_caps_get_allocated_size(p)) : 0;
   T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);  // calloc is malloc + memset(0);
   if (res) {
+    const int newSize = static_cast<int>(heap_caps_get_allocated_size(res));
+    totalAllocatedMB += (newSize - oldSize);
     // EXT_LOGD(...)
   } else
     EXT_LOGE(MB_TAG, "heap_caps_malloc for %s of %d x %d not succeeded", name ? name : "x", n, sizeof(T));
   return res;
 }
 
 template <typename T>
 void reallocMB2(T*& p, size_t& pSize, size_t n, const char* name = nullptr) {
+  const int oldSize = p ? static_cast<int>(heap_caps_get_allocated_size(p)) : 0;
   T* res = (T*)heap_caps_realloc_prefer(p, n * sizeof(T), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT);  // calloc is malloc + memset(0);
   if (res) {
+    const int newSize = static_cast<int>(heap_caps_get_allocated_size(res));
+    totalAllocatedMB += (newSize - oldSize);
     // EXT_LOGD(...)
     p = res;
     pSize = n;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/MemAlloc.h` around lines 49 - 65, The realloc
functions (reallocMB and reallocMB2) currently call heap_caps_realloc_prefer
without adjusting the global totalAllocatedMB, causing drift when buffers change
size; fix by computing the old allocated byte size (for reallocMB call
heap_caps_get_allocated_size(p) if p!=nullptr, for reallocMB2 use
pSize*sizeof(T) or heap_caps_get_allocated_size(p) to be safe), perform the
realloc, and on success update totalAllocatedMB by adding (newSizeBytes -
oldSizeBytes) (newSizeBytes = n * sizeof(T)); ensure p and pSize are updated in
reallocMB2 as now, and do not change totalAllocatedMB when realloc fails.
platformio.ini (1)

330-331: ⚠️ Potential issue | 🟠 Major

Remove board-specific FastLED include paths from [env:native].

Line 330 and Line 331 hardcode esp32-s3 libdeps in the native environment, which breaks portability on clean/native-only runners.

🔧 Proposed adjustment
 build_flags =
   -std=c++17
   -Isrc/MoonBase
   -Isrc/MoonBase/utilities
-  -I.pio/libdeps/esp32-s3/FastLED/src
-  -I.pio/libdeps/esp32-s3/FastLED/tests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platformio.ini` around lines 330 - 331, The platformio.ini's [env:native]
contains hardcoded board-specific FastLED include paths
(-I.pio/libdeps/esp32-s3/FastLED/src and -I.pio/libdeps/esp32-s3/FastLED/tests)
which break portability; remove these two include entries from the native
environment (or move them under the esp32-s3 env or replace with an
environment-agnostic lib_deps reference) so [env:native] no longer references
esp32-s3 libdeps.
🧹 Nitpick comments (2)
interface/src/lib/stores/socket.ts (1)

69-75: Deduplicate monitor-frame emission logic

new Uint8Array(message.data) + listeners.get('monitor') is now repeated in multiple branches. Extracting a tiny helper will reduce drift and keep behavior consistent.

♻️ Proposed refactor
 		ws.onmessage = (message) => {
 			resetUnresponsiveCheck();
 			let payload = message.data;
+			const emitMonitor = () => {
+				listeners.get('monitor')?.forEach((listener) => {
+					listener(new Uint8Array(message.data));
+				});
+			};

 			const binary = payload instanceof ArrayBuffer;
 			listeners.get(binary ? 'binary' : 'message')?.forEach((listener) => listener(payload));

 			try {
 				payload = binary ? msgpack.decode(new Uint8Array(payload)) : JSON.parse(payload);
 			} catch (error) {
 				if (binary) {
-					listeners.get('monitor')?.forEach((listener) => {
-						listener(new Uint8Array(message.data));
-					});
+					emitMonitor();
 					return;
 				}
 				console.error('[WebSocket] Decode error:', error); // 🌙
 				listeners.get('error')?.forEach((listener) => {
 					listener(error);
 				});
 				return;
 			}

 			// 🌙 Non-object binary = raw monitor data (not msgpack); route directly and skip destructuring
 			if (!payload || typeof payload !== 'object') {
 				if (binary) {
-					listeners.get('monitor')?.forEach((listener) => {
-						listener(new Uint8Array(message.data));
-					});
+					emitMonitor();
 				} else {
 					console.error('[WebSocket] Invalid payload:', payload);
 				}
 				return;
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/lib/stores/socket.ts` around lines 69 - 75, The monitor-frame
emission (listeners.get('monitor') and new Uint8Array(message.data)) is
duplicated; create a small helper function (e.g., emitMonitorFrame or
emitMonitorPayload) that accepts the raw data or payload, constructs the
Uint8Array from message.data (or uses the provided buffer) and calls
listeners.get('monitor')?.forEach(listener => listener(...)). Replace both
branches so they call this helper instead of repeating new
Uint8Array(message.data) and the listeners.get('monitor') logic, ensuring
identical behavior in all paths.
src/MoonBase/utilities/Coord3D.h (1)

14-20: Replace MIN/MAX macros with std::min/std::max in this header.

These macros leak into the global namespace and create collision risks—CoreDump.cpp already defines its own MIN macro with different semantics, demonstrating the maintenance burden. The macro implementations also risk double-evaluation bugs.

Proposed refactor
+#include <algorithm>
@@
-#ifndef MIN
-  `#define` MIN(a, b) (((a) < (b)) ? (a) : (b))
-#endif
-
-#ifndef MAX
-  `#define` MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
@@
-  Coord3D maximum(const Coord3D& rhs) const { return Coord3D(MAX(x, rhs.x), MAX(y, rhs.y), MAX(z, rhs.z)); }
+  Coord3D maximum(const Coord3D& rhs) const { return Coord3D(std::max(x, rhs.x), std::max(y, rhs.y), std::max(z, rhs.z)); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/MoonBase/utilities/Coord3D.h` around lines 14 - 20, The header defines
global MIN and MAX macros that should be removed; delete the MIN/MAX macro
definitions in Coord3D.h, add `#include` <algorithm>, and update any code in this
header (and callers if needed) to use std::min and std::max (fully qualified)
instead of MIN(...) / MAX(...) to avoid macro collisions and double-evaluation;
search for occurrences of MIN and MAX in functions or inline code in Coord3D.h
and replace them with std::min/std::max (or std::clamp where appropriate) and
ensure <algorithm> is included so the symbols resolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/iwyu.py`:
- Around line 53-54: The except subprocess.TimeoutExpired handler currently
returns (False, "(timeout)") but the reporting code later (the block that maps
failures to "ESP32-toolchain skip" for all non-success results) blindly treats
every non-zero result as an ESP32 skip; change the exception handler to return a
distinct, specific failure status (e.g., False with a code or message like
"timeout" or raise a custom TimeoutError) and update the reporting logic that
inspects the iwyu result to only classify a skip as ESP32-toolchain when a real
ESP32-toolchain indicator is present (rather than any non-success), otherwise
surface timeouts and other errors with their actual messages; locate and modify
the TimeoutExpired except branch and the reporting branch that currently
collapses all failures into an ESP32-toolchain skip to implement this
distinction.

In `@src/MoonBase/utilities/Coord3D.h`:
- Around line 87-90: The distanceSquared function in Coord3D currently
multiplies int values and returns unsigned which can overflow; change
Coord3D::distanceSquared to return uint64_t, compute deltas and their squared
products using widened signed type (e.g., int64_t) and accumulate into uint64_t
to avoid UB, and include <cstdint> if needed; then update all call sites that
assumed an unsigned result (e.g., uses in E_MoonLight where the result was
stored in unsigned variables) to use uint64_t (or a matching wider type) and
adjust any comparisons or casts accordingly.

---

Duplicate comments:
In `@interface/src/lib/stores/socket.ts`:
- Around line 54-75: Revert the upstream edits in the socket handler so you no
longer add special-case monitor/binary routing and extra console.error calls
directly in the socket file; instead create a helper in the allowed MoonLight
area (e.g., a new function like handleRawMonitorData or routeMonitorMessage)
that encapsulates the logic checking binary, converting to new
Uint8Array(message.data), and emitting to listeners.get('monitor') or
listeners.get('error'), and then call that helper from the socket code where the
payload decode/error and the check (!payload || typeof payload !== 'object')
happen; keep the socket file minimal (emit listeners.get('error') on decode
failure using the existing error object and only delegate binary/raw monitor
routing to the new MoonLight helper so the upstream file changes are minimal.

In `@platformio.ini`:
- Around line 330-331: The platformio.ini's [env:native] contains hardcoded
board-specific FastLED include paths (-I.pio/libdeps/esp32-s3/FastLED/src and
-I.pio/libdeps/esp32-s3/FastLED/tests) which break portability; remove these two
include entries from the native environment (or move them under the esp32-s3 env
or replace with an environment-agnostic lib_deps reference) so [env:native] no
longer references esp32-s3 libdeps.

In `@src/MoonBase/utilities/MemAlloc.h`:
- Around line 14-17: MemAlloc.h uses heap_caps_* functions and MALLOC_CAP_*
macros and std::forward but doesn't include their defining headers; update the
header to directly include the ESP heap caps API and the C++ utility header by
adding `#include` <esp_heap_caps.h> (or the appropriate ESP-IDF header that
defines heap_caps_* and MALLOC_CAP_*) and `#include` <utility> so symbols like
heap_caps_malloc, MALLOC_CAP_* and std::forward are available without relying on
include order; ensure these includes are placed near the top of MemAlloc.h
alongside the existing standard includes to avoid fragile include-order
dependencies.
- Around line 50-60: The multiplication n * sizeof(T) in reallocMB and
reallocMB2 can overflow; before calling heap_caps_realloc_prefer compute a safe
byte_count and guard it with if (sizeof(T) != 0 && n > SIZE_MAX / sizeof(T)) (or
equivalently compare n to SIZE_MAX/sizeof(T)), log/handle the error (e.g.,
EXT_LOGE and return nullptr) and only call heap_caps_realloc_prefer with the
verified byte_count; update any pSize bookkeeping in reallocMB2 after a
successful realloc. Ensure these checks are added to both reallocMB and
reallocMB2 and reference the exact expression "n * sizeof(T)" in your change.
- Around line 49-65: The realloc functions (reallocMB and reallocMB2) currently
call heap_caps_realloc_prefer without adjusting the global totalAllocatedMB,
causing drift when buffers change size; fix by computing the old allocated byte
size (for reallocMB call heap_caps_get_allocated_size(p) if p!=nullptr, for
reallocMB2 use pSize*sizeof(T) or heap_caps_get_allocated_size(p) to be safe),
perform the realloc, and on success update totalAllocatedMB by adding
(newSizeBytes - oldSizeBytes) (newSizeBytes = n * sizeof(T)); ensure p and pSize
are updated in reallocMB2 as now, and do not change totalAllocatedMB when
realloc fails.

---

Nitpick comments:
In `@interface/src/lib/stores/socket.ts`:
- Around line 69-75: The monitor-frame emission (listeners.get('monitor') and
new Uint8Array(message.data)) is duplicated; create a small helper function
(e.g., emitMonitorFrame or emitMonitorPayload) that accepts the raw data or
payload, constructs the Uint8Array from message.data (or uses the provided
buffer) and calls listeners.get('monitor')?.forEach(listener => listener(...)).
Replace both branches so they call this helper instead of repeating new
Uint8Array(message.data) and the listeners.get('monitor') logic, ensuring
identical behavior in all paths.

In `@src/MoonBase/utilities/Coord3D.h`:
- Around line 14-20: The header defines global MIN and MAX macros that should be
removed; delete the MIN/MAX macro definitions in Coord3D.h, add `#include`
<algorithm>, and update any code in this header (and callers if needed) to use
std::min and std::max (fully qualified) instead of MIN(...) / MAX(...) to avoid
macro collisions and double-evaluation; search for occurrences of MIN and MAX in
functions or inline code in Coord3D.h and replace them with std::min/std::max
(or std::clamp where appropriate) and ensure <algorithm> is included so the
symbols resolve.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 536621e and 2d58e65.

📒 Files selected for processing (10)
  • .github/workflows/lint.yml
  • ci/iwyu.py
  • interface/src/lib/stores/socket.ts
  • interface/src/routes/wifi/sta/EditNetwork.svelte
  • interface/vite-plugin-littlefs.ts
  • lib/framework/WWWData.h
  • misc/upstream-prs/001-lint-fixes.md
  • platformio.ini
  • src/MoonBase/utilities/Coord3D.h
  • src/MoonBase/utilities/MemAlloc.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • interface/src/routes/wifi/sta/EditNetwork.svelte

pio.ini: fix env:native

frontend
=======
- statusbar: show ethernet and battery conditional
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@interface/src/routes/statusbar.svelte`:
- Around line 134-136: This upstream statusbar.svelte change injects
MoonLight-specific UI (the {`#if` page.data.features.ethernet &&
$telemetry.ethernet.connected} conditional and the PlugConnected element) and
must be removed from the upstream route; extract that conditional UI into a new
MoonLight-only component (e.g., StatusbarMoonlight.svelte) placed in the
MoonLight frontend area, implement the same logic there referencing
$telemetry.ethernet.connected and PlugConnected, and update MoonLight-specific
routes to import and render that component instead; finally revert the changes
in statusbar.svelte to the original upstream state so it contains no
MoonLight-only conditionals.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d58e65 and b16b3b0.

📒 Files selected for processing (4)
  • interface/src/routes/statusbar.svelte
  • lib/framework/WWWData.h
  • platformio.ini
  • src/native_stub.cpp
✅ Files skipped from review due to trivial changes (1)
  • src/native_stub.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • platformio.ini

@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai, I am about to merge this PR, can you do an in depth review of all the changes made in this PR, give a merge advice and provide a list with must have and possible follow up actions before merging this pr and after merging this pr

Please review especially the automated unit tests added: doctest, vitest and IWYU. Has this been setup correctly?
Also, I want to see the results of this test somewhere in the readme page, can you tell how to do that ?

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Examine the CI workflow in detail to verify test setup
echo "=== CI Workflow: lint.yml ==="
cat .github/workflows/lint.yml

echo -e "\n\n=== README.md (checking for badges section) ==="
head -50 README.md

echo -e "\n\n=== PlatformIO.ini native test configuration ==="
grep -A 30 "\[env:native\]" platformio.ini

echo -e "\n\n=== Vitest configuration ==="
cat interface/vitest.config.ts

echo -e "\n\n=== Package.json test script ==="
cat interface/package.json | grep -A2 -B2 "test"

echo -e "\n\n=== Doctest test file structure ==="
ls -la test/test_native/

echo -e "\n\n=== Check if .github/workflows has other workflows ==="
ls -la .github/workflows/

Length of output: 7124


🏁 Script executed:

#!/bin/bash
# Check if there's a native test job in any workflow
echo "=== Searching for native test execution in all workflows ==="
grep -r "pio test" .github/workflows/ || echo "No 'pio test' found in workflows"

echo -e "\n=== Check build.yml for test jobs ==="
cat .github/workflows/build.yml

echo -e "\n=== Verify doctest is properly included ==="
cat test/test_native/test_utilities.cpp | head -20

echo -e "\n=== Check if there's a .claude/settings.json with test hooks ==="
cat .claude/settings.json

echo -e "\n=== Verify cpp_lint.py exists and is executable ==="
ls -la ci/cpp_lint.py
head -30 ci/cpp_lint.py

Length of output: 4120


🏁 Script executed:

#!/bin/bash
# Check if there are any workflow status checks or test result configurations
echo "=== Check if workflows produce test artifacts or reports ==="
grep -r "upload-artifact" .github/workflows/

echo -e "\n=== Check existing badges in README ==="
grep "shields.io\|badge" README.md | head -10

echo -e "\n=== Verify IWYU script exit behavior ==="
tail -20 ci/iwyu.py

echo -e "\n=== Check if there's a test results directory or config ==="
ls -la test/ 2>/dev/null || echo "No test directory listing"

echo -e "\n=== Verify frontend test output format ==="
grep -A5 "test:" interface/vite.config.ts || echo "No test reporter config in vitest"

Length of output: 1730


@ewowi, excellent timing! I've conducted a thorough review of all testing infrastructure. Here's my comprehensive analysis:

🔍 In-Depth Test Framework Review

1. Vitest (Frontend Unit Tests) — PROPERLY CONFIGURED

  • Configuration: Clean minimal setup in vitest.config.ts
  • Test Coverage: moonbase_utilities.test.ts with 10 test cases for initCap and getTimeAgo
  • CI Integration: ✅ Runs in frontend-lint job via npm run test
  • Verdict: Ready for production

⚠️ 2. Doctest (Backend Native Tests) — CRITICAL ISSUE

  • Configuration: ✅ Properly set up in platformio.ini with test_framework = doctest
  • Test File: ✅ Excellent test/test_native/test_utilities.cpp with comprehensive coverage
  • Local Execution: ✅ Works via .claude/settings.json hooks (pio test -e native)
  • CI Integration: ❌ MISSING FROM GITHUB ACTIONS WORKFLOWS
  • Verdict: BLOCKER - Tests exist but never run in CI ⚠️

3. IWYU (Include What You Use) — EXCELLENTLY CONFIGURED

  • Script Quality: Professional with timeout handling, error recovery, ESP32 file skipping
  • CI Integration: ✅ Dedicated job with continue-on-error: true
  • Exit Strategy: ✅ Always exits 0 (informational only, never blocks)
  • Reporting: Clear summary format
  • Verdict: Perfect setup

4. cpp_lint.py (Custom C++ Linting) — PROPERLY CONFIGURED

  • Rules: no-string-member, no-static-member, no-serial-print
  • CI Integration: ✅ Runs in backend-lint job
  • Exit Codes: ✅ Properly fails CI on violations
  • Verdict: Ready for production

🚨 MERGE DECISION: CONDITIONAL APPROVAL

Status: Ready to merge AFTER addressing the critical doctest CI gap.


📋 MUST-HAVE Actions (Before Merge)

🔴 CRITICAL: Add Doctest to CI Workflow

The native C++ tests are configured but never run in CI. Add this job to .github/workflows/lint.yml:

  backend-tests:
    name: Backend Unit Tests (doctest)
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install PlatformIO
        run: pip install platformio

      - name: Install native dependencies
        run: pio pkg install -e native

      - name: Run native tests
        run: pio test -e native

Why critical: You have 30+ test cases in test_utilities.cpp covering critical functions (Coord3D, PureFunctions, map_range) that are never executed in CI. This defeats the purpose of having tests.


🟡 RECOMMENDED Actions (Before Merge)

1. Add Test Coverage Reporting

Frontend (Vitest):
Update interface/vitest.config.ts:

export default defineConfig({
	test: {
		include: ['src/**/*.test.ts'],
		coverage: {
			provider: 'v8',
			reporter: ['text', 'json', 'html'],
			include: ['src/lib/stores/**/*.ts'],
			exclude: ['**/*.test.ts']
		}
	}
});

Update interface/package.json:

"test": "vitest run",
"test:coverage": "vitest run --coverage"

Backend (doctest):
Doctest doesn't have built-in coverage, but you could add gcov/lcov later.

2. Add README Status Badges

Add this section after the existing badges in README.md:

<p align="center">
  <a href="https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml"><img src="https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml/badge.svg" alt="Lint Status"></a>
  <a href="https://github.com/MoonModules/MoonLight/actions/workflows/build.yml"><img src="https://github.com/MoonModules/MoonLight/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
</p>

For individual test status per job, you can use:

[![Frontend Tests](https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml/badge.svg?event=push&job=frontend-lint)](https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml)
[![Backend Tests](https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml/badge.svg?event=push&job=backend-tests)](https://github.com/MoonModules/MoonLight/actions/workflows/lint.yml)

✅ OPTIONAL Actions (After Merge)

1. Add Test Result Artifacts

Upload test results for better debugging:

- name: Run native tests
  run: pio test -e native --junit-output-path=test-results.xml

- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: test-results-native
    path: test-results.xml

2. Add Coverage Badges

Use Codecov or Coveralls for coverage badges (requires account setup).

3. Expand Test Coverage

  • Add more frontend tests for other utilities in moonbase_utilities.ts
  • Add tests for Svelte component behavior
  • Add integration tests for backend modules

4. Add Test Performance Metrics

Track test execution time trends over time.


📊 Overall Quality Assessment

Component Status Grade Notes
Vitest Setup ✅ Ready A Clean configuration, runs in CI
Doctest Setup ⚠️ Incomplete C Configured but missing CI integration
IWYU Setup ✅ Excellent A+ Professional, informational-only approach
cpp_lint Setup ✅ Ready A Custom rules, proper CI integration
CI Workflow ⚠️ Incomplete B- Missing native test execution
Documentation ✅ Good B+ CLAUDE.md updated with test guidance
Test Coverage 🟡 Basic C+ Only 2 utility functions tested

🎯 Merge Recommendation

Verdict: MERGE AFTER adding the doctest CI job.

Reasoning:

  1. ✅ All test frameworks are technically sound
  2. ✅ IWYU is brilliantly implemented (informational-only is the right choice)
  3. ✅ Custom cpp_lint rules enforce coding standards
  4. ✅ Frontend tests work and run in CI
  5. ⚠️ Backend tests exist but don't run in CI (critical gap)
  6. ✅ Overall architecture is production-ready

Time Estimate: 5 minutes to add the backend-tests job to lint.yml


🔧 Quick Fix Script

Want me to generate the complete updated lint.yml with the backend-tests job included? Just confirm and I can provide the full file ready to commit.

Post-merge: The badges can be added in a follow-up PR as they're cosmetic improvements, not functional requirements.

Does this assessment help? Should I generate the updated workflow file for you?


🧠 Learnings used
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/*.{cpp,h,ts,tsx,js} : Mark upstream sveltekit changes with `// 🌙` (moonbase additions) or `// 💫` (moonlight additions) in source code and consider whether changes could be submitted as a PR to upstream

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/interface/src/routes/moonbase/**/*.{ts,tsx,svelte,js} : ESLint only lints MoonLight-specific files in `src/routes/moonbase/`, `src/lib/components/moonbase/`, and moonbase stores/types — not upstream paths

@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai, regarding

Want me to generate the complete updated lint.yml with the backend-tests job included?

Yes

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the current lint.yml to generate the updated version
cat .github/workflows/lint.yml

Length of output: 2794


@ewowi, here's the complete updated .github/workflows/lint.yml with the backend-tests job added:

name: Lint

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  frontend-lint:
    name: Frontend (ESLint + Prettier)
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: interface

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
          cache-dependency-path: interface/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Generate SvelteKit types
        run: npx svelte-kit sync

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm run test

  iwyu:
    name: IWYU (Include What You Use)
    runs-on: ubuntu-latest
    continue-on-error: true  # informational only — never blocks CI

    steps:
      - uses: actions/checkout@v4

      - name: Install IWYU
        run: sudo apt-get install -y iwyu

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install PlatformIO
        run: pip install platformio

      - name: Install native dependencies
        run: pio pkg install -e native

      - name: Run IWYU analysis
        run: python3 ci/iwyu.py

  backend-lint:
    name: Backend (cppcheck + cpp_lint)
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Run custom lint rules
        run: python3 ci/cpp_lint.py

      - name: Install cppcheck
        run: sudo apt-get install -y cppcheck

      - name: Run cppcheck
        run: |
          cppcheck \
            --enable=warning,performance,style \
            --suppress=missingInclude \
            --suppress=missingIncludeSystem \
            --inline-suppr \
            --language=c++ \
            --std=c++20 \
            --error-exitcode=1 \
            -DPLATFORMIO=1 \
            -DFT_MOONLIGHT=1 \
            -DFT_MOONBASE=1 \
            "-DFT_ENABLED(x)=x" \
            --suppress=noExplicitConstructor \
            --suppress=functionStatic \
            --suppress=constParameterReference \
            --suppress=constParameterPointer \
            --suppress=constParameterCallback \
            --suppress=constVariablePointer \
            --suppress=constVariableReference \
            --suppress=constVariable \
            --suppress=cstyleCast \
            --suppress=unusedStructMember \
            --suppress=useStlAlgorithm \
            --suppress=variableScope \
            --suppress=shadowVariable \
            --suppress=shadowArgument \
            --suppress=stlFindInsert \
            --suppress=noConstructor \
            --template="{file}:{line}: {severity}: {message} [{id}]" \
            $(find src \( -name "*.cpp" -o -name "*.h" \) ! -path "*moonmanpng*" -print)

  backend-tests:
    name: Backend Unit Tests (doctest)
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install PlatformIO
        run: pip install platformio

      - name: Install native dependencies
        run: pio pkg install -e native

      - name: Run native tests
        run: pio test -e native

📝 What Changed

Added new job: backend-tests after backend-lint that:

  1. ✅ Sets up Python 3.11 (consistent with IWYU job)
  2. ✅ Installs PlatformIO
  3. ✅ Installs native environment dependencies
  4. ✅ Runs pio test -e native to execute all doctest tests

This will now execute your 30+ test cases in test/test_native/test_utilities.cpp on every push and PR, ensuring your C++ utility functions (Coord3D, PureFunctions, map_range, etc.) are properly validated in CI.

Ready to commit! 🚀 Just replace the entire .github/workflows/lint.yml file with this content.


🧠 Learnings used
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/*.{cpp,h,ts,tsx,js} : Mark upstream sveltekit changes with `// 🌙` (moonbase additions) or `// 💫` (moonlight additions) in source code and consider whether changes could be submitted as a PR to upstream

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/interface/src/routes/moonbase/**/*.{ts,tsx,svelte,js} : ESLint only lints MoonLight-specific files in `src/routes/moonbase/`, `src/lib/components/moonbase/`, and moonbase stores/types — not upstream paths

@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai, is cppcheck now defined on 2 places (also in Pio.ini)?
And I don't understand the need for env:native. Why is that? Can that be removed including the cop file it calls ?

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if cppcheck is defined in platformio.ini
echo "=== Searching for cppcheck in platformio.ini ==="
grep -n "cppcheck\|check_" platformio.ini || echo "No cppcheck config found in platformio.ini"

echo -e "\n=== Show the env:native section ==="
grep -A 30 "\[env:native\]" platformio.ini

echo -e "\n=== Show what native_stub.cpp contains ==="
cat src/native_stub.cpp

echo -e "\n=== Show the test file that uses env:native ==="
ls -la test/test_native/
head -30 test/test_native/test_utilities.cpp

Length of output: 2412


@ewowi, great questions! Let me clarify both:

1️⃣ cppcheck in Two Places — Different Purposes

YES, cppcheck appears in both locations, but they serve different purposes:

A. PlatformIO (platformio.ini lines 182-186)

check_tool = cppcheck
check_skip_packages = yes
check_severity = medium
check_flags =
  cppcheck: --enable=warning
  • Purpose: Local development checks
  • How to use: pio check command
  • Scope: Basic warnings only
  • Who runs it: Developers locally (optional)

B. CI Workflow (.github/workflows/lint.yml)

- name: Run cppcheck
  run: cppcheck --enable=warning,performance,style ...
  • Purpose: Automated CI enforcement
  • Scope: More comprehensive (warning + performance + style)
  • Suppressions: Extensive list of suppressions (noExplicitConstructor, functionStatic, etc.)
  • Who runs it: GitHub Actions on every push/PR (mandatory)

Verdict: Keep both. They complement each other — local quick checks + comprehensive CI validation.


2️⃣ env:native — CANNOT Be Removed! ⚠️

Short answer: env:native is the entire foundation of your backend unit testing. Removing it would eliminate all C++ unit tests.

What is env:native?

It's a special PlatformIO environment that compiles your C++ code to run on your host machine (Linux/Mac/Windows) instead of ESP32 hardware. This allows you to:

✅ Test pure C++ functions without hardware
✅ Run tests in CI without physical devices
✅ Use standard debugging tools
✅ Execute tests in milliseconds (no flashing delays)

What Tests Depend on env:native?

test/test_native/test_utilities.cpp

This file contains 30+ test cases covering:

  • Math utilities (gcd, lcm, fastDiv255, crc16)
  • Bit operations (getBitValue, setBitValue)
  • Coord3D (constructors, operators, distance)
  • Path extraction (extractPath)
  • String utilities (equal, equalAZaz09, contains)
  • FastLED map_range functions

All of these tests require env:native to run.

What About native_stub.cpp?

int main() { return 0; }

This 1-line file is required by PlatformIO to create a linkable program target. It's not "waste" — it's a technical requirement. The real main() comes from doctest via DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN in the test file.


🎯 Summary

Component Can Remove? Impact
cppcheck in platformio.ini Optional Loses local pio check convenience
cppcheck in CI workflow ❌ No Breaks automated linting
env:native ABSOLUTELY NOT Destroys all backend unit tests
native_stub.cpp ❌ No Required by PlatformIO build system

If you remove env:native, the backend-tests CI job I just recommended will fail, and you'll have zero automated C++ testing.

Keep everything as-is — the setup is correct! 🚀

Does this clarify the architecture? The tests you've built are properly structured for both local and CI execution.


🧠 Learnings used
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/*.{cpp,h,ts,tsx,js} : Mark upstream sveltekit changes with `// 🌙` (moonbase additions) or `// 💫` (moonlight additions) in source code and consider whether changes could be submitted as a PR to upstream

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/interface/src/routes/moonbase/**/*.{ts,tsx,svelte,js} : ESLint only lints MoonLight-specific files in `src/routes/moonbase/`, `src/lib/components/moonbase/`, and moonbase stores/types — not upstream paths

lint.yml: add doctest
index/readme: add badges
@ewowi
Copy link
Collaborator Author

ewowi commented Mar 3, 2026

@coderabbitai, resume, review 3aaedf7 and verify and summarize the readiness to merge (see above, all boxes checked now?)

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch details of the specific commit
echo "=== Commit 3aaedf70 Details ==="
gh api repos/MoonModules/MoonLight/commits/3aaedf70e95f7732e1b109e1d6e54890c51159e7 --jq '{
  sha: .sha,
  message: .commit.message,
  author: .commit.author.name,
  date: .commit.author.date,
  stats: .stats,
  files: [.files[] | {filename: .filename, status: .status, additions: .additions, deletions: .deletions}]
}'

echo -e "\n\n=== Current lint.yml content ==="
cat .github/workflows/lint.yml

echo -e "\n\n=== Verify all test configurations are in place ==="
echo "--- Vitest config ---"
cat interface/vitest.config.ts

echo -e "\n--- Package.json test script ---"
grep -A2 '"test"' interface/package.json

echo -e "\n--- PlatformIO native env ---"
grep -A 15 '\[env:native\]' platformio.ini | head -20

echo -e "\n--- Test file exists ---"
ls -la test/test_native/test_utilities.cpp

echo -e "\n--- IWYU script exists ---"
ls -la ci/iwyu.py

echo -e "\n--- cpp_lint script exists ---"
ls -la ci/cpp_lint.py

Length of output: 5096


🏁 Script executed:

#!/bin/bash
# Check the README and docs badges section
echo "=== README.md badges section ==="
head -30 README.md

echo -e "\n\n=== docs/index.md badges section ==="
head -30 docs/index.md

Length of output: 3791


@ewowi, 🎉 EXCELLENT NEWS — ALL BOXES CHECKED!

✅ Commit 3aaedf7 Review

Your latest commit successfully addresses the critical missing piece:

Changes in this commit:

  • ✅ Added backend-tests job to .github/workflows/lint.yml (+20 lines)
  • ✅ Added status badges to README.md and docs/index.md
  • Minor docs update to docs/gettingstarted/installer.md

📋 Final Merge Readiness Checklist

🟢 MUST-HAVE Items (Critical)

Item Status Notes
Backend tests in CI COMPLETE backend-tests job now runs pio test -e native
Frontend tests in CI ✅ COMPLETE frontend-lint runs npm run test (Vitest)
IWYU configured ✅ COMPLETE Informational-only, never blocks CI
cpp_lint configured ✅ COMPLETE Custom rules enforced in CI
cppcheck configured ✅ COMPLETE Static analysis with proper suppressions

🟡 RECOMMENDED Items

Item Status Notes
Status badges in README COMPLETE Lint, Build, Nightly, Frontend Tests, Backend Tests
Test coverage reporting ⚪ SKIPPED Optional — can be added later

🔵 OPTIONAL Items (Post-Merge)

Item Status Notes
Test result artifacts ⚪ FUTURE Can add later for debugging
Coverage badges (Codecov) ⚪ FUTURE Requires external service setup
Expand test coverage ⚪ FUTURE Current coverage is a good start

🎯 FINAL VERDICT: ✅ READY TO MERGE

All critical requirements are met! Your PR now has:

Complete CI test automation:

  • Frontend: Vitest running on every push/PR
  • Backend: doctest running on every push/PR (30+ test cases)
  • Linting: cpp_lint + cppcheck + ESLint/Prettier
  • Include analysis: IWYU (informational)

Proper status visibility:

  • GitHub Actions badges showing workflow status
  • Job-specific badges for Frontend Tests and Backend Tests

Correct architecture:

  • env:native properly configured for platform-independent C++ testing
  • native_stub.cpp serving its required purpose
  • Test files properly organized in test/test_native/

🚀 You're Clear to Merge!

The only minor cosmetic issue I noticed is that some badge text appears outside the image tags in the README (e.g., "Lint Status" and "Build Status" will render as plain text), but this is purely visual and doesn't affect functionality. You can fix this in a follow-up if desired:

<!-- Current (works but shows extra text): -->
<a href="..."><img src="..." alt="Lint Status">Lint Status</a>

<!-- Cleaner alternative: -->
<a href="..."><img src="..." alt="Lint Status"></a>

But don't let this minor detail block the merge — your testing infrastructure is solid! 🎉

Go ahead and merge with confidence! 🚀


🧠 Learnings used
Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/*.{cpp,h,ts,tsx,js} : Mark upstream sveltekit changes with `// 🌙` (moonbase additions) or `// 💫` (moonlight additions) in source code and consider whether changes could be submitted as a PR to upstream

Learnt from: CR
Repo: MoonModules/MoonLight PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T11:14:24.005Z
Learning: Applies to **/interface/src/routes/moonbase/**/*.{ts,tsx,svelte,js} : ESLint only lints MoonLight-specific files in `src/routes/moonbase/`, `src/lib/components/moonbase/`, and moonbase stores/types — not upstream paths

@ewowi ewowi merged commit 5b92e93 into main Mar 3, 2026
14 of 24 checks passed
@ewowi ewowi deleted the linting branch March 3, 2026 20:54
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