craft-native 0.0.204, iOS 26.5 simulator, an app with renderer: "native" whose screens are compiled .stx (no web view for the screen itself).
What happens
Calling craft.dialog.actionSheet({ title, actions }) from a native screen draws a centred rounded card with inset pill buttons, not a bottom-anchored UIAlertController. No Cancel is visible on it.
The two screenshots below are the same app: the first is an actionSheet with three actions (Choose photo, Remove photo, and a { style: 'cancel' }), the second a confirmAction({ destructive: true }), which routes to actionSheet as well.
Both render as a floating card in the middle of the screen. A real .actionSheet would be pinned to the bottom, full width, with Cancel in its own separated group.
Why I think it is the fallback
CraftNativeUI.swift is correct — CraftDialogs.actionSheet builds a UIAlertController(preferredStyle: .actionSheet) and explicitly guarantees a way out:
if !hasCancel {
sheet.addAction(UIAlertAction(title: CraftPresenter.systemString("Cancel"), style: .cancel) { _ in answer(nil) })
}So what is on screen is not that code. The compiled native bundle contains the JS DOM renderer instead — native-dialog-panel, native-dialog-group, native-dialog-backdrop — reached through
dialog.actionSheet = ($) => bridge("dialog.actionSheet", [$], () => domSheet($), { fallbackOnError: true })fallbackOnError: true means a failing bridge call is silent: the DOM renderer runs and the caller gets a plausible-looking dialog. On a native screen there is no page to style it, which is why it looks like a generic card.
What I would expect
dialog.actionSheet/dialog.confirm/dialog.alertreach the Swift implementation on a native screen.- If the bridge genuinely is unavailable there, the fallback should say so rather than silently rendering an unstyled substitute —
fallbackOnErrorhides the real failure.
Why it matters beyond looks
The sheet is where destructive confirmations live. In our app "Remove your photo?" renders with its destructive action and no visible Cancel, so the only obvious way out of a destructive prompt is to perform it. The Swift path would not have that problem; the fallback is what produces it.
Happy to dig further or test a patch.