Expose Perception check suppression as SPI - #397
Draft
johnnewman-square wants to merge 1 commit into
Draft
Conversation
Suppression of Perception's debug-only runtime check is applied at two places in this module: Store state reads, and the workflow render pass that workflowPreview drives from its representable callbacks. Both are covered, but the funnel deciding when suppression applies is internal, so a preview host outside this module cannot reach it. MarketWorkflowUI is such a host. It declares its own PreviewView rather than building on workflowPreview, drives a render pass the same way, and so reproduces the same misreported reads for any workflow previewed through marketPreview. With no entry point here it would have to duplicate the predicate, which means a second copy of the rule in another repository and a new direct dependency on swift-perception for a module that has no other use for one. Expose the existing funnel under the PreviewHosting SPI instead. Its signature names no Perception type, so a host can adopt it with an @_spi import and nothing else. Behavior is unchanged: same availability gate, same predicate, same call sites within this module. Document the pre-iOS 17 limitation while that reasoning is in one place. Suppression stays off below iOS 17 because a view body there genuinely needs WithPerceptionTracking to observe state at all, so a warning about one is actionable and worth keeping. The cost is that a render-pass read still warns on those versions, where nothing can act on it, since WithPerceptionTracking is a view modifier and a workflow's render cannot be wrapped in one.
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.
Follow-up to #396, which suppresses Perception's runtime check in two places:
Storestate reads, and the workflow render pass thatworkflowPreviewdrives from itsUIViewControllerRepresentablecallbacks. Both are covered, but the funnel deciding when suppression applies is internal, so a preview host outside this module can't reach it.MarketWorkflowUIis such a host. It declares its ownPreviewViewrather than building onworkflowPreview, drives a render pass the same way, and so reproduces the same misreported reads for any workflow previewed throughmarketPreview. With no entry point here it would have to duplicate the predicate — a second copy of the rule in another repository, plus a new direct dependency onswift-perceptionfor a module that has no other use for one.Summary
PreviewHostingSPIWhy SPI rather than public
Preview hosts are few and all of them are library code, so this is meaningless to an app and doesn't belong in the public surface. The signature names no Perception type —
<T>(_ operation: () -> T) -> T— so a host adopts it with an@_spi(PreviewHosting) import WorkflowSwiftUIand nothing else. No Perception import, no new package.The alternative shapes were worse. Making it public puts a niche primitive in front of every app developer. Leaving it internal pushes each host to reimplement the predicate, which is the drift #396 just removed from this module, recreated across repository boundaries where nobody will notice it diverging.
On the pre-iOS 17 limitation
Worth stating explicitly rather than leaving as an implementation detail, since it isn't obvious from the code and this module supports back to iOS 16.
Suppression stays off below iOS 17 and its siblings. A view body on those versions genuinely does need
WithPerceptionTrackingto observe state at all, so a warning about one is actionable and hiding it would turn a preview that silently stops updating into a preview that silently stops updating for no visible reason.The cost is that a render-pass read still warns on those versions, where nothing can act on it:
WithPerceptionTrackingis a view modifier, and a workflow'srendercannot be wrapped in one. Splitting the funnel in two would fix that, at the price of two near-identical entry points whose distinction only matters pre-17. Not worth it — but the gap should be written down.Test plan
swift build --target WorkflowSwiftUI, debug and release — the release build matters here, since the function ispublicwith a#if DEBUGbodyswiftformat --lintclean acrossWorkflowSwiftUI/SourcesXcodePreviewsTestsand thetest_perceptionRuntimeWarnings*tests are untouched, and the change is a visibility modifier plus documentation — leaving these to CI rather than a localtuist testrunMarketWorkflowUIchange is written but not yet up, and depends on this landing firstChecklist