Skip to content

Pause task-list polling when window is hidden#2335

Open
pauldambra wants to merge 6 commits into
mainfrom
posthog-code/poll-tasks-while-visible
Open

Pause task-list polling when window is hidden#2335
pauldambra wants to merge 6 commits into
mainfrom
posthog-code/poll-tasks-while-visible

Conversation

@pauldambra
Copy link
Copy Markdown
Member

@pauldambra pauldambra commented May 24, 2026

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:

  • Polling was a hot 30s timer that never paused. It kept hitting the API even when the window was hidden.
  • The on-return refetch was being silently swallowed. The global staleTime is 5 minutes and the global refetchOnWindowFocus is true, 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:

  • Gate refetchInterval on useRendererWindowFocusStore — polls every 3 minutes when the document is visible and the window has OS focus, otherwise stops.
  • Set 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?

  • Unit tests / typecheck on the touched file pass locally.
  • I could not run the Electron app in my sandbox, so the on-return refetch hasn't been observed in a real window. Worth a 30-second smoke before un-drafting: blur the window, create a task from another client, return to the window, confirm the new task appears immediately (from the forced focus refetch) rather than waiting up to 3 minutes for the next poll.

Publish to changelog?

no


Created with PostHog Code

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
@pauldambra pauldambra marked this pull request as ready for review May 24, 2026 12:53
@pauldambra pauldambra requested a review from a team May 24, 2026 12:55
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Reviews (1): Last reviewed commit: "refactor(code): drop poll backoff — flat..." | Re-trigger Greptile

Copy link
Copy Markdown
Member Author

@pauldambra pauldambra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 on useTasks.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 wires focus/blur/visibilitychange into its own focusManager over in queryClient.ts, and this store listens to the same three events for what feels like the same job. Worth checking whether focusManager.isFocused() (or just relying on TanStack's refetchIntervalInBackground default) could do the work without a second store. Not blocking — possibly already considered.
  • Naming clash — xp: useFocusStore (session/workspace focus) and useRendererWindowFocusStore (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

Comment thread apps/code/src/renderer/features/tasks/hooks/useTasks.ts Outdated
Comment thread apps/code/src/renderer/features/tasks/hooks/useTasks.ts Outdated
Comment thread apps/code/src/renderer/features/tasks/hooks/useTasks.ts Outdated
Comment thread apps/code/src/renderer/features/tasks/hooks/useTasks.ts Outdated
Comment thread apps/code/src/renderer/features/tasks/hooks/useTasks.ts Outdated
@pauldambra pauldambra added the Stamphog This will request an autostamp by stamphog on small changes label May 24, 2026
pauldambra and others added 2 commits May 24, 2026 14:05
…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
posthog Bot added a commit that referenced this pull request May 24, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Stamphog This will request an autostamp by stamphog on small changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant