Skip to content

feat(example): Add Weather sample app#395

Open
cameroncooke wants to merge 9 commits intomainfrom
weather-app
Open

feat(example): Add Weather sample app#395
cameroncooke wants to merge 9 commits intomainfrom
weather-app

Conversation

@cameroncooke
Copy link
Copy Markdown
Collaborator

@cameroncooke cameroncooke commented May 5, 2026

This pull request introduces the initial Atmos Weather iOS app prototype, including project configuration, documentation, assets, and core application logic. The changes establish the project's structure, define API schemas, provide developer documentation, and implement the main SwiftUI view for the app.

Project configuration and assets:

  • Added Xcode project scheme (Weather.xcscheme) to support building, running, and testing the app, including configuration for launching with mock weather data.
  • Added Xcode asset catalogs for app icons and accent colors (Assets.xcassets), providing required resources for the app's appearance. [1] [2] [3]
  • Introduced XcodeBuildMCP configuration (config.yaml) for defining build and test workflows and simulator preferences.

Documentation and API schemas:

  • Added comprehensive README.md with instructions for building, running, and testing the app, as well as details about API endpoints, fixtures, and schemas.
  • Added AGENTS.md with guidance for using XcodeBuildMCP tools.
  • Introduced OpenAI-compatible JSON schema files for default locations and search locations API responses, describing the expected data structure for the DTO layer. [1] [2]

Core application logic:

  • Implemented the main SwiftUI view (ContentView.swift), handling weather data loading, location selection, error handling, and presentation of sheets for locations, settings, and precipitation details.

@cameroncooke cameroncooke marked this pull request as ready for review May 5, 2026 20:25
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 5, 2026

Open in StackBlitz

npm i https://pkg.pr.new/xcodebuildmcp@395

commit: a0ca9a7

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 4 issues found in the latest run.

  • ✅ Fixed: DTO enum missing snake_case raw values for decoding
    • Added explicit snake_case raw values to SolarDayProgressKindDTO enum ('before_sunrise' and 'after_sunset') to match the JSON schema.
  • ✅ Fixed: API client and schema disagree on response shape
    • Created WeatherLocationsResponseDTO wrapper and updated API client methods to decode the schema-compliant response with 'locations' key.
  • ✅ Fixed: SunMiniCard shows "Sunset was" before sunrise occurs
    • Updated otherLabel logic to return 'Sunset at' for beforeSunrise case instead of 'Sunset was' since sunset is still in the future.
  • ✅ Fixed: Hardcoded Apple development team ID in project
    • Removed hardcoded DEVELOPMENT_TEAM from all build configurations and removed machine-specific simulatorId from config.yaml.

Create PR

Or push these changes by commenting:

@cursor push 865da5b8e9
Preview (865da5b8e9)
diff --git a/example_projects/Weather/.xcodebuildmcp/config.yaml b/example_projects/Weather/.xcodebuildmcp/config.yaml
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/.xcodebuildmcp/config.yaml
@@ -1,0 +1,13 @@
+schemaVersion: 1
+enabledWorkflows:
+  - simulator
+  - ui-automation
+debug: false
+sentryDisabled: false
+sessionDefaults:
+  projectPath: Weather.xcodeproj
+  scheme: Weather
+  simulatorName: iPhone 17 Pro
+setupPreferences:
+  platforms:
+    - iOS

diff --git a/example_projects/Weather/AGENTS.md b/example_projects/Weather/AGENTS.md
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/AGENTS.md
@@ -1,0 +1,3 @@
+# AGENTS.md
+
+- If using XcodeBuildMCP, use the installed XcodeBuildMCP skill before calling XcodeBuildMCP tools.

diff --git a/example_projects/Weather/README.md b/example_projects/Weather/README.md
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/README.md
@@ -1,0 +1,101 @@
+# Atmos Weather
+
+Atmos Weather is a native SwiftUI weather app prototype for iOS.
+
+## Launch with mock weather data
+
+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
+```
+
+## JSON fixtures
+
+Fixture JSON files live in:
+
+```text
+WeatherTests/Fixtures/
+```
+
+Current fixtures:
+
+- `WeatherTests/Fixtures/default-locations.json`
+- `WeatherTests/Fixtures/search-locations.json`
+- `WeatherTests/Fixtures/weather-report-loc-current-san-francisco.json`
+
+## API schemas
+
+OpenAI-compatible API schema files live in:
+
+```text
+Schemas/
+```
+
+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
+
+The production client is `URLSessionWeatherAPIClient`. It currently expects a JSON API rooted at:
+
+```text
+https://api.atmosweather.example/v1
+```
+
+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:
+
+```http
+GET /v1/locations/default
+```
+
+Search locations:
+
+```http
+GET /v1/locations/search?query=San%20Francisco
+```
+
+Weather report:
+
+```http
+GET /v1/weather/loc-current-san-francisco
+```
+
+### Response expectations
+
+- Responses must be JSON.
+- Successful responses should use a `2xx` HTTP status code.
+- Non-`2xx` responses are treated as API failures.
+
+## Tests
+
+Run the app test suite through XcodeBuildMCP:
+
+```bash
+../../build/cli.js simulator test
+```
+
+UI tests inject `--mock-weather-api` themselves so they do not depend on the production API endpoint.
\ No newline at end of file

diff --git a/example_projects/Weather/Schemas/default-locations.schema.json b/example_projects/Weather/Schemas/default-locations.schema.json
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/Schemas/default-locations.schema.json
@@ -1,0 +1,93 @@
+{
+  "type": "json_schema",
+  "json_schema": {
+    "name": "default_locations_response",
+    "strict": true,
+    "schema": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "locations": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "name": {
+                "type": "string"
+              },
+              "subtitle": {
+                "type": "string"
+              },
+              "country": {
+                "type": [
+                  "string",
+                  "null"
+                ]
+              },
+              "temperatureC": {
+                "type": "integer"
+              },
+              "highC": {
+                "type": "integer"
+              },
+              "lowC": {
+                "type": "integer"
+              },
+              "condition": {
+                "type": "string",
+                "enum": [
+                  "sunny",
+                  "mostly_sunny",
+                  "partly_cloudy",
+                  "cloudy",
+                  "clear_day",
+                  "clear_night",
+                  "light_rain",
+                  "heavy_rain",
+                  "light_snow",
+                  "snow_showers",
+                  "thunderstorms",
+                  "hazy"
+                ]
+              },
+              "localTime": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                  "hour": {
+                    "type": "integer"
+                  },
+                  "minute": {
+                    "type": "integer"
+                  }
+                },
+                "required": [
+                  "hour",
+                  "minute"
+                ]
+              }
+            },
+            "required": [
+              "id",
+              "name",
+              "subtitle",
+              "country",
+              "temperatureC",
+              "highC",
+              "lowC",
+              "condition",
+              "localTime"
+            ]
+          }
+        }
+      },
+      "required": [
+        "locations"
+      ]
+    }
+  }
+}

diff --git a/example_projects/Weather/Schemas/search-locations.schema.json b/example_projects/Weather/Schemas/search-locations.schema.json
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/Schemas/search-locations.schema.json
@@ -1,0 +1,93 @@
+{
+  "type": "json_schema",
+  "json_schema": {
+    "name": "search_locations_response",
+    "strict": true,
+    "schema": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "locations": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "name": {
+                "type": "string"
+              },
+              "subtitle": {
+                "type": "string"
+              },
+              "country": {
+                "type": [
+                  "string",
+                  "null"
+                ]
+              },
+              "temperatureC": {
+                "type": "integer"
+              },
+              "highC": {
+                "type": "integer"
+              },
+              "lowC": {
+                "type": "integer"
+              },
+              "condition": {
+                "type": "string",
+                "enum": [
+                  "sunny",
+                  "mostly_sunny",
+                  "partly_cloudy",
+                  "cloudy",
+                  "clear_day",
+                  "clear_night",
+                  "light_rain",
+                  "heavy_rain",
+                  "light_snow",
+                  "snow_showers",
+                  "thunderstorms",
+                  "hazy"
+                ]
+              },
+              "localTime": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                  "hour": {
+                    "type": "integer"
+                  },
+                  "minute": {
+                    "type": "integer"
+                  }
+                },
+                "required": [
+                  "hour",
+                  "minute"
+                ]
+              }
+            },
+            "required": [
+              "id",
+              "name",
+              "subtitle",
+              "country",
+              "temperatureC",
+              "highC",
+              "lowC",
+              "condition",
+              "localTime"
+            ]
+          }
+        }
+      },
+      "required": [
+        "locations"
+      ]
+    }
+  }
+}

diff --git a/example_projects/Weather/Schemas/weather-report.schema.json b/example_projects/Weather/Schemas/weather-report.schema.json
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/Schemas/weather-report.schema.json
@@ -1,0 +1,510 @@
+{
+  "type": "json_schema",
+  "json_schema": {
+    "name": "weather_report_response",
+    "strict": true,
+    "schema": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "current": {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "id": {
+              "type": "string"
+            },
+            "temperatureC": {
+              "type": "integer"
+            },
+            "highC": {
+              "type": "integer"
+            },
+            "lowC": {
+              "type": "integer"
+            },
+            "feelsLikeC": {
+              "type": "integer"
+            },
+            "condition": {
+              "type": "string",
+              "enum": [
+                "sunny",
+                "mostly_sunny",
+                "partly_cloudy",
+                "cloudy",
+                "clear_day",
+                "clear_night",
+                "light_rain",
+                "heavy_rain",
+                "light_snow",
+                "snow_showers",
+                "thunderstorms",
+                "hazy"
+              ]
+            },
+            "solarProgress": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "kind": {
+                  "type": "string",
+                  "enum": [
+                    "before_sunrise",
+                    "daylight",
+                    "after_sunset"
+                  ]
+                },
+                "daylightFraction": {
+                  "type": [
+                    "number",
+                    "null"
+                  ]
+                }
+              },
+              "required": [
+                "kind",
+                "daylightFraction"
+              ]
+            },
+            "sunrise": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "hour": {
+                  "type": "integer"
+                },
+                "minute": {
+                  "type": "integer"
+                }
+              },
+              "required": [
+                "hour",
+                "minute"
+              ]
+            },
+            "sunset": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "hour": {
+                  "type": "integer"
+                },
+                "minute": {
+                  "type": "integer"
+                }
+              },
+              "required": [
+                "hour",
+                "minute"
+              ]
+            },
+            "airQualityIndex": {
+              "type": "integer"
+            },
+            "airQualityCategory": {
+              "type": "string",
+              "enum": [
+                "good",
+                "moderate",
+                "unhealthy_for_sensitive_groups",
+                "unhealthy",
+                "very_unhealthy",
+                "hazardous"
+              ]
+            },
+            "uvIndex": {
+              "type": "integer"
+            },
+            "uvCategory": {
+              "type": "string",
+              "enum": [
+                "none",
+                "low",
+                "moderate",
+                "high",
+                "very_high",
+                "extreme"
+              ]
+            },
+            "windKph": {
+              "type": "integer"
+            },
+            "windDirectionDegrees": {
+              "type": "number"
+            },
+            "humidity": {
+              "type": "integer"
+            },
+            "visibilityKilometers": {
+              "type": "number"
+            },
+            "pressureMillibars": {
+              "type": "integer"
+            },
+            "pressureTrend": {
+              "type": "string",
+              "enum": [
+                "rising",
+                "steady",
+                "falling"
+              ]
+            },
+            "precipChance": {
+              "type": "integer"
+            }
+          },
+          "required": [
+            "id",
+            "temperatureC",
+            "highC",
+            "lowC",
+            "feelsLikeC",
+            "condition",
+            "solarProgress",
+            "sunrise",
+            "sunset",
+            "airQualityIndex",
+            "airQualityCategory",
+            "uvIndex",
+            "uvCategory",
+            "windKph",
+            "windDirectionDegrees",
+            "humidity",
+            "visibilityKilometers",
+            "pressureMillibars",
+            "pressureTrend",
+            "precipChance"
+          ]
+        },
+        "hourly": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "hour": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                  "kind": {
+                    "type": "string",
+                    "enum": [
+                      "current",
+                      "clock"
+                    ]
+                  },
+                  "hour": {
+                    "type": [
+                      "integer",
+                      "null"
+                    ]
+                  },
+                  "minute": {
+                    "type": [
+                      "integer",
+                      "null"
+                    ]
+                  }
+                },
+                "required": [
+                  "kind",
+                  "hour",
+                  "minute"
+                ]
+              },
+              "temperatureC": {
+                "type": "integer"
+              },
+              "condition": {
+                "type": "string",
+                "enum": [
+                  "sunny",
+                  "mostly_sunny",
+                  "partly_cloudy",
+                  "cloudy",
+                  "clear_day",
+                  "clear_night",
+                  "light_rain",
+                  "heavy_rain",
+                  "light_snow",
+                  "snow_showers",
+                  "thunderstorms",
+                  "hazy"
+                ]
+              }
+            },
+            "required": [
+              "id",
+              "hour",
+              "temperatureC",
+              "condition"
+            ]
+          }
+        },
+        "daily": {
+          "type": "array",
+          "items": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+              "id": {
+                "type": "string"
+              },
+              "day": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                  "kind": {
+                    "type": "string",
+                    "enum": [
+                      "today",
+                      "weekday"
+                    ]
+                  },
+                  "weekdayRawValue": {
+                    "type": [
+                      "integer",
+                      "null"
+                    ],
+                    "enum": [
+                      1,
+                      2,
+                      3,
+                      4,
+                      5,
+                      6,
+                      7,
+                      null
+                    ]
+                  }
+                },
+                "required": [
+                  "kind",
+                  "weekdayRawValue"
+                ]
+              },
+              "condition": {
+                "type": "string",
+                "enum": [
+                  "sunny",
+                  "mostly_sunny",
+                  "partly_cloudy",
+                  "cloudy",
+                  "clear_day",
+                  "clear_night",
+                  "light_rain",
+                  "heavy_rain",
+                  "light_snow",
+                  "snow_showers",
+                  "thunderstorms",
+                  "hazy"
+                ]
+              },
+              "lowC": {
+                "type": "integer"
+              },
+              "highC": {
+                "type": "integer"
+              },
+              "weekLowC": {
+                "type": "integer"
+              },
+              "weekHighC": {
+                "type": "integer"
+              }
+            },
+            "required": [
+              "id",
+              "day",
+              "condition",
+              "lowC",
+              "highC",
+              "weekLowC",
+              "weekHighC"
+            ]
+          }
+        },
+        "precipitationDetailCurrent": {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "id": {
+              "type": "string"
+            },
+            "temperatureC": {
+              "type": "integer"
+            },
+            "highC": {
+              "type": "integer"
+            },
+            "lowC": {
+              "type": "integer"
+            },
+            "feelsLikeC": {
+              "type": "integer"
+            },
+            "condition": {
+              "type": "string",
+              "enum": [
+                "sunny",
+                "mostly_sunny",
+                "partly_cloudy",
+                "cloudy",
+                "clear_day",
+                "clear_night",
+                "light_rain",
+                "heavy_rain",
+                "light_snow",
+                "snow_showers",
+                "thunderstorms",
+                "hazy"
+              ]
+            },
+            "solarProgress": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "kind": {
+                  "type": "string",
+                  "enum": [
+                    "before_sunrise",
+                    "daylight",
+                    "after_sunset"
+                  ]
+                },
+                "daylightFraction": {
+                  "type": [
+                    "number",
+                    "null"
+                  ]
+                }
+              },
+              "required": [
+                "kind",
+                "daylightFraction"
+              ]
+            },
+            "sunrise": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "hour": {
+                  "type": "integer"
+                },
+                "minute": {
+                  "type": "integer"
+                }
+              },
+              "required": [
+                "hour",
+                "minute"
+              ]
+            },
+            "sunset": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "hour": {
+                  "type": "integer"
+                },
+                "minute": {
+                  "type": "integer"
+                }
+              },
+              "required": [
+                "hour",
+                "minute"
+              ]
+            },
+            "airQualityIndex": {
+              "type": "integer"
+            },
+            "airQualityCategory": {
+              "type": "string",
+              "enum": [
+                "good",
+                "moderate",
+                "unhealthy_for_sensitive_groups",
+                "unhealthy",
+                "very_unhealthy",
+                "hazardous"
+              ]
+            },
+            "uvIndex": {
+              "type": "integer"
+            },
+            "uvCategory": {
+              "type": "string",
+              "enum": [
+                "none",
+                "low",
+                "moderate",
+                "high",
+                "very_high",
+                "extreme"
+              ]
+            },
+            "windKph": {
+              "type": "integer"
+            },
+            "windDirectionDegrees": {
+              "type": "number"
+            },
+            "humidity": {
+              "type": "integer"
+            },
+            "visibilityKilometers": {
+              "type": "number"
... diff truncated: showing 800 of 6515 lines

You can send follow-ups to the cloud agent here.

Comment thread example_projects/Weather/Weather/Services/WeatherClientDTOs.swift
Comment thread example_projects/Weather/Weather/Services/WeatherAPIClient.swift
Comment thread example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift
Comment thread example_projects/Weather/Weather.xcodeproj/project.pbxproj Outdated
Comment thread example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 6 total unresolved issues (including 4 from previous reviews).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Invalid clock time crashes instead of throwing error
    • Added guard validation in LocalClockTime.init(dto:) to check hour (0...23) and minute (0...59) ranges and throw WeatherDTOMappingError.invalidClockTime before calling the precondition-backed domain initializer.
  • ✅ Fixed: Task cancellation mishandled as weather API error
    • Added specific catch clause for CancellationError that returns early without setting weatherErrorMessage, preventing false error banners when tasks are cancelled due to location changes.

Create PR

Or push these changes by commenting:

@cursor push 178a174816
Preview (178a174816)
diff --git a/example_projects/Weather/Weather/ContentView.swift b/example_projects/Weather/Weather/ContentView.swift
--- a/example_projects/Weather/Weather/ContentView.swift
+++ b/example_projects/Weather/Weather/ContentView.swift
@@ -150,6 +150,8 @@
 
         do {
             report = try await weatherService.weather(for: locationID)
+        } catch is CancellationError {
+            return
         } catch {
             weatherErrorMessage = "Weather unavailable"
         }

diff --git a/example_projects/Weather/Weather/Services/WeatherClientDTOs.swift b/example_projects/Weather/Weather/Services/WeatherClientDTOs.swift
--- a/example_projects/Weather/Weather/Services/WeatherClientDTOs.swift
+++ b/example_projects/Weather/Weather/Services/WeatherClientDTOs.swift
@@ -142,6 +142,7 @@
     case missingWeekday
     case invalidWeekday(Int)
     case invalidWindDirection(Double)
+    case invalidClockTime(hour: Int, minute: Int)
 }
 
 extension WeatherReport {
@@ -241,6 +242,9 @@
 
 private extension LocalClockTime {
     init(dto: LocalClockTimeDTO) throws {
+        guard (0...23).contains(dto.hour), (0...59).contains(dto.minute) else {
+            throw WeatherDTOMappingError.invalidClockTime(hour: dto.hour, minute: dto.minute)
+        }
         self.init(hour: dto.hour, minute: dto.minute)
     }
 }

You can send follow-ups to the cloud agent here.

Comment thread example_projects/Weather/Weather/Services/WeatherClientDTOs.swift
Comment thread example_projects/Weather/Weather/ContentView.swift
Align API decoding with the response schemas, validate DTO clock values
before constructing domain models, and fix UI edge cases reported in PR
review.

Remove account- and machine-specific Weather example configuration so the
sample project is portable for contributors.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Comment thread example_projects/Weather/Weather/Services/WeatherClientDTOs.swift Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: SunMiniCard shows wrong event after sunset
    • Changed afterSunset case to show next sunrise as primary display instead of repeating the past sunset event.
  • ✅ Fixed: Noise texture is invisible due to rounding
    • Removed .rounded(.down) call that was causing alpha to always be 0, making noise texture visible.

Create PR

Or push these changes by commenting:

@cursor push d434957ba2
Preview (d434957ba2)
diff --git a/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift b/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
--- a/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
+++ b/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
@@ -72,7 +72,7 @@
         Canvas { context, size in
             for x in stride(from: 0, through: size.width, by: 18) {
                 for y in stride(from: 0, through: size.height, by: 18) {
-                    let alpha = WeatherMetricHelpers.deterministicPercent(seed: Int(x + y), index: Int(x * 3 + y)).rounded(.down) * 0.006
+                    let alpha = WeatherMetricHelpers.deterministicPercent(seed: Int(x + y), index: Int(x * 3 + y)) * 0.006
                     context.fill(
                         Path(CGRect(x: x, y: y, width: 1, height: 1)),
                         with: .color(.white.opacity(alpha))

diff --git a/example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift b/example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift
--- a/example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift
+++ b/example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift
@@ -34,18 +34,18 @@
 
     private var nextLabel: String {
         switch current.solarProgress {
-        case .beforeSunrise:
+        case .beforeSunrise, .afterSunset:
             "SUNRISE"
-        case .daylight, .afterSunset:
+        case .daylight:
             "SUNSET"
         }
     }
 
     private var nextTime: String {
         switch current.solarProgress {
-        case .beforeSunrise:
+        case .beforeSunrise, .afterSunset:
             current.sunrise.fullClockLabel
-        case .daylight, .afterSunset:
+        case .daylight:
             current.sunset.fullClockLabel
         }
     }

You can send follow-ups to the cloud agent here.

Comment thread example_projects/Weather/Weather/Views/Sections/SunMiniCard.swift
Comment thread example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift Outdated
cameroncooke and others added 3 commits May 5, 2026 22:16
Normalize the API value 360 degrees to the domain model's zero-degree
north representation before constructing WindDirection.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Show the sun card's after-sunset state as a past event and restore the
background noise layer by preserving deterministic alpha values.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Update the sun card after-sunset state to surface sunrise as the next solar
event while keeping the passed sunset in the secondary label.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Stale error message applied to wrong location
    • Added stale-location guard before setting weatherErrorMessage in the catch block to prevent old tasks from overwriting error state after location switch.
  • ✅ Fixed: Triplicate Array[safe:] extension across view files
    • Extracted the duplicate Array[safe:] extension into a shared ArrayExtensions.swift file and removed it from AtmosBackground, ConditionGrid, and DailyForecastCard.

Create PR

Or push these changes by commenting:

@cursor push 83a718035f
Preview (83a718035f)
diff --git a/example_projects/Weather/Weather/ContentView.swift b/example_projects/Weather/Weather/ContentView.swift
--- a/example_projects/Weather/Weather/ContentView.swift
+++ b/example_projects/Weather/Weather/ContentView.swift
@@ -155,6 +155,7 @@
             report = loadedReport
         } catch is CancellationError {
         } catch {
+            guard selectedLocation?.id == locationID else { return }
             weatherErrorMessage = "Weather unavailable"
         }
 

diff --git a/example_projects/Weather/Weather/Extensions/ArrayExtensions.swift b/example_projects/Weather/Weather/Extensions/ArrayExtensions.swift
new file mode 100644
--- /dev/null
+++ b/example_projects/Weather/Weather/Extensions/ArrayExtensions.swift
@@ -1,0 +1,14 @@
+//
+//  ArrayExtensions.swift
+//  Weather
+//
+//  Created by Cameron on 05/05/2026.
+//
+
+import Foundation
+
+extension Array {
+    subscript(safe index: Int) -> Element? {
+        indices.contains(index) ? self[index] : nil
+    }
+}

diff --git a/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift b/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
--- a/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
+++ b/example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift
@@ -234,9 +234,3 @@
             .opacity(opacity)
     }
 }
-
-private extension Array {
-    subscript(safe index: Int) -> Element? {
-        indices.contains(index) ? self[index] : nil
-    }
-}

diff --git a/example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift b/example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift
--- a/example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift
+++ b/example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift
@@ -366,9 +366,3 @@
         return path
     }
 }
-
-private extension Array {
-    subscript(safe index: Int) -> Element? {
-        indices.contains(index) ? self[index] : nil
-    }
-}

diff --git a/example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift b/example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift
--- a/example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift
+++ b/example_projects/Weather/Weather/Views/Sections/DailyForecastCard.swift
@@ -110,9 +110,3 @@
         .frame(height: 12)
     }
 }
-
-private extension Array {
-    subscript(safe index: Int) -> Element? {
-        indices.contains(index) ? self[index] : nil
-    }
-}

You can send follow-ups to the cloud agent here.

Comment thread example_projects/Weather/Weather/ContentView.swift
Comment thread example_projects/Weather/Weather/Views/Backgrounds/AtmosBackground.swift Outdated
Avoid applying stale weather errors after location changes and centralize the
shared safe array subscript used by Weather views.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Comment thread example_projects/Weather/Weather/Views/Sections/ConditionGrid.swift Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Hero subtitle hardcodes "My Location" for all cities
    • Threaded the subtitle property from WeatherLocation through ContentView, AtmosWeatherScreen, and WeatherHeroView to display location-specific subtitles instead of the hardcoded 'My Location' text.

Create PR

Or push these changes by commenting:

@cursor push 4554cb49ff
Preview (4554cb49ff)
diff --git a/example_projects/Weather/Weather/ContentView.swift b/example_projects/Weather/Weather/ContentView.swift
--- a/example_projects/Weather/Weather/ContentView.swift
+++ b/example_projects/Weather/Weather/ContentView.swift
@@ -51,6 +51,7 @@
             if let report, let selectedLocation {
                 AtmosWeatherScreen(
                     locationName: selectedLocation.name,
+                    locationSubtitle: selectedLocation.subtitle,
                     current: report.current,
                     hourly: report.hourly,
                     daily: report.daily,

diff --git a/example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift b/example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift
--- a/example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift
+++ b/example_projects/Weather/Weather/Views/AtmosWeatherScreen.swift
@@ -2,6 +2,7 @@
 
 struct AtmosWeatherScreen: View {
     let locationName: String
+    let locationSubtitle: String
     let current: CurrentWeather
     let hourly: [HourlyForecast]
     let daily: [DailyForecast]
@@ -17,7 +18,7 @@
 
                 ScrollView {
                     VStack(spacing: 0) {
-                        WeatherHeroView(locationName: locationName, current: current, units: units)
+                        WeatherHeroView(locationName: locationName, locationSubtitle: locationSubtitle, current: current, units: units)
 
                         VStack(spacing: 10) {
                             HourlyForecastCard(forecasts: hourly, current: current, units: units)

diff --git a/example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift b/example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift
--- a/example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift
+++ b/example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift
@@ -2,6 +2,7 @@
 
 struct WeatherHeroView: View {
     let locationName: String
+    let locationSubtitle: String
     let current: CurrentWeather
     let units: WeatherUnits
 
@@ -12,7 +13,7 @@
                 .tracking(-0.3)
                 .accessibilityIdentifier("weather.heroLocation")
 
-            Text("My Location")
+            Text(locationSubtitle)
                 .font(.system(size: 13))
                 .tracking(0.2)
                 .foregroundStyle(current.theme.foregroundMuted)

You can send follow-ups to the cloud agent here.

Comment thread example_projects/Weather/Weather/Views/Sections/WeatherHeroView.swift Outdated
Plumb dew point and location subtitles through the Weather example and
make cancelled location searches clear their current loading state safely.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Comment thread example_projects/Weather/Weather/ContentView.swift
Keep Weather loading flags scoped to the active request and handle search
cancellations separately from genuine search errors.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Hourly curve points misaligned with forecast cells
    • Changed curve point x-coordinates from step-based distribution to cell-center alignment using (index + 0.5) * cellWidth formula.

Create PR

Or push these changes by commenting:

@cursor push b90a74ec6b
Preview (b90a74ec6b)
diff --git a/example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift b/example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift
--- a/example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift
+++ b/example_projects/Weather/Weather/Views/Sections/HourlyForecastCard.swift
@@ -100,13 +100,13 @@
         let minTemp = temperatures.min() ?? 0
         let maxTemp = temperatures.max() ?? minTemp + 1
         let range = max(maxTemp - minTemp, 1)
-        let step = size.width / CGFloat(max(forecasts.count - 1, 1))
+        let cellWidth = size.width / CGFloat(forecasts.count)
         let pad: CGFloat = 8
 
         return forecasts.enumerated().map { index, forecast in
             let normalized = CGFloat(forecast.temperatureC - minTemp) / CGFloat(range)
             return CGPoint(
-                x: CGFloat(index) * step,
+                x: (CGFloat(index) + 0.5) * cellWidth,
                 y: pad + (1 - normalized) * (size.height - pad * 2)
             )
         }

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit e6d3b4b. Configure here.

Center hourly curve points over their corresponding forecast cells so the
curve dots align with the hour labels and weather icons.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
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