Boundless can push your progress to a tracker now. The code for it shipped with the rewrite and none of it worked, because every part of the chain failed in a way that never surfaced.
That turned out to be the theme of the whole release.
Getting a tracker connected
There are six adapters in the app: AniList, Kitsu, MyAnimeList, MangaUpdates, Shikimori, and Bangumi. Three of them needed real credentials, and getting there meant fixing three separate silent failures stacked on top of each other.
The client IDs were placeholders. MyAnimeList, Bangumi, and Shikimori now use Boundless's own registered apps. Bangumi and Shikimori are confidential-client flows, which means a client secret that cannot live in the app, so each one gets a Supabase Edge Function that holds the secret and performs the token exchange server-side.
hCaptcha was verifying against nothing. The solver renders hCaptcha inside a WebView from inline HTML, and that WebView had no base URL, so the widget's origin was about:blank. It looked completely normal. It rendered, you tapped it, and hCaptcha's own hostname check rejected every verification without saying so. It also had no way to grow: when a tap didn't pass frictionlessly and a real image challenge appeared, the challenge was clipped to the checkbox's own 86 points of height. Both fixed, with a real base URL and hCaptcha's open and close callbacks resizing the view while a challenge is up.
Shikimori was rejected before it was attempted. The OAuth completion path hard-required a PKCE verifier for every tracker in that shape. Shikimori is a confidential-client flow and has no PKCE step at all, so its own authorize URL never produces one. Every attempt threw "missing PKCE verifier" before any exchange happened, and because the caller never awaited the promise, the throw went nowhere.
The real errors were being thrown away. Both Edge Functions returned 502 for their own handled failures, like an upstream token exchange being rejected. That is not just the wrong status code. The Supabase client treats a 502 from an Edge Function as a platform-level crash and replaces the response body with a generic "non-2xx status code" message, discarding the actual error text. Every Shikimori failure looked identical and undebuggable from the client, purely because of the status code. They return 400 now.
None of those four announce themselves. Three of them look, from the outside, exactly like a tap that did nothing.
Account Sync was saving almost nothing
library_entries in production was missing twenty-three columns.
Account Sync is opt-in and off by default, so this went unnoticed for a while. When it was on, every push succeeded and quietly saved a fraction of each library entry, dropping most of the fields on the way. The columns exist now.
The privacy manifest was wrong in the other direction: NSPrivacyCollectedDataTypes was empty while Account Sync was pushing email-authenticated library entries, reading progress, and history to Supabase. It now declares email address, user ID, and other user content, all linked to identity, for app functionality and not for tracking, which is what the sync code actually does. iCloud Sync is not declared separately, because that goes through your own iCloud account via CloudKit and never touches my server.
Dyslexia-Friendly was a preview, not a setting
A dyslexia-friendly typeface is not just a typeface. The accommodation is the letter spacing and the line height as much as the letterforms.
Both of those values lived in one place: the preview swatch on the reading settings screen. Picking the font and opening a book gave you the typeface swap with none of the spacing, which makes it a font change rather than an accommodation. The values now live in the reader font module that the file's own comment already described as the single source of truth, and the prose reader uses them for real. There are tests pinning them, because the same file's comment warns about exactly the three-copies drift that caused this.
Two Android deep links
The share handler treated every ACTION_VIEW intent as a candidate shared file, with no scheme check. So any boundless:// deep link, which includes every OAuth redirect and every add-repository link, arrived twice: once correctly through the navigation listener, and once here, racing it and stranding you on an import error screen for a file that was never a file. It only reproduced when the app was already warm, which is the normal state right after a browser round trip.
The second one was subtler. The tracker auth deep link navigated straight to the Tracking screen on a settings stack that had never mounted, so the navigator initialised with Tracking as its only route. Its back button had nothing to pop to and dropped you out to the Library tab instead.
Mac, again
A broad pass across most of the app on Mac, where Boundless runs as the iPad app. Hover and pointer feedback, custom sheet and modal dismissal, and layout width assumptions across the settings screens, the browse, library, and search grids, the migration flow, and the reader.
One regression came out of it. The detail screen's primary action, library heart, and share buttons had been moved to an animated pressable, which collapsed their gradient backgrounds to a sliver on real hardware while looking fine everywhere else. They are back on their original implementation.
Also in this build
The reader no longer auto-zooms into the first page of a chapter as though it were a two-page spread. A scanlation credit or title banner on page one clears the same aspect ratio threshold a genuine spread does, and a genuine spread on a chapter's opening page is effectively nonexistent, so page one is excluded from the check. Real spread detection everywhere else is untouched.
The About section in Settings got its descriptive paragraph back, having been dropped by mistake.
Downloads are on /download, and the feature docs are at /docs.