Summary
When textsplit.py splits a large chapter XHTML file into multiple pieces, the TOC/NCX anchor for that chapter ends up pointing at the last split fragment instead of the first — so the chapter's title only becomes "active" right before the next chapter starts, and every page before that shows the previous chapter's title in the reader's status bar.
Root cause
In split_epub_text / the per-spine-item split loop (crosspoint_reader/textsplit.py, around line 396-403):
for k, (name, content) in enumerate(pieces):
...
for frag in re.findall(r'id="([^"]+)"', content):
anchor_map[(posixpath.basename(href), frag)] = name
The element that originally carried the chapter's anchor id (e.g. a wrapping <div class="chapter" id="TOC-6">) gets duplicated into every resulting fragment so each piece stays self-contained/well-formed. Since this loop walks fragments k = 0, 1, 2, ... and unconditionally overwrites anchor_map on every match, the last fragment that (redundantly) contains the id always wins — sending the anchor to the tail of the chapter instead of its start.
I confirmed this by pulling the actual toc.ncx produced for an epub with several long chapters. Every split chapter showed the same pattern (last fragment wins), e.g.:
| Original file |
Split into |
TOC points to |
..._TOC-6.html (Chapter I) |
base + _ek1…_ek5 (6 pieces) |
_ek5 (last) |
..._TOC-7.html (Chapter II) |
base + _ek1…_ek4 (5 pieces) |
_ek4 (last) |
..._TOC-2.html (Introduction) |
base + _ek1…_ek2 (3 pieces) |
_ek2 (last) |
I also verified id="TOC-6" literally appears in the base file, _ek1, and _ek5 alike — confirming the duplication-across-fragments behavior that makes the last-wins bug bite every time a chapter is split.
Downstream effect on the reader
The CrossPoint/YACP firmware's spine→TOC mapping (BookMetadataCache) intentionally falls back to the previous TOC entry for any spine item that has no direct match (reasonable default behavior for legitimately un-tocced continuation files). Combined with this bug, every fragment before the mis-pointed last fragment has no TOC match of its own, so the reader's chapter-title status bar shows the previous chapter for essentially the whole chapter, correcting only right at its very end — right before the next chapter begins. This reproduces as: "the reader shows the previous chapter as current" while reading normally through a book with long, auto-split chapters.
Fix
Make the anchor assignment first-occurrence-wins instead of last-wins, since the anchor's true target is definitionally wherever the wrapping element originally opens (the first fragment):
for frag in re.findall(r'id="([^"]+)"', content):
anchor_map.setdefault((posixpath.basename(href), frag), name)
I applied this locally and regenerated the epub for the affected book; the resulting toc.ncx correctly points every chapter at its first fragment (e.g. ..._TOC-6.html#TOC-6 instead of ..._TOC-6_ek5.html#TOC-6), confirmed via a diff of before/after toc.ncx.
Repro
- Convert/optimize an EPUB containing at least one chapter whose
<body> exceeds SPLIT_LIMIT (triggers split_xhtml_doc). minimal-split-anchor-repro.zip
- Inspect the resulting
toc.ncx: the chapter's <content src="..."> points at the highest-numbered _ekN fragment rather than the base file.
- On-device (or via the firmware's status bar / chapter-title logic), the previous chapter's title is shown for nearly the whole chapter.
Summary
When
textsplit.pysplits a large chapter XHTML file into multiple pieces, the TOC/NCX anchor for that chapter ends up pointing at the last split fragment instead of the first — so the chapter's title only becomes "active" right before the next chapter starts, and every page before that shows the previous chapter's title in the reader's status bar.Root cause
In
split_epub_text/ the per-spine-item split loop (crosspoint_reader/textsplit.py, around line 396-403):The element that originally carried the chapter's anchor id (e.g. a wrapping
<div class="chapter" id="TOC-6">) gets duplicated into every resulting fragment so each piece stays self-contained/well-formed. Since this loop walks fragmentsk = 0, 1, 2, ...and unconditionally overwritesanchor_mapon every match, the last fragment that (redundantly) contains the id always wins — sending the anchor to the tail of the chapter instead of its start.I confirmed this by pulling the actual
toc.ncxproduced for an epub with several long chapters. Every split chapter showed the same pattern (last fragment wins), e.g.:..._TOC-6.html(Chapter I)_ek1…_ek5(6 pieces)_ek5(last)..._TOC-7.html(Chapter II)_ek1…_ek4(5 pieces)_ek4(last)..._TOC-2.html(Introduction)_ek1…_ek2(3 pieces)_ek2(last)I also verified
id="TOC-6"literally appears in the base file,_ek1, and_ek5alike — confirming the duplication-across-fragments behavior that makes the last-wins bug bite every time a chapter is split.Downstream effect on the reader
The CrossPoint/YACP firmware's spine→TOC mapping (
BookMetadataCache) intentionally falls back to the previous TOC entry for any spine item that has no direct match (reasonable default behavior for legitimately un-tocced continuation files). Combined with this bug, every fragment before the mis-pointed last fragment has no TOC match of its own, so the reader's chapter-title status bar shows the previous chapter for essentially the whole chapter, correcting only right at its very end — right before the next chapter begins. This reproduces as: "the reader shows the previous chapter as current" while reading normally through a book with long, auto-split chapters.Fix
Make the anchor assignment first-occurrence-wins instead of last-wins, since the anchor's true target is definitionally wherever the wrapping element originally opens (the first fragment):
I applied this locally and regenerated the epub for the affected book; the resulting
toc.ncxcorrectly points every chapter at its first fragment (e.g...._TOC-6.html#TOC-6instead of..._TOC-6_ek5.html#TOC-6), confirmed via a diff of before/aftertoc.ncx.Repro
<body>exceedsSPLIT_LIMIT(triggerssplit_xhtml_doc). minimal-split-anchor-repro.ziptoc.ncx: the chapter's<content src="...">points at the highest-numbered_ekNfragment rather than the base file.