summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-03-23-solstice-widget-design.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs/2026-03-23-solstice-widget-design.md')
-rw-r--r--docs/superpowers/specs/2026-03-23-solstice-widget-design.md307
1 files changed, 0 insertions, 307 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