ReviewOS

craft-native/craft

No Shortcuts module: an all-native app cannot register home-screen quick actions, so registerAppShortcuts can only answer 'unsupported'

#412
Closed glennmichael123 opened this 8 days ago ·
8 days ago

An all-native app cannot register home-screen quick actions. There is no Shortcuts module on either side of the bridge, so the three surfaces an App Shortcut is supposed to have are two.

What is missing

  • CraftNativeActions.swift (0.0.293) has no "Shortcuts" case — nothing calls UIApplication.shared.shortcutItems.
  • stx's native bridge exposes no craft.shortcuts, so a screen or app script has nothing to call.
  • Craft generates App Intents from appShortcuts in the mobile config, and those work: Spotlight and Siri both surface them, and Metadata.appintents ships in the bundle. It is only the long-press menu on the icon that has no path.

The app script does receive craftShortcut and craftSiriShortcut events and routes them correctly, so the listening half is in place — #127 covered that. What has no implementation is the registering half.

How this shows up

Our shortcut list lives in one module and is fed to three surfaces, with a composable that registers the home-screen items:

const api = craftBridge()?.shortcuts
if (!api?.set)
  return 'unsupported'
return await settled(api.set(craftShortcutItems())) === null ? 'failed' : 'registered'

The guard is correct and the answer is honest — unsupported, every time, in an all-native build. craftShortcutItems() is dead code on that path, and the comment above it describing the home-screen menu as one of three surfaces is wrong in a way nothing can catch.

There is no UIApplicationShortcutItems in the generated Info.plist either, so the static route is not an alternative.

Why we care

The quick actions are a real entry point, and the one most likely to be reached by somebody who is not already in the app. We found a gate leak through the equivalent deep link — a signed-out viewer taking "Trails Near Me" landed on a trail list they should not have seen — and could not write a device test for the menu itself, because the menu does not exist. That check is now a hole in our iOS UI suite with a comment explaining why.

What would help

Either a Shortcuts module (set / clear, mapping to UIApplication.shared.shortcutItems) exposed as craft.shortcuts, or generating UIApplicationShortcutItems into Info.plist from the same appShortcuts config that already produces the App Intents. The second is less flexible — no badge counts, no runtime changes — but it would close the gap with no new bridge surface.

Versions

craft-native 0.0.293, stx 0.2.663, iOS 26.5 simulator and device.

7 days ago

Could this be reopened? The fix in 17e09f4d looks right, but as far as I can tell it never reached main, so it isn't in any published release yet. Happy to be told I'm looking in the wrong place.

The commit isn't an ancestor of main:

$ gh api repos/craft-native/craft/compare/main...17e09f4db6 --jq '{status, ahead: .ahead_by, behind: .behind_by}'
{"status":"diverged","ahead":4,"behind":287}

No branch has it as head and no PR references it (/commits/17e09f4db6/branches-where-head and /commits/17e09f4db6/pulls both come back empty).

main has no shortcut handling in that file at all — not a renamed or reworked version, zero occurrences:

$ gh api repos/craft-native/craft/contents/packages/ios/templates/CraftNativeScreen.swift \
    --jq '.content' | base64 -d | grep -ic shortcut
0

Its keep() list is still the same six modules as before the fix:

keep('storage',       …)
keep('snapshots',     …)
keep('secureStorage', …)
keep('app',           …)
keep('session',       …)
keep('navigation',    …)

And the published package matches main, not the fix. craft-native@0.0.358 was published 2026-10-26, three days after the commit landed on 2026-10-23, and its dist/ios/templates/CraftNativeScreen.swift has no keep('shortcuts', …) either.

So from a consumer's side the original gap is unchanged: in an all-native build craft.shortcuts is still undefined, and registerAppShortcuts still answers unsupported.

For what it's worth the approach in the commit is exactly what I was hoping for — keeping the typed module present so the answer is an honest unsupported rather than a fall-through to an undefined namespace, with the comment that dynamic quick actions belong to the app shell rather than an individual JavaScriptCore screen. It just needs to get onto main.

If it did ship and I'm checking the wrong artifact, pointing me at the release that contains it would sort me out.

7 days ago

Reopening: the fix exists in 17e09f4d but that commit is not on main and is not in any published release, so the gap is still live for consumers.

7 days ago

Implemented and pushed to main in 0dd6c64 and be0f625 (five-file runtime commit, three-file contract/docs commit).

This is real iOS native quick-action registration, not an unsupported stub: craft.shortcuts.set(items) and clear() are available in native screens and nativeApp. Calls update UIApplication.shared.shortcutItems on main through the existing capability protocol. Registration validates the complete list before replacement (up to four unique, non-empty types/titles, valid optional metadata), and cancelled queued calls cannot overwrite the app-wide menu. Older STX/browser-shaped namespace assignments cannot replace the native set/clear methods.

The existing craftShortcut { type } event continues through the app handler; registration does not bypass app routing or authorization checks. Configured appShortcuts and Siri/Spotlight intents remain separate. Android native support is not claimed by this change.

Validation: five simulator-hosted tests passed on iOS 26.5, exercising the actual UIKit menu, invalid-input atomicity, queued cancellation, JavaScript promise calls from screens and app scripts, and cold/warm event delivery to the app handler exactly once. These are native/API and event-queue tests, not an end-to-end SpringBoard tap claim. The first simulator attempt failed before testing because its installation directory was missing; all five passed on a fresh disposable simulator. Also green: 443 builder/renderer-contract tests, 796 SDK tests (8 expected skips), 397 release tests, typecheck, package build/verification, formatting, and lint (0 errors).

Leaving open pending latest-commit CI and publication. v0.0.359 is published but predates this fix; it is not the version to use for this feature.

8 days ago

Fixed in commit 17e09f4. All-native screens now expose the typed craft.shortcuts namespace, with dynamic set/clear calls resolving false (unsupported) instead of falling through to an undefined module. Static appShortcuts remain configured at the app shell. Added generated-source coverage and documented the contract.

6 days ago

Shipped in v0.0.360, release commit a5465af.

Verified exact-source CI (40374785563), Mobile E2E (40374785445, successful second attempt), and the complete publication workflow. This includes native asset verification, npm publication, and Pantry publication. Both craft-native@0.0.360 and @craft-native/ios@0.0.360 are available in the public npm registry; the published iOS archive was inspected for the implementation.

The published native screen template exposes craft.shortcuts.set/clear; the implementation provides iOS home-screen quick-action registration and the existing craftShortcut event delivery. Closing for the verified iOS scope. Android native quick-action support is not claimed, nor is an automated end-to-end SpringBoard tap test.

Sign in to comment on this issue.