#3351
Search Cross-Seeds dialog hides "Refresh from indexers" during its cooldown instead of showing it disabled with a countdown
This was generated by AI during triage.
Agent Brief
Category: bug Summary: The "Refresh from indexers" row in the Search Cross-Seeds dialog disappears while the 30-second refresh throttle or a search is running, so the disabled state and the "Ready in Ns" countdown never show, and the button comes back only on the next unrelated render.
Current behavior: After a user clicks Refresh from indexers on torrent A and then opens Search Cross-Seeds on torrent B within 30 seconds, the dialog for B shows the cached result but no refresh button. The button appears a few seconds later with no user action, and no countdown shows in the meantime.
useCrossSeedSearchpassesonForceRefreshtoCrossSeedDialogonly whencanForceCrossSeedRefreshis true.CrossSeedDialogrenders the refresh row only whenonForceRefreshis set. So the row'sdisabled={!canForceRefresh}and therefreshCooldownLabeltext (hooks.search.refreshReadyIn) never render: in every case where they would apply, the row is gone.- The same gating hides the row while a search is loading.
- The hook computes the remaining throttle time from
Date.now()during render. Nothing schedules a render when the throttle ends, so the button comes back only at the next unrelated render, such as the async-filtering poll. - The throttle timestamp (
crossSeedRefreshCooldownUntil) belongs to the hook, not to a torrent, andresetCrossSeedStatekeeps it. So torrent B gets the throttle that started on torrent A.
Desired behavior:
- While the dialog has a torrent, the refresh row always renders. During the throttle or a running search the button is disabled. During the throttle, the "Ready in Ns" label shows the remaining seconds.
- While the throttle runs, the label counts down once per second. When the throttle ends, the button turns enabled at once, with no other interaction.
- The timer stops when the throttle ends and when the dialog closes or unmounts.
- Keep the current scope of the throttle: it stays shared across torrents in the hook. Because the countdown now shows on torrent B too, the user can see why the button is disabled.
Key interfaces:
useCrossSeedSearch: passonForceRefreshwhenever a torrent is open, and letcanForceRefreshcontrol the disabled state. Make the component re-render on each second while the throttle runs.CrossSeedDialog: no prop changes are needed. Its existingcanForceRefresh/refreshCooldownLabelhandling must now take effect.
Acceptance criteria:
- With an open torrent and an active throttle, the refresh button renders disabled, and the "Ready in Ns" label shows.
- While a search is loading, the refresh button renders disabled. It is not removed.
- The countdown label goes down without user interaction, and the button turns enabled within about 1 second after the throttle ends.
- Opening the dialog on a second torrent within 30 seconds of a refresh on the first one shows the disabled button with the countdown.
- No interval keeps running after the throttle ends or after the dialog closes.
- A Vitest test with fake timers covers the disabled-to-enabled change of the hook or dialog.
Out of scope:
- Making the throttle per torrent or resetting it in
resetCrossSeedState. That is a product decision on how much indexer load to allow, and it is not needed to fix the missing button. - The backend search cache and the indexer rate-limit cooldown.
Domain:
- Glossary terms: none used. The 30-second client-side throttle on Refresh from indexers has no glossary entry. It is not the glossary's Rate-limit cooldown, which is the backend's 429 block per indexer.
- ADRs: None
- Owner:
useCrossSeedSearch(it owns the throttle state and the props that it passes toCrossSeedDialog)
Logs:
- None
From discussion #3350
Activity
0No activity yet.