Skip to content
Open
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
4 changes: 3 additions & 1 deletion .bumper/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ tests and generated files are outside the architecture graph.
| Component | Allowed Where dependencies | Framework capabilities |
| --- | --- | --- |
| `RegionKit` | none | Foundation |
| `WhereCore` | `RegionKit` | Foundation, persistence |
| `WhereSurface` | none | Foundation |
| `WhereCore` | `RegionKit`, `WhereSurface` | Foundation, persistence |
| `WhereUI` | `RegionKit`, `WhereCore` | Foundation, SwiftUI, UIKit |
| `WhereIntents` | `RegionKit`, `WhereCore`, `WhereUI` | Foundation, SwiftUI, UIKit |
| `Where` app | `RegionKit`, `WhereCore`, `WhereUI`, `WhereIntents` | Foundation, SwiftUI, UIKit |
| `WhereWidgets` | `RegionKit`, `WhereCore`, `WhereUI` | Foundation, SwiftUI, UIKit |
| `WhereShareExtension` | `WhereCore`, `WhereUI` | Foundation, SwiftUI, UIKit |
| `WhereMenuBar` | `WhereSurface` | Foundation, SwiftUI, AppKit |
| `RegionViewer` | `RegionKit`, `WhereCore`, `WhereUI` | Foundation, SwiftUI, UIKit |

An import of a declared Where module outside these edges is a
Expand Down
7 changes: 7 additions & 0 deletions .bumper/Sources/WhereArchitecture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ extension ComponentShape {
static let whereHostLayer = ComponentShape {
MayUse(.foundation, .swiftUI, .uiKit)
}

static let whereMacHostLayer = ComponentShape {
// Bumper Bowling has no AppKit capability yet; AppKit is the native
// host framework and the component's explicit dependency rules still
// forbid CoreLocation and SwiftData.
MayUse(.foundation, .swiftUI)
}
}

extension AssertionShape {
Expand Down
66 changes: 46 additions & 20 deletions .bumper/Tests/WhereArchitectureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,47 @@ func `Where architecture accepts downward dependencies`() throws {
files: [
SourceInput(
path: "Where/WhereCore/Sources/Service.swift",
component: try ComponentID(WhereComponent.whereCore.rawValue),
source: "import RegionKit\nstruct Service {}"
component: ComponentID(WhereComponent.whereCore.rawValue),
source: "import RegionKit\nimport WhereSurface\nstruct Service {}",
),
SourceInput(
path: "Where/WhereUI/Sources/Screen.swift",
component: try ComponentID(WhereComponent.whereUI.rawValue),
source: "import WhereCore\nimport SwiftUI\nstruct Screen {}"
component: ComponentID(WhereComponent.whereUI.rawValue),
source: "import WhereCore\nimport SwiftUI\nstruct Screen {}",
),
]
)
SourceInput(
path: "Where/WhereMenuBar/Sources/MenuBar.swift",
component: ComponentID(WhereComponent.menuBar.rawValue),
source: "import SwiftUI\nimport WhereSurface\nstruct MenuBar {}",
),
],
),
)

#expect(report.violations.isEmpty)
}

@Test
func `WhereSurface cannot depend upward on WhereCore`() throws {
let report = try bumper.evaluate(
RepositoryInput(
architecture: bumper.architecture,
files: [
SourceInput(
path: "Where/WhereSurface/Sources/Snapshot.swift",
component: ComponentID(WhereComponent.whereSurface.rawValue),
source: "import WhereCore\nstruct Snapshot {}",
),
],
),
)

let violation = try #require(report.violations.first)
#expect(report.violations.count == 1)
#expect(violation.rule.id == .componentBoundary)
#expect(violation.path.rawValue == "Where/WhereSurface/Sources/Snapshot.swift")
}

@Test
func `RegionKit cannot depend upward on WhereCore`() throws {
let report = try bumper.evaluate(
Expand All @@ -32,11 +58,11 @@ func `RegionKit cannot depend upward on WhereCore`() throws {
files: [
SourceInput(
path: "Where/RegionKit/Sources/Region.swift",
component: try ComponentID(WhereComponent.regionKit.rawValue),
source: "import WhereCore\nstruct Region {}"
component: ComponentID(WhereComponent.regionKit.rawValue),
source: "import WhereCore\nstruct Region {}",
),
]
)
],
),
)

let violation = try #require(report.violations.first)
Expand All @@ -53,11 +79,11 @@ func `WhereUI cannot import persistence`() throws {
files: [
SourceInput(
path: "Where/WhereUI/Sources/Screen.swift",
component: try ComponentID(WhereComponent.whereUI.rawValue),
source: "import SwiftData\nstruct Screen {}"
component: ComponentID(WhereComponent.whereUI.rawValue),
source: "import SwiftData\nstruct Screen {}",
),
]
)
],
),
)

let violation = try #require(report.violations.first)
Expand All @@ -74,16 +100,16 @@ func `Where adapters cannot link Broadway directly`() throws {
files: [
SourceInput(
path: "Where/WhereWidgets/Sources/Widget.swift",
component: try ComponentID(WhereComponent.widgets.rawValue),
source: "import BroadwayUI\nstruct Widget {}"
component: ComponentID(WhereComponent.widgets.rawValue),
source: "import BroadwayUI\nstruct Widget {}",
),
SourceInput(
path: "Where/WhereIntents/Sources/Intent.swift",
component: try ComponentID(WhereComponent.whereIntents.rawValue),
source: "import BroadwayCore\nstruct Intent {}"
component: ComponentID(WhereComponent.whereIntents.rawValue),
source: "import BroadwayCore\nstruct Intent {}",
),
]
)
],
),
)

#expect(report.violations.count == 2)
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this seems sketchy, review please

Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,37 @@ jobs:
if-no-files-found: warn
retention-days: 7

catalyst:
name: Build (Mac Catalyst)
needs: format
runs-on: xcode-27
timeout-minutes: 30
env:
CATALYST_DERIVED_DATA: ${{ github.workspace }}/catalyst-derived
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- name: Generate project
run: ./ide --no-open
- name: Build Where for Mac Catalyst
run: |
xcodebuild build \
-workspace Stuff.xcworkspace \
-scheme Where-Catalyst \
-destination 'generic/platform=macOS,variant=Mac Catalyst' \
-derivedDataPath "$CATALYST_DERIVED_DATA" \
CODE_SIGNING_ALLOWED=NO
- name: Verify embedded Mac surfaces
run: |
APP="$CATALYST_DERIVED_DATA/Build/Products/Debug-maccatalyst/Where.app"
HELPER="$APP/Contents/Library/LoginItems/WhereMenuBar.app"
test -x "$HELPER/Contents/MacOS/WhereMenuBar"
test "$(plutil -extract CFBundleIdentifier raw -o - "$HELPER/Contents/Info.plist")" = "com.stuff.where.menubar"
test "$(plutil -extract CFBundlePackageType raw -o - "$HELPER/Contents/Info.plist")" = "APPL"
! plutil -extract NSMainStoryboardFile raw -o - "$HELPER/Contents/Info.plist"
test -d "$APP/Contents/PlugIns/WhereWidgets.appex"
test -d "$APP/Contents/PlugIns/WhereShareExtension.appex"

snapshot:
name: Snapshot Tests (iOS)
needs: format
Expand Down
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ app onto a connected iPhone without the Xcode UI, use
configured once via `./ide --team-id` (see
[`Where/AGENTS.md`](Where/AGENTS.md#installing-to-a-device)).

Where's Mac app is the same target built for Mac Catalyst. Use the explicit
`Where-Catalyst` scheme: it builds the native `WhereMenuBar` login item first,
then the Catalyst app conditionally embeds it at
`Contents/Library/LoginItems`. Keep that manual build order and copy phase
together; Tuist rejects a direct dependency edge between the Catalyst and
native-macOS targets.

## Per-module docs

Shared modules live under `Shared/`, feature modules under a top-level folder
Expand Down
20 changes: 19 additions & 1 deletion BumperBowling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ import BumperBowlingCore

enum WhereComponent: String, ComponentKey {
case regionKit
case whereSurface
case whereCore
case whereUI
case whereIntents
case app
case widgets
case shareExtension
case menuBar
case regionViewer
}

let bumper = BumperProject {
Included {
"Where/RegionKit/Sources"
"Where/WhereSurface/Sources"
"Where/WhereCore/Sources"
"Where/WhereUI/Sources"
"Where/WhereIntents/Sources"
"Where/Where/Sources"
"Where/WhereWidgets/Sources"
"Where/WhereShareExtension/Sources"
"Where/WhereMenuBar/Sources"
"Where/RegionViewer/Sources"
}

Expand All @@ -37,10 +41,16 @@ let bumper = BumperProject {
DoesNotUse("CoreLocation")
}

Component(.whereSurface) {
Owns("Where/WhereSurface/Sources")
Modules("WhereSurface")
Applies(.whereFoundationLayer)
}

Component(.whereCore) {
Owns("Where/WhereCore/Sources")
Modules("WhereCore")
MayDependOn(.regionKit)
MayDependOn(.regionKit, .whereSurface)
Applies(.whereDomainLayer)
}

Expand Down Expand Up @@ -82,6 +92,14 @@ let bumper = BumperProject {
Applies(.whereAdapterLayer)
}

Component(.menuBar) {
Owns("Where/WhereMenuBar/Sources")
Modules("WhereMenuBar")
MayDependOn(.whereSurface)
Applies(.whereMacHostLayer)
DoesNotUse("CoreLocation", "SwiftData")
}

Component(.regionViewer) {
Owns("Where/RegionViewer/Sources")
Modules("RegionViewer")
Expand Down
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let package = Package(
defaultLocalization: "en",
platforms: [
.iOS(.v26),
.macOS(.v26),
],
products: [
.library(name: "StuffCore", targets: ["StuffCore"]),
Expand All @@ -22,6 +23,7 @@ let package = Package(
.library(name: "SnapshotKitTesting", targets: ["SnapshotKitTesting"]),
.library(name: "TestHostSupport", targets: ["TestHostSupport"]),
.library(name: "RegionKit", targets: ["RegionKit"]),
.library(name: "WhereSurface", targets: ["WhereSurface"]),
.library(name: "WhereCore", targets: ["WhereCore"]),
.library(name: "WhereUI", targets: ["WhereUI"]),
.library(name: "WhereIntents", targets: ["WhereIntents"]),
Expand Down Expand Up @@ -136,12 +138,17 @@ let package = Package(
.process("Resources"),
],
),
.target(
name: "WhereSurface",
path: "Where/WhereSurface/Sources",
),
.target(
name: "WhereCore",
dependencies: [
.target(name: "CreditKit"),
.target(name: "PeriscopeCore"),
.target(name: "RegionKit"),
.target(name: "WhereSurface"),
.product(name: "ZIPFoundation", package: "ZIPFoundation"),
],
path: "Where/WhereCore/Sources",
Expand Down
Loading