From platform-conventions
Use when writing, editing, or reviewing Apple-platform code — iOS, macOS, watchOS, Swift, SwiftUI, Xcode projects, or Apple frameworks (SwiftData, GRDB, CloudKit, Photos, Foundation Models, Liquid Glass).
How this skill is triggered — by the user, by Claude, or both
Slash command
/platform-conventions:appleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
An opinionated house style for Apple-platform code. Load alongside the public Apple stack — Axiom plugin (`axiom-*` skills · `/axiom:*` · `xclog`/`xcsym`/`xcui` CLI), XcodeBuildMCP/`xcodebuildmcp-cli`, sosumi MCP (Apple docs). These conventions are the judgment layer the public tooling does not carry.
An opinionated house style for Apple-platform code. Load alongside the public Apple stack — Axiom plugin (axiom-* skills · /axiom:* · xclog/xcsym/xcui CLI), XcodeBuildMCP/xcodebuildmcp-cli, sosumi MCP (Apple docs). These conventions are the judgment layer the public tooling does not carry.
@Observable only. No ObservableObject, no @Published.Sendable.Core stance: push every cross-cutting concern to one seam, modularize for the compiler, and treat Combine / @escaping completion handlers / ObservableObject / UIKit-for-new-screens / Core-Data-for-greenfield / XCTest as legacy bridges to retire, not patterns to extend. Keep what's load-bearing (a working CloudKit-synced store with tested migrations); modernize what's greenfield. Make wrong states fail at launch/in tests, never in prod.
Sources/ + Tests/. Folder-only "modules" inside one target enforce nothing.swiftSettings + upcoming-feature flags) and migrate module-by-module, not the whole app at once — keep adoption visible and reversible per module.NSColor/UIColor, NSView/UIView, …) to shared aliases, so cross-platform code is written once against the alias — a single #if os() there replaces thousands at call sites.any A & B), never concretes.UserDefaults.standard.string(forKey:), and typed event factories instead of inline analytics strings.@preconcurrency import for legacy SDKs; nonisolated(unsafe) / @unchecked Sendable only where an invariant is hand-proven (a type that internally serializes access). Reach for an actor or an immutable Sendable value first.ObservableObject + @Published + @StateObject → @Observable macro + plain @State.AnyPublisher, Set<AnyCancellable>) → async/await, AsyncStream/AsyncSequence, Observation.@escaping completion handlers in new APIs → async funcs; bridge legacy callbacks with withCheckedContinuation.NavigationStack (value-based routes); keep a VC router only as a legacy bridge.#if os() sprinkled through feature code → the platform-type-alias seam + Shared/iOS/macOS folder split.NSPersistentCloudKitContainer only where a synced store + migration history already exist — don't rewrite a working synced store for novelty.@Suite/@Test/#expect/#require, .serialized, @MainActor); migrate older suites opportunistically.A repeated string literal drifts silently; a value already in a config has two sources of truth; a hardcoded value blocks white-label reuse. So:
UserDefaults keys, notification names) → one static let in a single config type. Never a bare literal at the call site.CKContainer.default() (resolves the first id in the entitlement). Never CKContainer(identifier: "iCloud.…").Bundle.main / Info.plist (Bundle.main.bundleIdentifier, infoDictionary), fed by .xcconfig..xcconfig + Info.plist, surfaced through one typed AppConfig accessor — no #if DEBUG string forks scattered in code.Base rule (all platforms): no hardcoded color, font, number, or radius at a call site — always a named token; if none fits, add one. A view recipe seen a 2nd time → shared component in UI/Components/.
Apple names: color AppColor.* · font AppFont.* · spacing/size AppSpacing.* · radius AppShape.* · icons SFIcon.*.
AppSpacing.* steps only — a numeric literal at a call site is a violation (.padding(32), .frame(width: 240), cornerRadius(8)). No step fits → add one.SFIcon.* — never Image(systemName:) at a call site; use SFIcon.xxx.rawValue for systemImage:. Repeated render recipe (.symbolRenderingMode, .foregroundStyle, .font) → a factory method on SFIcon (e.g. .closeButtonView(...)), one line at the site.ViewModifier + View extension.session_set_defaults before first build in a session.useLatestOS true (running/UI only on explicit request).#if os() in view bodies)#if os() or #if canImport() inside a var body or any view builder = mandatory rewrite before proceeding.#if os(macOS) wraps the entire file — use for platform-exclusive features.ViewModifier: one modifier owns the #if; the call site contains no conditional — e.g. .pageTabStyle(animating:).View extension: func iOSOnly() -> some View with the #if inside the extension body.#if branches — factor the shared block out first.#if os(), #if canImport()) that appears more than once → extract to a named modifier, extension, or type immediately.NSMetadataItem) must be converted to Sendable Swift value types before entering any Task { } closure.WKRunsIndependentlyOfCompanionApp = YES): installs/updates on its own, separate App Store product. Use when the watch app is fully useful without the phone.WKRunsIndependentlyOfCompanionApp = NO): bundled inside the iOS app and shipped through the single iOS record (TestFlight/App Store), so it reaches testers/users with the phone build. Requires: watch bundle id = <iOS-id>.watchkitapp, WKCompanionAppBundleIdentifier = the iOS bundle id, and the iOS target embedding it via a target dependency + "Embed Watch Content" copy-files phase. If the iOS target is multiplatform (SDKROOT=auto), that phase and the dependency must be platformFilter = ios or the macOS build fails trying to embed a watchOS app.CKDatabase directly for read-only CloudKit access.PHAsset and Photos framework are absent on watchOS; guard all Photos API behind #if canImport(Photos).backgroundTask(.appRefresh) only — no persistent background processes.npx claudepluginhub romacv/romacv-claude-skills --plugin platform-conventionsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.