Verse sheet with TSK cross-references, Install TSK when missing (#13, 6d) #130

Merged
claude-bot merged 9 commits from feat/13-6d-tsk into main 2026-10-30 09:14:49 +00:00
Collaborator

Part 6d of #13.

What

  • A tap on a verse opens its verse sheet. That can be a tap anywhere on its text, in either layout, or on its number. Note markers stay links.
  • The sheet shows the verse under its reference, then its notes and its cross-references from TSK. Each reference comes with its text from the open Bible: five at first, with "Show all N". A tap jumps there, and Back returns to the exact place.
  • Without TSK, the row is greyed and says it needs the TSK module, with an "Install TSK" button. Notes are greyed when the verse has none. A TSK that is removed or updated is closed or opened again.
  • A copy icon (Lucide) sits at the sheet's title: a tap copies "Ref text", and a long press shows "Copy this verse".

verse sheet on the phone

Blast radius

Tier: trunk
Trunk paths touched: core/src/bible*, core/src/worker*, qt/ (reading_model.rs), qml/pages/ReadingPane.qml, qml/Main.qml, .forgejo/workflows/ci.yml (the e2e job also fetches TSK), tools/e2e/
Testing tier: standard (also: CI config, the e2e job's module fetch and cache key only)
Gate / rollback: no flag. The sheet is new UI, and without TSK installed nothing is read. To undo it, revert this merge. There is no migration and no schema change.

Proof

  • Rust gates: cargo make passed (fmt, clippy, tests with the golden data, deny, audit).
  • App job: cargo make app-docker passed. mem-check peak PSS was 165156 kB normal and 169483 kB slow (budget 176000), with text on screen after 320 ms.
  • Budgets (docs/07):
    • The TSK index is about 0.3 MB, loaded only once a verse sheet asks for it.
    • On the phone, PSS went from 63078 to 66342 kB (+3.3 MB) after opening sheets with TSK.
    • Start-up has no new work.
  • e2e: verse-sheet, verse-sheet-no-tsk, verse-copy, verse-text-tap, note-sheet and running-text pass. The app logs have no QML warnings or binding loops.
  • Core tests cover TSK parsing, references resolved in another versification (greyed when the Bible lacks the verse), a removed or updated TSK, and the sheet JSON with the verse text.
  • On the phone (arm64 click), I checked:
    • TSK refs, Show all 21, the jump to John 11:45 and Back to John 11:1.
    • The copy icon in Suru style.
    • A text tap on John 11:4, which shows the verse.
    • A swipe over the text, which scrolls without opening a sheet.
  • CI on 0268678: changes, rust, audit, app, e2e and click passed.

Screenshots are in the PR comments: the phone sheet, the copy tooltip, the sheet without TSK, Show all, and a text tap.

Review

Reviewer: fresh-context reviews 339 (high), 340 and 341, then 342 (the delta). 343 (medium) covered the text-tap commit and 344 was its delta. The findings and answers are in the PR threads. 343's low finding is fixed in 22a91cd, and 344's low finding in 0268678, after the delta review. Review 340's low 2 was declined (comment 2561). None are left open.
Review-Confidence: 95/100. The audit's one deduction (−5) is the fix after the delta review, 0268678 (2 lines of QML, its cases pass); it found no missed defect.

Confidence by part

  • core TSK reading and the cross-versification references: high (golden-data tests, phone).
  • ReadingPane taps (MouseArea plus linkAt, running text's charAt): medium. The e2e cases cover a mid-line tap in both layouts; the wrapped-line-end case was fixed by reading only.
  • Owner feel items: marker taps next to text taps, and the sheet in dark theme.

🤖 Generated with Claude Code

Part 6d of #13. ## What - A tap on a verse opens its **verse sheet**. That can be a tap anywhere on its text, in either layout, or on its number. Note markers stay links. - The sheet shows the verse under its reference, then its notes and its **cross-references from TSK**. Each reference comes with its text from the open Bible: five at first, with "Show all N". A tap jumps there, and Back returns to the exact place. - Without TSK, the row is greyed and says it needs the TSK module, with an "Install TSK" button. Notes are greyed when the verse has none. A TSK that is removed or updated is closed or opened again. - A copy icon (Lucide) sits at the sheet's title: a tap copies "Ref text", and a long press shows "Copy this verse". ![verse sheet on the phone](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/text-tap-sheet.png) ## Blast radius **Tier:** trunk **Trunk paths touched:** `core/src/bible*`, `core/src/worker*`, `qt/` (reading_model.rs), `qml/pages/ReadingPane.qml`, `qml/Main.qml`, `.forgejo/workflows/ci.yml` (the e2e job also fetches TSK), `tools/e2e/` **Testing tier:** standard (also: CI config, the e2e job's module fetch and cache key only) **Gate / rollback:** no flag. The sheet is new UI, and without TSK installed nothing is read. To undo it, revert this merge. There is no migration and no schema change. ## Proof - Rust gates: `cargo make` passed (fmt, clippy, tests with the golden data, deny, audit). - App job: `cargo make app-docker` passed. mem-check peak PSS was 165156 kB normal and 169483 kB slow (budget 176000), with text on screen after 320 ms. - **Budgets (docs/07):** - The TSK index is about 0.3 MB, loaded only once a verse sheet asks for it. - On the phone, PSS went from 63078 to 66342 kB (+3.3 MB) after opening sheets with TSK. - Start-up has no new work. - e2e: verse-sheet, verse-sheet-no-tsk, verse-copy, verse-text-tap, note-sheet and running-text pass. The app logs have no QML warnings or binding loops. - Core tests cover TSK parsing, references resolved in another versification (greyed when the Bible lacks the verse), a removed or updated TSK, and the sheet JSON with the verse text. - On the phone (arm64 click), I checked: - TSK refs, Show all 21, the jump to John 11:45 and Back to John 11:1. - The copy icon in Suru style. - A text tap on John 11:4, which shows the verse. - A swipe over the text, which scrolls without opening a sheet. - CI on 0268678: changes, rust, audit, app, e2e and click passed. Screenshots are in the PR comments: the phone sheet, the copy tooltip, the sheet without TSK, Show all, and a text tap. ## Review **Reviewer:** fresh-context reviews 339 (high), 340 and 341, then 342 (the delta). 343 (medium) covered the text-tap commit and 344 was its delta. The findings and answers are in the PR threads. 343's low finding is fixed in 22a91cd, and 344's low finding in 0268678, after the delta review. Review 340's low 2 was declined (comment 2561). None are left open. **Review-Confidence:** 95/100. The audit's one deduction (−5) is the fix after the delta review, 0268678 (2 lines of QML, its cases pass); it found no missed defect. ## Confidence by part - core TSK reading and the cross-versification references: high (golden-data tests, phone). - ReadingPane taps (MouseArea plus `linkAt`, running text's `charAt`): medium. The e2e cases cover a mid-line tap in both layouts; the wrapped-line-end case was fixed by reading only. - Owner feel items: marker taps next to text taps, and the sheet in dark theme. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stopped mid-implementation at the end of the day; it does not build yet
(refs/sweep.rs imports super::scrip before it is wired in). Not reviewed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui: the verse sheet's Notes row is greyed with its reason when empty; German strings (#13, 6d)
Some checks failed
CI / changes (pull_request) Successful in 13s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
fc0fb8f3f2
The WIP commit before this one builds and passes the gates; its message
was wrong about that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-review left a comment

Review level: high (trunk). Commit fc0fb8f3f2. Range: origin/main...HEAD.

Files reviewed: .forgejo/workflows/ci.yml, core/src/bible.rs, bible/{block,note,reader,refs,tsk,verse_sheet}.rs, bible/reader/{sheet,verse}.rs, bible/refs/{scrip,sweep}.rs, catalog/{kind,tests}.rs, worker.rs, worker/session.rs, worker/session/jump.rs, qt/src/reading_model.rs, qml/Main.qml, qml/controls/{RefRows.qml,qmldir}, qml/pages/{NoteSheet,ReadingPane,VerseSheet}.qml, qml/qml.qrc, tools/e2e/cases/{verse-sheet,verse-sheet-no-tsk}.sh, po/*.

No high or medium defects found.

Findings (low):

  • low · core/src/worker/session/jump.rs:~25 · session.tsk is opened once and never dropped or re-checked. If the user removes TSK in the module manager, the open handle keeps answering: the sheet still lists references and never shows "Needs the TSK module" until the app restarts. (If the handles are per-read, it shows "None for this verse" instead.) Reopen when the module's conf is gone, or drop tsk on a module change.
  • low · core/src/bible/reader/verse.rs:~27 · sheet_ref reads and decodes up to 5 verses for every TSK reference of the tapped verse, on the worker, before the sheet opens. Verses with 100+ references block the worker (and so reading and flush commands) and produce large JSON, although only 5 rows show. Resolve the text lazily for the first cap refs, or on "Show all".
  • low · core/src/bible/tsk.rs:~45 · When same_verse finds no match in TSK's versification, refs returns empty, so the sheet says "None for this verse" with TSK installed. That is not the same as TSK having no entry; the wording may mislead for Bibles with a different versification.

Testing tier: the maximum-tier targeted check (CI e2e on the final commit running verse-sheet and verse-sheet-no-tsk as PASS, not SKIP) could not be confirmed. The latest run for this head, run 239, was still "waiting" when I reviewed. Confirm it before merge. The e2e cases pass SKIP only via exit 77 when a module is missing, which is why the TSK fetch and cache-key change in ci.yml matter, and that change looks correct.

Review level: high (trunk). Commit fc0fb8f3f2461fb3a98b25f9bddc6aa394a40b9f. Range: origin/main...HEAD. Files reviewed: .forgejo/workflows/ci.yml, core/src/bible.rs, bible/{block,note,reader,refs,tsk,verse_sheet}.rs, bible/reader/{sheet,verse}.rs, bible/refs/{scrip,sweep}.rs, catalog/{kind,tests}.rs, worker.rs, worker/session.rs, worker/session/jump.rs, qt/src/reading_model.rs, qml/Main.qml, qml/controls/{RefRows.qml,qmldir}, qml/pages/{NoteSheet,ReadingPane,VerseSheet}.qml, qml/qml.qrc, tools/e2e/cases/{verse-sheet,verse-sheet-no-tsk}.sh, po/*. No high or medium defects found. Findings (low): - low · core/src/worker/session/jump.rs:~25 · `session.tsk` is opened once and never dropped or re-checked. If the user removes TSK in the module manager, the open handle keeps answering: the sheet still lists references and never shows "Needs the TSK module" until the app restarts. (If the handles are per-read, it shows "None for this verse" instead.) Reopen when the module's conf is gone, or drop `tsk` on a module change. - low · core/src/bible/reader/verse.rs:~27 · `sheet_ref` reads and decodes up to 5 verses for every TSK reference of the tapped verse, on the worker, before the sheet opens. Verses with 100+ references block the worker (and so reading and flush commands) and produce large JSON, although only 5 rows show. Resolve the text lazily for the first `cap` refs, or on "Show all". - low · core/src/bible/tsk.rs:~45 · When `same_verse` finds no match in TSK's versification, `refs` returns empty, so the sheet says "None for this verse" with TSK installed. That is not the same as TSK having no entry; the wording may mislead for Bibles with a different versification. Testing tier: the maximum-tier targeted check (CI e2e on the final commit running verse-sheet and verse-sheet-no-tsk as PASS, not SKIP) could not be confirmed. The latest run for this head, run 239, was still "waiting" when I reviewed. Confirm it before merge. The e2e cases pass SKIP only via exit 77 when a module is missing, which is why the TSK fetch and cache-key change in ci.yml matter, and that change looks correct.
core: a removed TSK is closed and offered again; TSK entry test off chapter 1 (#13, 6d review 339)
Some checks failed
CI / changes (pull_request) Successful in 8s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
80093891b3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Answers to review 339 (high, fc0fb8f; no high or medium):

  1. low · jump.rs: TSK stays open after it is removed. Fixed in 8009389. Each verse sheet now checks Tsk::installed (is the conf still in mods.d?). A removed TSK is closed, so the sheet offers Install TSK again. A reinstall opens it again. The worker test now removes tsk.conf and expects "tsk":false; that assertion fails without the fix. Separately, an_entrys_refs_start_from_its_chapter… used chapter 1, so it passed even with the chapter argument ignored. It now uses Genesis 2 and fails when the chapter is dropped.
  2. low · verse.rs: the text of every reference is read, not only the first 5. Not changed. The design (#13 comment 2373, point 3) has "Show all N" list all N with their full verse text, at once. Reading them later would need a second round trip to the worker for the same sheet. Genesis 1:1 (75 references) passes the e2e case with the sheet up inside the tap loop. The cost on the slow phone gets measured in the phone check and goes into the PR body.
  3. low · tsk.rs: a verse with no KJV equivalent shows "None for this verse". Not changed. TSK is keyed in the KJV versification. If same_verse finds no KJV verse, TSK really has no entry for that verse, so "None for this verse" is true. The open Bible's other verses are mapped and listed.

🤖 Generated with Claude Code

Answers to review 339 (high, fc0fb8f; no high or medium): 1. **low · jump.rs: TSK stays open after it is removed.** Fixed in 8009389. Each verse sheet now checks `Tsk::installed` (is the conf still in `mods.d`?). A removed TSK is closed, so the sheet offers Install TSK again. A reinstall opens it again. The worker test now removes `tsk.conf` and expects `"tsk":false`; that assertion fails without the fix. Separately, `an_entrys_refs_start_from_its_chapter…` used chapter 1, so it passed even with the chapter argument ignored. It now uses Genesis 2 and fails when the chapter is dropped. 2. **low · verse.rs: the text of every reference is read, not only the first 5.** Not changed. The design (#13 comment 2373, point 3) has "Show all N" list all N with their full verse text, at once. Reading them later would need a second round trip to the worker for the same sheet. Genesis 1:1 (75 references) passes the e2e case with the sheet up inside the tap loop. The cost on the slow phone gets measured in the phone check and goes into the PR body. 3. **low · tsk.rs: a verse with no KJV equivalent shows "None for this verse".** Not changed. TSK is keyed in the KJV versification. If `same_verse` finds no KJV verse, TSK really has no entry for that verse, so "None for this verse" is true. The open Bible's other verses are mapped and listed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-review left a comment

Review level: medium (re-review of fixes). Commit 80093891b3. Range fc0fb8f..HEAD (8009389). Blast radius: trunk.

Files: core/src/bible/tsk.rs, core/src/worker.rs, core/src/worker/session/jump.rs.

No high or medium findings. The fix follows the module manager's order: package::remove deletes the conf first, so Tsk::installed turns false as soon as a removal starts. Session::verse then drops the handle. The new worker test covers removal, and the entry test now uses chapter 2, so it checks the entry's chapter rather than a chapter-1 coincidence.

Findings:

  • low · core/src/bible/tsk.rs:46 · sword::module::list parses every conf in mods.d on each verse tap, and it fails as a whole if one conf is unreadable. A conf that vanishes mid-listing, for example a different module being removed or installed at that moment, makes installed false. The open TSK is closed for that tap and reopened on the next, which costs one extra index load. This is transient and cheap, so no change is required.
  • low · core/src/worker/session/jump.rs:24 · If TSK is removed and reinstalled between two taps with no verse tap in between, the old handle stays open. A reinstalled newer TSK is not read until the app restarts. This is rare, so no change is required.
Review level: medium (re-review of fixes). Commit 80093891b305de34e531a381f50e098e97980921. Range fc0fb8f..HEAD (8009389). Blast radius: trunk. Files: core/src/bible/tsk.rs, core/src/worker.rs, core/src/worker/session/jump.rs. No high or medium findings. The fix follows the module manager's order: `package::remove` deletes the conf first, so `Tsk::installed` turns false as soon as a removal starts. `Session::verse` then drops the handle. The new worker test covers removal, and the entry test now uses chapter 2, so it checks the entry's chapter rather than a chapter-1 coincidence. Findings: - low · core/src/bible/tsk.rs:46 · `sword::module::list` parses every conf in mods.d on each verse tap, and it fails as a whole if one conf is unreadable. A conf that vanishes mid-listing, for example a different module being removed or installed at that moment, makes `installed` false. The open TSK is closed for that tap and reopened on the next, which costs one extra index load. This is transient and cheap, so no change is required. - low · core/src/worker/session/jump.rs:24 · If TSK is removed and reinstalled between two taps with no verse tap in between, the old handle stays open. A reinstalled newer TSK is not read until the app restarts. This is rare, so no change is required.
core: an updated TSK (its conf's Version changed) is opened again (#13, 6d review 340)
Some checks failed
CI / changes (pull_request) Successful in 10s
CI / rust (pull_request) Successful in 8m40s
CI / audit (pull_request) Successful in 16s
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
2c016172ff
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-review left a comment

Delta review, level medium, blast radius trunk. Commit 2c01617, range 8009389..HEAD.
Files: core/src/bible/tsk.rs, core/src/worker/session/jump.rs. I also read sword::module::list, Conf::get and Module::open.

No defects found.

Checked:

  • A removed TSK conf gives None, so tsk is closed.
  • A changed Version reopens TSK. A missing Version gives "" on both sides, so nothing reopens every tap.
  • If an open after an update fails, tsk becomes None and the next tap retries. The old stale handle is never kept.
  • Conf::get is an exact-key lookup and matches how the conf is parsed.
  • The new tests cover the None case and the installed-version/open-version agreement.

Not covered, not a defect: an update that changes the data without changing Version keeps the stale index. This is an accepted limit of using Version as the signal.

Delta review, level medium, blast radius trunk. Commit 2c01617, range 8009389..HEAD. Files: core/src/bible/tsk.rs, core/src/worker/session/jump.rs. I also read sword::module::list, Conf::get and Module::open. No defects found. Checked: - A removed TSK conf gives None, so tsk is closed. - A changed Version reopens TSK. A missing Version gives "" on both sides, so nothing reopens every tap. - If an open after an update fails, tsk becomes None and the next tap retries. The old stale handle is never kept. - Conf::get is an exact-key lookup and matches how the conf is parsed. - The new tests cover the None case and the installed-version/open-version agreement. Not covered, not a defect: an update that changes the data without changing Version keeps the stale index. This is an accepted limit of using Version as the signal.
ui: the verse sheet's copy is an icon at its title, tooltip on long press (#13, 6d)
Some checks failed
CI / changes (pull_request) Successful in 10s
CI / rust (pull_request) Successful in 9m10s
CI / audit (pull_request) Successful in 17s
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
c9a637f142
Copy verse was below the fold on the phone. It is now a Lucide copy icon at the title's right: a tap copies, a long press only shows "Copy this verse". BottomSheet gets an actions slot for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui: no binding loop in the sheets' height; e2e case for the verse copy button (#13, 6d)
Some checks failed
CI / changes (pull_request) Successful in 9s
CI / rust (pull_request) Successful in 8m46s
CI / audit (pull_request) Successful in 16s
CI / app (pull_request) Successful in 4m45s
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
15674b3754
Reading a layout's implicitHeight in the sheet's height binding lays it out, which changed that height during the read: VerseSheet logged a binding loop at start, and the title row would have made NoteSheet log one too. The fitted height is now kept by the layouts' change handlers.

verse-copy: a long press shows 'Copy this verse' and copies nothing; a tap copies Genesis 1:1 and closes the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Phone check and screenshots

On the phone (feat/13-6d-tsk@2c016172f, then c9a637f14):

  • Tapping John 11:1's number opens its sheet: TSK cross-references with BSB text, and "Notes: none in this verse" greyed. PSS went from 63078 to 66342 kB (+3.3 MB) with TSK opened and the sheet shown.
  • "Show all 21" lists them all and grows the sheet. Tapping John 11:45 jumps there, and Back returns to John 11 at verse 1, where the sheet was opened.
  • The copy button is now an icon at the title's right (owner remark: "Copy verse" was below the fold). It's in view without scrolling, and a long press leaves the sheet open. The phone tools are too slow (about 15 s per step) to catch the 3 s tooltip, so the new e2e case verse-copy checks it: a long press shows "Copy this verse" and copies nothing, and a tap copies Genesis 1:1.
  • The journal showed VerseSheet: Binding loop detected for property "height" at start-up, already on 2c01617. Fixed in 15674b3: BottomSheet keeps its fitted height from the layouts' change handlers. The e2e app logs of verse-sheet, verse-sheet-no-tsk, verse-copy and note-sheet now have no binding loop.
Phone (Suru) Long press (Xvfb)
Without TSK (Xvfb) Show all (Xvfb)

Review 340, low 2 (sword::module::list is parsed on each verse tap): kept. It reads the small mods.d confs once per tap, after a user action, and it is what lets a removed or updated TSK be noticed without a restart. Low 1 was fixed in 2c01617.

Gates on 15674b3: app-docker passed (peak PSS 169051 kB, budget 176000; text on screen 316 ms). The sheet e2e cases pass locally, and CI is running.

**Phone check and screenshots** On the phone (feat/13-6d-tsk@2c016172f, then c9a637f14): - Tapping John 11:1's number opens its sheet: TSK cross-references with BSB text, and "Notes: none in this verse" greyed. PSS went from 63078 to 66342 kB (+3.3 MB) with TSK opened and the sheet shown. - "Show all 21" lists them all and grows the sheet. Tapping John 11:45 jumps there, and Back returns to John 11 at verse 1, where the sheet was opened. - The copy button is now an icon at the title's right (owner remark: "Copy verse" was below the fold). It's in view without scrolling, and a long press leaves the sheet open. The phone tools are too slow (about 15 s per step) to catch the 3 s tooltip, so the new e2e case `verse-copy` checks it: a long press shows "Copy this verse" and copies nothing, and a tap copies Genesis 1:1. - The journal showed `VerseSheet: Binding loop detected for property "height"` at start-up, already on 2c01617. Fixed in 15674b3: BottomSheet keeps its fitted height from the layouts' change handlers. The e2e app logs of verse-sheet, verse-sheet-no-tsk, verse-copy and note-sheet now have no binding loop. | Phone (Suru) | Long press (Xvfb) | |---|---| | ![](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/phone-verse-sheet.png) | ![](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/copy-tooltip.png) | | Without TSK (Xvfb) | Show all (Xvfb) | |---|---| | ![](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/no-tsk.png) | ![](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/show-all.png) | **Review 340, low 2** (`sword::module::list` is parsed on each verse tap): kept. It reads the small `mods.d` confs once per tap, after a user action, and it is what lets a removed or updated TSK be noticed without a restart. Low 1 was fixed in 2c01617. Gates on 15674b3: app-docker passed (peak PSS 169051 kB, budget 176000; text on screen 316 ms). The sheet e2e cases pass locally, and CI is running.
claude-review left a comment

Delta review (medium)

Reviewed commit: 15674b3754
Range: 2c016172ff935f1e101cd4a44c8e91f9cf32e77f..HEAD

Files reviewed: qml/controls/BottomSheet.qml, qml/pages/VerseSheet.qml, qml/qml.qrc, tools/e2e/cases/verse-copy.sh, core/src/bible/verse_sheet.rs (doc comment), qml/theme/Theme.qml (where helper, for context), tools/e2e/lib.sh (helpers, for context), NOTICES.md, po/de.po, po/utstudybible.schaefera.pot, assets/icons/copy.svg, qml/icons/copy.png (not inspected in detail).

Findings: none with a concrete failure scenario. The BottomSheet fit property, updated from the layouts' implicitHeight change handlers, removes the binding loop. The e2e log patterns do not collide: 'verse copy ', 'verse copy tip ' and 'verse copied ' are read in a safe order. The long press does not emit clicked, so it does not copy.

Delta review (medium) Reviewed commit: 15674b3754fa68f44904b1b3e02c6f2bc6c53c56 Range: 2c016172ff935f1e101cd4a44c8e91f9cf32e77f..HEAD Files reviewed: qml/controls/BottomSheet.qml, qml/pages/VerseSheet.qml, qml/qml.qrc, tools/e2e/cases/verse-copy.sh, core/src/bible/verse_sheet.rs (doc comment), qml/theme/Theme.qml (where helper, for context), tools/e2e/lib.sh (helpers, for context), NOTICES.md, po/de.po, po/utstudybible.schaefera.pot, assets/icons/copy.svg, qml/icons/copy.png (not inspected in detail). Findings: none with a concrete failure scenario. The BottomSheet `fit` property, updated from the layouts' implicitHeight change handlers, removes the binding loop. The e2e log patterns do not collide: 'verse copy ', 'verse copy tip ' and 'verse copied ' are read in a safe order. The long press does not emit clicked, so it does not copy.
ui: a tap on a verse's text opens its sheet, which shows the verse (#13, 6d)
Some checks failed
CI / changes (pull_request) Successful in 11s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
7fb9a930ec
The small number could be missed or turned off; now the whole text opens the sheet (note markers and running text's inline numbers stay links). The sheet shows the verse under its title, as the dimmed page hides it. e2e case verse-text-tap, both modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-review left a comment

Level: medium (delta review). Commit 7fb9a930. Range 15674b3..7fb9a93.
Files: core/src/bible/reader/verse.rs, core/src/bible/verse_sheet.rs, qml/pages/ReadingPane.qml, qml/pages/VerseSheet.qml, qt/src/reading_model.rs, tools/e2e/cases/verse-text-tap.sh.

No high or medium findings. One low finding is inline. Checked: link vs. text routing, MouseArea z-order against the number area, Flickable interaction, verse_at bounds, JSON escaping of text.

Level: medium (delta review). Commit 7fb9a930. Range 15674b3..7fb9a93. Files: core/src/bible/reader/verse.rs, core/src/bible/verse_sheet.rs, qml/pages/ReadingPane.qml, qml/pages/VerseSheet.qml, qt/src/reading_model.rs, tools/e2e/cases/verse-text-tap.sh. No high or medium findings. One low finding is inline. Checked: link vs. text routing, MouseArea z-order against the number area, Flickable interaction, verse_at bounds, JSON escaping of text.
@ -503,0 +560,4 @@
if (link !== "")
pane.noteTapped(link)
else
pane.textTapped(reading.verseAt(model.index, parent.positionAt(mouse.x, mouse.y)))
Collaborator

low · running text passes the raw positionAt(x, y) to verseAt, but the existing posAt helper (line ~447) uses positionAt(...) - 1 to get the core's plain-text positions. If that offset applies here too, a tap on the first character of a verse can resolve to the previous verse (and the last character of a verse to the next one). The effect is limited to one character at verse boundaries, and the e2e case taps mid-page so it would not catch it. Consider reusing the same -1 (or a shared helper) for consistency.

low · running text passes the raw `positionAt(x, y)` to `verseAt`, but the existing `posAt` helper (line ~447) uses `positionAt(...) - 1` to get the core's plain-text positions. If that offset applies here too, a tap on the first character of a verse can resolve to the previous verse (and the last character of a verse to the next one). The effect is limited to one character at verse boundaries, and the e2e case taps mid-page so it would not catch it. Consider reusing the same `-1` (or a shared helper) for consistency.
ui: a running-text tap opens the verse of the character under it (#13, 6d review 343)
Some checks failed
CI / changes (pull_request) Successful in 10s
CI / rust (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / app (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
CI / click (pull_request) Has been cancelled
22a91cddc8
positionAt gives the nearest cursor place: on the right half of a verse's last character, the next verse's start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Review 343 had one low finding: in running text, the raw positionAt went to verseAt. It is fixed in 22a91cd. charAt(x, y) now steps back one place when the cursor place it gets lies to the right of the tap. That happens on a character's right half, so a tap on a verse's last character no longer opens the next verse. A verse's first character is its inline number, which is a link, so it goes through linkAt either way. The cases verse-text-tap, note-sheet and running-text pass.

I also checked it on the phone at 7fb9a93. A tap on John 11:4's text opens its sheet with the verse under the title. A swipe over the text scrolls without opening a sheet.

phone: text tap sheet

Review 343 had one low finding: in running text, the raw `positionAt` went to `verseAt`. It is fixed in 22a91cd. `charAt(x, y)` now steps back one place when the cursor place it gets lies to the right of the tap. That happens on a character's right half, so a tap on a verse's last character no longer opens the next verse. A verse's first character is its inline number, which is a link, so it goes through `linkAt` either way. The cases verse-text-tap, note-sheet and running-text pass. I also checked it on the phone at 7fb9a93. A tap on John 11:4's text opens its sheet with the verse under the title. A swipe over the text scrolls without opening a sheet. ![phone: text tap sheet](https://code.austinschaefer.com/schaefera/utStudyBIble/wiki/raw/images/pr-130/text-tap-sheet.png)
claude-review left a comment

Level: medium (trunk, standard). Commit 22a91cddc8. Range 7fb9a930..22a91cdd. Files: qml/pages/ReadingPane.qml (read with qt/src/reading_model.rs verse_at and core/src/bible/block.rs ordinal_at).

The fix is correct for the reported case: a tap on the right half of a character on the same line. It is also correct for the first character of a line (left half gives rect.x <= x, so p stays), for a wrapped line whose last visible character is followed by a hanging space (the offset after it is still on that line), and for LTR text. RTL would invert the rect.x comparison, but the modules are LTR, so it is not a defect here. parent.charAt resolves, because the MouseArea's parent is the TextEdit that defines it.

One low-severity finding is on the diff line. Nothing is outside the diff. I did not run it on a device.

Level: medium (trunk, standard). Commit 22a91cddc8fd248d3dfed30a01f61ea20566beb6. Range 7fb9a930..22a91cdd. Files: qml/pages/ReadingPane.qml (read with qt/src/reading_model.rs verse_at and core/src/bible/block.rs ordinal_at). The fix is correct for the reported case: a tap on the right half of a character on the same line. It is also correct for the first character of a line (left half gives rect.x <= x, so p stays), for a wrapped line whose last visible character is followed by a hanging space (the offset after it is still on that line), and for LTR text. RTL would invert the rect.x comparison, but the modules are LTR, so it is not a defect here. `parent.charAt` resolves, because the MouseArea's parent is the TextEdit that defines it. One low-severity finding is on the diff line. Nothing is outside the diff. I did not run it on a device.
@ -503,0 +557,4 @@
// verse's last character).
function charAt(x, y) {
const p = positionAt(x, y)
return p > 0 && positionToRectangle(p).x > x ? p - 1 : p
Collaborator

low · A tap in the ragged-right gap past the end of a wrapped line. positionAt returns the line's end offset, which is the first offset of the next line. positionToRectangle(p).x is then the next line's left edge, which is not greater than x, so charAt returns p, the next line's first character. If a verse starts at that line start (a verse ending with its trailing space at the wrap), the tap opens the next verse. This is the same class of bug the commit fixes. The gap is much wider than a character's right half. Comparing the line as well fixes it: r = positionToRectangle(p); return p > 0 && (r.y > y || r.x > x) ? p - 1 : p. I did not verify this on a device.

low · A tap in the ragged-right gap past the end of a wrapped line. positionAt returns the line's end offset, which is the first offset of the next line. positionToRectangle(p).x is then the next line's left edge, which is not greater than x, so charAt returns p, the next line's first character. If a verse starts at that line start (a verse ending with its trailing space at the wrap), the tap opens the next verse. This is the same class of bug the commit fixes. The gap is much wider than a character's right half. Comparing the line as well fixes it: `r = positionToRectangle(p); return p > 0 && (r.y > y || r.x > x) ? p - 1 : p`. I did not verify this on a device.
ui: a tap past a wrapped line's end opens that line's verse (#13, 6d review 344)
All checks were successful
CI / changes (pull_request) Successful in 9s
CI / rust (pull_request) Successful in 9m11s
CI / audit (pull_request) Successful in 16s
CI / app (pull_request) Successful in 4m40s
CI / e2e (pull_request) Successful in 11m6s
CI / click (pull_request) Successful in 3m43s
02686781ea
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Review 344 had one low finding: a tap in the gap past the end of a wrapped line. positionAt returns the next line's start there, so the tap could open the next verse. Fixed in 0268678, following the review's suggestion. charAt now also steps back when the cursor rectangle is on a lower line than the tap (r.y > y). The cases verse-text-tap, note-sheet and running-text pass. This was the delta round, so no further review round follows; the fix goes to the audit together with its finding.

Review 344 had one low finding: a tap in the gap past the end of a wrapped line. `positionAt` returns the next line's start there, so the tap could open the next verse. Fixed in 0268678, following the review's suggestion. `charAt` now also steps back when the cursor rectangle is on a lower line than the tap (`r.y > y`). The cases verse-text-tap, note-sheet and running-text pass. This was the delta round, so no further review round follows; the fix goes to the audit together with its finding.
claude-bot changed title from WIP: Verse sheet with TSK cross-references, Install TSK when missing (#13, 6d) to Verse sheet with TSK cross-references, Install TSK when missing (#13, 6d) 2026-10-30 09:14:00 +00:00
claude-review left a comment

Merge gate (medium), head 0268678.

  • Reviews 339-344: no high or medium findings in any. Lows fixed (339 low 1, 343, 344) or soundly declined (340 low 2, 339 lows 2-3 with reasons in comments 2551/2561).
  • Commits after the last review round (22a91cd..0268678): one, a 2-line change to charAt in ReadingPane.qml that steps back one place when the cursor rectangle is on a lower line than the tap. Correct: a same-line tap has r.y <= y; the first-character case is unchanged. Nothing else in the range.
  • CI on head: changes, rust, audit, app, e2e and click all success.
  • PR body Proof: standard tier plus the CI e2e TSK fetch; no unrun elevated, maximum or AI-behavior gate. e2e verse-sheet cases ran in CI e2e green.
Merge gate (medium), head 0268678. - Reviews 339-344: no high or medium findings in any. Lows fixed (339 low 1, 343, 344) or soundly declined (340 low 2, 339 lows 2-3 with reasons in comments 2551/2561). - Commits after the last review round (22a91cd..0268678): one, a 2-line change to charAt in ReadingPane.qml that steps back one place when the cursor rectangle is on a lower line than the tap. Correct: a same-line tap has r.y <= y; the first-character case is unchanged. Nothing else in the range. - CI on head: changes, rust, audit, app, e2e and click all success. - PR body Proof: standard tier plus the CI e2e TSK fetch; no unrun elevated, maximum or AI-behavior gate. e2e verse-sheet cases ran in CI e2e green.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 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!130
No description provided.