Skip to content

refactor: dev#11

Open
pablohc wants to merge 44 commits into
masterfrom
dev
Open

refactor: dev#11
pablohc wants to merge 44 commits into
masterfrom
dev

Conversation

@pablohc
Copy link
Copy Markdown
Owner

@pablohc pablohc commented May 17, 2026

Summary

  • What is the goal of this PR? (e.g., Implements the new feature for file uploading.)
  • What changes are included?

Additional Context

  • Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
    specific areas to focus on).

AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? < YES | PARTIALLY | NO >

Summary by CodeRabbit

  • New Features

    • Sleep Screen modes updated (Logo, Custom, Cover Fit/Crop/Custom, Blank) and new Sleep Screen Filter options (None/Contrast/Negative)
    • Home Screen Cover controls: enable/timeout/disable with regenerate/delete actions
    • Power-button quick menu (sleep, refresh, screenshot)
    • Reader menu: per-book cover enable/disable and cover regeneration
  • Bug Fixes

    • More robust cover/thumbnail generation with timeouts and better cleanup
  • Documentation

    • User guide updated for new sleep/home cover settings
  • Localization

    • Added/updated translation strings for new settings and a 3‑minute timeout option

pablohc and others added 30 commits May 10, 2026 01:20
Propagate uint32_t deadline through the full rendering stack:
- Epub::generateThumbBmp → readItemContentsToStream → ZipFile::readFileToStream
- ZipFile: deadline check in stored (read loop) and deflated (inflate loop) paths
- JpegToBmpConverter: deadline check in JPEGDEC bmpDrawCallback (per MCU row)
- PngToBmpConverter: deadline check per scanline in decode loop
- All functions use deadline=0 default for backward compatibility
- Uses millis rollover-safe comparison: static_cast<int32_t>(millis() - deadline) >= 0
…and drawBitmap fallback

- GfxRenderer: drawBitmap/drawBitmap1Bit now return bool for error detection
- UITheme: add getAllCoverHeights() for thumbnail generation
- BaseTheme: add BookCoverParams struct, drawClassicalBookCover method, stale
  buffer clearing, and drawBitmap failure fallback to placeholder
- LyraTheme/Lyra3CoversTheme: gate cover rendering on coverDisabled flag
- Replace Bookerly font references with Noto Serif (master licensing change)
Check return value of readItemContentsToStream in generateThumbBmp
for both JPG and PNG paths to properly handle timeout/extraction failures.
- GfxRenderer: drawBitmap/drawBitmap1Bit return bool instead of void

- JpegToBmpConverter/PngToBmpConverter: add missing deadline param to internal callers

- Lyra3CoversTheme: fix .values. access and handle drawBitmap failure
- Add confirmation popup when switching Home Screen Cover to Disabled
  with Disable (future only) and Remove (delete ePub thumbnails) options
- Add drawDialogBackground() to BaseTheme with theme-aware styling
- Move Home Screen Cover setting after UI Theme in Display settings
- Rename setting label from Home Cover to Home Screen Cover
- Document Home Screen Cover in USER_GUIDE.md section 3.6.1
…ettings

Add drawPopupSelection virtual method to BaseTheme with per-theme overrides:
- Classic: solid black fillRect with white inverted text
- Lyra: light gray fillRoundedRect with black text
- RoundedRaff: black fillRoundedRect with white inverted text

Simplify cover popup: single-line descriptions, 20% wider dialog,
centered text in selection highlight, no wrappedText logic needed.
…heme rendering

- Rebuild EPUB cache when missing (load(true,true)) so generateThumbBmp succeeds
- Don't re-disable covers on forced regeneration failure (else if !isBookForced)
- Add missing hasCover=false when openFileForRead fails in Lyra3CoversTheme
- Set coverRendered=true unconditionally to prevent infinite SD re-reads
- Fix tileX to include rect.x in Lyra3CoversTheme, LyraTheme, RoundedRaffTheme
- Move coverWidth inside anonymous namespace in RoundedRaffTheme (fix linkage)
- Swap coverRendered/coverBufferStored order in BaseTheme
- Clear forceRenderCoverPath on Delete All in SettingsActivity
- Use translated button labels instead of hardcoded English
- Handle XTC files in deleteAllCoverThumbs()
- Remove dead handleCoverDisablePopup() declaration
- Check readItemContentsToStream return in Epub generateCoverBmp
- Clean up file handles and temp files in all error paths
- Use nothrow allocation in PngToBmpConverter
An empty marker file caused Storage.exists() to return true, preventing
future regeneration attempts for books with unsupported cover formats.
The mappedInput.mapLabels style made hints indistinguishable from
surrounding text; bracketed labels improve readability.
Adds a 5th short press power button option 'Action Menu' that opens a
navigable popup with Sleep, Refresh Screen, and Screenshot actions.
Includes theme-aware popup rendering (Classic/Lyra/RoundedRaff) using
drawDialogBackground and drawPopupSelection virtual methods.
feat: apply custom cross logo to boot and sleep screens
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR refactors sleep screen rendering with new modes and filters, adds interactive home cover management, implements deadline-aware image generation, enables bitmap rendering success reporting, removes the RoundedRaff theme, and integrates a power button menu activity.

Changes

Sleep Screen Modes, Cover Management, and Bitmap Rendering

Layer / File(s) Summary
Settings enums, migration, and persistence
src/CrossPointSettings.h, src/CrossPointSettings.cpp, src/JsonSettingsIO.cpp, src/RecentBooksStore.h, src/RecentBooksStore.cpp, src/SettingsList.h
Introduce new sleep-screen mode/filter enums and COVER_MODE, migrate legacy binary/JSON settings, add coverDisabled per recent-book and persistence.
Deadline-aware streaming & converters
lib/ZipFile/*, lib/JpegToBmpConverter/*, lib/PngToBmpConverter/*, lib/Epub/*
Add optional deadline parameter through Zip/JPEG/PNG/Epub read paths; enforce timeouts and handle allocation failures without exceptions.
Renderer success reporting
lib/GfxRenderer/GfxRenderer.h, lib/GfxRenderer/GfxRenderer.cpp
Change drawBitmap/drawBitmap1Bit to return bool to signal success/failure for caller fallback logic.
SleepActivity: modes & filters
src/activities/boot_sleep/SleepActivity.h, src/activities/boot_sleep/SleepActivity.cpp, src/images/CrossLarge.h
Dispatch on new sleep-screen modes and filters, add renderLogoSleepScreen, make cover/custom/bitmap renderers return success booleans and support crop gating.
Recent books & cover state
src/RecentBooksStore.h, src/RecentBooksStore.cpp, src/JsonSettingsIO.cpp
Add coverDisabled to RecentBook, compute defaults on add, provide setCoverDisabled and updatePath, and persist coverDisabled in JSON.
Settings: interactive Home Cover popup
src/activities/settings/SettingsActivity.h, src/activities/settings/SettingsActivity.cpp
Add cover options popup (regenerate/delete/enable/disable) with input handling and actions that trigger regeneration or deletion and update recent-book state.
Cover generation and home/reader flows
src/activities/home/HomeActivity.cpp, src/activities/reader/*, src/components/UITheme.*, src/components/themes/*
Refactor home cover loading with forced regeneration, optional generation deadlines, respect coverDisabled, add UITheme::getAllCoverHeights(), and wire reader menu actions to toggle/regenerate/delete covers.
Power button menu
src/activities/util/PowerButtonMenuActivity.h, src/activities/util/PowerButtonMenuActivity.cpp, src/main.cpp
Add PowerButtonMenuActivity for sleep/refresh/screenshot actions; main loop dispatches pendingMenuAction and opens menu on configured short-power press.
Themes and UI helpers
src/components/UITheme.*, src/components/themes/BaseTheme.*, src/components/themes/lyra/*, removed RoundedRaff files
Remove RoundedRaff/Lyra3Covers variants, add BaseTheme BookCoverParams and popup/dialog helpers, Lyra overrides, and theme-safe-area/centering helpers.
Docs & translations
USER_GUIDE.md, lib/I18n/translations/*.yaml
Update USER_GUIDE sleep-screen and home-cover docs; add/remove translation keys to match new settings UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through pixels, chased a blinking dream,
New sleep modes, cover popups, and a streaming scheme,
Deadlines tick-tock while bitmaps race,
Renderers now answer true — no silent embrace,
RoundedRaff hopped off — Lyra takes the lead, hooray!

✨ 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 dev
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch dev

@pablohc pablohc changed the title dev refactor: dev May 17, 2026
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: 9

Caution

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

⚠️ Outside diff range comments (1)
src/activities/boot_sleep/SleepActivity.cpp (1)

162-228: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Change renderBitmapSleepScreen() to return bool and propagate renderer failures to callers.

The method currently returns void and ignores failures from renderer.drawBitmap(), which returns bool. Meanwhile, renderCustomSleepScreen() and renderCoverSleepScreen() already return bool to signal success/failure to their callers.

When header parsing succeeds but drawing fails (corrupt/unsupported bitmap), renderBitmapSleepScreen() has no way to report the failure. The calling methods then return true anyway, suppressing the fallback-to-logo logic. Other code in BaseTheme, LyraTheme, and Lyra3CoversTheme correctly checks drawBitmap() return values.

Update renderBitmapSleepScreen() to return bool, have it return false if any drawBitmap() call fails (lines 195, 207, 213), and update both callers to check and propagate that return value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/boot_sleep/SleepActivity.cpp` around lines 162 - 228,
renderBitmapSleepScreen currently returns void and ignores the boolean result of
renderer.drawBitmap(), so drawing failures aren't propagated and callers
(renderCustomSleepScreen, renderCoverSleepScreen) incorrectly treat failures as
success; change renderBitmapSleepScreen to return bool, check the return value
of each renderer.drawBitmap(...) call (the initial draw and the two grayscale
draws) and return false immediately if any call fails, otherwise return true at
the end, then update renderCustomSleepScreen and renderCoverSleepScreen to check
and propagate the new bool return value from renderBitmapSleepScreen so drawing
failures trigger the fallback logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/PngToBmpConverter/PngToBmpConverter.cpp`:
- Around line 643-646: The scaling path currently allocates rowAccum and
rowCount with new (std::nothrow) but never checks for null, which causes a crash
on OOM; inside the needsScaling branch (where rowAccum, rowCount and
nextOutY_srcStart are set) add null checks after each allocation, free any
partially allocated buffer (delete[] rowAccum and/or delete[] rowCount), set
pointers to nullptr, and return or propagate a clean error (e.g., return false
or set an error state) from the enclosing function (the function containing the
needsScaling logic in PngToBmpConverter.cpp) so the caller can handle allocation
failure instead of dereferencing a null pointer. Ensure all code paths that
assume these buffers exist only run after successful allocation.

In `@lib/ZipFile/ZipFile.cpp`:
- Around line 444-445: ZipFile::readFileToStream must guard against a zero
chunkSize to avoid non-progress loops; add an early check (before creating
ScopedOpenClose or immediately after) that if chunkSize == 0 the function
returns false (or fails fast), and include an appropriate debug/error log if
available; locate the check in ZipFile::readFileToStream around the
ScopedOpenClose usage and return early when chunkSize is zero.

In `@src/activities/boot_sleep/SleepActivity.cpp`:
- Around line 50-63: renderLogoSleepScreen() currently calls
renderer.invertScreen() when SETTINGS.sleepScreenFilter is not FILTER_NEGATIVE,
which is the inverse of how renderBlankSleepScreen() and
renderBitmapSleepScreen() handle FILTER_NEGATIVE; change the branch in
renderLogoSleepScreen() so that renderer.invertScreen() is called only when
SETTINGS.sleepScreenFilter ==
CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE to match the other
sleep-screen paths and ensure correct polarity for the logo fallback.

In `@src/activities/home/HomeActivity.cpp`:
- Around line 97-107: The current logic treats any false from
epub.generateThumbBmp(...) as a permanent failure and persists coverDisabled;
change epub.generateThumbBmp to return a richer result (e.g., a struct or
enum/tuple like {bool success; bool timedOut}) so callers can distinguish
timeout vs real failure, then update this call site (the loadRecentCovers block
using deadline, isBookForced, COVER_RENDER_TIMEOUT_MS, epub.generateThumbBmp,
RECENT_BOOKS.setCoverDisabled, and book.coverDisabled) to only persist/set
coverDisabled when success==false AND timedOut==false (i.e., a non-timeout
failure), leaving timed-out attempts as transient (do not call
RECENT_BOOKS.setCoverDisabled nor set book.coverDisabled on timedOut); keep
existing behavior for successful renders and for forced books.
- Around line 76-77: The loop that regenerates covers is capped to only index 0
when forceRegenerate is false (bookCount is set to 1); change bookCount logic so
it iterates over all recentBooks (or up to the active theme's requested count)
instead of just 1. Specifically, update the calculation of bookCount (currently:
int bookCount = forceRegenerate ? static_cast<int>(recentBooks.size()) : 1;) to
compute the full desired count (e.g., int bookCount =
static_cast<int>(recentBooks.size()); or int bookCount =
std::min(static_cast<int>(recentBooks.size()), themeRequestedCount);) so the
for-loop (for (int bi = 0; bi < bookCount && bi <
static_cast<int>(recentBooks.size()); bi++)) will regenerate covers for books
0..N-1 when not forcing a full regenerate.

In `@src/activities/reader/XtcReaderActivity.cpp`:
- Around line 50-52: Guard setting APP_STATE.pendingCoverGeneration behind a
valid reader instance: check the reader pointer (xtc or the reader instance used
in XtcReaderActivity) is non-null/valid before assigning
APP_STATE.pendingCoverGeneration = true, leaving
APP_STATE.readerActivityLoadCount = 0 and APP_STATE.saveToFile() as needed; only
queue cover generation when the reader is confirmed valid to avoid enqueuing
stale work.

In `@src/components/themes/BaseTheme.cpp`:
- Around line 484-487: The code currently sets coverRendered = true immediately
after drawing and then calls storeCoverBuffer(), which can fail and leave
coverRendered true without a stored buffer; change the flow so coverRendered is
only set when storeCoverBuffer() returns success (e.g., assign coverBufferStored
= storeCoverBuffer() and then set coverRendered = coverBufferStored), ensuring
renderer.drawBitmap/drawRect are followed by a successful buffer snapshot before
marking the cover as rendered.

In `@src/images/CrossLarge.h`:
- Line 1: The file CrossLarge.h contains a UTF-8 BOM before the directive;
remove the BOM so the very first byte is '#' and the line reads "`#pragma` once"
exactly, then save the file as UTF-8 without BOM (e.g., using your editor's
"Save without BOM" or re-encode the file); this ensures the preprocessor sees
the `#pragma` once token correctly in CrossLarge.h.

In `@src/JsonSettingsIO.cpp`:
- Around line 243-282: The migration block is triggering for new-format files
because it checks doc["sleepScreen"] (shared by old and new formats); change the
guard so the legacy conversion runs only when legacy-only keys exist (e.g.,
doc["sleepScreenCoverMode"] or doc["sleepScreenCoverFilter"]) and remove the
doc["sleepScreen"] check; update the if condition around the migration in
JsonSettingsIO.cpp to check only !doc["sleepScreenCoverMode"].isNull() ||
!doc["sleepScreenCoverFilter"].isNull(), leaving the rest of the mapping logic
that assigns s.sleepScreen and s.sleepScreenFilter (and uses
CrossPointSettings::SLEEP_SCREEN_MODE / _FILTER enums) unchanged so
current-format sleepScreen values are not reinterpreted.

---

Outside diff comments:
In `@src/activities/boot_sleep/SleepActivity.cpp`:
- Around line 162-228: renderBitmapSleepScreen currently returns void and
ignores the boolean result of renderer.drawBitmap(), so drawing failures aren't
propagated and callers (renderCustomSleepScreen, renderCoverSleepScreen)
incorrectly treat failures as success; change renderBitmapSleepScreen to return
bool, check the return value of each renderer.drawBitmap(...) call (the initial
draw and the two grayscale draws) and return false immediately if any call
fails, otherwise return true at the end, then update renderCustomSleepScreen and
renderCoverSleepScreen to check and propagate the new bool return value from
renderBitmapSleepScreen so drawing failures trigger the fallback logic.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f347b34-a0e8-4241-b7ae-5f16d83e05f3

📥 Commits

Reviewing files that changed from the base of the PR and between 061c768 and cf1b2ad.

📒 Files selected for processing (53)
  • USER_GUIDE.md
  • lib/Epub/Epub.cpp
  • lib/Epub/Epub.h
  • lib/GfxRenderer/GfxRenderer.cpp
  • lib/GfxRenderer/GfxRenderer.h
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/french.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/JpegToBmpConverter/JpegToBmpConverter.cpp
  • lib/JpegToBmpConverter/JpegToBmpConverter.h
  • lib/PngToBmpConverter/PngToBmpConverter.cpp
  • lib/PngToBmpConverter/PngToBmpConverter.h
  • lib/ZipFile/ZipFile.cpp
  • lib/ZipFile/ZipFile.h
  • src/CrossPointSettings.cpp
  • src/CrossPointSettings.h
  • src/CrossPointState.h
  • src/JsonSettingsIO.cpp
  • src/RecentBooksStore.cpp
  • src/RecentBooksStore.h
  • src/SettingsList.h
  • src/activities/boot_sleep/BootActivity.cpp
  • src/activities/boot_sleep/SleepActivity.cpp
  • src/activities/boot_sleep/SleepActivity.h
  • src/activities/home/HomeActivity.cpp
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderMenuActivity.cpp
  • src/activities/reader/EpubReaderMenuActivity.h
  • src/activities/reader/ReaderActivity.cpp
  • src/activities/reader/XtcReaderActivity.cpp
  • src/activities/settings/SettingsActivity.cpp
  • src/activities/settings/SettingsActivity.h
  • src/activities/util/PowerButtonMenuActivity.cpp
  • src/activities/util/PowerButtonMenuActivity.h
  • src/components/UITheme.cpp
  • src/components/UITheme.h
  • src/components/themes/BaseTheme.cpp
  • src/components/themes/BaseTheme.h
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
  • src/components/themes/lyra/LyraTheme.cpp
  • src/components/themes/lyra/LyraTheme.h
  • src/components/themes/roundedraff/RoundedRaffTheme.cpp
  • src/components/themes/roundedraff/RoundedRaffTheme.h
  • src/images/CrossLarge.h
  • src/main.cpp
💤 Files with no reviewable changes (12)
  • lib/I18n/translations/french.yaml
  • src/components/themes/roundedraff/RoundedRaffTheme.h
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/spanish.yaml
  • src/components/themes/roundedraff/RoundedRaffTheme.cpp
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/german.yaml

Comment on lines 643 to 646
if (needsScaling) {
rowAccum = new uint32_t[outWidth]();
rowCount = new uint16_t[outWidth]();
rowAccum = new (std::nothrow) uint32_t[outWidth]();
rowCount = new (std::nothrow) uint16_t[outWidth]();
nextOutY_srcStart = scaleY_fp;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fail fast when the scaling buffers cannot be allocated.

rowAccum and rowCount are dereferenced unconditionally in the scaling path, so new (std::nothrow) turns an OOM into a null-pointer crash here instead of a clean failure.

Suggested fix
   if (needsScaling) {
     rowAccum = new (std::nothrow) uint32_t[outWidth]();
     rowCount = new (std::nothrow) uint16_t[outWidth]();
+    if (!rowAccum || !rowCount) {
+      LOG_ERR("PNG", "Failed to allocate scaling buffers");
+      delete[] rowAccum;
+      delete[] rowCount;
+      delete atkinsonDitherer;
+      delete fsDitherer;
+      delete atkinson1BitDitherer;
+      free(rowBuffer);
+      free(ctx.currentRow);
+      free(ctx.previousRow);
+      return false;
+    }
     nextOutY_srcStart = scaleY_fp;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/PngToBmpConverter/PngToBmpConverter.cpp` around lines 643 - 646, The
scaling path currently allocates rowAccum and rowCount with new (std::nothrow)
but never checks for null, which causes a crash on OOM; inside the needsScaling
branch (where rowAccum, rowCount and nextOutY_srcStart are set) add null checks
after each allocation, free any partially allocated buffer (delete[] rowAccum
and/or delete[] rowCount), set pointers to nullptr, and return or propagate a
clean error (e.g., return false or set an error state) from the enclosing
function (the function containing the needsScaling logic in
PngToBmpConverter.cpp) so the caller can handle allocation failure instead of
dereferencing a null pointer. Ensure all code paths that assume these buffers
exist only run after successful allocation.

Comment thread lib/ZipFile/ZipFile.cpp
Comment on lines +444 to 445
bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize, uint32_t deadline) {
const ScopedOpenClose zip{*this};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard zero chunkSize to prevent non-progress loops.

Line 444 accepts chunkSize without validation. If it is 0, the deflated path can spin without progress (or rely only on deadline to exit). Add an early guard.

Suggested fix
 bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize, uint32_t deadline) {
+  if (chunkSize == 0) {
+    LOG_ERR("ZIP", "Invalid chunk size: 0");
+    return false;
+  }
   const ScopedOpenClose zip{*this};
   if (!zip) return false;
📝 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
bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize, uint32_t deadline) {
const ScopedOpenClose zip{*this};
bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t chunkSize, uint32_t deadline) {
if (chunkSize == 0) {
LOG_ERR("ZIP", "Invalid chunk size: 0");
return false;
}
const ScopedOpenClose zip{*this};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ZipFile/ZipFile.cpp` around lines 444 - 445, ZipFile::readFileToStream
must guard against a zero chunkSize to avoid non-progress loops; add an early
check (before creating ScopedOpenClose or immediately after) that if chunkSize
== 0 the function returns false (or fails fast), and include an appropriate
debug/error log if available; locate the check in ZipFile::readFileToStream
around the ScopedOpenClose usage and return early when chunkSize is zero.

Comment on lines +50 to +63
void SleepActivity::renderLogoSleepScreen() const {
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();

renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING), true, EpdFontFamily::BOLD);

if (SETTINGS.sleepScreenFilter != CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE) {
renderer.invertScreen();
}

renderer.displayBuffer(HalDisplay::HALF_REFRESH);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the logo fallback's negative filter with the other sleep-screen paths.

FILTER_NEGATIVE is handled as "invert" in renderBlankSleepScreen() and renderBitmapSleepScreen(), but this branch inverts for every other filter. The logo fallback will therefore render with the opposite polarity from the selected sleep-screen filter.

Suggested fix
-  if (SETTINGS.sleepScreenFilter != CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE) {
+  if (SETTINGS.sleepScreenFilter == CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE) {
     renderer.invertScreen();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/boot_sleep/SleepActivity.cpp` around lines 50 - 63,
renderLogoSleepScreen() currently calls renderer.invertScreen() when
SETTINGS.sleepScreenFilter is not FILTER_NEGATIVE, which is the inverse of how
renderBlankSleepScreen() and renderBitmapSleepScreen() handle FILTER_NEGATIVE;
change the branch in renderLogoSleepScreen() so that renderer.invertScreen() is
called only when SETTINGS.sleepScreenFilter ==
CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE to match the other
sleep-screen paths and ensure correct polarity for the logo fallback.

Comment on lines +76 to +77
int bookCount = forceRegenerate ? static_cast<int>(recentBooks.size()) : 1;
for (int bi = 0; bi < bookCount && bi < static_cast<int>(recentBooks.size()); bi++) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't cap normal cover generation to the first recent book.

This activity already loads as many recent books as the active theme asks for, but this branch only regenerates a missing thumbnail for index 0. In multi-cover home themes, books 2..N will keep showing stale/missing art until a forced full regenerate happens.

Suggested fix
-  int bookCount = forceRegenerate ? static_cast<int>(recentBooks.size()) : 1;
+  int bookCount = static_cast<int>(recentBooks.size());
📝 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
int bookCount = forceRegenerate ? static_cast<int>(recentBooks.size()) : 1;
for (int bi = 0; bi < bookCount && bi < static_cast<int>(recentBooks.size()); bi++) {
int bookCount = static_cast<int>(recentBooks.size());
for (int bi = 0; bi < bookCount && bi < static_cast<int>(recentBooks.size()); bi++) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/home/HomeActivity.cpp` around lines 76 - 77, The loop that
regenerates covers is capped to only index 0 when forceRegenerate is false
(bookCount is set to 1); change bookCount logic so it iterates over all
recentBooks (or up to the active theme's requested count) instead of just 1.
Specifically, update the calculation of bookCount (currently: int bookCount =
forceRegenerate ? static_cast<int>(recentBooks.size()) : 1;) to compute the full
desired count (e.g., int bookCount = static_cast<int>(recentBooks.size()); or
int bookCount = std::min(static_cast<int>(recentBooks.size()),
themeRequestedCount);) so the for-loop (for (int bi = 0; bi < bookCount && bi <
static_cast<int>(recentBooks.size()); bi++)) will regenerate covers for books
0..N-1 when not forcing a full regenerate.

Comment on lines +97 to 107
const uint32_t deadline = (useTimeout && !isBookForced) ? (millis() + COVER_RENDER_TIMEOUT_MS) : 0;
LOG_DBG("HOME", "loadRecentCovers: generating cover (deadline=%u ms)", deadline);
bool success = epub.generateThumbBmp(coverHeight, deadline);
LOG_DBG("HOME", "loadRecentCovers: generateThumbBmp result=%d", success);
if (success) {
RECENT_BOOKS.setCoverDisabled(book.path, false);
book.coverDisabled = false;
} else if (!isBookForced) {
RECENT_BOOKS.setCoverDisabled(book.path, true);
book.coverDisabled = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Timeout mode currently turns a transient miss into a permanent per-book disable.

A deadline expiry makes generateThumbBmp() return false, and this branch then persists coverDisabled = true. After one slow render, that book will stop being retried automatically, which makes the “Timeout (10s)” mode behave like “try once, then disable”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/home/HomeActivity.cpp` around lines 97 - 107, The current
logic treats any false from epub.generateThumbBmp(...) as a permanent failure
and persists coverDisabled; change epub.generateThumbBmp to return a richer
result (e.g., a struct or enum/tuple like {bool success; bool timedOut}) so
callers can distinguish timeout vs real failure, then update this call site (the
loadRecentCovers block using deadline, isBookForced, COVER_RENDER_TIMEOUT_MS,
epub.generateThumbBmp, RECENT_BOOKS.setCoverDisabled, and book.coverDisabled) to
only persist/set coverDisabled when success==false AND timedOut==false (i.e., a
non-timeout failure), leaving timed-out attempts as transient (do not call
RECENT_BOOKS.setCoverDisabled nor set book.coverDisabled on timedOut); keep
existing behavior for successful renders and for forced books.

Comment on lines 50 to 52
APP_STATE.readerActivityLoadCount = 0;
APP_STATE.pendingCoverGeneration = true;
APP_STATE.saveToFile();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard pending cover generation behind a valid reader instance.

Line 51 sets cover generation unconditionally. If xtc is null, this can queue cover work for stale state.

🔧 Proposed fix
 void XtcReaderActivity::onExit() {
   Activity::onExit();

   APP_STATE.readerActivityLoadCount = 0;
-  APP_STATE.pendingCoverGeneration = true;
+  if (xtc) {
+    APP_STATE.pendingCoverGeneration = true;
+  }
   APP_STATE.saveToFile();
   xtc.reset();
 }
📝 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
APP_STATE.readerActivityLoadCount = 0;
APP_STATE.pendingCoverGeneration = true;
APP_STATE.saveToFile();
APP_STATE.readerActivityLoadCount = 0;
if (xtc) {
APP_STATE.pendingCoverGeneration = true;
}
APP_STATE.saveToFile();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/reader/XtcReaderActivity.cpp` around lines 50 - 52, Guard
setting APP_STATE.pendingCoverGeneration behind a valid reader instance: check
the reader pointer (xtc or the reader instance used in XtcReaderActivity) is
non-null/valid before assigning APP_STATE.pendingCoverGeneration = true, leaving
APP_STATE.readerActivityLoadCount = 0 and APP_STATE.saveToFile() as needed; only
queue cover generation when the reader is confirmed valid to avoid enqueuing
stale work.

Comment on lines +484 to +487
if (renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight)) {
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
coverBufferStored = storeCoverBuffer();
coverRendered = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only mark the cover as rendered after the buffer snapshot succeeds.

If storeCoverBuffer() fails here, later renders skip drawBitmap() because coverRendered is already true, but they also cannot restore anything because no buffer was stored. The next full redraw leaves the cover blank.

Suggested fix
-            coverBufferStored = storeCoverBuffer();
-            coverRendered = true;
+            coverBufferStored = storeCoverBuffer();
+            coverRendered = coverBufferStored;
📝 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
if (renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight)) {
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
coverBufferStored = storeCoverBuffer();
coverRendered = true;
if (renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight)) {
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
coverBufferStored = storeCoverBuffer();
coverRendered = coverBufferStored;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/themes/BaseTheme.cpp` around lines 484 - 487, The code
currently sets coverRendered = true immediately after drawing and then calls
storeCoverBuffer(), which can fail and leave coverRendered true without a stored
buffer; change the flow so coverRendered is only set when storeCoverBuffer()
returns success (e.g., assign coverBufferStored = storeCoverBuffer() and then
set coverRendered = coverBufferStored), ensuring renderer.drawBitmap/drawRect
are followed by a successful buffer snapshot before marking the cover as
rendered.

Comment thread src/images/CrossLarge.h
@@ -0,0 +1,112 @@
#pragma once
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the UTF-8 BOM from this header.

Line 1 includes a BOM before #pragma once; that can cause preprocessing issues on stricter compilers.

🔧 Proposed fix
-`#pragma` once
+#pragma once
📝 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
#pragma once
`#pragma` once
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/images/CrossLarge.h` at line 1, The file CrossLarge.h contains a UTF-8
BOM before the directive; remove the BOM so the very first byte is '#' and the
line reads "`#pragma` once" exactly, then save the file as UTF-8 without BOM
(e.g., using your editor's "Save without BOM" or re-encode the file); this
ensures the preprocessor sees the `#pragma` once token correctly in CrossLarge.h.

Comment thread src/JsonSettingsIO.cpp
Comment on lines +243 to +282
// Legacy migration: if old keys exist, convert to unified sleep screen format.
if (!doc["sleepScreenCoverMode"].isNull() || !doc["sleepScreen"].isNull()) {
uint8_t oldSleepScreen = doc["sleepScreen"] | (uint8_t)0;
uint8_t oldCoverMode = doc["sleepScreenCoverMode"] | (uint8_t)0;
switch (oldSleepScreen) {
case 0: // DARK
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::LOGO;
break;
case 1: // LIGHT
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::LOGO;
s.sleepScreenFilter = CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_NEGATIVE;
break;
case 2: // CUSTOM
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM;
break;
case 3: // COVER
s.sleepScreen = (oldCoverMode == 1) ? CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CROP
: CrossPointSettings::SLEEP_SCREEN_MODE::COVER_FIT;
break;
case 4: // BLANK
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::BLANK;
break;
case 5: // COVER_CUSTOM
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CUSTOM;
break;
default:
s.sleepScreen = CrossPointSettings::SLEEP_SCREEN_MODE::LOGO;
break;
}
if (!doc["sleepScreenCoverFilter"].isNull()) {
uint8_t oldFilter = doc["sleepScreenCoverFilter"] | (uint8_t)0;
if ((s.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER_FIT ||
s.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CROP ||
s.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CUSTOM) &&
oldFilter < CrossPointSettings::SLEEP_SCREEN_FILTER_COUNT) {
s.sleepScreenFilter = oldFilter;
}
}
if (needsResave) *needsResave = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This migration rewrites already-migrated sleepScreen values.

New-format settings still use the sleepScreen key, so this block also runs on current files and interprets the new enum as the legacy one. That will silently change persisted settings on load/resave.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/JsonSettingsIO.cpp` around lines 243 - 282, The migration block is
triggering for new-format files because it checks doc["sleepScreen"] (shared by
old and new formats); change the guard so the legacy conversion runs only when
legacy-only keys exist (e.g., doc["sleepScreenCoverMode"] or
doc["sleepScreenCoverFilter"]) and remove the doc["sleepScreen"] check; update
the if condition around the migration in JsonSettingsIO.cpp to check only
!doc["sleepScreenCoverMode"].isNull() ||
!doc["sleepScreenCoverFilter"].isNull(), leaving the rest of the mapping logic
that assigns s.sleepScreen and s.sleepScreenFilter (and uses
CrossPointSettings::SLEEP_SCREEN_MODE / _FILTER enums) unchanged so
current-format sleepScreen values are not reinterpreted.

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

♻️ Duplicate comments (1)
src/CrossPointSettings.h (1)

110-118: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

SLEEP_3_MIN appended at end breaks UI label mapping.

This is the root cause of the sleep timeout mismatch flagged in SettingsList.h. The new SLEEP_3_MIN = 5 is added at the end of the enum, but the UI labels in SettingsList.h place "3 min" second in the list. Move SLEEP_3_MIN to index 1 for correct mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/CrossPointSettings.h` around lines 110 - 118, The SLEEP_3_MIN enumerator
was appended at the end of the SLEEP_TIMEOUT enum which breaks the mapping to UI
labels in SettingsList.h; relocate SLEEP_3_MIN to index 1 in the SLEEP_TIMEOUT
enum (i.e., place it right after SLEEP_1_MIN) so the order becomes SLEEP_1_MIN,
SLEEP_3_MIN, SLEEP_5_MIN, SLEEP_10_MIN, SLEEP_15_MIN, SLEEP_30_MIN and ensure
SLEEP_TIMEOUT_COUNT remains the last value; update any explicit numeric values
in the enum to reflect the new order if present so the integer mapping matches
the labels in SettingsList.h.
🧹 Nitpick comments (6)
src/SettingsList.h (1)

72-72: 💤 Low value

UTF-8 encoding corruption in comment.

The text â€" should be an em-dash (—). This is a minor display issue in the source.

Fix encoding
-      // SD font name not found in registry â€" fall through to built-in
+      // SD font name not found in registry — fall through to built-in
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/SettingsList.h` at line 72, Replace the corrupted UTF-8 sequence in the
comment inside SettingsList.h: change the text "SD font name not found in
registry — fall through to built-in" to use a proper em-dash ("—") so it reads
"SD font name not found in registry — fall through to built-in", and ensure the
file is saved in UTF-8 encoding (without a BOM) so the em-dash renders
correctly.
lib/I18n/translations/spanish.yaml (1)

64-65: 💤 Low value

Missing translations for new strings added in this PR.

The Spanish translation file retains old keys (STR_SLEEP_SCREEN, STR_SLEEP_COVER_MODE) but is missing the new strings added to english.yaml:

  • Sleep screen mode/filter strings (STR_SLEEP_SCREEN_MODE, STR_SLEEP_SCREEN_FILTER, STR_SLEEP_MODE_*, STR_SLEEP_FILTER_*)
  • STR_ACTION_MENU
  • Home cover settings (STR_HOME_COVER_*)
  • Cover disable dialog (STR_COVER_DISABLE_*)

The app will fall back to English for these untranslated strings. Consider adding placeholder translations or tracking this as a follow-up task.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/I18n/translations/spanish.yaml` around lines 64 - 65, The Spanish
translation file is missing the newly added keys (e.g. STR_SLEEP_SCREEN_MODE,
STR_SLEEP_SCREEN_FILTER, STR_SLEEP_MODE_*, STR_SLEEP_FILTER_*, STR_ACTION_MENU,
STR_HOME_COVER_*, STR_COVER_DISABLE_*) so update the Spanish translations by
adding these keys with appropriate Spanish text or safe placeholder translations
(e.g. English text or “TODO: traducir”) to avoid fallback to English; ensure
each new key exactly matches the key names used in english.yaml and follow
existing YAML formatting/quoting for values, or add a note/ticket to track
completing proper translations later.
src/activities/network/WifiSelectionActivity.cpp (1)

551-567: 💤 Low value

Unused metrics parameter in several render methods.

The metrics parameter is passed but not used in renderConnecting, renderConnected, renderConnectionFailed, and renderForgetPrompt. This is likely intentional for API consistency with renderNetworkList which does use it.

Also applies to: 569-587, 631-642, 644-684

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/network/WifiSelectionActivity.cpp` around lines 551 - 567,
These render methods (renderConnecting, renderConnected, renderConnectionFailed,
renderForgetPrompt) accept a ThemeMetrics* parameter that is not used and
triggers unused-parameter warnings; fix this by explicitly marking the parameter
as unused in each function body (e.g., add (void)metrics; or use
[[maybe_unused]] on the parameter) so the API remains consistent with
renderNetworkList while removing the warning. Ensure you update all four
functions (renderConnecting, renderConnected, renderConnectionFailed,
renderForgetPrompt) in the file.
src/activities/reader/EpubReaderActivity.cpp (1)

40-44: 💤 Low value

Pointer to vector element - ensure vector stability.

findRecentBook returns a raw pointer to an element within RECENT_BOOKS.getBooks(). This is safe as long as the vector isn't modified (which would invalidate the pointer). The current usage at lines 246-252 is safe since the pointer is consumed immediately before any vector-modifying operations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/reader/EpubReaderActivity.cpp` around lines 40 - 44,
findRecentBook currently returns a raw pointer to an element in
RECENT_BOOKS.getBooks(), which can dangle if the vector is modified; change the
function to return a safe, non-dangling result such as std::optional<RecentBook>
(or the element index) instead of const RecentBook* so callers (e.g., code using
findRecentBook at lines ~246-252) receive a stable copy or an index and avoid
relying on the vector's iterator/pointer stability; update callers to handle the
optional/index accordingly and remove any assumptions about pointer lifetime
tied to RECENT_BOOKS.getBooks().
src/components/UITheme.cpp (2)

61-92: 💤 Low value

Unused hasSideButtonHints parameter.

The hasSideButtonHints parameter is accepted but never used in the function body. All branches only check hasFrontButtonHints. If side button hints no longer affect the safe area, consider deprecating or removing this parameter in a future cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/UITheme.cpp` around lines 61 - 92, The hasSideButtonHints
parameter of UITheme::getScreenSafeArea is unused; remove it from the function
signature and all callers (and any forward declarations) so the function becomes
getScreenSafeArea(const GfxRenderer& renderer) and callers pass only the
renderer, or alternatively if side hints should affect layout, update the switch
branches to check hasSideButtonHints where appropriate; adjust references to
currentMetrics and GfxRenderer::Orientation cases accordingly to maintain the
same safeArea behavior.

102-102: 💤 Low value

Hardcoded cover heights.

getAllCoverHeights() returns a hardcoded list {226, 300, 400}. This works but couples the theme system to specific pixel values. Consider deriving these from theme metrics or documenting why these specific heights are used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/UITheme.cpp` at line 102, The method
UITheme::getAllCoverHeights currently returns hardcoded pixel values {226, 300,
400}; update it to derive these heights from theme configuration or central
metrics rather than literal numbers — e.g., read from a ThemeMetrics/ThemeConfig
instance or use existing constants (like
coverHeightSmall/coverHeightMedium/coverHeightLarge) so the values remain
consistent and configurable across the app; change the implementation of
UITheme::getAllCoverHeights to fetch those metric values (or document why fixed
values must be used if no metrics exist) and ensure any callers still receive
the same vector<int> shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/activities/reader/EpubReaderPercentSelectionActivity.cpp`:
- Around line 77-79: The fixed barWidth (360) can overflow in narrow safe areas
because barX centers using screen.width; update the layout to clamp barWidth
against the safe-area width before computing barX. In the
EpubReaderPercentSelectionActivity layout code, compute an effectiveWidth =
min(barWidth, safeAreaWidth or (screen.width - horizontalPadding*2)) and then
use that effectiveWidth when calculating barX and any positioning (instead of
the constant barWidth) so the slider never renders outside the visible safe
area.

In `@src/SettingsList.h`:
- Around line 178-179: The sleep timeout labels in SettingsList
(StrId::STR_MIN_1, STR_MIN_3, STR_MIN_5, STR_MIN_10, STR_MIN_15, STR_MIN_30) do
not map to the existing CrossPointSettings::SLEEP_TIMEOUT enum order (which
currently maps 3 and 5 out of order), causing wrong values to be written when
using SettingInfo::Enum; fix by making the enum ordering match the UI labels
(reorder CrossPointSettings::SLEEP_TIMEOUT entries so their ordinal values
correspond to {1,3,5,10,15,30}) or alternatively reorder the labels in
SettingsList to match the current enum ordering—update whichever file
(CrossPointSettings.h for enum, or SettingsList.h for labels) and ensure
SettingInfo::Enum usage remains index-based and consistent.

---

Duplicate comments:
In `@src/CrossPointSettings.h`:
- Around line 110-118: The SLEEP_3_MIN enumerator was appended at the end of the
SLEEP_TIMEOUT enum which breaks the mapping to UI labels in SettingsList.h;
relocate SLEEP_3_MIN to index 1 in the SLEEP_TIMEOUT enum (i.e., place it right
after SLEEP_1_MIN) so the order becomes SLEEP_1_MIN, SLEEP_3_MIN, SLEEP_5_MIN,
SLEEP_10_MIN, SLEEP_15_MIN, SLEEP_30_MIN and ensure SLEEP_TIMEOUT_COUNT remains
the last value; update any explicit numeric values in the enum to reflect the
new order if present so the integer mapping matches the labels in
SettingsList.h.

---

Nitpick comments:
In `@lib/I18n/translations/spanish.yaml`:
- Around line 64-65: The Spanish translation file is missing the newly added
keys (e.g. STR_SLEEP_SCREEN_MODE, STR_SLEEP_SCREEN_FILTER, STR_SLEEP_MODE_*,
STR_SLEEP_FILTER_*, STR_ACTION_MENU, STR_HOME_COVER_*, STR_COVER_DISABLE_*) so
update the Spanish translations by adding these keys with appropriate Spanish
text or safe placeholder translations (e.g. English text or “TODO: traducir”) to
avoid fallback to English; ensure each new key exactly matches the key names
used in english.yaml and follow existing YAML formatting/quoting for values, or
add a note/ticket to track completing proper translations later.

In `@src/activities/network/WifiSelectionActivity.cpp`:
- Around line 551-567: These render methods (renderConnecting, renderConnected,
renderConnectionFailed, renderForgetPrompt) accept a ThemeMetrics* parameter
that is not used and triggers unused-parameter warnings; fix this by explicitly
marking the parameter as unused in each function body (e.g., add (void)metrics;
or use [[maybe_unused]] on the parameter) so the API remains consistent with
renderNetworkList while removing the warning. Ensure you update all four
functions (renderConnecting, renderConnected, renderConnectionFailed,
renderForgetPrompt) in the file.

In `@src/activities/reader/EpubReaderActivity.cpp`:
- Around line 40-44: findRecentBook currently returns a raw pointer to an
element in RECENT_BOOKS.getBooks(), which can dangle if the vector is modified;
change the function to return a safe, non-dangling result such as
std::optional<RecentBook> (or the element index) instead of const RecentBook* so
callers (e.g., code using findRecentBook at lines ~246-252) receive a stable
copy or an index and avoid relying on the vector's iterator/pointer stability;
update callers to handle the optional/index accordingly and remove any
assumptions about pointer lifetime tied to RECENT_BOOKS.getBooks().

In `@src/components/UITheme.cpp`:
- Around line 61-92: The hasSideButtonHints parameter of
UITheme::getScreenSafeArea is unused; remove it from the function signature and
all callers (and any forward declarations) so the function becomes
getScreenSafeArea(const GfxRenderer& renderer) and callers pass only the
renderer, or alternatively if side hints should affect layout, update the switch
branches to check hasSideButtonHints where appropriate; adjust references to
currentMetrics and GfxRenderer::Orientation cases accordingly to maintain the
same safeArea behavior.
- Line 102: The method UITheme::getAllCoverHeights currently returns hardcoded
pixel values {226, 300, 400}; update it to derive these heights from theme
configuration or central metrics rather than literal numbers — e.g., read from a
ThemeMetrics/ThemeConfig instance or use existing constants (like
coverHeightSmall/coverHeightMedium/coverHeightLarge) so the values remain
consistent and configurable across the app; change the implementation of
UITheme::getAllCoverHeights to fetch those metric values (or document why fixed
values must be used if no metrics exist) and ensure any callers still receive
the same vector<int> shape.

In `@src/SettingsList.h`:
- Line 72: Replace the corrupted UTF-8 sequence in the comment inside
SettingsList.h: change the text "SD font name not found in registry — fall
through to built-in" to use a proper em-dash ("—") so it reads "SD font name not
found in registry — fall through to built-in", and ensure the file is saved in
UTF-8 encoding (without a BOM) so the em-dash renders correctly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4a18ead-51ec-43ef-8161-eac85ced00cc

📥 Commits

Reviewing files that changed from the base of the PR and between cf1b2ad and 58cb983.

📒 Files selected for processing (42)
  • lib/I18n/translations/belarusian.yaml
  • lib/I18n/translations/catalan.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/danish.yaml
  • lib/I18n/translations/dutch.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/finnish.yaml
  • lib/I18n/translations/french.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/hungarian.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/lithuanian.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/romanian.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/slovenian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/turkish.yaml
  • lib/I18n/translations/ukrainian.yaml
  • src/CrossPointSettings.cpp
  • src/CrossPointSettings.h
  • src/RecentBooksStore.cpp
  • src/RecentBooksStore.h
  • src/SettingsList.h
  • src/activities/network/WifiSelectionActivity.cpp
  • src/activities/network/WifiSelectionActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderChapterSelectionActivity.cpp
  • src/activities/reader/EpubReaderMenuActivity.cpp
  • src/activities/reader/EpubReaderPercentSelectionActivity.cpp
  • src/activities/reader/KOReaderSyncActivity.cpp
  • src/components/UITheme.cpp
  • src/components/UITheme.h
  • src/components/themes/BaseTheme.cpp
  • src/components/themes/BaseTheme.h
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
  • src/components/themes/lyra/Lyra3CoversTheme.h
  • src/components/themes/lyra/LyraTheme.cpp
💤 Files with no reviewable changes (2)
  • src/components/themes/lyra/Lyra3CoversTheme.h
  • src/components/themes/lyra/Lyra3CoversTheme.cpp
✅ Files skipped from review due to trivial changes (4)
  • lib/I18n/translations/turkish.yaml
  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/dutch.yaml
  • lib/I18n/translations/german.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/UITheme.h
  • src/components/themes/BaseTheme.h
  • src/CrossPointSettings.cpp
  • src/components/themes/BaseTheme.cpp

Comment on lines +77 to 79
const int barX = screen.x + (screen.width - barWidth) / 2;
const int barY = contentTop + metrics.verticalSpacing * 2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp the slider width to the safe-area width.

barWidth is fixed at 360 while centering uses screen.width; in narrow safe areas the slider can render partially off-screen.

Suggested fix
+#include <algorithm>
...
-  constexpr int barWidth = 360;
+  const int barWidth = std::max(1, std::min(360, screen.width - 8));
   constexpr int barHeight = 16;
   const int barX = screen.x + (screen.width - barWidth) / 2;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/activities/reader/EpubReaderPercentSelectionActivity.cpp` around lines 77
- 79, The fixed barWidth (360) can overflow in narrow safe areas because barX
centers using screen.width; update the layout to clamp barWidth against the
safe-area width before computing barX. In the EpubReaderPercentSelectionActivity
layout code, compute an effectiveWidth = min(barWidth, safeAreaWidth or
(screen.width - horizontalPadding*2)) and then use that effectiveWidth when
calculating barX and any positioning (instead of the constant barWidth) so the
slider never renders outside the visible safe area.

Comment thread src/SettingsList.h
Comment on lines +178 to +179
{StrId::STR_MIN_1, StrId::STR_MIN_3, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15,
StrId::STR_MIN_30},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Sleep timeout UI labels don't match enum order.

The UI displays options as {1, 3, 5, 10, 15, 30} minutes, but CrossPointSettings::SLEEP_TIMEOUT enum values are {1=0, 5=1, 10=2, 15=3, 30=4, 3=5}. Since SettingInfo::Enum stores the selection index directly, selecting "3 min" (index 1) will actually set SLEEP_5_MIN.

Either reorder the enum in CrossPointSettings.h to match the desired UI order, or reorder the labels here to match the existing enum.

Recommended fix: Reorder enum in CrossPointSettings.h
  enum SLEEP_TIMEOUT {
    SLEEP_1_MIN = 0,
-   SLEEP_5_MIN = 1,
-   SLEEP_10_MIN = 2,
-   SLEEP_15_MIN = 3,
-   SLEEP_30_MIN = 4,
-   SLEEP_3_MIN = 5,
+   SLEEP_3_MIN = 1,
+   SLEEP_5_MIN = 2,
+   SLEEP_10_MIN = 3,
+   SLEEP_15_MIN = 4,
+   SLEEP_30_MIN = 5,
    SLEEP_TIMEOUT_COUNT
  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/SettingsList.h` around lines 178 - 179, The sleep timeout labels in
SettingsList (StrId::STR_MIN_1, STR_MIN_3, STR_MIN_5, STR_MIN_10, STR_MIN_15,
STR_MIN_30) do not map to the existing CrossPointSettings::SLEEP_TIMEOUT enum
order (which currently maps 3 and 5 out of order), causing wrong values to be
written when using SettingInfo::Enum; fix by making the enum ordering match the
UI labels (reorder CrossPointSettings::SLEEP_TIMEOUT entries so their ordinal
values correspond to {1,3,5,10,15,30}) or alternatively reorder the labels in
SettingsList to match the current enum ordering—update whichever file
(CrossPointSettings.h for enum, or SettingsList.h for labels) and ensure
SettingInfo::Enum usage remains index-based and consistent.

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.

4 participants