Skip to content

Fix blurred Google Sheets canvas rendering - #8697

Merged
austinywang merged 10 commits into
mainfrom
fix/webview-google-sheets-blur-8657
Jul 24, 2026
Merged

austinywang merged 10 commits into
mainfrom
fix/webview-google-sheets-blur-8657

Conversation

@austinywang

@austinywang austinywang commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • stop forcing standalone Safari identity onto embedded WKWebViews, which made Google Sheets choose a half-resolution canvas rendering path
  • restore the native embedded WebKit identity at WebView creation, top-level navigation, reload preparation, and popup creation
  • keep the Safari-style user agent only for auxiliary URLSession requests
  • remove the earlier reveal-geometry policy changes after controlled experiments disproved geometry as the cause
  • add a runtime regression test that seeds a legacy custom identity and verifies navigation clears it

Closes #8657

Root cause evidence

The same public Google Sheet was loaded in controlled stock WKWebViews:

  • native embedded WebKit identity: devicePixelRatio 2 and canvas backing/CSS ratio 2.0 (sharp)
  • cmux forced Safari identity: devicePixelRatio 2 but canvas backing/CSS ratio 0.5 (blurred)

Layer wrapping, synchronous geometry nudging, and hide/resize/reveal remained sharp in isolation, so the prior geometry hypothesis was rejected.

Validation

  • tagged Debug build succeeded with scripts/reload.sh --tag issue8657-fix --launch --swift-frontend-workaround
  • live tagged-app evaluation on the reported Sheet: visible canvas 2166 x 1232 for 1083 x 616 CSS, ratio 2.0 on both axes
  • captured a 2190 x 1920 browser screenshot showing crisp grid and text
  • swiftc frontend parse passed for the changed Swift and test files
  • git diff --check passed
  • focused app-host XCTest was not run locally because this shared Mac had multiple Xcode build-service deadlocks; CI is the runtime test authority

Review notes

  • CodeRabbit suggestion about making browserPortalAllowsFirstSizedRevealGeometryNudge default false is superseded: the current head removes that property and its configuration function entirely with the disproven geometry-policy approach
  • regression test is commit b71e98d; implementation is commit 6b55250
  • no user-facing strings changed; localization audit found no changed localized surfaces

@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The browser now clears custom WebKit user-agent overrides for embedded web views and uses a separate user-agent for auxiliary URLSession requests. Popup, navigation, creation, and reload paths apply the embedded identity, with regression coverage for navigation.

Changes

Embedded WebKit identity

Layer / File(s) Summary
User-agent identity contract and auxiliary requests
Sources/Panels/BrowserPanel.swift
Defines the embedded identity reset and dedicated auxiliary request user-agent, then applies the latter to remote suggestions and favicon requests.
Web view identity wiring and regression coverage
Sources/Panels/BrowserPanel.swift, Sources/Panels/BrowserPopupWindowController.swift, cmuxTests/BrowserPanelTests.swift
Applies the embedded identity during web view creation, popup setup, navigation, and reload recovery; tests that navigation clears customUserAgent.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #8657 by restoring remote webview geometry synchronously and adding regression tests for the reported blur.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the user-agent updates and tests appear tied to the blur fix.
Cmux Swift Actor Isolation ✅ Passed The patch only touches MainActor UI types/tests and a nonisolated UA helper; it adds no new Sendable reference types or background UI access.
Cmux Swift Blocking Runtime ✅ Passed Diff only swaps user-agent setup to customUserAgent = nil and UA constants; no new waits, sleeps, syncs, locks, or asyncAfter in touched production Swift.
Cmux Browser Automation Off-Main ✅ Passed The PR only changes WKWebView user-agent setup and a regression test; it doesn’t touch browser socket routing or ControlCommandExecutionPolicy.
Cmux Expensive Synchronous Load ✅ Passed Touched code only changes WebKit user-agent setup and a test; no agent-history loader, JSON decode, directory scan, or other expensive sync load was added on MainActor.
Cmux Cache Substitution Correctness ✅ Passed UA handling only; no authoritative persistence/history/snapshot read was replaced by a cache, and no cache-staleness path changed.
Cmux No Hacky Sleeps ✅ Passed The diff only touches Swift sources plus project.pbxproj; no TS/JS/shell/runtime delay primitives or hacky sleeps were added.
Cmux Algorithmic Complexity ✅ Passed Diff only changes user-agent handling and removes a few calls; no new nested scans, per-target rescans, or hot-path sorting/filtering were introduced.
Cmux Swift Concurrency ✅ Passed The diff only adjusts webview UA/reveal-policy plumbing and removes test-only async helpers; it adds no new legacy Dispatch/Combine/completion-handler patterns.
Cmux Swift @Concurrent ✅ Passed No changed Swift code adds/removes @concurrent or nonisolated async; the only new async work is a Task@MainActor restore, which is intentionally UI-bound.
Cmux Swift Package Boundaries ✅ Passed Changes are thin app-target WKWebView/URLSession glue in panel controllers; no reusable domain logic was left outside CmuxBrowser.
Cmux Swiftpm Lockfiles ✅ Passed No .gitignore or Package.resolved files changed, and the pbxproj diff has 0 SwiftPM reference edits.
Cmux Swift Logging ✅ Passed The diff adds no print/debugPrint/dump/NSLog/Logger in production Swift; it only removes some debug logging and changes UA/policy code.
Cmux User-Facing Error Privacy ✅ Passed Production changes only adjust internal UA/navigation behavior; no user-facing errors, alerts, or recovery copy were added or modified.
Cmux Full Internationalization ✅ Passed The patch only changes WebKit UA and navigation behavior; no user-facing Swift text, web copy, or locale assets were added or modified.
Cmux Swiftui State Layout ✅ Passed Patch only adjusts WKWebView/AppKit user-agent and navigation plumbing; it adds no new SwiftUI observation, GeometryReader layout, or render-time state writes.
Cmux Architecture Rethink ✅ Passed Small invariant fix: central UA helper and removal of duplicate nudge wiring; no new timing hacks, observers, or split ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR only changes popup UA/navigation behavior; the existing NSPanel already uses cmux.browser-popup and is registered in cmuxAuxiliaryWindowIdentifiers.
Cmux Source Artifacts ✅ Passed All changed paths are source/tests/config or deletions thereof; no added logs, screenshots, caches, build outputs, temp dirs, or broad artifact folders.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Diff only adds a production UA helper and removes obsolete calls; no new #if DEBUG/test-only members or widened test seams were introduced in Sources/.
Cmux No Ambient Global State ✅ Passed PASS: The PR only reuses an existing BrowserUserAgentSettings namespace and removes nudge globals; it adds no new top-level mutable state or singleton.
Title check ✅ Passed It clearly summarizes the main user-visible fix: blurred Google Sheets canvas rendering.
Description check ✅ Passed The PR includes strong Summary and Validation details, but it omits the template's Testing, Demo Video, Review Trigger, and Checklist sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/webview-google-sheets-blur-8657

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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes blurred canvas rendering in Google Sheets by removing the forced standalone Safari user-agent from embedded WKWebView instances. The root cause was that Google Sheets detects the Version/Safari UA token and selects a Safari-only half-resolution canvas path that WKWebView does not share; restoring the native embedded-WebKit identity (i.e., customUserAgent = nil) brings the canvas backing/CSS ratio back to 2.0.

  • BrowserUserAgentSettings.safariUserAgent is kept as auxiliaryRequestUserAgent for URLSession-based auxiliary fetches (search suggestions, favicons), where no WebKit identity is inherited.
  • applyEmbeddedWebKitIdentity(to:) replaces every direct webView.customUserAgent = safariUserAgent assignment at webView creation, top-level navigation (performNavigation), reload preparation, and popup creation.
  • A new @MainActor Swift Testing regression test seeds a legacy Chrome UA, calls navigate(to:), and asserts the override is cleared synchronously — though the defer teardown is missing panel.close().

Confidence Score: 4/5

Production changes are correct and well-scoped; the regression test's teardown skips panel.close(), leaving automation coordinators live after the test exits.

The production fix is straightforward and the auxiliary URLSession paths correctly retain a Safari UA. The regression test calls panel.webView.stopLoading() in its defer but never calls panel.close(), which is how every other panel test in the suite tears down. close() cancels the hidden-web-view discard manager and invalidates automation coordinators; skipping it leaves those objects live and risks cross-test interference in CI, which the PR description specifically calls the runtime test authority.

cmuxTests/BrowserPanelTests.swift — the new regression test needs panel.close() in its defer and should pass renderInitialNavigation: false to match every other BrowserPanel test construction in the suite.

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanel.swift Renames safariUserAgent to auxiliaryRequestUserAgent for URLSession calls, and replaces every webView.customUserAgent = safariUserAgent assignment with applyEmbeddedWebKitIdentity(to:) (which sets customUserAgent = nil). Change is localised to the UA enum, webView creation, navigation, reload, and URLSession request paths — all correct.
Sources/Panels/BrowserPopupWindowController.swift Single-line change: popup webView creation also cleared of forced Safari UA, consistent with the BrowserPanel change.
cmuxTests/BrowserPanelTests.swift New regression test seeds a legacy custom UA and asserts navigate(to:) clears it. Missing panel.close() in the defer leaves automation coordinators and the discard manager live after the test, risking cross-test interference.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WKWebView created] -->|applyEmbeddedWebKitIdentity| B[customUserAgent = nil\nnative embedded identity]
    C[navigate / reload / popup] -->|applyEmbeddedWebKitIdentity| B
    D[URLSession auxiliary request\nsearch suggestions / favicon] -->|auxiliaryRequestUserAgent| E[User-Agent: Safari string]
    B --> F{Google Sheets UA check}
    E -. not seen by WebKit page .-> F
    F -->|embedded WebKit UA| G[canvas backing/CSS ratio 2.0 — sharp]
    F -->|Safari standalone UA| H[canvas backing/CSS ratio 0.5 — blurred]
Loading

Reviews (6): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment on lines +127 to +131
switch scheme {
case "about", "applewebdata", "cmux-diff-viewer", "data", "file":
allowsNudge = true
default:
allowsNudge = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 blob: excluded from nudge allowlist — may regress blob-loaded local content

blob: is listed in browserEmbeddedNavigationSchemes as embeddable content, meaning cmux can navigate a WKWebView to a blob: URL via browserLoadRequest. After this change, any such navigation sets allowsNudge = false, so the transient geometry nudge — and its render-state kickstart — will be silently skipped. If cmux generates blob URLs for local application content (e.g. a PDF preview or an embedded document viewer) that currently relies on the nudge to recover from a hidden-host reveal, those surfaces will regress to the pre-nudge stale-paint behavior with no error or warning.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in dc47cd5: blob is explicitly included in isRemoteWebOrigin, so blob navigations retain the first-sized reveal nudge and restore it synchronously. The current policy only disables the nudge for unsupported/unknown schemes.

— Claude Code

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Panels/BrowserPanel.swift (1)

967-978: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Centralize first-sized-reveal nudge configuration on navigation start

browserLoadRequest is the only place currently calling browserPortalConfigureFirstSizedRevealGeometryNudge(...), but navigation can also start via direct webView.load(...), history traversal (webView.goBack()/goForward()), and reload. Make the configure-before-mark policy the single source of truth for browser navigations so WKNavigationDelegate/same-document reloads cannot observe stale nudge flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Panels/BrowserPanel.swift` around lines 967 - 978, Centralize the
configure-before-mark first-sized-reveal nudge policy used by browser
navigations instead of limiting it to browserLoadRequest. Update the relevant
WKWebView navigation entry points, including direct loads, history traversal,
reloads, and same-document navigation, to invoke
browserPortalConfigureFirstSizedRevealGeometryNudge before marking or handling
navigation-start flags, while preserving existing file and prepared-request
loading behavior.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/BrowserPortalRemoteNavigationRevealTests.swift`:
- Around line 70-71: Replace the live Google URL load in the test around
browserLoadRequest with a direct invocation of the navigation policy
configuration being asserted, or use a local fake URL/server if the request path
must be exercised. Keep the test focused on the synchronous policy side effect
and ensure it never contacts an external host or relies on a fixed non-zero
port.

---

Outside diff comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 967-978: Centralize the configure-before-mark first-sized-reveal
nudge policy used by browser navigations instead of limiting it to
browserLoadRequest. Update the relevant WKWebView navigation entry points,
including direct loads, history traversal, reloads, and same-document
navigation, to invoke browserPortalConfigureFirstSizedRevealGeometryNudge before
marking or handling navigation-start flags, while preserving existing file and
prepared-request loading behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ff13fa5-d744-4b83-b984-339c8f27aa6a

📥 Commits

Reviewing files that changed from the base of the PR and between b17a87a and 1494043.

📒 Files selected for processing (4)
  • Sources/BrowserPortalWebViewRenderingState.swift
  • Sources/Panels/BrowserPanel.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserPortalRemoteNavigationRevealTests.swift

Comment thread cmuxTests/BrowserPortalRemoteNavigationRevealTests.swift Outdated

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/BrowserPortalWebViewRenderingState.swift (1)

106-119: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail closed until a navigation URL configures the policy.

The default true authorizes a nudge when a load path misses configuration. Default this to false; browserPortalConfigureFirstSizedRevealGeometryNudge(forNavigationURL:) should be the sole authority that enables it.

Proposed fix
-                .boolValue ?? true
+                .boolValue ?? false

As per path instructions, “fail closed when the source is absent.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/BrowserPortalWebViewRenderingState.swift` around lines 106 - 119,
Update the getter for browserPortalAllowsFirstSizedRevealGeometryNudge to
default to false when no associated value exists. Keep
browserPortalConfigureFirstSizedRevealGeometryNudge(forNavigationURL:) as the
sole path that enables the policy, preserving configured values unchanged.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/BrowserPortalWebViewRenderingState.swift`:
- Around line 106-119: Update the getter for
browserPortalAllowsFirstSizedRevealGeometryNudge to default to false when no
associated value exists. Keep
browserPortalConfigureFirstSizedRevealGeometryNudge(forNavigationURL:) as the
sole path that enables the policy, preserving configured values unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bd641926-427a-4fac-b9ec-8e3cea844260

📥 Commits

Reviewing files that changed from the base of the PR and between 1494043 and aabedf5.

📒 Files selected for processing (7)
  • Sources/BrowserPortalWebViewRenderingState.swift
  • Sources/Panels/BrowserNavigationDelegate.swift
  • Sources/Panels/BrowserPopupWindowController.swift
  • Sources/Panels/BrowserPrewarmedWebViewPool.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserPortalRemoteNavigationRevealTests.swift
  • cmuxTests/BrowserPrewarmedWebViewPoolTests.swift

@austinywang austinywang changed the title Fix remote WKWebView reveal blur Fix blurred Google Sheets canvas rendering Jul 24, 2026
@austinywang

Copy link
Copy Markdown
Contributor Author

Addressed the CodeRabbit fail-closed finding from review 4768837367 in the current head (6b55250). The finding targeted browserPortalAllowsFirstSizedRevealGeometryNudge and browserPortalConfigureFirstSizedRevealGeometryNudge(forNavigationURL:), but the controlled same-URL WKWebView experiment disproved the geometry-policy hypothesis. The current head removes that property, configuration function, and all PR-added call sites rather than retaining a policy with either default. The fix now acts at the demonstrated boundary by restoring native embedded WebKit identity.

Live tagged verification on the reported Sheet now shows devicePixelRatio 2 and a 2166 x 1232 backing canvas for 1083 x 616 CSS (2.0 on both axes; the bad build was 0.5).

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.

Bug: Google Sheets webview completely blurs after initial crisp render on M1 Mac (macOS Tahoe)

1 participant