summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs
diff options
context:
space:
mode:
authorivar <i@oiee.no>2026-05-07 01:24:28 +0200
committerivar <i@oiee.no>2026-05-07 01:24:28 +0200
commit6eb17a18e901e2d7faa219d7e5a79083a5891dc9 (patch)
tree3d0796e1e567864dfdf7c675f7e8a5a40fb51a95 /docs/superpowers/specs
parent4fb690150b77afced6453e6bdb14cc4cf00d5305 (diff)
downloadsolverv-master.tar.xz
solverv-master.zip
RefactorsHEADmaster
Diffstat (limited to 'docs/superpowers/specs')
-rw-r--r--docs/superpowers/specs/2026-03-23-solstice-widget-design.md307
-rw-r--r--docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md152
2 files changed, 0 insertions, 459 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
deleted file mode 100644
index d91f1f2..0000000
--- a/docs/superpowers/specs/2026-03-23-solstice-widget-design.md
+++ /dev/null
@@ -1,307 +0,0 @@
-# Solstice Countdown Widget & App Design
-
-**Date:** 2026-03-23
-**Project:** Solverv (Solstice Countdown)
-**Status:** Design Approved
-
----
-
-## Overview
-
-Build an iOS app that displays a countdown to the next solstice or equinox, with season-specific imagery and detailed information. The app includes:
-- **Widget Extension:** Compact countdown widget in small, medium, and large sizes
-- **Main App:** Info screen showing expanded details including sunrise/sunset times and upcoming events
-
----
-
-## Requirements
-
-### Functional Requirements
-1. Track all four annual solstices/equinoxes (Spring, Summer, Autumn, Winter)
-2. Display countdown to next event in **days only**
-3. Show season-specific images that change based on upcoming event
-4. Calculate and display sunrise/sunset times for user's current location
-5. Show expanded event details in main app (exact times, season descriptions, upcoming event previews)
-6. Support multiple widget sizes (small, medium, large)
-
-### Non-Functional Requirements
-- All calculations happen locally (no external APIs)
-- Works offline after initial setup
-- Respects location privacy with explicit permission
-- Single location permission prompt (shared via AppGroup)
-- Data synced between app and widget via AppGroup container
-
----
-
-## Architecture
-
-### Data Models
-
-**SolsticeEvent**
-```
-- name: String (e.g., "Summer Solstice", "Spring Equinox")
-- date: Date (UTC)
-- season: Season (enum: spring, summer, autumn, winter)
-- seasonDescription: String (brief description of the season)
-```
-
-**SolsticeData**
-```
-- events: [SolsticeEvent] (hardcoded data, 2025-2030)
-- nextEvent() -> SolsticeEvent (returns next upcoming event)
-- allUpcoming(count: Int) -> [SolsticeEvent] (returns next N events)
-- daysUntil(_ event: SolsticeEvent) -> Int
-- progressToEvent(_ event: SolsticeEvent) -> (elapsed: Int, total: Int)
-```
-
-**SunTimes**
-```
-- latitude: Double
-- longitude: Double
-- date: Date
-- sunrise() -> Date
-- sunset() -> Date
-```
-
-### Data Sharing
-
-**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 (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
-- Cache invalidation: Sunrise/sunset cache is recalculated if stored date differs from today or location changes
-
----
-
-## Widget Specification
-
-### Small Widget (169×169)
-- **Layout:** Vertical stack
- - Seasonal image (fills most of space)
- - Event name (small caption)
- - Countdown in days (large, bold text)
-- **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)
-- **Layout:** Horizontal split
- - Left: Seasonal image (square)
- - Right: Vertical stack with event name, countdown (large), progress bar
-- **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown
-- **Purpose:** Balance of visual and numeric information
-
-### Large Widget (364×364)
-- **Layout:** Vertical stack
- - Top half: Seasonal image
- - Bottom half: Event name, countdown, progress bar, preview of next 3 upcoming events (mini list)
-- **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown
-- **Purpose:** Comprehensive view with upcoming events preview
-
-### Images
-- One image per season (spring, summer, autumn, winter)
-- Sourced from Assets.xcassets
-- Same image shown for all events in that season
-
-**Image Specifications:**
-- **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)
-
----
-
-## Main App Info Screen
-
-### Top Section
-- 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 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**
- - Sunrise time
- - Sunset time
- - Calculated from device location
-- **Season Info**
- - Season name
- - Brief description (e.g., "Spring Equinox — Day and night are approximately equal length")
-
-### Bottom Section
-- **Upcoming Events List** (scrollable)
- - Shows next 8-12 events
- - Each row displays:
- - Event name (e.g., "Spring Equinox")
- - Date/Time in local timezone, 12-hour format with AM/PM (e.g., "Mar 20, 2026 2:46 PM")
- - Days remaining as integer (e.g., "45 days")
- - Season color indicator: 12pt circle matching the season's primary color (spring: green, summer: yellow, autumn: orange, winter: blue)
-
-### Navigation
-- Tab bar or simple navigation to this screen
-- Refresh button to manually update sunrise/sunset (in case location changed)
-
----
-
-## Time Zone & Location Handling
-
-### Location
-- Request permission on first app launch
-- Store latitude/longitude in AppGroup container
-- Fall back to Greenwich/UTC (0°, 0°) if permission denied
-- User can manually update location in app settings
-
-### Time Zones
-- All solstice times stored in UTC (as they are now)
-- Convert to user's local timezone for display
-- Sunrise/sunset calculated for user's timezone and location
-
-### Sunrise/Sunset Algorithm
-- 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
-
----
-
-## Error Handling & Edge Cases
-
-### Location Permission
-- App requests permission on first launch
-- If denied, use default location (Greenwich) and notify user
-- User can grant permission later in system Settings
-
-### Time Zone Edge Cases
-- Solstice at midnight: Display correctly in both UTC and local time
-- User crosses timezone: Times update automatically on app launch
-- Widget timezone: Uses device timezone (set by system)
-
-### Data Integrity
-- Solstice dates are hardcoded and immutable
-- Sunrise/sunset cached but recalculated daily
-- Widget syncs with app via AppGroup on launch
-
----
-
-## Testing Strategy
-
-### Widget Testing
-- Preview all three widget sizes with mock solstice data
-- Verify countdown updates correctly across timezone boundaries
-- Test image display in different iOS versions (iOS 17 fallback)
-
-### App Testing
-- Verify sunrise/sunset calculations against known values
-- Test location permission flows (allowed, denied, not yet asked)
-- Test data sync between app and widget
-- Verify time zone conversions for various user locations
-
-### Integration Testing
-- Widget refreshes daily and displays current countdown
-- App and widget show consistent "next event" data
-- Location changes update sunrise/sunset in real time
-
----
-
-## Implementation Notes
-
-### Existing Code
-- `SolvervDef` already contains solstice dates (2025-2030)
-- Widget structure (`Solsnu_Widget.swift`) is scaffolded
-- Main app has basic SwiftUI structure ready for info screen
-
-### New Components to Build
-- `SunTimes` calculation (sunrise/sunset)
-- `SolsticeEvent` model
-- Widget layout variants (small, medium, large)
-- Info screen UI
-- AppGroup data sharing
-
-### Dependencies
-- None (all calculations are built-in or custom)
-- WidgetKit (already available)
-- SwiftUI (already used)
-
----
-
-## Additional Clarifications
-
-### Solstice Date Coverage
-- Hardcoded data spans 2025–2030
-- After 2030, app continues functioning but upcoming events won't display beyond December 2030
-- Plan for data expansion before 2030: extend to 2040+ in an app update
-
-### Season Assignment
-- The displayed season for an event is always the season being celebrated
-- Example: Spring Equinox always displays spring imagery (green), even if shown during late winter on the calendar
-
-### Offline Functionality
-- **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
-
----
-
-## Success Criteria
-
-✅ Widget displays countdown in days
-✅ Seasonal image changes based on next event
-✅ All four solstices/equinoxes tracked
-✅ Sunrise/sunset times calculated from location
-✅ Info screen shows all requested details
-✅ App and widget data stay in sync
-✅ Works offline after initial setup
-✅ All three widget sizes render correctly
diff --git a/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md b/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md
deleted file mode 100644
index 1e4ed54..0000000
--- a/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md
+++ /dev/null
@@ -1,152 +0,0 @@
-# Sunrise/Sunset Times Widget Feature
-
-**Date:** 2026-03-23
-**Status:** Design Approved
-
-## Overview
-
-Add sunrise and sunset time display to both small and medium widgets. Times are calculated locally using the existing NOAA solar position algorithm, formatted as HH:mm, and updated daily at midnight based on user location.
-
-## Requirements
-
-- Display sunrise/sunset times in HH:mm format on widgets
-- Calculate times locally using existing `SunTimes` utility
-- Use user's current location for calculations
-- Update once daily at midnight (aligned with current refresh policy)
-- Show nothing if location permission is denied or unavailable
-- Apply to both small and medium widget families
-
-## Architecture
-
-### Data Model
-
-Extend `SolvervDef` to include sunrise/sunset times:
-```
-SolvervDef
-├── date: Date
-├── bg: String
-├── sunriseTime: Date? (optional)
-├── sunsetTime: Date? (optional)
-├── sunriseFormatted: String (computed)
-└── sunsetFormatted: String (computed)
-```
-
-**Constructor signature:**
-```swift
-init(date: Date, sunriseTime: Date? = nil, sunsetTime: Date? = nil) {
- self.date = date
- self.sunriseTime = sunriseTime
- self.sunsetTime = sunsetTime
- self.bg = "smallbg"
-}
-```
-
-The formatted properties extract HH:mm in device local time.
-
-### Timeline Provider Logic
-
-Update `Provider.getTimeline()`:
-
-1. Get user's cached location from `AppGroupManager` (widget must use App Group storage, not LocationManager)
-2. Check if location is recent (less than 24 hours old); if stale/unavailable → create entry with nil sunrise/sunset
-3. Initialize local `SunTimes(latitude, longitude, date: currentDate)` calculator
-4. Extract sunrise and sunset times (already converted to device local time)
-5. Create entry with times: `SolvervEntry(def: SolvervDef(date: currentDate, sunriseTime: sr, sunsetTime: ss))`
-6. Return timeline refreshing at next midnight
-
-**Note:** Widget runs in separate process and cannot access main app's LocationManager. Location MUST come from AppGroupManager storage, populated by the main app.
-
-### Data Calculation Flow
-
-```
-Location Permission Check
- ↓ (if granted)
-Get Cached Coordinates
- ↓
-SunTimes Calculator
- ├─ Input: latitude, longitude, date
- ├─ Output: sunrise Date, sunset Date
- └─ Fallback: nil (polar regions, edge cases)
- ↓
-Format as HH:mm
- ↓
-SolvervDef/Entry
- ↓
-Widget Display
-```
-
-### Error Handling
-
-| Scenario | Behavior |
-|----------|----------|
-| Location permission denied | No sunrise/sunset displayed |
-| Location unavailable in AppGroupManager | No sunrise/sunset displayed |
-| Location older than 24 hours | Treat as unavailable; no sunrise/sunset displayed |
-| SunTimes returns nil (polar regions, 24h sun/darkness) | No sunrise/sunset displayed |
-
-### Timezone Handling
-
-- `SunTimes` internally converts to device's local timezone (uses `TimeZone.current`)
-- Sunrise/sunset `Date` objects are in device local time
-- Formatted display uses device local time (HH:mm)
-- **Edge case:** If user changes timezone between midnight and next refresh, sun times remain valid for the previous timezone. They will update correctly at next midnight refresh (when new location may have different timezone). This is acceptable since widget only refreshes daily.
-
-## Implementation Details
-
-### Changes to SolvervDef
-
-Add properties:
-- `sunriseTime: Date?` — raw sunrise time in device local timezone
-- `sunsetTime: Date?` — raw sunset time in device local timezone
-
-Add computed properties for formatted times:
-- `sunriseFormatted: String` — returns "HH:mm" format or empty string if nil
-- `sunsetFormatted: String` — returns "HH:mm" format or empty string if nil
-
-Computed properties use device locale and 24-hour format.
-
-### Changes to Provider
-
-Modify `getTimeline()` to:
-1. Fetch location from `AppGroupManager.getUserLocation()` — widget runs in separate process and must use App Group storage
-2. Check timestamp: if location is older than 24 hours, treat as unavailable
-3. If available, instantiate sunrise/sunset calculator: `SunTimes(latitude: lat, longitude: lon, date: currentDate)` from `/Solverv/Utilities/SunTimes.swift`
-4. Call `calculator.sunrise()` and `calculator.sunset()` to get Date objects
-5. Pass times to `SolvervDef(date: currentDate, sunriseTime: sr, sunsetTime: ss)`
-6. Create entry and return timeline refreshing at next midnight (unchanged)
-
-### Widget View Updates
-
-**SmallWidgetView:**
-- If sunrise/sunset available: display as "↑ HH:mm ↓ HH:mm" or similar compact format
-- If unavailable: display nothing (no placeholder or empty space)
-
-**MediumWidgetView:**
-- If sunrise/sunset available: display sunrise and sunset times in HH:mm format (exact layout TBD based on available space)
-- If unavailable: display nothing (no placeholder or empty space)
-
-Both views:
-- Access times via `entry.def.sunriseFormatted` and `entry.def.sunsetFormatted`
-- Only render if both are non-empty strings
-
-## Timeline & Refresh
-
-- **Frequency:** Once per day at midnight
-- **Rationale:** Sun times change gradually; daily updates are sufficient
-- **Alignment:** Matches existing solstice countdown refresh policy
-
-## Testing
-
-- Test with various locations (tropics, temperate, near poles)
-- Test without location permission
-- Test at date boundaries (midnight refresh)
-- Verify formatted times display correctly
-- Verify nil times result in no display
-
-## Future Considerations
-
-- Visualization of sunrise/sunset (charts, gradients)
-- Daylight duration calculation and trend
-- Multiple locations support
-- Custom location override
-