Skip to content

feat: slim footnotes support#988

Closed
Uri-Tauber wants to merge 6 commits intocrosspoint-reader:masterfrom
Uri-Tauber:feat/slim-footnotes-support
Closed

feat: slim footnotes support#988
Uri-Tauber wants to merge 6 commits intocrosspoint-reader:masterfrom
Uri-Tauber:feat/slim-footnotes-support

Conversation

@Uri-Tauber
Copy link
Contributor

Summary

What is the goal of this PR? Implement support for footnotes in epub files.
It is based on #553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub.

What changes are included?
- FootnoteEntry struct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI.
- Parser: <a href> detection (ChapterHtmlSlimParser) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1).
- Footnote-to-page assignment (ChapterHtmlSlimParser, Page) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (Page, Section) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild.
- Href → spine resolution (Epub) — resolveHrefToSpineIndex() maps an href (e.g. chapter2.xhtml#note1) to its spine index by filename matching.
- Footnotes menu + activity (EpubReaderMenuActivity, EpubReaderFootnotesActivity) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate.
- Navigate & restore (EpubReaderActivity) — navigateToHref() saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote.

Additional Context

What was removed vs #553: virtual spine items (addVirtualSpineItem, isVirtualSpineItem), two-pass parsing, <aside> content extraction to temp HTML files, <p class="note"> paragraph note extraction, replaceHtmlEntities (master already has lookupHtmlEntity), footnotePages / buildFilteredChapterList, noterefCallback / Noteref struct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task).

Performance: Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in currentPageFootnotes). Flash usage is unchanged at 97.4%; RAM stays at 31%.

Known limitations: When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon).


AI Usage

Did you use AI tools to help write this code? < PARTIALLY>
Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Adds footnote support across the EPUB stack: new FootnoteEntry type, per-page footnote collection and I/O, HTML parser detection/collection, reader UI for listing/navigating footnotes, href→spine resolution, menu/localization entries, and navigation state save/restore for footnote jumps.

Changes

Cohort / File(s) Summary
Core types
lib/Epub/Epub/FootnoteEntry.h
New FootnoteEntry struct with fixed-size number and href and default ctor.
Page model & I/O
lib/Epub/Epub/Page.h, lib/Epub/Epub/Page.cpp
Adds std::vector<FootnoteEntry> footnotes, addFootnote() (cap 16), and serialization/deserialization of footnotes with validation and error checks.
EPUB API & version
lib/Epub/Epub.h, lib/Epub/Epub.cpp, lib/Epub/Epub/Section.cpp
Adds int Epub::resolveHrefToSpineIndex(const std::string&) const (exact href then filename-only match). Bumps SECTION_FILE_VERSION 13→14.
HTML parsing / footnote extraction
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h, lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
Detects internal <a href> links, tracks inside-footnote-link state, collects display text/href into pending footnotes, transfers pending notes to pages based on word indices with fallback, and manages inline link styling.
Text API
lib/Epub/Epub/blocks/TextBlock.h
Added size_t wordCount() const.
Reader activity & navigation
src/activities/reader/EpubReaderActivity.h, src/activities/reader/EpubReaderActivity.cpp
Adds navigateToHref(const char*, bool) and restoreSavedPosition(), savedPositions stack, footnoteDepth, collects current page footnotes, and adjusts back-button behavior to restore saved positions when viewing footnotes.
Footnotes viewer activity
src/activities/reader/EpubReaderFootnotesActivity.h, src/activities/reader/EpubReaderFootnotesActivity.cpp
New activity to list, scroll, select footnotes and invoke callbacks to navigate or go back.
Menu & i18n
src/activities/reader/EpubReaderMenuActivity.h, lib/I18n/I18nKeys.h, lib/I18n/translations/english.yaml
Adds FOOTNOTES menu action; new string IDs STR_FOOTNOTES, STR_NO_FOOTNOTES, STR_LINK and English entries ("Footnotes", "No footnotes on this page", "[link]").

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Parser as ChapterHtmlSlimParser
    participant Page
    participant Reader as EpubReaderActivity
    participant FootnotesView as EpubReaderFootnotesActivity

    User->>Parser: Provide chapter HTML
    activate Parser
    Parser->>Parser: Detect internal <a href> and collect display text & href
    Parser->>Page: Transfer pending footnotes when word threshold met
    deactivate Parser

    User->>Reader: Open menu -> Select FOOTNOTES
    Reader->>Page: Read current page footnotes
    Reader->>FootnotesView: Launch with footnote list
    activate FootnotesView
    FootnotesView->>User: Render list / handle selection
    User->>FootnotesView: Select footnote
    FootnotesView->>Reader: onSelectFootnote(href)
    deactivate FootnotesView

    activate Reader
    Reader->>Reader: save current position
    Reader->>Reader: resolveHrefToSpineIndex(href)
    Reader->>Reader: navigateToHref -> load target section/page
    Reader->>User: Display target page
    deactivate Reader

    User->>Reader: Press BACK
    activate Reader
    Reader->>Reader: restoreSavedPosition()
    Reader->>User: Return to original spine/page
    deactivate Reader
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature addition: implementing basic footnote support for EPUB files, which aligns directly with the substantial changes across parser, page storage, UI, and navigation components.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the goal (footnote support), lists all major components affected (FootnoteEntry struct, parser enhancements, page serialization, href resolution, UI menu and activity, navigation), provides context on what was removed from #553, and discusses performance implications.

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


Comment @coderabbitai help to get the list of available commands and usage tips.

@Uri-Tauber
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 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
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: 3

🧹 Nitpick comments (1)
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp (1)

325-367: Consider implementing the data- href extraction TODO.*

Right now javascript:void(0) links are dropped, so EPUBs that store hrefs in data attributes will be missed. Want me to sketch a parser for the common data-* patterns?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 840e8c3 and b866ea1.

📒 Files selected for processing (16)
  • lib/Epub/Epub.cpp
  • lib/Epub/Epub.h
  • lib/Epub/Epub/FootnoteEntry.h
  • lib/Epub/Epub/Page.cpp
  • lib/Epub/Epub/Page.h
  • lib/Epub/Epub/Section.cpp
  • lib/Epub/Epub/blocks/TextBlock.h
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
  • lib/I18n/I18nKeys.h
  • lib/I18n/translations/english.yaml
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.h
  • src/activities/reader/EpubReaderMenuActivity.h
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().

Applied to files:

  • src/activities/reader/EpubReaderMenuActivity.h
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
📚 Learning: 2026-02-18T15:43:07.515Z
Learnt from: GavinHigham
Repo: crosspoint-reader/crosspoint-reader PR: 966
File: src/activities/ActivityWithSubactivity.h:20-21
Timestamp: 2026-02-18T15:43:07.515Z
Learning: In the crosspoint-reader activity architecture, when using ActivityWithSubactivity, the currentActivity global variable remains at the top-level activity (e.g., ReaderActivity) even when nested subactivities are active. The supportsOrientation() delegation chain starts from the top-level activity and stops when it reaches a subactivity that overrides the method (e.g., EpubReaderActivity returns true). Deeper nested subactivities (like EpubReaderChapterSelectionActivity or EpubReaderPercentSelectionActivity) do not need to override supportsOrientation() because they never become the currentActivity and the delegation chain doesn't reach them.

Applied to files:

  • src/activities/reader/EpubReaderMenuActivity.h
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
📚 Learning: 2026-02-12T06:57:35.955Z
Learnt from: CaptainFrito
Repo: crosspoint-reader/crosspoint-reader PR: 725
File: src/activities/network/CalibreConnectActivity.cpp:218-264
Timestamp: 2026-02-12T06:57:35.955Z
Learning: In src/activities/network/CalibreConnectActivity.cpp, button hints (« Exit) are intentionally omitted from the SERVER_STARTING and ERROR states—only the SERVER_RUNNING state displays navigation hints. This is a deliberate design decision by the author.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
🧬 Code graph analysis (6)
src/activities/reader/EpubReaderFootnotesActivity.cpp (2)
src/activities/reader/EpubReaderActivity.cpp (4)
  • loop (121-273)
  • loop (121-121)
  • render (510-645)
  • render (510-510)
lib/Epub/Epub/Page.cpp (6)
  • render (6-8)
  • render (6-6)
  • render (28-31)
  • render (28-28)
  • render (51-55)
  • render (51-51)
lib/Epub/Epub.h (1)
lib/Epub/Epub.cpp (2)
  • resolveHrefToSpineIndex (850-875)
  • resolveHrefToSpineIndex (850-850)
src/activities/reader/EpubReaderFootnotesActivity.h (2)
src/activities/ActivityWithSubactivity.h (1)
  • ActivityWithSubactivity (6-21)
src/activities/reader/EpubReaderFootnotesActivity.cpp (8)
  • onEnter (12-16)
  • onEnter (12-12)
  • onExit (18-18)
  • onExit (18-18)
  • loop (20-51)
  • loop (20-20)
  • render (53-95)
  • render (53-53)
src/activities/reader/EpubReaderActivity.cpp (3)
src/main.cpp (4)
  • exitActivity (136-142)
  • exitActivity (136-136)
  • enterNewActivity (144-147)
  • enterNewActivity (144-144)
src/activities/ActivityWithSubactivity.cpp (6)
  • exitActivity (20-27)
  • exitActivity (20-20)
  • enterNewActivity (29-34)
  • enterNewActivity (29-29)
  • requestUpdate (42-47)
  • requestUpdate (42-42)
src/activities/Activity.cpp (2)
  • requestUpdate (42-48)
  • requestUpdate (42-42)
lib/Epub/Epub/Page.cpp (1)
lib/Serialization/Serialization.h (1)
  • writePod (8-10)
src/activities/reader/EpubReaderActivity.h (1)
src/activities/reader/EpubReaderActivity.cpp (4)
  • navigateToHref (816-856)
  • navigateToHref (816-816)
  • restoreSavedPosition (858-873)
  • restoreSavedPosition (858-858)
🔇 Additional comments (24)
lib/Epub/Epub/Section.cpp (1)

12-12: LGTM!

Version bump to 14 correctly invalidates existing section caches, forcing re-parsing with the new footnote serialization format.

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

5-13: LGTM!

Fixed-size buffers are appropriate for this embedded context, avoiding heap allocations in the hot path. The buffer sizes (24 for number, 64 for href) align with the parser's internal buffers.

lib/Epub/Epub/blocks/TextBlock.h (1)

31-31: LGTM!

Clean accessor method enabling word count tracking for footnote page assignment.

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

19-19: LGTM!

Translation entry correctly added and positioned to match the enum ordering in I18nKeys.h.

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

28-46: LGTM!

Well-structured footnote navigation state. The save/restore pattern using -1 sentinels for savedSpineIndex/savedPageNumber correctly handles the "no saved position" case, as confirmed by the implementation in navigateToHref() and restoreSavedPosition().

lib/Epub/Epub.h (1)

75-75: LGTM!

Clean API addition for resolving hrefs to spine indices. The implementation correctly handles anchor stripping and provides both exact and filename-only matching for robustness across varying EPUB path conventions.

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

68-75: LGTM!

Footnote link tracking state is well-designed. Fixed buffer sizes match FootnoteEntry struct (24/64 bytes), and pairing footnotes with word indices enables accurate page assignment during the single-pass parse.

lib/I18n/I18nKeys.h (1)

53-53: LGTM!

Enum value correctly positioned to match the translation file ordering.

lib/Epub/Epub/Page.cpp (1)

106-123: Footnote deserialization guards look solid.

Count validation, read checks, and explicit null-termination are all good.

src/activities/reader/EpubReaderMenuActivity.h (2)

15-15: MenuAction updated cleanly with FOOTNOTES.


42-46: Menu list integration for FOOTNOTES looks good.

lib/Epub/Epub.cpp (1)

850-874: Href-to-spine resolution logic looks consistent with intended navigation.

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

12-51: Input handling and selection bounds are clean.

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

190-195: Back-press handling for footnote restore is well-contained.


386-402: Footnotes menu action wiring looks correct.


636-638: Capturing page footnotes during render looks good.


816-873: navigateToHref/restoreSavedPosition flow is clear and safe.

src/activities/reader/EpubReaderFootnotesActivity.h (1)

12-34: Footnotes activity interface is tidy and self-contained.

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

52-68: Attribute helper and internal-link filter are straightforward.


138-139: Resetting wordsExtractedInBlock on new blocks makes sense.


516-527: Footnote label normalization logic looks good.


801-811: Pending-footnote transfer in addLineToPage is clear.


850-858: Fallback footnote transfer is a good safety net.

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

59-69: The null-pointer guard is unnecessary; number and href are char array members, not pointers.

addFootnote() is called only with FootnoteEntry struct members (it->second.number, it->second.href, and fn.number, fn.href). These are addresses of char arrays within the struct, not pointer members. Since FootnoteEntry's constructor initializes both fields as empty strings and entries are only added after safe strncpy with null-termination, null pointers cannot reach addFootnote(). The suggested guard is redundant.

Likely an incorrect or invalid review comment.

🤖 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/Page.cpp`:
- Around line 70-79: serialize() writes the full footnotes.size() which can
exceed the 16-entry limit assumed by deserialize() and addFootnote(); fix
serialize() to clamp the written count to 16 (e.g., write uint16_t count =
std::min<uint16_t>(footnotes.size(), 16) instead of fnCount) and iterate only up
to that clamped count when writing fn.number and fn.href (the loop that writes
each footnote), optionally logging a warning when footnotes.size() > 16; refer
to the footnotes member, serialize()/deserialize() methods, the fnCount
variable, and the code that writes fn.number/fn.href to locate the change.

In `@lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`:
- Around line 649-660: When closing a footnote link in the block that checks
insideFootnoteLink and footnoteLinkDepth, include the cumulative
wordsExtractedInBlock offset when computing wordIndex so the pendingFootnotes
entry uses a global word index; currently wordIndex only uses
currentTextBlock->size() which is relative. Update the calculation that assigns
wordIndex (used when pushing into pendingFootnotes) to add wordsExtractedInBlock
to the current block size (respecting the existing null checks and casts), so
FootnoteEntry indices (entry.number/entry.href) are compared correctly against
wordsExtractedInBlock later.

In `@src/activities/reader/EpubReaderFootnotesActivity.cpp`:
- Around line 53-94: In EpubReaderFootnotesActivity::render replace the two
hardcoded UI strings with localized string IDs: add new StrId entries (e.g.
STR_NO_FOOTNOTES_ON_PAGE and STR_LINK_PLACEHOLDER) in the i18n resources and use
tr(STR_NO_FOOTNOTES_ON_PAGE) instead of "No footnotes on this page" and
tr(STR_LINK_PLACEHOLDER) instead of "[link]" where label is set; keep the
existing logic for empty label fallback and ensure any tests/resources are
updated to include the new StrId keys.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements basic footnote support for EPUB files, allowing users to navigate from footnote links within the text to their corresponding content and back. The implementation is intentionally simplified compared to PR #553, removing complex features like virtual spine items, two-pass parsing, and extracted footnote content in favor of a single-pass approach with fixed-size buffers.

Changes:

  • Added FootnoteEntry struct with fixed-size buffers (24 bytes for display text, 64 bytes for href) to track footnote links
  • Enhanced HTML parser to detect internal <a href> links, collect them as footnotes, and assign them to pages based on word-count tracking
  • Added footnotes menu UI (EpubReaderFootnotesActivity) accessible from the reader menu to display and navigate footnotes on the current page
  • Implemented navigation state management to save/restore reading position when jumping to footnotes via the Back button
  • Updated Page serialization to include footnotes and bumped section cache version to 14

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
lib/Epub/Epub/FootnoteEntry.h New POD struct defining fixed-size footnote entry (number[24], href[64])
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h Added state tracking for footnote link parsing and word counting
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp Implemented link detection, text normalization, and page assignment logic in single-pass parser
lib/Epub/Epub/blocks/TextBlock.h Added wordCount() accessor for word-based footnote tracking
lib/Epub/Epub/Page.h Added footnotes vector and addFootnote() method with 16-entry limit
lib/Epub/Epub/Page.cpp Implemented footnote serialization/deserialization with validation
lib/Epub/Epub/Section.cpp Bumped SECTION_FILE_VERSION to 14 to invalidate old cache
lib/Epub/Epub.h Added resolveHrefToSpineIndex() method declaration
lib/Epub/Epub.cpp Implemented href-to-spine resolution with filename matching fallback
src/activities/reader/EpubReaderMenuActivity.h Added FOOTNOTES menu action to reader menu
src/activities/reader/EpubReaderFootnotesActivity.h New activity header for footnotes list UI
src/activities/reader/EpubReaderFootnotesActivity.cpp Implemented scrollable footnote list with selection handling
src/activities/reader/EpubReaderActivity.h Added footnote state tracking (saved position, viewing flag)
src/activities/reader/EpubReaderActivity.cpp Implemented footnote navigation, position save/restore, and Back button integration
lib/I18n/translations/english.yaml Added STR_FOOTNOTES, STR_NO_FOOTNOTES, STR_LINK strings
lib/I18n/I18nKeys.h Added corresponding enum entries for new strings

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Deserialize footnotes
uint16_t fnCount;
serialization::readPod(file, fnCount);
if (fnCount > 16) {
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

The constant 16 is used in three places (addFootnote limit check, serialize clamp, deserialize validation) to cap the number of footnotes per page. Consider defining a named constant like 'MAX_FOOTNOTES_PER_PAGE' in FootnoteEntry.h or as a static constexpr in the Page class to avoid magic numbers and ensure consistency if this limit needs to be changed in the future.

Copilot uses AI. Check for mistakes.
osteotek
osteotek previously approved these changes Feb 19, 2026
@osteotek
Copy link
Member

osteotek commented Feb 19, 2026

Works in my testing. And code looks good to me

Copy link
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.

🧹 Nitpick comments (1)
src/activities/reader/EpubReaderActivity.cpp (1)

386-402: Minor: Redundant requestUpdate() call.

The navigation callback calls navigateToHref(href, true) which already invokes requestUpdate() at the end of its implementation (line 851). The subsequent call on line 399 is redundant. While harmless due to eIncrement semantics, it could be removed for clarity.

♻️ Optional cleanup
       [this](const char* href) {
         // Navigate to selected footnote
         navigateToHref(href, true);
         exitActivity();
-        requestUpdate();
       }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/reader/EpubReaderActivity.cpp` around lines 386 - 402, In the
EpubReaderMenuActivity::MenuAction::FOOTNOTES case remove the redundant
requestUpdate() inside the footnote navigation lambda because
navigateToHref(href, true) already calls requestUpdate(); locate the lambda
passed to the EpubReaderFootnotesActivity constructor (the [this](const char*
href) callback), delete the trailing requestUpdate() call while keeping the call
to navigateToHref(href, true) and the exitActivity() invocation to preserve
flow.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b866ea1 and 6a96afd.

📒 Files selected for processing (7)
  • lib/Epub/Epub/Page.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/I18n/I18nKeys.h
  • lib/I18n/translations/english.yaml
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
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:00.962Z
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.
📚 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/english.yaml
  • lib/I18n/I18nKeys.h
📚 Learning: 2026-02-19T12:17:00.962Z
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:00.962Z
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
  • src/activities/reader/EpubReaderActivity.h
  • lib/Epub/Epub/Page.cpp
  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().

Applied to files:

  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-18T15:43:07.515Z
Learnt from: GavinHigham
Repo: crosspoint-reader/crosspoint-reader PR: 966
File: src/activities/ActivityWithSubactivity.h:20-21
Timestamp: 2026-02-18T15:43:07.515Z
Learning: In the crosspoint-reader activity architecture, when using ActivityWithSubactivity, the currentActivity global variable remains at the top-level activity (e.g., ReaderActivity) even when nested subactivities are active. The supportsOrientation() delegation chain starts from the top-level activity and stops when it reaches a subactivity that overrides the method (e.g., EpubReaderActivity returns true). Deeper nested subactivities (like EpubReaderChapterSelectionActivity or EpubReaderPercentSelectionActivity) do not need to override supportsOrientation() because they never become the currentActivity and the delegation chain doesn't reach them.

Applied to files:

  • src/activities/reader/EpubReaderActivity.h
  • 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-02-12T06:57:35.955Z
Learnt from: CaptainFrito
Repo: crosspoint-reader/crosspoint-reader PR: 725
File: src/activities/network/CalibreConnectActivity.cpp:218-264
Timestamp: 2026-02-12T06:57:35.955Z
Learning: In src/activities/network/CalibreConnectActivity.cpp, button hints (« Exit) are intentionally omitted from the SERVER_STARTING and ERROR states—only the SERVER_RUNNING state displays navigation hints. This is a deliberate design decision by the author.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
🧬 Code graph analysis (3)
src/activities/reader/EpubReaderActivity.h (1)
src/activities/reader/EpubReaderActivity.cpp (4)
  • navigateToHref (816-853)
  • navigateToHref (816-816)
  • restoreSavedPosition (855-868)
  • restoreSavedPosition (855-855)
lib/Epub/Epub/Page.cpp (2)
lib/Serialization/Serialization.h (1)
  • writePod (8-10)
src/CrossPointSettings.cpp (2)
  • file (94-100)
  • file (94-94)
src/activities/reader/EpubReaderActivity.cpp (3)
src/main.cpp (4)
  • exitActivity (136-142)
  • exitActivity (136-136)
  • enterNewActivity (144-147)
  • enterNewActivity (144-144)
src/activities/ActivityWithSubactivity.cpp (6)
  • exitActivity (20-27)
  • exitActivity (20-20)
  • enterNewActivity (29-34)
  • enterNewActivity (29-29)
  • requestUpdate (42-47)
  • requestUpdate (42-42)
src/activities/Activity.cpp (2)
  • requestUpdate (42-48)
  • requestUpdate (42-42)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (18)
src/activities/reader/EpubReaderActivity.h (2)

28-36: LGTM!

Well-designed footnote navigation state with bounded memory usage. The fixed-size array with MAX_FOOTNOTE_DEPTH = 3 is appropriate for embedded constraints and prevents unbounded stack growth during nested footnote navigation.


48-50: LGTM!

Clean method declarations with a sensible default for savePosition.

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

190-198: LGTM!

Clean integration of footnote position restoration into the back button flow. The guard footnoteDepth > 0 correctly distinguishes between normal back navigation and returning from footnote jumps.


635-638: LGTM!

Properly captures footnotes from the temporary page object via copy assignment, ensuring availability for the footnotes UI.


816-853: LGTM!

Solid implementation with proper error handling:

  • Null guards on entry
  • Bounded position stack with MAX_FOOTNOTE_DEPTH check
  • Correct undo of push on resolution failure
  • RenderLock properly protects state mutations
  • String copy ensures href survives any subsequent memory changes

855-868: LGTM!

Clean stack-pop implementation with proper boundary check and lock-protected state restoration.

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

52-58: LGTM!

Clean helper to retrieve XML attribute values. Null checks are properly handled.


60-68: LGTM!

The protocol exclusion list covers the common external link types. Internal EPUB links (relative paths, fragment-only #anchor) will correctly return true.


325-367: LGTM!

Internal link detection and footnote tracking setup is well-structured. The buffer flush before style change ensures correct styling, and the underline styling via both depth tracking and style stack is consistent with other inline elements.


516-527: LGTM!

The label normalization correctly strips brackets and whitespace. Buffer overflow is prevented with bounds checking, and null-termination is maintained after each character addition.


649-662: LGTM!

The wordIndex calculation correctly uses the cumulative approach (wordsExtractedInBlock + currentTextBlock->size()), ensuring footnotes attach to the correct page even after mid-paragraph flushes. Based on learnings: "the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary."


138-138: LGTM!

Correctly resets the cumulative word counter when starting a new text block/paragraph.


802-812: LGTM!

The footnote-to-page assignment correctly uses cumulative word tracking. The iterator-based erase pattern handles invalidation properly.


851-859: The codebase explicitly enables C++20 support (-std=gnu++2a in platformio.ini, line 31) and removes the default C++11 flag. The structured binding syntax at line 855 is fully compatible with C++20 and requires no changes.

lib/I18n/I18nKeys.h (1)

354-356: LGTM!

New i18n keys for footnotes feature are correctly positioned before the _COUNT sentinel. Based on learnings, missing translations in non-English YAML files will be auto-filled with English strings at build time by gen_i18n.py.

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

70-80: LGTM!

Serialization correctly clamps footnote count to 16 and uses indexed iteration to match. This prevents serialization/deserialization mismatch if footnotes is modified directly.


107-124: LGTM!

Deserialization validates the footnote count before reading and defensively null-terminates strings. The count validation ensures compatibility with the serialization limit.

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

320-322: LGTM!

Clear and appropriate translation strings for the footnotes feature. The order matches the enum in I18nKeys.h. Based on learnings, missing translations in other language files will be auto-filled with these English strings at build time.

🤖 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/activities/reader/EpubReaderActivity.cpp`:
- Around line 386-402: In the EpubReaderMenuActivity::MenuAction::FOOTNOTES case
remove the redundant requestUpdate() inside the footnote navigation lambda
because navigateToHref(href, true) already calls requestUpdate(); locate the
lambda passed to the EpubReaderFootnotesActivity constructor (the [this](const
char* href) callback), delete the trailing requestUpdate() call while keeping
the call to navigateToHref(href, true) and the exitActivity() invocation to
preserve flow.

@Uri-Tauber
Copy link
Contributor Author

Added a nested positions tracking (Array of 3 SavedPosition structs).
Each navigateToHref pushes, each Back button pops. At depth 3, further navigation still works but without saving. Efficient and simple.

  ┌──────────────────┬───────────────────┬───────┬─────────────────────┐
  │ Action           │ Stack [0] [1] [2] │ depth │ User sees           │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Start reading    │ —                 │ 0     │ Original position O │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Go to footnote 1 │ O _ _             │ 1     │ F1                  │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Go to footnote 2 │ O F1 _            │ 2     │ F2                  │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Go to footnote 3 │ O F1 F2           │ 3     │ F3                  │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Go to footnote 4 │ O F1 F2 (no push) │ 3     │ F4                  │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Back             │ O F1 _            │ 2     │ F2 ← skipped F3     │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Back             │ O _ _             │ 1     │ F1                  │
  ├──────────────────┼───────────────────┼───────┼─────────────────────┤
  │ Back             │ _ _ _             │ 0     │ O ✓                 │
  └──────────────────┴───────────────────┴───────┴─────────────────────┘

@Uri-Tauber Uri-Tauber force-pushed the feat/slim-footnotes-support branch from 6a96afd to e3fd6b4 Compare February 19, 2026 21:53
Copy link
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.

🧹 Nitpick comments (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)

12-16: Consider resetting scrollOffset in onEnter.

selectedIndex is reset to 0, but scrollOffset is not explicitly reset. If the activity is re-entered (e.g., user opens footnotes, navigates, returns, opens footnotes again), a stale scrollOffset could cause the list to render from an unexpected position.

Proposed fix
 void EpubReaderFootnotesActivity::onEnter() {
   ActivityWithSubactivity::onEnter();
   selectedIndex = 0;
+  scrollOffset = 0;
   requestUpdate();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/reader/EpubReaderFootnotesActivity.cpp` around lines 12 - 16,
EpubReaderFootnotesActivity::onEnter resets selectedIndex but not scrollOffset,
so re-entering can leave a stale scroll position; update onEnter (alongside
ActivityWithSubactivity::onEnter) to explicitly reset scrollOffset (e.g., set
scrollOffset = 0 or its default) before calling requestUpdate so the footnotes
list always starts at the top when the activity is entered.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a96afd and e3fd6b4.

📒 Files selected for processing (5)
  • lib/I18n/I18nKeys.h
  • lib/I18n/translations/english.yaml
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/I18n/translations/english.yaml
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
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:00.962Z
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.
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().

Applied to files:

  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-18T15:43:07.515Z
Learnt from: GavinHigham
Repo: crosspoint-reader/crosspoint-reader PR: 966
File: src/activities/ActivityWithSubactivity.h:20-21
Timestamp: 2026-02-18T15:43:07.515Z
Learning: In the crosspoint-reader activity architecture, when using ActivityWithSubactivity, the currentActivity global variable remains at the top-level activity (e.g., ReaderActivity) even when nested subactivities are active. The supportsOrientation() delegation chain starts from the top-level activity and stops when it reaches a subactivity that overrides the method (e.g., EpubReaderActivity returns true). Deeper nested subactivities (like EpubReaderChapterSelectionActivity or EpubReaderPercentSelectionActivity) do not need to override supportsOrientation() because they never become the currentActivity and the delegation chain doesn't reach them.

Applied to files:

  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-19T12:17:00.962Z
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:00.962Z
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:

  • src/activities/reader/EpubReaderActivity.h
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 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/I18nKeys.h
📚 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-02-12T06:57:35.955Z
Learnt from: CaptainFrito
Repo: crosspoint-reader/crosspoint-reader PR: 725
File: src/activities/network/CalibreConnectActivity.cpp:218-264
Timestamp: 2026-02-12T06:57:35.955Z
Learning: In src/activities/network/CalibreConnectActivity.cpp, button hints (« Exit) are intentionally omitted from the SERVER_STARTING and ERROR states—only the SERVER_RUNNING state displays navigation hints. This is a deliberate design decision by the author.

Applied to files:

  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/EpubReaderFootnotesActivity.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 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/activities/reader/EpubReaderFootnotesActivity.cpp
🧬 Code graph analysis (2)
src/activities/reader/EpubReaderActivity.h (1)
src/activities/reader/EpubReaderActivity.cpp (4)
  • navigateToHref (816-853)
  • navigateToHref (816-816)
  • restoreSavedPosition (855-868)
  • restoreSavedPosition (855-855)
src/activities/reader/EpubReaderActivity.cpp (3)
src/main.cpp (4)
  • exitActivity (136-142)
  • exitActivity (136-136)
  • enterNewActivity (144-147)
  • enterNewActivity (144-144)
src/activities/ActivityWithSubactivity.cpp (6)
  • exitActivity (20-27)
  • exitActivity (20-20)
  • enterNewActivity (29-34)
  • enterNewActivity (29-29)
  • requestUpdate (42-47)
  • requestUpdate (42-42)
src/activities/Activity.cpp (2)
  • requestUpdate (42-48)
  • requestUpdate (42-42)
🔇 Additional comments (8)
lib/I18n/I18nKeys.h (1)

353-356: LGTM — new StrId entries look consistent with the existing enum pattern.

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

28-50: LGTM! Clean footnote navigation scaffolding.

The state management design is sound:

  • Fixed-size savedPositions[3] avoids heap allocation on the hot path per PR objectives
  • SavedPosition POD struct is appropriately minimal
  • Default parameter savePosition = false for navigateToHref allows flexible usage
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)

53-95: LGTM! Render logic is solid.

  • Visible count calculation safely handles edge cases with std::max(1, ...)
  • Scroll offset adjustment keeps selection in view
  • Localization properly uses tr() with string IDs
src/activities/reader/EpubReaderActivity.cpp (5)

190-198: LGTM! Back button logic correctly prioritizes footnote restoration.

The conditional check for footnoteDepth > 0 before onGoHome() ensures users can navigate back through footnote history before exiting the reader.


386-402: Verify currentPageFootnotes reference lifetime.

EpubReaderFootnotesActivity receives currentPageFootnotes by const reference. Since currentPageFootnotes is a member of EpubReaderActivity and the parent activity remains alive while the subactivity runs, this should be safe. However, if render() were called and updated currentPageFootnotes while the footnotes activity is open, it could cause inconsistency.

Given that the subactivity blocks render updates (per ActivityWithSubactivity::requestUpdate() behavior), this appears safe.


635-638: LGTM! Footnote collection is straightforward.

Copying footnotes from the loaded page ensures currentPageFootnotes always reflects the currently displayed page.


816-853: LGTM! Navigation logic handles edge cases well.

Key observations:

  • Null checks for epub and href prevent crashes
  • Position only saved when section exists (valid state to restore)
  • Failed resolution correctly undoes the stack push
  • RenderLock properly protects state mutations

855-868: LGTM! Position restoration is correctly implemented.

The decrement-before-access pattern (footnoteDepth-- then savedPositions[footnoteDepth]) correctly maps depth to array index.

🤖 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/activities/reader/EpubReaderFootnotesActivity.cpp`:
- Around line 12-16: EpubReaderFootnotesActivity::onEnter resets selectedIndex
but not scrollOffset, so re-entering can leave a stale scroll position; update
onEnter (alongside ActivityWithSubactivity::onEnter) to explicitly reset
scrollOffset (e.g., set scrollOffset = 0 or its default) before calling
requestUpdate so the footnotes list always starts at the top when the activity
is entered.

@Uri-Tauber Uri-Tauber requested a review from osteotek February 19, 2026 22:06
@Uri-Tauber Uri-Tauber closed this by deleting the head repository Feb 20, 2026
@osteotek
Copy link
Member

@Uri-Tauber will you bring back the PR?

@Uri-Tauber
Copy link
Contributor Author

I'm sorry. I accidently deleted my fork of crosspoint.
I wasn't able to figure out how to reopen the PR, so I just forked again and created new PR #1031.

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.

3 participants