Skip to content

37 id concept the id as a unique identifier does not scale#39

Merged
kimkulling merged 7 commits intomainfrom
37-id-concept-the-id-as-a-unique-identifier-does-not-scale
Apr 29, 2026
Merged

37 id concept the id as a unique identifier does not scale#39
kimkulling merged 7 commits intomainfrom
37-id-concept-the-id-as-a-unique-identifier-does-not-scale

Conversation

@kimkulling
Copy link
Copy Markdown
Owner

@kimkulling kimkulling commented Apr 28, 2026

Summary by CodeRabbit

  • Refactoring
    • Streamlined widget creation API by transitioning from manual ID assignment to automatic handle-based identification.
    • Enhanced type safety in widget references and callbacks, reducing errors in widget management and interaction.

@kimkulling kimkulling linked an issue Apr 28, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Walkthrough

The pull request migrates the widget identification system from raw integer IDs to a strongly-typed WidgetHandle abstraction. All widget creation functions are refactored to auto-generate handles and return them instead of ret_code, with updated callback signatures and sample code accordingly.

Changes

Cohort / File(s) Summary
Type System & Exports
src/tinyui.h
Introduces new WidgetHandle struct wrapping Id with validity checks and root handle helpers; updates callback signatures to receive WidgetHandle instead of raw Id.
Widget Header API
src/widgets.h
Replaces Widget::mId field with mHandle; updates all 14 widget-creation functions to accept WidgetHandle parentId and return WidgetHandle instead of ret_code; updates 6 utility/lookup functions to use WidgetHandle instead of Id; removes Widgets::RootItem constant.
Widget Implementation
src/widgets.cpp
Refactors all widget-creation and utility functions to use WidgetHandle for parent identification and handle generation; updates widget lookup, callback invocation, and state-manipulation functions to work with handles; adjusts debug logging and internal comparisons to use mHandle.mId.
Core Runtime
src/tinyui.cpp
Updates update-callback invocation in TinyUi::run() to pass WidgetHandle{1} instead of raw integer literal 1 to callback handlers.
Sample Code
samples/demo/main.cpp, samples/hello_world/main.cpp
Migrate widget creation from hard-coded numeric IDs and RootPanelId to WidgetHandle-based API; update callback signatures; capture and reuse widget handles for parent references.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feature/treeview #15: This PR's refactoring of widget creation and lookup APIs in src/widgets.* directly relates to handle-based widget identification that PR #15 also touches.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% 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 is vague and generic, using non-descriptive phrasing that doesn't clearly convey the main technical change despite referencing issue #37. Revise the title to clearly describe the main change, such as: 'Replace manual widget IDs with automatic WidgetHandle creation' or 'Refactor widget identification to use automatic handles instead of explicit IDs'.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 37-id-concept-the-id-as-a-unique-identifier-does-not-scale

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
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (5)
src/widgets.h (1)

162-272: 🛠️ Refactor suggestion | 🟠 Major

Keep error reporting separate from the returned handle.

These factory APIs now return only WidgetHandle, so callers lose the distinction between invalid context, invalid parent, and generic creation failure. Consider keeping ret_code as the return type and writing the generated handle to an out-parameter, then update the Doxygen blocks to match the new contract.

As per coding guidelines, "Use ret_code (int32_t) for return values".

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

In `@src/widgets.h` around lines 162 - 272, The factory methods (e.g., container,
box, imageBox, label, inputText, textButton, imageButton, panel, treeView,
treeItem, progressBar) currently return WidgetHandle which hides error codes;
change their signature to return ret_code (int32_t) and add an out-parameter
like WidgetHandle *outHandle to receive the created handle, update each
implementation to return distinct error codes (invalid context/parent/generic
failure) while writing the handle to outHandle on success, and update the
Doxygen comments for each affected function to document the new return type and
the out-parameter; keep findWidget/findSelectedWidget unchanged.
src/widgets.cpp (4)

467-473: ⚠️ Potential issue | 🟠 Major

Persist the requested initial fill state.

fillRate is clamped into state.filledState, but that value is never copied into payload.payload. New progress bars therefore start at 0 until the update callback mutates them.

💡 Copy the initial state into the payload
     FilledState state;
     state.filledState = fillRate;
     child->mContent = new uint8_t[sizeof(EventPayload)];
     child->mCallback = callback;
     EventPayload payload;
     payload.type = EventDataType::FillState;
+    std::memcpy(payload.payload, &state, sizeof(state));
     memcpy(child->mContent, &payload, sizeof(EventPayload));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets.cpp` around lines 467 - 473, The initial clamped fill value in
FilledState (state.filledState) is never copied into the EventPayload, so new
progress bars start at 0; before copying payload into child->mContent assign the
initial value (e.g., payload.payload = state.filledState or memcpy into
payload.payload) so EventPayload contains the requested fill rate, then
memcpy(child->mContent, &payload, sizeof(EventPayload)); ensure you reference
FilledState, fillRate, EventPayload, and child->mContent when making the change.

129-136: ⚠️ Potential issue | 🔴 Critical

Delete orphan widgets when parent resolution fails.

If setParent() returns nullptr, this function still returns a fresh Widget* with a valid-looking handle. Every factory built on top of this can then leak the allocation and hand back a handle for a widget that is not attached to the tree.

💡 Minimal fix in this helper
 static Widget *createWidget(Context &ctx, WidgetHandle parentId, const Rect &rect, WidgetType type) {
     auto *widget = new Widget;
     widget->mHandle = WidgetHandle{createHandle()};
     widget->mType = type;
     widget->mRect = rect;
     widget->mParent = setParent(ctx, widget, parentId);
+    if (widget->mParent == nullptr) {
+        delete widget;
+        return nullptr;
+    }
 
     return widget;
 }

Each caller should then translate nullptr into WidgetHandle{WidgetHandle::InvalidId}.

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

In `@src/widgets.cpp` around lines 129 - 136, createWidget currently allocates a
Widget and always returns it even if setParent(ctx, widget, parentId) fails,
producing orphan widgets and leaking memory; change createWidget so that after
calling setParent(...) you check if it returned nullptr, and if so delete the
newly allocated Widget and return nullptr instead of the orphaned pointer (keep
creating the handle via createHandle and assigning mType/mRect only after parent
success or if you prefer create early, still delete on failure); callers of
createWidget (which expect a Widget* to be turned into a WidgetHandle) should
then treat a nullptr return as WidgetHandle{WidgetHandle::InvalidId}.

714-716: ⚠️ Potential issue | 🟠 Major

Honor the recursive flag in clearItem().

The function always deletes every child subtree, even when recursive is false. That breaks the documented contract and can remove more UI than the caller asked for.

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

In `@src/widgets.cpp` around lines 714 - 716, clearItem() currently ignores the
recursive flag and always calls recursiveClear on every child (the loop calling
recursiveClear(widget->mChildren[i])), removing entire subtrees; change the
logic in clearItem() so that when the recursive parameter is false it only
removes direct child items (e.g., delete or clear widget->mChildren[i] without
descending) and when recursive is true it calls recursiveClear for each child as
before; locate the clearItem function and the loop that invokes recursiveClear
and branch on the recursive parameter to perform non-recursive versus recursive
deletion accordingly.

474-476: ⚠️ Potential issue | 🟠 Major

Update callbacks outlive deleted widgets.

This registers the callback in Context::mUpdateCallbackList, but widget teardown does not remove it. After a progress bar is cleared, TinyUi::run() can still invoke the stale callback while mInstance points at freed widget memory.

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

In `@src/widgets.cpp` around lines 474 - 476, The update callback registered into
Context::mUpdateCallbackList from the snippet (callback->mInstance = child) can
outlive the widget and later be invoked by TinyUi::run() with mInstance pointing
at freed memory; fix this by removing or nulling callbacks during widget
teardown: add logic in the widget destructor/clear routine (or a
Widget::destroy/clear method) to call a Context method (e.g.,
Context::removeUpdateCallback or similar) to erase any entries whose mInstance
equals this, or alternatively set callback->mInstance = nullptr for all
callbacks owned by the widget so TinyUi::run() can skip them, and ensure
Context::mUpdateCallbackList iteration checks for nullptr mInstance before
invoking.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@samples/hello_world/main.cpp`:
- Around line 51-56: Check the WidgetHandle returned by Widgets::panel (the
local variable panel) for validity before creating child widgets; if the handle
is invalid, log or return early instead of calling Widgets::label or
Widgets::textButton. Move creation of dynamicQuitCallback (CallbackI) and any
subsequent UI additions (Widgets::label, Widgets::textButton) to after the
validity check so you don't allocate callbacks or build UI under an invalid
parent, and ensure you handle/cleanup the callback on early exit.

In `@src/tinyui.cpp`:
- Around line 148-149: The code is passing a bogus WidgetHandle{1} into every
update callback; replace the synthesized constant with the widget's real handle
(e.g. use the widget object's handle member or accessor rather than
WidgetHandle{1}) so callbacks get the actual id. Specifically, in the call site
that constructs WidgetHandle handle{1} and invokes
(*it)->mfuncCallback[Events::UpdateEvent](handle, (*it)->mInstance), use the
widget's real handle (for example (*it)->mHandle or (*it)->getHandle()) and
ensure the type matches WidgetHandle (note WidgetHandle::getRootHandle() is 0,
so using 1 is incorrect).

In `@src/tinyui.h`:
- Around line 101-108: InvalidId is currently set to 999999 which can collide
with generated Ids; change the sentinel to an out-of-band value (e.g. use the
maximum representable Id via std::numeric_limits<Id>::max()) or alternatively
add a separate validity flag (e.g. bool mValid) to track handle validity, then
update the isValid() method to check the new sentinel or the new flag; update
references to InvalidId, mId, and isValid() accordingly (or add mValid and
set/reset it where handles are created/destroyed).

In `@src/widgets.cpp`:
- Around line 173-185: The code currently overwrites ctx.mRoot with the newly
created container (Widgets::container -> ctx.mRoot = widget), which can replace
a synthetic root created by TinyUi::getContext()/getValidRoot() and leave
widget->mParent dangling; change the logic so you only set ctx.mRoot when it was
previously null (i.e., if (ctx.mRoot == nullptr) ctx.mRoot = widget;) or simply
remove the unconditional assignment, leaving createWidget/getValidRoot to attach
the new container under the existing root and preserving widget->mParent.

---

Outside diff comments:
In `@src/widgets.cpp`:
- Around line 467-473: The initial clamped fill value in FilledState
(state.filledState) is never copied into the EventPayload, so new progress bars
start at 0; before copying payload into child->mContent assign the initial value
(e.g., payload.payload = state.filledState or memcpy into payload.payload) so
EventPayload contains the requested fill rate, then memcpy(child->mContent,
&payload, sizeof(EventPayload)); ensure you reference FilledState, fillRate,
EventPayload, and child->mContent when making the change.
- Around line 129-136: createWidget currently allocates a Widget and always
returns it even if setParent(ctx, widget, parentId) fails, producing orphan
widgets and leaking memory; change createWidget so that after calling
setParent(...) you check if it returned nullptr, and if so delete the newly
allocated Widget and return nullptr instead of the orphaned pointer (keep
creating the handle via createHandle and assigning mType/mRect only after parent
success or if you prefer create early, still delete on failure); callers of
createWidget (which expect a Widget* to be turned into a WidgetHandle) should
then treat a nullptr return as WidgetHandle{WidgetHandle::InvalidId}.
- Around line 714-716: clearItem() currently ignores the recursive flag and
always calls recursiveClear on every child (the loop calling
recursiveClear(widget->mChildren[i])), removing entire subtrees; change the
logic in clearItem() so that when the recursive parameter is false it only
removes direct child items (e.g., delete or clear widget->mChildren[i] without
descending) and when recursive is true it calls recursiveClear for each child as
before; locate the clearItem function and the loop that invokes recursiveClear
and branch on the recursive parameter to perform non-recursive versus recursive
deletion accordingly.
- Around line 474-476: The update callback registered into
Context::mUpdateCallbackList from the snippet (callback->mInstance = child) can
outlive the widget and later be invoked by TinyUi::run() with mInstance pointing
at freed memory; fix this by removing or nulling callbacks during widget
teardown: add logic in the widget destructor/clear routine (or a
Widget::destroy/clear method) to call a Context method (e.g.,
Context::removeUpdateCallback or similar) to erase any entries whose mInstance
equals this, or alternatively set callback->mInstance = nullptr for all
callbacks owned by the widget so TinyUi::run() can skip them, and ensure
Context::mUpdateCallbackList iteration checks for nullptr mInstance before
invoking.

In `@src/widgets.h`:
- Around line 162-272: The factory methods (e.g., container, box, imageBox,
label, inputText, textButton, imageButton, panel, treeView, treeItem,
progressBar) currently return WidgetHandle which hides error codes; change their
signature to return ret_code (int32_t) and add an out-parameter like
WidgetHandle *outHandle to receive the created handle, update each
implementation to return distinct error codes (invalid context/parent/generic
failure) while writing the handle to outHandle on success, and update the
Doxygen comments for each affected function to document the new return type and
the out-parameter; keep findWidget/findSelectedWidget unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb895a24-9752-49fd-9555-5f25df37d364

📥 Commits

Reviewing files that changed from the base of the PR and between f386ea8 and 77029f2.

📒 Files selected for processing (6)
  • samples/demo/main.cpp
  • samples/hello_world/main.cpp
  • src/tinyui.cpp
  • src/tinyui.h
  • src/widgets.cpp
  • src/widgets.h

Comment thread samples/hello_world/main.cpp
Comment thread src/tinyui.cpp
Comment thread src/tinyui.h
Comment thread src/widgets.cpp
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 29, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
21 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@kimkulling kimkulling merged commit e8dfd24 into main Apr 29, 2026
4 checks passed
@kimkulling kimkulling deleted the 37-id-concept-the-id-as-a-unique-identifier-does-not-scale branch April 29, 2026 07: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.

Id-Concept: The id as a unique identifier does not scale

2 participants