Skip to content

fix: Fix kosync repositioning issue#783

Merged
daveallie merged 7 commits intocrosspoint-reader:masterfrom
jpirnay:fix-kosync
Feb 19, 2026
Merged

fix: Fix kosync repositioning issue#783
daveallie merged 7 commits intocrosspoint-reader:masterfrom
jpirnay:fix-kosync

Conversation

@jpirnay
Copy link
Contributor

@jpirnay jpirnay commented Feb 9, 2026

Summary

  • Original implementation had inconsistent positioning logic:
    • When XPath parsing succeeded: incorrectly set pageNumber = 0 (always beginning of chapter)
    • When XPath parsing failed: used percentage for positioning (worked correctly)
    • Result: Positions restored to wrong locations depending on XPath parsing success
    • Mentioned in Issue KOReader Sync Not Properly #581
  • Solution
    • Unified ProgressMapper::toCrossPoint() to use percentage-based positioning exclusively for both spine identification and intra-chapter page calculation, eliminating unreliable XPath parsing entirely.

Additional Context

  • ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage for positioning, removed parseDocFragmentIndex() function
  • ProgressMapper.h: Updated comments and removed unused function declaration
  • Tests confirmed appropriate positioning
  • Notabene: the syncing to another device will (most probably) end up at the current chapter of crosspoints reading position. There is not much we can do about it, as KOReader needs to have the correct XPath information - we can only provide an apporximate position (plus percentage) - the percentage information is not used in KOReaders current implementation

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

@osteotek
Copy link
Member

osteotek commented Feb 9, 2026

Seems like it was built on top of overlay PR branch, better to separate them

@jpirnay
Copy link
Contributor Author

jpirnay commented Feb 9, 2026

Rebased

@iandchasse
Copy link
Contributor

How well does this work? I was doing some light testing in #61 on the KOReader side of things by replacing its own sync function with a percentage based look-up. If you do this, then the syncing can technically be two-way preserved.

But, I think KOReader bases its % on ‘page index / total pages’, not book size as crosspoint does. So text based books should probably be pretty close in terms of syncing, but books with a lot of images might be a bit off.

I don’t own an X4, so I could be very wrong.

@jpirnay
Copy link
Contributor Author

jpirnay commented Feb 9, 2026

Caveat; I dont use a loot of books containing images, so YMMV
The relative position within a file will probably be different if the reader would include graphics as the rendered images will consume space.
For my use cases, it was close enough

@osteotek
Copy link
Member

osteotek commented Feb 9, 2026

But, I think KOReader bases its % on ‘page index / total pages’, not book size as crosspoint does. So text based books should probably be pretty close in terms of syncing, but books with a lot of images might be a bit off.

GetCumulativeSize function that percentage calculation relies on uses uncompressed sizes of each spine item file (the XHTML/HTML/other spine href targets) and accumulates those sizes; it does not include images referenced inside those files. So it should work the same for image-heavy books as for non-image books

@iandchasse
Copy link
Contributor

@osteotek Do you happen to know if there are methods already implemented to achieve a ‘current page / total pages’ calculation instead of using the size? Or does the file have to be fully indexed before that can be done? I don't think that should replace all references to % as already done, but in this specific domain of KOSync, it's probably valuable to try and match up so that it'll work regardless of the file being synced.

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 fixes a KOReader synchronization bug where reading positions were incorrectly restored depending on whether XPath parsing succeeded or failed. The fix unifies the positioning logic to use percentage-based calculations exclusively, removing the unreliable XPath parsing approach that caused positions to jump to chapter beginnings.

Changes:

  • Removed XPath-based positioning logic that incorrectly set pageNumber to 0
  • Unified toCrossPoint() to use percentage-based positioning for both spine identification and page calculation
  • Added intelligent default selection in sync UI to prefer the furthest progress between local and remote

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
lib/KOReaderSync/ProgressMapper.cpp Removed parseDocFragmentIndex() function and refactored toCrossPoint() to use percentage-based positioning exclusively with density estimation for page calculations
lib/KOReaderSync/ProgressMapper.h Updated function signature to accept currentSpineIndex and totalPagesInCurrentSpine parameters; updated comments to reflect percentage-based approach
src/activities/reader/KOReaderSyncActivity.cpp Updated toCrossPoint() call with new parameters; added logic to default to furthest progress option

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

} else {
selectedOption = 0; // Apply remote progress
}

Copy link
Member

Choose a reason for hiding this comment

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

nice touch

@osteotek
Copy link
Member

osteotek commented Feb 10, 2026

@jpirnay I’ve tested this, and it appears to work better than before. At least, it doesn’t always sync to the beginning of the chapter on the Crosspoint side. However, syncing from crosspoint to the remote server and then fetching progress on another Koreader always puts me at the beginning of the next chapter (not the same one I’ve been reading on Crosspoint). Is this the same for you?

@jpirnay
Copy link
Contributor Author

jpirnay commented Feb 11, 2026

@jpirnay I’ve tested this, and it appears to work better than before. At least, it doesn’t always sync to the beginning of the chapter on the Crosspoint side. However, syncing from crosspoint to the remote server and then fetching progress on another Koreader always puts me at the beginning of the next chapter (not the same one I’ve been reading on Crosspoint). Is this the same for you?

You are pointing our a very important issue:
The XPath being handed over is a synthetic, chapter-level XPath pointing to the DocFragment corresponding to the current spine item (chapter). It is not the XPath of the closest text fragment, as CrossPoint does not preserve detailed HTML structure during parsing—only the linear spine and page counts.

We are assuming KOReader uses the XPath to identify the DocFragment and the percentage for fine positioning within it (thats how Crosspoint does it when reading something). However, based on KOReader's syncToProgress implementation, it only uses GotoXPointer(xPath) for EPUB, ignoring percentage. This results in positioning at the start of the specified DocFragment (chapter start), not an exact location.

Reality: Sync achieves approximate, chapter-level positioning, not pixel-perfect sync. KOReader lands at the beginning of the DocFragment because the XPath lacks detailed element paths (e.g., no <p> or <div> tags). Real KOReader XPaths are detailed (e.g., /body/DocFragment/body/div/div[4]/ul/li[3]...), but CrossPoint can't generate them.

If KOReader were modified to use percentage for intra-DocFragment positioning, sync would be more accurate. As-is, this is the best CrossPoint can do. It works for round-trip CrossPoint sync, and provides chapter-level KOReader positioning.

@osteotek
Copy link
Member

@jpirnay thanks for the analysis, seems like we can't solve this without modifications to how crosspoint stores text, or without reparsing html doc on every sync. PR in its current form looks like a good addition, I've tested it, works good.

osteotek
osteotek previously approved these changes Feb 11, 2026
@osteotek osteotek requested a review from a team February 11, 2026 13:35
@jpirnay
Copy link
Contributor Author

jpirnay commented Feb 12, 2026

For the brave of heart, @iandchasse pointed out a solution for better KOReader sync behaviour, which requires the patching of a single file though: #61 (comment)

@osteotek
Copy link
Member

@jpirnay would it be possible to fix merge conflicts here?

@jpirnay
Copy link
Contributor Author

jpirnay commented Feb 18, 2026

@jpirnay would it be possible to fix merge conflicts here?

Done

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48a19bc and 12d79d2.

📒 Files selected for processing (1)
  • src/activities/reader/KOReaderSyncActivity.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 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/KOReaderSyncActivity.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/KOReaderSyncActivity.cpp
🧬 Code graph analysis (1)
src/activities/reader/KOReaderSyncActivity.cpp (1)
lib/KOReaderSync/ProgressMapper.cpp (2)
  • toCrossPoint (32-107)
  • toCrossPoint (32-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cppcheck
  • GitHub Check: build
🔇 Additional comments (2)
src/activities/reader/KOReaderSyncActivity.cpp (2)

123-126: Progress mapping call aligns with updated ProgressMapper signature.

Looks correct with the added currentSpineIndex and the current spine’s total pages being passed through.


135-141: Default option selection now tracks the furthest progress.

The comparison-based selection matches the intended behavior and keeps the UI decision consistent.


📝 Walkthrough

Walkthrough

ProgressMapper.toCrossPoint was refactored to use percentage-based mapping (no XPath/DocFragment parsing), accept (currentSpineIndex, totalPagesInCurrentSpine) instead of totalPagesInSpine, and add fallbacks/estimations for spine/page totals. KOReaderSyncActivity call sites updated and selection logic now compares local vs remote percentage; RenderLock usage unchanged.

Changes

Cohort / File(s) Summary
ProgressMapper core
lib/KOReaderSync/ProgressMapper.h, lib/KOReaderSync/ProgressMapper.cpp
Signature changed to accept currentSpineIndex and totalPagesInCurrentSpine. Replaced XPath/DocFragment-based mapping with percentage-based spine/page mapping. Removed parseDocFragmentIndex. generateXPath simplified to 0-based DocFragment indices. Added fallback mapping when target byte beyond last spine and estimation of totalPages per spine; initialized result.totalPages to 0 and populate conditionally. Logging updated to reflect new calculation path.
Activity integration
src/activities/reader/KOReaderSyncActivity.cpp
Updated call to ProgressMapper::toCrossPoint(epub, koPos, currentSpineIndex, totalPagesInSpine). Selection logic in SHOWING_RESULT now chooses option based on comparing local vs remote progress percentages (previously defaulted to Apply). RenderLock-based rendering synchronization remains in place (no new mutex introduced).

Sequence Diagram(s)

sequenceDiagram
    participant Activity as KOReaderSyncActivity
    participant Mapper as ProgressMapper
    participant Epub as Epub
    participant UI as UI/RenderLock

    Activity->>Mapper: toCrossPoint(epub, koPos, currentSpineIndex, totalPagesInCurrentSpine)
    Mapper->>Epub: read spine boundaries / byte positions
    Epub-->>Mapper: spine byte map
    Mapper-->>Activity: CrossPointPosition (spineIndex, pageNumber, totalPages)
    Activity->>Activity: compare localProgress.percentage vs remoteProgress.percentage
    Activity->>UI: acquire RenderLock / update selected option
    UI-->>Activity: release RenderLock
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 'fix: Fix kosync repositioning issue' accurately describes the main change: fixing an inconsistent positioning bug in kosync that caused incorrect reading position restoration.
Description check ✅ Passed The description is directly related to the changeset, explaining the inconsistent XPath parsing logic bug and the solution to use percentage-based positioning exclusively.

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

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

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ec5fc5 and 48a19bc.

📒 Files selected for processing (3)
  • lib/KOReaderSync/ProgressMapper.cpp
  • lib/KOReaderSync/ProgressMapper.h
  • src/activities/reader/KOReaderSyncActivity.cpp
🧰 Additional context used
🧠 Learnings (1)
📚 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/KOReaderSyncActivity.cpp
🧬 Code graph analysis (1)
src/activities/reader/KOReaderSyncActivity.cpp (1)
lib/KOReaderSync/ProgressMapper.cpp (4)
  • toCrossPoint (32-107)
  • toCrossPoint (32-33)
  • toKOReader (7-30)
  • toKOReader (7-7)
⏰ 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: cppcheck
🔇 Additional comments (3)
lib/KOReaderSync/ProgressMapper.h (1)

53-58: Signature update looks consistent.
No concerns with the new parameters or defaults.

lib/KOReaderSync/ProgressMapper.cpp (1)

32-38: Initialization change looks fine.
No issues spotted.

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

126-143: Progress-based default selection looks good.
The mutex-guarded update and comparison read clean and should pick the sensible default.

🤖 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/KOReaderSync/ProgressMapper.cpp`:
- Around line 44-99: The file uses std::min and std::max in ProgressMapper.cpp
(e.g., clampedProgress calculation and pageNumber clamping) but is missing the
<algorithm> include; add the missing `#include` <algorithm> at the top of
ProgressMapper.cpp (near other includes) so std::min/std::max resolve cleanly
for functions/methods like the progress-to-page calculation that reference
result.pageNumber and clampedProgress.
- Around line 109-113: The XPath generation in ProgressMapper::generateXPath
currently uses a 0-based spineIndex, producing invalid XPath like
/body/DocFragment[0]/body; change the string construction to use (spineIndex +
1) so the returned path is "/body/DocFragment[" + std::to_string(spineIndex + 1)
+ "]/body"; also update the inline comment to reflect that XPath uses 1-based
indices rather than claiming 0-based DocFragment indices.

---

Duplicate comments:
In `@lib/KOReaderSync/ProgressMapper.h`:
- Around line 63-65: The header comment for generateXPath is out of sync: it
states the format uses "spineIndex+1" but the implementation returns the raw
spine index; update the comment in ProgressMapper.h to reflect that
generateXPath returns the raw spineIndex (not +1) and describe the format
accordingly (e.g., "/body/DocFragment[spineIndex]/body" or explicitly state
"uses raw spineIndex"), ensuring the doc matches the generateXPath
implementation.

osteotek
osteotek previously approved these changes Feb 18, 2026
@daveallie daveallie merged commit 00e25b1 into crosspoint-reader:master Feb 19, 2026
6 checks passed
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* Original implementation had inconsistent positioning logic:
- When XPath parsing succeeded: incorrectly set pageNumber = 0 (always
beginning of chapter)
- When XPath parsing failed: used percentage for positioning (worked
correctly)
- Result: Positions restored to wrong locations depending on XPath
parsing success
  - Mentioned in Issue crosspoint-reader#581 
* Solution
- Unified ProgressMapper::toCrossPoint() to use percentage-based
positioning exclusively for both spine identification and intra-chapter
page calculation, eliminating unreliable XPath parsing entirely.

## Additional Context

* ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage
for positioning, removed parseDocFragmentIndex() function
* ProgressMapper.h: Updated comments and removed unused function
declaration
* Tests confirmed appropriate positioning
* __Notabene: the syncing to another device will (most probably) end up
at the current chapter of crosspoints reading position. There is not
much we can do about it, as KOReader needs to have the correct XPath
information - we can only provide an apporximate position (plus
percentage) - the percentage information is not used in KOReaders
current implementation__
---

### 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
@jpirnay jpirnay deleted the fix-kosync branch February 19, 2026 18:33
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* Original implementation had inconsistent positioning logic:
- When XPath parsing succeeded: incorrectly set pageNumber = 0 (always
beginning of chapter)
- When XPath parsing failed: used percentage for positioning (worked
correctly)
- Result: Positions restored to wrong locations depending on XPath
parsing success
  - Mentioned in Issue crosspoint-reader#581 
* Solution
- Unified ProgressMapper::toCrossPoint() to use percentage-based
positioning exclusively for both spine identification and intra-chapter
page calculation, eliminating unreliable XPath parsing entirely.

## Additional Context

* ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage
for positioning, removed parseDocFragmentIndex() function
* ProgressMapper.h: Updated comments and removed unused function
declaration
* Tests confirmed appropriate positioning
* __Notabene: the syncing to another device will (most probably) end up
at the current chapter of crosspoints reading position. There is not
much we can do about it, as KOReader needs to have the correct XPath
information - we can only provide an apporximate position (plus
percentage) - the percentage information is not used in KOReaders
current implementation__
---

### 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
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 20, 2026
## Summary

* Original implementation had inconsistent positioning logic:
- When XPath parsing succeeded: incorrectly set pageNumber = 0 (always
beginning of chapter)
- When XPath parsing failed: used percentage for positioning (worked
correctly)
- Result: Positions restored to wrong locations depending on XPath
parsing success
  - Mentioned in Issue crosspoint-reader#581 
* Solution
- Unified ProgressMapper::toCrossPoint() to use percentage-based
positioning exclusively for both spine identification and intra-chapter
page calculation, eliminating unreliable XPath parsing entirely.

## Additional Context

* ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage
for positioning, removed parseDocFragmentIndex() function
* ProgressMapper.h: Updated comments and removed unused function
declaration
* Tests confirmed appropriate positioning
* __Notabene: the syncing to another device will (most probably) end up
at the current chapter of crosspoints reading position. There is not
much we can do about it, as KOReader needs to have the correct XPath
information - we can only provide an apporximate position (plus
percentage) - the percentage information is not used in KOReaders
current implementation__
---

### 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
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.

5 participants