Skip to content

Conversation

@vignesh07
Copy link
Contributor

Summary

The OpenAI dashboard WebView was being destroyed after every fetch, causing the full ChatGPT SPA bundle to be re-downloaded on each refresh. Users reported 15+ GB of network traffic from chatgpt.com over a two-week period.

Root Cause

Commit 1c55dc7 ("perf: reduce OpenAI web idle CPU") changed the release behavior:

-                    entry.host.hide()
-                    self.prune(now: Date())
+                    entry.host.close()
+                    self.entries.removeValue(forKey: key)

This was intended to reduce idle CPU usage by destroying cached WebViews. However, it caused every refresh to create a new WebView and download all assets fresh.

With the default 5-minute refresh interval (multiplied by 5 for OpenAI web = 25 minutes), this resulted in approximately 57 page loads per day. If each load downloads 20-50 MB of assets, that adds up to 1-3 GB per day.

The Fix

On release, call hide() instead of close() to keep the WebView in cache:

// Hide instead of close - keep WebView cached for reuse.
// This avoids re-downloading the ChatGPT SPA bundle on every refresh,
// saving significant network bandwidth. See GitHub issues #269, #251.
entry.host.hide()
self.prune(now: Date())

This gives us the best of both worlds:

  • Low idle CPU: WebView is suspended when hidden (inactiveSchedulingPolicy=.suspend + alphaValue=0)
  • Low network usage: WebView is reused across refreshes, assets stay cached
  • Memory cleanup: The existing 10-minute idle timeout prunes unused WebViews

Testing

Added 7 unit tests to verify cache behavior:

Test Verifies
webViewCachedAfterRelease WebView stays in cache after release
separateCachesPerDataStore Different accounts get separate WebViews
webViewPrunedAfterIdleTimeout 10-minute idle timeout works
recentlyUsedWebViewNotPruned Active WebViews are not pruned
evictRemovesSpecificWebView Explicit eviction works
busyWebViewCreatesTemporary Concurrent access creates temporary WebView
sequentialFetchesReuseWebView 5 sequential fetches reuse the same WebView

All tests pass.

Impact

  • Network traffic reduction: ~1-3 GB/day to ~50 MB/day (first load only)
  • No increase in idle CPU (WebView is suspended when hidden)
  • No increase in memory (10-minute prune timeout unchanged)

Fixes #269, #251

Copy link

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

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: 10fa03c44a

ℹ️ 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 on lines 108 to +115
guard let self, let entry else { return }
entry.isBusy = false
entry.lastUsedAt = Date()
entry.host.close()
self.entries.removeValue(forKey: key)
// Hide instead of close - keep WebView cached for reuse.
// This avoids re-downloading the ChatGPT SPA bundle on every refresh,
// saving significant network bandwidth. See GitHub issues #269, #251.
entry.host.hide()
self.prune(now: Date())

Choose a reason for hiding this comment

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

P2 Badge Ensure cache reuse with stable data-store identity

Keeping the WebView alive on release only helps if the same WKWebsiteDataStore instance is reused on the next fetch. In the current call path, OpenAIDashboardWebsiteDataStore.store(forAccountEmail:) creates a new WKWebsiteDataStore each time, so the cache key (object identity) changes per refresh. With this new hide-and-keep behavior, each refresh inserts a new cached entry that is never reused, so the network savings won’t materialize and you’ll keep extra hidden WebViews alive until pruning. Consider keying by a stable store identifier or reusing the same WKWebsiteDataStore instance per account.

Useful? React with 👍 / 👎.

The OpenAI dashboard WebView was being destroyed after every fetch,
causing the full ChatGPT SPA bundle to be re-downloaded on each refresh.
Users reported 15+ GB of network traffic from chatgpt.com over a two-week
period.

Root cause: Commit 1c55dc7 changed the release behavior from hide() to
close() to reduce idle CPU usage. However, this caused every refresh to
create a new WebView and download all assets fresh.

The fix has two parts:

1. OpenAIDashboardWebViewCache: On release, call hide() instead of close()
   to keep WebView in cache. The existing prune mechanism (10 minute idle
   timeout) handles cleanup. The inactiveSchedulingPolicy=.suspend setting
   still reduces idle CPU.

2. OpenAIDashboardWebsiteDataStore: Cache WKWebsiteDataStore instances so
   the same object is returned for the same account email. This ensures
   stable object identity for WebView cache lookups (the cache uses
   ObjectIdentifier as the key).

This gives us the best of both worlds: low idle CPU (WebView suspended
when hidden) and low network usage (WebView reused across refreshes).

Added test instrumentation and 9 unit tests to verify:
- WKWebsiteDataStore returns same instance for same email
- WebView is cached after release, not destroyed
- Different data stores have separate cached WebViews
- Idle timeout pruning works correctly
- Sequential fetches reuse the same WebView instance
- Integration test with real data store factory

Fixes steipete#269, steipete#251
@vignesh07 vignesh07 force-pushed the fix/webview-cache-network-traffic branch from 10fa03c to 515ae98 Compare February 1, 2026 03:22
@vignesh07
Copy link
Contributor Author

Addressed the Codex review feedback in the updated commit.

The issue was that OpenAIDashboardWebsiteDataStore.store(forAccountEmail:) was creating a new WKWebsiteDataStore instance on each call. Even though the underlying UUID was stable (derived from email hash), the object identity changed, so the cache key via ObjectIdentifier never matched.

The fix adds instance caching to OpenAIDashboardWebsiteDataStore so the same object is returned for the same account email. Added an integration test that verifies sequential fetches with the real data store factory reuse the same WebView instance.

@steipete steipete merged commit 41dc781 into steipete:main Feb 1, 2026
1 check passed
@steipete
Copy link
Owner

steipete commented Feb 1, 2026

Landed via temp rebase onto main.

  • Gate: pnpm lint && pnpm build && pnpm test
  • Land commit: 1975e54
  • Merge commit: 41dc781

Thanks @vignesh07!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Significant usage of battery

2 participants