ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-07-04Advanced

When AppEnum Breaks in App Intents — Designing EntityQuery so Siri Can Pick From a Catalog That Grows Every Day

Writing an App Intents parameter with AppEnum works fine while the options are fixed, but it cannot survive content that grows daily. Here is the AppEntity + EntityQuery design that lets Siri and Shortcuts correctly pick from a dynamic catalog, including identifier stability and Spotlight pitfalls.

antigravity413app-intents3siri3ios36swift9app-entity

Premium Article

When I asked Antigravity to "add an App Intent to my wallpaper app so a collection can be opened by name," the Swift it returned ran cleanly. It listed a few representative collection names in an AppEnum and offered them as parameter options. Testing in the Shortcuts app, the suggestions appeared. Had I been satisfied and shipped it there, it would have quietly broken a few weeks later.

The problem is that the collections in the wallpaper apps I run as an indie developer grow and shrink almost daily. AppEnum is a type that assumes its options are fixed at compile time; it cannot represent a catalog that changes at runtime. The generated code wasn't bad — the prompt simply never carried the fact that this catalog is dynamic. Here I'll walk through the exact conditions under which AppEnum breaks, and the move to AppEntity + EntityQuery, all the way down to identifier stability.

AppEnum Is a Fixed Menu, Not a Catalog

App Intents gives you two main ways to offer parameter options: AppEnum and AppEntity. They produce similar-looking code, but they assume completely different worlds.

AspectAppEnumAppEntity
How options are decidedFixed at compile timeResolved by a query at runtime
Good forFinite settings: sort order, theme, qualityData that grows: articles, collections, products
CountA handful to a dozenHundreds to thousands
SearchNo (present all only)Filter by string match
Spotlight exposureNoYes, via IndexedEntity

So a fixed value like "wallpaper quality (standard / high / max)" is a correct fit for AppEnum, while a growing target like "the user's collections" should use AppEntity. Get this line wrong early and it will pass your tests while the catalog is small, then degrade silently in production. The code I first received was exactly this mix-up.

The deciding question is simple: can these options grow without waiting for the next app update? If yes, it's AppEntity.

AppEntity Is the Data; EntityQuery Is How to Find It

An AppEntity is the type that represents one item you expose to Siri and Shortcuts. One collection in the catalog looks like this.

import AppIntents
 
struct WallpaperCollection: AppEntity {
    // Stable identifier: use the data's persistent ID, not an array index
    let id: String
    let name: String
    let itemCount: Int
 
    static var typeDisplayRepresentation: TypeDisplayRepresentation {
        TypeDisplayRepresentation(name: "Wallpaper Collection")
    }
 
    // How a single item looks in Siri / Shortcuts
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(name)",
            subtitle: "\(itemCount) images"
        )
    }
 
    static var defaultQuery = WallpaperCollectionQuery()
}

On its own, AppEntity has no idea how to find one item. That job belongs to EntityQuery. This is the biggest difference from AppEnum: most of the implementation weight sits on the query side.

struct WallpaperCollectionQuery: EntityQuery {
    // (1) Rebuild entities from identifiers. Called every time a saved shortcut re-resolves.
    func entities(for identifiers: [String]) async throws -> [WallpaperCollection] {
        let all = await CollectionStore.shared.load()
        let map = Dictionary(uniqueKeysWithValues: all.map { ($0.id, $0) })
        return identifiers.compactMap { map[$0] }
    }
 
    // (2) The first candidates shown in the parameter editor
    func suggestedEntities() async throws -> [WallpaperCollection] {
        let all = await CollectionStore.shared.load()
        // Don't return everything — just the most recently used
        return Array(all.sorted { $0.lastUsedAt > $1.lastUsedAt }.prefix(10))
    }
}

Do not treat entities(for:) lightly. It runs every time a user later executes a shortcut they saved earlier. If you can't rebuild the entity from its identifier here, the saved shortcut breaks with "not found." AppEnum has no concept of this rebuild, which is precisely why using it for dynamic data undermines the durability of saved shortcuts.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Understand exactly when AppEnum fails to represent a dynamic catalog, so you can decide with confidence when to move to AppEntity
Implement EntityQuery's entities(for:) / suggestedEntities() / entities(matching:) by role, so both Siri and Shortcuts can search and present your catalog
Avoid the silent breakage of using an array index as an identifier, and land on a design with stable IDs and Spotlight integration that actually holds up in production
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-07-03
Before You Let Siri Run an Agent-Written App Intent — Classify by Side Effect and Gate the Destructive Ones
Letting Siri or an assistant run an Antigravity-generated App Intent without a gate means a destructive action can fire from a single voice command. Here is how I classify intents by side effect, gate the irreversible ones, and catch missing gates before push.
App Dev2026-04-20
Building App Intents with Antigravity: Siri and Shortcuts Integration in Swift
A practical guide to implementing App Intents in iOS apps using Antigravity. Learn how to integrate Siri and the Shortcuts app, plus the pitfalls to watch for when working with Antigravity-generated Swift code.
App Dev2026-06-15
Stop Adding a Ternary Every Time a New iPhone Ships: A Table-Driven Resolution Design
Every time a new resolution arrived—iPhone Air, 17 Pro—I was bolting another screen-size ternary onto a 29-branch pile. Here is how I reshaped that into a table-driven design where adding the next device is a one-line data change, with the actual Swift from my wallpaper apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →