Skip to content

fix(crossseed): pin explicit savepath when cross category save path diverges - #1993

Merged
s0up4200 merged 1 commit into
developfrom
fix/crossseed-divergent-category-savepath
Jun 3, 2026
Merged

fix(crossseed): pin explicit savepath when cross category save path diverges#1993
s0up4200 merged 1 commit into
developfrom
fix/crossseed-divergent-category-savepath

Conversation

@s0up4200

@s0up4200 s0up4200 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Regular (non-hardlink/reflink) cross-seed mode enabled autoTMM for affixed categories whenever the matched torrent was auto-managed, handing save-path resolution to qBittorrent. With a .cross affix, qBittorrent resolves the path from the affixed category (its configured save_path, or the implicit <default>/<category> when none is set), which need not equal the matched torrent's location — and ensureCrossCategory only warns on a divergent pre-existing category, never rewrites it. The result: the cross-seed was relocated into e.g. <base>/<category>.cross and re-downloaded 100% instead of reusing existing files. Hardlink/reflink modes were immune because they always pin an explicit savepath with autoTMM off.

ensureCrossCategory now returns the category's actual save path, and regular mode disables autoTMM + pins an explicit savepath to the matched torrent's location when that path diverges (or is unset), mirroring link modes. The matching-path case is unchanged (autoTMM stays on). Includes a regression test covering a pre-existing divergent .cross category. Reported on Discord: a "seedbox" instance using a category suffix re-downloaded cross-seeds while the hardlink "media" instance worked.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced cross-seed functionality to intelligently handle category save-path configurations, ensuring correct behavior when actual category paths diverge from matched torrent locations and maintaining expected file placement outcomes.
  • Tests

    • Added comprehensive test cases validating cross-seed behavior with divergent category save paths.
    • Improved cross-seed testing infrastructure with refactored utilities for better maintainability.

…iverges

Regular (non-hardlink/reflink) mode enabled autoTMM for affixed cross-seed categories whenever the matched torrent was auto-managed, delegating save-path resolution to qBittorrent. With a category affix like ".cross", qBittorrent resolves the path from the affixed category — its configured save_path, or the implicit <default>/<category> when none is set — which need not equal the matched torrent's location. ensureCrossCategory only warns on a pre-existing category whose path diverges (never rewrites it), so the cross-seed was relocated into e.g. <base>/<category>.cross and re-downloaded instead of reusing existing files. Hardlink/reflink modes were immune because they always pin an explicit savepath with autoTMM off.

ensureCrossCategory now returns the category's actual save path, and regular mode disables autoTMM + pins an explicit savepath to the matched torrent's location when that path diverges (or is unset), mirroring link modes. The matching-path case is unchanged (autoTMM stays on). Adds a regression test covering a pre-existing divergent .cross category.
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR addresses cross-seed category save-path drift by making ensureCrossCategory return the actual category save path alongside the error, then using that value to detect when the category location diverges from the matched torrent location. When divergence occurs, autoTMM is disabled and an explicit savepath is pinned to prevent qBittorrent relocation.

Changes

AutoTMM safety on category save-path divergence

Layer / File(s) Summary
ensureCrossCategory signature change and documentation
internal/services/crossseed/service.go
ensureCrossCategory now returns (actualSavePath string, err error) instead of only error. Documentation updated to explain the actual save path (existing or created) is returned and used to detect divergence so regular mode can pin an explicit save path instead of trusting autoTMM.
AutoTMM divergence detection and pinning
internal/services/crossseed/service.go
In the main cross-seed add flow, crossCategoryActualSavePath is declared, the actual save path is captured when ensuring the category, and a guard checks if autoTMM is enabled but the category's actual save path diverges from the matched torrent's save path; if so, autoTMM is disabled and an explicit savepath is pinned.
Call-site updates for new ensureCrossCategory signature
internal/services/crossseed/service.go, internal/services/crossseed/seasonpack.go
Hardlink-mode, reflink-mode, and season-pack webhook call sites are updated to unpack the new two-value return from ensureCrossCategory, discarding the actual save path since those modes do not need divergence detection.
Test coverage for category divergence and cache assertions
internal/services/crossseed/category_divergence_test.go, internal/services/crossseed/category_cache_test.go
A new end-to-end test validates that when a cross-seed category save path diverges from the matched torrent location, the service disables autoTMM and pins savepath correctly. Existing cache tests are refactored to use a local ensure() helper and add intermediate cache call assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • autobrr/qui#767: Both PRs modify ensureCrossCategory and autoTMM decision logic in the main cross-seed add path, with #767 addressing race conditions and autoTMM changes in the same code flow.
  • autobrr/qui#1753: Both PRs modify ensureCrossCategory to handle category save-path differences, with the main PR using returned actual save path to detect and handle divergence while #1753 adjusts mismatch warnings in link modes.
  • autobrr/qui#700: Both PRs implement "use matched torrent save path instead of category path" behavior in cross-seed candidate processing, with the main PR detecting divergence via ensureCrossCategory return value and pinning savepath accordingly.

Suggested labels

bugfix, cross-seed, backend

Poem

🐰 A rabbit hops through cross-seed paths,
Where categories drift and diverge in half—
Now we detect when they stray apart,
And pin the savepath to its heart,
AutoTMM rests while the torrent stays smart!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: pinning an explicit savepath when the cross category save path diverges, which is the core fix implemented across service.go and related files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/crossseed-divergent-category-savepath

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@s0up4200 s0up4200 added bug Something isn't working cross-seed backend go Pull requests that update go code tests labels Jun 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/services/crossseed/service.go (1)

11251-11265: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Revalidate qBittorrent state before reusing cached actualSavePath.

Now that ensureCrossCategory's return value drives the autoTMM/savepath choice, these fast paths can reintroduce the relocation bug after an operator changes the category's save_path while the service is still running. With a stale createdCategories entry, regular mode will believe the cross category still matches props.SavePath, keep autoTMM on, and qBittorrent will relocate the add into the new category path. Skip the session cache whenever compareExistingSavePath is true (both here and inside the singleflight closure), so the load-bearing path always comes from a fresh GetCategories read.

Proposed fix
-	if cached, ok := s.createdCategories.Load(key); ok && cacheMatches(cached) {
+	if !compareExistingSavePath {
+		if cached, ok := s.createdCategories.Load(key); ok {
+			cachedSavePath, _ := cached.(string)
+			return cachedSavePath, nil
+		}
+	}
-		cachedSavePath, _ := cached.(string)
-		return cachedSavePath, nil
-	}

 	actualSavePathValue, err, _ := s.categoryCreationGroup.Do(key, func() (any, error) {
-		if cached, ok := s.createdCategories.Load(key); ok && cacheMatches(cached) {
+		if !compareExistingSavePath {
+			if cached, ok := s.createdCategories.Load(key); ok {
+				cachedSavePath, _ := cached.(string)
+				return cachedSavePath, nil
+			}
+		}
-			cachedSavePath, _ := cached.(string)
-			return cachedSavePath, nil
-		}

Based on learnings: ensureCrossCategory intentionally keeps warn-only behavior when an existing category’s save_path differs from the desired path and callers should not attempt repair.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/services/crossseed/service.go` around lines 11251 - 11265, The
fast-path cache in ensureCrossCategory is being used even when
compareExistingSavePath is true, which can return stale createdCategories and
cause incorrect autoTMM/savepath behavior; modify ensureCrossCategory so both
the initial createdCategories.Load(key) check and the duplicate check inside
categoryCreationGroup.Do skip using the session cache when
compareExistingSavePath == true (i.e., only use createdCategories and
cacheMatches when compareExistingSavePath is false) so that the load-bearing
path falls back to a fresh GetCategories read; touch the symbols
ensureCrossCategory, createdCategories.Load(key), categoryCreationGroup.Do(...)
and cacheMatches(...) when implementing this conditional skip.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/services/crossseed/service.go`:
- Around line 11251-11265: The fast-path cache in ensureCrossCategory is being
used even when compareExistingSavePath is true, which can return stale
createdCategories and cause incorrect autoTMM/savepath behavior; modify
ensureCrossCategory so both the initial createdCategories.Load(key) check and
the duplicate check inside categoryCreationGroup.Do skip using the session cache
when compareExistingSavePath == true (i.e., only use createdCategories and
cacheMatches when compareExistingSavePath is false) so that the load-bearing
path falls back to a fresh GetCategories read; touch the symbols
ensureCrossCategory, createdCategories.Load(key), categoryCreationGroup.Do(...)
and cacheMatches(...) when implementing this conditional skip.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 024cf577-dbff-479f-a3ba-109191042e42

📥 Commits

Reviewing files that changed from the base of the PR and between 31f2cd9 and 257a306.

📒 Files selected for processing (4)
  • internal/services/crossseed/category_cache_test.go
  • internal/services/crossseed/category_divergence_test.go
  • internal/services/crossseed/seasonpack.go
  • internal/services/crossseed/service.go

@s0up4200
s0up4200 merged commit 8b278e5 into develop Jun 3, 2026
13 checks passed
@s0up4200
s0up4200 deleted the fix/crossseed-divergent-category-savepath branch June 3, 2026 21:48
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request Jun 9, 2026
… (#950)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.19.0` → `v1.20.0` |

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.20.0`](https://github.com/autobrr/qui/releases/tag/v1.20.0)

[Compare Source](autobrr/qui@v1.19.0...v1.20.0)

##### Changelog

##### New Features

- [`9ad7ffe`](autobrr/qui@9ad7ffe): feat(automations): add include/exclude for trackers ([#&#8203;1953](autobrr/qui#1953)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#&#8203;1969](autobrr/qui#1969)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#&#8203;1990](autobrr/qui#1990)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#&#8203;1972](autobrr/qui#1972)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#&#8203;1956](autobrr/qui#1956)) ([@&#8203;OlziYT](https://github.com/OlziYT))
- [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#&#8203;1965](autobrr/qui#1965)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#&#8203;1732](autobrr/qui#1732)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#&#8203;551](autobrr/qui#551)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#&#8203;1945](autobrr/qui#1945)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results  ([#&#8203;1944](autobrr/qui#1944)) ([@&#8203;GtwoK](https://github.com/GtwoK))

##### Bug Fixes

- [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#&#8203;1964](autobrr/qui#1964)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#&#8203;1986](autobrr/qui#1986)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#&#8203;1912](autobrr/qui#1912)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#&#8203;1993](autobrr/qui#1993)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#&#8203;1981](autobrr/qui#1981)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#&#8203;1955](autobrr/qui#1955)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#&#8203;1948](autobrr/qui#1948)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#&#8203;1960](autobrr/qui#1960)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#&#8203;1994](autobrr/qui#1994)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#&#8203;1992](autobrr/qui#1992)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#&#8203;1979](autobrr/qui#1979)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#&#8203;1985](autobrr/qui#1985)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))

##### Other Changes

- [`1006338`](autobrr/qui@1006338): chore(deps): bump actions/checkout from 6 to 6.0.2 in the github group ([#&#8203;1999](autobrr/qui#1999)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#&#8203;2007](autobrr/qui#2007)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#&#8203;1937](autobrr/qui#1937)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#&#8203;1959](autobrr/qui#1959)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#&#8203;1988](autobrr/qui#1988)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#&#8203;1989](autobrr/qui#1989)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#&#8203;1946](autobrr/qui#1946)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#&#8203;1966](autobrr/qui#1966)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#&#8203;1936](autobrr/qui#1936)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#&#8203;1957](autobrr/qui#1957)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#&#8203;1995](autobrr/qui#1995)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#&#8203;1740](autobrr/qui#1740)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#&#8203;1980](autobrr/qui#1980)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#&#8203;1976](autobrr/qui#1976)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#&#8203;1970](autobrr/qui#1970)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#&#8203;1977](autobrr/qui#1977)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#&#8203;1974](autobrr/qui#1974)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#&#8203;1975](autobrr/qui#1975)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#&#8203;1978](autobrr/qui#1978)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#&#8203;1973](autobrr/qui#1973)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#&#8203;1971](autobrr/qui#1971)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#&#8203;1982](autobrr/qui#1982)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#&#8203;1968](autobrr/qui#1968)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#&#8203;1938](autobrr/qui#1938)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#&#8203;1987](autobrr/qui#1987)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#&#8203;1940](autobrr/qui#1940)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#&#8203;1962](autobrr/qui#1962)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))

**Full Changelog**: <autobrr/qui@v1.19.0...v1.20.0>

##### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.20.0`
- `docker pull ghcr.io/autobrr/qui:latest`

##### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/950
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Jun 10, 2026
… ) (#78)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.19.0` → `v1.20.0` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the Dependency Dashboard for more information.

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.20.0`](https://github.com/autobrr/qui/releases/tag/v1.20.0)

[Compare Source](autobrr/qui@v1.19.0...v1.20.0)

##### Changelog

##### New Features

- [`9ad7ffe`](autobrr/qui@9ad7ffe): feat(automations): add include/exclude for trackers ([#&#8203;1953](autobrr/qui#1953)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#&#8203;1969](autobrr/qui#1969)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#&#8203;1990](autobrr/qui#1990)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#&#8203;1972](autobrr/qui#1972)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#&#8203;1956](autobrr/qui#1956)) ([@&#8203;OlziYT](https://github.com/OlziYT))
- [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#&#8203;1965](autobrr/qui#1965)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#&#8203;1732](autobrr/qui#1732)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#&#8203;551](autobrr/qui#551)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#&#8203;1945](autobrr/qui#1945)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results  ([#&#8203;1944](autobrr/qui#1944)) ([@&#8203;GtwoK](https://github.com/GtwoK))

##### Bug Fixes

- [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#&#8203;1964](autobrr/qui#1964)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#&#8203;1986](autobrr/qui#1986)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#&#8203;1912](autobrr/qui#1912)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#&#8203;1993](autobrr/qui#1993)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#&#8203;1981](autobrr/qui#1981)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#&#8203;1955](autobrr/qui#1955)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#&#8203;1948](autobrr/qui#1948)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#&#8203;1960](autobrr/qui#1960)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#&#8203;1994](autobrr/qui#1994)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#&#8203;1992](autobrr/qui#1992)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#&#8203;1979](autobrr/qui#1979)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#&#8203;1985](autobrr/qui#1985)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))

##### Other Changes

- [`1006338`](autobrr/qui@1006338): chore(deps): bump actions/checkout from 6 to 6.0.2 in the github group ([#&#8203;1999](autobrr/qui#1999)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#&#8203;2007](autobrr/qui#2007)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#&#8203;1937](autobrr/qui#1937)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#&#8203;1959](autobrr/qui#1959)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#&#8203;1988](autobrr/qui#1988)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#&#8203;1989](autobrr/qui#1989)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#&#8203;1946](autobrr/qui#1946)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#&#8203;1966](autobrr/qui#1966)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#&#8203;1936](autobrr/qui#1936)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#&#8203;1957](autobrr/qui#1957)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#&#8203;1995](autobrr/qui#1995)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#&#8203;1740](autobrr/qui#1740)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#&#8203;1980](autobrr/qui#1980)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#&#8203;1976](autobrr/qui#1976)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#&#8203;1970](autobrr/qui#1970)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#&#8203;1977](autobrr/qui#1977)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#&#8203;1974](autobrr/qui#1974)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#&#8203;1975](autobrr/qui#1975)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#&#8203;1978](autobrr/qui#1978)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#&#8203;1973](autobrr/qui#1973)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#&#8203;1971](autobrr/qui#1971)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#&#8203;1982](autobrr/qui#1982)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#&#8203;1968](autobrr/qui#1968)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#&#8203;1938](autobrr/qui#1938)) ([@&#8203;Audionut](https://github.com/Audionut))
- [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#&#8203;1987](autobrr/qui#1987)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#&#8203;1940](autobrr/qui#1940)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))
- [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#&#8203;1962](autobrr/qui#1962)) ([@&#8203;nitrobass24](https://github.com/nitrobass24))

**Full Changelog**: <autobrr/qui@v1.19.0...v1.20.0>

##### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.20.0`
- `docker pull ghcr.io/autobrr/qui:latest`

##### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjE5NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/78
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend bug Something isn't working cross-seed go Pull requests that update go code tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant