Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Workflow/Sources/RuntimeConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@ extension Runtime {
/// This is expected to eventually be removed and become the default behavior.
public var useSinkEventHandler: Bool = false

/// Whether WorkflowSwiftUI suppresses Perception's debug-only runtime warning when using
/// native Observation.
/// Whether WorkflowSwiftUI runs Perception's debug-only runtime check on `Store` reads.
///
/// Defaults to `false`, so Store access continues through Perception normally.
public var suppressPerceptionCheckingWhenUsingObservation: Bool = false
/// Defaults to `false`. The check reports state read from a view body that is not wrapped
/// in `WithPerceptionTracking`. That modifier is required for observation to work below
/// iOS 17, but is unnecessary at iOS 17 and above, where native Observation tracks the read
/// on its own — and the check cannot tell the two situations apart, so it reports reads
/// that are already working correctly.
///
/// Opt in when using WorkflowSwiftUI with a deployment target below iOS 17, where an
/// untracked read is a real defect: without `WithPerceptionTracking`, a view does not
/// update when the state it reads changes.
public var enablePerceptionChecking: Bool = false
}
}
22 changes: 8 additions & 14 deletions WorkflowSwiftUI/Sources/PerceptionCheckSuppression.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import Perception
@_spi(WorkflowRuntimeConfig) import Workflow

/// Runs `operation` with Perception's debug-only runtime check suppressed, if suppression applies.
/// Runs `operation` with Perception's debug-only runtime check suppressed, unless the check has
/// been opted into.
///
/// Suppression is opt-in through
/// `Runtime.Configuration.suppressPerceptionCheckingWhenUsingObservation`, so `operation` executes
/// normally by default.
///
/// It is additionally applied whenever the process is rendering Xcode previews. That opt-in is
/// meant to be set once at app startup, and a preview has no equivalent entry point — the canvas
/// instantiates a view directly, with no app delegate and no runtime to configure — so a preview
/// would otherwise have no way to reach the configuration at all.
/// The check is off by default — see `Runtime.Configuration.enablePerceptionChecking` — because at
/// iOS 17 and above it reports `Store` reads that native Observation is already tracking
/// correctly. Clients below iOS 17 opt in, where an untracked read is a real defect rather than a
/// false positive.
func withPerceptionCheckSuppressed<T>(_ operation: () -> T) -> T {
#if DEBUG && canImport(Observation)
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *),
Runtime.configuration.suppressPerceptionCheckingWhenUsingObservation
|| XcodePreviews.isRunning
{
#if DEBUG
if !Runtime.configuration.enablePerceptionChecking {
return _PerceptionLocals.$skipPerceptionChecking.withValue(true, operation: operation)
}
#endif
Expand Down
25 changes: 0 additions & 25 deletions WorkflowSwiftUI/Sources/XcodePreviews.swift

This file was deleted.

26 changes: 14 additions & 12 deletions WorkflowSwiftUI/Tests/StoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ final class StoreTests: XCTestCase {
// MARK: - Native SwiftUI Bindings

@MainActor
func test_perceptionRuntimeWarningsWhenUsingObservation() throws {
func test_perceptionRuntimeWarningsWhenCheckingIsEnabled() throws {
#if DEBUG
guard #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) else {
throw XCTSkip("Requires native Observation")
Expand All @@ -790,15 +790,20 @@ final class StoreTests: XCTestCase {
)
let (store, _) = Store.make(model: model)

let image = ImageRenderer(content: PerceptionRuntimeWarningView(store: store)).cgImage
let image = Runtime.withConfiguration(
override: { $0.enablePerceptionChecking = true },
operation: {
ImageRenderer(content: PerceptionRuntimeWarningView(store: store)).cgImage
}
)
_ = image
#else
throw XCTSkip("Perception runtime warnings are debug-only")
#endif
}

@MainActor
func test_perceptionRuntimeWarningsCanBeSuppressedWhenUsingObservation() throws {
func test_perceptionRuntimeWarningsAreDisabledByDefault() throws {
#if DEBUG
guard #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) else {
throw XCTSkip("Requires native Observation")
Expand All @@ -812,15 +817,12 @@ final class StoreTests: XCTestCase {
)
let (store, _) = Store.make(model: model)

// Rendering evaluates the Store reads inside the override. If suppression fails,
// Perception reports an unexpected XCTest failure, so the absence of a failure is the
// assertion.
let image = Runtime.withConfiguration(
override: { $0.suppressPerceptionCheckingWhenUsingObservation = true },
operation: {
ImageRenderer(content: SuppressedPerceptionRuntimeWarningView(store: store)).cgImage
}
)
// Rendering evaluates the Store reads with no configuration override at all. If the check
// runs, Perception reports an unexpected XCTest failure, so the absence of a failure is
// the assertion.
let image = ImageRenderer(
content: SuppressedPerceptionRuntimeWarningView(store: store)
).cgImage
_ = image
#else
throw XCTSkip("Perception runtime warnings are debug-only")
Expand Down
34 changes: 0 additions & 34 deletions WorkflowSwiftUI/Tests/XcodePreviewsTests.swift

This file was deleted.

Loading