skills/generators/whats-new/SKILL.md
Generates a "What's New" / changelog screen shown after app updates with version tracking, feature highlights, and one-time display per version. Use when user wants release notes UI, update notifications, or feature announcements.
npx skillsauth add rshankras/claude-code-apple-skills whats-newInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate a complete "What's New" screen that displays after app updates — highlights new features, changes, and improvements with version tracking to ensure it only shows once per update.
Use this skill when the user:
Bundle.main.infoDictionary, custom build config, etc.)Search for existing what's new or changelog implementations:
Glob: **/*WhatsNew*.swift, **/*Changelog*.swift, **/*ReleaseNotes*.swift, **/*VersionTracker*.swift
Grep: "WhatsNew" or "whatsNew" or "lastShownVersion" or "changelog"
If found, ask user:
Detect how the app reads its version:
Grep: "CFBundleShortVersionString" or "Bundle.main.infoDictionary" or "appVersion"
Use whichever pattern the project already employs. If none found, default to Bundle.main.infoDictionary?["CFBundleShortVersionString"].
Ask user via AskUserQuestion:
Presentation style?
.sheet(item:) auto-dismiss).fullScreenCover)Content source?
Dismiss behavior?
Page indicators?
Read templates.md for production Swift code.
Generate these files:
WhatsNewFeature.swift — Model for a single feature (title, description, SF Symbol, tint color)WhatsNewRelease.swift — Groups features by version string with dateVersionTracker.swift — Tracks last-shown version in UserDefaults, compares against current bundle versionWhatsNewProvider.swift — Protocol + local implementation (optional remote)WhatsNewView.swift — Paged view with TabView(.page) showing featuresWhatsNewSheet.swift — Wrapper that auto-presents via .sheet(item:) when new version detectedBased on configuration:
RemoteWhatsNewProvider.swift — If remote content source selectedWhatsNewJSON.swift — If local JSON source selectedCheck project structure:
Sources/ exists -> Sources/WhatsNew/App/ exists -> App/WhatsNew/WhatsNew/After generation, provide:
WhatsNew/
├── WhatsNewFeature.swift # Single feature model
├── WhatsNewRelease.swift # Version-grouped features
├── VersionTracker.swift # Version persistence & comparison
├── WhatsNewProvider.swift # Content provider protocol + local impl
├── WhatsNewView.swift # Paged feature display
├── WhatsNewSheet.swift # Auto-presenting sheet wrapper
└── RemoteWhatsNewProvider.swift # Remote content (optional)
Option 1: View Modifier Style (Recommended)
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.whatsNewSheet() // Automatically shows after updates
}
}
}
Option 2: Manual Control in Root View
@main
struct MyApp: App {
@State private var whatsNewRelease: WhatsNewRelease?
var body: some Scene {
WindowGroup {
ContentView()
.sheet(item: $whatsNewRelease) { release in
WhatsNewView(release: release)
}
.task {
let tracker = VersionTracker()
if tracker.shouldShowWhatsNew() {
whatsNewRelease = WhatsNewProvider.local.latestRelease()
}
}
}
}
}
Option 3: Inline Embedding
struct HomeView: View {
@State private var tracker = VersionTracker()
var body: some View {
VStack {
if tracker.shouldShowWhatsNew(),
let release = WhatsNewProvider.local.latestRelease() {
WhatsNewView(release: release) {
tracker.markVersionAsShown()
}
}
// Rest of home content...
}
}
}
// In LocalWhatsNewProvider.swift — add a new entry per release
static let releases: [WhatsNewRelease] = [
WhatsNewRelease(
version: "2.1.0",
date: Date(timeIntervalSince1970: 1_700_000_000),
features: [
WhatsNewFeature(
title: "Dark Mode Support",
description: "Full dark mode across all screens.",
systemImage: "moon.fill",
tintColor: .indigo
),
WhatsNewFeature(
title: "Faster Search",
description: "Search results now appear instantly.",
systemImage: "magnifyingglass",
tintColor: .orange
),
]
),
// Previous releases...
]
@Test
func showsWhatsNewForNewVersion() {
let defaults = UserDefaults(suiteName: "TestWhatsNew")!
defaults.removePersistentDomain(forName: "TestWhatsNew")
let tracker = VersionTracker(
defaults: defaults,
currentVersion: "2.0.0"
)
// First launch — no previous version stored
#expect(tracker.shouldShowWhatsNew() == true)
tracker.markVersionAsShown()
#expect(tracker.shouldShowWhatsNew() == false)
}
@Test
func skipsWhatsNewWhenVersionUnchanged() {
let defaults = UserDefaults(suiteName: "TestWhatsNew2")!
defaults.removePersistentDomain(forName: "TestWhatsNew2")
let tracker = VersionTracker(
defaults: defaults,
currentVersion: "1.5.0"
)
tracker.markVersionAsShown()
// Same version — should not show
let tracker2 = VersionTracker(
defaults: defaults,
currentVersion: "1.5.0"
)
#expect(tracker2.shouldShowWhatsNew() == false)
}
@Test
func showsWhatsNewAfterUpdate() {
let defaults = UserDefaults(suiteName: "TestWhatsNew3")!
defaults.removePersistentDomain(forName: "TestWhatsNew3")
let tracker = VersionTracker(
defaults: defaults,
currentVersion: "1.0.0"
)
tracker.markVersionAsShown()
// Simulate update
let trackerAfterUpdate = VersionTracker(
defaults: defaults,
currentVersion: "2.0.0"
)
#expect(trackerAfterUpdate.shouldShowWhatsNew() == true)
}
Group features by release version so older users who skipped updates still see relevant changes:
// Show features for all versions newer than lastShownVersion
func featuresSinceLastShown() -> [WhatsNewFeature] {
let lastShown = tracker.lastShownVersion ?? "0.0.0"
return releases
.filter { $0.version.compare(lastShown, options: .numeric) == .orderedDescending }
.flatMap(\.features)
}
Only show if there are actually features to display for the current version:
if let release = provider.release(for: currentVersion),
tracker.shouldShowWhatsNew() {
// Present What's New
}
Fetch features from a server to update without app releases:
let provider = RemoteWhatsNewProvider(
endpoint: URL(string: "https://api.example.com/whats-new")!,
fallback: LocalWhatsNewProvider() // Offline fallback
)
let release = try await provider.latestRelease()
CFBundleShortVersionString = marketing version (e.g., "2.1.0") — use this for What's NewCFBundleVersion = build number (e.g., "47") — changes every build, NOT suitable for What's New trackinglastShownVersion is nillastShownVersion is nil, either skip What's New or set the current version without showing:func shouldShowWhatsNew() -> Bool {
guard let lastShown = lastShownVersion else {
// First install — mark current version, don't show
markVersionAsShown()
return false
}
return currentVersion.compare(lastShown, options: .numeric) == .orderedDescending
}
.numeric option in String.compare(_:options:) so "2.0.0" > "1.10.0" (not lexicographic)< or > operators directly — "9.0" would sort after "10.0" lexicographically.accessibilityElement(children: .combine) on feature cardsgenerators/onboarding-generator — First-launch onboarding (complementary — onboarding for first install, What's New for updates)development
Build, install, and launch an iOS app on a physical iPhone or iPad entirely from the command line (no Xcode GUI), using xcodebuild + devicectl. Use when the user wants to run, test, or screenshot their app on a real device without opening Xcode.
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.
development
Build, install, launch, and screenshot an iOS app in the Simulator to verify a change visually. Use when the user wants to run the app, see a change live, screenshot the running app, or confirm a UI fix actually works (not just that it compiles).
development
Audits skills in this repo for consistency, API drift, and structural gaps. Produces a prioritized report grouped by severity (Critical/High/Medium/Low). Use when asked to "audit skills", "check the skill repo for drift", or when planning bulk skill cleanup. Read-only — does not apply fixes.