refactor(web): extract useTorrentSelectionDerivations - #1975
Conversation
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.
WalkthroughThis PR extracts selection-derivation logic from TorrentTableOptimized.tsx into a reusable custom hook ChangesSelection Derivation Hook
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…ection-derivations # Conflicts: # web/src/components/torrents/TorrentTableOptimized.tsx
… (#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 ([#​1953](autobrr/qui#1953)) ([@​martylukyy](https://github.com/martylukyy)) - [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#​1969](autobrr/qui#1969)) ([@​martylukyy](https://github.com/martylukyy)) - [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#​1990](autobrr/qui#1990)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#​1972](autobrr/qui#1972)) ([@​s0up4200](https://github.com/s0up4200)) - [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#​1956](autobrr/qui#1956)) ([@​OlziYT](https://github.com/OlziYT)) - [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#​1965](autobrr/qui#1965)) ([@​s0up4200](https://github.com/s0up4200)) - [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#​1732](autobrr/qui#1732)) ([@​s0up4200](https://github.com/s0up4200)) - [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#​551](autobrr/qui#551)) ([@​s0up4200](https://github.com/s0up4200)) - [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#​1945](autobrr/qui#1945)) ([@​martylukyy](https://github.com/martylukyy)) - [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results ([#​1944](autobrr/qui#1944)) ([@​GtwoK](https://github.com/GtwoK)) ##### Bug Fixes - [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#​1964](autobrr/qui#1964)) ([@​s0up4200](https://github.com/s0up4200)) - [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#​1986](autobrr/qui#1986)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#​1912](autobrr/qui#1912)) ([@​Audionut](https://github.com/Audionut)) - [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#​1993](autobrr/qui#1993)) ([@​s0up4200](https://github.com/s0up4200)) - [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#​1981](autobrr/qui#1981)) ([@​Audionut](https://github.com/Audionut)) - [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#​1955](autobrr/qui#1955)) ([@​s0up4200](https://github.com/s0up4200)) - [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#​1948](autobrr/qui#1948)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#​1960](autobrr/qui#1960)) ([@​s0up4200](https://github.com/s0up4200)) - [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#​1994](autobrr/qui#1994)) ([@​s0up4200](https://github.com/s0up4200)) - [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#​1992](autobrr/qui#1992)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#​1979](autobrr/qui#1979)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#​1985](autobrr/qui#1985)) ([@​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 ([#​1999](autobrr/qui#1999)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#​2007](autobrr/qui#2007)) ([@​s0up4200](https://github.com/s0up4200)) - [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#​1937](autobrr/qui#1937)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#​1959](autobrr/qui#1959)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#​1988](autobrr/qui#1988)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#​1989](autobrr/qui#1989)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#​1946](autobrr/qui#1946)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#​1966](autobrr/qui#1966)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#​1936](autobrr/qui#1936)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#​1957](autobrr/qui#1957)) ([@​martylukyy](https://github.com/martylukyy)) - [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#​1995](autobrr/qui#1995)) ([@​Audionut](https://github.com/Audionut)) - [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#​1740](autobrr/qui#1740)) ([@​s0up4200](https://github.com/s0up4200)) - [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#​1980](autobrr/qui#1980)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#​1976](autobrr/qui#1976)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#​1970](autobrr/qui#1970)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#​1977](autobrr/qui#1977)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#​1974](autobrr/qui#1974)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#​1975](autobrr/qui#1975)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#​1978](autobrr/qui#1978)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#​1973](autobrr/qui#1973)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#​1971](autobrr/qui#1971)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#​1982](autobrr/qui#1982)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#​1968](autobrr/qui#1968)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#​1938](autobrr/qui#1938)) ([@​Audionut](https://github.com/Audionut)) - [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#​1987](autobrr/qui#1987)) ([@​s0up4200](https://github.com/s0up4200)) - [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#​1940](autobrr/qui#1940)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#​1962](autobrr/qui#1962)) ([@​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
… ) (#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 ([#​1953](autobrr/qui#1953)) ([@​martylukyy](https://github.com/martylukyy)) - [`e73867f`](autobrr/qui@e73867f): feat(ci): build binaries for develop branch ([#​1969](autobrr/qui#1969)) ([@​martylukyy](https://github.com/martylukyy)) - [`2020847`](autobrr/qui@2020847): feat(crossseed): match cross-tracker relabels and alternate and/& title spellings ([#​1990](autobrr/qui#1990)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`719866b`](autobrr/qui@719866b): feat(crossseed): route season packs to categories by resolution and source ([#​1972](autobrr/qui#1972)) ([@​s0up4200](https://github.com/s0up4200)) - [`a6e22ba`](autobrr/qui@a6e22ba): feat(i18n): add French translation, dynamic loading, and browser locale auto-detect ([#​1956](autobrr/qui#1956)) ([@​OlziYT](https://github.com/OlziYT)) - [`77c6ea8`](autobrr/qui@77c6ea8): feat(i18n): add German (de) translation ([#​1965](autobrr/qui#1965)) ([@​s0up4200](https://github.com/s0up4200)) - [`fc8664b`](autobrr/qui@fc8664b): feat(i18n): add multi-language support with Simplified Chinese ([#​1732](autobrr/qui#1732)) ([@​s0up4200](https://github.com/s0up4200)) - [`11ace50`](autobrr/qui@11ace50): feat(sse): stream torrent and activity updates, replacing background polling ([#​551](autobrr/qui#551)) ([@​s0up4200](https://github.com/s0up4200)) - [`9b7ed6c`](autobrr/qui@9b7ed6c): feat(torrents): filter for torrents with other tracker errors ([#​1945](autobrr/qui#1945)) ([@​martylukyy](https://github.com/martylukyy)) - [`62bbdb4`](autobrr/qui@62bbdb4): feat(web): virtualized and memoized the indexer search results ([#​1944](autobrr/qui#1944)) ([@​GtwoK](https://github.com/GtwoK)) ##### Bug Fixes - [`b10ef98`](autobrr/qui@b10ef98): fix(api): remove WriteTimeout that aborted downloads and SSE at 120s ([#​1964](autobrr/qui#1964)) ([@​s0up4200](https://github.com/s0up4200)) - [`255bb3e`](autobrr/qui@255bb3e): fix(arr): make arr\_id\_cache expiry timezone-independent ([#​1986](autobrr/qui#1986)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`4c1d743`](autobrr/qui@4c1d743): fix(crossseed): harden filesystem fallback and post-add rechecks ([#​1912](autobrr/qui#1912)) ([@​Audionut](https://github.com/Audionut)) - [`8b278e5`](autobrr/qui@8b278e5): fix(crossseed): pin explicit savepath when cross category save path diverges ([#​1993](autobrr/qui#1993)) ([@​s0up4200](https://github.com/s0up4200)) - [`cab1e0f`](autobrr/qui@cab1e0f): fix(gazelle): pre-search content matching and api limits ([#​1981](autobrr/qui#1981)) ([@​Audionut](https://github.com/Audionut)) - [`c3fa169`](autobrr/qui@c3fa169): fix(i18n): translate torrent state labels ([#​1955](autobrr/qui#1955)) ([@​s0up4200](https://github.com/s0up4200)) - [`ccb601d`](autobrr/qui@ccb601d): fix(proxy): forward API key auth to qBittorrent on passthrough requests ([#​1948](autobrr/qui#1948)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`3cba046`](autobrr/qui@3cba046): fix(qbittorrent): match 'repack available'/'grab internal' as unregistered ([#​1960](autobrr/qui#1960)) ([@​s0up4200](https://github.com/s0up4200)) - [`591941a`](autobrr/qui@591941a): fix(sse): pause torrent-list stream while the tab is hidden ([#​1994](autobrr/qui#1994)) ([@​s0up4200](https://github.com/s0up4200)) - [`f75a1eb`](autobrr/qui@f75a1eb): fix(sse): stop intermittent "offline" flips that fall back to polling ([#​1992](autobrr/qui#1992)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`c720c39`](autobrr/qui@c720c39): fix(web): emit allowSubcategories from the filtered-data callback ([#​1979](autobrr/qui#1979)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`602d440`](autobrr/qui@602d440): fix(web): preserve paginated rows on unified-view SSE updates ([#​1985](autobrr/qui#1985)) ([@​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 ([#​1999](autobrr/qui#1999)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`667d654`](autobrr/qui@667d654): chore(deps): bump rls to v0.8.1 ([#​2007](autobrr/qui#2007)) ([@​s0up4200](https://github.com/s0up4200)) - [`cc7d70a`](autobrr/qui@cc7d70a): chore(deps): bump the github group with 5 updates ([#​1937](autobrr/qui#1937)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`cc49a49`](autobrr/qui@cc49a49): chore(deps): bump the github group with 5 updates ([#​1959](autobrr/qui#1959)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`eff1a65`](autobrr/qui@eff1a65): chore(deps): bump the golang group with 14 updates ([#​1988](autobrr/qui#1988)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`11da2c7`](autobrr/qui@11da2c7): chore(deps): bump the npm group in /web with 11 updates ([#​1989](autobrr/qui#1989)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`33aad41`](autobrr/qui@33aad41): chore(deps): bump the npm group in /web with 28 updates ([#​1946](autobrr/qui#1946)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`5c74fcf`](autobrr/qui@5c74fcf): chore(refactor): remove explicit any and enable no-explicit-any rule ([#​1966](autobrr/qui#1966)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fefa624`](autobrr/qui@fefa624): chore(web): add vitest test infrastructure and CI gating ([#​1936](autobrr/qui#1936)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`90b573a`](autobrr/qui@90b573a): chore(web): remove unused code and packages ([#​1957](autobrr/qui#1957)) ([@​martylukyy](https://github.com/martylukyy)) - [`078fae9`](autobrr/qui@078fae9): docs(agents): scope agent guidance ([#​1995](autobrr/qui#1995)) ([@​Audionut](https://github.com/Audionut)) - [`264ccfb`](autobrr/qui@264ccfb): refactor(go): remove dead code ([#​1740](autobrr/qui#1740)) ([@​s0up4200](https://github.com/s0up4200)) - [`48d6f57`](autobrr/qui@48d6f57): refactor(web): extract TorrentTableDialogs from TorrentTableOptimized ([#​1980](autobrr/qui#1980)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`022f553`](autobrr/qui@022f553): refactor(web): extract compact-sort, cross-seed, and hotkey hooks ([#​1976](autobrr/qui#1976)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`298c78c`](autobrr/qui@298c78c): refactor(web): extract torrent-table display helpers and row components ([#​1970](autobrr/qui#1970)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`a5b741a`](autobrr/qui@a5b741a): refactor(web): extract useBulkActionWrappers ([#​1977](autobrr/qui#1977)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`d4a051e`](autobrr/qui@d4a051e): refactor(web): extract useTorrentSelection ([#​1974](autobrr/qui#1974)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`cf84390`](autobrr/qui@cf84390): refactor(web): extract useTorrentSelectionDerivations ([#​1975](autobrr/qui#1975)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`18adff9`](autobrr/qui@18adff9): refactor(web): extract useTorrentTableColumns and useTorrentTableNotifications ([#​1978](autobrr/qui#1978)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`eb336ac`](autobrr/qui@eb336ac): refactor(web): extract useTorrentTableFilterExpr ([#​1973](autobrr/qui#1973)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`f1556e8`](autobrr/qui@f1556e8): refactor(web): extract useTrackerIconCache and useEffectiveServerState ([#​1971](autobrr/qui#1971)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`31f2cd9`](autobrr/qui@31f2cd9): refactor(web): extract virtualization, filter-lifecycle FSM, and column DnD from TorrentTableOptimized ([#​1982](autobrr/qui#1982)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`fdd0e56`](autobrr/qui@fdd0e56): refactor(web): split types/index.ts into domain modules behind a barrel ([#​1968](autobrr/qui#1968)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`2cca076`](autobrr/qui@2cca076): test(db): speed up migrated database tests ([#​1938](autobrr/qui#1938)) ([@​Audionut](https://github.com/Audionut)) - [`a10e697`](autobrr/qui@a10e697): test(sse): warm up subscription before coalescing burst ([#​1987](autobrr/qui#1987)) ([@​s0up4200](https://github.com/s0up4200)) - [`05fda2a`](autobrr/qui@05fda2a): test(web): add unit tests for Priority 1 pure utilities ([#​1940](autobrr/qui#1940)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`8df70a5`](autobrr/qui@8df70a5): test: fan out testdb helper to remaining database.New callers ([#​1962](autobrr/qui#1962)) ([@​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
Summary
Fifth extraction in the
TorrentTableOptimized.tsxdecomposition (#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.tsselectedHashes,effectiveSelectionCount,selectedTorrents,selectedTotalSize(+ formatted), the delete-dialog sizes, and the select-all targeting inputsselectAllFilters,selectAllExcludedTargets,selectAllExcludeHashes.The #1925 pin (the other half)
selectAllFiltersALWAYS combinescolumnFiltersExprwithfilters.exprwhen both are present — it must NOT adopt the cross-seed early-return thatuseTorrentTableFilterExpr.combinedFiltersExpruses (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
getVisibleRows()callback (reusing PR-04'sSelectionRowshape) so the hook is unit-testable.torrentIdentityCountswas intentionally left in the component — it feedsuseReactTable'sgetRowIdand must stay before the table (belongs to the later column-wiring PR).TorrentTableOptimized.tsxdrops from 3007 → 2894 lines.Tests
16
renderHooktests: the #1925 always-combine pin (+ the no-column / no-filter / not-select-all variants),selectedHashes/selectedTorrentsresolution (regular + select-all-minus-exclusions),effectiveSelectionCount,selectedTotalSize(sum vs aggregate-minus-excluded),selectAllExcludeHashes(undefined for cross-instance), excluded-targets, and delete-dialog sizing.Testing
pnpm test— 445/445 pass (incl. the whole-table smoke test)tsc -bclean; test-config typecheck cleanpnpm lint— new files 0 problemspnpm buildsucceedsSummary by CodeRabbit
Refactor
Tests