summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs')
-rw-r--r--docs/superpowers/specs/2026-03-23-solstice-widget-design.md89
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
---