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
2 changes: 1 addition & 1 deletion example_projects/Weather/.xcodebuildmcp/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ enabledWorkflows:
debug: false
sentryDisabled: false
sessionDefaults:
projectPath: Weather.xcodeproj
projectPath: app/Weather.xcodeproj
scheme: Weather
simulatorName: iPhone 17 Pro
setupPreferences:
Expand Down
111 changes: 41 additions & 70 deletions example_projects/Weather/README.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,72 @@
# Atmos Weather

Atmos Weather is a native SwiftUI weather app prototype for iOS.
Atmos Weather is a native SwiftUI weather app with a Node.js backend API.

## Launch with mock weather data
## Project structure

Build and run the app with XcodeBuildMCP first:

```bash
../../build/cli.js simulator build-and-run
```

Then relaunch the installed app with the mock API argument:

```bash
../../build/cli.js simulator launch-app \
--bundle-id com.sentry.weather.Weather \
--args=--mock-weather-api
Weather/
app/ iOS app (Xcode project)
backend/ API server (Hono + Node.js)
```

## JSON fixtures
## Backend

Fixture JSON files live in:
Start the API server:

```text
WeatherTests/Fixtures/
```bash
cd backend
npm install
npm run dev
```

Current fixtures:

- `WeatherTests/Fixtures/default-locations.json`
- `WeatherTests/Fixtures/search-locations.json`
- `WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json`
The server runs on `http://localhost:3001` by default. Set `PORT` to change it.

## API schemas
## iOS app

OpenAI-compatible API schema files live in:
Build and run with XcodeBuildMCP from the `app/` directory:

```text
Schemas/
```bash
cd app
../../../build/cli.js simulator build-and-run
```

Current schemas:

- `Schemas/default-locations.schema.json`
- `Schemas/search-locations.schema.json`
- `Schemas/weather-report.schema.json`

These schemas describe the JSON response shape expected by the DTO layer.

## Expected API endpoints
### Mock mode

The production client is `URLSessionWeatherAPIClient`. It currently expects a JSON API rooted at:
Relaunch with mock data (no backend required):

```text
https://api.atmosweather.example/v1
```bash
../../../build/cli.js simulator launch-app \
--bundle-id com.sentry.weather.Weather \
--args=--mock-weather-api
```

All endpoints are `GET` requests.

| Purpose | Method | Path | Request shape | Schema |
| --- | --- | --- | --- | --- |
| Default saved locations | `GET` | `/locations/default` | No path params, query params, or body. | `Schemas/default-locations.schema.json` |
| Search locations | `GET` | `/locations/search` | Query string: `query=<string>` | `Schemas/search-locations.schema.json` |
| Weather report for a location | `GET` | `/weather/{locationID}` | Path param: `locationID=<WeatherLocationDTO.id>` | `Schemas/weather-report.schema.json` |

### Request examples

Default locations:
### Tests

```http
GET /v1/locations/default
```bash
../../../build/cli.js simulator test
```

Search locations:

```http
GET /v1/locations/search?query=San%20Francisco
```
UI tests inject `--mock-weather-api` so they do not depend on the backend.

Weather report:
## API endpoints

```http
GET /v1/weather/loc-current-san-francisco
```
The backend serves three `GET` endpoints under `/v1`:

### Response expectations
| Purpose | Path | Params |
| --- | --- | --- |
| Default locations | `/v1/locations/default` | None |
| Search locations | `/v1/locations/search` | `?query=<string>` |
| Weather report | `/v1/weather/:locationID` | Path param |

- Responses must be JSON.
- Successful responses should use a `2xx` HTTP status code.
- Non-`2xx` responses are treated as API failures.
### JSON schemas

## Tests
Schema files in `app/Schemas/` describe the expected response shapes:

Run the app test suite through XcodeBuildMCP:
- `default-locations.schema.json`
- `search-locations.schema.json`
- `weather-report.schema.json`

```bash
../../build/cli.js simulator test
```
### Test fixtures

UI tests inject `--mock-weather-api` themselves so they do not depend on the production API endpoint.
Fixture JSON files in `app/WeatherTests/Fixtures/` are used by unit tests.
3 changes: 3 additions & 0 deletions example_projects/Weather/app/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# AGENTS.md

- If using XcodeBuildMCP, use the installed XcodeBuildMCP skill before calling XcodeBuildMCP tools.
11 changes: 11 additions & 0 deletions example_projects/Weather/app/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Info.plist;
Comment on lines 400 to +403
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The project has conflicting build settings for the Info.plist. GENERATE_INFOPLIST_FILE is YES while also specifying a custom INFOPLIST_FILE, which will likely be ignored.
Severity: HIGH

Suggested Fix

To ensure the custom Info.plist is used, set GENERATE_INFOPLIST_FILE = NO; in the build settings for both Debug and Release configurations. This resolves the conflict and allows the NSAllowsLocalNetworking key from your custom Info.plist to be included in the final app bundle.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: example_projects/Weather/app/Weather.xcodeproj/project.pbxproj#L400-L403

Potential issue: The Xcode project is configured with conflicting build settings for
handling the `Info.plist` file. Both `GENERATE_INFOPLIST_FILE` is set to `YES` and a
custom `INFOPLIST_FILE` is specified. These settings are mutually exclusive. Xcode will
likely prioritize auto-generating the `Info.plist`, causing the custom file and its
`NSAllowsLocalNetworking` key to be ignored. Without this key, App Transport Security
(ATS) will block HTTP requests to the local development server at
`http://localhost:3001`, resulting in network failures at runtime when the app tries to
fetch data.

Also affects:

  • example_projects/Weather/app/Weather.xcodeproj/project.pbxproj:445~448

"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -410,10 +411,10 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.3;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.Weather;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -427,7 +428,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
Expand All @@ -444,6 +445,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Info.plist;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand All @@ -454,10 +456,10 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.3;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.Weather;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -471,7 +473,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
Expand All @@ -483,8 +485,8 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MACOSX_DEPLOYMENT_TARGET = 26.3;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -497,7 +499,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Weather.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Weather";
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
Expand All @@ -509,8 +511,8 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MACOSX_DEPLOYMENT_TARGET = 26.3;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -523,7 +525,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Weather.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Weather";
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
Expand All @@ -534,8 +536,8 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MACOSX_DEPLOYMENT_TARGET = 26.3;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -548,7 +550,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_TARGET_NAME = Weather;
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
Expand All @@ -559,8 +561,8 @@
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.4;
MACOSX_DEPLOYMENT_TARGET = 26.3;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sentry.weather.WeatherUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -573,7 +575,7 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
TEST_TARGET_NAME = Weather;
XROS_DEPLOYMENT_TARGET = 26.4;
XROS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct WeatherAPIConfiguration: Sendable {
let baseURL: URL

static let production = WeatherAPIConfiguration(
baseURL: URL(string: "https://api.atmosweather.example/v1")!
baseURL: URL(string: "http://localhost:3001/v1")!
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ private struct WindCompass: View {
let opacity = cardinal ? 0.55 : intercardinal ? 0.32 : 0.16
let radians = (Double(angle) - 90) * .pi / 180
var path = Path()
path.move(to: CGPoint(x: center.x + cos(radians) * (outer - length), y: center.y + sin(radians) * (outer - length)))
path.addLine(to: CGPoint(x: center.x + cos(radians) * outer, y: center.y + sin(radians) * outer))
path.move(to: CGPoint(x: center.x + CGFloat(cos(radians)) * (outer - length), y: center.y + CGFloat(sin(radians)) * (outer - length)))
path.addLine(to: CGPoint(x: center.x + CGFloat(cos(radians)) * outer, y: center.y + CGFloat(sin(radians)) * outer))
context.stroke(path, with: .color(current.theme.foreground.opacity(opacity)), style: StrokeStyle(lineWidth: width, lineCap: .round))
}
}
Expand Down Expand Up @@ -158,7 +158,7 @@ private struct WindCompass: View {

private func point(center: CGPoint, radius: CGFloat, degrees: Double) -> CGPoint {
let radians = (degrees - 90) * .pi / 180
return CGPoint(x: center.x + cos(radians) * radius, y: center.y + sin(radians) * radius)
return CGPoint(x: center.x + CGFloat(cos(radians)) * radius, y: center.y + CGFloat(sin(radians)) * radius)
}
}

Expand Down
1 change: 1 addition & 0 deletions example_projects/Weather/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
Loading
Loading