Skip to content

feat: focus reading#1670

Merged
pablohc merged 12 commits into
crosspoint-reader:masterfrom
vjapolitzer:feat/focus-reading
May 10, 2026
Merged

feat: focus reading#1670
pablohc merged 12 commits into
crosspoint-reader:masterfrom
vjapolitzer:feat/focus-reading

Conversation

@vjapolitzer
Copy link
Copy Markdown
Contributor

@vjapolitzer vjapolitzer commented Apr 16, 2026

Summary

This PR introduces Focus Reading, a generic implementation of artificial fixation points (similar to Bionic Reading) designed to improve reading speed and focus by bolding the initial characters of words. This is achieved by dynamically bolding characters during indexing.

Focus Reading on X3

Implementation Details

Core Text Engine (ParsedText)

  • Modified ParsedText::addWord to implement a custom bolding algorithm. It uses a 45% ratio for bolding, with a minimum of 1 character and a maximum of 9.
  • UTF-8 Safety: Integrated utf8NextCodepoint to ensure character counting and string slicing occur at safe byte boundaries, preventing corruption of multi-byte characters (e.g., accented letters or smart quotes).
  • Intelligent Tokenization: This correctly identifies and separates "word" characters (letters, apostrophes, hyphens) from "non-word" characters (numbers, brackets, smart quotes).
  • Formatting Preservation: The logic ensures that punctuation is not "stolen" for the bolding count and that existing styles (like italics or underlines) are preserved across the bold/regular split.
  • Processing at indexing stage reduces CPU load at render-time and ensures layout/fit is unaffected.
  • Split details are tracked with wordIsFocusSuffix. After splitting and layout, suffixes are merged back into their preceding word entries to prevent a doubling of RAM usage.

Settings and UI

  • Version Management: Bumped SECTION_FILE_VERSION to 21
  • Global Settings: Added focusReadingEnabled to CrossPointSettings.
  • User Interface: Added a new toggle in the "Reader" section of the settings menu, positioned after the "Embedded Style" option.
  • Localization: Added the STR_FOCUS_READING string

Plumbing

  • Plumbed the focusReadingEnabled boolean through EpubReaderActivity, Section, and ChapterHtmlSlimParser to ensure the user's setting reaches the ParsedText constructor during chapter indexing.

Additional Context

Files Changed

  • lib/Epub/Epub/ParsedText.h / .cpp: Core fixation logic and UTF-8 tokenization.
  • lib/Epub/Epub/Section.h / .cpp: Cache header updates and invalidation logic.
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h / .cpp: Plumbing the setting to text blocks.
  • src/CrossPointSettings.h: Data persistence for the new setting.
  • src/SettingsList.h: UI toggle implementation.
  • src/activities/reader/EpubReaderActivity.cpp: Handling settings changes during reading sessions.
  • lib/I18n/translations/*.yaml: UI strings.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 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

Adds a focusReadingEnabled flag through settings, section serialization, parser construction, and reader call sites; implements UTF‑8 codepoint-aware tokenization in ParsedText to emit bold-prefix + remainder tokens for focus reading; and adds localized STR_FOCUS_READING entries.

Changes

Cohort / File(s) Summary
Text Processing
lib/Epub/Epub/ParsedText.cpp, lib/Epub/Epub/ParsedText.h
Add isWordCharacter() and refactor ParsedText::addWord() to perform UTF‑8 codepoint segmentation when focus reading is enabled; split words into a bold-prefix (~40% of codepoints, clamped) and remainder; store focusReadingEnabled in ParsedText.
Section Serialization
lib/Epub/Epub/Section.cpp, lib/Epub/Epub/Section.h
Bump section file version 19→20, increase header size, serialize new focusReadingEnabled bool in header, and extend writeSectionFileHeader/loadSectionFile/createSectionFile signatures and header checks.
Parser Integration
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp, lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
Add focusReadingEnabled parser member and constructor parameter; pass flag into ParsedText when starting new text blocks.
Settings & UI
src/CrossPointSettings.h, src/SettingsList.h
Add persisted focusReadingEnabled field and register a new reader toggle bound to STR_FOCUS_READING with JSON key focusReadingEnabled.
Reader Call Sites
src/activities/reader/EpubReaderActivity.cpp
Thread SETTINGS.focusReadingEnabled into Section::loadSectionFile() and Section::createSectionFile() for render and silent indexing paths.
Localization
lib/I18n/translations/*
lib/I18n/translations/english.yaml, ... (multiple files)
Add STR_FOCUS_READING translation entries across many languages (English, French, German, Spanish, Belarusian, etc.).

Sequence Diagram

sequenceDiagram
    participant Settings as App Settings
    participant Reader as EpubReaderActivity
    participant Section as Section
    participant Parser as ChapterHtmlSlimParser
    participant Parsed as ParsedText

    Settings->>Reader: provide focusReadingEnabled
    Reader->>Section: create/loadSectionFile(..., focusReadingEnabled)
    Section->>Section: write/load header (includes focusReadingEnabled)
    Section->>Parser: construct ChapterHtmlSlimParser(..., focusReadingEnabled)
    Parser->>Parsed: startNewTextBlock(..., focusReadingEnabled)
    Parsed->>Parsed: addWord(text) — classify via isWordCharacter(), UTF-8 codepoint segmentation
    Parsed->>Parser: emit tokens (first segment may be bold-prefix + remainder)
    Parser-->>Section: assembled styled tokens
    Section-->>Reader: serialized section / pages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ui-device

Suggested reviewers

  • znelson
  • osteotek
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: focus reading' directly describes the main feature being added and is consistent with the substantial changeset implementing the Focus Reading feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is directly related to the changeset, detailing implementation of Focus Reading feature with specific technical details about UTF-8 handling, bolding algorithm, settings integration, and file changes.

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@vjapolitzer vjapolitzer force-pushed the feat/focus-reading branch 2 times, most recently from 55f4532 to 2bf8732 Compare April 16, 2026 06:13
@vjapolitzer vjapolitzer marked this pull request as ready for review April 18, 2026 03:40
@vjapolitzer
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
lib/Epub/Epub/ParsedText.cpp (1)

113-201: LGTM, with a small operational note on memory.

Segmentation, UTF-8 slicing, and attach-flag propagation all look correct:

  • First segment inherits attachToPrevious, every subsequent segment is forced to attach=true so the sub-tokens glue back into a single visual word.
  • targetBoldChars is clamped to [1, 9] and the >= charCount branch correctly emits a single fully-bold entry instead of an empty remainder.
  • std::string_view-based slicing avoids intermediate std::string allocations on the hot path; the only allocations are the final emplace_back copies into words.

Operational note (not blocking): when focusReadingEnabled is true, each split word produces two entries in words / wordStyles / wordContinues (plus additional entries per punctuation transition). For a chapter that normally sits near the mid-paragraph flush at 750 words, the effective vector size can roughly double, which is worth keeping an eye on given the ESP32-C3 RAM budget. If you start seeing OOM on large chapters with focus reading on, reserving capacity up-front in addWord or pre-estimating the split count could reduce reallocations.

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

In `@lib/Epub/Epub/ParsedText.cpp` around lines 113 - 201, When
focusReadingEnabled is true the split logic can double (or more) the number of
entries and cause many reallocations; in the addWord method (where
focusReadingEnabled, words, wordStyles, wordContinues and the processSegment
lambda live) pre-reserve capacity for the vectors before running the splitter:
compute a cheap upper-bound extra entries (e.g. +2 for a simple word split or
estimate by counting UTF‑8 codepoints in the input to add that many entries) and
call words.reserve(words.size()+extra), wordStyles.reserve(...),
wordContinues.reserve(...). This avoids repeated reallocations while leaving the
splitting logic otherwise unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/Epub/Epub/ParsedText.cpp`:
- Around line 77-100: isWordCharacter currently returns true for all cp values
outside a few punctuation ranges, causing symbols like currency, arrows, math
operators, dingbats and emoji (e.g., U+20AC) to be treated as letters; update
isWordCharacter to explicitly exclude symbol blocks by returning false for
ranges such as Currency Symbols (0x20A0–0x20CF), Arrows (0x2190–0x21FF),
Mathematical Operators (0x2200–0x22FF), Misc Symbols (0x2600–0x26FF), Dingbats
(0x2700–0x27BF) and the common emoji blocks (e.g.,
0x1F300–0x1F5FF/0x1F600–0x1F64F), while preserving the existing ASCII, General
Punctuation (0x2000–0x206F) and Latin-1 checks so that letter blocks
(CJK/Greek/Cyrillic/etc.) still default to word characters.

---

Nitpick comments:
In `@lib/Epub/Epub/ParsedText.cpp`:
- Around line 113-201: When focusReadingEnabled is true the split logic can
double (or more) the number of entries and cause many reallocations; in the
addWord method (where focusReadingEnabled, words, wordStyles, wordContinues and
the processSegment lambda live) pre-reserve capacity for the vectors before
running the splitter: compute a cheap upper-bound extra entries (e.g. +2 for a
simple word split or estimate by counting UTF‑8 codepoints in the input to add
that many entries) and call words.reserve(words.size()+extra),
wordStyles.reserve(...), wordContinues.reserve(...). This avoids repeated
reallocations while leaving the splitting logic otherwise unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 286b00e2-4d5e-4011-986a-c1d64006d15f

📥 Commits

Reviewing files that changed from the base of the PR and between 81ae9dd and bf6eba0.

📒 Files selected for processing (31)
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/ParsedText.h
  • lib/Epub/Epub/Section.cpp
  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • 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.h
  • src/SettingsList.h
  • src/activities/reader/EpubReaderActivity.cpp
📜 Review details
🧰 Additional context used
🧠 Learnings (33)
📚 Learning: 2026-02-23T22:11:42.181Z
Learnt from: ariel-lindemann
Repo: crosspoint-reader/crosspoint-reader PR: 1133
File: lib/I18n/translations/finnish.yaml:92-92
Timestamp: 2026-02-23T22:11:42.181Z
Learning: In translation YAML files located in lib/I18n/translations/, literal percent signs (%) in strings (e.g., STR_HIDE_BATTERY, STR_GO_TO_PERCENT, STR_PERCENT_STEP_HINT) do not require escaping as %% and this convention is consistent across all language translations. Ensure that this pattern is followed for all YAML files in this directory to avoid introducing incorrect formatting markers in localized strings.

Applied to files:

  • lib/I18n/translations/dutch.yaml
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/romanian.yaml
  • lib/I18n/translations/slovenian.yaml
  • lib/I18n/translations/danish.yaml
  • lib/I18n/translations/hungarian.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/turkish.yaml
  • lib/I18n/translations/lithuanian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/finnish.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/catalan.yaml
  • lib/I18n/translations/belarusian.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/french.yaml
📚 Learning: 2026-03-25T10:28:30.516Z
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1495
File: lib/I18n/translations/german.yaml:294-322
Timestamp: 2026-03-25T10:28:30.516Z
Learning: In crosspoint-reader, it is expected to include “pre-emptive” translation keys in non-English YAML files before the corresponding English keys exist (e.g., german.yaml ahead of en.yaml). When reviewing changes to files under lib/I18n/translations/*.yaml, do not flag missing corresponding English keys in non-English locales as an error if the repo’s gen_i18n.py fallback mechanism is intended to handle missing English keys safely until the pending PRs land.

Applied to files:

  • lib/I18n/translations/dutch.yaml
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/romanian.yaml
  • lib/I18n/translations/slovenian.yaml
  • lib/I18n/translations/danish.yaml
  • lib/I18n/translations/hungarian.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/turkish.yaml
  • lib/I18n/translations/lithuanian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/finnish.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/catalan.yaml
  • lib/I18n/translations/belarusian.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/french.yaml
📚 Learning: 2026-04-17T18:59:20.878Z
Learnt from: KymAndriy
Repo: crosspoint-reader/crosspoint-reader PR: 1684
File: lib/I18n/translations/ukrainian.yaml:29-29
Timestamp: 2026-04-17T18:59:20.878Z
Learning: For Ukrainian (and other Slavic-language) translation YAML strings in lib/I18n/translations/*.yaml: if a format string contains a numeric placeholder produced via snprintf-style formatting (e.g., %zu) and runtime noun inflection is not feasible, place the relevant noun in genitive plural before the number in the string (e.g., "мереж %zu") rather than after it (e.g., "мережу %zu"). This avoids incorrect case forms that would otherwise result from the value affecting inflection (e.g., wrong forms like "мережу/мережі/мереж"). In such cases, do not flag the word order as incorrect.

Applied to files:

  • lib/I18n/translations/dutch.yaml
  • lib/I18n/translations/swedish.yaml
  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/romanian.yaml
  • lib/I18n/translations/slovenian.yaml
  • lib/I18n/translations/danish.yaml
  • lib/I18n/translations/hungarian.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/turkish.yaml
  • lib/I18n/translations/lithuanian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/finnish.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/catalan.yaml
  • lib/I18n/translations/belarusian.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/french.yaml
📚 Learning: 2026-02-16T22:25:35.674Z
Learnt from: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: lib/I18n/I18nKeys.h:271-272
Timestamp: 2026-02-16T22:25:35.674Z
Learning: In the crosspoint-reader i18n system (lib/I18n/), missing translation keys in non-English YAML files are automatically filled with English strings at build time by the gen_i18n.py script. All generated string arrays (STRINGS_EN, STRINGS_ES, etc.) have identical lengths, so runtime array indexing is safe without per-string fallback logic. The system prints "INFO: '{key}' missing in {lang_code}, using English fallback" during code generation when this occurs.

Applied to files:

  • lib/I18n/translations/kazakh.yaml
  • lib/I18n/translations/portuguese.yaml
  • lib/I18n/translations/hungarian.yaml
  • lib/I18n/translations/ukrainian.yaml
  • lib/I18n/translations/czech.yaml
  • lib/I18n/translations/polish.yaml
  • lib/I18n/translations/lithuanian.yaml
  • lib/I18n/translations/spanish.yaml
  • lib/I18n/translations/russian.yaml
  • lib/I18n/translations/german.yaml
  • lib/I18n/translations/english.yaml
  • lib/I18n/translations/catalan.yaml
  • lib/I18n/translations/belarusian.yaml
  • lib/I18n/translations/italian.yaml
  • lib/I18n/translations/french.yaml
📚 Learning: 2026-04-05T10:43:16.171Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1534
File: src/activities/boot_sleep/SleepActivity.cpp:0-0
Timestamp: 2026-04-05T10:43:16.171Z
Learning: In crosspoint-reader/crosspoint-reader (src/CrossPointSettings.h), CrossPointSettings::SLEEP_COVER_OVERLAY enum has values: OVERLAY_OFF=0, OVERLAY_WHITE=1, OVERLAY_GRAY=2, OVERLAY_BLACK=3 (as of commit 2471b5a in PR `#1534`). The default sleepCoverOverlay byte is 0 (OVERLAY_OFF). Do not use the old ordering WHITE=0/GRAY=1/BLACK=2/OFF=3 in future reviews.

Applied to files:

  • src/CrossPointSettings.h
📚 Learning: 2026-04-02T15:35:02.228Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1554
File: src/activities/boot_sleep/SleepActivity.cpp:40-63
Timestamp: 2026-04-02T15:35:02.228Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/boot_sleep/SleepActivity.cpp, SleepActivity::renderBitmapSleepScreen), CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_CONTRAST is intentionally handled implicitly: `hasGreyscale` is computed as `bitmap.hasGreyscale() && SETTINGS.sleepScreenFilter == FILTER_NONE`. When FILTER_CONTRAST is selected, `hasGreyscale` evaluates to false, which skips the 4-level greyscale rendering path and falls back to 1-bit B&W dithered rendering, producing a visibly higher-contrast result on bitmaps with greyscale data. LOGO and BLANK screens are unaffected by FILTER_CONTRAST by design — they contain no bitmap content where a contrast effect would be meaningful. Do not flag FILTER_CONTRAST as unimplemented or a no-op in future reviews.

Applied to files:

  • src/CrossPointSettings.h
📚 Learning: 2026-03-25T01:30:23.778Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:64-68
Timestamp: 2026-03-25T01:30:23.778Z
Learning: In crosspoint-reader/crosspoint-reader, `CrossPointState::pendingCoverGeneration` (bool, default false) and `CrossPointState::forceRenderCoverPath` (std::string, default "") are purely transient in-memory fields. They are NOT serialized in `JsonSettingsIO::saveState`/`loadState`. On every reboot they revert to their defaults, so there is no reboot-replay risk and no need to call `APP_STATE.saveToFile()` after consuming them in `HomeActivity::loadRecentCovers()`. Do not flag their in-memory consumption as a persistence bug.

Applied to files:

  • src/CrossPointSettings.h
📚 Learning: 2026-04-09T20:05:11.820Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/reader/EpubReaderMenuActivity.cpp:39-45
Timestamp: 2026-04-09T20:05:11.820Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/EpubReaderMenuActivity.cpp, EpubReaderMenuActivity::buildMenuItems), the `hasCoverForTheme` boolean (derived from `Storage.exists(coverPath)`) is intentionally used — not `!coverDisabled` — to choose between STR_HOME_COVER_ACTION_DISABLE and STR_HOME_COVER_ACTION_ENABLE in non-TIMEOUT global modes. The label reflects the action that will be performed: BMP exists → "Disable" (delete it); no BMP → "Enable" (generate it). Using `coverDisabled` would create confusing edge cases (e.g., coverDisabled=false with no BMP on disk would show "Disable" when there is nothing to disable). Do not flag this as a bug in future reviews.

Applied to files:

  • src/CrossPointSettings.h
📚 Learning: 2026-03-25T01:15:06.744Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/components/themes/lyra/Lyra3CoversTheme.cpp:39-40
Timestamp: 2026-03-25T01:15:06.744Z
Learning: In crosspoint-reader/crosspoint-reader (src/components/themes/lyra/Lyra3CoversTheme.cpp and other theme drawing functions), `COVER_DISABLED_MODE` (global setting) only prevents new cover *generation* on HOME entry to avoid blocking the main thread. It does NOT suppress the display of already-cached covers. Only the per-book `coverDisabled` flag (RecentBook::coverDisabled) controls whether a cached cover BMP is rendered. Do not flag theme draw paths for not checking COVER_DISABLED_MODE; checking only coverDisabled is correct by design.

Applied to files:

  • src/CrossPointSettings.h
  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-21T16:47:45.578Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1031
File: src/activities/reader/EpubReaderActivity.cpp:816-853
Timestamp: 2026-02-21T16:47:45.578Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the navigateToHref() function uses a fixed-size mini-stack (MAX_FOOTNOTE_DEPTH = 3) for saved positions rather than a dynamic stack. This is intentional to minimize RAM usage on ESP32-C3, which has limited memory. The design accepts that failed navigation at max depth may decrement footnoteDepth even when no push occurred, as a tradeoff for memory efficiency. Users exit by pressing Back three times maximum.

Applied to files:

  • src/CrossPointSettings.h
  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-04-09T19:53:18.381Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:73-77
Timestamp: 2026-04-09T19:53:18.381Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers and HomeActivity::render), `coverDisabled` must ONLY be cleared (set to false) at two explicit points: (1) after a successful `generateThumbBmp()` call in `loadRecentCovers`, and (2) after an explicit user "Enable cover" / "Generate cover" COVER_ACTION in EpubReaderActivity. Do NOT clear `coverDisabled` based on the mere existence of a cached cover BMP (neither in `loadRecentCovers` nor in `render`). A stale BMP on disk is not proof of user re-enable intent and must not override the per-book `coverDisabled` flag.

Applied to files:

  • src/CrossPointSettings.h
  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-19T17:46:36.345Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:36.345Z
Learning: In src/activities/reader/EpubReaderActivity.cpp's renderContents() method, when uncached images exist, Phase 1 intentionally calls displayBuffer(forceFullRefresh) to perform a HALF_REFRESH (if needed), while Phase 2 intentionally calls renderer.displayBuffer() directly without forceFullRefresh. This is by design: Phase 1's refresh clears the screen properly to prevent ghosting, so Phase 2 can use a faster refresh mode for better performance. The grayscale anti-aliasing is handled separately after renderContents() via displayGrayBuffer().

Applied to files:

  • src/CrossPointSettings.h
📚 Learning: 2026-04-12T12:28:33.205Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1629
File: src/activities/home/HomeActivity.cpp:119-120
Timestamp: 2026-04-12T12:28:33.205Z
Learning: When reviewing code in this repository (C/C++ sources), set review comment severity according to this policy:
- Use **Major** (🟠) only for defects with realistic risk of crash, out-of-memory (OOM), invalid pointer dereference, data corruption, or other severe issues that are unlikely to be caught in casual/manual device testing.
- Use **Minor** or informational for UX gaps, logic edge-cases, style issues, or missing feature completeness that the author can verify (or has verified) through normal device use.
- Do **not** escalate severity to Major based on behavioral/UX observations alone; assume the author has already tested the feature on their own device and only treat issues as Major if they match the high-risk defect categories above.

Applied to files:

  • src/CrossPointSettings.h
  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.h
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • src/SettingsList.h
  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-20T19:51:12.696Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp, prepareSection), when `epub->getTocIndexForSpineIndex(currentSpineIndex)` returns a negative value (pre-TOC spine with no TOC entry), `prepareSection` skips the entire chapter walk and leaves `chapterPageInfo` unset. The status bar correctly falls back to `section->pageCount` in this scenario. This means the `pageTocIndex == -1` path inside the `render()` per-page TOC update block is handled correctly by simply leaving `chapterPageInfo` unchanged — no explicit clear is needed.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-02-22T19:13:22.166Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:22.166Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the EpubReaderMenuActivity result callback intentionally applies orientation changes (via applyOrientation) before checking result.isCancelled. This differs from other callbacks in the file because orientation is treated as an immediate setting that should persist even when the user cancels the menu, rather than a deferred action requiring confirmation.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-19T17:46:26.871Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:26.871Z
Learning: In EpubReaderActivity.cpp, renderContents() behavior is intentional: if uncached images exist, Phase 1 should call displayBuffer(forceFullRefresh) to perform a HALF_REFRESH and clear ghosting; Phase 2 should call renderer.displayBuffer() without forceFullRefresh for a faster refresh. Grayscale anti-aliasing is applied separately after renderContents() via displayGrayBuffer(). This guidance applies specifically to this file and situation.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-28T11:14:39.035Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:144-145
Timestamp: 2026-03-28T11:14:39.035Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity), `recentsLoaded` must NOT be reset in `onEnter()`. It defaults to `false` for new instances, but for retained instances it must stay `true` between HOME visits to prevent redundant cover regeneration on every entry. Cover generation is gated on `APP_STATE.pendingCoverGeneration` (set in `EpubReaderActivity::onExit()`) and `APP_STATE.forceRenderCoverPath` (set by Reader Menu cover actions) — these are the only authoritative triggers. Resetting `recentsLoaded` in `onEnter()` would cause generation to fire on every HOME visit (e.g., returning from Settings). Only `coverRendered` and `coverBufferStored` are correctly reset in `onEnter()`.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-28T20:57:09.493Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-03-28T20:57:09.493Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::onEnter), `coverRendered = false` and `coverBufferStored = false` ARE correctly reset in `onEnter()`. This was fixed in PR `#1488`. Do not flag their absence as a bug in future reviews.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-04-09T18:06:14.309Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-09T18:06:14.309Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers), the XTC generateThumbBmp() failure path uses master-style handling: it clears coverBmpPath by calling RECENT_BOOKS.updateBook(book.path, book.title, book.author, "") rather than setting coverDisabled=true. Only the EPUB path uses setCoverDisabled(true) on generateThumbBmp failure. Do not flag XTC failure→clear-bmpPath vs EPUB failure→coverDisabled as an inconsistency; they are intentionally different by design.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-29T19:50:44.877Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1534
File: src/activities/boot_sleep/SleepActivity.cpp:177-183
Timestamp: 2026-03-29T19:50:44.877Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/boot_sleep/SleepActivity.cpp, SleepActivity::getBookOverlayInfo), when reading totalPages from the TXT index cache (index.bin), the code must validate the cache header: check the magic number (0x54585449 / "TXTI") and version fields before trusting totalPages. This mirrors TxtReaderActivity::loadPageIndexCache(). If validation fails, totalPages stays 0 and the overlay falls back to "Page X" (no total) via STR_OVERLAY_READING_PROGRESS_NO_TOTAL. Do not flag this validation as unnecessary in future reviews.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-04-09T18:26:10.532Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-09T18:26:10.532Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers), the XTC generateThumbBmp() failure path uses master-style handling: it clears coverBmpPath by calling RECENT_BOOKS.updateBook(book.path, book.title, book.author, "") rather than setting coverDisabled=true. Only the EPUB path uses setCoverDisabled(true) on generateThumbBmp failure. The reason is intentional by design: XTC reader has no reader menu COVER_ACTION (no "Enable/Generate cover" option), so setting coverDisabled=true on XTC failure would permanently block regeneration with no user-accessible recovery path. Clearing the BMP path instead allows HOME to fall back to the classical placeholder, and a future successful generateThumbBmp() call will repopulate it naturally. Do not flag XTC failure→clear-bmpPath vs EPUB failure→coverDisabled as an inconsistency in future reviews.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-20T19:51:12.696Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp), orphan/post-TOC spines always inherit a non-negative `tocIndex` from the previous chapter via `BookMetadataCache`. Therefore `getTocIndexForSpineIndex` and `getTocIndexForPage` (which falls back to `getTocIndexForSpineIndex` when `tocBoundaries` is empty) will never return -1 for post-TOC spines. The only case where `pageTocIndex == -1` can occur is a genuine pre-TOC spine (e.g. the very first spine) that has no TOC entry at all and no predecessor to inherit from.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-04-16T06:13:29.082Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1582
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:497-501
Timestamp: 2026-04-16T06:13:29.082Z
Learning: In `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`, when the image consumes the empty block's accumulated vertical spacing, the reset of `currentTextBlock`'s `BlockStyle` to a fresh `BlockStyle` (with only `alignment` set) is intentionally correct. `getCombinedBlockStyle(..., CombineAxis::Vertical)` initializes `BlockStyle result = child`, so horizontal properties on the empty block are never read — the block-style stack provides horizontal context for the next element. Only the vertical margins/padding from the empty block are read during the merge, and the reset zeroes exactly those. Preserving horizontal insets from the empty block would add complexity for zero behavioral difference. Do not flag this reset as dropping inherited horizontal style in future reviews.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.h
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-02T10:14:16.036Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:277-308
Timestamp: 2026-03-02T10:14:16.036Z
Learning: Guideline: Strengthen serialization::readString to defend against unbounded growth when reading from disk data. Implement and enforce a maximum allowed length (e.g., a configured or reasonable constant) and validate the incoming length before resizing or allocating. Audit all call sites (e.g., BookMetadataCache, TextBlock, KOReaderCredentialStore, Section cache readers) to ensure they do not rely on unbounded len-based resizing. If the readString API must remain, add internal safeguards (bounds checks, length validation, and error handling) so per-call-site validations are not required. Ensure Section cache files remain versioned (SECTION_FILE_VERSION) and parameter mismatches invalidate caches, but do not rely on unsafe allocations; prefer safe, bounded reads with explicit errors on invalid data.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-28T11:06:29.611Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:92-95
Timestamp: 2026-03-28T11:06:29.611Z
Learning: When reviewing crosspoint-reader code, avoid flagging a missing `renderer.displayBuffer()` call immediately after `GUI.drawPopup()` / `BaseTheme::drawPopup()`: `BaseTheme::drawPopup()` already calls `renderer.displayBuffer()` before returning, so the popup is guaranteed to be flushed to the e-ink panel before subsequent blocking work begins. Conversely, do not require a `renderer.displayBuffer()` call after `fillPopupProgress()`; it intentionally does not flush, so intermediate progress-bar updates may not appear unless the update granularity warrants an explicit flush elsewhere.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-02-19T12:17:05.421Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:05.421Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.

Applied to files:

  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/Epub/Epub/ParsedText.cpp
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-02T21:18:25.387Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:137-150
Timestamp: 2026-03-02T21:18:25.387Z
Learning: In ChapterHtmlSlimParser.cpp, pendingAnchorId is a single string (not a vector) because EPUB footnote targets are virtually always block-level elements (<aside>, <p>, <div>) that trigger startNewTextBlock() and flush the anchor before overwrite can occur. Multiple consecutive inline IDs would resolve to the same completedPageCount anyway, and vector allocation overhead is unnecessary for ESP32-C3. Only inline IDs in practice are footnote back-references (<a id="ref1">), which are never navigation targets.

Applied to files:

  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-21T19:30:33.664Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1454
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:101-105
Timestamp: 2026-03-21T19:30:33.664Z
Learning: In `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`, the `blockStyleStack` is a fixed-capacity array (`MAX_BLOCK_STYLE_DEPTH`, currently 8). On overflow, `pushBlockStyle()` only logs an error and increments `blockStyleStackSize` without writing a new entry; `getAccumulatedBlockStyle()` returns the deepest stored accumulated entry as a fallback. This is intentional — real EPUBs are expected to have ≤ 8 block-style nesting levels. Inline styles use a `vector`-based stack and can nest more deeply. The overflow threshold or container type can be revisited if 8 proves insufficient in practice. Do not flag the overflow fallback behavior as a bug in future reviews.

Applied to files:

  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-04-13T09:12:18.349Z
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1646
File: lib/KOReaderSync/ChapterXPathIndexerState.h:80-80
Timestamp: 2026-04-13T09:12:18.349Z
Learning: In `lib/KOReaderSync/ChapterXPathIndexerState.h`, `shouldSkipText(len)` is intentionally a fast structural gate only (skipDepth, zero-length, outside body). Both callers (`ChapterXPathForwardMapper.cpp` and `ChapterXPathReverseMapper.cpp`) check `isWhitespaceOnly(text, len)` separately and later, because whitespace-only nodes may still require partial processing before being discarded — in particular, the forward mapper accumulates `codepointsInBodyTextNode` for body-level whitespace text nodes. Do not suggest merging `isWhitespaceOnly` into `shouldSkipText` in future reviews.

Applied to files:

  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/ParsedText.cpp
📚 Learning: 2026-03-02T09:58:47.539Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:238-246
Timestamp: 2026-03-02T09:58:47.539Z
Learning: In EPUB section files, typical sections contain approximately 20 footnote/note anchors plus a few sub-chapter anchors (max ~30-50 total) even in large chapters. While the code tracks all HTML id attributes, realistic anchor counts per section remain well under 255, making uint8_t sufficient for 99% of EPUBs, though uint16_t provides extra safety margin for edge cases.

Applied to files:

  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-20T19:43:16.969Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: lib/Epub/Epub/Section.cpp:115-117
Timestamp: 2026-03-20T19:43:16.969Z
Learning: In crosspoint-reader/crosspoint-reader, `Section::buildTocBoundariesFromFile()` and `Section::buildTocBoundaries()` are always called on freshly constructed `Section` objects (created via `std::make_unique<Section>(...)` in `prepareSection`). The `tocBoundaries` member vector is therefore always default-initialized (empty) before either builder runs — no explicit `tocBoundaries.clear()` is needed inside these methods. Do not flag missing clears as a bug in future reviews.

Applied to files:

  • lib/Epub/Epub/Section.h
  • lib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-17T15:27:17.468Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1413
File: lib/EpdFont/EpdFont.cpp:34-36
Timestamp: 2026-03-17T15:27:17.468Z
Learning: In crosspoint-reader/crosspoint-reader, `EpdFont::getGlyph()` falls back to `getGlyph(REPLACEMENT_GLYPH)` (U+FFFD) before returning `nullptr`. All fonts in this project are guaranteed to include a U+FFFD replacement glyph, so the `!glyph` null branch in `EpdFont::getTextBounds()` (and similar rendering paths) is unreachable in practice. Do not flag stale-state issues in that branch (e.g., leftover `lastBaseAdvanceFP`/`lastBaseTop` after a null glyph) as bugs in future reviews.

Applied to files:

  • lib/Epub/Epub/ParsedText.cpp
🔇 Additional comments (31)
lib/I18n/translations/finnish.yaml (1)

261-261: LGTM!

The Finnish translation for the Focus Reading feature is correctly formatted and appropriately placed in alphabetical order. The translation "Keskittynyt lukeminen" (Focused reading) accurately conveys the feature's purpose.

lib/I18n/translations/swedish.yaml (1)

290-290: Looks good — Swedish key added correctly.

STR_FOCUS_READING is present with a clear localized value and matches the new setting key naming.

lib/I18n/translations/danish.yaml (1)

286-286: Looks good — Danish key added correctly.

The new STR_FOCUS_READING entry is consistent with the i18n key structure.

lib/I18n/translations/slovenian.yaml (1)

286-286: Looks good — Slovenian key added correctly.

STR_FOCUS_READING is correctly introduced with a valid localized string.

lib/I18n/translations/turkish.yaml (1)

261-261: Looks good — Turkish key added correctly.

The STR_FOCUS_READING translation is properly defined and aligned with the feature toggle key.

lib/I18n/translations/dutch.yaml (1)

286-286: Looks good — Dutch key added correctly.

STR_FOCUS_READING is added in the expected format with an appropriate localized value.

lib/I18n/translations/czech.yaml (1)

262-262: Looks good — Czech key added correctly.

The new STR_FOCUS_READING entry is consistent with the existing translation schema.

lib/I18n/translations/portuguese.yaml (1)

262-262: Looks good — Portuguese key added correctly.

STR_FOCUS_READING is properly added and matches the expected i18n key pattern.

lib/I18n/translations/kazakh.yaml (1)

260-260: Looks good — Kazakh key added correctly.

The new STR_FOCUS_READING translation is well-formed and aligned with the new setting key.

lib/I18n/translations/catalan.yaml (1)

286-286: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Catalan translation "Lectura enfocada" is correctly placed and formatted.

lib/I18n/translations/romanian.yaml (1)

286-286: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Romanian translation "Lectură concentrată" is correctly placed and formatted.

lib/I18n/translations/belarusian.yaml (1)

261-261: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Belarusian translation "Фокуснае чытанне" is correctly placed and formatted.

lib/I18n/translations/german.yaml (1)

287-287: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with German translation "Fokus-Lesen" is correctly placed and formatted.

lib/I18n/translations/russian.yaml (1)

289-289: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Russian translation "Фокусное чтение" is correctly placed and formatted.

lib/I18n/translations/hungarian.yaml (1)

286-286: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Hungarian translation "Fókuszált olvasás" is correctly placed and formatted.

lib/I18n/translations/polish.yaml (1)

286-286: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with Polish translation "Czytanie skupione" is correctly placed and formatted.

lib/I18n/translations/english.yaml (1)

290-290: LGTM! Translation addition is clean and properly formatted.

The new STR_FOCUS_READING key with English translation "Focus Reading" is correctly placed and formatted. This serves as the base translation for the i18n fallback system.

lib/I18n/translations/ukrainian.yaml (1)

286-286: LGTM. Translation reads naturally.

lib/I18n/translations/lithuanian.yaml (1)

286-286: LGTM.

lib/I18n/translations/spanish.yaml (1)

286-286: LGTM.

lib/I18n/translations/italian.yaml (1)

262-262: LGTM.

lib/I18n/translations/french.yaml (1)

286-286: LGTM.

src/CrossPointSettings.h (1)

198-199: LGTM. New setting field with safe default (disabled). Consistent with other boolean toggles stored as uint8_t.

lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp (1)

152-152: Constructor arguments align with ParsedText signature.

Positional order (extraParagraphSpacing, hyphenationEnabled, focusReadingEnabled, blockStyle) matches the declaration in lib/Epub/Epub/ParsedText.h:38-42. No concerns.

src/activities/reader/EpubReaderActivity.cpp (1)

549-560: Flag threaded consistently through both cache load and build paths.

focusReadingEnabled is passed to loadSectionFile and createSectionFile at all four call sites (main render at lines 552/560 and silent pre-index at lines 674/682) using the same SETTINGS.focusReadingEnabled value, which is required so the cache-hit check and subsequent build agree on the parameter and don't trigger a rebuild every open. Argument order matches Section.h:34-41.

Also applies to: 671-682

src/SettingsList.h (1)

56-57: LGTM.

Toggle is correctly placed in the Reader category and wired to CrossPointSettings::focusReadingEnabled with a stable JSON key.

lib/Epub/Epub/ParsedText.h (1)

38-43: Parameter insertion position is safe.

Inserting focusReadingEnabled before blockStyle is source-compatible: any existing caller passing a BlockStyle positionally as the 3rd argument would fail to compile (BlockStyle is not bool-convertible), so no silent regressions are possible. Initializer list is clean.

lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h (1)

50-126: LGTM.

focusReadingEnabled plumbed through cleanly; signature and initializer list are consistent with how other trailing config flags (e.g., hyphenationEnabled) are handled.

lib/Epub/Epub/Section.h (1)

19-41: LGTM.

focusReadingEnabled is inserted consistently as a trailing mandatory parameter across all three methods, and is placed before popupFn in createSectionFile so the default-argument tail is preserved.

lib/Epub/Epub/Section.cpp (2)

13-16: Version bump + header layout are correct.

Bumping SECTION_FILE_VERSION to 20 ensures existing caches are invalidated on first boot after upgrade, and the HEADER_SIZE addition plus the static_assert at lines 45-50 keep the header layout verifiable at compile time. The parameter-mismatch guard at line 111 also includes focusReadingEnabled, so toggling the setting invalidates the cache cleanly.


215-219: Argument position verified against parser signature.

focusReadingEnabled is passed in the 11th position (right after hyphenationEnabled, before the completePageFn lambda), which matches the ChapterHtmlSlimParser constructor declaration in the header. No misalignment with surrounding positional arguments.

Comment thread lib/Epub/Epub/ParsedText.cpp
Expanded the range of characters in the punctuation check to include currency, math symbols, arrows, and other symbols.
Added logic to pre-reserve capacity for word splits to avoid reallocation mid-word
@Kile
Copy link
Copy Markdown

Kile commented Apr 18, 2026

This is SO COOL! I was hoping the firmware would have this when I first installed it and I'm glad someone beat me to adding it!! Awesome job :D

@mitmarcus
Copy link
Copy Markdown

While bionic reading doesn't make one faster, and supposedly marginally decreases text comprehension, it is so much more pleasant to read on a small screen with it.

@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented Apr 20, 2026

Thank you so much for this feature, @vjapolitzer, It looks amazing!!
I'll give it a try and let you know what I think! ;)

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

Thanks @pablohc
One known issue is the integer truncation on the 40% bold calculation is rounding down, but I'll fix that later after work =) Hopefully it works well for you!

vjapolitzer and others added 2 commits April 20, 2026 19:31
Adjust target bold character calculation to use a fixed percentage for better readability and improve tuning for desired bolding
@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented Apr 21, 2026

Thanks @pablohc One known issue is the integer truncation on the 40% bold calculation is rounding down, but I'll fix that later after work =) Hopefully it works well for you!

I'm not used to this font, but I saw it has 5 levels. I thought F4 was intentionally set by default, rather than a bug 🤣

I was going to suggest being able to choose the level between F1 and F5, but I see you've already implemented it. I'll try it again today with the latest changes.

Amazing work, @vjapolitzer!

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

I'm not used to this font, but I saw it has 5 levels. I thought F4 was intentionally set by default, rather than a bug 🤣

I was going to suggest being able to choose the level between F1 and F5, but I see you've already implemented it. I'll try it again today with the latest changes.

Amazing work, @vjapolitzer!

Thank you! I can certainly change the settings toggle to an Off/1/2/3/4/5 enum if folks would like that. I was guessing that something similar to F3 might be the most popular and went simple, for now.

@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented Apr 21, 2026

Oh no, no, I misread the commit description: ec4639f

Improve target bold tuning
Adjust target bold character calculation to use a fixed percentage for better readability and improve tuning for desired bolding

I had understood that it was adjustable, but if it is not, I also think that the F3 is the most balanced option and avoids greater complexity. If the truncation bug is already resolved, it will be perfect.

I have done a quick test based on 9e99759 and I don't know if it is solved in the following commits, if so, don't take it into account.

9e99759 Expected
Goldman-de-Baltimore Goldman-de-Baltimore

Remove hyphen whitelist in isWordCharacter
@vjapolitzer
Copy link
Copy Markdown
Contributor Author

vjapolitzer commented Apr 21, 2026

Oh no, no, I misread the commit description: ec4639f

Improve target bold tuning
Adjust target bold character calculation to use a fixed percentage for better readability and improve tuning for desired bolding

I had understood that it was adjustable, but if it is not, I also think that the F3 is the most balanced option and avoids greater complexity. If the truncation bug is already resolved, it will be perfect.

I have done a quick test based on 9e99759 and I don't know if it is solved in the following commits, if so, don't take it into account.

9e99759 Expected
Goldman-de-Baltimore Goldman-de-Baltimore

Ah I see! Yes, I have tweaked the truncation to yield a visual anchor weight similar to F3. Took a bit of fiddling since we aren't rendering the half-bold characters like Bionic Reading does. It wouldn't be too much more work to make it configurable to F1-F5, but I've been happy with F3.

Good catch on compound words! When I whitelisted apostrophes, I also mistakenly added hyphens (compound words) as well. That should be fixed now, thanks! Hmm, I think there are multiple types of hyphens, so some are still getting through. I'll fix this soon. OK, the previous commit 03141f2 seems to have fixed it, but I added some more hyphen-like characters in ab4fea1 just in case.

Added checks to reject specific dash and hyphen characters.
@Cesarsk
Copy link
Copy Markdown

Cesarsk commented Apr 26, 2026

Thanks for this feature, very much appreciated!

@pablohc pablohc self-requested a review April 26, 2026 13:52
pablohc
pablohc previously approved these changes Apr 26, 2026
Copy link
Copy Markdown
Contributor

@pablohc pablohc left a comment

Choose a reason for hiding this comment

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

I've been testing it directly on the device for several days now, and I haven't had any issues so far with the latest updates. Thank you so much for this amazing work, @vjapolitzer

@lukestein
Copy link
Copy Markdown
Contributor

I'm testing on device and reading (English) successfully. Very nice; thanks!

I want to test a bit more before my signoff, and note as always I'm not doing code review. Also, would like to make sure that if this gets merged it doesn't interfere with the ongoing custom font progress being made.

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

I'm testing on device and reading (English) successfully. Very nice; thanks!

I want to test a bit more before my signoff, and note as always I'm not doing code review. Also, would like to make sure that if this gets merged it doesn't interfere with the ongoing custom font progress being made.

Thank you!

It works by applying the bold font styling property during indexing, so I'm hopeful it shouldn't interfere. I've been testing it with the custom font PRs cherry picked in with good results.

@pablohc pablohc requested review from Uri-Tauber and znelson May 5, 2026 21:44
@lukestein
Copy link
Copy Markdown
Contributor

Hi @vjapolitzer … excited to test this with the new SD card font updates

@vjapolitzer vjapolitzer dismissed stale reviews from lukestein and pablohc via 1bab9d0 May 10, 2026 00:35
# Conflicts:
#	lib/Epub/Epub/Section.cpp
#	lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
#	src/CrossPointSettings.h
@vjapolitzer vjapolitzer force-pushed the feat/focus-reading branch from 1bab9d0 to 33e0d6b Compare May 10, 2026 00:36
@pablohc pablohc requested a review from mcrosson May 10, 2026 00:53
@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented May 10, 2026

I'm adding @mcrosson to the conversation in the hope that he might be able to share his thoughts once version 1.3.0 and the downloadable fonts have been released.

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

Thanks! Happy to discuss. I'm not familiar with the inner workings of the SD font code, but if we can get it working with contextual alternates, then I think "Bionic Reading" could work as a downloaded font (but that doesn't seem to work for me at this time.)

As a counter-point, "Bionic Reading" isn't necessarily a font, per-se, and having the feature implemented as in this PR allows readers to have the dynamic bolding with any font without needing to do some sort of processing or custom font creation.

In the meantime, here's it working with the downloaded Literata font after merging master:
pr1670_09052026

@mcrosson
Copy link
Copy Markdown
Contributor

Some thoughts after a quick review

  • This wouldnt be a font, its styling. Granted, its highly specific styling but its definitely not a font based on my understanding. I think trying to make it a font will artificially limit utility. As a human who's said 'you can take Atkinson Hyperlegible from my cold, dead hands'... I'd be a bit grumpy if I couldnt apply this on top of the font that makes reading easiest on my eyes and brain.
  • Is there a reason to do the style as part of the pipeline using custom values instead of just using standard bold weighting?
  • I'd like to see this tested with at least a couple/few sd fonts. SD fonts change the way some rendering works and I want to be sure this wont be problematic if a user is using an sd font. Ideally at least two 'normal', one mono and alll accessible (lexica, atkinson, open dyslexic). I know thats a bit of a PITA and tedious but itll ensure we have broad coverage for folk who want to use this feature.
  • I'd like this called out as a user manual item, ideally with screen shots (maybe using the fonts used for testing? ;) ). Itd probably be best as a new document in the docs folder along side the other user manual stuff.
  • How different is this code from crossink? If we do merge this code we remove the need for crossink to manage their own code and, ideally, wed work with @uxjulia on this PR to ensure that anything we add wont create more downstream work for them. If we do merge this feature I'd like to ensure crossink no longer has to manage things locally and can work with us going forward for maintaining the feature. I'd hate to see extra work made for crossink and us because we werent closely collaborating.
  • Is this a simple on/off setting or is there more tunability to the feature?
  • Do you have info on this approach to reading? I'd like to learn a bit more how folk use it to help their reading and how it may be used in terms of accessibility by others. This is mainly my own curiousity but I think it would help me better understand the purpose of the feature and how users will engage with it long term.

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

Some thoughts after a quick review

  1. This wouldnt be a font, its styling. Granted, its highly specific styling but its definitely not a font based on my understanding. I think trying to make it a font will artificially limit utility. As a human who's said 'you can take Atkinson Hyperlegible from my cold, dead hands'... I'd be a bit grumpy if I couldnt apply this on top of the font that makes reading easiest on my eyes and brain.
  2. Is there a reason to do the style as part of the pipeline using custom values instead of just using standard bold weighting?
  3. I'd like to see this tested with at least a couple/few sd fonts. SD fonts change the way some rendering works and I want to be sure this wont be problematic if a user is using an sd font. Ideally at least two 'normal', one mono and alll accessible (lexica, atkinson, open dyslexic). I know thats a bit of a PITA and tedious but itll ensure we have broad coverage for folk who want to use this feature.
  4. I'd like this called out as a user manual item, ideally with screen shots (maybe using the fonts used for testing? ;) ). Itd probably be best as a new document in the docs folder along side the other user manual stuff.
  5. How different is this code from crossink? If we do merge this code we remove the need for crossink to manage their own code and, ideally, wed work with @uxjulia on this PR to ensure that anything we add wont create more downstream work for them. If we do merge this feature I'd like to ensure crossink no longer has to manage things locally and can work with us going forward for maintaining the feature. I'd hate to see extra work made for crossink and us because we werent closely collaborating.
  6. Is this a simple on/off setting or is there more tunability to the feature?
  7. Do you have info on this approach to reading? I'd like to learn a bit more how folk use it to help their reading and how it may be used in terms of accessibility by others. This is mainly my own curiousity but I think it would help me better understand the purpose of the feature and how users will engage with it long term.
  1. Fair point, I do like being able to apply it to my favorite fonts.
  2. The pipeline approach keeps the EPUB untouched and the feature toggleable — pre-processing the source file to apply bold markup directly would require either storing the original or re-parsing to strip it when disabled. Additionally, standard bold is all-or-nothing at the word level, so the custom pipeline is necessary anyway to bold only the first ~X% of characters within each word rather than the whole word.
  3. I can try with some more fonts and report back (and I welcome others to report their results, too!)
  4. Happy to work on new user manual documentation.
  5. @uxjulia's "Bionic Reading" feature is derived from this one, with some added RAM optimizations, which I folded back into this PR. One slight difference is I bumped the percentage to 45% to more closely match my observations with Bionic Reading F3. Another difference is this implementation changed from always-populating the boundary vector to leaving it empty when there's no focus/bionic split data, avoiding heap allocations on blocks where focus reading doesn't apply (e.e. already-bold text, or when the feature is disabled).
  6. As-is, it is on/off. It would be simple enough to change the setting to allow for a few different bolding percentages like real Bionic Reading, but it is unclear to me how often folks tend to prefer something other than "F3". It would just replace the FOCUS_READING_PERCENT constant with a few options from an enum, and it would work.
  7. There are claims that it helps with speeding up reading by creating artificial points for a reader's eyes to snap to while reading. Others claim that it aids in focus and retention, reducing the need to re-read sections for readers with ADHD, for example. However, the benefits may be placebo, as various attempts to validate changes in reading have failed to discern an appreciable difference (for example: https://pmc.ncbi.nlm.nih.gov/articles/PMC12565662/). To be completely honest, I'm not really sure it has any measurable effect, but plenty of people seem to really enjoy it.

pablohc
pablohc previously approved these changes May 10, 2026
@mcrosson
Copy link
Copy Markdown
Contributor

  • The pipeline approach keeps the EPUB untouched and the feature toggleable — pre-processing the source file to apply bold markup directly would require either storing the original or re-parsing to strip it when disabled. Additionally, standard bold is all-or-nothing at the word level, so the custom pipeline is necessary anyway to bold only the first ~X% of characters within each word rather than the whole word.

Understood. Is this ~X% tunable. You mention below you tweaked the crossink implementation. This is fine but speaks to a 'this may be best as a tunable' since we almost immediately have 2 slightly different values in play (your and crossink).

  • I can try with some more fonts and report back (and I welcome others to report their results, too!)

Thank you :)

  • Happy to work on new user manual documentation.

Thank you :)

  • @uxjulia's "Bionic Reading" feature is derived from this one, with some added RAM optimizations, which I folded back into this PR. One slight difference is I bumped the percentage to 45% to more closely match my observations with Bionic Reading F3. Another difference is this implementation changed from always-populating the boundary vector to leaving it empty when there's no focus/bionic split data, avoiding heap allocations on blocks where focus reading doesn't apply (e.e. already-bold text, or when the feature is disabled).

Gotcha, so there are some improvements over the crossink form? Asking to ensure I read this properly.

  • As-is, it is on/off. It would be simple enough to change the setting to allow for a few different bolding percentages like real Bionic Reading, but it is unclear to me how often folks tend to prefer something other than "F3". It would just replace the FOCUS_READING_PERCENT constant with a few options from an enum, and it would work.

Given we have a variety of fonts with a variety of weights and sizes... I think it wise to have a reasonable default but allow tuning. There is just too much variability to fonts and way too much personal need/preference in this world to be able to say 'this is the way'.

Also: what exactly do you mean by 'F3' here?

  • There are claims that it helps with speeding up reading by creating artificial points for a reader's eyes to snap to while reading. Others claim that it aids in focus and retention, reducing the need to re-read sections for readers with ADHD, for example. However, the benefits may be placebo, as various attempts to validate changes in reading have failed to discern an appreciable difference (for example: https://pmc.ncbi.nlm.nih.gov/articles/PMC12565662/). To be completely honest, I'm not really sure it has any measurable effect, but plenty of people seem to really enjoy it.

Having some eyeball needs of my own... Loads of folk don't get why I'm so aggressive about applying Atkinson Hyperlegible to my text. It's so I can read easier, I really don't give a crap about others thoughts unless they know a font that I should try. ;)

I think this being an accessibility win is a good thing and we should work to make it flexible and adaptable so folk who do need/want this form of reading can adjust it to their own needs.


One other thing for @crosspoint-reader/firmware-maintainers in particular: before we proceed with a merge, I want to get @uxjulia 's input. We are going to collide with crossink and they've been an asset to our community. I'd like to ensure we dont create a mess for them.

@uxjulia
Copy link
Copy Markdown
Contributor

uxjulia commented May 10, 2026

I think the improvements that @vjapolitzer implemented are warranted. I also thought about bumping the percentage up to 45% to more closely match true bionic but decided against it (just boiled down to the fact I wasn't sure if I liked 3-char words having 2-bolded chars so I left it alone). The change in boundary vector also makes sense from a memory perspective and I'd fold in these changes into Crossink as-is.

Edit: I'm not sure we need to provide fine-tuning for bionic reading. F3 is generally noted for being the preferred middle-ground, and honestly most people probably don't even know it's tunable. I'd say as a first release, no custom tuning is fine. If there is a demand for tuning, then consider it (I have yet to have a single person ask for this). Don't need to bloat the firmware for something that may not be necessary.

image

@pablohc pablohc requested a review from lukestein May 10, 2026 15:29
@mcrosson
Copy link
Copy Markdown
Contributor

Given @uxjulia 's feedback, I'm comfortable with this being added. However I will defer 2nd review to @lukestein as they are more familiar with the feature and code.

Copy link
Copy Markdown
Contributor

@lukestein lukestein left a comment

Choose a reason for hiding this comment

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

Thank you to @vjapolitzer and many others for great work and productive conversation on this. As always, I note that I am not competent to comment on code quality. But my testing of this most recent version against current main suggests a continued great reader experience.

I've tried several different epubs (note: all English-language), fonts (built in and SD card), and other settings combos (e.g., hyphenation).

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

Alright, as requested, I have added a user guide with examples of the feature with various fonts. I am happy to see the function working as intended with the downloaded SD card fonts.

@lukestein lukestein requested a review from pablohc May 10, 2026 17:52
@pablohc pablohc merged commit 4e5e2fe into crosspoint-reader:master May 10, 2026
6 checks passed
@mcrosson
Copy link
Copy Markdown
Contributor

mcrosson commented May 10, 2026

@uxjulia / @vjapolitzer / @lukestein / @pablohc would this benefit from a 'per book option' as well? We've been slowly moving in the direction of book level overrides and this may be a candidate.

Thoughts?

Edit: if so, would you be open to an additional PR for this?

@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented May 10, 2026

Thank you very much for all your hard work on this PR @vjapolitzer

@uxjulia / @vjapolitzer / @lukestein / @pablohc would this benefit from a 'per book option' as well? We've been slowly moving in the direction of book level overrides and this may be a candidate.

Thoughts?

Edit: if so, would you be open to an additional PR for this?

I totally agree with that, @mcrosson. I think the In-Reader menu should be completely redesigned to offer more granular settings and eliminate the need to exit reading mode and go into global settings just to change a current reading setting.

@vjapolitzer
Copy link
Copy Markdown
Contributor Author

@uxjulia / @vjapolitzer / @lukestein / @pablohc would this benefit from a 'per book option' as well? We've been slowly moving in the direction of book level overrides and this may be a candidate.

Thoughts?

Edit: if so, would you be open to an additional PR for this?

Yes, I agree with @pablohc. If we move to non-global, per-book font settings, this setting should also follow.

@uxjulia
Copy link
Copy Markdown
Contributor

uxjulia commented May 10, 2026

@uxjulia / @vjapolitzer / @lukestein / @pablohc would this benefit from a 'per book option' as well? We've been slowly moving in the direction of book level overrides and this may be a candidate.

Thoughts?

Edit: if so, would you be open to an additional PR for this?

As @pablohc mentioned, adding quick access to all reader options (not just bionic reading) within a book would be a huge win.

@mcrosson
Copy link
Copy Markdown
Contributor

While I agree 'all' is the better option, if we can start small that will hopefully help nudge others and we can pacman our way through it instead of trying to get a huge PR merged.

@pablohc
Copy link
Copy Markdown
Contributor

pablohc commented May 10, 2026

While I agree 'all' is the better option, if we can start small that will hopefully help nudge others and we can pacman our way through it instead of trying to get a huge PR merged.

I understand your point, @mcrosson, and maybe we should start a new thread to avoid spamming this PR.
In my opinion, I suggest a complete redesign of the menu in Reader, so that all elements are consistent. Otherwise, we’ll end up back with a Frankenstein-style menu because everyone adds a shortcut they find useful, and we end up with a “Go Home” option that requires five clicks or more to do something that could be done better with just pressing [back] button.

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.

9 participants