diff options
| author | ivar <i@oiee.no> | 2026-03-23 15:24:10 +0100 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-03-23 15:24:10 +0100 |
| commit | dd2c54bb1a69fbfa0bf053e89ce256f2806086d6 (patch) | |
| tree | 820e7ce6ed701b51e07ef05fe2d06ab851807890 | |
| parent | 4e3def0d052ed048f21d90b3e1c3b9e7c4480060 (diff) | |
| download | solverv-dd2c54bb1a69fbfa0bf053e89ce256f2806086d6.tar.xz solverv-dd2c54bb1a69fbfa0bf053e89ce256f2806086d6.zip | |
Add concrete implementation details: algorithm reference, AppGroup schema with ISO 8601, widget refresh code, image asset names, progress calculation, state recovery logic
| -rw-r--r-- | docs/superpowers/specs/2026-03-23-solstice-widget-design.md | 89 |
1 files changed, 61 insertions, 28 deletions
diff --git a/docs/superpowers/specs/2026-03-23-solstice-widget-design.md b/docs/superpowers/specs/2026-03-23-solstice-widget-design.md index 1271435..d91f1f2 100644 --- a/docs/superpowers/specs/2026-03-23-solstice-widget-design.md +++ b/docs/superpowers/specs/2026-03-23-solstice-widget-design.md @@ -68,22 +68,21 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with **AppGroup Container:** Store user location and cached sunrise/sunset times using the container ID `group.com.ivarlovlie.solverv` **AppGroup Data Schema:** +Stored in UserDefaults using the `group.com.ivarlovlie.solverv` container. ```json { - "userLocation": { - "latitude": Double, - "longitude": Double, - "timestamp": Date (when location was last updated), - "isDefaultLocation": Boolean (true if using Greenwich fallback) - }, - "sunTimes": { - "date": Date (which day these times are for), - "sunrise": Date (full DateTime in local timezone), - "sunset": Date (full DateTime in local timezone), - "timestamp": Date (when this was calculated) - } + "userLocation.latitude": Double (e.g., 59.9139), + "userLocation.longitude": Double (e.g., 10.7522), + "userLocation.timestamp": String (ISO 8601, e.g., "2026-03-23T10:30:00Z"), + "userLocation.isDefaultLocation": Boolean (true = Greenwich, false = user-granted location), + + "sunTimes.date": String (ISO 8601 date only, e.g., "2026-03-23"), + "sunTimes.sunrise": String (ISO 8601 datetime in local timezone, e.g., "2026-03-23T06:42:00"), + "sunTimes.sunset": String (ISO 8601 datetime in local timezone, e.g., "2026-03-23T18:15:00"), + "sunTimes.timestamp": String (ISO 8601, when calculated, e.g., "2026-03-23T10:30:00Z") } ``` +All Date values stored as ISO 8601 strings for cross-process compatibility. - **Widget Timeline:** Updates at midnight (local time) using `timelineReloadPolicy: .after(nextMidnight)` - Both app and widget read from the same AppGroup container for consistency @@ -98,7 +97,17 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with - Seasonal image (fills most of space) - Event name (small caption) - Countdown in days (large, bold text) -- **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown +- **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` where `nextMidnight` is calculated as: + ```swift + var calendar = Calendar.current + var components = calendar.dateComponents([.year, .month, .day], from: Date()) + components.hour = 0 + components.minute = 0 + components.second = 0 + let todayMidnight = calendar.date(from: components)! + let nextMidnight = calendar.date(byAdding: .day, value: 1, to: todayMidnight)! + ``` + The widget refreshes daily at midnight local time, automatically accounting for DST transitions. - **Purpose:** Quick glance at how many days remain ### Medium Widget (364×169) @@ -121,11 +130,16 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with - Same image shown for all events in that season **Image Specifications:** -- Aspect ratio: 1:1 (square) -- Resolutions: 1x (1024×1024), 2x (2048×2048), 3x (3072×3072) -- Variants: One version each for light mode and dark mode -- Safe area: Ensure important visual content avoids outer 20-point margin -- Format: PNG with alpha channel (for light/dark transparency support) +- **Asset Names:** `SeasonSpring`, `SeasonSummer`, `SeasonAutumn`, `SeasonWinter` (stored in Assets.xcassets) +- **Color Set Strategy:** Each season asset has two variants (light and dark mode) using Xcode's Color Set appearance settings +- **Aspect Ratio:** 1:1 (square) +- **Resolutions per variant:** + - 1x: 1024×1024 + - 2x: 2048×2048 + - 3x: 3072×3072 +- **Safe Area:** Ensure important visual content avoids outer 20-point margin (on a 1024×1024 base, keep content within 960×960 center area) +- **Format:** PNG with alpha channel +- **Fallback:** If image fails to load, display solid color matching the season (spring: #4CAF50, summer: #FFC107, autumn: #FF9800, winter: #2196F3) --- @@ -135,8 +149,10 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with - Seasonal image (landscape orientation friendly) - Next event name (large) - Countdown in days (very large, prominent) -- Progress bar showing days elapsed since the previous solstice/equinox divided by total days in that cycle - - Example: If Winter Solstice (Dec 21) is passed and Summer Solstice (Jun 21) is upcoming, on Jan 21 the progress is 31/183 days elapsed +- Progress bar showing days elapsed since the previous solstice/equinox divided by total days until next solstice/equinox + - Calculation: `(today - lastSolsticeDate) / (nextSolsticeDate - lastSolsticeDate)` + - Example: Winter Solstice was Dec 21, 2025 (passed). Spring Equinox is Mar 20, 2026 (89 days later). On Jan 21, 2026, progress is 31/89 days elapsed. + - The progress bar **resets to 0%** the moment a new solstice/equinox occurs ### Middle Section - **Today's Sun Times** @@ -176,11 +192,17 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with - Sunrise/sunset calculated for user's timezone and location ### Sunrise/Sunset Algorithm -- Implement a simplified solar position algorithm based on the NOAA Sunrise/Sunset and Solar Equinoxes/Solstices algorithm -- No external APIs or third-party dependencies -- Algorithm inputs: latitude, longitude, date -- Algorithm output: sunrise time, sunset time (in local timezone) -- Cache results in AppGroup container to avoid recalculation on each app launch +- Implement a simplified solar position algorithm based on the NOAA algorithm (https://github.com/NOAA-OWP/sunpy) +- No external APIs; algorithm is self-contained +- **Algorithm Reference:** NOAA Solar Position Algorithm + - Calculate solar declination using Spencer's formula + - Calculate equation of time for date + - Calculate hour angle at sunrise/sunset + - Convert to local solar time then UTC +- **Inputs:** latitude (Double), longitude (Double), date (Date in user's timezone) +- **Outputs:** sunrise (Date), sunset (Date) in local timezone +- **Cache:** Store in AppGroup container; recalculate daily or when location changes +- **Accuracy:** Results valid to ±2 minutes --- @@ -256,9 +278,20 @@ Build an iOS app that displays a countdown to the next solstice or equinox, with - Example: Spring Equinox always displays spring imagery (green), even if shown during late winter on the calendar ### Offline Functionality -- **First Launch:** Location permission is requested. If granted, app calculates and caches sunrise/sunset. If denied, app uses Greenwich (0°, 0°) and displays all features with default location times. -- **Subsequent Use:** App and widget work entirely offline; all solstice dates and countdown calculations are local -- **Sunrise/Sunset:** Cached and recalculated daily or when location changes. If unavailable (permission denied), widget displays countdown only +- **First Launch:** + - App requests location permission + - If granted: Fetch location and calculate sunrise/sunset for that location, cache in AppGroup container + - If denied: Use default location (Greenwich, 0°/0°) and cache as `isDefaultLocation: true` + - All subsequent uses work offline +- **Subsequent Use:** + - App and widget work entirely offline; solstice dates and countdown calculations are all local + - Sunrise/sunset times use cached values from AppGroup container +- **State Recovery on Permission Changes:** + - If app is running when user changes location permission in Settings, recalculate sunrise/sunset on next app foreground event + - If AppGroup container is missing or corrupted on launch, fall back to Greenwich location and log error +- **Widget Behavior When Location Unavailable:** + - Widget always displays countdown (never fails) + - Sunrise/sunset times display as "—" if location permission denied and no cached data exists --- |
