Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .bumper/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ This rule follows the typed-query pattern proven by TheButtonHeist's
audited and retained: Where needs the same check plus explicit exceptions for
its documented observation-task lifecycle.

## Scene navigation ownership

`where.scene_navigation_ownership` permits direct `SceneNavigationModel` construction in `MainTabs` and the DEBUG-only `AdaptiveHistoryLabModel`.
The root retains this model across layout changes and replaces it when the session changes.
Destination views receive focused child models. Isolated previews may construct child models.
Repair a violation by injecting the existing scene model or its child.
The syntax rule does not resolve type aliases, factories, or contextually inferred `.init()` calls.
`WhereProjectRulesTests` covers direct constructor mutations. The lab exception is path-scoped; release compilation must continue excluding that file's implementation.

`where.reserved_region_layout_ownership` permits `reservedRegions` calls only in `AdaptiveHistoryGeometry` within WhereUI.
Views use that adapter or `AdaptiveHistoryContainer`. Keep the system margins and physical coordinate mapping there.
The syntax guard checks member-call spelling; it does not resolve aliases or function references.
`WhereProjectRulesTests` covers direct member-call mutations.

## Composition ownership

`where.services_composition_ownership` keeps direct `WhereServices`
Expand Down
35 changes: 35 additions & 0 deletions .bumper/Sources/WhereProjectRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import BumperBowlingCore
import SwiftSyntax

let whereProjectRules = RuleSet {
Rules.constructionOwnership(
"SceneNavigationModel",
allowed: RuleScope.files([
"Where/WhereUI/Sources/MainTabs.swift",
"Where/WhereUI/Sources/Developer/AdaptiveHistory/AdaptiveHistoryLabModel.swift",
])
.union(nonWhereProductionScope),
id: "where.scene_navigation_ownership",
)
Rules.constructionOwnership(
"WhereServices",
allowed: whereServicesConstructionScope.union(nonWhereProductionScope),
Expand All @@ -18,6 +27,7 @@ let whereProjectRules = RuleSet {
owner: whereLoggingScope.union(nonWhereProductionScope),
id: "where.logging_type_ownership",
)
reservedRegionLayoutOwnershipRule
productionStoreOpeningRule
checkedConcurrencyBoundaryRule
gregorianCalendarRule
Expand Down Expand Up @@ -305,3 +315,28 @@ private let previewCoverageRule = Rules.files(
)
}
}

/// Keep system geometry mapping in one adapter, shared by production and injected fixtures.
private let reservedRegionLayoutOwnershipRule = Rules.files(
"where.reserved_region_layout_ownership",
severity: .error,
summary: "WhereUI reads system reserved regions through its shared geometry adapter.",
scope: .component(WhereComponent.whereUI),
) { file in
guard file.path.rawValue != "Where/WhereUI/Sources/Shared/AdaptiveHistoryGeometry.swift"
else { return [] }
return functionCalls()
.filter { match in
match.node.calledExpression.as(MemberAccessExprSyntax.self)?.declName.baseName.text == "reservedRegions"
}
.matches(in: file)
.map { match in
match.failure(
message: "Reserved regions are read outside AdaptiveHistoryGeometry.",
evidence: ViolationEvidence(
observed: file.path.rawValue,
expectation: "use AdaptiveHistoryGeometry or AdaptiveHistoryContainer",
),
)
}
}
43 changes: 43 additions & 0 deletions .bumper/Tests/WhereProjectRulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,49 @@ import BumperBowlingTestSupport
import Testing

struct WhereProjectRulesTests {
@Test
func `only the stable tab root constructs scene navigation`() throws {
let allowed = try evaluate(
path: "Where/WhereUI/Sources/MainTabs.swift",
component: .whereUI,
source: "let navigation = SceneNavigationModel()",
)
let rejected = try evaluate(
path: "Where/WhereUI/Sources/Primary/LocationsView.swift",
component: .whereUI,
source: "let navigation = SceneNavigationModel()",
)
#expect(allowed.violations.isEmpty)
#expect(rejected.violations.map(\.rule.id) == ["where.scene_navigation_ownership"])
}

@Test
func `the DEBUG layout lab owns an independent demo scene`() throws {
let allowed = try evaluate(
path: "Where/WhereUI/Sources/Developer/AdaptiveHistory/AdaptiveHistoryLabModel.swift",
component: .whereUI,
source: "#if DEBUG\nlet navigation = SceneNavigationModel()\n#endif",
)
#expect(allowed.violations.isEmpty)
}

@Test
func `system reserved regions use one geometry adapter`() throws {
let source = "let regions = proxy.reservedRegions(kind: .division, options: .includeInactive, layoutDirectionBehavior: .fixed)"
let allowed = try evaluate(
path: "Where/WhereUI/Sources/Shared/AdaptiveHistoryGeometry.swift",
component: .whereUI,
source: source,
)
let rejected = try evaluate(
path: "Where/WhereUI/Sources/Primary/LocationsView.swift",
component: .whereUI,
source: source,
)
#expect(allowed.violations.isEmpty)
#expect(rejected.violations.map(\.rule.id) == ["where.reserved_region_layout_ownership"])
}

@Test
func `production store opens at process composition roots`() throws {
let allowed = try evaluate(
Expand Down
6 changes: 5 additions & 1 deletion Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,11 @@ let project = Project(
name: "WhereUISnapshotTests",
bundleIdSuffix: "whereui.snapshot",
productDependency: "WhereUI",
sources: ["Where/WhereUI/SnapshotTests/**"],
sources: [
"Where/WhereUI/SnapshotTests/**",
"Where/WhereUI/Tests/Support/AdaptiveHistoryLoadFixture.swift",
"Where/WhereUI/Tests/Support/TestStore.swift",
],
extraPackageProducts: ["SnapshotKitTesting"],
environmentVariables: snapshotEnvironment,
),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading