Core layout: bible and worker modules, Window unit-tested #17

Merged
claude-bot merged 25 commits from phase1/core-layout into main 2026-10-21 13:16:06 +00:00
Collaborator

Part of #11

Now also carries #18 (merged into this branch as 9677417): chapter and book headings in the module's language, book names and chapter words as data files, a typed Locale, and no scroll bar. #18 has its own What / Proof / Review; its last commit has the trailers Review-Confidence: 80 and Review-Tier: trunk.

And #19 (merged as c77d170): versification tables as an ini file read by a shared ini reader, and a quiet "· · ·" break between chapters, none above the Bible's first chapter. #19 has its own What / Proof / Review; its last commit has Review-Confidence: 100 and Review-Tier: trunk.

The sections below describe only this PR's own commits.

What

This PR's own commits change no behaviour. They restructure the code before PR 3 (picker, history, Back, Jump):

  • core/src/reader.rs becomes bible/ (Place and navigation as methods, Anchor, Chapter, Reader).
  • worker.rs is split into worker/{session,window,debounce}.rs. The loaded-chapter logic is now a pure Window type with unit tests that need no module data.
  • db/mod.rs becomes db.rs, and v11n_data.rs becomes v11n/data.rs.
  • CLAUDE.md gains a ## Code layout section with the five rules, and its blast-radius paths are updated.

Blast radius

Tier: trunk
Trunk paths touched: sword/src/v11n*, core/src/db*, core/src/worker*, core/src/bible* (was core/src/reader.rs), qt/src/reading_model.rs (import only), tools/mem-check/run.sh (comment only). #18 and #19 add qml/pages/ReadingPane.qml and the reading model's heading role.
Gate / rollback: revert this merge. It moves code only, with no schema or data change (#18 and #19 have none either).
Budgets: unchanged. mem-check gives start-up 84 ms and peak PSS 146382 kB (budget 165000 kB); #16 gave 83 ms and 148340 kB. With #18: 147565 kB, start-up 130 ms (noise range 80–130). With #19: 146923 kB, 126 ms.

Proof

  • cargo make (fmt, clippy, tests with coverage, release check, deny, audit; run with SWORD_TEST_REQUIRED=1): all pass. sword runs 16+7+3+3 tests and core runs 31. Coverage is 94.17% of regions (floor 93.97%). With #18: 40 core tests, 94.78%. With #19: 94.96%.
  • cargo make app-docker: exit 0. mem-check results:
    • "down: 68 top-verse reports, steps of at most 8 verses"
    • "up: 21 … at most 7"
    • "inactive right after a scroll: 1 save(s), seen 189 ms after the scroll"
    • "after kill -9: … saved 'BSB 268 0.545', restored 'BSB 268 0.545'"
  • Fails without the change: with have < MARGIN + n changed to have < MARGIN in Window::trim, cargo test -p utsb-core window:: fails at window.rs:145. Reverted, it passes 2/2.
  • worker::tests::keeps_a_margin_of_verses_around_the_top_verse passes unchanged after the switch to Window, so the Slid/Dropped event stream is the same as before.
  • RUSTDOCFLAGS="-D warnings" cargo doc -p utsb-core (with and without --document-private-items): clean.
  • The arm64 click builds and passes click review. It is installed on the phone for a manual check.
  • git grep for the old paths (outside docs/superpowers): empty.

Review

Reviewer: a fresh-context Sonnet agent, asked for code-review at high. It reviewed by that method directly, comparing old and new code side by side. It found 0 bugs and raised 3 nits:

  • 1 fixed: a doc comment says a Window is built for one versification.
  • 2 declined: one is a stale reference in a historical spec. The other, window::MARGIN, is plain text on purpose, because a link to a private item fails rustdoc -D warnings.

Review-Confidence: 90/100. Every changed trunk file was opened and compared side by side, and the new Window test fails when the trim condition is mutated. The only gaps are two nits that gave no line number.

Confidence by part

  • File moves (v11n/data.rs, db.rs, bible/*, debounce.rs): high. The bodies are verbatim apart from the switch to method syntax.
  • worker/window.rs and Session::slide: medium-high. This is the one real restructure, and verse counts are now cached per chapter. The unchanged integration test and mem-check both pass. Read this part if you read only one.
  • #18's and #19's parts: see their "Confidence by part".

Merge with "Create merge commit".

🤖 Generated with Claude Code

Part of #11 **Now also carries #18** (merged into this branch as 9677417): chapter and book headings in the module's language, book names and chapter words as data files, a typed `Locale`, and no scroll bar. #18 has its own What / Proof / Review; its last commit has the trailers `Review-Confidence: 80` and `Review-Tier: trunk`. **And #19** (merged as c77d170): versification tables as an ini file read by a shared ini reader, and a quiet "· · ·" break between chapters, none above the Bible's first chapter. #19 has its own What / Proof / Review; its last commit has `Review-Confidence: 100` and `Review-Tier: trunk`. The sections below describe only this PR's own commits. ## What This PR's own commits change no behaviour. They restructure the code before PR 3 (picker, history, Back, Jump): - `core/src/reader.rs` becomes `bible/` (`Place` and navigation as methods, `Anchor`, `Chapter`, `Reader`). - `worker.rs` is split into `worker/{session,window,debounce}.rs`. The loaded-chapter logic is now a pure `Window` type with unit tests that need no module data. - `db/mod.rs` becomes `db.rs`, and `v11n_data.rs` becomes `v11n/data.rs`. - CLAUDE.md gains a `## Code layout` section with the five rules, and its blast-radius paths are updated. ## Blast radius **Tier:** trunk **Trunk paths touched:** `sword/src/v11n*`, `core/src/db*`, `core/src/worker*`, `core/src/bible*` (was `core/src/reader.rs`), `qt/src/reading_model.rs` (import only), `tools/mem-check/run.sh` (comment only). #18 and #19 add `qml/pages/ReadingPane.qml` and the reading model's heading role. **Gate / rollback:** revert this merge. It moves code only, with no schema or data change (#18 and #19 have none either). **Budgets:** unchanged. mem-check gives start-up 84 ms and peak PSS 146382 kB (budget 165000 kB); #16 gave 83 ms and 148340 kB. With #18: 147565 kB, start-up 130 ms (noise range 80–130). With #19: 146923 kB, 126 ms. ## Proof - `cargo make` (fmt, clippy, tests with coverage, release check, deny, audit; run with SWORD_TEST_REQUIRED=1): all pass. sword runs 16+7+3+3 tests and core runs 31. Coverage is 94.17% of regions (floor 93.97%). With #18: 40 core tests, 94.78%. With #19: 94.96%. - `cargo make app-docker`: exit 0. mem-check results: - "down: 68 top-verse reports, steps of at most 8 verses" - "up: 21 … at most 7" - "inactive right after a scroll: 1 save(s), seen 189 ms after the scroll" - "after kill -9: … saved 'BSB 268 0.545', restored 'BSB 268 0.545'" - **Fails without the change:** with `have < MARGIN + n` changed to `have < MARGIN` in `Window::trim`, `cargo test -p utsb-core window::` fails at `window.rs:145`. Reverted, it passes 2/2. - `worker::tests::keeps_a_margin_of_verses_around_the_top_verse` passes unchanged after the switch to `Window`, so the Slid/Dropped event stream is the same as before. - `RUSTDOCFLAGS="-D warnings" cargo doc -p utsb-core` (with and without `--document-private-items`): clean. - The arm64 click builds and passes click review. It is installed on the phone for a manual check. - `git grep` for the old paths (outside `docs/superpowers`): empty. ## Review **Reviewer:** a fresh-context Sonnet agent, asked for `code-review` at high. It reviewed by that method directly, comparing old and new code side by side. It found 0 bugs and raised 3 nits: - 1 fixed: a doc comment says a `Window` is built for one versification. - 2 declined: one is a stale reference in a historical spec. The other, `window::MARGIN`, is plain text on purpose, because a link to a private item fails rustdoc `-D warnings`. **Review-Confidence:** 90/100. Every changed trunk file was opened and compared side by side, and the new Window test fails when the trim condition is mutated. The only gaps are two nits that gave no line number. ## Confidence by part - File moves (`v11n/data.rs`, `db.rs`, `bible/*`, `debounce.rs`): high. The bodies are verbatim apart from the switch to method syntax. - `worker/window.rs` and `Session::slide`: medium-high. This is the one real restructure, and verse counts are now cached per chapter. The unchanged integration test and mem-check both pass. Read this part if you read only one. - #18's and #19's parts: see their "Confidence by part". Merge with "Create merge commit". 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
core: note that a Window is built for one versification
All checks were successful
CI / rust (pull_request) Successful in 3m38s
CI / audit (pull_request) Successful in 13s
CI / app (pull_request) Successful in 1m52s
CI / click (pull_request) Successful in 1m13s
3f57382878
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 90
Review-Tier: trunk
Each chapter opens with a heading ("Chapter N", "Psalm N" in Psalms) and
each book with its name, as ListView sections on a new "reference" role.
The top-verse report looks past a heading to the first verse below; a
restore with a fraction lands on the verse, not the heading. The scroll
indicator is gone: the list is only the loaded chapters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading model: a chapter's rows and entry change together
All checks were successful
CI / rust (pull_request) Successful in 3m19s
CI / audit (pull_request) Successful in 12s
CI / app (pull_request) Successful in 1m51s
CI / click (pull_request) Successful in 1m14s
be765b5f6a
data() now reads the chapter list for the reference role, so it changes
between begin and end with the rows. The heading scan steps by at least
one pixel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 55
Review-Tier: trunk
Book names come from JSword's BibleNames tables (LGPL-2.1-or-later),
generated into core by tools/booknames; the chapter word from a small
table (de en es fr it nl pt), else the book's name and number. The
module's conf Lang picks the table, else the phone's language, else
English: GerSch shows "1. Mose / Kapitel 1". The glue's section role
is now the heading itself, and qml/components/books.js is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LANGUAGE=":de" now picks de, as glibc does. tools/booknames/fetch.sh
fetches one JSword commit; it regenerates data.rs unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Book names: number-only headings without chapter words, gettext's language order
All checks were successful
CI / rust (pull_request) Successful in 2m46s
CI / audit (pull_request) Successful in 10s
CI / app (pull_request) Successful in 1m34s
CI / click (pull_request) Successful in 58s
36ee071d26
A language without chapter words no longer shows the book's name twice
on chapter 1: the heading is the number. The phone's languages are the
LANGUAGE list then the locale, none under C, read through a pure
function the tests feed. gen.py writes UTF-8 whatever the locale, and
fetch.sh fails on an incomplete table list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 55
Review-Tier: trunk
The heading was a Column, which sizes itself at polish. The ListView
reuses section items, so a one-line heading reused for "Exodus /
Chapter 1" kept its old height and overlapped verse 1. An Item whose
height binds to its Texts' implicit heights resizes at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading pane: one source for the heading's layout, plain text
All checks were successful
CI / rust (pull_request) Successful in 2m32s
CI / audit (pull_request) Successful in 11s
CI / app (pull_request) Successful in 1m33s
CI / click (pull_request) Successful in 59s
37025d4239
The height reads the chapter line's position instead of repeating the
sum, so the two can't drift apart. A module's book name is never parsed
as rich text, as for the verses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 75
Review-Tier: trunk
JSword's names now live in core/src/bible/names/books.ini, one
[locale] section of Gen=Genesis lines per language, embedded with
include_str! and read in place: no allocation, and the code file holds
only code. data.rs and its BOOKS/NAMES consts are gone. A test checks
every language has all 66 books in order. gen.py regenerates the file
byte-identical from the pinned JSword commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Book names: books.ini keeps LF endings; gen.py refuses empty names
All checks were successful
CI / rust (pull_request) Successful in 2m32s
CI / audit (pull_request) Successful in 11s
CI / app (pull_request) Successful in 1m25s
CI / click (pull_request) Successful in 53s
5baa837a6c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 85
Review-Tier: trunk
The "Kapitel"/"Psalm" table moves from names.rs to a hand-written
names/headings.ini, in the books.ini shape. Both files are read by one
small Section type (names/section.rs); the English fallback is a named
constant. Tests check every heading language has both words and a book
table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Book names: a typed Locale instead of locale strings
All checks were successful
CI / rust (pull_request) Successful in 1m52s
CI / audit (pull_request) Successful in 9s
CI / app (pull_request) Successful in 1m13s
CI / click (pull_request) Successful in 42s
0025b06788
A conf's Lang (BCP 47) and the phone's locales (POSIX) parse once into a
Locale: lower-case language, upper-case region, codeset, modifier and
script dropped, None for C and POSIX. Names matches .ini sections by
parsed Locale, not by slicing and comparing strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 80
Review-Tier: trunk
Merge pull request 'Reading pane: chapter and book headings in the module's language, no scroll bar' (#18) from phase1/chapter-headings into phase1/core-layout
All checks were successful
CI / rust (pull_request) Successful in 1m47s
CI / audit (pull_request) Successful in 10s
CI / app (pull_request) Successful in 1m13s
CI / click (pull_request) Successful in 43s
9677417bd7
Reviewed-on: #18
sword/src/v11n/systems.ini replaces the generated v11n/data.rs and its
Table type: a [System.OT] and [System.NT] section per system, of
Gen=31,25,... lines. Versification parses a system's counts when it is
built. The ini Section type moves from core's names into sword::ini so
core's book names and sword's v11n read data files the same way.
tools/v11n/fetch.sh is pinned to the JSword commit tools/booknames uses;
gen.py output is the same at that commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dim, centred row of three dots at the top of every section heading
gives the end of one chapter some room before the next one's heading.
It is on every heading, so a heading item the list reuses never
changes height; the first chapter shown also gets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadingPane: screen readers skip the chapter break
All checks were successful
CI / rust (pull_request) Successful in 2m29s
CI / audit (pull_request) Successful in 12s
CI / app (pull_request) Successful in 1m24s
CI / click (pull_request) Successful in 54s
470a584e58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading pane: no break above the Bible's first chapter; wider dots
All checks were successful
CI / rust (pull_request) Successful in 2m17s
CI / audit (pull_request) Successful in 11s
CI / app (pull_request) Successful in 1m24s
CI / click (pull_request) Successful in 51s
1815b14e8d
The "···" break sat above Genesis 1 too, where no chapter comes before
it. The core now marks a versification's first chapter
(Heading::first), and the glue passes the heading as three lines,
break, book and chapter, each empty where there is none, so the pane
shows the dots only where a chapter ends above. Em spaces between the
dots keep them from reading as an ellipsis, and centre them (letter
spacing left a trailing gap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review-Confidence: 100
Review-Tier: trunk
Merge pull request 'Reading pane: a quiet break before each chapter; v11n counts in a data file' (#19) from phase1/chapter-separator into phase1/core-layout
All checks were successful
CI / rust (pull_request) Successful in 2m16s
CI / audit (pull_request) Successful in 10s
CI / app (pull_request) Successful in 1m26s
CI / click (pull_request) Successful in 50s
c77d170b8b
Reviewed-on: #19
Reviewed-by: Austin Schaefer <austin.schaefer@mailo.eu>
schaefera approved these changes 2026-10-21 13:12:16 +00:00
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-10-21 13:15:11 +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!17
No description provided.