Skip to content

Bump desktop-mode viewport rewrite to width=1366#280

Open
theoden8 wants to merge 13 commits into
masterfrom
claude/fix-bluesky-android-layout-dgbLc
Open

Bump desktop-mode viewport rewrite to width=1366#280
theoden8 wants to merge 13 commits into
masterfrom
claude/fix-bluesky-android-layout-dgbLc

Conversation

@theoden8

@theoden8 theoden8 commented May 1, 2026

Copy link
Copy Markdown
Owner

Bluesky's useWebMediaQueries gates isDesktop on (min-width: 1300px) and treats 800-1299 as tablet, so the previous width=1280 shipped the tablet layout on Android — iOS is unaffected because WKWebView synthesizes a desktop viewport at the WebKit level via preferredContentMode=.desktop, but on Android the meta-rewrite is the only viewport signal.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE

@theoden8 theoden8 linked an issue May 1, 2026 that may be closed by this pull request
@theoden8 theoden8 force-pushed the claude/fix-bluesky-android-layout-dgbLc branch 2 times, most recently from 1795c5f to 6803e53 Compare May 3, 2026 09:59
@theoden8 theoden8 force-pushed the claude/fix-bluesky-android-layout-dgbLc branch from 6803e53 to 6dfc81b Compare May 12, 2026 18:33
@theoden8 theoden8 added the Android Android-specific issue label Jun 3, 2026
claude added 13 commits June 3, 2026 21:11
Bluesky's useWebMediaQueries gates isDesktop on (min-width: 1300px)
and treats 800-1299 as tablet, so the previous width=1280 shipped
the tablet layout on Android — iOS is unaffected because WKWebView
synthesizes a desktop viewport at the WebKit level via
preferredContentMode=.desktop, but on Android the meta-rewrite is
the only viewport signal.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Android Chromium WebView ignores meta-viewport mutations after the
initial parse, so the existing rewrite changes the attribute string
without changing window.innerWidth or CSS width media-query
evaluation. React Native Web sites (Bluesky and similar) read
innerWidth at boot and pick the mobile branch despite the desktop UA.

buildDesktopModeShim now takes spoofLayoutViewport, set true only
when Platform.isAndroid. iOS WKWebView re-evaluates the meta on
mutation and synthesizes a desktop viewport via
preferredContentMode=.desktop, so the spoof stays off there.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Android Chromium WebView ignores meta-viewport mutations after the
initial parse, so the JS shim's MutationObserver only changes the
attribute string — window.innerWidth and CSS width media queries
stay at the device's CSS width and React Native Web sites
(Bluesky and similar) ship the mobile branch.

Wire shouldInterceptRequest on Android when the per-site UA is
desktop-shaped, re-fetch the main document through outboundHttp,
and rewrite every <meta name=viewport> in the body to width=1366
before handing the response back to the parser. Sub-resources and
non-main-frame requests still go through the native
FastSubresourceInterceptor; non-HTML, non-GET, blocked-proxy and
upstream errors fall through to native fetch. iOS, macOS and
Linux are unaffected.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Move (b)'s rewrite logic from Dart shouldInterceptRequest into the
existing native FastSubresourceInterceptor (WebInterceptPlugin.kt)
where it sits next to DNS/ABP/LocalCDN sub-resource handling.

Path: Dart attachToWebViews(desktopMode: true) →
FastSubresourceInterceptor.isDesktopMode → checkUrl branches on
isForMainFrame for GET http(s)://… and re-fetches via
HttpURLConnection, rewrites <meta name=viewport> in the body, and
returns the modified WebResourceResponse. Forwards request headers
verbatim, back-stops cookies from CookieManager, and re-applies
Set-Cookie to keep logins alive through the rewrite. Any error path
(non-HTML, non-2xx, decompression failure, network error) returns
null so the WebView falls through to native fetch.

iOS, macOS, Linux are unaffected — only Android needs the wire-level
rewrite because Chromium WebView ignores meta-viewport mutations
post-parse, and there's no androidx.webkit knob to force a desktop
layout viewport.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
- Unconditional entry log in checkUrl so we can see whether the fork's
  WebViewClientCompat actually routes requests to our handler.
- Dump handler class + ruleList size at attach time so we can spot
  the case where prepare() races our swap and clears the rule list
  underneath us.

Temporary; will be reverted once we localize the issue.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
The previous flow lost the main-doc race: InAppWebView fired its
initial fetch from FlutterWebView.makeInitialLoad while our attach
was still pending in a microtask, so the main-doc went through the
default empty ContentBlockerHandler and the wire-level viewport
rewrite never ran. Sub-resources, fired later, hit our handler fine.

Skip initialUrlRequest for desktop-mode Android pages, then in
onWebViewCreated await the attach and call controller.loadUrl
synchronously. The first shouldInterceptRequest the WebView
dispatches now lands in our FastSubresourceInterceptor.

iOS, macOS, Linux and the cached-HTML (initialData) paths are
unchanged.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
When a site has cached HTML, InAppWebView loads it via initialData
(synthetic in-memory document) instead of initialUrlRequest. That
path bypasses shouldInterceptRequest for the main document, so our
native viewport rewrite never runs. The pendingLiveReload follow-up
fetches in the background but doesn't reliably reach the interceptor
either.

Force-skip the cached-HTML shortcut for desktop-mode Android pages
so the main-doc fetch goes through the network path and our
FastSubresourceInterceptor.tryRewriteMainDocViewport actually fires.
Sites without a desktop UA, file imports, and other platforms keep
the cached-HTML behaviour for first-paint speed.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Distinguish: rewrite returned a response (with applied=true/false),
status filtered out, mime filtered out, or threw. The user's diag
shows checkUrl ENTERED but layout still stuck at device width;
need to know which branch tryRewriteMainDocViewport took.

Temporary; will be removed once we localize the issue.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Need to confirm whether the deferred path is actually firing — user's
latest test shows attach+ruleListSize=1 but no checkUrl ENTERED for
the main-doc. Either the deferred path isn't taken, the loadUrl call
silently swallows, or the request bypasses our handler post-loadUrl.

Temporary; will be reverted with the other diag logs.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
User's diagnostic confirms our wire rewrite produces the right
meta (width=1366) in the parsed DOM but Chromium WebView still
lays out at device width (412). Either setUseWideViewPort(true)
isn't taking effect, or the WebView is ignoring meta viewport from
shouldInterceptRequest responses. Logging the live settings value
distinguishes the two.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
Lets us prove via JS introspection whether the viewport meta in the
parsed DOM came from the native wire rewrite or from the JS shim's
MutationObserver fallback. The data-* attribute is layout-invisible.

https://claude.ai/code/session_01T9LjSt3fB5V2igeJTeEeeE
@theoden8 theoden8 force-pushed the claude/fix-bluesky-android-layout-dgbLc branch from 6dfc81b to f345a0b Compare June 3, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Android Android-specific issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop mode

2 participants