Skip to content

Remove legacy ui.c and USE_MANIFEST conditionals#147

Open
durch wants to merge 4 commits intomasterfrom
issue/132
Open

Remove legacy ui.c and USE_MANIFEST conditionals#147
durch wants to merge 4 commits intomasterfrom
issue/132

Conversation

@durch
Copy link
Collaborator

@durch durch commented Feb 20, 2026

Closes #132

Summary

Removes the legacy ui.c implementation (1647 lines) and all USE_MANIFEST conditional compilation. The manifest-driven UI is now the only UI path.

Changes

Deleted

  • common/ui.c — entire legacy UI implementation

Modified

  • common/bridge_client.c — Removed dispatch macros (UI_SET_STATUS, UI_UPDATE, etc.), now calls manifest_ui_* directly. Removed dead code: struct now_playing_state, fetch_now_playing(), ui_update_cb(), post_ui_update(), default_now_playing().
  • common/manifest_ui.c — Removed #if USE_MANIFEST guard around shim section. Removed unused shims (ui_init, ui_set_status, ui_set_artwork, ui_dispatch_input, ui_handle_volume_rotation, zone picker shims, ui_test_pattern, ui_set_progress). Kept implementations for functions still called externally (ui_loop_iter, ui_set_controls_visible, OTA functions, battery, message/zone/network).
  • common/manifest_ui.h — Updated comments to remove USE_MANIFEST/ui.c references.
  • common/ui.h — Trimmed to shared types (ui_input_event_t, ui_input_cb_t) + functions still called from external files.
  • common/app_main.c — Calls manifest_ui_* directly (no conditional).
  • idf_app/main/main_idf.c — Calls manifest_ui_init() directly. Replaced ui_update() calls with ui_set_message().
  • idf_app/main/platform_input_idf.c — Calls manifest_ui_* directly (no conditional).
  • idf_app/main/CMakeLists.txt — Removed USE_MANIFEST option and conditional block.

Verification

  • No USE_MANIFEST or #if USE_MANIFEST anywhere in codebase
  • All ui_* functions declared in ui.h have implementations (in manifest_ui.c or ui_network.c)
  • All manifest_ui_* calls in bridge_client.c are declared in manifest_ui.h
  • No callers of removed functions remain

Note

Some documentation files (docs/) still reference ui.c — these should be updated separately.

Summary by CodeRabbit

  • Refactor
    • UI unified to a single manifest-driven system: initialization, screen updates, messages, zone selection, volume controls, artwork and network/status updates now follow the same manifest flow.
    • Legacy/alternate UI path and the build-time toggle removed, simplifying startup and runtime behavior for a more consistent user experience.

Delete common/ui.c (1647 lines) — manifest UI is now the only UI path.

Changes:
- Delete common/ui.c entirely
- Remove USE_MANIFEST option from CMakeLists.txt
- bridge_client.c: remove dispatch macros (UI_SET_STATUS etc.),
 call manifest_ui_* directly; remove dead code (now_playing_state,
 fetch_now_playing, ui_update_cb, post_ui_update, default_now_playing)
- manifest_ui.c: remove #if USE_MANIFEST guard around shim section,
 remove unused shims (ui_init, ui_set_status, ui_set_artwork, etc.),
 keep implementations for functions still called externally
 (ui_loop_iter, ui_set_controls_visible, OTA, battery)
- app_main.c: call manifest_ui_* directly (no conditional)
- main_idf.c: call manifest_ui_init() directly, replace ui_update()
 calls with ui_set_message()
- platform_input_idf.c: call manifest_ui_* directly (no conditional)
- ui.h: trim to shared types + functions still called from external
 files (ui_loop_iter, ui_set_message, ui_set_zone_name, settings,
 OTA, controls, network status, battery)
- Update comments in manifest_ui.c/h to remove ui.c references

Closes #132
@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Legacy LVGL-based UI and all USE_MANIFEST compile-time branches were removed; the codebase now always uses the manifest-driven UI (manifest_ui_*). Legacy common/ui.c and many legacy UI APIs were deleted and bridge, platform, and app startup code call manifest UI unconditionally.

Changes

Cohort / File(s) Summary
Legacy UI removal
common/ui.c, common/ui.h
Deleted the legacy LVGL UI implementation and removed numerous legacy UI public APIs, callbacks, and typedefs.
Bridge client consolidation
common/bridge_client.c
Replaced USE_MANIFEST branches with direct manifest_ui_* calls; unified manifest fetch/poll/update flows and removed legacy UI dispatch macros.
Manifest UI alignment
common/manifest_ui.c, common/manifest_ui.h
Converted compatibility shims into direct manifest bridges (added manifest_ui_* wrappers exposed as ui_* where needed), cleaned comments, and switched LVGL task handling to timer API.
App startup & platform input
common/app_main.c, idf_app/main/main_idf.c, idf_app/main/platform_input_idf.c
Removed conditional UI compilation; always include/init manifest_ui, replaced legacy ui_update(...)/rotation paths with manifest-based calls and input handlers.
Build config update
idf_app/main/CMakeLists.txt
Removed USE_MANIFEST option and conditional inclusion of legacy common/ui.c.
Docs/comments
idf_app/main/font_manager.h
Updated init-order comment to reference manifest_ui_init() instead of legacy ui_init().

Sequence Diagram(s)

sequenceDiagram
participant App as App Main
participant Bridge as Bridge Client
participant Platform as Platform Input
participant ManifestUI as manifest_ui

App->>ManifestUI: manifest_ui_init()
Platform->>ManifestUI: input callbacks (set via manifest bridge)
Platform->>Bridge: send input events / selection changes
Bridge->>ManifestUI: manifest_ui_set_zone_name(...)
Bridge->>ManifestUI: manifest_ui_set_message(...) / manifest_ui_set_network_status(...)
ManifestUI-->>App: render/update (manifest-driven)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #132 — Remove legacy ui.c and USE_MANIFEST conditionals — this PR implements the objectives (delete common/ui.c, remove USE_MANIFEST branches, consolidate to manifest UI).
  • Eliminate double-posting in bridge_client UI callbacks #148 — UI double-posting/inconsistent dispatch — consolidation to direct manifest_ui_* calls likely addresses the double-posting/inconsistent post paths described.

Possibly related PRs

Poem

🐰 I hopped through code with tiny paws,
Old branches gone, no conditional claws.
Manifest sings a single tune,
Inputs, zones, and messages in tune.
A carrot-shaped commit — neat and soon.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the primary objective: removing the legacy ui.c file and all USE_MANIFEST conditional compilation, making it the canonical approach.
Linked Issues check ✅ Passed The PR successfully addresses all major coding objectives from issue #132: deleted common/ui.c, removed all USE_MANIFEST conditionals from bridge_client.c/app_main.c/manifest_ui.c/CMakeLists.txt, trimmed ui.h to shared types, converted shims to direct implementations, updated callers to use manifest_ui_* directly, and achieved a clean build without the USE_MANIFEST flag.
Out of Scope Changes check ✅ Passed All changes are strictly within scope: file deletions (ui.c), conditional removal (USE_MANIFEST blocks), shim conversion/removal in manifest_ui.c, header trimming (ui.h), caller updates, and build system changes directly align with issue #132 objectives; no unrelated modifications detected.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue/132

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.

@github-actions
Copy link

github-actions bot commented Feb 20, 2026

Firmware Preview

Flash this PR's firmware directly in your browser:
https://roon-knob.muness.com/pr/147/flash.html

Updated: 2026-02-25T09:21:04.275Z

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

Caution

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

⚠️ Outside diff range comments (3)
common/bridge_client.c (2)

993-1002: 🛠️ Refactor suggestion | 🟠 Major

Inconsistent UI dispatch: direct manifest_ui_set_* calls here vs post_ui_* wrappers elsewhere in the same function.

Within bridge_poll_thread, Line 993 calls manifest_ui_set_network_status(NULL) directly (correct single-post), while Line 992 uses post_ui_message("Bridge: Connected") (double-post). Similarly, Line 1002 calls manifest_ui_set_zone_name(zone_name_copy) directly but other zone name updates use post_ui_zone_name. This inconsistency reinforces the need to standardize on direct manifest_ui_set_* calls.

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

In `@common/bridge_client.c` around lines 993 - 1002, In bridge_poll_thread, fix
the inconsistent UI dispatch by using the direct manifest_ui_set_* calls
everywhere in this function instead of mixing post_ui_* wrappers: replace
occurrences of post_ui_message("Bridge: Connected") and any
post_ui_zone_name(...) calls with the equivalent
manifest_ui_set_network_status(...) / manifest_ui_set_zone_name(...) invocations
(or remove redundant double-posts), and ensure you still copy the zone name into
zone_name_copy under lock before calling
manifest_ui_set_zone_name(zone_name_copy) so thread-safety and single direct UI
updates (manifest_ui_set_network_status, manifest_ui_set_zone_name) are
consistent.

166-191: ⚠️ Potential issue | 🟠 Major

Double-posting: callbacks already run on the UI thread but call manifest_ui_set_* which posts to the UI thread again.

ui_status_cb, ui_message_cb, and ui_zone_name_cb are dispatched via platform_task_post_to_ui, so they already execute on the UI thread. But calling manifest_ui_set_status / manifest_ui_set_message / manifest_ui_set_zone_name from these callbacks enqueues another post to the UI thread (with an extra strdup for the string variants). This means:

  1. Extra latency — the actual LVGL label update is deferred to the next platform_task_run_pending() cycle.
  2. Extra allocationsmanifest_ui_set_message does strdup, then ui_cb_set_message does free on the original copy, while the second copy lives until processed.

Since manifest_ui_set_* already handles thread-safe posting internally, the simplest fix is to eliminate the post_ui_* wrappers and call manifest_ui_set_* directly from the poll thread:

♻️ Proposed fix — remove double-posting helpers
-static void ui_status_cb(void *arg) {
-  bool *online = arg;
-  if (!online) {
-    return;
-  }
-  manifest_ui_set_status(*online);
-  free(online);
-}
-
-static void ui_message_cb(void *arg) {
-  char *msg = arg;
-  if (!msg) {
-    return;
-  }
-  manifest_ui_set_message(msg);
-  free(msg);
-}
-
-static void ui_zone_name_cb(void *arg) {
-  char *name = arg;
-  if (!name) {
-    return;
-  }
-  manifest_ui_set_zone_name(name);
-  free(name);
-}

Then replace all post_ui_message(...) calls with manifest_ui_set_message(...), post_ui_zone_name(...) with manifest_ui_set_zone_name(...), and post_ui_status(...) with manifest_ui_set_status(...). The post_ui_status_copy, post_ui_message_copy, post_ui_zone_name_copy helpers and their forward declarations can also be removed.

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

In `@common/bridge_client.c` around lines 166 - 191, The callbacks ui_status_cb,
ui_message_cb, and ui_zone_name_cb are double-posting to the UI thread: they run
on the UI thread already (via platform_task_post_to_ui) and then call post_ui_*
wrappers which post again and strdup strings, causing extra latency and
allocations; replace uses of post_ui_message/post_ui_zone_name/post_ui_status
with direct calls to
manifest_ui_set_message/manifest_ui_set_zone_name/manifest_ui_set_status, remove
the post_ui_* and post_ui_*_copy helpers and their forward declarations, and
update the callbacks (ui_status_cb, ui_message_cb, ui_zone_name_cb) to call the
manifest_ui_set_* functions directly while preserving correct ownership/free
semantics (do not strdup before calling manifest functions; free the original
buffer only if the manifest functions duplicate it).
common/manifest_ui.c (1)

1702-1705: ⚠️ Potential issue | 🟠 Major

Remove the redundant lv_task_handler() call; in LVGL 9.x it's only a compatibility alias.

lv_task_handler() is the legacy v7 name for tasks, kept as a wrapper via the v8 API mapping header for backward compatibility. In LVGL 9.x, lv_timer_handler() is the actual handler and lv_task_handler() merely aliases it. Calling both causes the same handler logic to run twice.

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

In `@common/manifest_ui.c` around lines 1702 - 1705, The ui_loop_iter function
currently calls lv_task_handler() which is a legacy alias to lv_timer_handler()
in LVGL 9.x causing duplicate handler execution; remove the lv_task_handler()
call from ui_loop_iter and leave platform_task_run_pending() and
lv_timer_handler() only so the timer/task handling runs once (update the
ui_loop_iter function to drop the lv_task_handler() invocation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common/manifest_ui.c`:
- Around line 1642-1650: The bridge callbacks (ui_message_cb, ui_zone_name_cb,
ui_status_cb) are double-posting because they call manifest_ui_set_* (which
strdup + platform_task_post_to_ui) after already being invoked on the UI thread
by post_ui_message/post_ui_zone_name/post_ui_status; fix by making the callbacks
call a non-posting, UI-thread-only setter (create/used functions named
manifest_ui_apply_message, manifest_ui_apply_zone_name,
manifest_ui_apply_network_status or similar) that update state directly without
strdup/platform_task_post_to_ui, and update manifest implementation to add these
direct setters (or add a boolean parameter to manifest_ui_set_* to skip posting)
so that ui_set_message/ui_set_zone_name/ui_set_network_status still post when
called from non-UI threads but bridge_client.c callbacks call the new
non-posting variants.

---

Outside diff comments:
In `@common/bridge_client.c`:
- Around line 993-1002: In bridge_poll_thread, fix the inconsistent UI dispatch
by using the direct manifest_ui_set_* calls everywhere in this function instead
of mixing post_ui_* wrappers: replace occurrences of post_ui_message("Bridge:
Connected") and any post_ui_zone_name(...) calls with the equivalent
manifest_ui_set_network_status(...) / manifest_ui_set_zone_name(...) invocations
(or remove redundant double-posts), and ensure you still copy the zone name into
zone_name_copy under lock before calling
manifest_ui_set_zone_name(zone_name_copy) so thread-safety and single direct UI
updates (manifest_ui_set_network_status, manifest_ui_set_zone_name) are
consistent.
- Around line 166-191: The callbacks ui_status_cb, ui_message_cb, and
ui_zone_name_cb are double-posting to the UI thread: they run on the UI thread
already (via platform_task_post_to_ui) and then call post_ui_* wrappers which
post again and strdup strings, causing extra latency and allocations; replace
uses of post_ui_message/post_ui_zone_name/post_ui_status with direct calls to
manifest_ui_set_message/manifest_ui_set_zone_name/manifest_ui_set_status, remove
the post_ui_* and post_ui_*_copy helpers and their forward declarations, and
update the callbacks (ui_status_cb, ui_message_cb, ui_zone_name_cb) to call the
manifest_ui_set_* functions directly while preserving correct ownership/free
semantics (do not strdup before calling manifest functions; free the original
buffer only if the manifest functions duplicate it).

In `@common/manifest_ui.c`:
- Around line 1702-1705: The ui_loop_iter function currently calls
lv_task_handler() which is a legacy alias to lv_timer_handler() in LVGL 9.x
causing duplicate handler execution; remove the lv_task_handler() call from
ui_loop_iter and leave platform_task_run_pending() and lv_timer_handler() only
so the timer/task handling runs once (update the ui_loop_iter function to drop
the lv_task_handler() invocation).

@durch
Copy link
Collaborator Author

durch commented Feb 20, 2026

Verify each finding against the current code and only fix it if needed.

In @common/manifest_ui.c around lines 1642 - 1650, The bridge callbacks
(ui_message_cb, ui_zone_name_cb, ui_status_cb) are double-posting because they
call manifest_ui_set_* (which strdup + platform_task_post_to_ui) after already
being invoked on the UI thread by
post_ui_message/post_ui_zone_name/post_ui_status; fix by making the callbacks
call a non-posting, UI-thread-only setter (create/used functions named
manifest_ui_apply_message, manifest_ui_apply_zone_name,
manifest_ui_apply_network_status or similar) that update state directly without
strdup/platform_task_post_to_ui, and update manifest implementation to add these
direct setters (or add a boolean parameter to manifest_ui_set_* to skip posting)
so that ui_set_message/ui_set_zone_name/ui_set_network_status still post when
called from non-UI threads but bridge_client.c callbacks call the new
non-posting variants.

- Remove double-posting: post_ui_* helpers did strdup+post, then
 callbacks called manifest_ui_set_* which did strdup+post again.
 Replace all post_ui_* calls with direct manifest_ui_set_* calls
 (which already handle thread-safe posting internally).
- Remove redundant lv_task_handler() call (alias for lv_timer_handler
 in LVGL 9.x, was causing duplicate handler execution).
@durch
Copy link
Collaborator Author

durch commented Feb 20, 2026

Fixed in bff1870:

  • Double-posting eliminated: Removed the entire post_ui_* / ui_*_cb callback layer (74 lines). All callers now use manifest_ui_set_* directly — which already handles thread-safe strdup + platform_task_post_to_ui internally. This fixes the double-post + double-alloc issue for message, zone_name, network_status, and status updates.
  • Inconsistent dispatch fixed: The mix of post_ui_* (double-post) vs direct manifest_ui_set_* (single-post) in bridge_poll_thread is now uniformly direct calls.
  • Redundant lv_task_handler() removed: In LVGL 9.x this is an alias for lv_timer_handler(), so calling both caused duplicate handler execution.

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.

Caution

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

⚠️ Outside diff range comments (2)
common/manifest_ui.c (2)

1580-1593: ⚠️ Potential issue | 🟡 Minor

Fallback path in manifest_ui_hide_zone_picker leaves zone_label permanently hidden.

When s_mgr.manifest.nav.default_screen is not found in the nav order, the function falls through to show_screen(0) without restoring the zone_label that manifest_ui_show_zone_picker hid. This is a latent edge case (misconfigured manifest), but the fix is trivial.

🛡️ Proposed fix
   }
-  show_screen(0);
+  show_screen(0);
+  if (s_chrome.zone_label)
+    lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/manifest_ui.c` around lines 1580 - 1593, manifest_ui_hide_zone_picker
can fall through to the fallback show_screen(0) without restoring
s_chrome.zone_label; ensure the zone label is unhidden in all exit paths by
calling lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN) before or
immediately after the fallback show_screen(0) (so both the matched-nav branch
and the fallback restore the label); reference manifest_ui_hide_zone_picker,
s_chrome.zone_label, show_screen, and LV_OBJ_FLAG_HIDDEN.

1404-1406: ⚠️ Potential issue | 🟡 Minor

lv_timer_create return value not checked — potential null dereference.

If lv_timer_create fails (OOM), s_msg_timer is NULL and the subsequent lv_timer_set_repeat_count(NULL, 1) call dereferences it, crashing. Additionally, if the timer creation fails, the status bar message would never be auto-cleared.

The codebase already handles this correctly elsewhere (e.g., battery_timer creation in the same file checks the return value before use).

🛡️ Proposed fix
      s_msg_timer = lv_timer_create(msg_timer_cb, 3000, NULL);
+     if (s_msg_timer) {
      lv_timer_set_repeat_count(s_msg_timer, 1);
+     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/manifest_ui.c` around lines 1404 - 1406, lv_timer_create can return
NULL on failure and s_msg_timer is dereferenced immediately; update the block
that calls lv_timer_create(msg_timer_cb, 3000, NULL) to check the return value
(s_msg_timer) before calling lv_timer_set_repeat_count(s_msg_timer, 1) and any
other timer operations; if s_msg_timer is NULL, avoid dereferencing and ensure
the status-bar auto-clear behavior still occurs (e.g., skip timer setup or
implement a safe fallback path), referencing s_msg_timer, lv_timer_create,
msg_timer_cb, and lv_timer_set_repeat_count to locate the code to change.
🧹 Nitpick comments (2)
common/bridge_client.c (2)

1135-1168: Dead code: zone-list arrays and line1_msg/line2_msg are never consumed after manifest migration.

Two orphaned cleanup artifacts:

  1. UI_INPUT_MENU branch (lines 1135–1167): names[], ids[], selected, count, and the back_name/back_id/settings_name/settings_id statics are all populated — including an unnecessary lock_state()/unlock_state() cycle — but none are passed to manifest_ui_show_zone_picker(), which takes no arguments. The manifest's own list screen contains the zone data.

  2. Error paths (lines ~936–1014): line1_msg and line2_msg are computed via snprintf throughout the error-handling branches but only status_msg is forwarded to manifest_ui_set_network_status().

Both patterns are remnants from the old dual-path code that fed explicit arrays into the legacy zone picker. The entire dead computation can be removed.

♻️ Proposed fix for the UI_INPUT_MENU branch
   if (event == UI_INPUT_MENU) {
-    const char *names[MAX_ZONES + 3]; /* +3 for Back, Settings, margin */
-    const char *ids[MAX_ZONES + 3];
-    static const char *back_name = "Back";
-    static const char *back_id = ZONE_ID_BACK;
-    static const char *settings_name = "Settings";
-    static const char *settings_id = ZONE_ID_SETTINGS;
-    int selected = 1; /* Default to first zone after Back */
-    int count = 0;
-
-    /* Add Back as first option */
-    names[count] = back_name;
-    ids[count] = back_id;
-    count++;
-
-    lock_state();
-    if (s_state.zone_count > 0) {
-      for (int i = 0; i < s_state.zone_count && count < MAX_ZONES + 2; ++i) {
-        names[count] = s_state.zones[i].name;
-        ids[count] = s_state.zones[i].id;
-        if (strcmp(s_state.zones[i].id, s_state.cfg.zone_id) == 0) {
-          selected = count;
-        }
-        count++;
-      }
-    }
-    unlock_state();
-
-    /* Add Settings as last option */
-    names[count] = settings_name;
-    ids[count] = settings_id;
-    count++;
-
     manifest_ui_show_zone_picker();
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/bridge_client.c` around lines 1135 - 1168, The UI_INPUT_MENU branch
contains dead setup code: remove the unused arrays/names/ids, the
back_/settings_ statics, selected/count variables and the
lock_state()/unlock_state() block that populates them (the code that builds
names[]/ids[] and updates selected) since manifest_ui_show_zone_picker() takes
no arguments; simply call manifest_ui_show_zone_picker() in that branch.
Likewise, remove the unused snprintf computations that build line1_msg and
line2_msg in the error-handling paths (they are never passed to
manifest_ui_set_network_status()); only keep status_msg and calls that actually
affect the manifest API. Ensure no remaining references to
names/ids/line1_msg/line2_msg remain in common/bridge_client.c and adjust
includes/variables if they become unused.

560-568: Duplicate null/error check — second block is dead code.

Lines 565–568 are an exact copy of lines 560–563; none of ret, resp, or resp_len change between the two checks. Remove the duplicate block.

♻️ Proposed fix
   if (ret != 0 || !resp || resp_len == 0) {
     platform_http_free(resp);
     return NULL;
   }
-
-  if (ret != 0 || !resp || resp_len == 0) {
-    platform_http_free(resp);
-    return NULL;
-  }
-
   manifest_t *m = malloc(sizeof(manifest_t));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/bridge_client.c` around lines 560 - 568, There is a duplicated
error/null-check block that repeats "if (ret != 0 || !resp || resp_len == 0) {
platform_http_free(resp); return NULL; }" — remove the second (redundant)
occurrence so the function only performs the check once; locate the duplicate by
searching for the conditional using variables ret, resp, resp_len and the call
to platform_http_free, delete the repeated block and keep a single, well-placed
check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@common/manifest_ui.c`:
- Around line 1580-1593: manifest_ui_hide_zone_picker can fall through to the
fallback show_screen(0) without restoring s_chrome.zone_label; ensure the zone
label is unhidden in all exit paths by calling
lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN) before or immediately
after the fallback show_screen(0) (so both the matched-nav branch and the
fallback restore the label); reference manifest_ui_hide_zone_picker,
s_chrome.zone_label, show_screen, and LV_OBJ_FLAG_HIDDEN.
- Around line 1404-1406: lv_timer_create can return NULL on failure and
s_msg_timer is dereferenced immediately; update the block that calls
lv_timer_create(msg_timer_cb, 3000, NULL) to check the return value
(s_msg_timer) before calling lv_timer_set_repeat_count(s_msg_timer, 1) and any
other timer operations; if s_msg_timer is NULL, avoid dereferencing and ensure
the status-bar auto-clear behavior still occurs (e.g., skip timer setup or
implement a safe fallback path), referencing s_msg_timer, lv_timer_create,
msg_timer_cb, and lv_timer_set_repeat_count to locate the code to change.

---

Duplicate comments:
In `@common/manifest_ui.c`:
- Around line 1642-1650: Previous double-posting issue is resolved; verify that
the thin wrappers ui_set_message, ui_set_zone_name, and ui_set_network_status
simply forward to manifest_ui_set_message / manifest_ui_set_zone_name /
manifest_ui_set_network_status and are only used from non-UI threads
(captive_portal.c, display_sleep.c, main_idf.c), while bridge_client.c calls
manifest_ui_set_* directly from its poll thread; no code change required unless
a duplicate platform_task_post_to_ui is later introduced—if so, remove the
redundant post from either the wrapper (ui_set_*) or the callee to ensure a
single post-to-UI path.

---

Nitpick comments:
In `@common/bridge_client.c`:
- Around line 1135-1168: The UI_INPUT_MENU branch contains dead setup code:
remove the unused arrays/names/ids, the back_/settings_ statics, selected/count
variables and the lock_state()/unlock_state() block that populates them (the
code that builds names[]/ids[] and updates selected) since
manifest_ui_show_zone_picker() takes no arguments; simply call
manifest_ui_show_zone_picker() in that branch. Likewise, remove the unused
snprintf computations that build line1_msg and line2_msg in the error-handling
paths (they are never passed to manifest_ui_set_network_status()); only keep
status_msg and calls that actually affect the manifest API. Ensure no remaining
references to names/ids/line1_msg/line2_msg remain in common/bridge_client.c and
adjust includes/variables if they become unused.
- Around line 560-568: There is a duplicated error/null-check block that repeats
"if (ret != 0 || !resp || resp_len == 0) { platform_http_free(resp); return
NULL; }" — remove the second (redundant) occurrence so the function only
performs the check once; locate the duplicate by searching for the conditional
using variables ret, resp, resp_len and the call to platform_http_free, delete
the repeated block and keep a single, well-placed check.

@durch
Copy link
Collaborator Author

durch commented Feb 23, 2026

Resolve conflicts

- Fix manifest_ui_hide_zone_picker fallback: restore zone_label visibility
  when default_screen not found in nav order
- Guard lv_timer_create return: check for NULL before calling
  lv_timer_set_repeat_count to prevent null dereference on OOM
- Remove dead code in UI_INPUT_MENU branch: arrays/names/ids/selected/count
  were populated but never consumed by manifest_ui_show_zone_picker()
- Remove dead line1_msg/line2_msg variables: formatted but never used,
  only status_msg is passed to manifest_ui_set_network_status()
- Remove duplicate null/error check in fetch_manifest()
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.

Caution

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

⚠️ Outside diff range comments (2)
common/manifest_ui.c (1)

1570-1598: ⚠️ Potential issue | 🟡 Minor

Inconsistent s_chrome.zone_label NULL guard across zone-picker show/hide paths.

Three call sites with different behavior:

Location Guard?
manifest_ui_show_zone_picker, line 1576 (lv_obj_add_flag) ❌ None
manifest_ui_hide_zone_picker early-return path, line 1590 (lv_obj_clear_flag) ❌ None
manifest_ui_hide_zone_picker fallback path, lines 1596-1597 if (s_chrome.zone_label)

s_chrome.zone_label is only set when build_chrome executes successfully (i.e., when lv_screen_active() returns non-NULL in manifest_ui_init). In any degenerate boot scenario where manifest_ui_init returns early, lines 1576 and 1590 will crash with a null dereference.

🛡️ Proposed fix — add guards to the two unprotected sites
 void manifest_ui_show_zone_picker(void) {
   for (int i = 0; i < s_mgr.manifest.nav.count; i++) {
     int idx = find_screen_index(s_mgr.manifest.nav.order[i]);
     if (idx >= 0 && s_mgr.manifest.screens[idx].type == SCREEN_TYPE_LIST) {
       show_screen(i);
-      lv_obj_add_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
+      if (s_chrome.zone_label)
+        lv_obj_add_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
       return;
     }
   }
 }

 void manifest_ui_hide_zone_picker(void) {
   int def = find_screen_index(s_mgr.manifest.nav.default_screen);
   if (def >= 0) {
     for (int i = 0; i < s_mgr.manifest.nav.count; i++) {
       if (find_screen_index(s_mgr.manifest.nav.order[i]) == def) {
         show_screen(i);
-        lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
+        if (s_chrome.zone_label)
+          lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
         return;
       }
     }
   }
   show_screen(0);
   if (s_chrome.zone_label)
     lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/manifest_ui.c` around lines 1570 - 1598, Add null checks for
s_chrome.zone_label before calling LVGL flag functions to avoid dereferencing a
NULL when build_chrome/manifest_ui_init didn’t set it: in
manifest_ui_show_zone_picker wrap the lv_obj_add_flag(s_chrome.zone_label,
LV_OBJ_FLAG_HIDDEN) call with an if (s_chrome.zone_label) guard, and in
manifest_ui_hide_zone_picker wrap the early-return-path
lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN) call with the same
guard; keep the existing guarded fallback path untouched.
common/bridge_client.c (1)

599-607: ⚠️ Potential issue | 🟠 Major

Blocking HTTP download on the UI thread — freezes LVGL for the duration of the artwork fetch.

ui_manifest_cb runs on the UI thread (posted via platform_task_post_to_ui). Calling manifest_ui_set_artwork here issues a synchronous platform_http_get_image request (100 KB RGB565 at ESP32 WiFi rates ≈ 150–800 ms) before returning. During that window lv_timer_handler() is not ticked: arcs stop animating, touch events queue up unprocessed, and the display appears frozen.

The artwork fetch should be performed in the bridge polling thread (or a dedicated task), with only the final LVGL widget update (lv_image_set_src / lv_obj_clear_flag) posted to the UI thread.

💡 Sketch of the fix

In bridge_client.c — kick off artwork download in the polling thread (already off the UI thread):

 static void ui_manifest_cb(void *arg) {
   manifest_t *m = arg;
   if (!m) return;

   s_last_known_volume      = m->fast.volume;
   s_last_known_volume_min  = m->fast.volume_min;
   s_last_known_volume_max  = m->fast.volume_max;
   s_last_known_volume_step = m->fast.volume_step;

   manifest_ui_update(m);

-  // Fetch artwork whenever screens are updated (SHA changed)
-  for (int i = 0; i < m->screen_count; i++) {
-    if (m->screens[i].type == SCREEN_TYPE_MEDIA) {
-      const char *url = m->screens[i].data.media.image_url;
-      if (url[0]) {
-        manifest_ui_set_artwork(url);
-      }
-      break;
-    }
-  }
   free(m);
 }

In bridge_poll_thread — after post_manifest_update, fetch artwork on the poll thread and post only the LVGL update:

// After post_manifest_update(manifest):
if (ok) {
    for (int i = 0; i < manifest->screen_count; i++) {
        if (manifest->screens[i].type == SCREEN_TYPE_MEDIA) {
            const char *url = manifest->screens[i].data.media.image_url;
            if (url[0])
                manifest_ui_set_artwork(url); // HTTP on poll thread, LVGL update posted internally
            break;
        }
    }
}

manifest_ui_set_artwork already posts the final lv_image_set_src step to the UI thread via platform_task_post_to_ui (or can be made to do so); only the platform_http_get_image call needs to move off the UI thread.

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

In `@common/bridge_client.c` around lines 599 - 607, The manifest artwork download
is blocking the UI because ui_manifest_cb calls manifest_ui_set_artwork (which
performs platform_http_get_image) on the UI thread; move the HTTP fetch off the
UI thread by invoking platform_http_get_image from the bridge_poll_thread (or
polling task) after post_manifest_update succeeds, and keep only the LVGL update
(lv_image_set_src / lv_obj_clear_flag) posted back to the UI via
platform_task_post_to_ui inside manifest_ui_set_artwork; update ui_manifest_cb
to only detect the media URL and defer the fetch to bridge_poll_thread (or call
a new helper from the poll thread) so platform_http_get_image is never run on
the UI thread.
🧹 Nitpick comments (1)
common/bridge_client.c (1)

1253-1293: manifest_ui_set_network_status called while s_state_lock is held — fragile lock ordering.

Lines 1275 and 1290 (and line 1080 in bridge_client_handle_input) call manifest_ui_set_network_status from inside lock_state(). That function calls strdup (heap allocation under a lock) followed by platform_task_post_to_ui, which acquires s_ui_mutex.

This creates a consistent lock-ordering chain s_state_lock → s_ui_mutex. No current UI callback acquires s_state_lock, so there is no deadlock today. However:

  • Heap allocation while holding a mutex inflates worst-case hold time.
  • Any future UI callback that calls lock_state() would immediately deadlock.

Prefer releasing the lock before posting to the UI thread:

♻️ Proposed refactor for bridge_client_set_network_ready
 void bridge_client_set_network_ready(bool ready) {
   s_network_ready = ready;
+  const char *net_status_msg = NULL;

   lock_state();
   if (ready) {
     ...
     if (s_device_state != DEVICE_STATE_OPERATIONAL) {
       ...
       s_device_state = DEVICE_STATE_CONNECTED;
-      manifest_ui_set_network_status("Loading zones...");
+      net_status_msg = "Loading zones...";
     }
     ...
   } else {
     ...
     if (new_state == DEVICE_STATE_RECONNECTING) {
-      manifest_ui_set_network_status("Reconnecting...");
+      net_status_msg = "Reconnecting...";
     }
   }
   unlock_state();
+
+  if (net_status_msg)
+    manifest_ui_set_network_status(net_status_msg);
 }

Apply the same pattern to the lock_state() block in bridge_client_handle_input (line 1080).

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

In `@common/bridge_client.c` around lines 1253 - 1293, The code calls
manifest_ui_set_network_status while holding s_state_lock (via lock_state()),
causing heap allocation and cross-lock ordering with s_ui_mutex; fix
bridge_client_set_network_ready (and the call site in
bridge_client_handle_input) by preparing the desired status text while holding
the state lock (e.g., choose an immutable const char* or copy a small
enum/value), then call unlock_state() before invoking
manifest_ui_set_network_status so the strdup/allocation and
platform_task_post_to_ui happen without s_state_lock held, preserving lock
ordering and minimizing time holding the mutex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@common/bridge_client.c`:
- Around line 599-607: The manifest artwork download is blocking the UI because
ui_manifest_cb calls manifest_ui_set_artwork (which performs
platform_http_get_image) on the UI thread; move the HTTP fetch off the UI thread
by invoking platform_http_get_image from the bridge_poll_thread (or polling
task) after post_manifest_update succeeds, and keep only the LVGL update
(lv_image_set_src / lv_obj_clear_flag) posted back to the UI via
platform_task_post_to_ui inside manifest_ui_set_artwork; update ui_manifest_cb
to only detect the media URL and defer the fetch to bridge_poll_thread (or call
a new helper from the poll thread) so platform_http_get_image is never run on
the UI thread.

In `@common/manifest_ui.c`:
- Around line 1570-1598: Add null checks for s_chrome.zone_label before calling
LVGL flag functions to avoid dereferencing a NULL when
build_chrome/manifest_ui_init didn’t set it: in manifest_ui_show_zone_picker
wrap the lv_obj_add_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN) call with an
if (s_chrome.zone_label) guard, and in manifest_ui_hide_zone_picker wrap the
early-return-path lv_obj_clear_flag(s_chrome.zone_label, LV_OBJ_FLAG_HIDDEN)
call with the same guard; keep the existing guarded fallback path untouched.

---

Nitpick comments:
In `@common/bridge_client.c`:
- Around line 1253-1293: The code calls manifest_ui_set_network_status while
holding s_state_lock (via lock_state()), causing heap allocation and cross-lock
ordering with s_ui_mutex; fix bridge_client_set_network_ready (and the call site
in bridge_client_handle_input) by preparing the desired status text while
holding the state lock (e.g., choose an immutable const char* or copy a small
enum/value), then call unlock_state() before invoking
manifest_ui_set_network_status so the strdup/allocation and
platform_task_post_to_ui happen without s_state_lock held, preserving lock
ordering and minimizing time holding the mutex.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bff1870 and 6db7367.

📒 Files selected for processing (2)
  • common/bridge_client.c
  • common/manifest_ui.c

# Conflicts:
#	common/app_main.c
#	common/bridge_client.c
#	common/ui.c
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)
common/bridge_client.c (1)

1198-1278: ⚠️ Potential issue | 🟠 Major

Remove the post_ui_* callback layer in the manifest branch to prevent double-posting.

The manifest path still calls post_ui_status/post_ui_message/post_ui_zone_name, and those callbacks invoke manifest_ui_set_* (which already does strdup + platform_task_post_to_ui). This reintroduces double queueing and extra heap churn for every status/message update.

Suggested fix direction
- post_ui_status(ok);
+ manifest_ui_set_status(ok);

- post_ui_zone_name(label_copy);
+ manifest_ui_set_zone_name(label_copy);

- post_ui_message("Loading zone...");
+ manifest_ui_set_message("Loading zone...");

Then remove post_ui_* and ui_*_cb helpers that only forward into manifest_ui_set_*.

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

In `@common/bridge_client.c` around lines 1198 - 1278, The manifest branch is
still invoking the indirect post_ui_* callbacks which forward into
manifest_ui_set_* causing double-queueing and redundant strdup; remove the
post_ui_status/post_ui_message/post_ui_zone_name calls in the manifest path and
call manifest_ui_set_status/manifest_ui_set_message/manifest_ui_set_zone_name
directly (preserve existing ownership semantics and strdup behavior), and delete
the now-unneeded post_ui_* helper functions and any ui_*_cb wrappers that merely
forwarded into manifest_ui_set_* to eliminate the extra allocation and task
post.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@common/bridge_client.c`:
- Around line 1198-1278: The manifest branch is still invoking the indirect
post_ui_* callbacks which forward into manifest_ui_set_* causing double-queueing
and redundant strdup; remove the
post_ui_status/post_ui_message/post_ui_zone_name calls in the manifest path and
call manifest_ui_set_status/manifest_ui_set_message/manifest_ui_set_zone_name
directly (preserve existing ownership semantics and strdup behavior), and delete
the now-unneeded post_ui_* helper functions and any ui_*_cb wrappers that merely
forwarded into manifest_ui_set_* to eliminate the extra allocation and task
post.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6db7367 and da530eb.

📒 Files selected for processing (3)
  • common/app_main.c
  • common/bridge_client.c
  • common/manifest_ui.c

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.

Remove legacy ui.c and USE_MANIFEST conditionals

1 participant