<!-- skill: iOS Widgets | version: 1.0.0 | description: WidgetKit timelines, Live Activities and App Intents for iOS | category: Mobile -->
## Instructions
- Model widget data with `TimelineEntry`: `struct ScoreEntry: TimelineEntry { let date: Date; let home: Int; let away: Int }`; provider computes entries, view renders SwiftUI only (no UIKit gestures).
- Implement `TimelineProvider`: `placeholder(in:)` (single static), `getSnapshot(in:completion:)` (widget gallery preview), `getTimeline(in:completion:)` returning `Timeline(entries:policy:)`.
- Choose reload policy deliberately: `.atEnd` for schedules, `.after(date)` for game end, `.never` + `WidgetCenter.shared.reloadAllTimelines()` for push-driven updates.
- Support families: `@main struct ScoreWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind:kind, provider:provider()) { ScoreView(entry:) }.supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular]) } }`.
- Intent-driven config: use `AppIntentTimelineProvider` + `struct TeamSelect: AppIntent { @Parameter var team: String }` so users pick team per widget; provide `defaultTeam` + `summary`.
- Live Activities: define `struct ScoreAttributes: ActivityAttributes { struct ContentState: Codable { var score: String }; var gameId: String }`; start with `Activity<ScoreAttributes>.request(attributes:contentState:pushType:)`; update/end from app or push.
- Push-to-start/update Live Activity: enable `NSSupportsLiveActivities` + push type `.token`; send `apns-topic: <bundle>.push-type.liveactivity`, handle `activityTokenUpdates` to ship tokens to server.
- App Intents for actions: `struct RefreshScore: AppIntent { static var title: LocalizedStringResource = "Refresh"; func perform() async throws -> some IntentResult { await reload(); return .result() } }`; expose in widget button via `Button(intent:)`.
- Shared data: `AppGroup` (`group.com.app.scores`) + `UserDefaults(suiteName:)` or shared Core Data; widget extension + app both read; keep payloads < 4KB for timeline.
- Deep link from widget: `Link(destination: URL(string: "myapp://game/42")!) { ScoreView }` or `.widgetURL(_:)`; handle in `onOpenURL` in app scene.
- Lock Screen + StandBy: add `.accessoryRectangular/.accessoryInline` families with monochrome-safe views; test StandBy night mode contrast.
- Test: `WidgetCenter` reload in simulator, Timeline debug with `getTimeline` logging, Live Activity via `xcrun simctl push <device> <bundle> activity.apns`; verify battery (no timers < 5min).
## Examples
- Score widget: timeline every 15 min during game, `.after(gameEnd)` final entry, per-team intent config, tap opens game detail.
- Delivery Live Activity: order states (preparing→courier→delivered) pushed from server, end on delivery, lock-screen accessory view.
- Habit widget: `AppIntent` toggle habit directly from medium widget, `reloadAllTimelines` after toggle.
## Workflows
1. Add Widget Extension target → App Group + intents entitlements.
2. Build TimelineProvider + families + placeholder/snapshot.
3. Add intent config (AppIntent params) → test per-widget customization.
4. Live Activity attributes + start/update/end + push token plumbing.
5. Deep links (`widgetURL`/Link) → `onOpenURL` routing test.
6. Simulator + device test (StandBy, lock screen, reload policy, battery).
## Templates
- Timeline widget:
```swift
import WidgetKit; import SwiftUI
struct ScoreEntry: TimelineEntry { let date: Date; let home: Int; let away: Int }
struct ScoreProvider: TimelineProvider {
  func placeholder(in c: Context) -> ScoreEntry { ScoreEntry(date: .now, home: 0, away: 0) }
  func getSnapshot(in c: Context, completion: @escaping (ScoreEntry) -> Void) {
    completion(ScoreEntry(date: .now, home: 2, away: 1))
  }
  func getTimeline(in c: Context, completion: @escaping (Timeline<ScoreEntry>) -> Void) {
    let store = UserDefaults(suiteName: "group.com.app.scores")
    let h = store?.integer(forKey: "home") ?? 0, a = store?.integer(forKey: "away") ?? 0
    let entries = (0..<5).map { ScoreEntry(date: .now.addingTimeInterval(Double($0*900)), home: h, away: a) }
    completion(Timeline(entries: entries, policy: .atEnd))
  }
}
struct ScoreView: View {
  var entry: ScoreEntry
  var body: some View {
    Link(destination: URL(string: "myapp://game/live")!) {
      VStack { Text("\(entry.home) - \(entry.away)").font(.title2.bold()); Text(entry.date, style: .time).font(.caption) }
    }
  }
}
@main struct ScoreWidget: Widget {
  var body: some WidgetConfiguration {
    StaticConfiguration(kind: "ScoreWidget", provider: ScoreProvider()) { ScoreView(entry: $0) }
      .configurationDisplayName("Live Score").description("Follow the game.")
      .supportedFamilies([.systemSmall, .systemMedium, .accessoryRectangular])
  }
}
```
- Live Activity + intent:
```swift
import ActivityKit
struct ScoreAttributes: ActivityAttributes {
  struct ContentState: Codable, Hashable { var score: String }
  var gameId: String
}
func startLive(gameId: String) throws {
  let attrs = ScoreAttributes(gameId: gameId)
  let state = ScoreAttributes.ContentState(score: "0 - 0")
  _ = try Activity<ScoreAttributes>.request(attributes: attrs, contentState: ActivityContent(state: state, staleDate: nil), pushType: .token)
}
struct RefreshScore: AppIntent {
  static var title: LocalizedStringResource = "Refresh score"
  func perform() async throws -> some IntentResult {
    WidgetCenter.shared.reloadAllTimelines()
    return .result()
  }
}
```
## Tools
- SARAH shell_run for `xcodebuild -scheme WidgetExtension`, `simctl push` Live Activity payloads.
- SARAH file_write for provider/view/intent scaffolds; web_fetch for WidgetKit + ActivityKit docs.
## Advanced
- Relevance: `TimelineEntryRelevance(score:)` to boost key games on Smart Stack; `getSnapshots` locales for gallery.
- Push budgets: Live Activity updates max ~60/hour via push, throttle server-side; end stale activities within 8h or system dismisses.
- Privacy: never render PII in accessory views (visible on lock screen); redact behind `isLocked` privacy blur with `.privacySensitive()`.
## Documentation
- https://developer.apple.com/documentation/widgetkit
- https://developer.apple.com/documentation/activitykit
- https://developer.apple.com/documentation/appintents
- https://developer.apple.com/documentation/widgetkit/widgetcenter
