Token导航 LogoToken导航

Agent Skill · 前端设计

axiom-extensions-widgets(axiom 扩展小部件)

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。它适合让 Agent 梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时,应先确认最小权限、脱敏方式和操作边界。

axiom-extensions-widgets 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 1,694。

总安装 4,296周安装 179GitHub Stars 873下载 1,432核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-extensions-widgets(axiom 扩展小部件)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-extensions-widgets
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-extensions-widgets
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-extensions-widgets

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查,适合梳理敏感配置和分析鉴权逻辑。适用于 Widgets、Live Activities、App Intents 等扩展开发场景,强调沙盒限制与系统调度机制。

通过 GitHub 仓库安装,使用 npx skills add 命令添加指定技能,需结合原始 README 进一步确认具体用法。使用时不能将工具输出直接当作最终结论,涉及密钥或用户数据时应先确认最小权限和操作边界。

axiom-extensions-widgets 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/axiom-extensions-widgets

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-extensions-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axiom-extensions-widgets 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-extensions-widgets。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-extensions-widgets 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-extensions-widgets 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axiom-extensions-widgets 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-extensions-widgets 用的人多吗?

公开数据是累计安装 4,296 次,近一周安装 179 次,下载 1,432 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-extensions-widgets 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
axiom-extensions-widgets

axiom 扩展小部件 · 本页

4,296873需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Extensions & Widgets — Discipline

Core Philosophy

"Widgets are not mini apps. They're glanceable views into your app's data, rendered at strategic moments and displayed by the system. Extensions run in sandboxed environments with limited memory and execution time."

Mental model: Think of widgets as archived snapshots on a timeline, not live views. Your widget doesn't "run" continuously — it renders, gets archived, and the system displays the snapshot.

Extension sandboxing: Extensions have:

  • Limited memory (~30MB)
  • No network access in widget views (fetch in TimelineProvider only)
  • Separate bundle container from main app
  • Require App Groups for data sharing

When to Use This Skill

✅ Use this skill when:

  • Implementing any widget (Home Screen, Lock Screen, StandBy, Control Center)
  • Creating Live Activities
  • Debugging why widgets show stale data
  • Widget not appearing in gallery
  • Interactive buttons not responding
  • Live Activity fails to start
  • Control Center control is unresponsive
  • Sharing data between app and widget/extension

❌ Do NOT use this skill for:

  • Pure App Intents implementation (use app-intents-ref)
  • SwiftUI layout questions (use swiftui-layout)
  • Performance profiling (use swiftui-performance)
  • General debugging (use xcode-debugging)

Related Skills

  • extensions-widgets-ref — Comprehensive API reference
  • app-intents-ref — App Intents for interactive widgets
  • swift-concurrency — Async patterns for data fetching
  • swiftdata — Using SwiftData with App Groups

Example Prompts

1. "My widget isn't updating"

→ This skill covers timeline policies, refresh budgets, manual reload, and App Groups configuration

2. "How do I share data between app and widget?"

→ This skill explains App Groups entitlement, shared UserDefaults, and container URLs

3. "Widget shows old data even after I update the app"

→ This skill covers container paths, UserDefaults suite names, and WidgetCenter reload

4. "Live Activity fails to start"

→ This skill covers 4KB data limit, ActivityAttributes constraints, authorization checks

5. "Control Center control takes forever to respond"

→ This skill covers async ValueProvider patterns and optimistic UI

6. "Interactive widget button does nothing"

→ This skill covers App Intent perform() implementation and WidgetCenter reload


Red Flags / Anti-Patterns

Pattern 1: Network Calls in Widget View

Time cost: 2-4 hours debugging why widgets are blank or show errors

Symptom

  • Widget renders but shows no data
  • Console errors: "NSURLSession not available in widget extension"
  • Widget appears blank intermittently

❌ BAD Code

struct MyWidgetView: View {
    @State private var data: String?

    var body: some View {
        VStack {
            if let data = data {
                Text(data)
            }
        }
        .onAppear {
            // ❌ WRONG — Network in widget view
            Task {
                let (data, _) = try await URLSession.shared.data(from: apiURL)
                self.data = String(data: data, encoding: .utf8)
            }
        }
    }
}

Why it fails: Widget views are rendered, archived, and reused. Network calls in views are unreliable and may not execute.

✅ GOOD Code

// Main app — prefetch and save
func updateWidgetData() async {
    let data = try await fetchFromAPI()
    let shared = UserDefaults(suiteName: "group.com.myapp")!
    shared.set(data, forKey: "widgetData")

    WidgetCenter.shared.reloadAllTimelines()
}

// Widget TimelineProvider — read from shared storage
struct Provider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let shared = UserDefaults(suiteName: "group.com.myapp")!
        let data = shared.string(forKey: "widgetData") ?? "No data"

        let entry = SimpleEntry(date: Date(), data: data)
        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }
}

Pattern: Fetch data in main app, save to shared storage, read in widget.

Can TimelineProvider make network requests?

Yes, but with important caveats:

struct Provider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        Task {
            // ✅ Network requests ARE allowed here
            let data = try await fetchFromAPI()
            let entry = SimpleEntry(date: Date(), data: data)
            completion(Timeline(entries: [entry], policy: .atEnd))
        }
    }
}

Constraints:

  • 30-second timeout - System kills extension if getTimeline() doesn't complete
  • No background sessions - Can't download large files
  • Battery cost - Every timeline reload uses battery
  • Not guaranteed - May fail on poor connections

Best practice: Prefetch in main app (faster, more reliable), use TimelineProvider network as fallback only.


Pattern 2: Missing App Groups

Time cost: 1-2 hours debugging why widget shows empty/default data

Symptom

  • Widget always shows placeholder or default values
  • Changes in main app don't reflect in widget
  • UserDefaults reads return nil in widget

❌ BAD Code

// Main app
UserDefaults.standard.set("Updated", forKey: "myKey")

// Widget extension
let value = UserDefaults.standard.string(forKey: "myKey") // Returns nil!

Why it fails: UserDefaults.standard accesses different containers in app vs. extension.

✅ GOOD Code

// 1. Enable App Groups entitlement in BOTH targets:
//    - Main app target: Signing & Capabilities → + App Groups → "group.com.myapp"
//    - Widget extension target: Same group identifier

// 2. Main app
let shared = UserDefaults(suiteName: "group.com.myapp")!
shared.set("Updated", forKey: "myKey")

// 3. Widget extension
let shared = UserDefaults(suiteName: "group.com.myapp")!
let value = shared.string(forKey: "myKey") // Returns "Updated"

Verification:

let containerURL = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "group.com.myapp"
)
print("Shared container: \(containerURL?.path ?? "MISSING")")
// Should print path, not "MISSING"

Pattern 3: Over-Refreshing (Budget Exhaustion)

Time cost: Poor user experience, battery drain, widgets stop updating

Symptom

  • Widget updates frequently at first, then stops
  • Console logs: "Timeline reload budget exhausted"
  • Widget becomes stale after a few hours

❌ BAD Code

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    var entries: [SimpleEntry] = []

    // ❌ WRONG — 60 entries at 1-minute intervals
    for minuteOffset in 0..<60 {
        let date = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: Date())!
        entries.append(SimpleEntry(date: date, data: "Data"))
    }

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

Why it's bad: System gives 40-70 reloads/day. This approach uses 24 reloads/hour → exhausts budget in 2-3 hours.

✅ GOOD Code

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    var entries: [SimpleEntry] = []

    // ✅ CORRECT — 8 entries at 15-minute intervals (2 hours coverage)
    for offset in 0..<8 {
        let date = Calendar.current.date(byAdding: .minute, value: offset * 15, to: Date())!
        entries.append(SimpleEntry(date: date, data: getData()))
    }

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

Guidelines:

  • 15-60 minute intervals for most widgets
  • 5-15 minutes for time-sensitive data (stocks, sports)
  • Use .atEnd policy for automatic reload
  • Let system decide optimal refresh based on user engagement

Pattern 4: Blocking Main Thread in Controls

Time cost: Control Center control unresponsive, poor UX

Symptom

  • Tapping control in Control Center shows spinner for seconds
  • Control seems "stuck" or frozen
  • No immediate visual feedback

❌ BAD Code

struct ThermostatControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "Thermostat") {
            ControlWidgetButton(action: GetTemperatureIntent()) {
                // ❌ WRONG — Synchronous fetch blocks UI
                let temp = HomeManager.shared.currentTemperature() // Blocking call
                Label("\(temp)°", systemImage: "thermometer")
            }
        }
    }
}

Why it's bad: Button renders on main thread. Blocking network/database calls freeze UI.

✅ GOOD Code

struct ThermostatProvider: ControlValueProvider {
    func currentValue() async throws -> ThermostatValue {
        // ✅ CORRECT — Async fetch, non-blocking
        let temp = try await HomeManager.shared.fetchTemperature()
        return ThermostatValue(temperature: temp)
    }

    var previewValue: ThermostatValue {
        ThermostatValue(temperature: 72) // Instant fallback
    }
}

struct ThermostatValue: ControlValueProviderValue {
    var temperature: Int
}

struct ThermostatControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "Thermostat", provider: ThermostatProvider()) { value in
            ControlWidgetButton(action: AdjustTemperatureIntent()) {
                Label("\(value.temperature)°", systemImage: "thermometer")
            }
        }
    }
}

Pattern: Use ControlValueProvider for async data, provide instant previewValue fallback.


Pattern 5: Missing Dismissal Policy (Zombie Live Activities)

Time cost: User annoyance, negative reviews

Symptom

  • Live Activities stay on Lock Screen for hours after event ends
  • Users must manually dismiss completed activities
  • Activity shows "Delivered" but won't disappear

❌ BAD Code

// Start activity
let activity = try Activity.request(attributes: attributes, content: initialContent)

// Later... event completes
// ❌ WRONG — Never call .end()
// Activity stays forever until user dismisses

Why it's bad: Activities persist indefinitely unless explicitly ended.

✅ GOOD Code

// When event completes
let finalState = DeliveryAttributes.ContentState(
    status: .delivered,
    deliveredAt: Date()
)

await activity.end(
    ActivityContent(state: finalState, staleDate: nil),
    dismissalPolicy: .default // Removes after ~4 hours
)

// Or for immediate removal
await activity.end(nil, dismissalPolicy: .immediate)

// Or remove at specific time
let dismissTime = Date().addingTimeInterval(30 * 60) // 30 min
await activity.end(nil, dismissalPolicy: .after(dismissTime))

Best practices:

  • .immediate — Transient events (timer completed, song finished)
  • .default — Most activities (shows "completed" state for ~4 hours)
  • .after(date) — Specific end time (meeting ends, flight lands)

Pattern 6: Exceeding 4KB Data Limit (Live Activities)

Time cost: Activity fails to start silently, hard to debug

Symptom

  • Activity.request() throws error
  • Console: "Activity attributes exceed size limit"
  • Activity never appears on Lock Screen

❌ BAD Code

struct GameAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var teamALogo: Data // ❌ Large image data
        var teamBLogo: Data
        var playByPlay: [String] // ❌ Unbounded array
        var statistics: [String: Any] // ❌ Large dictionary
    }

    var gameID: String
    var venueName: String
}

// Fails if total size > 4KB
let activity = try Activity.request(attributes: attrs, content: content)

Why it fails: ActivityAttributes + ContentState combined must be < 4KB.

✅ GOOD Code

struct GameAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var teamAScore: Int // ✅ Small primitives
        var teamBScore: Int
        var quarter: Int
        var timeRemaining: String // "2:34"
        var lastPlay: String? // Single most recent play
    }

    var gameID: String // ✅ Reference, not full data
    var teamAName: String
    var teamBName: String
}

// Use asset catalog for images in view
struct GameLiveActivityView: View {
    var context: ActivityViewContext<GameAttributes>

    var body: some View {
        HStack {
            Image(context.attributes.teamAName) // Asset catalog
            Text("\(context.state.teamAScore)")
            // ...
        }
    }
}

Strategies:

  • Store IDs/references, not full objects
  • Use asset catalogs for images (not embedded Data)
  • Keep ContentState minimal (only changeable data)
  • Use computed properties in views for derived data

Size Targets (Safety Margins)

Hard limit: 4096 bytes (4KB)

Target guidance:

  • ✅ < 2KB: Safe with room to grow - recommended for v1.0
  • ⚠️ 2-3KB: Acceptable but monitor closely as you add features
  • 🔴 3.5KB+: Risky - future fields may push you over limit

Why safety margins matter: You'll add fields later (new features, more data). Starting at 3.8KB leaves zero room for growth.

Checking size:

let attributes = GameAttributes(gameID: "123", teamAName: "Hawks", teamBName: "Eagles")
let state = GameAttributes.ContentState(teamAScore: 14, teamBScore: 10, quarter: 2, timeRemaining: "5:23", lastPlay: nil)

let encoder = JSONEncoder()
if let attributesData = try? encoder.encode(attributes),
   let stateData = try? encoder.encode(state) {
    let totalSize = attributesData.count + stateData.count
    print("Total size: \(totalSize) bytes")

    if totalSize < 2048 {
        print("✅ Safe with room to grow")
    } else if totalSize < 3072 {
        print("⚠️ Acceptable but monitor")
    } else if totalSize < 3584 {
        print("🔴 Risky - optimize now")
    } else {
        print("❌ CRITICAL - will likely fail")
    }
}

Optimization priorities (when over 2KB):

  1. Replace String descriptions with enums (if fixed set)
  2. Shorten string values ("Team A" → "A")
  3. Use smaller types (Int → Int8 if range allows)
  4. Remove optional fields that are rarely used

Pattern 7: Widget Not Appearing in Gallery

Time cost: 30 minutes debugging invisible widget

Symptom

  • Widget builds successfully
  • No errors in console
  • Widget doesn't appear in widget picker/gallery
  • Can't add to Home Screen

❌ BAD Code

@main
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
            MyWidgetView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("Shows data")
        // ❌ MISSING: supportedFamilies() — widget won't appear!
    }
}

Why it fails: Without supportedFamilies(), system doesn't know which sizes to offer.

✅ GOOD Code

@main
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
            MyWidgetView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("Shows data")
        .supportedFamilies([.systemSmall, .systemMedium]) // ✅ Required
    }
}

Other common causes:

  • Widget target's "Skip Install" set to YES (should be NO)
  • Widget extension not added to app's "Embed App Extensions"
  • Clean build folder needed (Cmd+Shift+K)

Decision Tree

Widget/Extension Issue?
│
├─ Widget not appearing in gallery?
│  ├─ Check WidgetBundle registered in @main
│  ├─ Verify supportedFamilies() includes intended families
│  └─ Clean build folder, restart Xcode
│
├─ Widget not refreshing?
│  ├─ Timeline policy set to .never?
│  │  └─ Change to .atEnd or .after(date)
│  ├─ Budget exhausted? (too frequent reloads)
│  │  └─ Increase interval between entries (15-60 min)
│  └─ Manual reload
│     └─ WidgetCenter.shared.reloadAllTimelines()
│
├─ Widget shows empty/old data?
│  ├─ App Groups configured in BOTH targets?
│  │  ├─ No → Add "App Groups" entitlement
│  │  └─ Yes → Verify same group ID
│  ├─ Using UserDefaults.standard?
│  │  └─ Change to UserDefaults(suiteName: "group.com.myapp")
│  └─ Shared container path correct?
│     └─ Print containerURL, verify not nil
│
├─ Interactive button not working?
│  ├─ App Intent perform() returns value?
│  │  └─ Must return IntentResult
│  ├─ perform() updates shared data?
│  │  └─ Update App Group storage
│  └─ Calls WidgetCenter.reloadTimelines()?
│     └─ Reload to reflect changes
│
├─ Live Activity fails to start?
│  ├─ Data size > 4KB?
│  │  └─ Reduce ActivityAttributes + ContentState
│  ├─ Authorization enabled?
│  │  └─ Check ActivityAuthorizationInfo().areActivitiesEnabled
│  └─ pushType correct?
│     └─ nil for local updates, .token for push
│
├─ Control Center control unresponsive?
│  ├─ Async operation blocking UI?
│  │  └─ Use ControlValueProvider with async currentValue()
│  └─ Provide previewValue for instant fallback
│
└─ watchOS Live Activity not showing?
   ├─ supplementalActivityFamilies includes .small?
   └─ Apple Watch paired and in range?

Mandatory First Steps

Before debugging any widget or extension issue, complete this checklist:

Widget Debugging Checklist

  • ☐ App Groups enabled in BOTH main app AND extension targets # Verify entitlements codesign -d --entitlements - /path/to/YourApp.app # Should show com.apple.security.application-groups
  • ☐ Widget in Widget Gallery (not just on Home Screen)

- Long-press Home Screen → + button → Find your widget - Verify it appears with correct name and description

  • ☐ Console logs for timeline errors # Xcode Console # Filter: "widget" OR "timeline" # Look for: "Timeline reload failed", "Budget exhausted"
  • ☐ Manual reload test WidgetCenter.shared.reloadAllTimelines()

- If this fixes it → problem is timeline policy or refresh budget

  • ☐ Shared container accessible let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.myapp") print("Container: \(container?.path?? "NIL")") // Must print valid path, not "NIL"

Live Activity Debugging Checklist

  • ☐ ActivityAttributes < 4KB let encoded = try JSONEncoder().encode(attributes) print("Size: \(encoded.count) bytes") // Must be < 4096
  • ☐ Authorization check let authInfo = ActivityAuthorizationInfo() print("Enabled: \(authInfo.areActivitiesEnabled)")
  • ☐ pushType matches server integration

- nil → local updates only - .token → expects push notifications

  • ☐ Dismissal policy implemented

- Every activity.end() must specify policy

Control Center Widget Checklist

  • ☐ ControlValueProvider for async data
  • ☐ previewValue provides instant fallback
  • ☐ App Intent perform() is async
  • ☐ No blocking network/database calls in views

Pressure Scenarios

Scenario 1: "Widget shows wrong data in production"

Situation

  • App released to App Store
  • Users report widget displaying incorrect/stale information
  • Works fine in development

Pressure Signals

  • 🚨 App Store reviews — 1-star reviews mentioning broken widget
  • ⏰ Time pressure — Need hotfix ASAP
  • 👔 Executive visibility — Management asking for status updates

Rationalization Traps (DO NOT)

  1. *"Just force a timeline reload more often"*

- Why it fails: Exhausts budget, makes problem worse

  1. *"The widget worked in testing"*

- Why it fails: Development vs. production App Groups mismatch

  1. *"Users should just restart their phone"*

- Why it fails: Not a fix, damages reputation

MANDATORY Systematic Fix

Step 1: Verify App Groups (30 min)
// Add logging to BOTH app and widget
let group = "group.com.myapp.production" // Must match exactly
let container = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: group
)

print("[\(Bundle.main.bundleIdentifier ?? "?")] Container: \(container?.path ?? "NIL")")

// Log EVERY read/write
let shared = UserDefaults(suiteName: group)!
print("Writing key 'lastUpdate' = \(Date())")
shared.set(Date(), forKey: "lastUpdate")

Verify: Run app, then widget. Both should print SAME container path.

Step 2: Check Container Paths
# Device logs (Xcode → Window → Devices and Simulators → View Device Logs)
# Filter: Your app bundle ID
# Look for: Container path mismatches

Common issues:

  • App uses group.com.myapp.dev
  • Widget uses group.com.myapp.production
  • Fix: Ensure EXACT same group ID in both.entitlements files
Step 3: Add Version Stamp
// Main app — stamp every write
struct WidgetData: Codable {
    var value: String
    var timestamp: Date
    var appVersion: String
}

let data = WidgetData(
    value: "Latest",
    timestamp: Date(),
    appVersion: Bundle.main.appVersion
)
shared.set(try JSONEncoder().encode(data), forKey: "widgetData")

// Widget — verify version
if let data = shared.data(forKey: "widgetData"),
   let decoded = try? JSONDecoder().decode(WidgetData.self, from: data) {
    print("Widget reading data from app version: \(decoded.appVersion)")
}
Step 4: Force Reload on App Launch
// AppDelegate / @main App
func applicationDidBecomeActive(_ application: UIApplication) {
    WidgetCenter.shared.reloadAllTimelines()
}

Communication Template

To stakeholders:

Status: Investigating widget data sync issue

Root cause: App Groups configuration mismatch between app and widget extension in production build

Fix: Updated both targets to use identical group identifier, added logging to prevent recurrence

Timeline: Hotfix submitted to App Store review (24-48h)

Workaround for users: Force-quit app and relaunch (triggers widget refresh)

Time Saved

  • Without systematic fix: 4-8 hours of trial-and-error, multiple resubmissions
  • With this process: 1-2 hours to identify, fix, and verify

Scenario 2: "Live Activity must update instantly"

Situation

  • Sports score app
  • Users expect scores to update within seconds of real game events
  • Current timeline-based approach too slow

Pressure

  • Competitive: "Other apps update faster"
  • Deadline: Marketing promised "real-time" updates

Rationalization Traps (DO NOT)

  1. *"Just create entries every 5 seconds"*

- Why it fails: Not real-time, exhausts battery, doesn't scale

  1. *"Add WebSocket to widget view"*

- Why it fails: Extensions can't maintain persistent connections

  1. *"Lower refresh interval to 1 second"*

- Why it fails: Timeline system not designed for sub-minute updates

MANDATORY Solution: Phased Approach

Critical reality check: Push notification entitlement approval takes 3-7 days. Never promise features before approval.

Phase 1: Ship with Local Updates (No Approval Required)

Ship immediately with app-driven updates:

// Start activity WITHOUT push (no entitlement needed)
let activity = try Activity.request(
    attributes: attributes,
    content: initialContent,
    pushType: nil  // Local updates only
)

// In your app when data changes (user opens app, pulls to refresh)
await activity.update(ActivityContent(
    state: updatedState,
    staleDate: nil
))

Set expectations: Updates occur when user interacts with app. This is acceptable for v1.0 and requires zero approval.

Phase 2: Add Push After Approval (3-7 Days)

After entitlement approved, switch to push:

Step 1: Enable Push for Live Activities
// 1. Entitlement: "com.apple.developer.activity-push-notification"

// 2. Request activity with push token
let activity = try Activity.request(
    attributes: attributes,
    content: initialContent,
    pushType: .token
)

// 3. Monitor for token
Task {
    for await pushToken in activity.pushTokenUpdates {
        let tokenString = pushToken.map { String(format: "%02x", $0) }.joined()
        await sendTokenToServer(activityID: activity.id, token: tokenString)
    }
}
Step 2: Server-Side Push (Phase 2 Only)
{
  "aps": {
    "timestamp": 1633046400,
    "event": "update",
    "content-state": {
      "teamAScore": 14,
      "teamBScore": 10,
      "quarter": 2,
      "timeRemaining": "5:23"
    },
    "alert": {
      "title": "Touchdown!",
      "body": "Team A scores"
    }
  }
}

Standard push limit: ~10-12 per hour

Step 3: Request Frequent Updates Entitlement (Phase 2, iOS 18.2+)

For apps requiring more frequent pushes (sports, stocks):

<key>com.apple.developer.activity-push-notification-frequent-updates</key>
<true/>

Requires justification in App Store Connect: "Live sports scores require immediate updates for user engagement"

Verification
// Log push receipt in Live Activity widget
#if DEBUG
let logURL = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "group.com.myapp"
)!.appendingPathComponent("push_log.txt")

let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
try! "\(timestamp): Received push\n".append(to: logURL)
#endif

Communication Template

To marketing/exec (Phase 1):

Launch Timeline:
- Phase 1 (immediate): Live Activities with app-driven updates. Updates appear when users open app or pull to refresh.
- Phase 2 (3-7 days): Push notification integration after Apple approval. Updates arrive within 1-3 seconds of server events.

Recommendation: Launch Phase 1 to market, communicate Phase 2 as "coming soon" once approved.

To marketing/exec (Phase 2):

"Real-time" positioning requires clarification:

Technical: Live Activities update via push notifications with 1-3 second latency from server to device

Constraints: Apple's push system has rate limits (~10/hour standard, axiom-higher with special entitlement)

Competitive analysis: Competitors likely use same system with similar limitations

Recommendation: Position as "near real-time" (accurate) vs "instant" (misleading)

Reality Check

  • Push notifications are fastest mechanism available
  • 1-3 second latency is normal
  • Budget limits exist for battery optimization
  • Users prefer longer battery life over millisecond-faster scores
For comprehensive push notification setup (APNs auth, payload format, token management, service extensions), see axiom-push-notifications and axiom-push-notifications-ref. This skill covers the ActivityKit UI and state management side.

Scenario 3: "Control Center control is slow"

Situation

  • Smart home control for lights
  • Tapping control in Control Center takes 3-5 seconds to respond
  • Users expect instant feedback

MANDATORY Fix: Optimistic UI + Async Value Provider

Problem Code
struct LightControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "Light") {
            ControlWidgetToggle(
                isOn: LightManager.shared.isOn, // ❌ Blocking fetch
                action: ToggleLightIntent()
            ) { isOn in
                Label(isOn ? "On" : "Off", systemImage: "lightbulb.fill")
            }
        }
    }
}
Fixed Code
// 1. Value Provider for async state
struct LightProvider: ControlValueProvider {
    func currentValue() async throws -> LightValue {
        // Async fetch from HomeKit/server
        let isOn = try await HomeManager.shared.fetchLightState()
        return LightValue(isOn: isOn)
    }

    var previewValue: LightValue {
        // Instant fallback from cache
        let shared = UserDefaults(suiteName: "group.com.myapp")!
        return LightValue(isOn: shared.bool(forKey: "lastKnownLightState"))
    }
}

struct LightValue: ControlValueProviderValue {
    var isOn: Bool
}

// 2. Optimistic Intent
struct ToggleLightIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle Light"

    func perform() async throws -> some IntentResult {
        // Immediately update cache (optimistic)
        let shared = UserDefaults(suiteName: "group.com.myapp")!
        let currentState = shared.bool(forKey: "lastKnownLightState")
        let newState = !currentState
        shared.set(newState, forKey: "lastKnownLightState")

        // Then update actual device (async)
        try await HomeManager.shared.setLight(isOn: newState)

        return .result()
    }
}

// 3. Control with provider
struct LightControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "Light", provider: LightProvider()) { value in
            ControlWidgetToggle(
                isOn: value.isOn,
                action: ToggleLightIntent()
            ) { isOn in
                Label(isOn ? "On" : "Off", systemImage: "lightbulb.fill")
                    .tint(isOn ? .yellow : .gray)
            }
        }
    }
}

Result: Control responds instantly with cached state, actual device updates in background.


Final Checklist

Before shipping widgets or Live Activities:

Pre-Release

  • ☐ App Groups entitlement in BOTH targets (app + extension)
  • ☐ Shared UserDefaults uses suiteName (not .standard)
  • ☐ Timeline entries ≥ 5 minutes apart (avoid budget exhaustion)
  • ☐ No network calls in widget views (only in TimelineProvider)
  • ☐ ActivityAttributes + ContentState < 4KB
  • ☐ Live Activities call .end() with appropriate dismissal policy
  • ☐ Control Center controls use ControlValueProvider for async data
  • ☐ Tested on actual device (not just simulator) — Required because:

- Simulator doesn't enforce timeline budget limits - Push notifications don't work in simulator - App Groups container paths differ (simulator vs device) - Memory limits not enforced in simulator - Background refresh behavior different

  • ☐ Tested all supported widget families
  • ☐ Verified widget appears in Widget Gallery

Post-Release Monitoring

  • ☐ Monitor for "Timeline reload budget exhausted" errors
  • ☐ Track widget data staleness in analytics
  • ☐ Watch App Store reviews for widget-related complaints
  • ☐ Log App Group container access for debugging

Common Failure Modes

  • Missing App Groups → Widget shows default data
  • Wrong group ID → App and widget can't communicate
  • Over-refreshing → Widget stops updating after hours
  • Network in view → Widget renders blank
  • No dismissal policy → Zombie Live Activities
  • Blocking main thread → Unresponsive controls

Remember: Widgets are NOT mini apps. They're glanceable snapshots rendered by the system. Extensions run in sandboxed environments with strict resource limits. Follow the patterns in this skill to avoid the most common pitfalls.


Resources

Skills: axiom-extensions-widgets-ref, axiom-push-notifications, axiom-push-notifications-ref, axiom-background-processing

平台分布

Claude Code

26.12%
按下载量换算374

OpenCode

23.5%
按下载量换算337

Codex

16.42%
按下载量换算235

Antigravity

11.75%
按下载量换算168

Cursor

8.41%
按下载量换算120

windsurf

3.67%
按下载量换算53

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills