Reading window 2/3: continuous scroll #16

Merged
schaefera merged 12 commits from phase1/continuous-scroll into main 2026-10-21 08:07:09 +00:00
Collaborator

Part of #11.

What

The text now scrolls straight through chapter and book boundaries in both directions, with no ‹ › buttons. The core keeps the fewest chapters that hold 40 verses on each side of the top verse. It slides one chapter on (Slid) or off (Dropped) as you read, so the text never jumps. Short chapters such as Ps 117, Obadiah and 2–3 John don't stop the scroll. The app still reopens on the saved verse.

Blast radius

Tier: trunk
Trunk paths touched: core/src/worker.rs, core/src/reader.rs, qt/src/reading_model.rs, qml/pages/ReadingPane.qml, tools/mem-check/
Gate / rollback: no flag, because this replaces the stepping. To undo it, revert this merge or reinstall the previous .click. No migration and no schema change.

Budgets (docs/07): peak PSS 148–150 MB in mem-check against a 165 MB budget. That is about +2–3 MB over PR 1's ~146.6 MB, from the 3–4 chapter window; the worst case is about 230 rows at Ps 119:1. Start-up is unchanged (83–87 ms).

Proof

  • cargo make (fmt, clippy, tests with coverage, release check, cargo-deny, cargo-audit): green. 29 core tests pass with SWORD_TEST_PATH set; line coverage 93.97%.
  • cargo make app-docker (C++/QML format and lint, qt/ clippy, -Werror build, clang-tidy, mem-check): green on HEAD:
    • fling down: 67 top-verse reports, steps of at most 8 verses. Fling up: 22 reports, at most 8. Limit 10: a slide doesn't move the text.
    • start-up 83 ms, 15 chapters shown, peak PSS 148340 kB
    • going inactive right after a scroll: 1 save, 188 ms after the scroll
    • after kill -9: restart 50 ms; saved 'BSB 262 0.682', restored the same, and the same verse on screen
  • cargo make click and click-review: pass.
  • Each of these deliberate breaks fails, then was reverted:
    • neighbour never None at the Bible's ends → reader.rs:278 test fails
    • no drop → slide test fails
    • place_of off by one → 4 tests fail
    • the old end-chapter trigger → keeps_a_margin_of_verses_around_the_top_verse fails at Ps 117
    • mem-check with Slid applied as a model reset → "the fling down didn't get there"
    • mem-check with contentY += 600 on rows removed → caught as a jump
  • Not covered: the glue's shown/latest gate (there is no glue test harness), the QML timer and restoring guards (timing), the ListView prepend at the origin, and short chapters on screen (mem-check flings only Gen 1–12). The UI is native Qt, so no screenshot beyond mem-check's logs.

Review

Reviewer: fresh-context code-review, four rounds: high, then medium ×3 on the fixes. 14 findings: 10 fixed and 4 declined.

  • Fixed: short chapters stopping the scroll, a stale indexAt after a slide, a report before the restore (twice), a flaky mem-check, the fling cap, a stale layout on the inactive path, stale spec lines, a slide gated on latest, and an overtaken successful jump.
  • Declined:
    • ListView prepend at the origin: with a 40-verse margin, reaching it needs more than 1000 px in 100 ms. The Qt behaviour itself is unverified.
    • The first load doesn't meet the margin until the first report: by design, since the restore always fires a report.
    • unwrap_or(0) at reader.rs:189 can't be reached.
    • Review 4: two low notes, not defects.

Review-Confidence: 45/100. The core logic is well reviewed with fails-without-change proof, but the glue's overtaken-jump gate, the QML timer and restore guards, and the ListView prepend have no executed proof. The audit found no missed defect.

Confidence by part

  • core/src/worker.rs margin slide (slide/grow/trim): high. Unit test across Gen, Ps 116–119, Mal→Matt and Rev, plus deliberate breaks.
  • core/src/reader.rs neighbours and ordinals: high. Tests and deliberate breaks.
  • qt/src/reading_model.rs receive gate (shown/latest/loading): low. Read this. It was rewritten twice after review and is exercised only by mem-check's happy path.
  • qml/pages/ReadingPane.qml report timer and restoring guards: low. They depend on timing and aren't provable in CI.
  • ListView prepend keeping the text still: medium. mem-check shows it in Genesis; at the very top of the list it is unverified.
  • Please check on the phone:
    • fling down and up through Ps 116–119 and Obadiah→Jonah;
    • two quick jumps in a row.

Merge style: Create merge commit (keeps the trailers).

🤖 Generated with Claude Code

Part of #11. ## What The text now scrolls straight through chapter and book boundaries in both directions, with no ‹ › buttons. The core keeps the fewest chapters that hold 40 verses on each side of the top verse. It slides one chapter on (`Slid`) or off (`Dropped`) as you read, so the text never jumps. Short chapters such as Ps 117, Obadiah and 2–3 John don't stop the scroll. The app still reopens on the saved verse. ## Blast radius **Tier:** trunk **Trunk paths touched:** `core/src/worker.rs`, `core/src/reader.rs`, `qt/src/reading_model.rs`, `qml/pages/ReadingPane.qml`, `tools/mem-check/` **Gate / rollback:** no flag, because this replaces the stepping. To undo it, revert this merge or reinstall the previous `.click`. No migration and no schema change. **Budgets (docs/07):** peak PSS 148–150 MB in mem-check against a 165 MB budget. That is about +2–3 MB over PR 1's ~146.6 MB, from the 3–4 chapter window; the worst case is about 230 rows at Ps 119:1. Start-up is unchanged (83–87 ms). ## Proof - `cargo make` (fmt, clippy, tests with coverage, release check, cargo-deny, cargo-audit): green. 29 core tests pass with SWORD_TEST_PATH set; line coverage 93.97%. - `cargo make app-docker` (C++/QML format and lint, qt/ clippy, -Werror build, clang-tidy, mem-check): green on HEAD: - fling down: 67 top-verse reports, steps of at most 8 verses. Fling up: 22 reports, at most 8. Limit 10: a slide doesn't move the text. - start-up 83 ms, 15 chapters shown, peak PSS 148340 kB - going inactive right after a scroll: 1 save, 188 ms after the scroll - after kill -9: restart 50 ms; saved 'BSB 262 0.682', restored the same, and the same verse on screen - `cargo make click` and click-review: pass. - Each of these deliberate breaks fails, then was reverted: - neighbour never None at the Bible's ends → reader.rs:278 test fails - no drop → slide test fails - `place_of` off by one → 4 tests fail - the old end-chapter trigger → `keeps_a_margin_of_verses_around_the_top_verse` fails at Ps 117 - mem-check with Slid applied as a model reset → "the fling down didn't get there" - mem-check with `contentY += 600` on rows removed → caught as a jump - Not covered: the glue's `shown`/`latest` gate (there is no glue test harness), the QML timer and `restoring` guards (timing), the ListView prepend at the origin, and short chapters on screen (mem-check flings only Gen 1–12). The UI is native Qt, so no screenshot beyond mem-check's logs. ## Review **Reviewer:** fresh-context `code-review`, four rounds: high, then medium ×3 on the fixes. 14 findings: 10 fixed and 4 declined. - Fixed: short chapters stopping the scroll, a stale `indexAt` after a slide, a report before the restore (twice), a flaky mem-check, the fling cap, a stale layout on the inactive path, stale spec lines, a slide gated on `latest`, and an overtaken successful jump. - Declined: - ListView prepend at the origin: with a 40-verse margin, reaching it needs more than 1000 px in 100 ms. The Qt behaviour itself is unverified. - The first load doesn't meet the margin until the first report: by design, since the restore always fires a report. - `unwrap_or(0)` at reader.rs:189 can't be reached. - Review 4: two low notes, not defects. **Review-Confidence:** 45/100. The core logic is well reviewed with fails-without-change proof, but the glue's overtaken-jump gate, the QML timer and restore guards, and the ListView prepend have no executed proof. The audit found no missed defect. ## Confidence by part - `core/src/worker.rs` margin slide (`slide`/`grow`/`trim`): **high**. Unit test across Gen, Ps 116–119, Mal→Matt and Rev, plus deliberate breaks. - `core/src/reader.rs` neighbours and ordinals: **high**. Tests and deliberate breaks. - `qt/src/reading_model.rs` `receive` gate (`shown`/`latest`/`loading`): **low**. Read this. It was rewritten twice after review and is exercised only by mem-check's happy path. - `qml/pages/ReadingPane.qml` report timer and `restoring` guards: **low**. They depend on timing and aren't provable in CI. - ListView prepend keeping the text still: **medium**. mem-check shows it in Genesis; at the very top of the list it is unverified. - **Please check on the phone:** - fling down and up through Ps 116–119 and Obadiah→Jonah; - two quick jumps in a row. **Merge style:** Create merge commit (keeps the trailers). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Also the PR 2 plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reader reads any chapter and finds the chapter of an ordinal; each row
carries its ordinal. Event::Chapter becomes Loaded (1-3 chapters, the
row counted across them) and Slid (one chapter on, maybe one off).
Step goes. UTSB_TRACE logs each top-verse report for mem-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "slide when the top verse is in an end chapter", a last chapter shorter than the screen (Ps 117, Obadiah, 2-3 John, Jude) ended the list before the top verse could get there: reading forward stopped. The worker now keeps the fewest chapters that hold MARGIN verses each way and says each change as Slid (one on) or Dropped (one off). Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker handles commands in order, so a Slid or Dropped after the shown Loaded is for those rows. Gating on the newest command instead dropped a slide the worker had made when a GoTo after it failed, and the two disagreed about the loaded chapters from then on. Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
qt, qml: show every newer load; no report before the restore
All checks were successful
CI / rust (pull_request) Successful in 4m9s
CI / audit (pull_request) Successful in 13s
CI / app (pull_request) Successful in 2m4s
CI / click (pull_request) Successful in 1m25s
e8e2ea2826
Review 3: a jump overtaken by a failing jump left the worker on its chapters while the glue dropped its Loaded, so later slides landed on the wrong rows. The worker handles commands in order, so the glue now shows every load newer than the shown one, stays loading while a newer jump is pending, and shows only the newest jump's error. A report between a model reset and its deferred restore named row 0; a restoring flag skips it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 45
Review-Tier: trunk
schaefera approved these changes 2026-10-21 08:07:03 +00:00
schaefera deleted branch phase1/continuous-scroll 2026-10-21 08:07:09 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
schaefera/utStudyBIble!16
No description provided.