Skip to content

2.1.7 fixes: letter rail, accent search, safer downloads, Wi-Fi only - #90

Merged
StaticHumStudio merged 30 commits into
masterfrom
fix/217-fixes
Oct 4, 2026
Merged

StaticHumStudio merged 30 commits into
masterfrom
fix/217-fixes

Conversation

@StaticHumStudio

Copy link
Copy Markdown
Owner

Why

Eleven follow-ups from the big-library release (#83), plus a crash in the grouped Library that the evidence pass for this build turned up on master.

Library

Downloads

Player and smaller fixes

Evidence

Headless emulator, mock server with 8,000 books:

  • Upgrade from 2.1.6 (DB 9 to 10): accented search works with no refresh, database log clean.
  • Grouped Author view with pull to refresh: master crashed in 3 of 3 sessions, this build 0.
  • Scrolling, median of 5 flat and 3 grouped runs: jank 1.64% to 1.67% flat and 1.40% to 1.59% grouped (within run noise), p50 frame time 27 to 19 ms flat and 28 to 18 ms grouped.
  • Letter rail, TalkBack labels, glow and reduced motion, series order, co-authors, chapter sheet, Dossier, finished flag (mock saw the sync then the finished PATCH), dark and light themes.
  • Downloads: an upgrade from 2.1.6 keeps old downloads playing offline and a paused one finishes in its old folder, two editions download into two folders, deleting one while the other downloads leaves the other intact, Wi-Fi only waits and the mobile data override, a Wi-Fi drop mid-download, flipping the setting, and the refusal notice. Not run on a device: a custom download folder (the app has no picker for one, unit tested only).

Review

Unit tests 1,243 to 1,425, all passing. Independent review: Sol 6.1 high on every part until clean, then the whole diff against master, clean.

Closes #49 #61 #63 #64 #67 #72 #75 #76 #79 #81 #82

Follow-ups filed: #85 #86 #87 #88 #89.

🤖 Generated with Claude Code

StaticHumStudio and others added 30 commits October 4, 2026 12:34
The reload check compared library ids only, so a rename kept the old name until the list itself changed. It now also notices a changed name for the same id.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…led to the current one

The chapter menu built every chapter up front. It is now a lazy list in a bottom sheet that only composes the rows on screen and opens at the chapter that is playing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ooks

The Dossier was one scrolling column, so Show all built every book card and cover at once. It is now a lazy list that only builds the cards on screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The glow around a cover no longer draws or animates at zero progress, so a
shelf of unstarted books runs no per-row animation. With reduced motion on
(system animation scale at zero, or the in-app toggle) it is drawn once and
held at a middle brightness. Its paints, paths and path effects are built
once and reused, so a frame only changes alphas, instead of allocating four
paints, three filters and two paths every frame.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
BookCoverImage now uses AsyncImage with the vintage placeholder composed
underneath it, instead of SubcomposeAsyncImage. The placeholder still shows
while a cover loads and stays when the cover fails, but each row no longer
runs a subcomposition for it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Each row used to read its title twice (cover and text) and left the progress
ring and downloaded dot unlabeled. A row now reads as one sentence, like
"Dune, by Frank Herbert, 42 percent listened, chapter 3 of 10, downloaded",
and the search box has a steady label instead of depending on its flavor hint.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Delete used to wipe whatever folder the book row pointed at, recursively.
Two editions with the same author and title share one folder, so deleting
either one took the other's audio too, and a stale path could point
anywhere.

Delete now runs through the same fences as cancel cleanup: the folder must
be a plain direct child of the download root, a user-picked root is left
alone, and nothing is ever deleted recursively. When another downloaded
book shares the folder, only this book's own tracks go and the cover stays
for the other one. A partial download of another book writing there keeps
everything. Delete also holds the row lock like cancel, and treats the
engine being on the book as downloading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pause save on a server book goes through its listening session. The
session sync carries the position but no finished flag, yet its success
deleted the queued progress row, including a finished one. A finished
pause save now also pushes the finished flag after the session sync and
keeps its queued row until that push lands. Unfinished saves are
delivered by the session sync alone, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the Library is sorted by title or author, a strip of letters runs down
the right edge. Touch or drag it to jump to that letter, with a big letter
shown while a finger is down. It works for the flat list and for the grouped
Series, Author and Genre lists (by group name), and TalkBack gets it as one
adjustable "Jump to letter" control. Each letter's position is worked out
once per list change off the main thread, so dragging only looks one up.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new Download on Wi-Fi only setting, on by default. Settings saved before
it existed decode to the default, so upgrades get it on too.

The single drain worker now carries the network rule: it waits for an
unmetered network while the setting is on. Sending one download over
mobile data runs the drain on any connection without touching the
setting, and that drain takes only the books sent that way. When it runs
out, the rest go to a follow-up drain appended behind it that waits for
Wi-Fi. A drain request from before this change reads as one on any
connection, so with the setting on it starts nothing on its own.

Flipping the setting applies to work already queued. Turning it on stops
a book streaming over mobile data and makes it wait, unless the user sent
it that way. Turning it off starts a queue that was waiting, and frees a
drain still bound to Wi-Fi. Every entry point goes through the same
enqueue, so Book Detail, Downloads, the paused notification and
entitlement changes all follow the rule. The per-download choice lives in
device-local prefs, so there is no schema change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…79)

Downloads shows Waiting for Wi-Fi instead of Queued, or instead of a
Downloading bar frozen after Wi-Fi dropped, plus a Use mobile data button
that starts that one book without changing the setting. Book Detail says
the same when a book queues on mobile data. Settings gets the Download on
Wi-Fi only switch in the Data group, and flipping it applies to downloads
already queued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A search for "Emile" now finds "Émile", "dune" finds "DUNE", and "nesbo"
finds "Nesbø", in the Library and in Android Auto. Each book row keeps a
folded copy of its title, author, series and narrator in a new SearchText
column, and search compares a folded query against it. "%" and "_" still
match themselves.

The database moves to version 10. The upgrade fills the column for every
book already on the phone, a page at a time, so an upgraded library
searches right away with no resync. Every later write fills it too. The
letter rail files titles with the same folding, so "Øystein" sits under O.

Android Auto search used to read title and author only. It now reads the
same column as the Library, so series and narrator match there too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each series now reads by its sequence number whatever the shelf sort, so
book 2 comes before book 10 and a book 1.5 sits between 1 and 2. A book
with no sequence, or one that is not a plain number, goes last, by title.
The series themselves are still ordered by the shelf sort, so Recently
Added still lists the series with the newest book first. Books with no
series keep the shelf order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Audiobookshelf's library list sends a co-authored book's authors as one
line, "Terry Pratchett, Neil Gaiman", and Author view filed it under a
group of that whole line. It is now split on the ", " Audiobookshelf joins
with, so the book is listed under Terry Pratchett and under Neil Gaiman,
and the row still shows the full line. A suffix piece like "Jr.", "III" or
"PhD" stays with the name before it. Local books keep their author tag
whole, since a tag like "Tolkien, J.R.R." is one person.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stopping the drain gives up after ten seconds, and WorkManager calls a
cancelled drain finished long before its engine unwinds. A read stuck on
a stalled server only gives up at the 60 second read timeout, and its
cleanup then deleted the .part file the replacement drain was writing,
so finalizing failed and the book downloaded again and again. Any restart
could hit this: the Wi-Fi only flip, pause, cancel and delete.

Engine runs now hold one process-wide lock from start to the end of
cleanup, so a replacement drain waits until the old engine has really
exited. The book recorded for cancel cleanup is set only once the lock is
held, and the row is read again after the wait so a book cancelled or
finished meanwhile is skipped rather than written back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…udio

The engine writes Completed, then waits on the cover, and only then saves
the book's localPath. Deleting another edition that shares the folder in
that window missed the finishing book on both counts: no stored path made
it a sharer, and its Completed row did not count as unfinished. So every
plain file went, and the finishing book was then marked downloaded with
its tracks gone.

A Completed row now counts as still owning its folder until the book row
holds a saved local copy, the book the engine is on always counts, and
the folder the engine is really writing to is protected too, in case the
title changed under it. Any doubt keeps the files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…book's files

Delete, cancel and the engine each decided who owned a download folder from
several separate reads while the others kept changing it, and each earlier
fix narrowed one interleaving without closing the next. Three were still
open: deleting one edition while the other saved its finish wiped the
other's audio, cancelling a paused book deleted the folder another book's
engine was still writing after a rename moved its row elsewhere, and a
queued book cancelled as the engine picked it came back and downloaded.

Now one lock covers every change to what the engine owns (claiming a row,
saving the finish, letting the folder go) and every delete, cancel or pause
from its decision through its last deleted file. The engine claims a row
only while it still exists and is still waiting, and saves a finish only
while the row still exists, with the Completed row and the book's local
copy written together. The folder the engine is really writing is never
deleted, whichever book it belongs to. No network call ever runs under the
lock, so a stalled read cannot freeze delete, cancel or the screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a big library the shelf read spans several cursor windows, and each
window was read afresh. A sync page saved in between gives the re-saved
books new rowids, so they came back again at the end of the read (and as
many other books went missing). A book twice in one group is two list rows
with one key, and the grouped list crashed when they scrolled into view.

The shelf now reads in one transaction, so it is one snapshot. The shelf
also keeps each book once whatever the read returns, the grouped list drops
any repeated row instead of crashing, and a row key can no longer run two
group and book names together. Author, series and genre names that differ
only in capitals or spacing ("John Smith, john  smith") are one group, so a
co-authored book is listed once per author and no two groups share a key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der removes the book's files

New downloads go to "Author - Title [id]", where id is the last eight
letters and digits of the ABS item id, so two editions of one title never
share a folder: a cancelled partial of one edition can no longer become the
other's audio, and deleting one while the other downloads takes all of its
files. The name is capped at 200 UTF-8 bytes with the id always kept.

Existing downloads keep their stored path and are not moved. A download that
started before this (bytes on record, no folder of its own yet) resumes in
its old "Author - Title" folder. Delete and cancel find the folder another
download may be writing with the same rule, and the shared-folder checks
stay in place for the old folders.

Deleting a download in a user-picked download folder used to leave its
files on disk while dropping the record. It now removes this book's own
named tracks, their .part leftovers and its cover, and the folder only once
empty. When the files cannot be proven this book's, the book stays
downloaded and the delete says so instead of pretending it worked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…groups

The duplicate-row fix merged group names that differ only in capitals,
which joined distinct series like Saga and SAGA (interleaving their
reading order) and named the merged group by whichever spelling sorted
first, so a collapsed group could reopen on a sort change. Keys are now
the name with runs of spaces collapsed, nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When storage turns read only mid-delete, the files stayed on disk but
the record was cleared, orphaning the audio. A file still present after
the delete now returns Keep(REMOVE_FAILED), so the book stays downloaded
and the refusal notice shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earlier #81 change fixed the Library screen's reload check, but the
visible selector lives in Settings, which loaded its list only at start
and on connect. Settings now follows the cached server libraries and
refreshes the names of the ones it shows. Which libraries appear and
which is selected still come from loadLibraries().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings' loadLibraries() could publish the cached snapshot it read
before a concurrent sync renamed the library, undoing the name the
collector had just applied. The collector now records the latest cached
libraries before updating state, and loadLibraries() applies those
names inside its own state update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-04T19:51:07.858414Z 5d11d5b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d11d5b079

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

*/
internal fun splitServerAuthorNames(authorName: String): List<String> {
val names = ArrayList<String>()
for (piece in authorName.split(", ")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve last-name-first server authors

When a server book uses a single author name containing a comma, such as Tolkien, J.R.R., splitting every ", " creates separate Tolkien and J.R.R. author groups. The local-book exception does not help server libraries, whose editable authorName is mapped into this same unstructured string. Use the API's structured authors data or otherwise avoid treating every comma as a co-author boundary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined for this PR, tracked as #87. Audiobookshelf rewrites "Last, First" into "First Last" when it scans (server/utils/parsers/parseNameString.js), so a comma inside one author's name needs a hand-typed name on the server. The minified library list carries no structured authors array, so the real fix is one /api/libraries/{id}/authors call per sync plus stored author ids, which is out of scope for this release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Download folders are keyed by author and title, so two editions can share files

1 participant