Skip to content

Draw the Pro celebration in a clear overlay, not over the tab view - #345

Merged
Ryanmello07 merged 1 commit into
urnetwork:mainfrom
Ryanmello07:fix/pro-celebration-unsupported-view
Sep 9, 2026
Merged

Draw the Pro celebration in a clear overlay, not over the tab view#345
Ryanmello07 merged 1 commit into
urnetwork:mainfrom
Ryanmello07:fix/pro-celebration-unsupported-view

Conversation

@Ryanmello07

Copy link
Copy Markdown
Contributor

The app renders as nothing but the unsupported-view placeholder

Built from main with 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:112 the filtered content is the entire app, including the UIKit-backed tab view (and, inside it, VerticalPanGesture, the app's one UIViewRepresentable). So the placeholder is the entire app.

isEnabled: false does not avoid it. The effect being attached is what asks for the raster layer, and ProPixelation attaches 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:

.overlay(
    Color.clear
        .proCelebrationLayer()
        .allowsHitTesting(false)
        .accessibilityHidden(true)
)

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 @EnvironmentObject resolves, and .overlay is proposed the same rect the layer received before, so the confetti geometry is unchanged.

allowsHitTesting(false) sits outside the layer on purpose: Color.clear is 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:234 carries a NavigationStack, so it is exposed exactly as the root is — and a fresh install is the first thing to hit it. UpgradeSubscriptionSheet.swift:272 is SwiftUI-only throughout (I checked the subtree: no TextEditor, no searchable, and SubscriptionPlanPicker is a custom SwiftUI view rather than a Picker) 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: selectedTab resets, the 1 s fade replays, four @StateObjects in MainTabView are recreated, AccountNavStackView's navigationPath pops to root, the connect drawer resets, and an unsent draft plus the keyboard in FeedbackView are 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 missing default.metallib. (For completeness: a clean build does compile the shader and ships default.metallib, 56808 bytes.)

What I cannot tell you:

  • Whether this reproduces on the pinned toolchain. CI pins Xcode 16.4; I only have Xcode 27. The author's own comment records the opposite symptom on the authoring machine — the effect-wrapped tab view rendering and merely re-hosting — so this may well be a newer-SDK regression rather than something broken since it landed. Both readings survive the evidence, and I'd rather say so than lead with a toolchain delta that reads as blame.
  • CI could never have caught it either way. The workflow builds but never launches the app, and runs -only-testing:networkTests, excluding the UI tests entirely. A green run proves nothing here.
  • The celebration itself has not been observed running on this toolchain — the whole app was a placeholder, so it has never had the chance. ProSpriteFlight is a plain Canvas on a TimelineView with no shader dependency, so it should be unaffected, but "should be, by reading" is not a measurement.
  • macOS is untouched and untested. The same construct wraps the AppKit-hosted NavigationSplitView there. Same reasoning applies; I have not run it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AWV4MaF9JBcQppSX9UxATa

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
@Ryanmello07
Ryanmello07 merged commit b104126 into urnetwork:main Sep 9, 2026
1 check failed
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.

1 participant