Agent skill
usage-insights
Generates user-facing usage statistics, activity summaries, and personalized insights dashboards (weekly recaps, year-in-review, Spotify Wrapped-style). Use when user wants to show usage stats, activity insights, or shareable recap screens. Different from analytics-setup which sends data to a backend — this shows insights to the USER on-device.
Install this agent skill to your Project
npx add-skill https://github.com/rshankras/claude-code-apple-skills/tree/main/skills/generators/usage-insights
SKILL.md
Usage Insights Generator
Generate a production usage insights system that records user activity events with SwiftData, computes personalized insights (streaks, most active day, top categories), and displays them in a dashboard with insight cards, period pickers, trend indicators, and optional shareable recap screens.
When This Skill Activates
Use this skill when the user:
- Asks to "show usage statistics" or "add usage stats"
- Wants "user insights" or "activity insights"
- Mentions "activity summary" or "weekly summary"
- Asks about a "usage dashboard" or "insights dashboard"
- Wants a "weekly recap" or "monthly recap"
- Mentions "year in review" or "year-in-review"
- Asks for "Spotify Wrapped style" or "Wrapped-style recap"
- Wants to "show the user their activity" or "personalized stats"
Pre-Generation Checks
1. Project Context Detection
- Check Swift version (requires Swift 5.9+)
- Check deployment target (iOS 17+ / macOS 14+ for @Observable and SwiftData)
- Identify source file locations
- Check for Swift Charts availability (iOS 16+ / macOS 13+, but recommend iOS 17+)
2. Conflict Detection
Search for existing usage tracking or insights code:
Glob: **/*UsageEvent*.swift, **/*Insight*.swift, **/*Recap*.swift, **/*ActivityLog*.swift
Grep: "UsageEvent" or "InsightCalculator" or "activitySummary" or "SwiftData" and "event"
If existing analytics/tracking found:
- Ask if user wants to build insights on top of existing event data
- If yes, adapt
InsightCalculatorto work with existing models - If no, generate fresh event recording alongside existing code
3. Data Layer Detection
Check for SwiftData usage:
Grep: "import SwiftData" or "@Model" or "ModelContainer"
If SwiftData already in use:
- Integrate
UsageEventinto existingModelContainer - Use existing schema migration strategy
If no SwiftData:
- Generate full setup including
ModelContainerconfiguration
Configuration Questions
Ask user via AskUserQuestion:
-
Insight period?
- Daily (today's activity breakdown)
- Weekly (7-day recap with day-by-day comparison) — recommended
- Monthly (30-day trends with weekly rollups)
- Yearly (year-in-review with monthly highlights)
- All of the above (period picker lets user switch)
-
Visualization style?
- Cards only (simple stat cards with trend indicators)
- Charts only (Swift Charts bar/line graphs)
- Both cards and charts — recommended
-
Include shareable recap card?
- Yes (generates a recap view that can be rendered to an image and shared)
- No (dashboard only, no sharing)
-
Data source?
- SwiftData (generate
UsageEventmodel and recorder) — recommended - Custom (user provides their own event data; generate calculator and views only)
- SwiftData (generate
Generation Process
Step 1: Read Templates
Read templates.md for production Swift code.
Step 2: Create Core Data Files
Generate these files:
UsageEvent.swift— SwiftData@Modelfor recording user activity eventsInsightResult.swift— Model for computed insights (title, value, trend, visualization type)
Step 3: Create Calculation Engine
InsightCalculator.swift— Pure functions that aggregate events into insights
Step 4: Create UI Files
InsightsDashboardView.swift— Main dashboard with grid of insight cards and period pickerInsightCardView.swift— Individual insight card with icon, value, trend indicator, sparkline
Step 5: Create Optional Files
Based on configuration:
UsageRecapView.swift— If shareable recap selected (paged summary with share card generation)UsageEventRecorder.swift— If SwiftData data source selected (convenience class for recording events)
Step 6: Determine File Location
Check project structure:
- If
Sources/exists ->Sources/UsageInsights/ - If
App/exists ->App/UsageInsights/ - Otherwise ->
UsageInsights/
Output Format
After generation, provide:
Files Created
UsageInsights/
├── UsageEvent.swift # SwiftData @Model for activity events
├── InsightResult.swift # Computed insight model
├── InsightCalculator.swift # Aggregation engine
├── InsightsDashboardView.swift # Dashboard with period picker
├── InsightCardView.swift # Individual insight card
├── UsageRecapView.swift # Shareable recap (optional)
└── UsageEventRecorder.swift # Event recording helper (optional)
Integration Steps
Add ModelContainer (if not already present):
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [UsageEvent.self])
}
}
Record events from anywhere in the app:
struct TaskDetailView: View {
@Environment(\.modelContext) private var modelContext
@State private var recorder: UsageEventRecorder?
var body: some View {
Button("Complete Task") {
completeTask()
recorder?.record(
.taskCompleted,
metadata: ["category": "work", "priority": "high"]
)
}
.onAppear {
recorder = UsageEventRecorder(modelContext: modelContext)
}
}
}
Show the insights dashboard:
NavigationLink("My Insights") {
InsightsDashboardView()
}
Show a weekly recap:
struct WeeklyRecapSheet: View {
@Environment(\.modelContext) private var modelContext
@State private var showRecap = false
var body: some View {
Button("View Weekly Recap") {
showRecap = true
}
.sheet(isPresented: $showRecap) {
UsageRecapView(period: .week)
}
}
}
Record events with duration tracking:
// Start a timed session
let sessionStart = Date()
// ... user does work ...
// Record when session ends
recorder?.record(
.sessionCompleted,
metadata: ["screen": "editor"],
duration: Date().timeIntervalSince(sessionStart)
)
Testing
@Test
func calculatesWeeklySummaryCorrectly() async throws {
let calendar = Calendar.current
let now = Date()
let events: [UsageEvent] = (0..<7).flatMap { dayOffset in
let date = calendar.date(byAdding: .day, value: -dayOffset, to: now)!
return (0..<(dayOffset == 2 ? 5 : 2)).map { _ in
UsageEvent(eventType: "taskCompleted", timestamp: date)
}
}
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: events, referenceDate: now)
let totalInsight = insights.first { $0.title == "Total Events" }
#expect(totalInsight != nil)
#expect(totalInsight?.value == "19") // 5 + (6 * 2)
}
@Test
func identifiesMostActiveDay() async throws {
let calendar = Calendar.current
let now = Date()
// Create 5 events on Wednesday, 2 on other days
var events: [UsageEvent] = []
for dayOffset in 0..<7 {
let date = calendar.date(byAdding: .day, value: -dayOffset, to: now)!
let count = calendar.component(.weekday, from: date) == 4 ? 5 : 1
for _ in 0..<count {
events.append(UsageEvent(eventType: "action", timestamp: date))
}
}
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: events, referenceDate: now)
let mostActive = insights.first { $0.title == "Most Active Day" }
#expect(mostActive?.value == "Wednesday")
}
@Test
func handlesEmptyEventList() async throws {
let calculator = InsightCalculator()
let insights = calculator.weeklySummary(from: [], referenceDate: Date())
#expect(!insights.isEmpty) // Should still return cards with zero values
let totalInsight = insights.first { $0.title == "Total Events" }
#expect(totalInsight?.value == "0")
}
@Test
func recorderBatchesWritesForPerformance() async throws {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: UsageEvent.self, configurations: config)
let context = ModelContext(container)
let recorder = UsageEventRecorder(modelContext: context)
// Record 100 events rapidly
for i in 0..<100 {
recorder.record(.custom("event_\(i)"))
}
// Flush pending writes
recorder.flush()
let descriptor = FetchDescriptor<UsageEvent>()
let count = try context.fetchCount(descriptor)
#expect(count == 100)
}
Common Patterns
Record an Event
recorder?.record(.featureUsed, metadata: ["feature": "darkMode"])
Calculate a Weekly Summary
let calculator = InsightCalculator()
let events = try modelContext.fetch(FetchDescriptor<UsageEvent>())
let insights = calculator.weeklySummary(from: events, referenceDate: Date())
Render Insight Cards in a Grid
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
ForEach(insights) { insight in
InsightCardView(insight: insight)
}
}
Generate and Share a Recap Image
let renderer = ImageRenderer(content: UsageRecapView(period: .week))
renderer.scale = 2.0
if let image = renderer.uiImage {
// Use with ShareLink or UIActivityViewController
}
Gotchas
Privacy: All Data Stays On-Device
- Usage events are stored in SwiftData on the user's device only
- Never transmit usage data to a server (that's what
analytics-setupis for) - Clearly communicate to users that insights are computed locally
- Consider adding a "Delete My Data" option in settings
Performance with Large Datasets
- Fetch only the events needed for the current period (use
#Predicatewith date range) - For yearly summaries, pre-aggregate monthly totals instead of scanning all events
- Batch event recording to avoid excessive SwiftData writes (use
UsageEventRecorder) - Set a retention policy — auto-delete events older than 1-2 years
Calendar-Based Period Calculations
- Always use
Calendar.currentfor date math — never assume 7 days = 1 week - Respect the user's first-day-of-week setting (
calendar.firstWeekday) - Use
calendar.dateInterval(of: .weekOfYear, for: date)for accurate week boundaries - Handle timezone changes gracefully — store timestamps in UTC, display in local time
Swift Charts Considerations
- Swift Charts is iOS 16+ / macOS 13+, but works best with iOS 17+ for latest features
- Keep chart data points reasonable (7 for weekly, 30 for monthly, 12 for yearly)
- Use
.chartYScale(domain:)to prevent axis from starting at a misleading value - Provide VoiceOver descriptions with
.accessibilityLabelon chart marks
Trend Comparison Edge Cases
- First week of use has no "previous period" — show "New!" instead of a trend arrow
- Handle periods with zero events gracefully (don't divide by zero for averages)
- Percentage changes can be misleading for small numbers (1 -> 2 = +100%)
References
- templates.md — All production Swift templates
- Related:
generators/share-card— Render recap views as shareable images - Related:
generators/analytics-setup— Backend analytics (complements on-device insights) - Related:
generators/streak-tracker— Streak tracking pairs well with usage insights - Related:
generators/milestone-celebration— Celebrate milestones surfaced by insights
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
legal
Legal document generation and compliance guidance for indie Apple developers. Covers privacy policies, terms of service, EULAs, GDPR/CCPA/DPDP compliance, and Apple App Store legal requirements. Use when user needs legal documents or compliance guidance.
privacy-policy
Generate privacy policies, terms of service, and EULAs for Apple platform apps. Detects data collection patterns, third-party SDKs, and generates region-specific legal documents with Apple Privacy Nutrition Label mapping. Use when user needs legal documents or data collection disclosure for App Store submission.
ios-development
Comprehensive iOS development guidance including Swift best practices, SwiftUI patterns, UI/UX review against HIG, and app planning. Use for iOS code review, best practices, accessibility audits, or planning new iOS apps.
assistive-access
Assistive Access implementation for cognitive accessibility including simplified scenes, navigation icons, runtime detection, and design principles. Use when optimizing apps for Assistive Access mode.
ui-review
Review SwiftUI code for iOS/watchOS Human Interface Guidelines compliance, font usage, Dynamic Type support, and accessibility. Use when user mentions UI review, HIG, accessibility audit, font checks, or wants to verify interface design against Apple standards.
navigation-patterns
SwiftUI navigation architecture patterns including NavigationStack, NavigationSplitView, TabView, programmatic navigation, and custom transitions. Use when reviewing or building navigation, fixing navigation bugs, or architecting app flow.
Didn't find tool you were looking for?