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
US web checkout via the StoreKit External Purchase Link entitlement — currently 0% Apple commission (litigation ongoing), how to ship it safely, and how to architect for a commission flip so a future ruling is a config change, not a rewrite. Use when adding external purchase links, weighing web checkout vs IAP, or planning US-storefront pricing strategy.
tools
Revenue beyond the single-app price tag — own-app bundles, Family Sharing as a conversion lever, cross-developer bundles & suites, and institutional licensing via Group Purchases / Apple School & Business Manager. Use when a developer has multiple apps, a subscription worth sharing, complementary indie partners, or school/clinic/business buyers.
testing
Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.
tools
Stage-by-stage audit of an app's App Store growth machinery against a 54-item P0–P9 playbook — every item scored from an App Store Connect MCP call, a codebase check, or an explicit question to the user, then routed to the skill or command that fixes it. Read-only on App Store Connect. Use for a growth audit or scorecard, a pre-launch growth plan, a quarterly re-audit, or "which growth levers am I missing."