Pause task-list polling when window is hidden#2335
Conversation
The task list polls every 30s for new tasks but never paused when the window lost focus, so the desktop kept fetching while the user was away. On return, the user was relying on `refetchOnWindowFocus` to surface tasks created elsewhere (e.g. cloud tasks created from mobile), and that didn't always feel reliable. Gate the `refetchInterval` on `useRendererWindowFocusStore` for the three task-list queries (`useTasks`, `useTaskSummaries`, `useSlackTasks`) so polling only runs when the document is visible and the window has OS focus. Bump the interval to 3 minutes — once focus is restored the existing `refetchOnWindowFocus: true` in `queryClient.ts` triggers an immediate fetch, so the in-foreground cadence doesn't need to be 30s. Generated-By: PostHog Code Task-Id: 4070b208-17d8-4eba-8b02-d8b6bbda6bbd
Two follow-ups to the original commit: 1. **Ramp polling, then back off.** A flat 3-minute interval was too slow right after the user returned to the desktop — tasks created elsewhere could take up to 3 minutes to appear. The list now polls at 30s for the first half-minute of focus, then 60s, then 120s, then settles at 180s. Each tier's threshold is set so it fires roughly once before promoting, which gives a fast catch-up without sustained API noise. 2. **Force refetch on return.** The global staleTime is 5 minutes, which meant `refetchOnWindowFocus: true` skipped the on-focus refetch when the user came back within that window — the exact "I just got back from a walk" case that motivated this whole change. Set `refetchOnWindowFocus: "always"` on the task-list queries so the refetch happens regardless of staleness. The focus store now also records `focusedAt` so the backoff knows when to reset. The transition is detected by comparing the previous focused state in the sync listener. Generated-By: PostHog Code Task-Id: 4070b208-17d8-4eba-8b02-d8b6bbda6bbd
Lock in the two behaviours the previous commits depend on: - `taskListPollInterval` — verify the tier boundaries (30s → 60s → 120s → 180s cap). Extracted the pure function into `features/tasks/utils/` so the test can import it without dragging in the trpc renderer client (which needs an Electron preload at import time). - `rendererWindowFocusStore` — verify the focus transition logic: `focusedAt` clears on blur, gets a fresh timestamp on focus, and is NOT reset by redundant focus events while already focused (which Electron can emit when a child frame regains focus). All 7 new tests pass. Generated-By: PostHog Code Task-Id: 4070b208-17d8-4eba-8b02-d8b6bbda6bbd
Applying XP simple-design rules to the previous commits: The 4-tier backoff (`taskListPollInterval`) existed because I added it, not because the user's bug required it. The actual bug — cloud tasks not appearing after returning to the desktop — is solved entirely by `refetchOnWindowFocus: "always"` overriding the 5-min staleTime that was silently swallowing the on-focus refetch. Once the backoff goes, its supporting cast goes too: - `taskListPollInterval` (4-tier ramp with magic thresholds) — deleted. - `focusedAt` timestamp on `rendererWindowFocusStore` — only existed to feed the ramp; reverted to the original boolean-only store. - Both new test files — they were guarding code that no longer exists. What remains: each task-list query polls every 3 min when focused, not at all when hidden, and force-refetches on focus return regardless of staleness. That's the whole feature. No new tests in this commit either — the change is a one-line ternary plus a declarative TanStack Query option. The behavior worth protecting (refetchOnWindowFocus: "always" overriding staleTime) is a library contract, not our logic. Tests would be testing TanStack Query, not us. Generated-By: PostHog Code Task-Id: 4070b208-17d8-4eba-8b02-d8b6bbda6bbd
|
Reviews (1): Last reviewed commit: "refactor(code): drop poll backoff — flat..." | Re-trigger Greptile |
pauldambra
left a comment
There was a problem hiding this comment.
Note
🤖 Automated comment by QA Swarm — not written by a human
Multi-perspective review: qa-team (specialists + generalists), paul-reviewer, xp-reviewer, security-audit.
Verdict: 💬 APPROVE WITH NITS
Clean, small, well-motivated change. The final-commit XP-style simplification (drop the tiered backoff, lean on refetchOnWindowFocus: "always") is a good design move. No security or correctness issues. The one finding with real weight is the 6x interval bump (30s → 3min) for users keeping the window focused — see inline comment on line 18. Most other notes are NITs around DRY (three identical option blocks across the three hooks) and naming.
Convergence (independent reviewers landed on the same things)
- Three places repeating the same
refetchInterval+refetchOnWindowFocus: "always"pair — flagged by qa-team/generalist-a, paul, and xp (3 reviewers). See inline onuseTasks.ts:83. - 30s → 3min interval bump may make focused-and-watching feel slow — flagged by qa-team/data-integrity (MEDIUM) + paul (NIT). See inline on
useTasks.ts:18.
Off-diff observations (no inline anchor — flagging here)
apps/code/src/renderer/stores/rendererWindowFocusStore.ts:5— qa-team/frontend: the JSDoc says "Used to pause inbox polling when the Electron window is in the background." This store now also drives task-list polling (and is imported elsewhere). Generalize, e.g. "Used to pause polling-style queries when the Electron window is in the background."apps/code/src/renderer/stores/rendererWindowFocusStore.ts— paul: TanStack Query already wiresfocus/blur/visibilitychangeinto its ownfocusManagerover inqueryClient.ts, and this store listens to the same three events for what feels like the same job. Worth checking whetherfocusManager.isFocused()(or just relying on TanStack'srefetchIntervalInBackgrounddefault) could do the work without a second store. Not blocking — possibly already considered.- Naming clash — xp:
useFocusStore(session/workspace focus) anduseRendererWindowFocusStore(OS window visibility) are close enough that a future reader will mis-import. Outside this PR's scope; flag as a follow-up rename candidate (e.g.useWindowVisibilityStore).
Reviewer summaries
| Reviewer | Assessment |
|---|---|
| 🔍 qa-team | APPROVE WITH NITS — one MEDIUM (interval bump trade-off), the rest LOW/NIT. |
| 👤 paul | approve with comments — nice catch on the staleTime/refetchOnWindowFocus interaction; main poke is whether the new store duplicates TanStack's focusManager. |
| 📐 xp | Good XP-shaped PR — the final-commit deletion of the backoff + tests was the right call. One real finding: collapse the three repeated option triples. |
| 🛡 security-audit | No findings. Client-side polling cadence change only — no new endpoint, sink, auth surface, or input handling. |
Automated by QA Swarm — not a human review
…test Addressing review feedback on PR #2335: - **DRY** (convergent finding from 3 reviewers): the trio of `refetchInterval`, `refetchOnWindowFocus: "always"`, and the focus store subscription was repeated across `useTasks`, `useTaskSummaries`, and `useSlackTasks`. Collapsed into a single `useTaskListQueryOptions` hook spread into each query. The "why always" comment now lives once, next to the option it explains. - **Naming**: renamed `TASK_LIST_POLL_INTERVAL_MS` to `TASK_LIST_FOCUSED_POLL_INTERVAL_MS` — the value only applies while the window is focused, so the name should say so. - **Test**: added `useTaskListQueryOptions.test.ts` covering the three contracts (paused when not focused, polling at the focused interval, forced `"always"` refetch on focus). The hook is the integration point between the focus store and TanStack Query — exactly the kind of glue that quietly breaks under refactors. - **Doc**: generalised the JSDoc on `rendererWindowFocusStore` — it no longer only powers inbox polling. Deliberately not addressed: - 30s→3min interval bump (MEDIUM): author themselves framed it as "not blocking, worth a beat before merging"; leaving the choice to the human reviewer rather than overriding the interval the user already chose in chat. - 3 simultaneous refetches on focus, focusManager duplication, useFocusStore naming clash: all flagged "not a blocker / outside scope" by the reviewer themselves. Generated-By: PostHog Code Task-Id: 4070b208-17d8-4eba-8b02-d8b6bbda6bbd
Polling cadence (and the focus-gated 3-min interval) was carrying no weight: `refetchOnWindowFocus: "always"` overriding the global 5-min staleTime is what actually surfaces tasks created elsewhere when the user returns to the desktop. The polling on top was an optimisation for a problem we hadn't observed. - `useTaskListQueryOptions` (the hook) had a single piece of state — the focus subscription used to derive `refetchInterval`. With polling gone there is no state to subscribe to, so demote to a plain `TASK_LIST_QUERY_OPTIONS` const and rename the file to match. Three call sites spread the const directly. - Delete `useTaskListQueryOptions.test.ts`: two of its three assertions covered the removed polling behaviour; the third (`refetchOnWindowFocus: "always"`) is a TanStack Query library contract and not ours to test. - `TASK_LIST_FOCUSED_POLL_INTERVAL_MS` removed alongside. Generated-By: PostHog Code Task-Id: 5b3f974d-074d-4bd8-aaff-974a34db5614
The cloud task list polled every 30s on a fixed timer regardless of window visibility, and the global 5-minute staleTime caused the implicit refetchOnWindowFocus path to silently skip the refetch when returning within that window — so tasks created elsewhere (e.g. on mobile while away from desktop) never appeared until the user manually reloaded. Add a visibility-aware adaptive poll in useTasks / useTaskSummaries / useSlackTasks: start at 30s while the window is focused, exponentially back off to a 3-minute ceiling, pause entirely while blurred or hidden, and invalidate the list immediately on focus return so the refresh happens explicitly rather than going through TanStack's staleness gate. Add useTasks.test.tsx covering the polling cadence, pause-on-hidden, the focus-return invalidate, and that the initial mount does not trigger an extra invalidate. Refs #2335. Generated-By: PostHog Code Task-Id: 0707617f-64d9-4a1b-b45a-b2e22e5cfc25
Problem
If you make cloud tasks somewhere else (e.g. PostHog Code mobile while on a walk) and then come back to the desktop, the new tasks don't appear in the list — not "slowly", not at all. Two things were going wrong:
staleTimeis 5 minutes and the globalrefetchOnWindowFocusistrue, which means "refetch on focus if the query is stale". Returning within 5 minutes left the query "fresh", so no refetch fired — and 5 minutes is exactly the window where this hurts.Changes
In
apps/code/src/renderer/features/tasks/hooks/useTasks.ts, the three task-list queries (useTasks,useTaskSummaries,useSlackTasks) now:refetchIntervalonuseRendererWindowFocusStore— polls every 3 minutes when the document is visible and the window has OS focus, otherwise stops.refetchOnWindowFocus: "always"so the return-to-desktop refetch fires regardless of staleness. This is the line that actually fixes the bug.That's the whole change — one helper hook, one option per query.
How did you test this?
Publish to changelog?
no
Created with PostHog Code