Skip to content

A module declares every control with addControl - #78

Merged
ewowi merged 1 commit into
mainfrom
next-iteration
Aug 24, 2026
Merged

A module declares every control with addControl#78
ewowi merged 1 commit into
mainfrom
next-iteration

Conversation

@ewowi

@ewowi ewowi commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Context

A contributor meets one concept spelled two ways. A MoonLive script declares a control like this:

byte speed = 50;
defineControls() { addControl("speed", speed, 1, 255); }

A compiled module declares the same thing like this:

uint8_t speed_ = 50;
controls_.addUint8("speed", speed_, 1, 255);

Same concept, different vocabulary — and the C++ one asks the author to name a width the compiler already knows from the variable. This collapses addUint8, addUint16, addInt16, addInt32 and addBool into one overloaded addControl, so both sides read identically.

What changed

Five overloads, on uint8_t&, uint16_t&, int16_t&, int32_t& and bool&. Bodies are unchanged from the adders they replace — the only edits are the name and the doc comment.

The safety property that makes a 384-site rename tractable: a non-const lvalue reference binds only to its exact type. No conversion is considered, no two overloads share a type, so a call that compiles produces the widget it always did. With the old names removed, a missed site is a compile error rather than a silent change.

The per-type defaults deliberately differ (uint8 0..255, uint16 0..65535, int16/int32 full signed, bool none). Each is its own type's full range, so a call omitting min/max still means "no UI constraint". Unifying them would silently move the bounds of the 24 sites that rely on them.

addControl on an int8_t is deleted, with a diagnostic naming addPin and addReadOnlyInt. An int8_t is a GPIO or telemetry, never a small number — and deducing a Pin from the type would make any future small signed control register as a claimed GPIO in PinsModule's scan.

The widget-specific adders keep their names: addPin, addSelect, addPalette, addText, addTextArea, addFilePath, addPassword, addIPv4, addReadOnly, addReadOnlyInt, addProgress, addList, addButton. They name a widget rather than a width, and that intent is not recoverable from the C++ type: uint8_t backs a slider, a dropdown and a palette picker. Widths are the compiler's business; widgets are the author's.

Two bugs found on the way

  • The new-script templates would not compile. MoonLiveScriptFile.h still seeded uint8_t / addUint8, so every script a user created from the card failed to compile. The MoonLive type migration missed them.
  • The spec-check range-drift gate would have gone silently green. Its regex matched controls_.add(Uint8|Int16|Uint16); after the rename it would have matched nothing and reported success while checking zero controls. Migrated and control-checked — it finds 216 range-bearing controls.

Reviewing this

129 files, but one needs judgement. src/core/Control.h (+43/−15) holds the overloads; 120 of the remaining files have equal insertions and deletions — pure line-for-line renames. Review effort belongs in Control.h.

This is past the ~100-file mark where CodeRabbit declines to review. It cannot be split: removing the old names has to be atomic with the sweep, or the tree does not compile in between.

Verification

  • arm64 1464 tests · x86-64 under Rosetta 1482 tests · ESP32 firmware builds clean (the target where int32_t is long)
  • All 11 pre-commit gates green, including the GCC and no-backend builds
  • The deleted int8_t overload verified by compiling a probe that must fail
  • New tests pin type→widget, per-type defaults, and explicit ranges

Impact

Nothing changes on a device: same control names, types, ranges, wire format and persisted values — a renamed call produces a byte-identical descriptor. MIGRATING.md says nothing for users, recompile for third-party module authors.

🤖 Generated with Claude Code

addUint8, addUint16, addInt16, addInt32 and addBool become one overloaded addControl: the widget follows the variable's own type, which the compiler already knows, so the name no longer repeats a width the declaration states. It is the same call a MoonLive script makes, so someone who has written a script can read a compiled module and the other way round.

Performance: desktop 132us idle, esp32 2,151us. Nothing on a tick path moved; a renamed call produces a byte-identical descriptor.

**Core**
- five addControl overloads on uint8_t, uint16_t, int16_t, int32_t and bool, bodies unchanged from the adders they replace. The per-type defaults DIFFER deliberately — each is its own type's full range, so a call omitting min/max still means "no UI constraint" — and unifying them would silently move the bounds of the 24 sites that rely on them
- addControl on an int8_t is DELETED, with a diagnostic naming addPin and addReadOnlyInt. An int8_t is a GPIO or telemetry, never a small number, and deducing a Pin from the type would make any future small signed control register as a claimed GPIO in PinsModule's scan
- the widget-specific adders keep their names: addPin, addSelect, addPalette, addText, addTextArea, addFilePath, addPassword, addIPv4, addReadOnly, addReadOnlyInt, addProgress, addList, addButton. They name a widget rather than a width, and that intent is not recoverable from the C++ type — uint8_t backs a slider, a dropdown AND a palette picker

**Light domain / everywhere**
- 384 call sites across 102 files renamed. A non-const lvalue reference binds only to its exact type, so a site that compiles produces the widget it always did; with the old names removed, a missed site is a compile error rather than a silent change

**Scripts**
- the NEW-SCRIPT templates still used uint8_t and addUint8, so every script created from the card failed to compile. The MoonLive type migration missed them

**MoonDeck**
- the spec check's range-drift regex matched add(Uint8|Int16|Uint16) and would have matched NOTHING after the rename: green while checking zero controls. Migrated and control-checked — it finds 216

**Tests**
- addControl binds the widget its member's type calls for; a call without a range gets that type's full range; an explicit range reaches the descriptor. The deleted int8_t overload is its own test, verified by compiling a probe that must fail

**Docs**
- the contributor tutorial, testing.md and MoonLiveLayout.md; MIGRATING says *nothing* for a device and *recompile* for a third-party module
- comments and backlog snippets now describe the API as it IS: a forward-looking doc telling an implementer to call addUint8 would be telling them to write something that does not compile

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 84d2ce2b-48d1-4409-bc85-3c3dfc1a8416

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@ewowi
ewowi merged commit f063879 into main Aug 24, 2026
8 checks passed
@ewowi
ewowi deleted the next-iteration branch August 24, 2026 09:21
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