Skip to content

perf(gui): share one timer per interval and skip redundant revisit fetches - #1857

Merged
lidge-jun merged 4 commits into
devfrom
codex/gui-poll-consolidation
Aug 16, 2026
Merged

perf(gui): share one timer per interval and skip redundant revisit fetches#1857
lidge-jun merged 4 commits into
devfrom
codex/gui-poll-consolidation

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Layer 4, the performance half: one timer per cadence, and a tab revisit that costs nothing.

Nine dashboard resources polling at 5s meant nine separate timers waking to do the same thing. Poll scheduling now lives in interval buckets — a cadence costs one wakeup no matter how many stores share it. Membership changes are bookkeeping, empty buckets are deleted rather than merely stopped, and layer 3's hidden-tab rule moves to bucket granularity with a single visibility listener re-evaluating every bucket. Per-store skip rules still run inside the tick, so opt-out subscribers keep working while paused peers stay silent.

The second half is where the request volume actually drops. Every route change evicts the store, so returning to a tab used to re-fetch everything — the Integrations overview alone re-issued eight requests. Session-cache seeds now carry a write timestamp (legacy untimestamped values read as unknown age and self-heal on first use), and staleAfterMs lets a revisit inside the window paint straight from cache with no request at all. Past the window the refetch is quiet, so cached content never flashes a skeleton.

Ten Integrations resources had no session cache at all, so useDataSurface gained an opt-in sessionCacheKey that owns the seed-and-write-back pair instead of ten hand-written copies.

Measured (sandboxed instance, CDP)

Scenario Before After
Dashboard, 31s visible 57 requests / 9 concurrent timers 57 requests / 1 shared timer
Integrations revisit inside 60s 8 requests + skeletons 0 requests, 0 skeletons
Hidden tab zero (layer 3) unchanged

The visible-tab request count is deliberately unchanged: bucketing removes wakeups, not cadence, and a live dashboard is for freshness. No poll interval was retuned — the measurement does not show the Logs or Debug polls dominating (both are page-gated and now visibility-paused), and changing a freshness default without numbers behind it is the kind of regression this work exists to avoid.

Design and evidence: devlog/_plan/260816_gui_loading_performance/040_phase4_poll_consolidation.md

Stack (merge bottom-up): #1854#1855#1856this PR (top).

Depends on #1856. Review this PR's diff only.

Verification

  • cd gui && bun test tests → 922 pass / 0 fail
  • cd gui && bun run lint, bun run lint:i18n, bun run build → green
  • bun run typecheck (root) → green
  • New gui/tests/client-resource-scheduler.test.tsx (4 cases: shared bucket, bucket move with empty-bucket deletion, hidden drop/visible re-arm, mixed opt-out membership) and client-resource-revalidate.test.tsx (5 cases: fresh seed skips the fetch, stale seed revalidates quietly without a skeleton, legacy seed self-heals, no-staleAfterMs keeps today's behavior, cache envelope round-trip).
  • Live browser check of the revisit flow produced the 0-request / 0-skeleton result in the table above.

Note: startVisibilityPoll schedules through window.setInterval when available — the migrated pollers all used the window timer and their tests intercept it there. The bare global bound to a different scope and broke nine tests, caught by running the full suite before commit.

No visual change: scheduling and caching only.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2db05437-bd4b-4e56-990e-9f960c9726d1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lidge-jun lidge-jun added the gui-screenshot-waived Maintainer waiver for false-positive GUI screenshot requirements label Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 044ab41c83

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gui/src/pages/Combos.tsx
Comment on lines +213 to +214
initialData: cached ?? undefined,
initialDataCachedAt: seedCombosCachedAt(cacheKey),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the cached payload and timestamp from the same entry

When the Combos panel remains mounted but active becomes false, its resource store is evicted; on reactivation, cached is still the payload frozen by the mount-time useMemo, while seedCombosCachedAt(cacheKey) rereads the timestamp written by the latest refresh. After creating, deleting, or refreshing a combo when an older session entry existed, switching tabs and returning within 60 seconds therefore seeds the old payload with the new timestamp, skips revalidation, and lets state.data override the newer retainedData, making the completed mutation appear undone. Read the payload and timestamp from one current envelope, or retain both together after successful loads; ClaudeDesktop has the same split-read pattern.

Useful? React with 👍 / 👎.

@lidge-jun
lidge-jun force-pushed the codex/gui-poll-consolidation branch from 044ab41 to dbf5188 Compare August 16, 2026 16:43
@lidge-jun
lidge-jun force-pushed the codex/gui-hidden-pause branch from 116bcae to c75f88d Compare August 16, 2026 17:02
@lidge-jun
lidge-jun force-pushed the codex/gui-poll-consolidation branch from e2e9de2 to 7a6ba92 Compare August 16, 2026 17:02
…tches

Nine dashboard resources polling at 5s meant nine timers waking to do the same thing; poll scheduling now lives in interval buckets, so a cadence costs one wakeup no matter how many stores share it. Membership changes are bookkeeping, empty buckets are deleted, and the hidden-tab rule moves to bucket granularity (a bucket with no eligible member holds no timer, one visibility listener re-evaluates all buckets). Per-store skip rules still run inside the tick, so opt-out subscribers keep working while paused peers stay silent.

Session-cache seeds now carry a write timestamp (legacy untimestamped values read as unknown age and self-heal), and staleAfterMs lets a revisit inside the window paint from cache with no request at all; past it the refetch is quiet, so cached content never flashes a skeleton. Wired into Combos, ApiKeys, ClaudeCode, ClaudeDesktop, Grok, and — via a new opt-in sessionCacheKey on useDataSurface — the ten Integrations resources that had no cache at all. New tests: client-resource-scheduler (4) and client-resource-revalidate (5).
…ne visibility listener

Review caught a real regression in the revisit work: the Integrations overview and the per-client page describe the same connection through different cache keys, so a toggle on one could be contradicted by the other for up to 60s, with no request in flight to correct it. Those ten resources now seed from cache without a staleness window — a revisit still paints instead of flashing a skeleton, but it always revalidates. The window stays where a surface owns its truth (Combos, ApiKeys, ClaudeCode, ClaudeDesktop, Grok). A test pins the distinction so the next person adding a cache key has to decide which kind it is.

Also from review: the per-store visibilitychange handlers collapse into one module listener (each was running the same global bucket sweep, N times per flip), and the session-cache seed read is memoized per key rather than re-parsed on every render of an eight-resource page.
@lidge-jun
lidge-jun force-pushed the codex/gui-hidden-pause branch from c75f88d to e1beb5e Compare August 16, 2026 17:41
@lidge-jun
lidge-jun force-pushed the codex/gui-poll-consolidation branch from be97ca6 to 17cfdcc Compare August 16, 2026 17:41
@lidge-jun
lidge-jun changed the base branch from codex/gui-hidden-pause to dev August 16, 2026 17:47
@lidge-jun
lidge-jun merged commit 9c5eb1e into dev Aug 16, 2026
27 of 41 checks passed
@lidge-jun
lidge-jun deleted the codex/gui-poll-consolidation branch August 17, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gui-screenshot-waived Maintainer waiver for false-positive GUI screenshot requirements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant