Skip to content

v3.8.47: qui proxy fix, plugin-link add fix, recent searches, plugin pinning, theme presets - #288

Merged
taylorcox75 merged 14 commits into
mainfrom
release/v3.8.47
Oct 11, 2026
Merged

taylorcox75 merged 14 commits into
mainfrom
release/v3.8.47

Conversation

@taylorcox75

@taylorcox75 taylorcox75 commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Everything in the v3.8.47 milestone: two bug fixes, three features, and one more bug fix, in 10 commits (one per issue, plus follow-ups). 49 files, +4683 / −417. Every new preference is additive and optional, no stored key is renamed, and every new string is translated in all 7 locales.

Issue Commit(s) What changed (user-facing)
#278 c908dfe qui Proxy connection failures fixed
#275 a688c77 Pull-to-refresh spinner no longer sits on the search box
#282 ffd5731 Recent searches + clear ✕ on the Torrents search box
#283 3692d73 c67505e 5964622 Pin Search plugins by tapping their chip's star icon
#284 b484eca Search results that are web pages (LimeTorrents, …) now actually get added
#286 ab375ee Theme presets in Settings → Theme
(reported ad hoc) cccd8b3 Search tab placeholder no longer cut off
(housekeeping) fe21065 package-lock.json transitive refresh, kept in its own commit

What changed, per issue

#278 qui Proxy

The qui Proxy mode itself worked (verified over real HTTP against a server that routes and checks keys the way qui does). The reports came from two other problems:

  • Proxy URL pasted into the Host field. The Port is appended after the whole Host text, so requests went to /proxy/<key>:<port>/api/v2/…. qui logs "Invalid client API key" and the app shows an auth failure. The add/edit forms now detect a qui URL in Host (detectQuiProxyUrlInHost, only when the key is 32+ hex characters, so a real qBittorrent at /proxy/<name> is untouched), switch to qui Proxy mode and move the URL over, with a toast.
  • Connection diagnostic refused to run in qui mode, demanding the hidden username/password. It now skips that check, and a 403 in qui mode is explained as qBittorrent rejecting qui's own session.
  • New tests/services/qui-proxy-e2e.test.ts uses real axios against a local qui-shaped server (the existing qui tests mock axios and never saw the request URL).

#275 Spinner overlapping the search box

RefreshControl was bound to isLoading, so iOS showed its spinner on every background load, drawn under the absolute-overlay header. It now follows a local isPullRefreshing and uses progressViewOffset={selectMode ? 0 : headerHeight}. The small spinner inside the search field stays as the background-load indicator.

#282 Recent searches

  • Clear ✕ in the field when there is text. When the field is empty, a clock button opens a dropdown (SearchHistoryDropdown) of recent and pinned searches: tap to reuse, pin, remove, or "Clear recent" (keeps pinned). It is mutually exclusive with the sort menu.
  • Recorded on submit, on blur (≥ 2 chars), and when picked from the list. Case-insensitive dedupe; pinned entries are capped at 10 and don't count toward the limit.
  • Settings → Appearance → Recent searches: Off / 3 / 5 / 10 / 15 (default 5). Lowering it trims stored history.
  • Pure logic in utils/search-history.ts (31 tests) plus a component test.

#283 Plugin pinning

  • Each plugin chip's icon is now a pin toggle: outline star = not pinned, filled star = pinned. Tapping the rest of the chip still selects the plugin.
  • Pinned chips sort to the front (after "All plugins"), alphabetically among themselves, and the row scrolls back to the start after pinning so you can see where the chip went.
  • Stored in the new searchPinnedPlugins pref (plugin ids); pins for uninstalled plugins are ignored and pruned.
  • FilterChip gained an optional onIconPress (separate tap target plus a VoiceOver custom action, since the chip is a single accessibility element). Nothing else uses it yet.
  • History: the first commit shipped a star button + picker; it was replaced by the chip-icon design in the two follow-up commits. They're separate so they're easy to squash if you prefer one commit per issue.

#284 Search results that "add" but never appear

Plugins like LimeTorrents return an .html description page as the result link and can only turn it into a torrent in their own download_torrent(). Since #217 the + opens the add dialog, which sent that page URL to a plain torrents/add. qBittorrent downloads the HTML, fails to parse it and still returns 200, so the app said "added" and nothing showed up.

  • Such results are now fetched by their plugin from the add dialog, the cart and instant add:
    • WebAPI ≥ 2.13.1: torrents/add with downloader=<plugin> (all dialog options still apply).
    • WebAPI 2.11 to 2.13.0 (qBittorrent 5.0–5.1): search/downloadTorrent (default add options only; an info toast says dialog options don't apply).
    • qBittorrent 4.x: unchanged (no such endpoint exists).
  • The plugin is applied only when the submitted URL is still exactly the pre-filled one; edited or multi-URL submissions use a plain add.
  • Aggregators (Jackett/Prowlarr) are excluded per result (engine name, or an apikey= link). The old whole-screen "all results share one site" heuristic misfires for a single-plugin search, so it is no longer used for this decision.
  • Behavior change to be aware of: instant add for non-magnet results from a single-plugin search now goes through the plugin, as multi-plugin searches already did and as qBittorrent's own WebUI does.
  • New utils/search-download.ts, with downloader support in torrentsApi.addTorrent. engineName is the plugin id (searchResult.engineName = plugin->name in qBittorrent's source).

#286 Theme presets: existing users are not affected

Default, OLED, Nord, Dracula, Solarized, High Contrast, qBittorrent Classic, each with light and dark variants, in a horizontally scrolling Presets row at the top of Settings → Theme.

  • Safety guarantees (all covered by tests): ThemeContext.tsx has no diff, so the default palettes and merge logic are untouched. Nothing runs at launch, and there is no migration or auto-apply. Existing custom colors are classified custom and are only replaced after a confirm dialog. Selection is derived by comparing stored colors with the preset, never from the saved id alone. Applying a preset is one read-modify-write of both modes. "Default" clears both modes and the preset id.
  • ColorTheme gained optional preset-only primaryOpac and onAccent so a preset's accent doesn't leave default-blue highlights behind (not editable rows).
  • Contrast is enforced by tests: text ≥ 4.5 (≥ 7 for High Contrast), secondary text, accents and torrent-state colors ≥ 3, and key state colors distinct. A few palette values are adjusted from the official Nord/Solarized/Dracula values to meet that.
  • Preset ids are persisted as themePresetId; never rename one.

Search tab placeholder

"Search torrent indexers…" ran off the field and iOS clips a placeholder with no ellipsis; Russian and Polish were longer still. Shortened in every locale (e.g. "Search indexers…"). Spanish, Chinese, French and German had been left in English and are now translated.

Compatibility and risk

Test plan

Automated (all passing locally and in CI, 4/4 checks):

  • npx tsc --noEmit: exit 0
  • npm test: 100 suites, 1856 tests (new: qui real-HTTP suite, search-history, search-plugins, search-download, search-cart, theme presets and safety/contrast, FilterChip icon button, ThemeContext/theme screen, SearchHistoryDropdown)
  • npm run lint: 0 errors (38 warnings, the existing baseline) · npm run format: no changes

On a device (not yet done):

  • [BUG] Loading gif in Home Screen is in wrong place #275: pull down on Torrents; the spinner appears below the search box. Background loads show only the small spinner inside the field.
  • [BUG] QUI proxy #278: Authentication Method → qui Proxy with the full proxy URL connects. Pasting that URL into Host switches the form to qui mode.
  • [Request] Search history #282: search a few times, clear the field, tap the clock; reuse, pin, remove and "Clear recent" work; Settings → Appearance → Recent searches (Off/3/5/10/15) trims or hides it.
  • [Request] Plugin Pinning #283: tap a chip's outline star → it fills and the chip jumps to the front; the pin survives relaunch; tapping again unpins; tapping the label still selects the plugin.
  • [BUG] Unable to add torrents with very few seeders/older #284 (qBittorrent 5.x): search LimeTorrents on its own and again with several plugins, + → Add; the torrent appears in the list. Cart checkout and a magnet result also still work.
  • [Request] Pre-made theme presets #286: Settings → Theme → tap a preset in light and dark. With hand-edited colors you get a "Replace your custom colors?" prompt first; Default restores the stock look.
  • Placeholder: the Search field text isn't clipped (also check Russian/Polish).

Five fake search plugins (Alpha/Bravo/Charlie/Zulu/HTML Dummy) were used to test pinning and #284. They are not part of this PR.

Not included

Closes #275
Closes #278
Closes #282
Closes #283
Closes #284
Closes #286

🤖 Generated with Claude Code

- A qui proxy URL pasted into the plain Host field could never connect: the
  Port was appended after the key (/proxy/<key>:<port>/...), so qui logged
  "Invalid client API key". The add/edit forms now detect a qui URL in Host
  and switch to qui Proxy mode with the URL moved over.
- The Full Diagnostic refused to run in qui Proxy mode, demanding the hidden
  username/password. It now skips that check, and explains a 403 in qui mode
  as qBittorrent rejecting qui's own session.
- Add a real-HTTP regression suite (real axios against a qui-shaped server);
  the existing qui tests mock axios and never saw the request URL.
- Changelog: v3.8.47.
Some search plugins (e.g. LimeTorrents) return an HTML description page as a
result's link and can only turn it into a torrent through their own
download_torrent(). The Search + button opens the add dialog with that URL
pre-filled, and both dialogs (and cart checkout) only ever called torrents/add
with it, so qBittorrent downloaded the page, failed to parse it, and still
answered 200: the app said "added" and nothing appeared.

Hand such URLs to their plugin:
- torrents/add gains a `downloader` option (WebAPI >= 2.13.1), which routes the
  URL through the plugin while keeping every other add option.
- On 5.0-5.1 fall back to search/downloadTorrent (default options only, with an
  info toast saying so); qBittorrent 4.x has no equivalent and is unchanged.
- Search passes the plugin to both dialogs as a `sourcePlugin` route param,
  applied only while the submitted URL is still exactly the pre-filled one.
- Cart items carry their `downloader`, checkout splits batches by it.
- Instant add prefers torrents/add + downloader (tags applied by the add).
- getResultDownloader() is the single rule for "does this result need its
  plugin", shared with the duplicate-grouping hash resolver.

Closes #284
@taylorcox75 taylorcox75 changed the title v3.8.47: qui proxy connection fixes (#278) v3.8.47: qui proxy, add-dialog fix, search history, plugin pins, theme presets Oct 11, 2026
Replace the star button and picker with a tap on each plugin chip's own
icon: tapping it pins (star) or unpins the plugin, and pinned plugins sort
to the front. The rest of the chip still selects the plugin. After pinning,
the row scrolls back to the start so the chip is visible where it landed.

FilterChip gains an optional onIconPress (a separate tap target for the
icon, plus a VoiceOver custom action since the chip is one element).

Refs #283
Unpinned plugin chips now show an outline star (tap to pin) and pinned
ones a filled star (tap to unpin), instead of the puzzle icon.

Refs #283
The "Search torrent indexers…" placeholder ran past the right edge of the
search field (iOS clips a placeholder without an ellipsis), and the Russian
and Polish versions were longer still. Use a shorter string in every
locale; es/zh/fr/de were still English and are translated now.
@taylorcox75 taylorcox75 changed the title v3.8.47: qui proxy, add-dialog fix, search history, plugin pins, theme presets v3.8.47: qui proxy fix, plugin-link add fix, recent searches, plugin pinning, theme presets Oct 11, 2026
The clock button only appeared after a search had been saved, so on a
fresh install the feature was invisible. Show it whenever history is on
(and the field is empty); with nothing saved yet, the dropdown explains
how searches get saved.

Refs #282
Ionicons' pin is a map-style needle that didn't read as "pin this". Use
MaterialCommunityIcons' thumbtack (outline to pin, filled when pinned)
for the row toggle and the pinned row's leading icon.

Refs #282
- Preferences: add storageService.updatePreferences, which queues writes
  and re-reads inside the queue. Recent searches and plugin pins use it,
  so overlapping saves no longer drop each other's keys.
- Recent searches: record only on submit and when picking an entry. The
  filter is live, so recording on blur saved half-typed fragments.
- Plugin pins: toggle from a ref so quick back-to-back taps don't lose a pin.
- 5.0-5.1 cart checkout: plugin-resolved items now get their indexer tag
  via the same best-effort poll instant add uses (moved to
  services/search-tracker-topup.ts as tagNewlyDownloadedTorrent).
- A path typed into the Host field now goes after the port in the API
  client and the debug panel ("host/qbit" + 8443 -> host:8443/qbit), and
  Origin no longer includes that path.
- Recent-search pin icon: back to Ionicons (tilted pin) instead of pulling
  in the MaterialCommunityIcons font for two glyphs.
@taylorcox75
taylorcox75 merged commit bc40423 into main Oct 11, 2026
4 checks passed
@taylorcox75
taylorcox75 deleted the release/v3.8.47 branch October 11, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant