Draw the Pro celebration in a clear overlay, not over the tab view - #345
Merged
Ryanmello07 merged 1 commit intoSep 9, 2026
Merged
Conversation
On a current toolchain the app renders as nothing but SwiftUI's unsupported-view placeholder -- a full-screen yellow field with a red slashed circle -- while the process runs normally underneath: the tunnel connects, the rpc flows, the connect grid updates. Only the UI is gone. The pixelation is a raster-layer filter. SwiftUI has to render the filtered content into an offscreen layer for the Metal function to sample, and content backed by UIKit cannot go into one; when it cannot, SwiftUI substitutes the placeholder for the whole filtered region. At MainView the filtered content is the entire app, including the UIKit-backed tab view, so the placeholder is the entire app. `isEnabled: false` does not avoid this. The effect being ATTACHED is what asks for the raster layer, and ProPixelation attaches it unconditionally -- deliberately, to keep the tab view from being re-hosted above the overlay when the structure changes. That trade is still the right one; it is only the choice of what to wrap that was wrong. So wrap nothing. The layer goes into a clear overlay in the same chain position, which keeps the confetti -- plain SwiftUI, and the part the user actually sees -- and costs only the mosaic over the root, which on this toolchain was never renderable there anyway. The environment object is still injected below the overlay, and the overlay is proposed the same rect the layer got before, so the confetti geometry is unchanged. The onboarding cover gets the same treatment: the body it wraps carries a NavigationStack, so it is exposed exactly as the root is, and a fresh install is the first thing to hit it. The upgrade sheet is SwiftUI-only and is left alone. Two comments corrected. The header said "Idle, the layer adds nothing: no effect, no overlay" -- the effect is attached either way, and that sentence is what makes the idle path look innocent. And the extension doc now states the constraint a caller needs: the wrapped content must be SwiftUI only. Verified on an iPhone 15 Pro built with Xcode 27: before, the placeholder at launch; after, the app renders. That the clear overlay cures it is also what distinguishes the two candidate causes -- a shader that failed to resolve would still mark over Color.clear. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AWV4MaF9JBcQppSX9UxATa
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The app renders as nothing but the unsupported-view placeholder
Built from
mainwith Xcode 27 and run on an iPhone 15 Pro, the app shows a full-screen yellow field with a large red slashed circle — SwiftUI's unsupported-view marker — and nothing else.The process is healthy underneath. The SDK logs show the tunnel connected, RPC flowing,
[grid]8->8 CONNECTED, packet stats ticking every second. Only the UI is gone.Cause
The pixelation is a raster-layer filter. SwiftUI must render the filtered content into an offscreen layer for the Metal function to sample, and content backed by UIKit cannot be rendered into one — when it can't, SwiftUI replaces the whole filtered region with the placeholder.
At
MainView.swift:112the filtered content is the entire app, including the UIKit-backed tab view (and, inside it,VerticalPanGesture, the app's oneUIViewRepresentable). So the placeholder is the entire app.isEnabled: falsedoes not avoid it. The effect being attached is what asks for the raster layer, andProPixelationattaches it unconditionally — deliberately, per its own comment, to keep the tab view from being re-hosted above the overlay when the view structure changes.That trade is still right. Only the choice of what to wrap was wrong.
The change
Wrap nothing. The layer moves into a clear overlay in the same chain position:
This keeps the confetti — plain SwiftUI, and the part actually seen — and costs only the mosaic over the root, which on this toolchain was never renderable there in the first place. The environment object is still injected below the overlay so
@EnvironmentObjectresolves, and.overlayis proposed the same rect the layer received before, so the confetti geometry is unchanged.allowsHitTesting(false)sits outside the layer on purpose:Color.clearis hit-testable, and without it the app renders perfectly and ignores every touch — a worse fault than the placeholder, and one a screenshot cannot catch.The onboarding cover gets the same treatment. The body wrapped at
IntroductionView.swift:234carries aNavigationStack, so it is exposed exactly as the root is — and a fresh install is the first thing to hit it.UpgradeSubscriptionSheet.swift:272is SwiftUI-only throughout (I checked the subtree: noTextEditor, nosearchable, andSubscriptionPlanPickeris a custom SwiftUI view rather than aPicker) and is left alone.Two comments corrected. The header said "Idle, the layer adds nothing: no effect, no overlay" — the effect is attached either way, and that sentence is precisely what makes the idle path look innocent to a reader. The extension doc now states the constraint a caller needs: the wrapped content must be SwiftUI only.
Why this and not the obvious alternative
The natural counter-proposal is to make attachment conditional — attach the effect only while a flight is in the air. That reinstates exactly the re-host the author was avoiding, twice per celebration, and it destroys real state:
selectedTabresets, the 1 s fade replays, four@StateObjects inMainTabVieware recreated,AccountNavStackView'snavigationPathpops to root, the connect drawer resets, and an unsent draft plus the keyboard inFeedbackVieware lost. Trading "the app doesn't render" for "the app silently eats your typed feedback and jumps tabs twenty seconds after an easter egg" is not an improvement.Deleting the shader path and keeping only the blur arm also works and is a smaller diff, but it removes the Metal mosaic on every platform, including ones where it renders fine today.
Verification, and its limits
Verified on an iPhone 15 Pro built with Xcode 27: before, the placeholder at launch; after, the app renders normally.
That the clear overlay cures it is also what distinguishes the two candidate causes — a shader that failed to resolve would still mark over
Color.clear. So this is UIKit-rasterization refusal, not a missingdefault.metallib. (For completeness: a clean build does compile the shader and shipsdefault.metallib, 56808 bytes.)What I cannot tell you:
-only-testing:networkTests, excluding the UI tests entirely. A green run proves nothing here.ProSpriteFlightis a plainCanvason aTimelineViewwith no shader dependency, so it should be unaffected, but "should be, by reading" is not a measurement.NavigationSplitViewthere. Same reasoning applies; I have not run it.🤖 Generated with Claude Code
https://claude.ai/code/session_01AWV4MaF9JBcQppSX9UxATa