refactor: Simplify new setting introduction#1086
refactor: Simplify new setting introduction#1086daveallie merged 13 commits intocrosspoint-reader:masterfrom
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces explicit field-by-field JSON (de)serialization with a SettingsList-driven loop in JsonSettingsIO, adds per-setting obfuscation metadata and string-offset usage, updates Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/JsonSettingsIO.cppsrc/SettingsList.hsrc/activities/settings/SettingsActivity.h
🧰 Additional context used
🧬 Code graph analysis (1)
src/JsonSettingsIO.cpp (2)
src/SettingsList.h (3)
string(99-102)string(106-109)string(113-116)lib/Serialization/ObfuscationUtils.cpp (4)
obfuscateToBase64(41-46)obfuscateToBase64(41-41)deobfuscateFromBase64(48-74)deobfuscateFromBase64(48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cppcheck
- GitHub Check: build
🔇 Additional comments (4)
src/activities/settings/SettingsActivity.h (1)
42-62: LGTM!The new
defaultValueandobfuscatedfields with their fluent setters follow a clean builder pattern. The implementation correctly returns*thisfor chaining, and the default initializations are appropriate.src/SettingsList.h (1)
14-135: LGTM!The default values are semantically appropriate, and
.withObfuscated()is correctly applied only to the OPDS password (KOReader credentials are handled separately in their own file storage).src/JsonSettingsIO.cpp (2)
52-66: LGTM!The generic save loop correctly handles both string and value types, with proper obfuscation suffix handling for sensitive fields.
95-108: LGTM!The obfuscation fallback logic for backward compatibility is well-implemented. The
needsResaveflag is correctly set only when migrating from plain to obfuscated format, and string copying safely handles buffer limits with proper null termination.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/JsonSettingsIO.cpp`:
- Around line 109-115: The code reads a raw uint8_t v from doc[info.key] and
only clamps ENUMs; update the load logic around v so VALUE types are clamped to
their configured range (use info.minValue and info.maxValue or equivalent fields
on info) and TOGGLE types are constrained to 0 or 1 before assigning
s.*(info.valuePtr); keep the existing ENUM clamp (clamp(v,
(uint8_t)info.enumValues.size(), info.defaultValue)) and ensure the initial read
uses the intended fallback (don’t rely on bitwise OR) so the value is validated
then assigned to s.*(info.valuePtr) using SettingType, info, and clamp().
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/JsonSettingsIO.cpp (1)
90-123: Consider markingneedsResavewhen defaults/clamps are applied.
Right now only obfuscation fallback triggers resave, so missing/out-of-range numeric values are corrected in memory but not persisted. MarkingneedsResavein those cases would let the file self-heal.💡 Possible adjustment
- uint8_t v = doc[info.key] | info.defaultValue; + bool corrected = !doc.containsKey(info.key); + uint8_t v = doc[info.key] | info.defaultValue; if (info.type == SettingType::ENUM) { - v = clamp(v, (uint8_t)info.enumValues.size(), info.defaultValue); + uint8_t clamped = clamp(v, (uint8_t)info.enumValues.size(), info.defaultValue); + corrected = corrected || (clamped != v); + v = clamped; } else if (info.type == SettingType::TOGGLE) { - v = clamp(v, (uint8_t)2, info.defaultValue); + uint8_t clamped = clamp(v, (uint8_t)2, info.defaultValue); + corrected = corrected || (clamped != v); + v = clamped; } else if (info.type == SettingType::VALUE) { - if (v < info.valueRange.min) - v = info.valueRange.min; - else if (v > info.valueRange.max) - v = info.valueRange.max; + if (v < info.valueRange.min) { + v = info.valueRange.min; + corrected = true; + } else if (v > info.valueRange.max) { + v = info.valueRange.max; + corrected = true; + } } + if (corrected && needsResave) *needsResave = true; s.*(info.valuePtr) = v;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/JsonSettingsIO.cpp` around lines 90 - 123, When reading numeric settings in the loop returned by getSettingsList(), if a setting's value is replaced by its default or clamped into range you should set *needsResave = true so the corrected value is persisted; specifically, in the else branch where you compute uint8_t v = doc[info.key] | info.defaultValue and then adjust v for SettingType::ENUM (using info.enumValues.size()), SettingType::TOGGLE, or SettingType::VALUE (using info.valueRange.min/max), detect when the final v differs from the raw document value (e.g. compare to doc[info.key] or the default that was used) and if so set *needsResave = true before assigning into s through the pointer (s.*(info.valuePtr)).
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/JsonSettingsIO.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
src/JsonSettingsIO.cpp (2)
src/SettingsList.h (3)
string(99-102)string(106-109)string(113-116)lib/Serialization/ObfuscationUtils.cpp (4)
obfuscateToBase64(41-46)obfuscateToBase64(41-41)deobfuscateFromBase64(48-74)deobfuscateFromBase64(48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build
- GitHub Check: cppcheck
🔇 Additional comments (3)
src/JsonSettingsIO.cpp (3)
9-15: LGTM — required includes for std::string and SettingsList.
125-136: LGTM — front-button mapping remains explicit and validated.
52-66: String settings binding is inconsistent with numeric settings. InsaveSettings(), enum/toggle values use the passedsparameter vias.*(info.valuePtr), but string values use hardcodedSETTINGSpointers via directinfo.stringPtraccess. While this works becausesaveSettings()is only called with the globalSETTINGSinstance, the inconsistent pattern creates fragility: the function signature suggests it can serialize anyCrossPointSettingsinstance, but strings always serialize from the globalSETTINGSregardless of thesparameter. Align string binding to the same parameterized pattern as numeric values to avoid future bugs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/JsonSettingsIO.cpp`:
- Around line 90-123: When reading numeric settings in the loop returned by
getSettingsList(), if a setting's value is replaced by its default or clamped
into range you should set *needsResave = true so the corrected value is
persisted; specifically, in the else branch where you compute uint8_t v =
doc[info.key] | info.defaultValue and then adjust v for SettingType::ENUM (using
info.enumValues.size()), SettingType::TOGGLE, or SettingType::VALUE (using
info.valueRange.min/max), detect when the final v differs from the raw document
value (e.g. compare to doc[info.key] or the default that was used) and if so set
*needsResave = true before assigning into s through the pointer
(s.*(info.valuePtr)).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/JsonSettingsIO.cpp`:
- Around line 95-122: String settings currently default to empty string when the
JSON key is absent, overwriting in-memory defaults; capture the existing string
buffer as the field default before reading JSON and use that as the fallback
value. Specifically, read a std::string fieldDefault =
std::string(info.stringPtr) (or "" if null) at the start of the info.stringPtr
branch, then replace occurrences of doc[info.key] | "" with doc[info.key] |
fieldDefault (and similarly for the obfuscated path using
obfuscation::deobfuscateFromBase64 and needsResave logic), so the
strncpy(info.stringPtr, ...) only overwrites when the JSON provides a value.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/JsonSettingsIO.cppsrc/SettingsList.hsrc/activities/settings/SettingsActivity.cppsrc/activities/settings/SettingsActivity.hsrc/network/CrossPointWebServer.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
- src/activities/settings/SettingsActivity.h
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-23T06:18:08.408Z
Learnt from: raygan
Repo: crosspoint-reader/crosspoint-reader PR: 1116
File: src/activities/settings/WallabagSettingsActivity.cpp:57-75
Timestamp: 2026-02-23T06:18:08.408Z
Learning: When navigating from a settings activity to KeyboardEntryActivity (e.g., KOReaderSettingsActivity, WallabagSettingsActivity), call exitActivity() on the parent before calling enterNewActivity(new KeyboardEntryActivity(...)). The KeyboardEntryActivity should then call exitActivity() via its callbacks to return to the parent. Note: ConfirmationActivity does not require exitActivity() before entering KeyboardEntryActivity. Apply this pattern to similar settings activities in this module.
Applied to files:
src/activities/settings/SettingsActivity.cpp
🧬 Code graph analysis (1)
src/JsonSettingsIO.cpp (1)
lib/Serialization/ObfuscationUtils.cpp (4)
obfuscateToBase64(41-46)obfuscateToBase64(41-41)deobfuscateFromBase64(48-74)deobfuscateFromBase64(48-48)
🔇 Additional comments (5)
src/SettingsList.h (1)
14-15: LGTM: shared list + obfuscated OPDS password flag.
Static list avoids per-call rebuilds, and the obfuscation metadata lines up with the new save/load path.Also applies to: 121-125
src/activities/settings/SettingsActivity.cpp (1)
31-41: LGTM: const iteration and safe copies into category vectors.
This keeps the shared list immutable while building UI-local collections.src/network/CrossPointWebServer.cpp (1)
1036-1036: LGTM: const reference for settings list in GET/POST handlers.
Avoids needless copies while preserving behavior.Also applies to: 1132-1136
src/JsonSettingsIO.cpp (2)
8-16: No review needed for include adjustments.
52-69: LGTM: SettingsList-driven serialization + obfuscated string write.
The loop cleanly handles string vs. numeric settings while skipping dynamic entries.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/JsonSettingsIO.cpp (1)
90-125: Good job addressing the previous review feedback.The
fieldDefaultpattern at lines 96 and 111 correctly preserves struct-initializer defaults when JSON keys are absent, and the per-type clamping (ENUM → default, TOGGLE → default, VALUE → boundary) is a sensible approach.One minor defensive concern: if a SettingsList entry ever has
stringMaxLen == 0, line 108'sstrncpy(..., info.stringMaxLen - 1)underflows toSIZE_MAXon unsigned types, causing a buffer overrun. Consider guarding:🛡️ Optional defensive guard
+ if (info.stringMaxLen == 0) continue; strncpy(info.stringPtr, val.c_str(), info.stringMaxLen - 1); info.stringPtr[info.stringMaxLen - 1] = '\0';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/JsonSettingsIO.cpp` around lines 90 - 125, The loop writes into info.stringPtr using strncpy with info.stringMaxLen-1 which underflows when info.stringMaxLen == 0; before the strncpy call in the stringPtr branch (inside getSettingsList() iteration, including the obfuscated path that reads doc[info.key+"_obf"]), add a guard that if info.stringMaxLen == 0 then skip writing to info.stringPtr (or treat as a zero-length buffer) and avoid using strncpy, and ensure you still handle needsResave/val logic as before; this prevents the unsigned underflow and a potential buffer overrun while preserving the existing default/obfuscation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/JsonSettingsIO.cpp`:
- Around line 52-66: The saveSettings implementation mixes raw char*
(info.stringPtr) that point into the singleton CrossPointSettings buffers with
member pointers (info.valuePtr), meaning JsonSettingsIO::saveSettings(s) will
serialize strings from the singleton even if s is a different CrossPointSettings
instance; fix this by making the pointer semantics consistent: either change
SettingsList/getSettingsList entries so string fields use member pointers (e.g.,
store a pointer-to-member for string fields and dereference from the passed `s`)
or require and assert that saveSettings only ever receives the singleton (add an
explicit check/assert that &s == &CrossPointSettings::instance() or document the
invariant), and update uses of info.stringPtr and info.valuePtr in
JsonSettingsIO::saveSettings accordingly so both string and numeric fields are
read from the same instance.
---
Nitpick comments:
In `@src/JsonSettingsIO.cpp`:
- Around line 90-125: The loop writes into info.stringPtr using strncpy with
info.stringMaxLen-1 which underflows when info.stringMaxLen == 0; before the
strncpy call in the stringPtr branch (inside getSettingsList() iteration,
including the obfuscated path that reads doc[info.key+"_obf"]), add a guard that
if info.stringMaxLen == 0 then skip writing to info.stringPtr (or treat as a
zero-length buffer) and avoid using strncpy, and ensure you still handle
needsResave/val logic as before; this prevents the unsigned underflow and a
potential buffer overrun while preserving the existing default/obfuscation
behavior.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/JsonSettingsIO.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cppcheck
- GitHub Check: build
🧰 Additional context used
🧬 Code graph analysis (1)
src/JsonSettingsIO.cpp (1)
lib/Serialization/ObfuscationUtils.cpp (4)
obfuscateToBase64(41-46)obfuscateToBase64(41-41)deobfuscateFromBase64(48-74)deobfuscateFromBase64(48-48)
🔇 Additional comments (2)
src/JsonSettingsIO.cpp (2)
9-15: LGTM!New includes are correct and necessary for the refactored loop-based approach.
127-138: LGTM!The front-button remap fields are correctly kept as explicit special cases with their own clamping and validation, consistent with the PR objectives.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/activities/settings/SettingsActivity.h (1)
107-107: Preferuintptr_toversize_tfor pointer-to-integer arithmetic.
size_tis not guaranteed to be large enough to hold a pointer on all platforms (though it is on most 32-bit embedded targets).uintptr_tfrom<cstdint>is the standard-defined type for this purpose.♻️ Suggested fix
- s.stringOffset = (size_t)ptr - (size_t)&SETTINGS; + s.stringOffset = reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(&SETTINGS);(Also ensure
#include <cstdint>is present, or thatuintptr_tis available via another header.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/activities/settings/SettingsActivity.h` at line 107, The current pointer-to-integer conversion uses size_t which may not be large enough on all platforms; change the calculation of s.stringOffset to use uintptr_t by casting ptr and &SETTINGS to uintptr_t (i.e., s.stringOffset = (uintptr_t)ptr - (uintptr_t)&SETTINGS) and ensure <cstdint> is included so uintptr_t is available; update the code referencing s.stringOffset, ptr and SETTINGS accordingly.src/JsonSettingsIO.cpp (1)
97-98: Consider usingdestPtrconsistently instead of computing two pointers from the same offset.Lines 97 and 110 both compute a pointer from
info.stringOffset— oneconstfor reading the default, one non-constfor writing. This is fine for correctness, but you could compute it once aschar*and use it for both purposes.♻️ Minor simplification
if (info.stringOffset) { - const char* strPtr = (const char*)&s + info.stringOffset; - const std::string fieldDefault = strPtr; // current buffer = struct-initializer default + char* fieldPtr = (char*)&s + info.stringOffset; + const std::string fieldDefault(fieldPtr); // current buffer = struct-initializer default std::string val; ... - char* destPtr = (char*)&s + info.stringOffset; - strncpy(destPtr, val.c_str(), info.stringMaxLen - 1); - destPtr[info.stringMaxLen - 1] = '\0'; + strncpy(fieldPtr, val.c_str(), info.stringMaxLen - 1); + fieldPtr[info.stringMaxLen - 1] = '\0'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/JsonSettingsIO.cpp` around lines 97 - 98, Compute the pointer from info.stringOffset only once and reuse it for both reading the default and writing the destination instead of creating two separate pointers; for example, replace separate uses of "const char* strPtr = (const char*)&s + info.stringOffset" and the later non-const "destPtr" by a single "char* basePtr = (char*)&s + info.stringOffset" (or similar name) and then use a const cast/view when reading the default and the non-const pointer when writing, so the same offset calculation is not duplicated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/activities/settings/SettingsActivity.h`:
- Around line 45-46: The field stringOffset in CrossPointSettings is using 0 as
a sentinel which hides a valid offset==0; change stringOffset from size_t to an
explicit optional (e.g., std::optional<size_t> stringOffset) or add a separate
bool (e.g., bool hasString) and leave stringOffset as a raw value, then update
the consumers (the checks in JsonSettingsIO.cpp that currently do
!info.stringOffset on lines that reference stringOffset) to test
has_value()/hasString instead; ensure stringMaxLen handling remains unchanged
and document the invariant if you choose the bool approach.
In `@src/JsonSettingsIO.cpp`:
- Around line 96-112: The code assumes info.stringMaxLen > 0 when
info.stringOffset is non-zero but doesn't guard against a zero max length, which
causes underflow in the strncpy size calculation; add a defensive check right
before computing destPtr/strncpy (in the block that uses info.stringOffset,
info.stringMaxLen, strncpy and destPtr) that when info.stringMaxLen == 0 you
avoid the oversized copy: make a safe fallback (e.g. treat effective maxLen as 1
so only a terminating NUL is written), ensure destPtr[0] = '\0', and mark
needsResave or log the misconfigured setting so the bad SettingInfo entry can be
fixed; keep all changes local to that branch and reference info.stringMaxLen and
destPtr/strncpy locations.
---
Nitpick comments:
In `@src/activities/settings/SettingsActivity.h`:
- Line 107: The current pointer-to-integer conversion uses size_t which may not
be large enough on all platforms; change the calculation of s.stringOffset to
use uintptr_t by casting ptr and &SETTINGS to uintptr_t (i.e., s.stringOffset =
(uintptr_t)ptr - (uintptr_t)&SETTINGS) and ensure <cstdint> is included so
uintptr_t is available; update the code referencing s.stringOffset, ptr and
SETTINGS accordingly.
In `@src/JsonSettingsIO.cpp`:
- Around line 97-98: Compute the pointer from info.stringOffset only once and
reuse it for both reading the default and writing the destination instead of
creating two separate pointers; for example, replace separate uses of "const
char* strPtr = (const char*)&s + info.stringOffset" and the later non-const
"destPtr" by a single "char* basePtr = (char*)&s + info.stringOffset" (or
similar name) and then use a const cast/view when reading the default and the
non-const pointer when writing, so the same offset calculation is not
duplicated.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/JsonSettingsIO.cppsrc/activities/settings/SettingsActivity.h
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build
- GitHub Check: cppcheck
🔇 Additional comments (4)
src/activities/settings/SettingsActivity.h (1)
54-57: LGTM — clean fluent builder for obfuscation flag.Simple, idiomatic, and consistent with other builder patterns.
src/JsonSettingsIO.cpp (3)
52-67: Clean generic save loop — good refactoring.The loop correctly handles all three paths (skip dynamic, string with obfuscation, numeric via member pointer) and the offset-based string access properly uses the passed-in
sreference, resolving the prior asymmetry concern.
91-128: Well-structured generic load loop with proper clamping for all numeric types.The clamping for ENUM, TOGGLE, and VALUE types properly addresses prior review feedback. The
fieldDefaultpattern correctly preserves struct-initializer defaults when JSON keys are absent.One minor observation on lines 120–124: VALUE clamping enforces min/max but doesn't snap to
valueRange.stepboundaries. A value like 7 would pass for a range of 5–40 with step 5. This is likely fine ifstepis only used for UI increment, but worth noting.
130-140: Front button special-case handling looks correct.The explicit handling outside the loop with per-field defaults and
validateFrontButtonMappingis appropriate given these fields are managed by a separate sub-activity.
|
Needs rework |
znelson
left a comment
There was a problem hiding this comment.
Tedious stuff, but really helpful in the long run. Thanks for taking it on!
* **What is the goal of this PR?** Eliminate the 3-file / 4-location
overhead for adding a new setting. Previously, every new setting
required manually editing JsonSettingsIO.cpp in two places (save +
load), duplicating knowledge already present in SettingsList.h. After
this PR, JsonSettingsIO.cpp never needs to be touched again for standard
settings.
* **What changes are included?**
* `SettingInfo` (in `SettingsActivity.h`) gains one new field: `bool
obfuscated` (base64 save/load for passwords), with a fluent builder
method `.withObfuscated()`. The previously proposed
`defaultValue`/`withDefault()` approach was dropped in favour of reading
the struct field's own initializer value as the fallback (see below).
* `SettingsList.h` entries are annotated with `.withObfuscated()` on the
OPDS password entry. The list is now returned as a `static const`
singleton (`const std::vector<SettingInfo>&`), so it is constructed
exactly once. A missing `key`/`category` on the
`statusBarProgressBarThickness` entry was also fixed — it was previously
skipped by the generic save loop, so changes were silently lost on
restart.
* `JsonSettingsIO::saveSettings` and `loadSettings` replace their ~90
lines of manual per-field code with a single generic loop over
`getSettingsList()`. The loop uses `info.key`,
`info.valuePtr`/`info.stringOffset`+`info.stringMaxLen` (for char-array
string fields), `info.enumValues.size()` (for enum clamping), and
`info.obfuscated`.
* **Default values**: instead of a duplicated `defaultValue` field in
`SettingInfo`, `loadSettings` reads `s.*(info.valuePtr)` *before*
overwriting it. Because `CrossPointSettings` is default-constructed
before `loadSettings` is called, this captures each field's struct
initializer value as the JSON-absent fallback. The single source of
truth for defaults is `CrossPointSettings.h`.
* One post-loop special case remains explicitly: the four `frontButton*`
remap fields (managed by the RemapFrontButtons sub-activity, not in
SettingsList) and `validateFrontButtonMapping()`.
* One pre-loop migration guard handles legacy settings files that
predate the status bar refactor: if `statusBarChapterPageCount` is
absent from the JSON, `applyLegacyStatusBarSettings()` is called first
so the generic loop picks up the migrated values as defaults and applies
its normal clamping.
* OPDS password backward-compat migration (plain `opdsPassword` →
obfuscated `opdsPassword_obf`) is preserved inside the generic
obfuscated-string path.
Say we want to add a new `bookmarkStyle` enum setting with options
`DOT`, `LINE`, `NONE` and a default of `DOT`:
1. `src/CrossPointSettings.h` — add enum and member:
```cpp
enum BOOKMARK_STYLE { BOOKMARK_DOT = 0, BOOKMARK_LINE = 1, BOOKMARK_NONE = 2 };
uint8_t bookmarkStyle = BOOKMARK_DOT;
```
2. `lib/I18n/translations/english.yaml` — add display strings:
```yaml
STR_BOOKMARK_STYLE: "Bookmark Style"
STR_BOOKMARK_DOT: "Dot"
STR_BOOKMARK_LINE: "Line"
```
(Other language files will fall back to English if not translated. Run
`gen_i18n.py` to regenerate `I18nKeys.h`.)
3. `src/SettingsList.h` — add one entry in the appropriate category:
```cpp
SettingInfo::Enum(StrId::STR_BOOKMARK_STYLE, &CrossPointSettings::bookmarkStyle,
{StrId::STR_BOOKMARK_DOT, StrId::STR_BOOKMARK_LINE, StrId::STR_NONE_OPT},
"bookmarkStyle", StrId::STR_CAT_READER),
```
That's it — no default annotation needed anywhere, because
`bookmarkStyle = BOOKMARK_DOT` in the struct already provides the
fallback. The setting will automatically persist to JSON on save, load
with clamping on boot, appear in the device settings UI under the Reader
category, and be exposed via the web API — all with no further changes.
---
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? _**< PARTIALLY>**_
…sspoint-reader#1086 followup) The static const initializer_list pattern from upstream crosspoint-reader#1086 causes a Guru Meditation stack protection fault in loopTask on first settings access — all 39 SettingInfo temporaries are constructed simultaneously during static init, overflowing the 8KB loopTask stack. Reverts to the push_back + reserve(39) pattern from a6a078b while preserving all additions from crosspoint-reader#1086: fork entries (feedUrl, dangerZone), .withObfuscated() on opdsPassword and dangerZonePassword, and the statusBarProgressBarThickness key/category fix. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Cherry-picked upstream fixes and features applied via rescue-1.0.0 worktree: - fix: properly implement requestUpdateAndWait() (crosspoint-reader#1218) - fix: Hide unusable button hints in empty directory (crosspoint-reader#1253) - fix: add missing keyboard metrics to Lyra3CoversTheme (crosspoint-reader#1101) - fix: remove bookProgressBarHeight from Lyra3CoversTheme - feat: replace picojpeg with JPEGDEC for JPEG decoding (crosspoint-reader#1136) - feat: WIFI pill, feed log fix, JPEGDEC version string - feat: Add git branch to version string (crosspoint-reader#1225) - fix: navigate directly to QR code after DZ auto-connect - perf: Removed unused ConfirmationActivity member (crosspoint-reader#1234) - refactor: Simplify new setting introduction (crosspoint-reader#1086) - fix: UI fonts, settings stack overflow, PULSR theme name - fix: convert SettingsList to push_back (prevent stack overflow) All commits built and verified on device (confirmed 1.1.0-dev+d1e786a).
Summary
SettingInfo(inSettingsActivity.h) gains one new field:bool obfuscated(base64 save/load for passwords), with a fluent builder method.withObfuscated(). The previously proposeddefaultValue/withDefault()approach was dropped in favour of reading the struct field's own initializer value as the fallback (see below).SettingsList.hentries are annotated with.withObfuscated()on the OPDS password entry. The list is now returned as astatic constsingleton (const std::vector<SettingInfo>&), so it is constructed exactly once. A missingkey/categoryon thestatusBarProgressBarThicknessentry was also fixed — it was previously skipped by the generic save loop, so changes were silently lost on restart.JsonSettingsIO::saveSettingsandloadSettingsreplace their ~90 lines of manual per-field code with a single generic loop overgetSettingsList(). The loop usesinfo.key,info.valuePtr/info.stringOffset+info.stringMaxLen(for char-array string fields),info.enumValues.size()(for enum clamping), andinfo.obfuscated.defaultValuefield inSettingInfo,loadSettingsreadss.*(info.valuePtr)before overwriting it. BecauseCrossPointSettingsis default-constructed beforeloadSettingsis called, this captures each field's struct initializer value as the JSON-absent fallback. The single source of truth for defaults isCrossPointSettings.h.frontButton*remap fields (managed by the RemapFrontButtons sub-activity, not in SettingsList) andvalidateFrontButtonMapping().statusBarChapterPageCountis absent from the JSON,applyLegacyStatusBarSettings()is called first so the generic loop picks up the migrated values as defaults and applies its normal clamping.opdsPassword→ obfuscatedopdsPassword_obf) is preserved inside the generic obfuscated-string path.Additional Context
Say we want to add a new
bookmarkStyleenum setting with optionsDOT,LINE,NONEand a default ofDOT:src/CrossPointSettings.h— add enum and member:lib/I18n/translations/english.yaml— add display strings:(Other language files will fall back to English if not translated. Run
gen_i18n.pyto regenerateI18nKeys.h.)src/SettingsList.h— add one entry in the appropriate category:That's it — no default annotation needed anywhere, because
bookmarkStyle = BOOKMARK_DOTin the struct already provides the fallback. The setting will automatically persist to JSON on save, load with clamping on boot, appear in the device settings UI under the Reader category, and be exposed via the web API — all with no further changes.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? < PARTIALLY>