Release / build-and-push (push) Successful in 2m57s
countTextCharsBefore started its sibling walk at the target node itself and recursed after counting the current node's subtree, so every ancestor level re-counted everything accumulated so far — a node at depth 4 in the single-document 1984 epub reported 2.38M chars before it in a 589k-char document, producing healed percentages of 4.04 (>1) and a 500 on the reading_progress percentage check constraint. Every healed progress save from a fresh client failed; only exact-context matches (no heal) stored. The walk now starts at target.PrevSibling: strictly the characters before the node, per the contract all three callers already assume (healed book offsets, CRE convert percentage, kepub offsets). Found by the real-phone validation pass: the phone's first healed submission 500ed where the emulator's had matched context exactly and never taken the heal path.