Pause watch polling automatically while sessions are idle#520
Pause watch polling automatically while sessions are idle#520tridha643 wants to merge 4 commits into
Conversation
Greptile SummaryThis PR introduces automatic idle detection for
Confidence Score: 5/5Safe to merge. The idle/active state machine is correct, the shared in-flight guard reliably prevents duplicate refreshes across the wake-up and polling paths, and the new integration tests cover the key lifecycle transitions. The core logic — idle timeout scheduling, guard-based refresh coalescing, and polling lifecycle tied to watchShouldPoll — is sound and well-tested. The only finding is that every keystroke unconditionally increments watchActivityRevision, causing a render and idle-timeout effect re-run even during continuous active use; this is a minor efficiency concern with no correctness impact. src/ui/App.tsx — the watchActivityRevision increment on every interaction is worth revisiting for render efficiency. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant U as User
participant A as App.tsx
participant T as IdleTimeout (useEffect)
participant G as watchRefreshInFlightRef
participant P as Polling (setInterval)
participant R as refreshCurrentInput
Note over A,P: Session starts active, polling running
P->>G: check guard (false)
P->>R: refreshWatchedInput (file changed)
G-->>P: "guard=true"
R-->>G: "finally → guard=false"
Note over A,T: 60s without input elapses
T->>A: setWatchSessionActivity("idle")
A-->>P: "watchShouldPoll=false → interval cleared"
Note over A,U: File changes while idle — not detected
U->>A: keypress / click / scroll
A->>A: markWatchSessionActivity()
A->>G: check guard (false)
A->>R: refreshWatchedInput (wake-up, immediate)
G-->>A: "guard=true"
A->>A: setWatchActivity("active")
A->>T: revision++ → new idle countdown
A-->>P: "watchShouldPoll=true → new interval"
P->>G: check guard (true, wake-up in flight)
G-->>P: returns false, skip tick
R-->>G: "wake-up refresh done → guard=false"
Note over A,P: bootstrap.input updates → polling effect re-runs with fresh lastSignature
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant U as User
participant A as App.tsx
participant T as IdleTimeout (useEffect)
participant G as watchRefreshInFlightRef
participant P as Polling (setInterval)
participant R as refreshCurrentInput
Note over A,P: Session starts active, polling running
P->>G: check guard (false)
P->>R: refreshWatchedInput (file changed)
G-->>P: "guard=true"
R-->>G: "finally → guard=false"
Note over A,T: 60s without input elapses
T->>A: setWatchSessionActivity("idle")
A-->>P: "watchShouldPoll=false → interval cleared"
Note over A,U: File changes while idle — not detected
U->>A: keypress / click / scroll
A->>A: markWatchSessionActivity()
A->>G: check guard (false)
A->>R: refreshWatchedInput (wake-up, immediate)
G-->>A: "guard=true"
A->>A: setWatchActivity("active")
A->>T: revision++ → new idle countdown
A-->>P: "watchShouldPoll=true → new interval"
P->>G: check guard (true, wake-up in flight)
G-->>P: returns false, skip tick
R-->>G: "wake-up refresh done → guard=false"
Note over A,P: bootstrap.input updates → polling effect re-runs with fresh lastSignature
Reviews (3): Last reviewed commit: "refactor: make watch sleep automatic" | Re-trigger Greptile |
Summary
--watchsession into an automatic low-cost sleep after one minute without keyboard or mouse interaction.Why
It is easy to leave several
hunk diff --watchtabs open while agents continue changing a repository. Those unattended sessions previously kept polling Git every 250 ms. Automatic sleep removes that background cost without asking users to configure each tab.Background diff reloads do not count as user activity, so a busy repository cannot keep an unattended session awake forever.
Testing
bun run typecheckbun run lintbun test ./src ./packages ./scripts ./test/cli ./test/session(1,012 passed, 13 skipped)bun test ./test/pty(51 passed)HUNK_RUN_TTY_SMOKE=1 bun test ./test/smoke(9 passed)Review guide
src/core/watch.tsfor the built-in inactivity threshold.src/ui/App.tsxfor activity tracking, sleep/wake transitions, and refresh coalescing.src/ui/AppHost.interactions.test.tsxfor the lifecycle and background-reload regressions.Tradeoff
A passively viewed watch session stops updating after one minute without input. The next key, click, drag, or scroll performs an immediate refresh before normal polling resumes.