Skip to content

Expose Perception check suppression as SPI - #397

Draft
johnnewman-square wants to merge 1 commit into
mainfrom
johnnewman/spi-preview-perception-suppression
Draft

Expose Perception check suppression as SPI#397
johnnewman-square wants to merge 1 commit into
mainfrom
johnnewman/spi-preview-perception-suppression

Conversation

@johnnewman-square

Copy link
Copy Markdown
Contributor

Follow-up to #396, which suppresses Perception's runtime check in two places: Store state reads, and the workflow render pass that workflowPreview drives from its UIViewControllerRepresentable callbacks. Both are covered, but the funnel deciding when suppression applies is internal, so a preview host outside this module can't 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 — a second copy of the rule in another repository, plus a new direct dependency on swift-perception for a module that has no other use for one.

Summary

  • Expose the existing suppression funnel under a new PreviewHosting SPI
  • Document the pre-iOS 17 limitation on the funnel, while that reasoning is in one place
  • No behavior change: same availability gate, same predicate, same call sites within this module

Why 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 WorkflowSwiftUI and 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 WithPerceptionTracking to 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: WithPerceptionTracking is a view modifier, and a workflow's render cannot 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 is public with a #if DEBUG body
  • swiftformat --lint clean across WorkflowSwiftUI/Sources
  • XcodePreviewsTests and the test_perceptionRuntimeWarnings* tests are untouched, and the change is a visibility modifier plus documentation — leaving these to CI rather than a local tuist test run
  • Adopted by a second preview host — the MarketWorkflowUI change is written but not yet up, and depends on this landing first

Checklist

  • Unit Tests (no behavior change; existing coverage applies)
  • UI Tests (not applicable)
  • Snapshot Tests (not applicable)
  • I have made corresponding changes to the documentation

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