v1.32.0
1.32.0 - 2026-08-18
Added
- Trust -> Find the feature breaking this page. A binary search over the features that are
actually running: it turns them all off, then back on in halves, asking after each round whether
the page is still wrong, and names the one responsible in about five rounds instead of a linear
hunt through thirty-odd settings. Turning a feature off runs its owndestroyrather than
writing a setting, so reloading restores everything however you stop -- including abandoning
mid-round. The first round turns everything off, so "this is not Aviary" is a result the search
can reach rather than a feature named at random. The Control Center and its locale stay on
throughout and can never be named. The outcome travels in Copy diagnostics as one
content-free line, and the bug report template has a field for it.
Fixed
-
"Aviary's page script did not load" no longer stands in for "something else answered it".
A script that wins the very first handshake owns the page agent -- the transferred control port
stops a later one from displacing it, but nothing can stop the first. The real bridge then sat
out a three-second timeout and reportedagent-absent, which reads as a browser compatibility
problem when what actually happened is that Aviary's default-on network ad guard is answering to
somebody else. The agent now refuses a late handshake audibly, and Trust says so. The design note
oninstallPageAgentstates plainly what the boundary defends and what it does not: it is not
cryptographic, the refusal itself is forgeable by the page, and it downgrades a diagnostic rather
than a decision. -
A browser download is only Saved once the browser says it finished.
chrome.downloads.download()resolves when the browser accepts the request, so an interrupted
transfer had already been reported as Saved, marked completed in the queue, and written into the
duplicate index -- which then refused the retry the user wanted. The extension now reports each
download's terminal state back to the tab that asked. Started and Saved are distinct
states on the button; an interrupted transfer reads Retry, marks its queue entry failed, and
is never recorded as a duplicate; and a transfer still running when the wait gives up stays
Started rather than claiming either outcome. The tracking is persisted, so the answer still
arrives after the service worker has been suspended and restarted, and a quality-fallback retry
reports under the id the page is waiting on. Userscript saves are unchanged:GM_download's own
callback already is the terminal state. -
Media inside a quoted post is the quoted account's, not the account that quoted them. The
extractor walked the wholearticlesubtree and filed every photo and player it found under the
outer post's handle and id, so saving a photo out of a quote wrote it as if the quoting account
had published it. Each asset now carries its owner. A quoted post's media is saved under the
quoted account's handle, its own text, and its own post id where the DOM or a captured record
supplies one; the post-level Download saves only the post's own media and says so, and every
excluded asset still has its own Save control. Export records mark quoted and card media with
whose it is instead of listing it among the account's own. -
A post with no permalink of its own -- a reply shell still building, or a quote-only post -- no
longer adopts the quoted post's id, handle, or text. The article's identity is read from outside
the quote it carries. -
The exporter's "this is a quoted post" test and the media extractor's are now one definition.
They disagreed: the exporter looked only for two named test ids while X's current Home renders a
quote as a focusabledivwith no test id, so a record could carry a quoted photo with no note
that a quote existed at all.
Changed
-
The browser floors are declared once, with their reason. They lived in three places that did
not know about each other -- a number in each manifest and a sentence indocs/INSTALL.md-- and
the floor is what decides whether a platform feature can be used directly or needs a detection
branch.src/extension/browser-floors.tsnow declares both, records per-feature which of the two
is true, and preflight fails the build if either manifest disagrees. The decision itself: Firefox
stays at 128. It is an ESR line, Aviary is sideloaded rather than distributed through a store,
and raising the floor to pick upURLPattern,@scopeor the Navigation API without a branch
would exclude the users most likely to be running ESR. -
FeatureRegistrygainedsuspend/resume, and now runsapplyin registration order rather
than initialization order -- the two differ only once a feature has been suspended and resumed,
and ad protection is registered first precisely so it runs before anything that reads the
timeline.