#3351

Search Cross-Seeds dialog hides "Refresh from indexers" during its cooldown instead of showing it disabled with a countdown

Opengithub-actions[bot]opened 9d ago

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.

  • useCrossSeedSearch passes onForceRefresh to CrossSeedDialog only when canForceCrossSeedRefresh is true. CrossSeedDialog renders the refresh row only when onForceRefresh is set. So the row's disabled={!canForceRefresh} and the refreshCooldownLabel text (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, and resetCrossSeedState keeps 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: pass onForceRefresh whenever a torrent is open, and let canForceRefresh control the disabled state. Make the component re-render on each second while the throttle runs.
  • CrossSeedDialog: no prop changes are needed. Its existing canForceRefresh / refreshCooldownLabel handling 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 to CrossSeedDialog)

Logs:

  • None

From discussion #3350

Activity

0

No activity yet.