Skip to content

New Apps - Lot 1#27

Merged
KenVanHoeylandt merged 7 commits intoTactilityProject:mainfrom
Shadowtrance:new-apps-lot1
Feb 19, 2026
Merged

New Apps - Lot 1#27
KenVanHoeylandt merged 7 commits intoTactilityProject:mainfrom
Shadowtrance:new-apps-lot1

Conversation

@Shadowtrance
Copy link
Contributor

@Shadowtrance Shadowtrance commented Feb 18, 2026

Trying to mix it up a little with some fun, useful and just weird. Adds more new apps...
Brainfuck
Breakout
Magic 8 Ball
Todo List

Also re-organized the app name part of main.yml

Summary by CodeRabbit

  • New Features

    • Brainfuck interpreter: editor, built‑in examples, SD card script loading, execution stats
    • Breakout: full brick‑breaking game with levels, power‑ups, sound toggle, and high‑score persistence
    • Magic 8‑Ball: animated response app with non‑repeating answers
    • Todo List: add/complete/delete tasks with persistence and task counter
  • Chores

    • CI workflow expanded to build the additional applications above

Trying to mix it up a little with some fun, useful and just weird.
Adds more new apps...
Brainfuck
Breakout
Magic 8 Ball
Todo List

Also re-organised the app name part of main.yml
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 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

Walkthrough

The PR adds four new applications—Brainfuck, Breakout, Magic8Ball, and TodoList—each with CMake project files, component source and header files, manifest.properties, and README documentation. It also updates the GitHub Actions workflow build matrix to include additional app_name entries: Brainfuck, Breakout, Magic8Ball, TodoList, and Todo. No other workflow steps were changed.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'New Apps - Lot 1' accurately describes the primary change: adding four new applications (Brainfuck, Breakout, Magic 8 Ball, Todo List) to the repository.

✏️ 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

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: 6

🧹 Nitpick comments (5)
Apps/Brainfuck/manifest.properties (1)

7-10: Consider adding a description field for consistency.

Both Breakout and TodoList manifests include a description key. Brainfuck omits it, leaving the manifest incomplete relative to its siblings.

✏️ Suggested addition
 name=Brainfuck interpreter
+description=Brainfuck esoteric language interpreter
Apps/TodoList/main/Source/TodoList.h (1)

26-27: Consider heap-allocating items to reduce object footprint on RAM-constrained targets.

TodoItem items[MAX_TODOS] embeds ~6.6 KB (50 × ~132 bytes) directly in every TodoList instance. On ESP32 targets with limited heap, this is a notable fixed cost even when the app has few or zero tasks.

♻️ Proposed refactor to dynamic allocation
-    // Data
-    TodoItem items[MAX_TODOS] = {};
-    int count = 0;
+    // Data
+    std::unique_ptr<TodoItem[]> items;
+    int count = 0;

Initialize in onShow / constructor:

+    items = std::make_unique<TodoItem[]>(MAX_TODOS);
Apps/Breakout/main/Source/Breakout.h (1)

17-27: static constexpr array in a header causes per-TU duplication.

The scalar constants on lines 17–24 are fine — they're optimized to immediates. However, COLOR_SCORES[] (line 27) is an array, and static constexpr at namespace scope gives it internal linkage, meaning every translation unit that includes this header gets its own copy.

If this header is included from multiple .cpp files, consider using inline constexpr (C++17) to share a single definition across TUs.

Suggested change
-static constexpr int COLOR_SCORES[] = { 50, 60, 70, 80, 90, 100, 110, 120 };
+inline constexpr int COLOR_SCORES[] = { 50, 60, 70, 80, 90, 100, 110, 120 };
Apps/TodoList/main/Source/TodoList.cpp (1)

126-156: Hoist UI density computation out of the per-item loop.

lvgl_get_ui_density() and getToolbarHeight() return the same values on every iteration. Computing them once before the loop avoids repeated calls.

♻️ Suggested refactor
 void TodoList::rebuildList() {
     if (!list) return;
 
     lv_obj_clean(list);
 
     if (count == 0) {
         lv_list_add_text(list, "No tasks yet. Add one below!");
     }
 
+    UiDensity uiDensity = lvgl_get_ui_density();
+    int toolbarHeight = getToolbarHeight(uiDensity);
+    int btnSize = (uiDensity == LVGL_UI_DENSITY_COMPACT) ? toolbarHeight - 8 : toolbarHeight - 6;
+
     for (int i = 0; i < count; i++) {
         TodoItem* item = &items[i];
 
         char display[MAX_TEXT_LEN + 8];
         snprintf(display, sizeof(display), "%s %s", item->done ? LV_SYMBOL_OK : LV_SYMBOL_DUMMY, item->text);
 
         lv_obj_t* btn = lv_list_add_button(list, NULL, display);
 
         if (item->done) {
             lv_obj_set_style_text_opa(btn, LV_OPA_50, LV_PART_MAIN);
         }
 
         lv_obj_add_event_cb(btn, onItemClicked, LV_EVENT_SHORT_CLICKED, (void*)(intptr_t)i);
 
-        UiDensity uiDensity = lvgl_get_ui_density();
-        int toolbarHeight = getToolbarHeight(uiDensity);
-        int btnSize = (uiDensity == LVGL_UI_DENSITY_COMPACT) ? toolbarHeight - 8 : toolbarHeight - 6;
-
         lv_obj_t* delBtn = lv_button_create(btn);
Apps/Brainfuck/main/Source/Brainfuck.h (1)

7-20: Consider the ~6 KB BfVM embedded in the class.

BfVM contains tape[4096] + output[2048] ≈ 6 KB. Since Brainfuck objects are heap-allocated via registerApp<Brainfuck>(), this is fine on ESP32. Just flagging for awareness in case the allocation strategy ever changes.

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)
Apps/TodoList/main/Source/TodoList.cpp (2)

141-143: Hoist density/height/button-size out of the loop.

lvgl_get_ui_density(), getToolbarHeight(), and btnSize are recomputed on every iteration even though they are loop-invariant. Move them above the for loop (consistent with how onShow already does this at lines 251-264).

♻️ Proposed refactor
 void TodoList::rebuildList() {
     if (!list) return;
 
     lv_obj_clean(list);
 
+    UiDensity uiDensity = lvgl_get_ui_density();
+    int toolbarHeight   = getToolbarHeight(uiDensity);
+    int btnSize = (uiDensity == LVGL_UI_DENSITY_COMPACT) ? toolbarHeight - 8 : toolbarHeight - 6;
+
     if (count == 0) {
         lv_list_add_text(list, "No tasks yet. Add one below!");
     }
 
     for (int i = 0; i < count; i++) {
         ...
-        UiDensity uiDensity    = lvgl_get_ui_density();
-        int toolbarHeight      = getToolbarHeight(uiDensity);
-        int btnSize = (uiDensity == LVGL_UI_DENSITY_COMPACT) ? toolbarHeight - 8 : toolbarHeight - 6;
         ...
     }

176-177: Redundant lv_event_get_code guard in onDeleteClicked.

The callback is registered for LV_EVENT_CLICKED only (line 156), so the runtime event-code check on lines 176-177 can never be false and is dead code.

♻️ Proposed cleanup
 void TodoList::onDeleteClicked(lv_event_t* e) {
     if (!g_instance) return;
-    lv_event_code_t code = lv_event_get_code(e);
-    if (code != LV_EVENT_CLICKED) return;
-
     int idx = (int)(intptr_t)lv_event_get_user_data(e);

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: 5

🧹 Nitpick comments (3)
Apps/Brainfuck/main/Source/Brainfuck.cpp (2)

82-90: ciStrcmp reimplements strcasecmp.

ESP-IDF / newlib provides strcasecmp from <strings.h>. The hand-rolled ASCII loop can be replaced directly.

♻️ Proposed refactor
+#include <strings.h>   // for strcasecmp
 ...
-static int ciStrcmp(const char* a, const char* b) {
-    while (*a && *b) {
-        int ca = (*a >= 'A' && *a <= 'Z') ? *a + 32 : *a;
-        int cb = (*b >= 'A' && *b <= 'Z') ? *b + 32 : *b;
-        if (ca != cb) return ca - cb;
-        a++; b++;
-    }
-    return (unsigned char)*a - (unsigned char)*b;
-}

Then at call sites replace ciStrcmp(...) with strcasecmp(...).


110-119: O(n²) bracket matching — consider a precomputed jump table.

bfFindBracket scans linearly on every [/] instruction. For a program with many nested loops this degrades to O(n²) before the cycle cap fires. A one-pass jump table (built once at the top of bfRun) reduces each lookup to O(1).

♻️ Sketch of a precomputed jump-table approach
-static int bfFindBracket(const char* code, int pos, int dir) { ... }

 void Brainfuck::bfRun(const char* code) {
     int len = strlen(code);
+    // Build bracket jump table O(n)
+    int* jumps = (int*)calloc(len, sizeof(int));
+    if (jumps) {
+        int stack[512]; int top = 0;
+        for (int i = 0; i < len; i++) {
+            if (code[i] == '[') { stack[top++] = i; }
+            else if (code[i] == ']' && top > 0) {
+                int open = stack[--top];
+                jumps[open] = i; jumps[i] = open;
+            }
+        }
+    }
     while (vm.pc < len && !vm.error) {
         ...
         case '[':
             if (vm.tape[vm.ptr] == 0) {
-                int target = bfFindBracket(code, vm.pc, 1);
+                int target = jumps ? jumps[vm.pc] : -1;
                 ...
             }
         case ']':
             if (vm.tape[vm.ptr] != 0) {
-                int target = bfFindBracket(code, vm.pc, -1);
+                int target = jumps ? jumps[vm.pc] : -1;
                 ...
             }
     }
+    free(jumps);
 }
Apps/TodoList/main/Source/TodoList.h (1)

26-29: Add timer handle member to allow cancellation in onHide.

scheduleRebuild() creates an lv_timer_t but discards the returned handle, leaving no way to cancel pending timers before the view is hidden. While rebuildList() has a guard (if (!list) return;) that prevents a crash, the timer callback still executes unnecessarily after onHide() nulls the list pointer.

Store the timer handle as a class member and cancel it in onHide() to avoid wasted callback invocations and follow the pattern established in other apps (Breakout, TamaTac).

🔒 Proposed fix

In TodoList.h, add:

     bool rebuildPending = false;
+    lv_timer_t* rebuildTimer = nullptr;

In TodoList.cppscheduleRebuild():

 void TodoList::scheduleRebuild() {
     if (rebuildPending) return;
     rebuildPending = true;
-    lv_timer_t* t = lv_timer_create(onDeferredRebuild, 0, this);
-    lv_timer_set_repeat_count(t, 1);
+    rebuildTimer = lv_timer_create(onDeferredRebuild, 0, this);
+    lv_timer_set_repeat_count(rebuildTimer, 1);
 }

In TodoList.cpponDeferredRebuild():

 void TodoList::onDeferredRebuild(lv_timer_t* timer) {
     TodoList* self = static_cast<TodoList*>(lv_timer_get_user_data(timer));
     if (self) {
+        self->rebuildTimer = nullptr;
         self->rebuildPending = false;
         self->rebuildList();
     }
 }

In TodoList.cpponHide():

 void TodoList::onHide(AppHandle app) {
+    if (rebuildTimer) {
+        lv_timer_del(rebuildTimer);  // lv_timer_delete in LVGL v9.x
+        rebuildTimer = nullptr;
+    }
     list = nullptr;
     inputRow = nullptr;
     inputTa = nullptr;
     countLabel = nullptr;
     g_instance = nullptr;
 }

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 (2)
Apps/Brainfuck/main/Source/Brainfuck.cpp (1)

349-351: Dead-code ternary operators — the fallback branches are unreachable after the len < 3 guard.

Since line 347 already guarantees len >= 3, the conditional expressions on lines 349–350 always take the true branch.

♻️ Proposed simplification
-    const char* ext3 = (len >= 3) ? &name[len - 3] : "";
-    const char* ext2 = (len >= 2) ? &name[len - 2] : "";
+    const char* ext3 = &name[len - 3];
+    const char* ext2 = &name[len - 2];
Apps/TodoList/main/Source/TodoList.cpp (1)

26-28: getToolbarHeight duplicates Breakout.cpp — extract to a shared utility.

The helper on lines 26-28 is functionally identical to Apps/Breakout/main/Source/Breakout.cpp:99-105. If a third app repeats this pattern it becomes a maintenance liability.

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

@KenVanHoeylandt KenVanHoeylandt merged commit a75279c into TactilityProject:main Feb 19, 2026
16 checks passed
@KenVanHoeylandt
Copy link
Contributor

Thank you!

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.

2 participants

Comments