First run: starter-set picker and background installs (#10 part 2a) #71

Merged
claude-bot merged 4 commits from phase1/first-run into main 2026-10-23 10:42:20 +00:00
Collaborator

What

With no Bible installed the app shows a starter page: language choice, the pre-ticked set from presets/ (description, licence, size per module), one Download tap, progress per module and overall, errors with Retry.
Installs run in the task runner off the GUI thread; a kill leaves a half-done set that resumes on the next start; the first Bible opens without a restart.
Modules come from CrossWire over HTTPS. Add-on sets are part 2b; preset descriptions are English data and not translated.
#10 (F1) part 2a.

Blast radius

Tier: trunk
Trunk paths touched: core/src/db.rs, core/src/tasks*, core/src/worker*, qt/ (installer.rs, reading_model.rs, build.rs, lib.rs), qml/Main.qml, qml/pages/ReadingPane.qml, tools/e2e/. Leaf parts: StarterPage.qml, po/, core/src/starter*, core/src/presets.rs.
Testing tier: standard (feature, plus test tooling). Downloads write module files outside the process, but only into the app's own data dir (sword/, tasks/), and the e2e cases install from a local repo and read the installed Bible back, so this is not raised to elevated.
Gate / rollback: no flag; revert the merge commit. No migration. The starter page appears only when no Bible is installed; the task runner starts on every launch (its cost is under docs/07 below). A started install resumes from its .part files. db.rs change: the app database is opened one at a time from the start (fixes a first-run "database is locked" race found by starter-fail; has a test). UTSB_TEST_REPO / UTSB_TEST_RATE exist only for the e2e cases (tools/e2e/lib.sh) and are honoured in development builds only.

Proof

  • CI on 7e757bd8e7: 6 of 6 green (rust, audit, app, e2e, click, preset-check).
  • cargo make (fmt, clippy, tests+coverage, release check, deny, audit): exit 0, after the review fixes.
  • cargo make app-docker: exit 0. PSS peak 151940 kB normal / 151153 kB slow (budget 165000).
  • cargo make e2e-docker, KJV only and full cache: 14 passed, 0 failed; slow profile 4 passed. New cases starter-install, starter-kill (kill -9 mid-download; after restart the first request is a Range bytes=557056-1081343), starter-fail (404 shows "the server answered HTTP 404", Retry installs) passed twice in different orders. No case uses the network; tools/e2e/repo-server.py serves packages built from SWORD_TEST_PATH on 127.0.0.1.
  • cargo make i18n: po/ refreshed, 60 translated messages. cargo make click: builds, click-review passes.
  • Fails without the change, Rust (scratch copy, code broken in turn):
    • NoBible arm off: worker::tests::a_bible_installed_after_no_bible_is_found_by_the_next_open, errors_without_a_bible, the_last_bible_removed_is_no_bible_again FAILED.
    • Retrying not emitted: a_server_error_is_reported_as_a_retry_and_the_download_still_finishes FAILED.
    • Restored not emitted: a_start_with_unfinished_installs_says_which_before_it_goes_on FAILED.
    • Board takes no event: 4 of 6 tasks::progress::tests FAILED.
    • Bibles not first: starter::tests::a_set_starts_with_its_default_ticks_and_the_bibles_come_first FAILED.
    • Old lock position in Db::try_open: db::tests::concurrent_opens_of_a_new_file_all_succeed FAILED with database is locked.
  • Fails without the change, e2e (scratch copy in the CI image): no starter page pushed: starter-install: FAIL: no 'utsb: starter download' (x1) after 30 s; runner not started with the app: starter-kill: FAIL: no 'utsb: showing' (x1) after 60 s; Retry does nothing: starter-fail: FAIL: no 'utsb: install KJV installed' (x1) after 60 s.
  • Real bug found by the cases: on a first run the reading worker and the task runner create app.sqlite3 together and the loser failed with database is locked (WAL switch ignores busy_timeout). Fixed by taking the existing in-process lock before the WAL switch; starter-fail then passed 8 of 8 repeated runs.
  • Review-245 fixes have tests (an_error_of_the_queue_stays_until_an_install_moves_again, a_row_says_the_bytes_or_why_it_stopped, a_failure_is_said_in_a_sentence_with_the_detail_in_it, the_test_variables_point_the_app_at_a_local_repository_and_slow_it), but they use new APIs, so they failed to compile before the change rather than failing against the old code.
  • The starter pushed over the open picker when the last Bible goes (Main.qml) has no failing-before proof and no e2e case.

Effect on the docs/07 budgets (measured, amd64 CI image, Xvfb, software GL)

  • Click size: 2,093,638 B (origin/main) to 3,652,908 B (+1.56 MB: HTTPS client, zip); budget 15 MB.
  • PSS: mem-check peak 150663 / 146873 kB (main, normal/slow) vs 151940 / 151153 kB (here); budget 165000. Nothing installed: 153.9 MB idle on the starter page; peak 159.2 MB while KJV downloads throttled to 1 MB/s; 164.4 MB up to 2 s after the first chapter opens (little headroom: a peak over download, unpack and first reading).
  • Start-up: reading start unchanged (318 ms vs 316 ms, budget 3000). Nothing installed: starter page ready 0.67-0.73 s after launch. The task runner thread (with its own SQLite connection) starts on every launch and is idle when there is nothing to do.
  • The numbers come from a one-off script, not committed; mem-check is unchanged.

Phone

PR build 0.0.1 phase1/first-run@7e757bd8e installed on the Volla/Plinius that already has modules: window loaded after 283-351 ms, restored KJV 7421 0.382, showing Ruth 3, and saved KJV 7421 0.382 on suspend. With modules installed the app starts and restores as before and the starter page does not appear.

The journal has QObject::startTimer: Timers cannot be started from another thread, once per launch. Not from this PR: a backtrace on the phone (temporary handler, not committed) puts it in UT's Mir platform plugin (libqpa-ubuntumirclient.so, QTimer::start from a libmir1client thread), and main's build on the same phone logs it too (PR comment 1740). Desktop e2e cannot see it (Xvfb has no Mir).

The first-run screen itself is not tried on the phone (modules present). It goes into the post-merge phone checklist on #10, and per docs/09 "On the phone" the owner tries it:

  1. Fresh install (or clear app data): the starter page shows, in your language if a preset exists; ticks, sizes and licences look right; Download is disabled with no Bible ticked.
  2. Download on mobile data/Wi-Fi: per-module and overall progress move; the first Bible opens as soon as it is installed while the lexicons carry on (strip at the bottom).
  3. Force-stop the app mid-download and reopen: it carries on by itself.
  4. Switch the network off mid-download: "trying again", then back on; also airplane mode from the start: an error and Retry.
  5. Memory feel on the old phone during download+unzip (peak PSS on the device).
  6. With the picker open, remove the last Bible: the starter page shows over it.

Review

Reviewer: fresh-context code-review at high (review 245 on dff31c6): 6 findings (no high or medium, 6 low), 5 fixed in 7e757bd, 0 declined, 0 deferred, 1 answered in this body, 0 left open. Delta reviews: 246 (medium, dff31c6..7e757bd) no findings; 247 (delta after the phone check) empty range, no findings.
Review-Confidence: 85/100, the only unproven change is the starter-over-the-picker behaviour (no e2e case, no failing-before check); everything else has fails-without proof and CI is green.

Review 245 findings and answers (author reply: PR comment 1737; delta comment 1739):

  1. An old queue error stayed after an install moved again: fixed (cleared when an install moves), with a test.
  2. Runner error texts were not translated: fixed (translated, detail kept in the sentence), with a test.
  3. Starter page did not show over the picker when the last Bible goes: fixed in Main.qml; no failing-before proof, no e2e case.
  4. Test repo/rate variables honoured in a release: fixed (development builds only), with a test.
  5. Missing null guard on the strip: fixed.
  6. Effect on docs/07 not stated: answered by the section above (counts as refuted).

Confidence by part

  • Core logic (Starter, Board, NoBible, events): high, unit tests fail when broken.
  • Db lock change: medium, one test, in-process only (the app is one process).
  • Installer QObject and StarterPage.qml: medium, covered by e2e at taps, not by a phone.
  • tools/e2e/repo-server.py: medium; python (tools/ are otherwise POSIX sh), because the CI image has no nc/socat for a Range-capable server.
  • Review-245 fix tests: medium, they use new APIs and failed to compile before rather than failing against old code.
  • Starter pushed over the picker when the last Bible goes: low, no case and no failing-before proof; read this hunk in qml/Main.qml.

🤖 Generated with Claude Code

## What With no Bible installed the app shows a starter page: language choice, the pre-ticked set from presets/ (description, licence, size per module), one Download tap, progress per module and overall, errors with Retry. Installs run in the task runner off the GUI thread; a kill leaves a half-done set that resumes on the next start; the first Bible opens without a restart. Modules come from CrossWire over HTTPS. Add-on sets are part 2b; preset descriptions are English data and not translated. #10 (F1) part 2a. ## Blast radius **Tier:** trunk **Trunk paths touched:** core/src/db.rs, core/src/tasks*, core/src/worker*, qt/ (installer.rs, reading_model.rs, build.rs, lib.rs), qml/Main.qml, qml/pages/ReadingPane.qml, tools/e2e/. Leaf parts: StarterPage.qml, po/, core/src/starter*, core/src/presets.rs. **Testing tier:** standard (feature, plus test tooling). Downloads write module files outside the process, but only into the app's own data dir (sword/, tasks/), and the e2e cases install from a local repo and read the installed Bible back, so this is not raised to elevated. **Gate / rollback:** no flag; revert the merge commit. No migration. The starter page appears only when no Bible is installed; the task runner starts on every launch (its cost is under docs/07 below). A started install resumes from its `.part` files. db.rs change: the app database is opened one at a time from the start (fixes a first-run "database is locked" race found by starter-fail; has a test). UTSB_TEST_REPO / UTSB_TEST_RATE exist only for the e2e cases (tools/e2e/lib.sh) and are honoured in development builds only. ## Proof - CI on 7e757bd8e739192a05109039f9dba805b459fa2b: 6 of 6 green (rust, audit, app, e2e, click, preset-check). - `cargo make` (fmt, clippy, tests+coverage, release check, deny, audit): exit 0, after the review fixes. - `cargo make app-docker`: exit 0. PSS peak 151940 kB normal / 151153 kB slow (budget 165000). - `cargo make e2e-docker`, KJV only and full cache: 14 passed, 0 failed; slow profile 4 passed. New cases starter-install, starter-kill (kill -9 mid-download; after restart the first request is a Range `bytes=557056-1081343`), starter-fail (404 shows "the server answered HTTP 404", Retry installs) passed twice in different orders. No case uses the network; tools/e2e/repo-server.py serves packages built from SWORD_TEST_PATH on 127.0.0.1. - `cargo make i18n`: po/ refreshed, 60 translated messages. `cargo make click`: builds, click-review passes. - Fails without the change, Rust (scratch copy, code broken in turn): - NoBible arm off: `worker::tests::a_bible_installed_after_no_bible_is_found_by_the_next_open`, `errors_without_a_bible`, `the_last_bible_removed_is_no_bible_again` FAILED. - Retrying not emitted: `a_server_error_is_reported_as_a_retry_and_the_download_still_finishes` FAILED. - Restored not emitted: `a_start_with_unfinished_installs_says_which_before_it_goes_on` FAILED. - Board takes no event: 4 of 6 `tasks::progress::tests` FAILED. - Bibles not first: `starter::tests::a_set_starts_with_its_default_ticks_and_the_bibles_come_first` FAILED. - Old lock position in Db::try_open: `db::tests::concurrent_opens_of_a_new_file_all_succeed` FAILED with `database is locked`. - Fails without the change, e2e (scratch copy in the CI image): no starter page pushed: `starter-install: FAIL: no 'utsb: starter download' (x1) after 30 s`; runner not started with the app: `starter-kill: FAIL: no 'utsb: showing' (x1) after 60 s`; Retry does nothing: `starter-fail: FAIL: no 'utsb: install KJV installed' (x1) after 60 s`. - Real bug found by the cases: on a first run the reading worker and the task runner create app.sqlite3 together and the loser failed with `database is locked` (WAL switch ignores busy_timeout). Fixed by taking the existing in-process lock before the WAL switch; starter-fail then passed 8 of 8 repeated runs. - Review-245 fixes have tests (`an_error_of_the_queue_stays_until_an_install_moves_again`, `a_row_says_the_bytes_or_why_it_stopped`, `a_failure_is_said_in_a_sentence_with_the_detail_in_it`, `the_test_variables_point_the_app_at_a_local_repository_and_slow_it`), but they use new APIs, so they failed to compile before the change rather than failing against the old code. - The starter pushed over the open picker when the last Bible goes (Main.qml) has no failing-before proof and no e2e case. ## Effect on the docs/07 budgets (measured, amd64 CI image, Xvfb, software GL) - Click size: 2,093,638 B (origin/main) to 3,652,908 B (+1.56 MB: HTTPS client, zip); budget 15 MB. - PSS: mem-check peak 150663 / 146873 kB (main, normal/slow) vs 151940 / 151153 kB (here); budget 165000. Nothing installed: 153.9 MB idle on the starter page; peak 159.2 MB while KJV downloads throttled to 1 MB/s; 164.4 MB up to 2 s after the first chapter opens (little headroom: a peak over download, unpack and first reading). - Start-up: reading start unchanged (318 ms vs 316 ms, budget 3000). Nothing installed: starter page ready 0.67-0.73 s after launch. The task runner thread (with its own SQLite connection) starts on every launch and is idle when there is nothing to do. - The numbers come from a one-off script, not committed; mem-check is unchanged. ## Phone PR build `0.0.1 phase1/first-run@7e757bd8e` installed on the Volla/Plinius that already has modules: `window loaded after 283-351 ms`, `restored KJV 7421 0.382`, `showing Ruth 3`, and `saved KJV 7421 0.382` on suspend. With modules installed the app starts and restores as before and the starter page does not appear. The journal has `QObject::startTimer: Timers cannot be started from another thread`, once per launch. Not from this PR: a backtrace on the phone (temporary handler, not committed) puts it in UT's Mir platform plugin (`libqpa-ubuntumirclient.so`, `QTimer::start` from a `libmir1client` thread), and main's build on the same phone logs it too (PR comment 1740). Desktop e2e cannot see it (Xvfb has no Mir). The first-run screen itself is not tried on the phone (modules present). It goes into the post-merge phone checklist on #10, and per docs/09 "On the phone" the owner tries it: 1. Fresh install (or clear app data): the starter page shows, in your language if a preset exists; ticks, sizes and licences look right; Download is disabled with no Bible ticked. 2. Download on mobile data/Wi-Fi: per-module and overall progress move; the first Bible opens as soon as it is installed while the lexicons carry on (strip at the bottom). 3. Force-stop the app mid-download and reopen: it carries on by itself. 4. Switch the network off mid-download: "trying again", then back on; also airplane mode from the start: an error and Retry. 5. Memory feel on the old phone during download+unzip (peak PSS on the device). 6. With the picker open, remove the last Bible: the starter page shows over it. ## Review **Reviewer:** fresh-context `code-review` at high (review 245 on dff31c6): 6 findings (no high or medium, 6 low), 5 fixed in 7e757bd, 0 declined, 0 deferred, 1 answered in this body, 0 left open. Delta reviews: 246 (medium, dff31c6..7e757bd) no findings; 247 (delta after the phone check) empty range, no findings. **Review-Confidence:** 85/100, the only unproven change is the starter-over-the-picker behaviour (no e2e case, no failing-before check); everything else has fails-without proof and CI is green. Review 245 findings and answers (author reply: PR comment 1737; delta comment 1739): 1. An old queue error stayed after an install moved again: fixed (cleared when an install moves), with a test. 2. Runner error texts were not translated: fixed (translated, detail kept in the sentence), with a test. 3. Starter page did not show over the picker when the last Bible goes: fixed in Main.qml; no failing-before proof, no e2e case. 4. Test repo/rate variables honoured in a release: fixed (development builds only), with a test. 5. Missing null guard on the strip: fixed. 6. Effect on docs/07 not stated: answered by the section above (counts as refuted). ## Confidence by part - Core logic (Starter, Board, NoBible, events): high, unit tests fail when broken. - Db lock change: medium, one test, in-process only (the app is one process). - Installer QObject and StarterPage.qml: medium, covered by e2e at taps, not by a phone. - tools/e2e/repo-server.py: medium; python (tools/ are otherwise POSIX sh), because the CI image has no nc/socat for a Range-capable server. - Review-245 fix tests: medium, they use new APIs and failed to compile before rather than failing against old code. - Starter pushed over the picker when the last Bible goes: low, no case and no failing-before proof; read this hunk in qml/Main.qml. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The starter set's choices (Starter), the install board (Board) and the
tasks' Restored/Retrying events are pure logic; the worker says NoBible
for an empty Bible folder instead of an error. UTSB_TEST_REPO and
UTSB_TEST_RATE point the task runner at a local repository for e2e.

Db::try_open now takes the one-at-a-time lock before switching a new file
to WAL: on a first run the reading worker and the task runner both create
it, and the loser failed with 'database is locked' at once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Installer QObject runs the task runner off the GUI thread and reports
its progress; StarterPage.qml is the first run's picker (language, ticked
modules with description, licence and size, progress per module and
overall, errors with Retry); the reading pane opens the first Bible when
it is installed, and shows a strip while other modules install.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
e2e: a local repository and the cases starter-install, starter-kill, starter-fail (#10 part 2a)
All checks were successful
CI / rust (pull_request) Successful in 3m17s
Presets / preset-check (pull_request) Successful in 1m29s
CI / audit (pull_request) Successful in 11s
CI / app (pull_request) Successful in 2m22s
CI / e2e (pull_request) Successful in 4m3s
CI / click (pull_request) Successful in 1m15s
dff31c6554
tools/e2e/repo-server.py serves packages built from SWORD_TEST_PATH over
127.0.0.1 with Range and ETag; the app is pointed at it through
UTSB_TEST_REPO (documented in lib.sh). No case uses the network.

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

Level: HIGH, single pass. Commit dff31c6, range origin/main...HEAD (3 commits). Blast radius trunk.

Files reviewed: core/src/db.rs, presets.rs, starter.rs (+tests), tasks.rs, tasks/exec.rs, tasks/progress.rs, tasks/tests.rs, worker.rs, worker/session.rs, qt/build.rs, qt/src/installer.rs, qt/src/lib.rs, qt/src/reading_model.rs, qml/Main.qml, qml/pages/ReadingPane.qml, qml/pages/StarterPage.qml, tools/e2e/lib.sh, repo-server.py, cases/starter-{install,kill,fail}.sh. Followed the install path through tasks/exec.rs and db/tasks.rs (enqueue, requeue, fail, clean).

No high or medium findings. Checked and found sound: kill mid-download (running rows requeued, Restored event, .part/.state kept on Stopped and cleaned only on Done/Failed/Cancelled, package::installed short-circuit); retry (enqueue deletes the old failed row); NoBible re-checked by each Open so a newly installed Bible shows without restart; DB first-run open serialised by MIGRATING taken before open/WAL.

Findings outside the diff / low:

  • low · qt/src/installer.rs:326 · Event::Error sets error and nothing clears it; it only shows when started, so a transient "tasks: ..." error stays on the page for the session after a later successful install.
  • low · qt/src/installer.rs:176, runner error strings · "HOME is not set" and core error texts (e.g. "HTTP 404") are shown to the user untranslated, unlike the rest of the page.
  • low · qml/Main.qml:71 · onNoBibleChanged only shows the starter when stack.depth === 1; if the picker is open when the last Bible goes, the starter is not pushed (the pane shows its "Choose Bibles" button, so recoverable).
  • low · core/src/tasks.rs:50 · UTSB_TEST_REPO / UTSB_TEST_RATE are honoured in release builds too (env only, no setting); acceptable, but a release build could ignore them behind cfg or UTSB_RELEASE to keep the install source fixed.
  • low · qml/pages/ReadingPane.qml:147 · the strip's text binding dereferences pane.installer without the null guard visible has (installer is always set by Main, so no current failure).
  • low · qml/Main.qml:39 · the runner (thread plus second SQLite connection) now starts on every launch, not only first runs; docs/07 start-up/memory effect is not stated in the diff; the PR description should say it.
Level: HIGH, single pass. Commit dff31c6, range origin/main...HEAD (3 commits). Blast radius trunk. Files reviewed: core/src/db.rs, presets.rs, starter.rs (+tests), tasks.rs, tasks/exec.rs, tasks/progress.rs, tasks/tests.rs, worker.rs, worker/session.rs, qt/build.rs, qt/src/installer.rs, qt/src/lib.rs, qt/src/reading_model.rs, qml/Main.qml, qml/pages/ReadingPane.qml, qml/pages/StarterPage.qml, tools/e2e/lib.sh, repo-server.py, cases/starter-{install,kill,fail}.sh. Followed the install path through tasks/exec.rs and db/tasks.rs (enqueue, requeue, fail, clean). No high or medium findings. Checked and found sound: kill mid-download (running rows requeued, Restored event, .part/.state kept on Stopped and cleaned only on Done/Failed/Cancelled, package::installed short-circuit); retry (enqueue deletes the old failed row); NoBible re-checked by each Open so a newly installed Bible shows without restart; DB first-run open serialised by MIGRATING taken before open/WAL. Findings outside the diff / low: - low · qt/src/installer.rs:326 · Event::Error sets `error` and nothing clears it; it only shows when `started`, so a transient "tasks: ..." error stays on the page for the session after a later successful install. - low · qt/src/installer.rs:176, runner error strings · "HOME is not set" and core error texts (e.g. "HTTP 404") are shown to the user untranslated, unlike the rest of the page. - low · qml/Main.qml:71 · onNoBibleChanged only shows the starter when stack.depth === 1; if the picker is open when the last Bible goes, the starter is not pushed (the pane shows its "Choose Bibles" button, so recoverable). - low · core/src/tasks.rs:50 · UTSB_TEST_REPO / UTSB_TEST_RATE are honoured in release builds too (env only, no setting); acceptable, but a release build could ignore them behind cfg or UTSB_RELEASE to keep the install source fixed. - low · qml/pages/ReadingPane.qml:147 · the strip's text binding dereferences pane.installer without the null guard `visible` has (installer is always set by Main, so no current failure). - low · qml/Main.qml:39 · the runner (thread plus second SQLite connection) now starts on every launch, not only first runs; docs/07 start-up/memory effect is not stated in the diff; the PR description should say it.
Review 245: translated runner errors, an old error goes when an install moves, starter over the picker, test variables only in development builds, null guard on the strip (#10 part 2a)
All checks were successful
CI / rust (pull_request) Successful in 4m38s
Presets / preset-check (pull_request) Successful in 2m9s
CI / audit (pull_request) Successful in 12s
CI / app (pull_request) Successful in 2m41s
CI / e2e (pull_request) Successful in 4m11s
CI / click (pull_request) Successful in 1m39s
7e757bd8e7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Answer to review 245 (all low), fixed in 7e757bd:

  1. installer.rs:326, error never cleared: fixed. The error now lives in Board; an install that starts, progresses or finishes, or a new Download/Retry, clears it. Test: tasks::progress::tests::an_error_of_the_queue_stays_until_an_install_moves_again.
  2. Untranslated texts: fixed. "HOME is not set", the queue errors and the install failures ("the server answered HTTP %1", "no connection: %1", "unexpected answer from the server: %1", "not a usable module: %1", "trying again: %1") go through tr(), with German in po/. The detail after the colon comes from the server/system and stays as said. Tests: exec::tests::a_failure_is_said_in_a_sentence_with_the_detail_in_it, a_row_says_the_bytes_or_why_it_stopped. e2e starter-fail now sees "the server answered HTTP 404".
  3. Main.qml:71, starter not pushed over the picker: fixed. When the last Bible goes the stack pops to the reading pane and the starter is pushed unless it is already the current page. Not covered by an e2e case (no case removes the last Bible while the picker is open); checked by reading and the unchanged starter-* cases passing.
  4. Test variables in release builds: fixed. Config::from_env(paths, dev) applies them only in a development build (utsb_stamp::SOURCE.is_some()). Test: the_test_variables_point_the_app_at_a_local_repository_and_slow_it now also checks that a release ignores them.
  5. ReadingPane.qml:147 null guard: fixed (text and value bindings guarded like visible). Cosmetic, no test.
  6. docs/07 effect of the runner on every launch: already stated in the PR body ("The task runner thread now starts with the app and opens the database"); the numbers (reading start 318 vs 316 ms, PSS peaks) are in the budget section, and mem-check after these fixes: 151.9 / 151.2 MB peak PSS (budget 165).
Answer to review 245 (all low), fixed in 7e757bd: 1. installer.rs:326, error never cleared: fixed. The error now lives in `Board`; an install that starts, progresses or finishes, or a new Download/Retry, clears it. Test: `tasks::progress::tests::an_error_of_the_queue_stays_until_an_install_moves_again`. 2. Untranslated texts: fixed. "HOME is not set", the queue errors and the install failures ("the server answered HTTP %1", "no connection: %1", "unexpected answer from the server: %1", "not a usable module: %1", "trying again: %1") go through `tr()`, with German in po/. The detail after the colon comes from the server/system and stays as said. Tests: `exec::tests::a_failure_is_said_in_a_sentence_with_the_detail_in_it`, `a_row_says_the_bytes_or_why_it_stopped`. e2e starter-fail now sees "the server answered HTTP 404". 3. Main.qml:71, starter not pushed over the picker: fixed. When the last Bible goes the stack pops to the reading pane and the starter is pushed unless it is already the current page. Not covered by an e2e case (no case removes the last Bible while the picker is open); checked by reading and the unchanged starter-* cases passing. 4. Test variables in release builds: fixed. `Config::from_env(paths, dev)` applies them only in a development build (`utsb_stamp::SOURCE.is_some()`). Test: `the_test_variables_point_the_app_at_a_local_repository_and_slow_it` now also checks that a release ignores them. 5. ReadingPane.qml:147 null guard: fixed (text and value bindings guarded like `visible`). Cosmetic, no test. 6. docs/07 effect of the runner on every launch: already stated in the PR body ("The task runner thread now starts with the app and opens the database"); the numbers (reading start 318 vs 316 ms, PSS peaks) are in the budget section, and mem-check after these fixes: 151.9 / 151.2 MB peak PSS (budget 165).
claude-review left a comment

Re-review of the fixes to review 245. Level MEDIUM, commit 7e757bd, range dff31c6..7e757bd.

High/medium findings: none. Low findings: none with a concrete failure scenario.

Checked against review 245:

  1. Error never cleared: Board.error is set by Event::Error and cleared on Started(Install), Progress, Done(Install), add() (Download or Retry). apply() returns true when it clears, so the UI refreshes. OK.
  2. Untranslated texts: tr() with .replace("%1", ..) as the i18n docs describe; describe() covers every Error variant (Cancelled and Io fall back to to_string, which is acceptable); the catalogue is a OnceLock, so calling tr() from the runner thread is safe. po/ has the new msgids. OK.
  3. Starter over the picker: Main.qml pops to the bottom and shows the starter unless it is already current; a null currentItem is handled. OK (no e2e case, as stated).
  4. Test variables in release: Config::from_env(paths, utsb_stamp::SOURCE.is_some()), the same dev signal build_info.rs uses; the test covers both modes. OK.
  5. Null guard on the strip: text and ProgressBar value are guarded. OK.
  6. docs/07 effect: stated in the PR body. OK.

Not run: build, e2e cases.

Files reviewed: core/src/tasks.rs, core/src/tasks/exec.rs, core/src/tasks/progress.rs, core/src/tasks/tests.rs, core/src/i18n.rs (context), qt/src/installer.rs, qml/Main.qml, qml/pages/ReadingPane.qml, tools/e2e/lib.sh, po/ (names only), sword/src/install.rs (Error enum, context).

Re-review of the fixes to review 245. Level MEDIUM, commit 7e757bd, range dff31c6..7e757bd. High/medium findings: none. Low findings: none with a concrete failure scenario. Checked against review 245: 1. Error never cleared: Board.error is set by Event::Error and cleared on Started(Install), Progress, Done(Install), add() (Download or Retry). apply() returns true when it clears, so the UI refreshes. OK. 2. Untranslated texts: tr() with .replace("%1", ..) as the i18n docs describe; describe() covers every Error variant (Cancelled and Io fall back to to_string, which is acceptable); the catalogue is a OnceLock, so calling tr() from the runner thread is safe. po/ has the new msgids. OK. 3. Starter over the picker: Main.qml pops to the bottom and shows the starter unless it is already current; a null currentItem is handled. OK (no e2e case, as stated). 4. Test variables in release: Config::from_env(paths, utsb_stamp::SOURCE.is_some()), the same dev signal build_info.rs uses; the test covers both modes. OK. 5. Null guard on the strip: text and ProgressBar value are guarded. OK. 6. docs/07 effect: stated in the PR body. OK. Not run: build, e2e cases. Files reviewed: core/src/tasks.rs, core/src/tasks/exec.rs, core/src/tasks/progress.rs, core/src/tasks/tests.rs, core/src/i18n.rs (context), qt/src/installer.rs, qml/Main.qml, qml/pages/ReadingPane.qml, tools/e2e/lib.sh, po/ (names only), sword/src/install.rs (Error enum, context).
Author
Collaborator

Thanks for review 246. No high, medium or concrete low findings, so there is no code change: the head stays 7e757bd and CI on it is green in all contexts (rust, audit, app, e2e, click, preset-check). Since you did not build or run anything, I did: the head built with cargo make click, installed on the phone and launched; it starts (322 ms), restores KJV and saves on suspend. The PR body's Phone section has the details, including one QObject::startTimer warning I could not tie to this PR, and says the first-run screen itself still needs a phone try before leaving draft. The PR stays a draft.

Thanks for review 246. No high, medium or concrete low findings, so there is no code change: the head stays 7e757bd and CI on it is green in all contexts (rust, audit, app, e2e, click, preset-check). Since you did not build or run anything, I did: the head built with `cargo make click`, installed on the phone and launched; it starts (322 ms), restores KJV and saves on suspend. The PR body's Phone section has the details, including one `QObject::startTimer` warning I could not tie to this PR, and says the first-run screen itself still needs a phone try before leaving draft. The PR stays a draft.
Author
Collaborator

Phone check of 7e757bd: the build starts, restores KJV and shows Ruth 3, and the starter page stays hidden because modules are installed. The journal also showed QObject::startTimer: Timers cannot be started from another thread, so I traced it.

Cause: it does not come from this PR. A backtrace on the phone (temporary message handler, not committed) puts it in libqpa-ubuntumirclient.so. That is Ubuntu Touch's Qt platform plugin: QTimer::start is called from a libmir1client dispatch thread. origin/main, built the same way and installed on the same phone, logs it on every launch too. I checked 3 of 3 journal windows (without my handler). The PR's build logged it on 4 of 4 launches, 5 of 5 counting the clean reinstall.

Fix: none in our code, and I did not silence the warning. My earlier note that main does not log it was wrong. The e2e harness runs under Xvfb, which has no Mir, so no case can see it (0 hits in the restore and starter-install app logs). I added no case, and the head stays 7e757bd.

The PR body's Phone section is updated with this.

Phone check of 7e757bd: the build starts, restores KJV and shows Ruth 3, and the starter page stays hidden because modules are installed. The journal also showed `QObject::startTimer: Timers cannot be started from another thread`, so I traced it. **Cause:** it does not come from this PR. A backtrace on the phone (temporary message handler, not committed) puts it in `libqpa-ubuntumirclient.so`. That is Ubuntu Touch's Qt platform plugin: `QTimer::start` is called from a `libmir1client` dispatch thread. origin/main, built the same way and installed on the same phone, logs it on every launch too. I checked 3 of 3 journal windows (without my handler). The PR's build logged it on 4 of 4 launches, 5 of 5 counting the clean reinstall. **Fix:** none in our code, and I did not silence the warning. My earlier note that main does not log it was wrong. The e2e harness runs under Xvfb, which has no Mir, so no case can see it (0 hits in the restore and starter-install app logs). I added no case, and the head stays 7e757bd. The PR body's Phone section is updated with this.
claude-review left a comment

Level: medium (delta review, trunk blast radius)
Commit: 7e757bd8e7
Range: 7e757bd8e739192a05109039f9dba805b459fa2b..7e757bd (empty: no code change)
Files: none changed. Checked qt/, qml/ for QTimer, startTimer, qInstallMessageHandler and backtrace: no matches, so the debug handler is not in the tree and no PR code starts a timer.

Findings: none.

The "fix" is a diagnosis with no code change. A QTimer started from a Mir client dispatch thread inside libqpa-ubuntumirclient is outside this repo, and the author reports the same warning on the unmodified main build. Nothing here introduces thread-affinity, blocking-GUI or ordering risk, and no proof depends on a code change. I could not reproduce the phone logs from this environment, so the backtrace and the main-build observation are taken from the author's report.

Level: medium (delta review, trunk blast radius) Commit: 7e757bd8e739192a05109039f9dba805b459fa2b Range: 7e757bd8e739192a05109039f9dba805b459fa2b..7e757bd (empty: no code change) Files: none changed. Checked qt/, qml/ for QTimer, startTimer, qInstallMessageHandler and backtrace: no matches, so the debug handler is not in the tree and no PR code starts a timer. Findings: none. The "fix" is a diagnosis with no code change. A QTimer started from a Mir client dispatch thread inside libqpa-ubuntumirclient is outside this repo, and the author reports the same warning on the unmodified main build. Nothing here introduces thread-affinity, blocking-GUI or ordering risk, and no proof depends on a code change. I could not reproduce the phone logs from this environment, so the backtrace and the main-build observation are taken from the author's report.
claude-bot changed title from WIP: First run: starter-set picker and background installs (#10 part 2a) to First run: starter-set picker and background installs (#10 part 2a) 2026-10-23 10:41:27 +00:00
claude-review left a comment

Merge gate (step 8) on head 7e757bd8e7: APPROVED.

  • Head matches 7e757bd; PR is mergeable; no commit after the last review round (245 on dff31c6, re-review 246 on dff31c6..7e757bd, delta 247 empty range).
  • Findings: reviews 245, 246 and 247 have no high or medium. Of the 6 lows, 5 are fixed in 7e757bd (each checked by review 246) and the 6th (docs/07 effect) is answered in the body.
  • CI on head: 6 of 6 green by the status API (rust, preset-check, audit, app, e2e, click).
  • Proof has no unrun elevated, maximum or AI-behavior gate. Downloads write only into the app's own data dir and the e2e cases read the installed Bible back, so standard tier is a fair call.
  • The audit is in the body (Review-Confidence 85, Review-Tier trunk). Rollback is stated: revert the merge commit, no migration.

Weaker spots the author already states, not blocking: the review-245 fix tests use new APIs and were never run against the old code, and the starter-over-the-picker fix has no failing-before proof or e2e case (it is on the owner's phone checklist). Merge with head pinned and the Review-Confidence and Review-Tier trailers ending the message.

Merge gate (step 8) on head 7e757bd8e739192a05109039f9dba805b459fa2b: APPROVED. - Head matches 7e757bd; PR is mergeable; no commit after the last review round (245 on dff31c6, re-review 246 on dff31c6..7e757bd, delta 247 empty range). - Findings: reviews 245, 246 and 247 have no high or medium. Of the 6 lows, 5 are fixed in 7e757bd (each checked by review 246) and the 6th (docs/07 effect) is answered in the body. - CI on head: 6 of 6 green by the status API (rust, preset-check, audit, app, e2e, click). - Proof has no unrun elevated, maximum or AI-behavior gate. Downloads write only into the app's own data dir and the e2e cases read the installed Bible back, so standard tier is a fair call. - The audit is in the body (Review-Confidence 85, Review-Tier trunk). Rollback is stated: revert the merge commit, no migration. Weaker spots the author already states, not blocking: the review-245 fix tests use new APIs and were never run against the old code, and the starter-over-the-picker fix has no failing-before proof or e2e case (it is on the owner's phone checklist). Merge with head pinned and the Review-Confidence and Review-Tier trailers ending the message.
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!71
No description provided.