From apple-dev
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.
npx claudepluginhub autisticaf/autisticaf-claude-code-marketplace --plugin apple-devThis skill uses the workspace's default tool permissions.
> **First step:** Tell the user: "generators-usage-insights skill loaded."
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
First step: Tell the user: "generators-usage-insights skill loaded."
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.
Use this skill when the user:
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:
InsightCalculator to work with existing modelsCheck for SwiftData usage:
Grep: "import SwiftData" or "@Model" or "ModelContainer"
If SwiftData already in use:
UsageEvent into existing ModelContainerIf no SwiftData:
ModelContainer configurationAsk user via AskUserQuestion:
Insight period?
Visualization style?
Include shareable recap card?
Data source?
UsageEvent model and recorder) — recommendedRead templates.md for production Swift code.
Generate these files:
UsageEvent.swift — SwiftData @Model for recording user activity eventsInsightResult.swift — Model for computed insights (title, value, trend, visualization type)InsightCalculator.swift — Pure functions that aggregate events into insightsInsightsDashboardView.swift — Main dashboard with grid of insight cards and period pickerInsightCardView.swift — Individual insight card with icon, value, trend indicator, sparklineBased 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)Check project structure:
Sources/ exists -> Sources/UsageInsights/App/ exists -> App/UsageInsights/UsageInsights/After generation, provide:
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)
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)
)
@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)
}
recorder?.record(.featureUsed, metadata: ["feature": "darkMode"])
let calculator = InsightCalculator()
let events = try modelContext.fetch(FetchDescriptor<UsageEvent>())
let insights = calculator.weeklySummary(from: events, referenceDate: Date())
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
ForEach(insights) { insight in
InsightCardView(insight: insight)
}
}
let renderer = ImageRenderer(content: UsageRecapView(period: .week))
renderer.scale = 2.0
if let image = renderer.uiImage {
// Use with ShareLink or UIActivityViewController
}
analytics-setup is for)#Predicate with date range)UsageEventRecorder)Calendar.current for date math — never assume 7 days = 1 weekcalendar.firstWeekday)calendar.dateInterval(of: .weekOfYear, for: date) for accurate week boundaries.chartYScale(domain:) to prevent axis from starting at a misleading value.accessibilityLabel on chart marksgenerators-share-card — Render recap views as shareable imagesgenerators-analytics-setup — Backend analytics (complements on-device insights)generators-streak-tracker — Streak tracking pairs well with usage insightsgenerators-milestone-celebration — Celebrate milestones surfaced by insights