86f5502724
Cherry-picked and re-verified the applicable subset of julianshen/readest@fa1b74a0 (its "address PR #15 bot reviews" commit). The fork-only AI-annotation-tool change was dropped — readest has no 'ai' toolbar tool. Each logic fix is covered by a failing-first test. - TTS position sequence is now an app-wide monotonic counter, so a fresh TTSController (constructed per `tts-speak`) isn't dropped by consumers holding `lastSequenceSeen` from a prior session. - share.ts only swallows AbortError (user cancel); other failures — e.g. NotAllowedError when a quick action fires without a user gesture — fall back to the clipboard so the text still reaches the user. - document.isTxt tolerates MIME params (text/plain;charset=utf-8), uppercase extensions (BOOK.TXT), and a nameless Blob, so a TXT can't slip onto the non-text path and yield a null book. - updater getNightlyPlatformKey matches x86_64/aarch64 explicitly; a 32-bit or otherwise unknown arch yields no nightly instead of mis-routing to aarch64. - UpdaterWindow downloadWithProgress resolves on tauriDownload completion even when Content-Length is absent (no more hang on portable/AppImage/Android). - nightly_update.rs uses async tokio::fs::read in the async command. - nightly.yml: serialize runs via a concurrency group (no cancel) and persist-credentials:false on checkouts. - edge TTS route only emits the word-boundary header when it fits under ~8KB; oversized values get dropped by proxies, and the client falls back to []. - RSVPOverlay drops the contradictory aria-disabled on the functional rate button (it opens the pace picker). - nightly verify harness handles artifact stream errors instead of crashing. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nightly updater — local verification harness
Exercises the in-app nightly check (Tier 2 detection) and the signature gate
(Tier 4) on a desktop pnpm tauri dev build, without waiting on CI.
Throwaway-signed fixtures only (the signing key was discarded).
What it can and can't prove
- ✅ Detection (Tier 2) — the app on the nightly channel fetches both
manifests, applies the comparator, offers the right version; the isolated check
never calls Tauri
check(); error / up-to-date states render. - ✅ Verify-gate REJECT (Tier 4) — a bad/garbage signature is refused.
- ✅ Resolver decision (no GUI) — the unit test
resolveNightlyUpdate — harness scenariosinsrc/__tests__/helpers/updater.test.tsruns the real resolver against these manifest builders (pnpm test src/__tests__/helpers/updater.test.ts). - ⚠️ Full install / accept-valid — needs the real signing key (CI only):
the app verifies against the production
READEST_UPDATER_PUBKEY, so the throwaway artifact correctly fails real-key verification if you click "Download & Install". Accept-valid is covered by the Rust testpnpm test:rust→nightly_update::tests::verify_accepts_valid_signature. - On macOS the install path routes through Tauri's updater (darwin key), so the custom verify-gate isn't hit from the UI — use the Tier 4 devtools snippet below (or the Rust test) to exercise it directly.
Tier 2 — live detection
- Start the server:
pnpm verify:nightly # or: node scripts/nightly-verify-harness/serve.mjs - Point the two manifest constants at the server. In
src/services/constants.tstemporarily change:(The app's HTTP capability already allowsexport const READEST_NIGHTLY_UPDATER_FILE = 'http://127.0.0.1:8788/nightly/latest.json'; // and export const READEST_UPDATER_FILE = 'http://127.0.0.1:8788/releases/latest.json';http://*:*, so localhost works.) - Run and opt in:
Settings → toggle Nightly Builds (Unstable) → About → Check Update.
pnpm tauri dev- Expect: "Nightly · (Jan 1, 2099, 00:00)" is available.
- Server log shows
GET /nightly/latest.jsonand/releases/latest.json(both fetched, then filtered/compared). - Error state: stop the server (Ctrl-C), re-run Check Update → expect "Failed to check for updates", not a blank pane.
- Cross-channel: point
READEST_UPDATER_FILEathttp://127.0.0.1:8788/releases/latest-surpass.json(stable = base, patch +1). The offered version should switch to the stable<base+1>— a higher-base stable beats the nightly. Switch back and the nightly wins again. - Revert the constants when done:
git checkout src/services/constants.ts
Tier 4 — verify-gate, directly (any platform)
In the dev window devtools console (right-click → Inspect):
const path = '<ABS>/apps/readest-app/scripts/nightly-verify-harness/artifacts/test.bin';
const pubKey = 'dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZFQTAxMjIzNUEwRkE0OUIKUldTYnBBOWFJeEtnL2x4Q3dKR3dSWVJCY3dLNXdCR1l4d1YyVkhaZUppOVVNVm1kOGprbU85bTMK';
const goodSig = 'dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTYnBBOWFJeEtnL3RvRC83dEJEUXZONVFZM1hranhKTUZxQzllR2lGWnNjckZMbCtOa3RXMi80aFdDYUNDUkdOa0NqUjJUQkZDL2dqaUVTeURlNzI0cW1BcUlZY2ZsOGcwPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzgxNDE0MzExCWZpbGU6bnYuYmluCkQzajlpbVZPOXVDYXdna2JBVWZ0TTE4K1d1cWdEYWVYQzVraGh4U1ZuOGNSTDZaOU5zV093OEVDajBvV0JydVV5VGY2K0tkb0hBbGJHYWprK0NsNUN3PT0K';
const { invoke } = window.__TAURI_INTERNALS__;
await invoke('verify_update_signature', { path, signature: goodSig, pubKey }); // → true
await invoke('verify_update_signature', { path, signature: 'AAAAgarbage', pubKey }); // → false
Replace <ABS>. (pubKey is the throwaway key that signed test.bin, not the
production key.) Tampering test.bin and re-running the good-sig call also → false.
Notes
- Nightly is stamped
<base>-2099010100so it's always newer than installed. serve.mjsreads the base version frompackage.jsoneach request, so it stays correct as the app version changes.