Skip to content

refactor(web): extract useTorrentSelectionDerivations - #1975

Merged
s0up4200 merged 8 commits into
developfrom
feat/1963-pr05-selection-derivations
Jun 3, 2026
Merged

refactor(web): extract useTorrentSelectionDerivations#1975
s0up4200 merged 8 commits into
developfrom
feat/1963-pr05-selection-derivations

Conversation

@nitrobass24

@nitrobass24 nitrobass24 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Strict merge order — #1963 decomposition stack

These PRs edit TorrentTableOptimized.tsx sequentially and must be merged strictly in order. Merging out of order will cause conflicts and force rebases of every downstream PR.

Order: #1970#1971#1973#1974#1975#1976#1977#1978#1980#1982

This is PR-05 (position 5). ⛔ Do not merge before #1970, #1971, #1973, and #1974.


Summary

Fifth extraction in the TorrentTableOptimized.tsx decomposition (#1963). Pulls the selection-derived values that bulk actions and dialogs depend on into a dedicated hook — and pins the #1925 invariant.

No behavior change — logic relocated verbatim; verified by an adversarial behavior-preservation review.

Extracted — hooks/torrent-table/useTorrentSelectionDerivations.ts

selectedHashes, effectiveSelectionCount, selectedTorrents, selectedTotalSize (+ formatted), the delete-dialog sizes, and the select-all targeting inputs selectAllFilters, selectAllExcludedTargets, selectAllExcludeHashes.

The #1925 pin (the other half)

selectAllFilters ALWAYS combines columnFiltersExpr with filters.expr when both are present — it must NOT adopt the cross-seed early-return that useTorrentTableFilterExpr.combinedFiltersExpr uses (PR-03). A named regression test asserts that with a column filter active and a cross-seed (Hash ==/||) expr, selectAllFilters.expr === "(state == \"downloading\") && (Hash == \"abc\" || Hash == \"def\")". Together with PR-03's opposite assertion, the divergence is now locked from both sides.

Notes

  • Visible-row access goes through an injected getVisibleRows() callback (reusing PR-04's SelectionRow shape) so the hook is unit-testable.
  • torrentIdentityCounts was intentionally left in the component — it feeds useReactTable's getRowId and must stay before the table (belongs to the later column-wiring PR).

TorrentTableOptimized.tsx drops from 3007 → 2894 lines.

Tests

16 renderHook tests: the #1925 always-combine pin (+ the no-column / no-filter / not-select-all variants), selectedHashes/selectedTorrents resolution (regular + select-all-minus-exclusions), effectiveSelectionCount, selectedTotalSize (sum vs aggregate-minus-excluded), selectAllExcludeHashes (undefined for cross-instance), excluded-targets, and delete-dialog sizing.

Testing

Summary by CodeRabbit

  • Refactor

    • Refactored torrent selection logic for improved code organization and maintainability.
  • Tests

    • Added comprehensive test suite for selection derivation functionality, covering regular and select-all modes with various filtering and exclusion scenarios.

Pull pure helpers (getRowBackgroundClass, getStatusBadgeVariant,
getStatusBadgeProps, shallowEqualTrackerIcons) and the TrackerIcon /
CompactRow sub-components out of TorrentTableOptimized into
lib/torrent-table/ and components/torrents/table/, with colocated tests
and a whole-table mount smoke test as a cross-PR regression tripwire.

Code is relocated verbatim; no behavior change. First extraction for the
TorrentTableOptimized decomposition (#1963). Main component drops from
3656 to 3302 lines.
- TrackerIcon: track the failed src instead of resetting a boolean in an
  effect, so a new src renders its image immediately without a one-frame
  fallback flash (also drops the effect per the project React guidelines).
- CompactRow: document why the memo comparator intentionally omits handler
  props (callers pass inline lambdas; comparing them would defeat row
  memoization).
- Smoke test: exclude the header checkbox from the post-select-all
  assertion so it verifies row checkboxes actually became checked.
Move the tracker-icon / customization-lookup ref caching and the
per-instance effective-server-state caching out of TorrentTableOptimized
into dedicated hooks under hooks/torrent-table/, with renderHook unit
tests and a makeServerState test fixture.

Code is relocated verbatim; no behavior change. Part of #1963.
Move the search / filter-expression derivation out of TorrentTableOptimized
into a dedicated hook: debounced + route search -> effectiveSearch, column
filters -> columnFiltersExpr, cross-seed detection, and the combined backend
expression (with the #1925 cross-seed early-return) plus the user-action
tracker.

Drops the dead `immediateSearch` state (a setter-less useState("") that was
always ""), simplifying effectiveSearch; `navigate` stays in the component.
Adds renderHook tests (including the #1925 regression pin) and a makeFilters
test fixture.

No behavior change. Part of #1963.
Seed previousSearchRef with the initial effectiveSearch (instead of "") so a
route-derived search present on mount — e.g. loading a URL with ?q= — is not
mistaken for a user-initiated search and no longer emits a spurious
{type: "search"} lastUserAction. Adds a regression test.
Move the Gmail-style select-all-with-exclusions state, the row / select-all /
compact-checkbox handlers, the derived checked/indeterminate flags, and the
selection-validation effects (prune-invalid + empty-reset) out of
TorrentTableOptimized into a dedicated hook.

The validation effect and the shift-range compact handler now read the visible
rows via an injected getVisibleRows() callback instead of touching the table
directly, so the hook is unit-testable in isolation. Code is otherwise
relocated verbatim; no behavior change. Part of #1963.
Move the selection-derived values bulk actions and dialogs depend on
(selectedHashes, selectedTorrents, effectiveSelectionCount, selectedTotalSize,
delete-dialog sizes, selectAllFilters, selectAllExcludedTargets,
selectAllExcludeHashes) out of TorrentTableOptimized into a dedicated hook.

#1925 pin: selectAllFilters ALWAYS combines columnFiltersExpr with filters.expr
(no cross-seed early-return), unlike useTorrentTableFilterExpr's
combinedFiltersExpr. A named regression test locks the divergence.

Visible-row access goes through an injected getVisibleRows() callback so the
hook is unit-testable. torrentIdentityCounts stays in the component (it feeds
useReactTable.getRowId). Code is otherwise relocated verbatim; no behavior
change, verified by an adversarial review. Part of #1963.
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR extracts selection-derivation logic from TorrentTableOptimized.tsx into a reusable custom hook useTorrentSelectionDerivations. The hook centralizes calculations for resolved selection hashes, torrents, effective counts, total sizes, select-all filters, and exclusion targeting across regular and select-all modes. Comprehensive tests cover all selection modes and edge cases.

Changes

Selection Derivation Hook

Layer / File(s) Summary
Hook interface and parameters
web/src/hooks/torrent-table/useTorrentSelectionDerivations.ts
UseTorrentSelectionDerivationsParams interface defines the hook's input contract: selection mode, row/torrent data, visible rows, sizes, filters, and cross-instance context.
Selection resolution and effective count
web/src/hooks/torrent-table/useTorrentSelectionDerivations.ts, web/src/hooks/torrent-table/__tests__/useTorrentSelectionDerivations.test.ts
Core logic resolves selectedHashes and selectedTorrents from either visible row ids (regular mode) or all sorted torrents minus exclusions (select-all mode); effectiveSelectionCount reflects visible selection length or total-minus-exclusions accordingly; tests verify both modes and exclusion handling.
Size calculations for selection and delete dialogs
web/src/hooks/torrent-table/useTorrentSelectionDerivations.ts, web/src/hooks/torrent-table/__tests__/useTorrentSelectionDerivations.test.ts
selectedTotalSize sums selected torrents in regular mode and uses aggregate stats.totalSize (minus excluded sizes) in select-all mode; deleteDialogTotalSize prefers selected total in select-all and falls back to context torrents; both return formatted byte strings; tests cover regular/select-all modes with and without exclusions.
Filter and exclusion targeting
web/src/hooks/torrent-table/useTorrentSelectionDerivations.ts, web/src/hooks/torrent-table/__tests__/useTorrentSelectionDerivations.test.ts
selectAllFilters combines columnFiltersExpr and filters.expr via && only in select-all mode; selectAllExcludedTargets and selectAllExcludeHashes build action targets for excluded torrents with cross-instance suppression; tests verify filter combination (including cross-seed regression) and exclusion derivations.
Component refactoring to use new hook
web/src/components/torrents/TorrentTableOptimized.tsx
TorrentTableOptimized imports useTorrentSelectionDerivations and destructures all selection-derived values from the hook, removing inline useMemo blocks for selectedHashes, selectedTorrents, effectiveSelectionCount, sizes, filters, and exclusion targets; getTotalSize removed from torrent-utils imports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • autobrr/qui#530: Both modify selection state derivation/maintenance in TorrentTableOptimized.tsx.
  • autobrr/qui#1926: Both move selectAllFilters construction and explicitly combine columnFiltersExpr with filters.expr via &&.
  • autobrr/qui#1455: Both adjust selection state and behavior in TorrentTableOptimized.tsx.

Suggested labels

refactor, web, torrent, tests

Suggested reviewers

  • s0up4200
  • zze0s

Poem

🐰 Hops through selection states so clear,
Derived values now consolidate here,
Hook logic dances, filters combine,
Select-all and regular, aligned just fine!
Tests verify every mode with care,
Selection refactored with such flair!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Title check ✅ Passed The title 'refactor(web): extract useTorrentSelectionDerivations' accurately and specifically describes the main change: extracting selection derivation logic into a new hook.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 feat/1963-pr05-selection-derivations

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.

…ection-derivations

# Conflicts:
#	web/src/components/torrents/TorrentTableOptimized.tsx
@s0up4200
s0up4200 merged commit cf84390 into develop Jun 3, 2026
13 checks passed
@s0up4200
s0up4200 deleted the feat/1963-pr05-selection-derivations branch June 3, 2026 10:50
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants